/* * Autopsy Forensic Browser * * Copyright 2013-16 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.imagegallery.actions; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.collections.ObservableSet; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import javax.swing.JOptionPane; import org.controlsfx.control.action.Action; import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** * */ @NbBundle.Messages({"CategorizeAction.displayName=Categorize"}) public class CategorizeAction extends Action { private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private final ImageGalleryController controller; private final UndoRedoManager undoManager; private final Category cat; private final Set<Long> selectedFileIDs; private final Boolean createUndo; public CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs) { this(controller, cat, selectedFileIDs, true); } private CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs, Boolean createUndo) { super(cat.getDisplayName()); this.controller = controller; this.undoManager = controller.getUndoManager(); this.cat = cat; this.selectedFileIDs = selectedFileIDs; this.createUndo = createUndo; setGraphic(cat.getGraphic()); setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs)); setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber())))); } static public Menu getCategoriesMenu(ImageGalleryController controller) { return new CategoryMenu(controller); } final void addCatToFiles(Set<Long> ids) { Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS controller.queueDBWorkerTask(new CategorizeTask(ids, cat, createUndo)); } /** * Instances of this class implement a context menu user interface for * selecting a category */ static private class CategoryMenu extends Menu { CategoryMenu(ImageGalleryController controller) { super(Bundle.CategorizeAction_displayName()); setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); ObservableSet<Long> selected = controller.getSelectionModel().getSelected(); // Each category get an item in the sub-menu. Selecting one of these menu items adds // a tag with the associated category. for (final Category cat : Category.values()) { MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected)); getItems().add(categoryItem); } } } @NbBundle.Messages({"# {0} - fileID number", "CategorizeTask.errorUnable.msg=Unable to categorize {0}.", "CategorizeTask.errorUnable.title=Categorizing Error"}) private class CategorizeTask extends ImageGalleryController.BackgroundTask { private final Set<Long> fileIDs; private final boolean createUndo; private final Category cat; CategorizeTask(Set<Long> fileIDs, @Nonnull Category cat, boolean createUndo) { super(); this.fileIDs = fileIDs; java.util.Objects.requireNonNull(cat); this.cat = cat; this.createUndo = createUndo; } @Override public void run() { final DrawableTagsManager tagsManager = controller.getTagsManager(); final CategoryManager categoryManager = controller.getCategoryManager(); Map<Long, Category> oldCats = new HashMap<>(); TagName tagName = categoryManager.getTagName(cat); TagName catZeroTagName = categoryManager.getTagName(Category.ZERO); for (long fileID : fileIDs) { try { DrawableFile file = controller.getFileFromId(fileID); //drawable db access if (createUndo) { Category oldCat = file.getCategory(); //drawable db access TagName oldCatTagName = categoryManager.getTagName(oldCat); if (false == tagName.equals(oldCatTagName)) { oldCats.put(fileID, oldCat); } } final List<ContentTag> fileTags = tagsManager.getContentTags(file); if (tagName == categoryManager.getTagName(Category.ZERO)) { // delete all cat tags for cat-0 fileTags.stream() .filter(tag -> CategoryManager.isCategoryTagName(tag.getName())) .forEach((ct) -> { try { tagsManager.deleteContentTag(ct); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error removing old categories result", ex); //NON-NLS } }); } else { //add cat tag if no existing cat tag for that cat if (fileTags.stream() .map(Tag::getName) .filter(tagName::equals) .collect(Collectors.toList()).isEmpty()) { tagsManager.addContentTag(file, tagName, ""); } } } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error categorizing result", ex); //NON-NLS JOptionPane.showMessageDialog(null, Bundle.CategorizeTask_errorUnable_msg(fileID), Bundle.CategorizeTask_errorUnable_title(), JOptionPane.ERROR_MESSAGE); break; } } if (createUndo && oldCats.isEmpty() == false) { undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats)); } } } /** * */ @Immutable private final class CategorizationChange implements UndoRedoManager.UndoableCommand { private final Category newCategory; private final ImmutableMap<Long, Category> oldCategories; private final ImageGalleryController controller; CategorizationChange(ImageGalleryController controller, Category newCategory, Map<Long, Category> oldCategories) { this.controller = controller; this.newCategory = newCategory; this.oldCategories = ImmutableMap.copyOf(oldCategories); } /** * * @param controller the controller to apply the changes with */ @Override public void run() { new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false) .handle(null); } /** * * @param controller the value of controller */ @Override public void undo() { for (Map.Entry<Long, Category> entry : oldCategories.entrySet()) { new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false) .handle(null); } } } }