MainWindow.fxml code from.PasswordManager.java.Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of the main components and how they interact with each other.
Main components of the architecture
Main (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI: The UI of the App.Logic: The command executor.Model: Holds the data of the App in memory.Storage: Reads data from, and writes data to, the hard disk.Commons represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command :rm -i 1.
Each of the four main components (also shown in the diagram above),
interface with the same name as the Component.{Component Name}Manager class (which follows the corresponding API interface mentioned in the previous point.For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI. Other than the MainWindow, we have created a PasswordPromptDialog that is used to prompt the user for a password when they try to access the app.
The UI component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml
The UI component,
Logic component.Model data so that the UI can be updated with the modified data.Logic component, because the UI relies on the Logic to execute commands.Model component, as it displays Person object residing in the Model.API : Logic.java
Here's a (partial) class diagram of the Logic component:
The sequence diagram below illustrates the interactions within the Logic component, taking execute(":rm -i 1") API call as an example.
Note: The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the Logic component works:
Logic is called upon to execute a command, it is passed to an AddressBookParser object which in turn creates a parser that matches the command (e.g., DeleteCommandParser) and uses it to parse the command.Command object (more precisely, an object of one of its subclasses e.g., DeleteCommand) which is executed by the LogicManager.Model when it is executed (e.g. to remove a person).Model) to achieve the result.CommandResult object which is returned back from Logic.Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
AddressBookParser class creates an XYZCommandParser (XYZ is a placeholder for the specific command name e.g., AddCommandParser) which uses the other classes shown above to parse the user command and create a XYZCommand object (e.g., AddCommand) which the AddressBookParser returns back as a Command object.XYZCommandParser classes (e.g., AddCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
The Model component,
Person objects (which are contained in a UniquePersonList object).Person objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.UserPref object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref objects.Model represents data entities of the domain, they should make sense on their own without depending on other components)Note: An alternative (arguably, a more OOP) model is given below. It has a Tag list in the AddressBook, which Person references. This allows AddressBook to only require one Tag object per unique tag, instead of each Person needing their own Tag objects.
API : Storage.java
The Storage component
AddressBookStorage and UserPrefStorage, which means it can be treated as either one (if only the functionality of only one is needed).Model component (because the Storage component's job is to save/retrieve objects that belong to the Model)Classes used by multiple components are in the seedu.address.commons package.
This section describes some noteworthy details on how certain features are implemented.
The undo/redo mechanism is facilitated by VersionedAddressBook. It extends AddressBook with an undo/redo history, stored internally as an addressBookStateList and currentStatePointer. Additionally, it implements the following operations:
VersionedAddressBook#commit() — Saves the current address book state in its history.VersionedAddressBook#undo() — Restores the previous address book state from its history.VersionedAddressBook#redo() — Restores a previously undone address book state from its history.These operations are exposed in the Model interface as Model#commitAddressBook(), Model#undoAddressBook() and Model#redoAddressBook() respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedAddressBook will be initialized with the initial address book state, and the currentStatePointer pointing to that single address book state.
Step 2. The user executes :rm -i 5 command to remove the 5th person in the address book. The :rm command calls Model#commitAddressBook(), causing the modified state of the address book after the :rm -i 5 command executes to be saved in the addressBookStateList, and the currentStatePointer is shifted to the newly inserted address book state.
Step 3. The user executes :add -n David to add a new person. The :add command also calls Model#commitAddressBook(), causing another modified address book state to be saved into the addressBookStateList.
Note: If a command fails its execution, it will not call Model#commitAddressBook(), so the address book state will not be saved into the addressBookStateList.
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the :undo command. The :undo command will call Model#undoAddressBook(), which will shift the currentStatePointer once to the left, pointing it to the previous address book state, and restores the address book to that state.
Note: If the currentStatePointer is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The :undo command uses Model#canUndoAddressBook() to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
The following sequence diagram shows how an undo operation goes through the Logic component:
Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how an undo operation goes through the Model component is shown below:
The :redo command does the opposite — it calls Model#redoAddressBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the address book to that state.
Note: If the currentStatePointer is at index addressBookStateList.size() - 1, pointing to the latest address book state, then there are no undone AddressBook states to restore. The :redo command uses Model#canRedoAddressBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command :list. Commands that do not modify the address book, such as :list, will usually not call Model#commitAddressBook(), Model#undoAddressBook() or Model#redoAddressBook(). Thus, the addressBookStateList remains unchanged.
Step 6. The user executes :clear, which calls Model#commitAddressBook(). Since the currentStatePointer is not pointing at the end of the addressBookStateList, all address book states after the currentStatePointer will be purged. Reason: It no longer makes sense to redo the :add -n David command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire address book.
Alternative 2: Individual command knows how to undo/redo by itself.
:remove, just save the person being removed).The remove feature allows removal of a person from the address book. The user can remove a person by specifying the index of the person to remove. The user can also remove multiple persons by specifying multiple indexes of the persons to remove.
How the remove feature works:
Format: :remove -i INDEX1, INDEX2...
The DeleteCommand class has a method DeleteCommand#execute(Model model) that calls the ModelManager.
The ModelManager class has a method ModelManager#deletePerson(Person target) that calls the AddressBook class.
The AddressBook class has a method AddressBook#removePerson(Person key) that removes a person from the UniquePersonList field persons in the AddressBook class.
The following class diagram shows the relationships between the classes involved in the remove feature:
The following sequence diagram shows how a remove operation goes through the Logic component:
Similarly, how a remove operation goes through the Model component is shown below:
The following activity diagram summarizes what happens when a user executes a new command:
Design considerations:
Aspect: How remove executes:
Alternative 1 (current choice): Removes the person by index.
Alternative 2: Removes the person by name.
The add feature allows a person to be added to the address book. It accepts the parameters name, phone, location, email, remark and tag. The name parameter is compulsory, while the rest are optional. Multiple tags are accepted for one person.
The add feature follows the remove feature in that AddCommand calls ModelManager, which calls the AddressBook
class, which adds a person to the UniquePersonList class. Therefore, the class and activity diagram will be omitted
for conciseness.
The following sequence diagram shows how an add operation goes through the Logic component:
The parsing process is described in detail in this sequence diagram:
Similar to the remove feature, how an add operation goes through the Model component is shown below:
The ExportCommand class is responsible for exporting address book data to a user-specified location in JSON format. It provides flexibility in its usage by allowing a destination to be selected via a file chooser or by setting a predetermined destination file, which is particularly useful for testing purposes. The data to be exported is encrypted, and the ExportCommand handles decryption, export location selection, and file I/O operations. The following outlines its components and workflow.
The ExportCommand class facilitates this export functionality and manages file I/O operations in a structured, asynchronous workflow.
Constructor Variants:
ExportCommand(): The default constructor for regular use, opening a file chooser dialog to select the export destination.ExportCommand(File destinationFile, File sourceFile, String keyPath): An overloaded constructor that allows specifying a destination file and encryption key path directly, which is particularly useful for testing.Attributes:
destinationFile: The file chosen or set as the target for the export.sourceFile: A temporary file that holds the JSON data to be exported.keyPath: The path to the decryption key required for decrypting the address book data.Given below is an example usage scenario and how the export process behaves at each step.
Step 1. The user initiates an export by executing :export. The ExportCommand will attempt to decrypt the data
before exporting it.
Step 2. The execute(Model model) method reads encrypted data from the sourceFile, decrypting it with
EncryptionManager.decrypt() using the provided keyPath. The decrypted data is written to a temporary file vbook.json.
Step 3. If destinationFile is not set, ExportCommand invokes chooseExportLocation(Stage stage), which displays
a file chooser dialog for the user to select an export location. If the user cancels this dialog, the export process
is aborted with an error message.
Step 4. The performExport(File sourceFile, File destinationFile) method copies the decrypted data to the specified destinationFile, using Files.copy() with StandardCopyOption.REPLACE_EXISTING to overwrite any existing file. The temporary file is then deleted.
Note: The performExport method is asynchronous, leveraging CompletableFuture to manage successful completion or error handling, ensuring smooth performance without blocking the main application thread.
The following sequence diagram explains how the export operation works:
Design Considerations:
Aspect: Export Execution and Destination Selection
Alternative 1 (current choice): Use a file chooser dialog to allow the user to select the export location.
Alternative 2: Set a default export location without user input.
The encryption mechanism is managed by the EncryptionManager class. This component is responsible for securely encrypting and decrypting sensitive data using the AES (Advanced Encryption Standard) algorithm. The EncryptionManager performs encryption and decryption with a secret key, which is securely loaded and stored using Java's Key Store API. The implementation details are as follows:
encrypt(String data, String keyPath):
vbook.jks if not provided).decrypt(byte[] data, String keyPath):
vbook.jks if not provided).generateKey(String keyPath):
vbook-encryption.
getKey(String keyPath):
generateKey() to create one.EncryptionManager is used in the ExportCommand to decrypt data before exporting it and in JsonAddressBookStorage to encrypt data before writing it to a file.// Encryption
String jsonData = JsonUtil.toJsonString(new JsonSerializableAddressBook(addressBook));
byte[] encryptedData = EncryptionManager.encrypt(jsonData, this.keyPath);
// Decryption
jsonData = EncryptionManager.decrypt(encryptedData, this.keyPath);
Step 1. The user initially adds a new contact in the address book. The EncryptionManager uses the AES algorithm and the secret key to encrypt the information before saving it.
Step 2. The encrypted data is stored securely. When needed, the user can request to decrypt the information.
Step 3. The EncryptionManager decrypts the data using the same AES algorithm and the secret key, ensuring that the information is securely handled at all times.
Note: If an error occurs during encryption or decryption (e.g., if the secret key is invalid or corrupted), the EncryptionManager will handle the error gracefully and return an appropriate error message.
The following sequence diagram shows how the encryption process works:
Note: The sequence diagram simplifies the encryption and decryption processes to focus on the main interactions between components.
Risk of Local KeyStore Exposure:
Alternative Storage Locations:
JAVA_HOME/lib/security/cacerts directory is an option, but this depends on the user’s configuration and permissions. Users might not have their JAVA_HOME path set correctly, which can lead to access issues.Security Through Obscurity:
The password management mechanism is handled by the PasswordManager class. This component is responsible for securely hashing and verifying user passwords using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm with HMAC-SHA1. The PasswordManager ensures that passwords are safely stored in a local text file, employing a salting strategy to enhance security. The implementation details are as follows:
readPassword(String path):
password.txt).null if the file does not exist.
savePassword(String password, String path):
password.txt).isPasswordCorrect(String inputPassword, String path):
true if they match or false otherwise.
hashPassword(String password, byte[] salt):
generateSalt():
SecureRandom for use in password hashing.PasswordManager is invoked during application startup to check for an existing password file.
// Saving a new password on initial startup
if (PasswordManager.readPassword(null) == null) {
String newPassword = scanner.nextLine();
PasswordManager.savePassword(newPassword, null);
}
// Verifying the password on subsequent starts
String inputPassword = scanner.nextLine();
if (PasswordManager.isPasswordCorrect(inputPassword, null)) {
// Handle correct password
} else {
// Handle wrong password
}
Step 1. On the initial startup of the application, the PasswordManager checks for the existence of the password.txt file. If the file is not found, the user is prompted to enter a new password.
Step 2. The entered password is hashed and saved securely, along with a generated salt.
Step 3. On subsequent startups, the user is prompted to input their original password. The PasswordManager verifies the input against the stored hash and salt, granting access if the password matches.
Note: If an error occurs during password hashing or verification (e.g., if the stored format is incorrect), the PasswordManager will handle the error gracefully and provide an appropriate error message.
The following activity diagram illustrates how the password management process operates:
Note: The activity diagram simplifies the password management processes to highlight the flow for user authentication.
password.txt file, they could potentially compromise user accounts. This poses a fundamental risk of local file storage, as the security of the password relies on the protection of the local file system.Target user profile:
Value proposition: manage contacts faster than a typical mouse/GUI driven app
Listed below are user stories for features that have already been implemented in the current version of the application.
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * * | first-time user | add contacts to my contact book | store my contacts |
* * * | user | add contacts to my contact book using only partial details | store contacts that I may not have full information about |
* * * | user | see all my contacts | see and manage my contacts |
* * * | user | remove contacts | remove contacts I do not need anymore |
* * | user | edit contact details | correct errors I made when adding a contact |
* * | first-time user | see sample contacts | explore the app's features without adding real data |
* * | first-time user | clear sample data and start fresh | input my real contacts securely |
* * | first-time user | view a tutorial on the app | learn how to use the app quickly |
* * | first-time user | quickly access a CLI command cheat sheet | learn essential commands without slowing down |
* * | new user | secure my contact data with a password | feel confident that my client information is protected |
* * | new user | choose to encrypt the contact data that is stored | ensure my client information cannot be accessed from the storage location |
* * | new and inexperienced user | undo actions like deletions CTRL+Z | recover data quickly if I make a mistake |
* * | new and inexperienced user | redo an action that was undone with undo CTRL+SHIFT+Z | reapply an action if I realise I need it after undoing it |
* * | new and inexperienced user | be prompted with why an invalid command is invalid | receive immediate and specific feedback if I type an invalid command |
* * | returning user | search contacts using partial details (name, email) | find relevant contacts faster |
* * | user whose contacts span multiple projects | tag contacts with a project or organisation name | organise my contacts better |
* * | user | filter contacts by project or organisation | quickly locate clients related to specific tasks |
* * | experienced user | use keyboard shortcuts to bring up the CLI | execute commands faster |
* * | experienced user | use keyboard shortcuts to manage contacts | manage my contacts faster |
* | user | multi-select contacts for deletion | manage my list more efficiently |
* | power user | export my contact list to JSON format | use it in other tools or projects |
Listed below are user stories that represent features that we have not implemented yet, but plan to in the future.
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * | new user | import contacts from a CSV or another format (e.g. Apple's .vcf) | use more data formats to quickly populate my address book |
* * | new user | open up a settings menu | configure keyboard shortcuts |
* | returning user | customise the app's theme | make my user experience more personalised as I use the app more |
* | frequent user | navigate command history with arrow keys | quickly fill the search field and modify and execute previous commands |
* | programmer | configure my shortcuts to be similar to my IDE shortcuts | switch between my IDE and VBook more effectively |
* | frequent user | pin important contacts | have them appear at the top of my list for easy access |
* | long time user | archive old contacts | clean up my address book without having to remove contacts |
(For all use cases below, the System is the VBook and the Actor is the user, unless specified otherwise)
Use case: UC01 - Add a person
MSS
User requests to add a specific person to VBook.
VBook adds the person.
Use case ends.
Extensions
1a. One or more of the inputted parameters, or the command, is invalid.
1a1. VBook shows an error message.
Use case ends.
1b. The name of the requested person is the same as an existing person in the address book.
1b1. VBook shows an error message.
Use case ends.
Use case: UC02 - Edit a person's details
MSS
User requests to list persons.
VBook shows a list of persons.
User requests to edit a specific person's details in the list.
VBook edits the person's details.
Use case ends.
Extensions
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. VBook shows an error message.
Use case resumes at step 2.
3b. One or more of the inputted parameters, or the command, is invalid.
3b1. VBook shows an error message.
Use case resumes at step 2.
3c. The name of the requested person is the same as an existing person in the address book.
3c1. VBook shows an error message.
Use case resumes at step 2.
Use case: UC03 - Remove a person
MSS
User requests to list persons.
VBook shows a list of persons.
User requests to remove a specific person in the list.
VBook removes the person.
Use case ends.
Extensions
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. VBook shows an error message.
Use case resumes at step 2.
Use case: UC04 - Find persons matching criteria
MSS
User requests to find persons in the list that match provided search criteria.
VBook displays a list of persons that match the criteria.
Use case ends.
Extensions
1a. No persons match the search criteria.
1a1. VBook displays a message indicating that no persons were found.
Use case ends.
1b. The command entered is invalid.
1b1. VBook shows an error message.
Use case ends.
Use case: UC05 - Export data
MSS
User requests to export data.
VBook opens the file explorer window.
User chooses the destination and name of the exported data.
VBook exports the data to the destination folder in JSON format.
Use case ends.
Extensions
2a. User closes the file explorer window without selecting a destination.
Use case ends.
3a. The name of the exported data clashes with an existing name in the same file destination.
3a1. The file explorer displays an error message.
Use case returns to step 2.
3b. The user enters an invalid name.
3b1. The file explorer displays an error message.
Use case returns to step 2.
Use case: UC06 - Enter password
Preconditions: User has already set a password previously.
MSS
User starts the app.
VBook displays a window prompting the user to enter a password.
User enters the correct password.
VBook closes the password prompt window and opens the main app window.
Use case ends.
Extensions
2a1. VBook displays an error message.
Use case returns to step 2.
Use case: UC07 - Undo command
Preconditions: At least one command that changes the address book has been entered.
MSS
User requests to undo the last command.
VBook undoes the last change to the data.
Use case ends.
Extensions
1a. There is no previously done add/edit/remove command found.
1a1. VBook displays a message that there are no more commands to undo.
Use case ends.
1b. User has undone more commands than the maximum amount allowed.
1b1. VBook displays a message that there are no more commands to undo.
Use case ends.
Use case: UC08 - Redo command
MSS
User requests to redo the last undone command.
VBook redoes the last undone command.
Use case ends.
Extensions
1a. There is no previously undone command.
1a1. VBook displays a message that there are no more commands to redo.
Use case ends.
1b. User has redone more commands than the maximum amount allowed.
1b1. VBook displays a message that there are no more commands to redo.
Use case ends.
17 or above installed.EncryptionManagerTest.java, temporary files are created before and deleted after each test for the Encryption and Export tests. Without this cleanup, subsequent runs of ./gradlew test will fail.java.nio.file.FileSystemException exception when attempting to delete the files due to the difference in how Windows processes manage files. (StackOverflow issue)*test.key files and test folder created, both in the root directory of VBook.jar.Team Size: 4
UI space. We plan to add a maximum length for the tags to be 50 characters, as the longest English word is 45 characters.0000000. Hence, we plan to split the location field into three distinct fields: postal code, street name, and block number. Input validation will ensure specificity, restrict ambiguity, and allow only valid special characters relevant to location data.integer is entered into our index field, the error message displays: Invalid command format! instead of The person index provided is invalid. This is likely due to an integer overflow error throwing a different exception than expected. We plan to fix this by adding a check for overflow and returning the appropriate error message afterwards.vbook.json data manually: Doing so would result in an exception if the program attempts to decrypt the data (for example, on :export command). However, this exception is not handled gracefully on the Command box and instead, only shown in the console. We plan to fix this by adding a Command Exception on decryption error to inform the user that the data has been tampered with.Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch:
Instructions:
.jar file and copy it into an empty folder.cd) to the folder where you saved the VBook.jar file, and use the following command to run the application:cd path/to/vbook
java -jar VBook.jar
Saving window preferences:
Basic help command:
:helpAdding a new person with all details:
:add -n John Doe -p 91234567 -e johndoe@example.com -l 123, Clementi Rd, #01-01, S123456 -t Friend -r Met at a weddingAdding a person with only mandatory fields:
:add -n Jane Smith:listEditing a person’s details with valid inputs:
:edit 1 -n Jonathan Doe -p 92345678 -e jonathan.doe@example.com -l 456, Orchard Rd, #02-02, S654321 -t FamilyEditing only a specific field:
:edit 1 -p 91234567Successful export to a chosen location:
:exportcontacts.json is created in the selected location, containing the contact data. A confirmation message should display the successful export.Export with existing file name in destination:
contacts.json is present in the selected export location.:exportcontacts.json for the export.Undo a recent add command:
:add -n John Doe -p 91234567 -e johndoe@example.com followed by :undoUndo a recent edit command:
:edit 1 -p 92345678 followed by :undoRedo a recent undo of an add command:
:add -n John Doe -p 91234567 -e johndoe@example.com, then :undo, followed by :redo:redo command, with a confirmation message indicating the redo was successful.Redo a recent undo of an edit command:
:edit 1 -p 92345678, then :undo, followed by :redo:redo command, with a confirmation message.Find by full name:
:find -n John DoeFind by partial name:
:find -n JohnRemoving the first person in the list:
:list command. Multiple persons are in the list.:remove -i 1Attempting to remove a person with an invalid index (zero):
:remove -i 0Basic clear command to remove all contacts:
:clearClear command when the contact list is already empty:
:clearBasic quit command to exit the application:
:quitQuit command immediately after startup:
:quitDealing with missing data files
data/vbook.json file in the home folder of the .jar file.data/vbook.json file, close the address book, and then open it again.Initial fresh startup - Cancelling prompt:
password.txt file in the root directory of the project.password.txt file. Press "Cancel" or close the password prompt using the cross button.password.txt file is created.Initial fresh startup - Creating a new password
password.txt file in the root directory of the project.password.txt file is created in the root directory containing the salted hash of the entered password.Initial fresh startup
Prerequisites: Ensure there is no data/vbook.json file or vbook.jks file in the root directory.
Test case: Start the program and invoke a GUI command (e.g., :add).
Action: Add a new entry using the GUI command.
Expected outcome:
vbook.jks file is created in the root directory, containing the encryption key.data/vbook.json file is created. Attempting to open the file from the file explorer shows unreadable binary data.Subsequent loads with existing vbook.jks and encrypted vbook.json
Prerequisites: Ensure a valid vbook.jks file exists in the root directory and an encrypted data/vbook.json file is present.
Test case: Start the program.
Action: Launch the program.
Expected outcome:
data/vbook.json file using the vbook.jks encryption key.data/vbook.json file, ensuring the file remains binary and unreadable outside the program.This project was challenging to implement, especially with our ambition of making it as keyboard-friendly as possible. Implementing keyboard shortcuts, as well as keyboard-friendly UI was not easy.
While we didn't expand the fields much from AB3, certain features like the Export feature took us a lot of time to debug, especially given known bugs with the Windows system (see failing tests on Windows when run more than once). The password prompt with proper salted hashing and encryption features with standard encryption algorithms also add an additional layer of security.