Java tutorial
/* * Universal Password Manager * Copyright (C) 2005-2013 Adrian Smith * * This file is part of Universal Password Manager. * * Universal Password Manager is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Universal Password Manager is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Universal Password Manager; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com._17od.upm.gui; import java.awt.Color; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import javax.swing.Timer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com._17od.upm.crypto.CryptoException; import com._17od.upm.crypto.InvalidPasswordException; import com._17od.upm.database.AccountInformation; import com._17od.upm.database.AccountsCSVMarshaller; import com._17od.upm.database.ExportException; import com._17od.upm.database.ImportException; import com._17od.upm.database.PasswordDatabase; import com._17od.upm.database.PasswordDatabasePersistence; import com._17od.upm.database.ProblemReadingDatabaseFile; import com._17od.upm.gui.MainWindow.ChangeDatabaseAction; import com._17od.upm.transport.Transport; import com._17od.upm.transport.TransportException; import com._17od.upm.util.FileChangedCallback; import com._17od.upm.util.FileMonitor; import com._17od.upm.util.Preferences; import com._17od.upm.util.Translator; import com._17od.upm.util.Util; public class DatabaseActions { private static Log LOG = LogFactory.getLog(DatabaseActions.class); private MainWindow mainWindow; private PasswordDatabase database; private ArrayList accountNames; private boolean localDatabaseDirty = true; private PasswordDatabasePersistence dbPers; private FileMonitor fileMonitor; private boolean databaseNeedsReload = false; private boolean lockIfInactive; private int msToWaitBeforeClosingDB; private boolean runSetDBDirtyThread = true; public DatabaseActions(MainWindow mainWindow) { this.mainWindow = mainWindow; } /** * This method asks the user for the name of a new database and then creates * it. If the file already exists then the user is asked if they'd like to * overwrite it. * @throws CryptoException * @throws IOException */ public void newDatabase() throws IOException, CryptoException { File newDatabaseFile = getSaveAsFile(Translator.translate("newPasswordDatabase")); if (newDatabaseFile == null) { return; } final JPasswordField masterPassword = new JPasswordField(""); boolean passwordsMatch = false; do { //Get a new master password for this database from the user JPasswordField confirmedMasterPassword = new JPasswordField(""); JOptionPane pane = new JOptionPane( new Object[] { Translator.translate("enterMasterPassword"), masterPassword, Translator.translate("confirmation"), confirmedMasterPassword }, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JDialog dialog = pane.createDialog(mainWindow, Translator.translate("masterPassword")); dialog.addWindowFocusListener(new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { masterPassword.requestFocusInWindow(); } }); dialog.show(); if (pane.getValue().equals(new Integer(JOptionPane.OK_OPTION))) { if (!Arrays.equals(masterPassword.getPassword(), confirmedMasterPassword.getPassword())) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("passwordsDontMatch")); } else { passwordsMatch = true; } } else { return; } } while (passwordsMatch == false); if (newDatabaseFile.exists()) { newDatabaseFile.delete(); } database = new PasswordDatabase(newDatabaseFile); dbPers = new PasswordDatabasePersistence(masterPassword.getPassword()); saveDatabase(); accountNames = new ArrayList(); doOpenDatabaseActions(); // If a "Database to Load on Startup" hasn't been set yet then ask the // user if they'd like to open this database on startup. if (Preferences.get(Preferences.ApplicationOptions.DB_TO_LOAD_ON_STARTUP) == null || Preferences.get(Preferences.ApplicationOptions.DB_TO_LOAD_ON_STARTUP).equals("")) { int option = JOptionPane.showConfirmDialog(mainWindow, Translator.translate("setNewLoadOnStartupDatabase"), Translator.translate("newPasswordDatabase"), JOptionPane.YES_NO_OPTION); if (option == JOptionPane.YES_OPTION) { Preferences.set(Preferences.ApplicationOptions.DB_TO_LOAD_ON_STARTUP, newDatabaseFile.getAbsolutePath()); Preferences.save(); } } } public void changeMasterPassword() throws IOException, ProblemReadingDatabaseFile, CryptoException, PasswordDatabaseException, TransportException { if (getLatestVersionOfDatabase()) { //The first task is to get the current master password boolean passwordCorrect = false; boolean okClicked = true; do { char[] password = askUserForPassword(Translator.translate("enterDatabasePassword")); if (password == null) { okClicked = false; } else { try { dbPers.load(database.getDatabaseFile(), password); passwordCorrect = true; } catch (InvalidPasswordException e) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("incorrectPassword")); } } } while (!passwordCorrect && okClicked); //If the master password was entered correctly then the next step is to get the new master password if (passwordCorrect == true) { final JPasswordField masterPassword = new JPasswordField(""); boolean passwordsMatch = false; Object buttonClicked; //Ask the user for the new master password //This loop will continue until the two passwords entered match or until the user hits the cancel button do { JPasswordField confirmedMasterPassword = new JPasswordField(""); JOptionPane pane = new JOptionPane( new Object[] { Translator.translate("enterNewMasterPassword"), masterPassword, Translator.translate("confirmation"), confirmedMasterPassword }, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JDialog dialog = pane.createDialog(mainWindow, Translator.translate("changeMasterPassword")); dialog.addWindowFocusListener(new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { masterPassword.requestFocusInWindow(); } }); dialog.show(); buttonClicked = pane.getValue(); if (buttonClicked.equals(new Integer(JOptionPane.OK_OPTION))) { if (!Arrays.equals(masterPassword.getPassword(), confirmedMasterPassword.getPassword())) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("passwordsDontMatch")); } else { passwordsMatch = true; } } } while (buttonClicked.equals(new Integer(JOptionPane.OK_OPTION)) && !passwordsMatch); //If the user clicked OK and the passwords match then change the database password if (buttonClicked.equals(new Integer(JOptionPane.OK_OPTION)) && passwordsMatch) { this.dbPers.getEncryptionService().initCipher(masterPassword.getPassword()); saveDatabase(); } } } } public void errorHandler(Exception e) { e.printStackTrace(); String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = e.getClass().getName(); } JOptionPane.showMessageDialog(mainWindow, errorMessage, Translator.translate("error"), JOptionPane.ERROR_MESSAGE); } private void doCloseDatabaseActions() { mainWindow.getAddAccountButton().setEnabled(false); mainWindow.getAddAccountMenuItem().setEnabled(false); mainWindow.getSearchField().setEnabled(false); mainWindow.getSearchField().setText(""); mainWindow.getSearchIcon().setEnabled(false); mainWindow.getResetSearchButton().setEnabled(false); mainWindow.getChangeMasterPasswordMenuItem().setEnabled(false); mainWindow.getDatabasePropertiesMenuItem().setEnabled(false); mainWindow.getExportMenuItem().setEnabled(false); mainWindow.getImportMenuItem().setEnabled(false); mainWindow.setTitle(MainWindow.getApplicationName()); mainWindow.getStatusBar().setText(""); databaseNeedsReload = false; SortedListModel listview = (SortedListModel) mainWindow.getAccountsListview().getModel(); listview.clear(); mainWindow.getEditAccountButton().setEnabled(false); mainWindow.getCopyUsernameButton().setEnabled(false); mainWindow.getCopyPasswordButton().setEnabled(false); mainWindow.getDeleteAccountButton().setEnabled(false); mainWindow.getEditAccountMenuItem().setEnabled(false); mainWindow.getCopyUsernameMenuItem().setEnabled(false); mainWindow.getCopyPasswordMenuItem().setEnabled(false); mainWindow.getDeleteAccountMenuItem().setEnabled(false); mainWindow.getViewAccountMenuItem().setEnabled(false); } private void doOpenDatabaseActions() { mainWindow.getAddAccountButton().setEnabled(true); mainWindow.getAddAccountMenuItem().setEnabled(true); mainWindow.getSearchField().setEnabled(true); mainWindow.getSearchField().setText(""); mainWindow.getSearchIcon().setEnabled(true); mainWindow.getResetSearchButton().setEnabled(true); mainWindow.getChangeMasterPasswordMenuItem().setEnabled(true); mainWindow.getDatabasePropertiesMenuItem().setEnabled(true); mainWindow.getExportMenuItem().setEnabled(true); mainWindow.getImportMenuItem().setEnabled(true); mainWindow.setTitle(database.getDatabaseFile() + " - " + MainWindow.getApplicationName()); setLocalDatabaseDirty(true); databaseNeedsReload = false; accountNames = getAccountNames(); populateListview(accountNames); // Start a thread to listen for changes to the db file FileChangedCallback callback = new FileChangedCallback() { public void fileChanged(File file) { databaseNeedsReload = true; mainWindow.setFileChangedPanelVisible(true); } }; fileMonitor = new FileMonitor(database.getDatabaseFile(), callback); Thread thread = new Thread(fileMonitor); thread.start(); // If the user asked for the db to close after a period of // inactivity then register a listener to capture window focus // events. configureAutoLock(); // Give the search field focus. // I'm using requestFocusInWindow() rather than // requestFocus() because the javadocs recommend it. mainWindow.getSearchField().requestFocusInWindow(); mainWindow.getDatabaseFileChangedPanel().setVisible(false); } private void configureAutoLock() { lockIfInactive = Preferences.get(Preferences.ApplicationOptions.DATABASE_AUTO_LOCK, "false").equals("true"); msToWaitBeforeClosingDB = Preferences.getInt(Preferences.ApplicationOptions.DATABASE_AUTO_LOCK_TIME, 5) * 60 * 1000; if (lockIfInactive) { LOG.debug("Enabling autoclose when focus lost"); if (mainWindow.getWindowFocusListeners().length == 0) { mainWindow.addWindowFocusListener(new AutoLockDatabaseListener()); } } else { LOG.debug("Disabling autoclose when focus lost"); for (int i = 0; i < mainWindow.getWindowFocusListeners().length; i++) { mainWindow.removeWindowFocusListener(mainWindow.getWindowFocusListeners()[i]); } } } public ArrayList getAccountNames() { ArrayList dbAccounts = database.getAccounts(); ArrayList accountNames = new ArrayList(); for (int i = 0; i < dbAccounts.size(); i++) { AccountInformation ai = (AccountInformation) dbAccounts.get(i); String accountName = (String) ai.getAccountName(); accountNames.add(accountName); } return accountNames; } /** * Prompt the user to enter a password * @return The password entered by the user or null of this hit escape/cancel */ private char[] askUserForPassword(String message) { char[] password = null; final JPasswordField masterPassword = new JPasswordField(""); JOptionPane pane = new JOptionPane(new Object[] { message, masterPassword }, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JDialog dialog = pane.createDialog(mainWindow, Translator.translate("masterPassword")); dialog.addWindowFocusListener(new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { masterPassword.requestFocusInWindow(); } }); dialog.show(); if (pane.getValue() != null && pane.getValue().equals(new Integer(JOptionPane.OK_OPTION))) { password = masterPassword.getPassword(); } return password; } public void openDatabase(String databaseFilename) throws IOException, ProblemReadingDatabaseFile, CryptoException { openDatabase(databaseFilename, null); } public void openDatabase(String databaseFilename, char[] password) throws IOException, ProblemReadingDatabaseFile, CryptoException { boolean passwordCorrect = false; boolean okClicked = true; while (!passwordCorrect && okClicked) { // If we weren't given a password then ask the user to enter one if (password == null) { password = askUserForPassword(Translator.translate("enterDatabasePassword")); if (password == null) { okClicked = false; } } else { okClicked = true; } if (okClicked) { try { dbPers = new PasswordDatabasePersistence(); database = dbPers.load(new File(databaseFilename), password); passwordCorrect = true; } catch (InvalidPasswordException e) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("incorrectPassword")); password = null; } } } if (passwordCorrect) { doOpenDatabaseActions(); } } public void openDatabase() throws IOException, ProblemReadingDatabaseFile, CryptoException { JFileChooser fc = new JFileChooser(); fc.setDialogTitle(Translator.translate("openDatabase")); int returnVal = fc.showOpenDialog(mainWindow); if (returnVal == JFileChooser.APPROVE_OPTION) { File databaseFile = fc.getSelectedFile(); if (databaseFile.exists()) { openDatabase(databaseFile.getAbsolutePath()); } else { JOptionPane.showMessageDialog(mainWindow, Translator.translate("fileDoesntExistWithName", databaseFile.getAbsolutePath()), Translator.translate("fileDoesntExist"), JOptionPane.ERROR_MESSAGE); } } // Stop any "SetDBDirtyThread"s that are running runSetDBDirtyThread = false; } public void deleteAccount() throws IOException, CryptoException, TransportException, ProblemReadingDatabaseFile, PasswordDatabaseException { if (getLatestVersionOfDatabase()) { SortedListModel listview = (SortedListModel) mainWindow.getAccountsListview().getModel(); String selectedAccName = (String) mainWindow.getAccountsListview().getSelectedValue(); int buttonSelected = JOptionPane.showConfirmDialog(mainWindow, Translator.translate("askConfirmDeleteAccount", selectedAccName), Translator.translate("confirmDeleteAccount"), JOptionPane.YES_NO_OPTION); if (buttonSelected == JOptionPane.OK_OPTION) { //Remove the account from the listview, accountNames arraylist & the database listview.removeElement(selectedAccName); int i = accountNames.indexOf(selectedAccName); accountNames.remove(i); database.deleteAccount(selectedAccName); saveDatabase(); //[1375385] Call the filter method so that the listview is //reinitialised with the remaining matching items filter(); } } } public void addAccount() throws IOException, CryptoException, TransportException, ProblemReadingDatabaseFile, PasswordDatabaseException { if (getLatestVersionOfDatabase()) { //Initialise the AccountDialog AccountInformation accInfo = new AccountInformation(); AccountDialog accDialog = new AccountDialog(accInfo, mainWindow, false, accountNames); accDialog.pack(); accDialog.setLocationRelativeTo(mainWindow); accDialog.show(); //If the user press OK then save the new account to the database if (accDialog.okClicked()) { database.deleteAccount(accInfo.getAccountName()); database.addAccount(accInfo); saveDatabase(); accountNames.add(accInfo.getAccountName()); //[1375390] Ensure that the listview is properly filtered after an add filter(); } } } public AccountInformation getSelectedAccount() { String selectedAccName = (String) mainWindow.getAccountsListview().getSelectedValue(); return database.getAccount(selectedAccName); } private boolean getLatestVersionOfDatabase() throws TransportException, ProblemReadingDatabaseFile, IOException, CryptoException, PasswordDatabaseException { boolean latestVersionDownloaded = false; // Ensure we're working with the latest version of the database if (databaseHasRemoteInstance() && localDatabaseDirty) { int answer = JOptionPane.showConfirmDialog(mainWindow, Translator.translate("askSyncWithRemoteDB"), Translator.translate("syncDatabase"), JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.YES_OPTION) { latestVersionDownloaded = syncWithRemoteDatabase(); } } else { latestVersionDownloaded = true; } return latestVersionDownloaded; } private boolean databaseHasRemoteInstance() { if (database.getDbOptions().getRemoteLocation().equals("")) { return false; } else { return true; } } public void viewAccount() { AccountInformation accInfo = getSelectedAccount(); AccountDialog accDialog = new AccountDialog(accInfo, mainWindow, true, accountNames); accDialog.pack(); accDialog.setLocationRelativeTo(mainWindow); accDialog.show(); } public void editAccount(String accountName) throws TransportException, ProblemReadingDatabaseFile, IOException, CryptoException, PasswordDatabaseException, InvalidPasswordException, UPMException { if (getLatestVersionOfDatabase()) { AccountInformation accInfo = database.getAccount(accountName); if (accInfo == null) { throw new UPMException(Translator.translate("accountDoesntExist", accountName)); } AccountDialog accDialog = new AccountDialog(accInfo, mainWindow, false, accountNames); accDialog.pack(); accDialog.setLocationRelativeTo(mainWindow); accDialog.show(); //If the ok button was clicked then save the account to the database and update the //listview with the new account name (if it's changed) if (accDialog.okClicked() && accDialog.getAccountChanged()) { accInfo = accDialog.getAccount(); database.deleteAccount(accountName); database.addAccount(accInfo); //If the new account name is different to the old account name then update the //accountNames array and refilter the listview if (!accInfo.getAccountName().equals(accountName)) { // User might change the account name for the Authentication Entry // so this has to be checked if (accountName.equals(database.getDbOptions().getAuthDBEntry())) { database.getDbOptions().setAuthDBEntry(accInfo.getAccountName()); } int i = accountNames.indexOf(accountName); accountNames.remove(i); accountNames.add(accInfo.getAccountName()); //[1375390] Ensure that the listview is properly filtered after an edit filter(); } saveDatabase(); } } } public void filter() { String filterStr = mainWindow.getSearchField().getText().toLowerCase(); ArrayList filteredAccountsList = new ArrayList(); for (int i = 0; i < accountNames.size(); i++) { String accountName = (String) accountNames.get(i); if (filterStr.equals("") || accountName.toLowerCase().indexOf(filterStr) != -1) { filteredAccountsList.add(accountName); } } populateListview(filteredAccountsList); //If there's only one item in the listview then select it if (mainWindow.getAccountsListview().getModel().getSize() == 1) { mainWindow.getAccountsListview().setSelectedIndex(0); } } public void populateListview(ArrayList accountNames) { SortedListModel listview = (SortedListModel) mainWindow.getAccountsListview().getModel(); listview.clear(); mainWindow.getAccountsListview().clearSelection(); for (int i = 0; i < accountNames.size(); i++) { listview.addElement(accountNames.get(i)); } setButtonState(); } public void setButtonState() { if (mainWindow.getAccountsListview().getSelectedValue() == null) { mainWindow.getEditAccountButton().setEnabled(false); mainWindow.getCopyUsernameButton().setEnabled(false); mainWindow.getCopyPasswordButton().setEnabled(false); mainWindow.getDeleteAccountButton().setEnabled(false); mainWindow.getEditAccountMenuItem().setEnabled(false); mainWindow.getCopyUsernameMenuItem().setEnabled(false); mainWindow.getCopyPasswordMenuItem().setEnabled(false); mainWindow.getDeleteAccountMenuItem().setEnabled(false); mainWindow.getViewAccountMenuItem().setEnabled(false); } else { mainWindow.getEditAccountButton().setEnabled(true); mainWindow.getCopyUsernameButton().setEnabled(true); mainWindow.getCopyPasswordButton().setEnabled(true); mainWindow.getDeleteAccountButton().setEnabled(true); mainWindow.getEditAccountMenuItem().setEnabled(true); mainWindow.getCopyUsernameMenuItem().setEnabled(true); mainWindow.getCopyPasswordMenuItem().setEnabled(true); mainWindow.getDeleteAccountMenuItem().setEnabled(true); mainWindow.getViewAccountMenuItem().setEnabled(true); } } public void options() { OptionsDialog oppDialog = new OptionsDialog(mainWindow); oppDialog.pack(); oppDialog.setLocationRelativeTo(mainWindow); oppDialog.show(); configureAutoLock(); if (oppDialog.hasLanguageChanged()) { mainWindow.initialiseControlsWithDefaultLanguage(); if (database != null) { setStatusBarText(); } } } public void showAbout() { AboutDialog aboutDialog = new AboutDialog(mainWindow); aboutDialog.pack(); aboutDialog.setLocationRelativeTo(mainWindow); aboutDialog.show(); } public void resetSearch() { mainWindow.getSearchField().setText(""); } public void showDatabaseProperties() throws ProblemReadingDatabaseFile, IOException, CryptoException, PasswordDatabaseException { try { if (getLatestVersionOfDatabase()) { DatabasePropertiesDialog dbPropsDialog = new DatabasePropertiesDialog(mainWindow, getAccountNames(), database); dbPropsDialog.pack(); dbPropsDialog.setLocationRelativeTo(mainWindow); dbPropsDialog.show(); if (dbPropsDialog.getDatabaseNeedsSaving()) { saveDatabase(); } } } catch (TransportException e) { int response = JOptionPane.showConfirmDialog(mainWindow, Translator.translate("problemRetrievingRemoteDB"), Translator.translate("detachDatabase"), JOptionPane.YES_NO_OPTION); if (response == JOptionPane.YES_OPTION) { database.getDbOptions().setRemoteLocation(""); database.getDbOptions().setAuthDBEntry(""); saveDatabase(); } } } public void openDatabaseFromURL() throws TransportException, IOException, ProblemReadingDatabaseFile, CryptoException { // Ask the user for the remote database location OpenDatabaseFromURLDialog openDBDialog = new OpenDatabaseFromURLDialog(mainWindow); openDBDialog.pack(); openDBDialog.setLocationRelativeTo(mainWindow); openDBDialog.show(); if (openDBDialog.getOkClicked()) { // Get the remote database options String remoteLocation = openDBDialog.getUrlTextField().getText(); String username = openDBDialog.getUsernameTextField().getText(); String password = openDBDialog.getPasswordTextField().getText(); // Ask the user for a location to save the database file to File saveDatabaseTo = getSaveAsFile(Translator.translate("saveDatabaseAs")); if (saveDatabaseTo != null) { // Download the database Transport transport = Transport.getTransportForURL(new URL(remoteLocation)); File downloadedDatabaseFile = transport.getRemoteFile(remoteLocation, username, password); // Delete the file is it already exists if (saveDatabaseTo.exists()) { saveDatabaseTo.delete(); } // Save the downloaded database file to the new location Util.copyFile(downloadedDatabaseFile, saveDatabaseTo); // Now open the downloaded database openDatabase(saveDatabaseTo.getAbsolutePath()); } } } public void reloadDatabase() throws InvalidPasswordException, ProblemReadingDatabaseFile, IOException { PasswordDatabase reloadedDb = null; try { reloadedDb = dbPers.load(database.getDatabaseFile()); } catch (InvalidPasswordException e) { // The password for the reloaded database is different to that of // the open database boolean okClicked = false; do { char[] password = askUserForPassword(Translator.translate("enterDatabasePassword")); if (password == null) { okClicked = false; } else { okClicked = true; try { reloadedDb = dbPers.load(database.getDatabaseFile(), password); } catch (InvalidPasswordException invalidPassword) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("incorrectPassword")); } catch (CryptoException e1) { errorHandler(e); } } } while (okClicked && reloadedDb == null); } if (reloadedDb != null) { database = reloadedDb; doOpenDatabaseActions(); } } public void reloadDatabaseBefore(ChangeDatabaseAction editAction) throws InvalidPasswordException, ProblemReadingDatabaseFile, IOException { boolean proceedWithAction = false; if (this.databaseNeedsReload) { int answer = JOptionPane.showConfirmDialog(mainWindow, Translator.translate("askReloadDatabase"), Translator.translate("reloadDatabase"), JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.YES_OPTION) { proceedWithAction = reloadDatabaseFromDisk(); } } else { proceedWithAction = true; } if (proceedWithAction) { editAction.doAction(); } } public boolean reloadDatabaseFromDisk() throws InvalidPasswordException, ProblemReadingDatabaseFile, IOException { boolean reloadSuccessful = false; PasswordDatabase reloadedDb = null; try { reloadedDb = dbPers.load(database.getDatabaseFile()); } catch (InvalidPasswordException e) { // The password for the reloaded database is different to that of // the open database boolean okClicked = false; do { char[] password = askUserForPassword(Translator.translate("enterDatabasePassword")); if (password == null) { okClicked = false; } else { okClicked = true; try { reloadedDb = dbPers.load(database.getDatabaseFile(), password); } catch (InvalidPasswordException invalidPassword) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("incorrectPassword")); } catch (CryptoException e1) { errorHandler(e); } } } while (okClicked && reloadedDb == null); } if (reloadedDb != null) { database = reloadedDb; doOpenDatabaseActions(); reloadSuccessful = true; } return reloadSuccessful; } public boolean syncWithRemoteDatabase() throws TransportException, ProblemReadingDatabaseFile, IOException, CryptoException, PasswordDatabaseException { boolean syncSuccessful = false; try { fileMonitor.pause(); mainWindow.getContentPane().setCursor(new Cursor(Cursor.WAIT_CURSOR)); // Get the remote database options String remoteLocation = database.getDbOptions().getRemoteLocation(); String authDBEntry = database.getDbOptions().getAuthDBEntry(); String httpUsername = null; String httpPassword = null; if (!authDBEntry.equals("")) { httpUsername = database.getAccount(authDBEntry).getUserId(); httpPassword = database.getAccount(authDBEntry).getPassword(); } // Download the database that's already at the remote location Transport transport = Transport.getTransportForURL(new URL(remoteLocation)); File remoteDatabaseFile = transport.getRemoteFile(remoteLocation, database.getDatabaseFile().getName(), httpUsername, httpPassword); // Attempt to decrypt the database using the password the user entered PasswordDatabase remoteDatabase = null; char[] password = null; boolean successfullyDecryptedDb = false; try { remoteDatabase = dbPers.load(remoteDatabaseFile); successfullyDecryptedDb = true; } catch (InvalidPasswordException e) { // The password for the downloaded database is different to that of the open database // (most likely the user changed the local database's master password) boolean okClicked = false; do { password = askUserForPassword(Translator.translate("enterPaswordForRemoteDB")); if (password == null) { okClicked = false; } else { okClicked = true; try { remoteDatabase = dbPers.load(remoteDatabaseFile, password); successfullyDecryptedDb = true; } catch (InvalidPasswordException invalidPassword) { JOptionPane.showMessageDialog(mainWindow, Translator.translate("incorrectPassword")); } } } while (okClicked && !successfullyDecryptedDb); } /* If the local database revision > remote database version => upload local database If the local database revision < remote database version => replace local database with remote database If the local database revision = remote database version => do nothing */ if (successfullyDecryptedDb) { if (database.getRevision() > remoteDatabase.getRevision()) { transport.delete(remoteLocation, database.getDatabaseFile().getName(), httpUsername, httpPassword); transport.put(remoteLocation, database.getDatabaseFile(), httpUsername, httpPassword); syncSuccessful = true; } else if (database.getRevision() < remoteDatabase.getRevision()) { Util.copyFile(remoteDatabaseFile, database.getDatabaseFile()); database = new PasswordDatabase(remoteDatabase.getRevisionObj(), remoteDatabase.getDbOptions(), remoteDatabase.getAccountsHash(), database.getDatabaseFile()); doOpenDatabaseActions(); syncSuccessful = true; } else { syncSuccessful = true; } if (syncSuccessful) { setLocalDatabaseDirty(false); // Create a thread that will mark the database dirty after // a short period. Without this the database would remain // in a synced state until the user makes a change. The // longer we wait before syncing up the greater chance there // is that we'll miss changes made elsewhere and end up // with a conflicting version of the database. final long dirtyThreadStartTime = System.currentTimeMillis(); runSetDBDirtyThread = true; Thread setDBDirtyThread = new Thread(new Runnable() { public void run() { while (runSetDBDirtyThread) { try { Thread.sleep(1000); } catch (InterruptedException e1) { } long currentTime = System.currentTimeMillis(); if (currentTime - dirtyThreadStartTime > 5 * 60 * 1000) { LOG.info("SetDBDirtyThread setting database dirty"); setLocalDatabaseDirty(true); runSetDBDirtyThread = false; } } } }); setDBDirtyThread.setName("SetDBDirty"); setDBDirtyThread.start(); LOG.info("Started SetDBDirtyThread thread"); } } } finally { mainWindow.getContentPane().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); fileMonitor.start(); } return syncSuccessful; } public void exitApplication() { System.exit(0); } public void export() { File exportFile = getSaveAsFile(Translator.translate("exportFile")); if (exportFile == null) { return; } if (exportFile.exists()) { exportFile.delete(); } AccountsCSVMarshaller marshaller = new AccountsCSVMarshaller(); try { marshaller.marshal(this.database.getAccounts(), exportFile); } catch (ExportException e) { JOptionPane.showMessageDialog(mainWindow, e.getMessage(), Translator.translate("problemExporting"), JOptionPane.ERROR_MESSAGE); } } public void importAccounts() throws TransportException, ProblemReadingDatabaseFile, IOException, CryptoException, PasswordDatabaseException { if (getLatestVersionOfDatabase()) { // Prompt for the file to import JFileChooser fc = new JFileChooser(); fc.setDialogTitle(Translator.translate("import")); int returnVal = fc.showOpenDialog(mainWindow); if (returnVal == JFileChooser.APPROVE_OPTION) { File csvFile = fc.getSelectedFile(); // Unmarshall the accounts from the CSV file try { AccountsCSVMarshaller marshaller = new AccountsCSVMarshaller(); ArrayList accountsInCSVFile = marshaller.unmarshal(csvFile); ArrayList accountsToImport = new ArrayList(); boolean importCancelled = false; // Add each account to the open database. If the account // already exits the prompt to overwrite for (int i = 0; i < accountsInCSVFile.size(); i++) { AccountInformation importedAccount = (AccountInformation) accountsInCSVFile.get(i); if (database.getAccount(importedAccount.getAccountName()) != null) { Object[] options = { "Overwrite Existing", "Keep Existing", "Cancel" }; int answer = JOptionPane.showOptionDialog(mainWindow, Translator.translate("importExistingQuestion", importedAccount.getAccountName()), Translator.translate("importExistingTitle"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (answer == 1) { continue; // If keep existing then continue to the next iteration } else if (answer == 2) { importCancelled = true; break; // Cancel the import } } accountsToImport.add(importedAccount); } if (!importCancelled && accountsToImport.size() > 0) { for (int i = 0; i < accountsToImport.size(); i++) { AccountInformation accountToImport = (AccountInformation) accountsToImport.get(i); database.deleteAccount(accountToImport.getAccountName()); database.addAccount(accountToImport); } saveDatabase(); accountNames = getAccountNames(); filter(); } } catch (ImportException e) { JOptionPane.showMessageDialog(mainWindow, e.getMessage(), Translator.translate("problemImporting"), JOptionPane.ERROR_MESSAGE); } catch (IOException e) { JOptionPane.showMessageDialog(mainWindow, e.getMessage(), Translator.translate("problemImporting"), JOptionPane.ERROR_MESSAGE); } catch (CryptoException e) { JOptionPane.showMessageDialog(mainWindow, e.getMessage(), Translator.translate("problemImporting"), JOptionPane.ERROR_MESSAGE); } } } } /** * This method prompts the user for the name of a file. * If the file exists then it will ask if they want to overwrite (the file isn't overwritten though, * that would be done by the calling method) * @param title The string title to put on the dialog * @return The file to save to or null */ private File getSaveAsFile(String title) { File selectedFile; boolean gotValidFile = false; do { JFileChooser fc = new JFileChooser(); fc.setDialogTitle(title); int returnVal = fc.showSaveDialog(mainWindow); if (returnVal != JFileChooser.APPROVE_OPTION) { return null; } selectedFile = fc.getSelectedFile(); //Warn the user if the database file already exists if (selectedFile.exists()) { Object[] options = { "Yes", "No" }; int i = JOptionPane.showOptionDialog(mainWindow, Translator.translate("fileAlreadyExistsWithFileName", selectedFile.getAbsolutePath()) + '\n' + Translator.translate("overwrite"), Translator.translate("fileAlreadyExists"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (i == JOptionPane.YES_OPTION) { gotValidFile = true; } } else { gotValidFile = true; } } while (!gotValidFile); return selectedFile; } private void saveDatabase() throws IOException, CryptoException { dbPers.save(database); if (fileMonitor != null) { fileMonitor.start(); } if (databaseHasRemoteInstance()) { setLocalDatabaseDirty(true); } else { setLocalDatabaseDirty(false); } } private void setLocalDatabaseDirty(boolean dirty) { localDatabaseDirty = dirty; if (databaseHasRemoteInstance()) { if (localDatabaseDirty) { mainWindow.getSyncWithRemoteDatabaseMenuItem().setEnabled(true); mainWindow.getSyncWithRemoteDatabaseButton().setEnabled(true); } else { mainWindow.getSyncWithRemoteDatabaseMenuItem().setEnabled(false); mainWindow.getSyncWithRemoteDatabaseButton().setEnabled(false); } } else { mainWindow.getSyncWithRemoteDatabaseMenuItem().setEnabled(false); mainWindow.getSyncWithRemoteDatabaseButton().setEnabled(false); } setStatusBarText(); } private void setStatusBarText() { String status = null; Color color = null; if (databaseHasRemoteInstance()) { if (localDatabaseDirty) { status = Translator.translate("unsynchronised"); color = Color.RED; } else { status = Translator.translate("synchronised"); color = Color.BLACK; } status = Translator.translate("revision") + ' ' + String.valueOf(database.getRevision()) + " - " + status; } else { status = Translator.translate("localDatabase"); color = Color.BLACK; } mainWindow.getStatusBar().setText(status); mainWindow.getStatusBar().setForeground(color); } private class AutoLockDatabaseListener implements WindowFocusListener { private String databaseClosedOnTimer; private Timer closeDBTimer; public synchronized void windowGainedFocus(WindowEvent we) { if (closeDBTimer != null) { LOG.debug("Stopping closeDBTimer"); closeDBTimer.removeActionListener(closeDBTimer.getActionListeners()[0]); closeDBTimer = null; } if (databaseClosedOnTimer != null) { try { openDatabase(databaseClosedOnTimer); } catch (Exception e) { errorHandler(e); } databaseClosedOnTimer = null; } } /** * If the app loses focus, there's an open db and there's no closeDBTimer * already registered then start a timer to close the database after the * configured number of minutes. */ public synchronized void windowLostFocus(WindowEvent e) { // If the window receiving focus is within this application then the // app isn't not losing focus so no further action is required. if (e.getOppositeWindow() != null && e.getOppositeWindow().getOwner() == mainWindow) { LOG.debug("Focus switched to another window within this app"); return; } if (database != null && closeDBTimer == null) { closeDBTimer = new Timer(msToWaitBeforeClosingDB, null); closeDBTimer.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { LOG.debug("Closing database due to inactivity"); databaseClosedOnTimer = database.getDatabaseFile().getAbsolutePath(); doCloseDatabaseActions(); database = null; closeDBTimer = null; } }); closeDBTimer.setRepeats(false); closeDBTimer.start(); LOG.debug("Started lost focus timer, " + msToWaitBeforeClosingDB); } } } }