/*
* 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 java.awt.Window;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.collections.ObservableSet;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
import org.sleuthkit.autopsy.actions.GetTagNameDialog;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
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.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Instances of this Action allow users to apply tags to content.
*/
public class AddTagAction extends Action {
private static final Logger LOGGER = Logger.getLogger(AddTagAction.class.getName());
private final ImageGalleryController controller;
private final Set<Long> selectedFileIDs;
private final TagName tagName;
public AddTagAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
super(tagName.getDisplayName());
this.controller = controller;
this.selectedFileIDs = selectedFileIDs;
this.tagName = tagName;
setGraphic(controller.getTagsManager().getGraphic(tagName));
setText(tagName.getDisplayName());
setEventHandler(actionEvent -> addTagWithComment(""));
}
static public Menu getTagMenu(ImageGalleryController controller) {
return new TagMenu(controller);
}
private void addTagWithComment(String comment) {
addTagsToFiles(tagName, comment, selectedFileIDs);
}
@NbBundle.Messages({"# {0} - fileID",
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
// check if the same tag is being added for the same abstract file.
DrawableTagsManager tagsManager = controller.getTagsManager();
for (Long fileID : selectedFiles) {
try {
final DrawableFile file = controller.getFileFromId(fileID);
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
List<ContentTag> contentTags = tagsManager.getContentTags(file);
Optional<TagName> duplicateTagName = contentTags.stream()
.map(ContentTag::getName)
.filter(tagName::equals)
.findAny();
if (duplicateTagName.isPresent()) {
LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
} else {
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
controller.getTagsManager().addContentTag(file, tagName, comment);
}
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); //NON-NLS
Platform.runLater(() ->
new Alert(Alert.AlertType.ERROR, Bundle.AddDrawableTagAction_addTagsToFiles_alert(fileID)).show()
);
break;
}
}
return null;
}
@Override
protected void done() {
super.done();
try {
get();
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex); //NON-NLS
}
}
}.execute();
}
@NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag",
"AddTagAction.menuItem.noTags=No tags",
"AddTagAction.menuItem.newTag=New Tag...",
"AddTagAction.menuItem.tagAndComment=Tag and Comment...",
"AddDrawableTagAction.displayName.plural=Tag Files",
"AddDrawableTagAction.displayName.singular=Tag File"})
private static class TagMenu extends Menu {
TagMenu(ImageGalleryController controller) {
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
setText(selectedFileIDs.size() > 1
? Bundle.AddDrawableTagAction_displayName_plural()
: Bundle.AddDrawableTagAction_displayName_singular());
// Create a "Quick Tag" sub-menu.
Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag());
getItems().add(quickTagMenu);
/*
* Each non-Category tag name in the current set of tags gets its
* own menu item in the "Quick Tags" sub-menu. Selecting one of
* these menu items adds a tag with the associated tag name.
*/
Collection<TagName> tagNames = controller.getTagsManager().getNonCategoryTagNames();
if (tagNames.isEmpty()) {
MenuItem empty = new MenuItem(Bundle.AddTagAction_menuItem_noTags());
empty.setDisable(true);
quickTagMenu.getItems().add(empty);
} else {
for (final TagName tagName : tagNames) {
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
quickTagMenu.getItems().add(tagNameItem);
}
}
/*
* The "Quick Tag" menu also gets an "New Tag..." menu item.
* Selecting this item initiates a dialog that can be used to create
* or select a tag name and adds a tag with the resulting name.
*/
MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag());
newTagMenuItem.setOnAction(actionEvent ->
SwingUtilities.invokeLater(() -> {
TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
if (tagName != null) {
new AddTagAction(controller, tagName, selectedFileIDs).handle(actionEvent);
}
}));
quickTagMenu.getItems().add(newTagMenuItem);
/*
* Create a "Tag and Comment..." menu item. Selecting this item
* initiates a dialog that can be used to create or select a tag
* name with an optional comment and adds a tag with the resulting
* name.
*/
MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment());
tagAndCommentItem.setOnAction(actionEvent ->
SwingUtilities.invokeLater(() -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
if (null != tagNameAndComment) {
new AddTagAction(controller, tagNameAndComment.getTagName(), selectedFileIDs).addTagWithComment(tagNameAndComment.getComment());
}
}));
getItems().add(tagAndCommentItem);
}
}
static private Window getIGWindow() {
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
return SwingUtilities.getWindowAncestor(etc);
}
}