/* * Autopsy Forensic Browser * * Copyright 2011-2014 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.casemodule; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import javax.swing.JMenuItem; import org.apache.commons.lang.ArrayUtils; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.Presenter; import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Logger; /** * The action in this class is to clear the list of "Recent Cases". The * constructor is called when the autopsy is running. All the method to create * and modify the properties file are within this class */ final class RecentCases extends CallableSystemAction implements Presenter.Menu { static final int LENGTH = 6; static final String NAME_PROP_KEY = "LBL_RecentCase_Name"; //NON-NLS static final String PATH_PROP_KEY = "LBL_RecentCase_Path"; //NON-NLS static final RecentCase BLANK_RECENTCASE = new RecentCase("", ""); private final static RecentCases INSTANCE = new RecentCases(); private Deque<RecentCase> recentCases; // newest case is last case /** * Gets the instance of the RecentCases singleton. * * * @return INSTANCE the RecentCases singleton */ static public RecentCases getInstance() { INSTANCE.refreshRecentCases(); return INSTANCE; } /** * the constructor */ private RecentCases() { for (int i = 0; i < LENGTH; i++) { try { if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, nameKey(i)) == null) { ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, nameKey(i), ""); } if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, pathKey(i)) == null) { ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, pathKey(i), ""); } } catch (Exception e) { } } // Load recentCases from properties recentCases = new LinkedList<RecentCase>(); for (int i = 0; i < LENGTH; i++) { final RecentCase rc = new RecentCase(getName(i), getPath(i)); if (!rc.equals(BLANK_RECENTCASE)) { recentCases.add(rc); } } refreshRecentCases(); } private static void validateCaseIndex(int i) { if (i < 0 || i >= LENGTH) { throw new IllegalArgumentException( NbBundle.getMessage(RecentCases.class, "RecentCases.exception.caseIdxOutOfRange.msg", i)); } } private static String nameKey(int i) { validateCaseIndex(i); return NAME_PROP_KEY + Integer.toString(i + 1); } private static String pathKey(int i) { validateCaseIndex(i); return PATH_PROP_KEY + Integer.toString(i + 1); } private String getName(int i) { try { return ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, nameKey(i)); } catch (Exception e) { return null; } } private String getPath(int i) { try { return ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, pathKey(i)); } catch (Exception e) { return null; } } private void setName(int i, String name) { ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, nameKey(i), name); } private void setPath(int i, String path) { ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, pathKey(i), path); } private void setRecentCase(int i, RecentCase rc) { setName(i, rc.name); setPath(i, rc.path); } private static final class RecentCase { String name, path; /** * @param name The case name or "" if a blank placeholder case * @param path A normalized path (via FileUtil.normalizePath(path)) or * "" if a blank placeholder case */ private RecentCase(String name, String path) { this.name = name; this.path = path; } /** * Used when creating RecentCases with external data. The path must be * normalized so that duplicate cases always have the same path. * * @param name The case name. * @param unsafePath The (potentially un-normalized) case path. * * @return The created RecentCase.s */ static RecentCase createSafe(String name, String unsafePath) { return new RecentCase(name, FileUtil.normalizePath(unsafePath)); } /** * Does this case exist or was it manually deleted or moved? * * @return true if the case exists, false otherwise */ boolean exists() { return !(name.equals("") || path.equals("") || !new File(path).exists()); } // netbeans autogenerated hashCode @Override public int hashCode() { int hash = 7; hash = 13 * hash + (this.name != null ? this.name.hashCode() : 0); hash = 13 * hash + (this.path != null ? this.path.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final RecentCase other = (RecentCase) obj; if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } if ((this.path == null) ? (other.path != null) : !this.path.equals(other.path)) { return false; } return true; } } /** * Refresh the current list of cases, removing any cases that no longer * exist. */ private void refreshRecentCases() { List<RecentCase> toDelete = new ArrayList<RecentCase>(); for (RecentCase rc : recentCases) { if (!rc.exists()) { toDelete.add(rc); } } for (RecentCase deleteMe : toDelete) { removeRecentCase(deleteMe.name, deleteMe.path); } } private void storeRecentCases() throws IOException { int i = 0; // store however many recent cases exist for (RecentCase rc : recentCases) { setRecentCase(i, rc); i++; } // set the rest to blanks while (i < LENGTH) { setRecentCase(i, BLANK_RECENTCASE); i++; } } /** * Gets a menu item that can present this action in a JMenu. * * @return menuItem the representation menu item for this action */ @Override public JMenuItem getMenuPresenter() { return new UpdateRecentCases(); } /** * This action is used to clear all the recent cases menu options. * * @param e the action event */ @Override public void actionPerformed(ActionEvent e) { UpdateRecentCases.hasRecentCase = false; recentCases.clear(); try { // clear the properties file storeRecentCases(); } catch (Exception ex) { Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not clear the properties file.", ex); //NON-NLS } } private void addRecentCase(RecentCase rc) { // remove the case if it's already in the list recentCases.remove(rc); // make space if it's needed if (recentCases.size() == LENGTH) { recentCases.remove(); } recentCases.add(rc); } /** * Adds a recent case to the top of the list. If the case is already in the * list, it will be removed before the new entry is added. * * @param name the name of the recent case to be added * @param unsafePath the (potentially un-normalized) path of the case config * file */ public void addRecentCase(String name, String unsafePath) { RecentCase rc = RecentCase.createSafe(name, unsafePath); addRecentCase(rc); this.getMenuPresenter().setVisible(true); // invoke the contructor again try { storeRecentCases(); } catch (Exception ex) { Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS } } /** * This method is used to update the name and path of a RecentCase. * * @param oldName the old recent case name * @param oldPath the old recent case config file path * @param newName the new recent case name * @param newPath the new recent case config file path * * @throws Exception */ public void updateRecentCase(String oldName, String oldPath, String newName, String newPath) throws Exception { RecentCase oldRc = RecentCase.createSafe(oldName, oldPath); RecentCase newRc = RecentCase.createSafe(newName, newPath); // remove all instances of the old recent case recentCases.removeAll(Arrays.asList(oldRc)); addRecentCase(newRc); this.getMenuPresenter().setVisible(true); // invoke the contructor again try { storeRecentCases(); } catch (Exception ex) { Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS } } /** * Gets the total number of recent cases * * @return total total number of recent cases */ public int getTotalRecentCases() { return recentCases.size(); } /** * This method is used to remove the selected name and path of the * RecentCase * * @param name the case name to be removed from the recent case * @param path the config file path to be removed from the recent case */ public void removeRecentCase(String name, String path) { RecentCase rc = RecentCase.createSafe(name, path); // remove all instances of the old recent case recentCases.removeAll(Arrays.asList(rc)); this.getMenuPresenter().setVisible(true); // invoke the contructor again // write the properties file try { storeRecentCases(); } catch (Exception ex) { Logger.getLogger(RecentCases.class.getName()).log(Level.WARNING, "Error: Could not update the properties file.", ex); //NON-NLS } } /** * Gets the recent case names. * * @return caseNames An array String[LENGTH - 1], newest case first, with any * extra spots filled with "" */ public String[] getRecentCaseNames() { String[] caseNames = new String[LENGTH]; Iterator<RecentCase> mostRecentFirst = recentCases.descendingIterator(); int i = 0; String currentCaseName = null; try { currentCaseName = Case.getCurrentCase().getName(); } catch (IllegalStateException ex) { // in case there is no current case. } while (mostRecentFirst.hasNext()) { String name = mostRecentFirst.next().name; if ((currentCaseName != null && !name.equals(currentCaseName)) || currentCaseName == null) { // exclude currentCaseName from the caseNames[] caseNames[i] = name; i++; } } while (i < caseNames.length) { caseNames[i] = ""; i++; } // return last 5 case names return (String[]) ArrayUtils.subarray(caseNames, 0, LENGTH - 1); } /** * Gets the recent case paths. * * @return casePaths An array String[LENGTH - 1], newest case first, with any * extra spots filled with "" */ public String[] getRecentCasePaths() { String[] casePaths = new String[LENGTH]; String currentCasePath = null; try { currentCasePath = Case.getCurrentCase().getCaseMetadata().getFilePath().toString(); } catch (IllegalStateException ex) { // in case there is no current case. } Iterator<RecentCase> mostRecentFirst = recentCases.descendingIterator(); int i = 0; while (mostRecentFirst.hasNext()) { String path = mostRecentFirst.next().path; if ((currentCasePath != null && !path.equals(currentCasePath)) || currentCasePath == null) { // exclude currentCasePath from the casePaths[] casePaths[i] = path; i++; } } while (i < casePaths.length) { casePaths[i] = ""; i++; } // return last 5 case paths return (String[]) ArrayUtils.subarray(casePaths, 0, LENGTH - 1); } /** * This method does nothing. Use the actionPerformed instead of this method. */ @Override public void performAction() { } /** * Gets the name of this action. This may be presented as an item in a menu. * * @return actionName */ @Override public String getName() { //return NbBundle.getMessage(RecentCases.class, "CTL_RecentCases"); return NbBundle.getMessage(RecentCases.class, "RecentCases.getName.text"); } /** * Gets the HelpCtx associated with implementing object * * @return HelpCtx or HelpCtx.DEFAULT_HELP */ @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } }