From 29bcda07ede99a422c715a4e0822629cea70b0d5 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Mon, 1 Sep 2014 00:19:17 -0500 Subject: [PATCH] Working single and multi delete for files. Laid the groundwork for other actions. Started working on a logger for searches to update the GUI as necessary. --- .../sothr/imagetools/engine/AppConfig.java | 7 ++ .../imagetools/engine/ConcurrentEngine.scala | 4 + .../com/sothr/imagetools/engine/Engine.scala | 22 +++++- .../imagetools/engine/dao/ImageDAO.scala | 7 ++ .../engine/image/ImageService.scala | 21 +++++- .../java/com/sothr/imagetools/ui/App.java | 11 ++- .../ui/component/ImageTilePane.scala | 73 ++++++++++++------- .../ui/controller/AppController.scala | 2 +- 8 files changed, 114 insertions(+), 33 deletions(-) diff --git a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java index ae580ba..b0acb9d 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java @@ -9,6 +9,7 @@ import ch.qos.logback.core.util.StatusPrinter; import com.sothr.imagetools.engine.dao.HibernateUtil; import com.sothr.imagetools.engine.util.PropertiesService; import com.sothr.imagetools.engine.util.ResourceLoader; +import javafx.fxml.FXMLLoader; import javafx.stage.Stage; import net.sf.ehcache.CacheManager; import org.slf4j.LoggerFactory; @@ -46,6 +47,12 @@ public class AppConfig { primaryStage = newPrimaryStage; } + public static FXMLLoader fxmlLoader = null; + + public static FXMLLoader getFxmlLoader() { return fxmlLoader; } + + public static void setFxmlLoader(FXMLLoader loader) { fxmlLoader = loader; } + public static ActorSystem getAppActorSystem() { return appSystem; } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala index 67458fa..3d9c9e1 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -19,6 +19,10 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { val engineSimilarityController = system.actorOf(Props[ConcurrentEngineSimilarityController], name = "EngineSimilarityController") implicit val timeout = Timeout(30, TimeUnit.SECONDS) + override def setSearchedListener(listenerRef: ActorRef) = { + + } + override def setProcessedListener(listenerRef: ActorRef) = { engineProcessingController ! SetNewListener(listenerRef) } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala index 17d1abc..a28a35b 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala @@ -2,8 +2,8 @@ package com.sothr.imagetools.engine import java.io.File -import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem} -import com.sothr.imagetools.engine.image.{Image, ImageFilter, SimilarImages} +import akka.actor._ +import com.sothr.imagetools.engine.image.{ImageService, Image, ImageFilter, SimilarImages} import com.sothr.imagetools.engine.util.DirectoryFilter import grizzled.slf4j.Logging @@ -19,6 +19,14 @@ abstract class Engine extends Logging { val imageFilter: ImageFilter = new ImageFilter() val imageCache = AppConfig.cacheManager.getCache("images") + //file search listener + var searchedListener = system.actorOf(Props[DefaultLoggingEngineListener], + name = "SearchedEngineListener") + + def setSearchedListener(listenerRef: ActorRef) = { + this.searchedListener = listenerRef + } + def setProcessedListener(listenerType: ActorRef) def setSimilarityListener(listenerType: ActorRef) @@ -55,6 +63,16 @@ abstract class Engine extends Logging { * Get all similar images for a directory with hashes */ def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] + + def deleteImage(image: Image): Unit = { + ImageService.deleteImage(image) + } + + def deleteImages(images: List[Image]): Unit = { + for (image <- images) { + deleteImage(image) + } + } } case class SubmitMessage(message: String) diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala index fc5887e..1a72bb8 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dao/ImageDAO.scala @@ -34,4 +34,11 @@ class ImageDAO { session.getTransaction.commit() } + def delete(image: Image) = { + val session: Session = sessionFactory.getCurrentSession + session.getTransaction.begin() + session.delete(image) + session.getTransaction.commit() + } + } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala index 93b0e8e..b4f7ecc 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala @@ -11,6 +11,7 @@ import com.sothr.imagetools.engine.util.{PropertiesService, PropertyEnum} import grizzled.slf4j.Logging import net.coobird.thumbnailator.Thumbnails import net.sf.ehcache.Element +import org.hibernate.HibernateException object ImageService extends Logging { @@ -73,12 +74,28 @@ object ImageService extends Logging { return saveImage(image) } } catch { - case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}", ioe) - case ex: Exception => error(s"Error processing ${file.getAbsolutePath}", ex) + case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}... ${ioe.getMessage}") + case ex: Exception => error(s"Error processing ${file.getAbsolutePath}... ${ex.getMessage}", ex) } null } + def deleteImage(image: Image) = { + debug(s"Attempting to delete all traces of image: ${image.getImagePath}") + try { + val imageFile = new File(image.imagePath) + //try to delete the file + imageFile.delete() + //purge the file from the database and cache + this.imageCache.remove(imageFile.getAbsolutePath) + this.imageDAO.delete(image) + } catch { + case se: SecurityException => error(s"Unable to delete file: ${image.getImagePath} due to a security exception", se) + case ise: IllegalStateException => error(s"Unable to delete file: ${image.getImagePath} due to an illegal state exception", ise) + case he: HibernateException => error(s"Unable to delete file: ${image.getImagePath} due to a hibernate exception", he) + } + } + def calculateThumbPath(md5: String): String = { //break the path down into 4 char parts val subPath = md5.substring(0, 3) diff --git a/gui/src/main/java/com/sothr/imagetools/ui/App.java b/gui/src/main/java/com/sothr/imagetools/ui/App.java index e559a0a..363570e 100644 --- a/gui/src/main/java/com/sothr/imagetools/ui/App.java +++ b/gui/src/main/java/com/sothr/imagetools/ui/App.java @@ -3,8 +3,10 @@ package com.sothr.imagetools.ui; import com.sothr.imagetools.engine.AppConfig; import com.sothr.imagetools.engine.errors.ImageToolsException; import com.sothr.imagetools.engine.util.ResourceLoader; +import com.sothr.imagetools.ui.controller.AppController; import javafx.application.Application; import javafx.fxml.FXMLLoader; +import javafx.fxml.JavaFXBuilderFactory; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; @@ -12,6 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.net.URL; import java.util.List; /** @@ -52,7 +55,13 @@ public class App extends Application { //store the primary stage globally for reference in popups and the like AppConfig.setPrimaryStage(primaryStage); try { - Parent root = FXMLLoader.load(ResourceLoader.get().getResource(MAINGUI_FXML)); + FXMLLoader loader = new FXMLLoader(); + URL location = ResourceLoader.get().getResource(MAINGUI_FXML); + loader.setLocation(location); + loader.setBuilderFactory(new JavaFXBuilderFactory()); + Parent root = loader.load(location.openStream()); + //save the primary controller + AppConfig.setFxmlLoader(loader); primaryStage.setScene(new Scene(root)); //config main scene primaryStage.setTitle("Image Tools"); diff --git a/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala b/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala index 41f1e18..6d549b4 100644 --- a/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala +++ b/gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTilePane.scala @@ -1,6 +1,7 @@ package com.sothr.imagetools.ui.component import java.util +import javafx.application.Platform import javafx.collections.{ModifiableObservableListBase, ObservableList} import javafx.event.{ActionEvent, EventHandler} import javafx.geometry.Side @@ -10,21 +11,22 @@ import javafx.scene.layout._ import javafx.scene.paint.Color import javafx.scene.Node +import scala.concurrent._ +import ExecutionContext.Implicits.global + +import com.sothr.imagetools.engine.AppConfig +import com.sothr.imagetools.ui.controller.AppController import grizzled.slf4j.Logging +import scala.util.{Failure, Success} + /** * Custom Tile Pane with a multi selection model * * Created by drew on 8/29/14. */ class ImageTilePane extends TilePane with Logging { - val selectionModel = new ImageTilePaneSelectionModel(this) - - //this.setOnContextMenuRequested(new EventHandler[ContextMenuEvent] { - // override def handle(event: ContextMenuEvent): Unit = { - // handleContextMenu(event) - // } - //}) + val selectionModel = new ImageTilePaneSelectionModel[ImageTile](this) def handleContextMenu(event: ContextMenuEvent) = { //Build and show a context menu @@ -46,38 +48,28 @@ class ImageTilePane extends TilePane with Logging { def getSingleSelectionContextMenu : ContextMenu = { debug("Building single-selection context menu") val contextMenu = new ContextMenu() - val item1 = new MenuItem("Single Selection") - item1.setOnAction(new EventHandler[ActionEvent]() { + val delete = new MenuItem("Delete") + delete.setOnAction(new EventHandler[ActionEvent]() { def handle(e: ActionEvent) = { - debug("Single Selection") + debug("Requesting Single Delete") + deleteSelected() } }) - val item2 = new MenuItem("BlahBlah") - item2.setOnAction(new EventHandler[ActionEvent]() { - def handle(e: ActionEvent) = { - debug("BlahBlah") - } - }) - contextMenu.getItems.addAll(item1, item2) + contextMenu.getItems.addAll(delete) contextMenu } def getMulipleSelectionContextMenu : ContextMenu = { debug("Building multi-selection context menu") val contextMenu = new ContextMenu() - val item1 = new MenuItem("Multi Selection") - item1.setOnAction(new EventHandler[ActionEvent]() { - def handle(e: ActionEvent) = { - debug("Multi Selection") - } - }) - val item2 = new MenuItem("BlahBlah") - item2.setOnAction(new EventHandler[ActionEvent]() { + val delete = new MenuItem("Delete") + delete.setOnAction(new EventHandler[ActionEvent]() { def handle(e: ActionEvent) = { - debug("BlahBlah") + debug("Requesting Multiple Delete") + deleteSelected() } }) - contextMenu.getItems.addAll(item1, item2) + contextMenu.getItems.addAll(delete) contextMenu } @@ -97,6 +89,33 @@ class ImageTilePane extends TilePane with Logging { this.selectionModel.clearSelection() } + //request deletion of selected images + def deleteSelected() = { + val f: Future[Unit] = Future { + val selected = this.selectionModel.getSelectedItems + val iterator = selected.iterator() + while (iterator.hasNext) { + val item = iterator.next() + val imageTile = item.asInstanceOf[ImageTile] + val data = imageTile.imageData + val controller = AppConfig.getFxmlLoader.getController[AppController]() + controller.engine.deleteImage(data) + + } + val pane = this + Platform.runLater(new Runnable() { + override def run(): Unit = { + //remove from the current panel + pane.getChildren.removeAll(selected) + clearSelection() + } + }) + } + f onComplete { + case Success(a) => info("Successfully deleted files") + case Failure(f) => error("Failed to delete files", f) + } + } } /** diff --git a/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala b/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala index bdedbe7..b973e22 100644 --- a/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala +++ b/gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala @@ -224,7 +224,7 @@ class AppController extends Logging { tempImages += similarImage.rootImage similarImage.similarImages.foreach(image => tempImages += image) } - tempImages.toList.sortWith((x,y) => x.imagePath < y.imagePath) + tempImages.toList } f onComplete {