From 031133caaa349e6f4bc21fc5fe20dcbbb36c3608 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Tue, 2 Jan 2018 15:16:31 -0600 Subject: [PATCH] Refactor: Code Style Cleanup * Whitespace fixes (tabs vs spaces for portability) * EOF whitespace removal --- cli/pom.xml | 226 ++--- cli/src/includes/logback.xml | 144 +-- .../java/com/sothr/imagetools/cli/AppCLI.java | 182 ++-- engine/pom.xml | 430 ++++----- .../sothr/imagetools/engine/AppConfig.java | 254 ++--- .../engine/errors/ImageToolsException.java | 26 +- .../imagetools/engine/image/ImageType.java | 4 +- .../engine/util/ResourceLoader.java | 34 +- engine/src/main/resources/ehcache.xml | 14 +- engine/src/main/resources/hibernate.cfg.xml | 80 +- .../main/resources/hibernate/Image.hbm.xml | 26 +- .../resources/hibernate/ImageHash.hbm.xml | 28 +- .../main/resources/logback-minimum-config.xml | 40 +- .../imagetools/engine/ConcurrentEngine.scala | 762 +++++++-------- .../com/sothr/imagetools/engine/Engine.scala | 348 +++---- .../imagetools/engine/SequentialEngine.scala | 150 +-- .../imagetools/engine/dao/HibernateUtil.scala | 54 +- .../imagetools/engine/dao/ImageDAO.scala | 71 +- .../sothr/imagetools/engine/image/Image.scala | 195 ++-- .../imagetools/engine/image/ImageFilter.scala | 26 +- .../engine/image/ImageService.scala | 256 ++--- .../engine/image/SimilarImages.scala | 58 +- .../engine/util/DirectoryFilter.scala | 16 +- .../imagetools/engine/util/Hamming.scala | 24 +- .../engine/util/PropertiesService.scala | 226 ++--- .../imagetools/engine/util/PropertyEnum.scala | 56 +- .../sothr/imagetools/engine/util/Timing.scala | 49 +- .../imagetools/engine/util/Version.scala | 164 ++-- .../imagetools/engine/vo/ImageHashVO.scala | 76 +- .../com/sothr/imagetools/engine/AppTest.java | 42 +- engine/src/test/resources/ehcache.xml | 12 +- engine/src/test/resources/hibernate.cfg.xml | 74 +- .../test/resources/hibernate/Image.hbm.xml | 26 +- .../resources/hibernate/ImageHash.hbm.xml | 28 +- .../test/resources/logback-minimum-config.xml | 112 +-- .../sothr/imagetools/engine/BaseTest.scala | 8 +- .../sothr/imagetools/engine/EngineTest.scala | 74 +- .../imagetools/engine/ScalaAppTest.scala | 8 +- .../sothr/imagetools/engine/TestParams.scala | 6 +- .../engine/image/ImageFilterTest.scala | 62 +- gui/pom.xml | 234 ++--- gui/src/includes/logback.xml | 144 +-- .../java/com/sothr/imagetools/ui/App.java | 138 +-- .../main/resources/fxml/mainapp/MainApp.fxml | 395 ++++---- .../imagetools/ui/component/ImageTile.scala | 186 ++-- .../ui/component/ImageTileFactory.scala | 25 +- .../ui/component/ImageTilePane.scala | 491 +++++----- .../ui/controller/AppController.scala | 843 +++++++++-------- .../sothr/imagetools/ui/util/FileUtil.scala | 34 +- hash/pom.xml | 4 +- .../sothr/imagetools/hash/HashService.scala | 32 +- parent/pom.xml | 892 +++++++++--------- pom.xml | 42 +- 53 files changed, 3975 insertions(+), 3956 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 980eb03..d74c730 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -1,122 +1,122 @@ - 4.0.0 + 4.0.0 - - com.sothr.imagetools - parent - 1.0.1 - ../parent - + + com.sothr.imagetools + parent + 1.0.1 + ../parent + - cli - 0.1.1 - jar + cli + 0.1.1 + jar - ImageTools-CLI - The Command Line Interface for Image-Tools - http://imagetools.sothr.com - - Sothr Software - + ImageTools-CLI + The Command Line Interface for Image-Tools + http://imagetools.sothr.com + + Sothr Software + - - - com.sothr.imagetools - engine - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-access - - - org.slf4j - slf4j-api - - - org.clapper - grizzled-slf4j_${scala.binary.version} - - - org.scala-lang - scala-library - - + + + com.sothr.imagetools + engine + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-access + + + org.slf4j + slf4j-api + + + org.clapper + grizzled-slf4j_${scala.binary.version} + + + org.scala-lang + scala-library + + - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - package - - jar - - - - - true - lib/ - com.sothr.imagetools.cli.AppCLI - - - - ${project.build.directory}/release - - - - - - - maven-antrun-plugin - 1.4 - - - prepare - process-resources - - - - - - - - - - run - - - - package - package - - - - - - - - run - - - - - - + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + package + + jar + + + + + true + lib/ + com.sothr.imagetools.cli.AppCLI + + + + ${project.build.directory}/release + + + + + + + maven-antrun-plugin + 1.4 + + + prepare + process-resources + + + + + + + + + + run + + + + package + package + + + + + + + + run + + + + + + diff --git a/cli/src/includes/logback.xml b/cli/src/includes/logback.xml index 077b4d1..845280a 100644 --- a/cli/src/includes/logback.xml +++ b/cli/src/includes/logback.xml @@ -1,75 +1,75 @@ - - - - - - false - [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - - - INFO - - - - - ImageTools.debug - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - DEBUG - - - 1 - ImageTools.debug.%i - - - 5MB - - - - - ImageTools.info - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - INFO - - - 1 - ImageTools.info.%i - - - 500KB - - - - - ImageTools.err - - false - [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - ERROR - - - 1 - ImageTools.err.%i - - - 500KB - - - - - - - - + + + + + + false + [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n + + + INFO + + + + + ImageTools.debug + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + DEBUG + + + 1 + ImageTools.debug.%i + + + 5MB + + + + + ImageTools.info + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + INFO + + + 1 + ImageTools.info.%i + + + 500KB + + + + + ImageTools.err + + false + [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + ERROR + + + 1 + ImageTools.err.%i + + + 500KB + + + + + + + + \ No newline at end of file diff --git a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java index 9275ec4..aa18a4e 100644 --- a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java +++ b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java @@ -8,7 +8,13 @@ import com.sothr.imagetools.engine.CLIEngineListener; import com.sothr.imagetools.engine.ConcurrentEngine; import com.sothr.imagetools.engine.Engine; import com.sothr.imagetools.engine.image.SimilarImages; -import org.apache.commons.cli.*; +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.collection.immutable.List; @@ -18,101 +24,101 @@ import scala.collection.immutable.List; */ class AppCLI { - private static final String HEADER = "Process images and search for duplicates and similar images heuristically"; - private static final String FOOTER = "Please report issues to..."; - private static Logger logger; + private static final String HEADER = "Process images and search for duplicates and similar images heuristically"; + private static final String FOOTER = "Please report issues to..."; + private static Logger logger; - public static void main(String[] args) { - try { - Options options = getOptions(); - CommandLineParser parser = new BasicParser(); - CommandLine cmd = parser.parse(options, args); - if (cmd.hasOption('h') || cmd.getOptions().length < 1 || cmd.getArgs().length > 0) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("Image-Tools CLI", HEADER, options, FOOTER, true); - } else { - AppConfig.configureApp(); - logger = LoggerFactory.getLogger(AppCLI.class); - logger.info("Started Image Tools CLI"); - process(cmd); - AppConfig.shutdown(); - } - System.exit(0); - } catch (Exception ex) { - logger.error("Unhandled exception in AppCLI", ex); - } - } + public static void main(String[] args) { + try { + Options options = getOptions(); + CommandLineParser parser = new BasicParser(); + CommandLine cmd = parser.parse(options, args); + if (cmd.hasOption('h') || cmd.getOptions().length < 1 || cmd.getArgs().length > 0) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Image-Tools CLI", HEADER, options, FOOTER, true); + } else { + AppConfig.configureApp(); + logger = LoggerFactory.getLogger(AppCLI.class); + logger.info("Started Image Tools CLI"); + process(cmd); + AppConfig.shutdown(); + } + System.exit(0); + } catch (Exception ex) { + logger.error("Unhandled exception in AppCLI", ex); + } + } - private static Options getOptions() { - //scan a list of directories - Options options = new Options(); + private static Options getOptions() { + //scan a list of directories + Options options = new Options(); - //show help - Option helpOption = OptionBuilder.create('h'); - helpOption.setLongOpt("help"); - helpOption.setDescription("Display this help dialog"); - options.addOption(helpOption); + //show help + Option helpOption = OptionBuilder.create('h'); + helpOption.setLongOpt("help"); + helpOption.setDescription("Display this help dialog"); + options.addOption(helpOption); - //scan directories - Option scanOption = OptionBuilder.create('s'); - scanOption.setLongOpt("scan"); - scanOption.setDescription("Scan directories for a list of similar images"); - scanOption.setArgs(1); - scanOption.setArgName("DIRECTORY"); - options.addOption(scanOption); + //scan directories + Option scanOption = OptionBuilder.create('s'); + scanOption.setLongOpt("scan"); + scanOption.setDescription("Scan directories for a list of similar images"); + scanOption.setArgs(1); + scanOption.setArgName("DIRECTORY"); + options.addOption(scanOption); - //scan directories in a recursive manner - Option recursiveOption = OptionBuilder.create('r'); - recursiveOption.setLongOpt("recursive"); - recursiveOption.setDescription("Scan directories recursively"); - options.addOption(recursiveOption); + //scan directories in a recursive manner + Option recursiveOption = OptionBuilder.create('r'); + recursiveOption.setLongOpt("recursive"); + recursiveOption.setDescription("Scan directories recursively"); + options.addOption(recursiveOption); - //depth limit - Option depthOption = OptionBuilder.create('d'); - depthOption.setLongOpt("depth"); - depthOption.setDescription("Limit the maximum depth of the recursive search"); - depthOption.setArgs(1); - depthOption.setArgName("INTEGER"); - options.addOption(depthOption); - return options; - } + //depth limit + Option depthOption = OptionBuilder.create('d'); + depthOption.setLongOpt("depth"); + depthOption.setDescription("Limit the maximum depth of the recursive search"); + depthOption.setArgs(1); + depthOption.setArgName("INTEGER"); + options.addOption(depthOption); + return options; + } - private static void process(CommandLine cmd) { - //scan a comma separated list of paths to search for image similarities - try { - Engine engine = new ConcurrentEngine(); + private static void process(CommandLine cmd) { + //scan a comma separated list of paths to search for image similarities + try { + Engine engine = new ConcurrentEngine(); - //create the listeners that will be passed onto the actors - ActorSystem system = AppConfig.getAppActorSystem(); - Props cliListenerProps = Props.create(CLIEngineListener.class); - ActorRef cliListener = system.actorOf(cliListenerProps); + //create the listeners that will be passed onto the actors + ActorSystem system = AppConfig.getAppActorSystem(); + Props cliListenerProps = Props.create(CLIEngineListener.class); + ActorRef cliListener = system.actorOf(cliListenerProps); - //set the listeners - engine.setProcessedListener(cliListener); - engine.setSimilarityListener(cliListener); + //set the listeners + engine.setProcessedListener(cliListener); + engine.setSimilarityListener(cliListener); - if (cmd.hasOption('s')) { - Boolean recursive = false; - Integer recursiveDepth = 500; - if (cmd.hasOption('r')) { - recursive = true; - } - if (cmd.hasOption('d')) { - recursiveDepth = Integer.parseInt(cmd.getOptionValue('d')); - } - String scanList = cmd.getOptionValue('s'); - String[] paths = scanList.split(","); - for (String path : paths) { - List similarImages = engine.getSimilarImagesForDirectory(path, recursive, recursiveDepth); - for (int index = 0; index < similarImages.length(); index++) { - SimilarImages similar = similarImages.apply(index); - System.out.println(similar.toString()); - } - } - } - } catch (Exception ex) { - throw new IllegalArgumentException("One or more arguments could not be parsed correctly", ex); - } - } -} + if (cmd.hasOption('s')) { + Boolean recursive = false; + Integer recursiveDepth = 500; + if (cmd.hasOption('r')) { + recursive = true; + } + if (cmd.hasOption('d')) { + recursiveDepth = Integer.parseInt(cmd.getOptionValue('d')); + } + String scanList = cmd.getOptionValue('s'); + String[] paths = scanList.split(","); + for (String path : paths) { + List similarImages = engine.getSimilarImagesForDirectory(path, recursive, recursiveDepth); + for (int index = 0; index < similarImages.length(); index++) { + SimilarImages similar = similarImages.apply(index); + System.out.println(similar.toString()); + } + } + } + } catch (Exception ex) { + throw new IllegalArgumentException("One or more arguments could not be parsed correctly", ex); + } + } +} \ No newline at end of file diff --git a/engine/pom.xml b/engine/pom.xml index 4a88187..4848cd5 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -1,223 +1,223 @@ - 4.0.0 + 4.0.0 - - com.sothr.imagetools - parent - 1.0.1 - ../parent - + + com.sothr.imagetools + parent + 1.0.1 + ../parent + - engine - 0.1.3 - jar - ImageTools-Engine - An image collection management utility - http://imagetools.sothr.com - - Sothr Software - + engine + 0.1.3 + jar + ImageTools-Engine + An image collection management utility + http://imagetools.sothr.com + + Sothr Software + - - - com.sothr.imagetools - hash - - - junit - junit - test - - - org.scalatest - scalatest_${scala.binary.version} - test - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-access - - - org.slf4j - slf4j-api - - - org.clapper - grizzled-slf4j_${scala.binary.version} - - - org.scala-lang - scala-library - - - com.jsuereth - scala-arm_${scala.binary.version} - - - net.coobird - thumbnailator - - - com.typesafe - config - - - net.sourceforge.jtransforms - jtransforms - - - commons-cli - commons-cli - - - commons-codec - commons-codec - - - javax.transaction - jta - - - net.sf.ehcache - ehcache - - - com.typesafe.akka - akka-actor_${scala.binary.version} - - - com.typesafe.akka - akka-slf4j_${scala.binary.version} - - - com.h2database - h2 - - - org.hibernate - hibernate-core - - - org.hibernate - hibernate-ehcache - - - net.sf.ehcache - ehcache-core - - - org.hibernate - hibernate-c3p0 - - - org.commonjava.googlecode.markdown4j - markdown4j - - - com.jsuereth - scala-arm_${scala.binary.version} - - - com.twelvemonkeys.imageio - imageio-jpeg - - + + + com.sothr.imagetools + hash + + + junit + junit + test + + + org.scalatest + scalatest_${scala.binary.version} + test + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-access + + + org.slf4j + slf4j-api + + + org.clapper + grizzled-slf4j_${scala.binary.version} + + + org.scala-lang + scala-library + + + com.jsuereth + scala-arm_${scala.binary.version} + + + net.coobird + thumbnailator + + + com.typesafe + config + + + net.sourceforge.jtransforms + jtransforms + + + commons-cli + commons-cli + + + commons-codec + commons-codec + + + javax.transaction + jta + + + net.sf.ehcache + ehcache + + + com.typesafe.akka + akka-actor_${scala.binary.version} + + + com.typesafe.akka + akka-slf4j_${scala.binary.version} + + + com.h2database + h2 + + + org.hibernate + hibernate-core + + + org.hibernate + hibernate-ehcache + + + net.sf.ehcache + ehcache-core + + + org.hibernate + hibernate-c3p0 + + + org.commonjava.googlecode.markdown4j + markdown4j + + + com.jsuereth + scala-arm_${scala.binary.version} + + + com.twelvemonkeys.imageio + imageio-jpeg + + - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.7 - - false - - - - - org.scalatest - scalatest-maven-plugin - 1.0-RC2 - - ${project.build.directory}/surefire-reports - . - WDF TestSuite.txt - -Xmx128m - - - - test - - test - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - package - - jar - - - - - true - lib/ - - - - ${project.build.directory}/release - - - - - - - - maven-antrun-plugin - 1.4 - - - prepare - process-resources - - - - - - - - - - - - - run - - - - - - + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.7 + + false + + + + + org.scalatest + scalatest-maven-plugin + 1.0-RC2 + + ${project.build.directory}/surefire-reports + . + WDF TestSuite.txt + -Xmx128m + + + + test + + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + package + + jar + + + + + true + lib/ + + + + ${project.build.directory}/release + + + + + + + + maven-antrun-plugin + 1.4 + + + prepare + process-resources + + + + + + + + + + + + + run + + + + + + 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 3369ed1..b8e9bb1 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java @@ -16,130 +16,130 @@ import org.slf4j.LoggerFactory; import java.io.File; -public class AppConfig { - - // Logging defaults - private static final String LOGSETTINGSFILE = "./logback.xml"; - // Properties defaults - private static final String DEFAULTPROPERTIESFILE = "application.conf"; - private static final String USERPROPERTIESFILE = "user.conf"; - // General Akka Actor System - private static final ActorSystem appSystem = ActorSystem.create("ITActorSystem"); - public static CacheManager cacheManager; - public static FXMLLoader fxmlLoader = null; - private static Logger logger; - private static Boolean configuredLogging = false; - private static Boolean loadedProperties = false; - // Cache defaults - private static Boolean configuredCache = false; - // The Main App - private static Stage primaryStage = null; - - public static Stage getPrimaryStage() { - return primaryStage; - } - - public static void setPrimaryStage(Stage newPrimaryStage) { - primaryStage = newPrimaryStage; - } - - public static FXMLLoader getFxmlLoader() { - return fxmlLoader; - } - - public static void setFxmlLoader(FXMLLoader loader) { - fxmlLoader = loader; - } - - public static ActorSystem getAppActorSystem() { - return appSystem; - } - - public static void configureApp() { - logger = (Logger) LoggerFactory.getLogger(AppConfig.class); - loadProperties(); - configLogging(); - configCache(); - } - - private static void configLogging(String location) { - //Logging Config - //remove previous configuration if it exists - Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - LoggerContext context = rootLogger.getLoggerContext(); - context.reset(); - File file = new File(location); - Boolean fromFile = false; - if (file.exists()) { - fromFile = true; - try { - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(context); - // Call context.reset() to clear any previous configuration, e.g. default - // configuration. For multi-step configuration, omit calling context.reset(). - context.reset(); - configurator.doConfigure(location); - } catch (JoranException je) { - // StatusPrinter will handle this - } - StatusPrinter.printInCaseOfErrorsOrWarnings(context); - } else { - try { - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(context); - // Call context.reset() to clear any previous configuration, e.g. default - // configuration. For multi-step configuration, omit calling context.reset(). - context.reset(); - configurator.doConfigure(ResourceLoader.get().getResourceStream("logback-minimum-config.xml")); - } catch (JoranException je) { - // StatusPrinter will handle this - } - StatusPrinter.printInCaseOfErrorsOrWarnings(context); - } - String message = fromFile ? "From File" : "From Defaults"; - logger.info(String.format("Configured Logger %s", message)); - logger.info(String.format("Detected Version: %s of Image Tools", PropertiesService.getVersion().toString())); - logger.info(String.format("Running on %s, %s, %s", PropertiesService.OS(), PropertiesService.OS_VERSION(), PropertiesService.OS_ARCH())); - } - - //Only configure logging from the default file once - private static void configLogging() { - if (!configuredLogging) { - configLogging(LOGSETTINGSFILE); - configuredLogging = true; - logger.info("Configured logging"); - } - } - - private static void loadProperties() { - if (!loadedProperties) { - File file = new File(USERPROPERTIESFILE); - if (file.exists()) { - PropertiesService.loadProperties(DEFAULTPROPERTIESFILE, USERPROPERTIESFILE); - } else { - PropertiesService.loadProperties(DEFAULTPROPERTIESFILE, null); - } - loadedProperties = true; - logger.info("Loaded Properties"); - } - } - - private static void configCache() { - if (!configuredCache) { - cacheManager = CacheManager.newInstance(); - configuredCache = true; - logger.info("Configured EHCache"); - } - } - - public static void shutdown() { - saveProperties(); - HibernateUtil.getSessionFactory().close(); - } - - private static void saveProperties() { - PropertiesService.saveConf(USERPROPERTIESFILE); - logger.debug("Saved properties"); - } - -} +public class AppConfig { + + // Logging defaults + private static final String LOGSETTINGSFILE = "./logback.xml"; + // Properties defaults + private static final String DEFAULTPROPERTIESFILE = "application.conf"; + private static final String USERPROPERTIESFILE = "user.conf"; + // General Akka Actor System + private static final ActorSystem appSystem = ActorSystem.create("ITActorSystem"); + public static CacheManager cacheManager; + public static FXMLLoader fxmlLoader = null; + private static Logger logger; + private static Boolean configuredLogging = false; + private static Boolean loadedProperties = false; + // Cache defaults + private static Boolean configuredCache = false; + // The Main App + private static Stage primaryStage = null; + + public static Stage getPrimaryStage() { + return primaryStage; + } + + public static void setPrimaryStage(Stage newPrimaryStage) { + primaryStage = newPrimaryStage; + } + + public static FXMLLoader getFxmlLoader() { + return fxmlLoader; + } + + public static void setFxmlLoader(FXMLLoader loader) { + fxmlLoader = loader; + } + + public static ActorSystem getAppActorSystem() { + return appSystem; + } + + public static void configureApp() { + logger = (Logger) LoggerFactory.getLogger(AppConfig.class); + loadProperties(); + configLogging(); + configCache(); + } + + private static void configLogging(String location) { + //Logging Config + //remove previous configuration if it exists + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + LoggerContext context = rootLogger.getLoggerContext(); + context.reset(); + File file = new File(location); + Boolean fromFile = false; + if (file.exists()) { + fromFile = true; + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + // Call context.reset() to clear any previous configuration, e.g. default + // configuration. For multi-step configuration, omit calling context.reset(). + context.reset(); + configurator.doConfigure(location); + } catch (JoranException je) { + // StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } else { + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + // Call context.reset() to clear any previous configuration, e.g. default + // configuration. For multi-step configuration, omit calling context.reset(). + context.reset(); + configurator.doConfigure(ResourceLoader.get().getResourceStream("logback-minimum-config.xml")); + } catch (JoranException je) { + // StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + String message = fromFile ? "From File" : "From Defaults"; + logger.info(String.format("Configured Logger %s", message)); + logger.info(String.format("Detected Version: %s of Image Tools", PropertiesService.getVersion().toString())); + logger.info(String.format("Running on %s, %s, %s", PropertiesService.OS(), PropertiesService.OS_VERSION(), PropertiesService.OS_ARCH())); + } + + //Only configure logging from the default file once + private static void configLogging() { + if (!configuredLogging) { + configLogging(LOGSETTINGSFILE); + configuredLogging = true; + logger.info("Configured logging"); + } + } + + private static void loadProperties() { + if (!loadedProperties) { + File file = new File(USERPROPERTIESFILE); + if (file.exists()) { + PropertiesService.loadProperties(DEFAULTPROPERTIESFILE, USERPROPERTIESFILE); + } else { + PropertiesService.loadProperties(DEFAULTPROPERTIESFILE, null); + } + loadedProperties = true; + logger.info("Loaded Properties"); + } + } + + private static void configCache() { + if (!configuredCache) { + cacheManager = CacheManager.newInstance(); + configuredCache = true; + logger.info("Configured EHCache"); + } + } + + public static void shutdown() { + saveProperties(); + HibernateUtil.getSessionFactory().close(); + } + + private static void saveProperties() { + PropertiesService.saveConf(USERPROPERTIESFILE); + logger.debug("Saved properties"); + } + +} \ No newline at end of file diff --git a/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java b/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java index b4ae892..4d9eb2c 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/errors/ImageToolsException.java @@ -7,20 +7,20 @@ package com.sothr.imagetools.engine.errors; */ public class ImageToolsException extends Exception { - public ImageToolsException() { - super(); - } + public ImageToolsException() { + super(); + } - public ImageToolsException(String message) { - super(message); - } + public ImageToolsException(String message) { + super(message); + } - public ImageToolsException(String message, Throwable cause) { - super(message, cause); - } + public ImageToolsException(String message, Throwable cause) { + super(message, cause); + } - public ImageToolsException(Throwable cause) { - super(cause); - } + public ImageToolsException(Throwable cause) { + super(cause); + } -} +} \ No newline at end of file diff --git a/engine/src/main/java/com/sothr/imagetools/engine/image/ImageType.java b/engine/src/main/java/com/sothr/imagetools/engine/image/ImageType.java index 610514e..cfb49ef 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/image/ImageType.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/image/ImageType.java @@ -1,5 +1,5 @@ package com.sothr.imagetools.engine.image; public enum ImageType { - SingleFrameImage, MultiFrameImage -} + SingleFrameImage, MultiFrameImage +} \ No newline at end of file diff --git a/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java b/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java index c85e6a5..8f793b7 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/util/ResourceLoader.java @@ -13,26 +13,26 @@ import java.net.URL; */ public class ResourceLoader { - private static final ResourceLoader instance = new ResourceLoader(); + private static final ResourceLoader instance = new ResourceLoader(); - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private ResourceLoader() { - logger.info("Created Resource Loader"); - } + private ResourceLoader() { + logger.info("Created Resource Loader"); + } - public static ResourceLoader get() { - return instance; - } + public static ResourceLoader get() { + return instance; + } - public URL getResource(String location) { - logger.debug(String.format("Attempting to load resource: %s", location)); - return Thread.currentThread().getContextClassLoader().getResource(location); - } + public URL getResource(String location) { + logger.debug(String.format("Attempting to load resource: %s", location)); + return Thread.currentThread().getContextClassLoader().getResource(location); + } - public InputStream getResourceStream(String location) { - logger.debug(String.format("Attempting to get stream for resource: %s", location)); - return Thread.currentThread().getContextClassLoader().getResourceAsStream(location); - } + public InputStream getResourceStream(String location) { + logger.debug(String.format("Attempting to get stream for resource: %s", location)); + return Thread.currentThread().getContextClassLoader().getResourceAsStream(location); + } -} +} \ No newline at end of file diff --git a/engine/src/main/resources/ehcache.xml b/engine/src/main/resources/ehcache.xml index ef3a96d..1a7019b 100644 --- a/engine/src/main/resources/ehcache.xml +++ b/engine/src/main/resources/ehcache.xml @@ -1,9 +1,9 @@ - - - - - + + + + + \ No newline at end of file diff --git a/engine/src/main/resources/hibernate.cfg.xml b/engine/src/main/resources/hibernate.cfg.xml index 4de017a..63a59ae 100644 --- a/engine/src/main/resources/hibernate.cfg.xml +++ b/engine/src/main/resources/hibernate.cfg.xml @@ -1,51 +1,51 @@ + "-//Hibernate/Hibernate Configuration DTD//EN" + "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> - - + + - - org.h2.Driver - - org.hibernate.dialect.H2Dialect - false - - - - java:comp/UserTransaction + + org.h2.Driver + + org.hibernate.dialect.H2Dialect + false + + + + java:comp/UserTransaction - update + update - - thread + + thread - - org.hibernate.cache.ehcache.EhCacheRegionFactory - - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory - - true + + org.hibernate.cache.ehcache.EhCacheRegionFactory + + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + + true - 1 - 100 - - 50 - 0 - 5 - 100 - + 1 + 100 + + 50 + 0 + 5 + 100 + - - - + + + - - - + + + \ No newline at end of file diff --git a/engine/src/main/resources/hibernate/Image.hbm.xml b/engine/src/main/resources/hibernate/Image.hbm.xml index bbe679b..c662c7a 100644 --- a/engine/src/main/resources/hibernate/Image.hbm.xml +++ b/engine/src/main/resources/hibernate/Image.hbm.xml @@ -1,17 +1,17 @@ + "-//Hibernate/Hibernate Mapping DTD//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - - - This class contains the image hashes and meta data - - - - - - - + + + This class contains the image hashes and meta data + + + + + + + \ No newline at end of file diff --git a/engine/src/main/resources/hibernate/ImageHash.hbm.xml b/engine/src/main/resources/hibernate/ImageHash.hbm.xml index 3d79acb..7cac328 100644 --- a/engine/src/main/resources/hibernate/ImageHash.hbm.xml +++ b/engine/src/main/resources/hibernate/ImageHash.hbm.xml @@ -1,18 +1,18 @@ + "-//Hibernate/Hibernate Mapping DTD//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - - - This class contains the image hashes - - - - - - - - - + + + This class contains the image hashes + + + + + + + + + \ No newline at end of file diff --git a/engine/src/main/resources/logback-minimum-config.xml b/engine/src/main/resources/logback-minimum-config.xml index 3d5fd20..0203de6 100644 --- a/engine/src/main/resources/logback-minimum-config.xml +++ b/engine/src/main/resources/logback-minimum-config.xml @@ -1,23 +1,23 @@ - - ImageTools.err - - false - [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - - - ERROR - - - 5 - ImageTools.err.%i - - - 500KB - - - - - + + ImageTools.err + + false + [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n + + + ERROR + + + 5 + ImageTools.err.%i + + + 500KB + + + + + \ No newline at end of file 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 7ad6e0e..d245b0c 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -7,101 +7,101 @@ import akka.actor.{Actor, ActorLogging, ActorRef, PoisonPill, Props} import akka.pattern.ask import akka.routing.{Broadcast, RoundRobinPool, SmallestMailboxPool} import akka.util.Timeout -import com.sothr.imagetools.hash.{HashService, ImageHash} import com.sothr.imagetools.engine.image.{Image, ImageService, SimilarImages} import com.sothr.imagetools.engine.util.{PropertiesService, PropertyEnum} +import com.sothr.imagetools.hash.HashService import scala.collection.mutable import scala.concurrent.Await class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { - val engineProcessingController = system.actorOf(Props[ConcurrentEngineProcessingController], name = "EngineProcessingController") - val engineSimilarityController = system.actorOf(Props[ConcurrentEngineSimilarityController], name = "EngineSimilarityController") - implicit val timeout = Timeout(30, TimeUnit.SECONDS) - - override def setSearchedListener(listenerRef: ActorRef) = { - this.searchedListener = listenerRef - } - - override def setProcessedListener(listenerRef: ActorRef) = { - engineProcessingController ! SetNewListener(listenerRef) - } - - override def setSimilarityListener(listenerRef: ActorRef) = { - engineSimilarityController ! SetNewListener(listenerRef) - } - - //needs to be rebuilt - def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { - debug(s"Looking for similar images in directory: $directoryPath") - val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) - info(s"Searching ${images.length} images for similarities") - // make sure the engine is listening - engineSimilarityController ! EngineStart - for (rootImage <- images) { - debug(s"Looking for images similar to: ${rootImage.imagePath}") - engineSimilarityController ! EngineCompareImages(rootImage, images) - } - //tell the comparison engine there's nothing left to compare - engineSimilarityController ! EngineNoMoreComparisons - var doneProcessing = false - while (!doneProcessing) { - val f = engineSimilarityController ? EngineIsSimilarityFinished - val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] - result match { - case true => - doneProcessing = true - debug("Processing Complete") - case false => - debug("Still Processing") - //sleep thread - Thread.sleep(5000L) - //val future = Future { blocking(Thread.sleep(5000L)); "done" } - } - } - val f = engineSimilarityController ? EngineGetSimilarityResults - val result = Await.result(f, timeout.duration).asInstanceOf[List[SimilarImages]] - - val cleanedSimilarImages = this.processSimilarities(result) - var similarCount = 0 - for (similarImage <- cleanedSimilarImages) { - similarCount += 1 + similarImage.similarImages.size - } - - info(s"Finished processing ${images.size} images. Found $similarCount similar images") - cleanedSimilarImages - } - - def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { - debug(s"Looking for images in directory: $directoryPath") - val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() - // make sure the engine is listening - engineProcessingController ! EngineStart - for (file <- imageFiles) { - engineProcessingController ! EngineProcessFile(file) - } - engineProcessingController ! EngineNoMoreFiles - var doneProcessing = false - while (!doneProcessing) { - val f = engineProcessingController ? EngineIsProcessingFinished - val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] - result match { - case true => - doneProcessing = true - debug("Processing Complete") - case false => - debug("Still Processing") - //sleep thread - Thread.sleep(5000L) - //val future = Future { blocking(Thread.sleep(5000L)); "done" } - } - } - val f = engineProcessingController ? EngineGetProcessingResults - val result = Await.result(f, timeout.duration).asInstanceOf[List[Image]] - images ++= result - images.toList - } + val engineProcessingController = system.actorOf(Props[ConcurrentEngineProcessingController], name = "EngineProcessingController") + val engineSimilarityController = system.actorOf(Props[ConcurrentEngineSimilarityController], name = "EngineSimilarityController") + implicit val timeout = Timeout(30, TimeUnit.SECONDS) + + override def setSearchedListener(listenerRef: ActorRef) = { + this.searchedListener = listenerRef + } + + override def setProcessedListener(listenerRef: ActorRef) = { + engineProcessingController ! SetNewListener(listenerRef) + } + + override def setSimilarityListener(listenerRef: ActorRef) = { + engineSimilarityController ! SetNewListener(listenerRef) + } + + //needs to be rebuilt + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { + debug(s"Looking for similar images in directory: $directoryPath") + val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) + info(s"Searching ${images.length} images for similarities") + // make sure the engine is listening + engineSimilarityController ! EngineStart + for (rootImage <- images) { + debug(s"Looking for images similar to: ${rootImage.imagePath}") + engineSimilarityController ! EngineCompareImages(rootImage, images) + } + //tell the comparison engine there's nothing left to compare + engineSimilarityController ! EngineNoMoreComparisons + var doneProcessing = false + while (!doneProcessing) { + val f = engineSimilarityController ? EngineIsSimilarityFinished + val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] + result match { + case true => + doneProcessing = true + debug("Processing Complete") + case false => + debug("Still Processing") + //sleep thread + Thread.sleep(5000L) + //val future = Future { blocking(Thread.sleep(5000L)); "done" } + } + } + val f = engineSimilarityController ? EngineGetSimilarityResults + val result = Await.result(f, timeout.duration).asInstanceOf[List[SimilarImages]] + + val cleanedSimilarImages = this.processSimilarities(result) + var similarCount = 0 + for (similarImage <- cleanedSimilarImages) { + similarCount += 1 + similarImage.similarImages.size + } + + info(s"Finished processing ${images.size} images. Found $similarCount similar images") + cleanedSimilarImages + } + + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { + debug(s"Looking for images in directory: $directoryPath") + val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + // make sure the engine is listening + engineProcessingController ! EngineStart + for (file <- imageFiles) { + engineProcessingController ! EngineProcessFile(file) + } + engineProcessingController ! EngineNoMoreFiles + var doneProcessing = false + while (!doneProcessing) { + val f = engineProcessingController ? EngineIsProcessingFinished + val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] + result match { + case true => + doneProcessing = true + debug("Processing Complete") + case false => + debug("Still Processing") + //sleep thread + Thread.sleep(5000L) + //val future = Future { blocking(Thread.sleep(5000L)); "done" } + } + } + val f = engineProcessingController ? EngineGetProcessingResults + val result = Await.result(f, timeout.duration).asInstanceOf[List[Image]] + images ++= result + images.toList + } } @@ -127,144 +127,144 @@ case object EngineActorProcessingFinished case object EngineActorReactivate class ConcurrentEngineProcessingController extends Actor with ActorLogging { - val numOfRouters = { - val max = PropertiesService.get(PropertyEnum.ConcurrentProcessingLimit.toString).toInt - val processors = Runtime.getRuntime.availableProcessors() - var threads = 0 - if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 - threads - } - var router = context.actorOf(Props[ConcurrentEngineProcessingActor].withRouter(SmallestMailboxPool(nrOfInstances = numOfRouters))) - - var images: mutable.MutableList[Image] = new mutable.MutableList[Image]() - var toProcess = 0 - var processed = 0 - - var processorsFinished = 0 - var listener = context.actorOf(Props[DefaultLoggingEngineListener], - name = "ProcessedEngineListener") - - override def preStart() = { - log.info("Staring the controller for processing images") - log.info("Using {} actors to process images", numOfRouters) - } - - override def receive = { - case command: SetNewListener => setListener(command.listenerType) - case command: EngineProcessFile => processFile(command) - case command: EngineFileProcessed => fileProcessed(command) - case EngineStart => startEngine() - case EngineNoMoreFiles => requestWrapup() - case EngineActorProcessingFinished => actorProcessingFinished() - case EngineIsProcessingFinished => checkIfProcessingIsFinished() - case EngineGetProcessingResults => checkForResults() - case _ => log.info("received unknown message") - } - - def setListener(newListener: ActorRef) = { - //remove the old listener - this.listener ! PoisonPill - //setup the new listener - this.listener = newListener - } - - def startEngine() = { - router ! Broadcast(EngineActorReactivate) - } - - def processFile(command: EngineProcessFile) = { - log.debug(s"Started evaluating ${command.file.getAbsolutePath}") - toProcess += 1 - router ! command - } - - def fileProcessed(command: EngineFileProcessed) = { - processed += 1 - if (processed % 25 == 0 || processed == toProcess) { - //log.info(s"Processed $processed/$toProcess") - listener ! ComparedFileCount(processed, toProcess) - } - if (command.image != null) { - log.debug(s"processed image: ${command.image.imagePath}") - images += command.image - } - } - - def requestWrapup() = { - router ! Broadcast(EngineNoMoreFiles) - } - - /* - * Record that a processor is done - */ - def actorProcessingFinished() = { - processorsFinished += 1 - } - - /* - * Check if processing is done - */ - def checkIfProcessingIsFinished() = { - try { - if (processorsFinished >= numOfRouters) sender ! true else sender ! false - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } - } - - /* - * Get the results of the processing - */ - def checkForResults() = { - try { - listener ! SubmitMessage(s"Finished processing $processed/$processed images") - processorsFinished = 0 - toProcess = 0 - processed = 0 - sender ! images.toList - images.clear() - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } - } - - override def postStop() = { - super.postStop() - this.listener ! PoisonPill - } + val numOfRouters = { + val max = PropertiesService.get(PropertyEnum.ConcurrentProcessingLimit.toString).toInt + val processors = Runtime.getRuntime.availableProcessors() + var threads = 0 + if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 + threads + } + var router = context.actorOf(Props[ConcurrentEngineProcessingActor].withRouter(SmallestMailboxPool(nrOfInstances = numOfRouters))) + + var images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + var toProcess = 0 + var processed = 0 + + var processorsFinished = 0 + var listener = context.actorOf(Props[DefaultLoggingEngineListener], + name = "ProcessedEngineListener") + + override def preStart() = { + log.info("Staring the controller for processing images") + log.info("Using {} actors to process images", numOfRouters) + } + + override def receive = { + case command: SetNewListener => setListener(command.listenerType) + case command: EngineProcessFile => processFile(command) + case command: EngineFileProcessed => fileProcessed(command) + case EngineStart => startEngine() + case EngineNoMoreFiles => requestWrapup() + case EngineActorProcessingFinished => actorProcessingFinished() + case EngineIsProcessingFinished => checkIfProcessingIsFinished() + case EngineGetProcessingResults => checkForResults() + case _ => log.info("received unknown message") + } + + def setListener(newListener: ActorRef) = { + //remove the old listener + this.listener ! PoisonPill + //setup the new listener + this.listener = newListener + } + + def startEngine() = { + router ! Broadcast(EngineActorReactivate) + } + + def processFile(command: EngineProcessFile) = { + log.debug(s"Started evaluating ${command.file.getAbsolutePath}") + toProcess += 1 + router ! command + } + + def fileProcessed(command: EngineFileProcessed) = { + processed += 1 + if (processed % 25 == 0 || processed == toProcess) { + //log.info(s"Processed $processed/$toProcess") + listener ! ComparedFileCount(processed, toProcess) + } + if (command.image != null) { + log.debug(s"processed image: ${command.image.imagePath}") + images += command.image + } + } + + def requestWrapup() = { + router ! Broadcast(EngineNoMoreFiles) + } + + /* + * Record that a processor is done + */ + def actorProcessingFinished() = { + processorsFinished += 1 + } + + /* + * Check if processing is done + */ + def checkIfProcessingIsFinished() = { + try { + if (processorsFinished >= numOfRouters) sender ! true else sender ! false + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e + } + } + + /* + * Get the results of the processing + */ + def checkForResults() = { + try { + listener ! SubmitMessage(s"Finished processing $processed/$processed images") + processorsFinished = 0 + toProcess = 0 + processed = 0 + sender ! images.toList + images.clear() + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e + } + } + + override def postStop() = { + super.postStop() + this.listener ! PoisonPill + } } class ConcurrentEngineProcessingActor extends Actor with ActorLogging { - var ignoreMessages = false - - override def receive = { - case command: EngineProcessFile => processFile(command) - case EngineNoMoreFiles => finishedProcessingFiles() - case EngineActorReactivate => ignoreMessages = false - case _ => log.info("received unknown message") - } - - def processFile(command: EngineProcessFile) = { - if (!ignoreMessages) { - val image = ImageService.getImage(command.file) - if (image != null) { - sender ! EngineFileProcessed(image) - } else { - log.debug(s"Failed to process image: ${command.file.getAbsolutePath}") - } - } - } - - def finishedProcessingFiles() = { - if (!ignoreMessages) { - ignoreMessages = true - sender ! EngineActorProcessingFinished - } - } + var ignoreMessages = false + + override def receive = { + case command: EngineProcessFile => processFile(command) + case EngineNoMoreFiles => finishedProcessingFiles() + case EngineActorReactivate => ignoreMessages = false + case _ => log.info("received unknown message") + } + + def processFile(command: EngineProcessFile) = { + if (!ignoreMessages) { + val image = ImageService.getImage(command.file) + if (image != null) { + sender ! EngineFileProcessed(image) + } else { + log.debug(s"Failed to process image: ${command.file.getAbsolutePath}") + } + } + } + + def finishedProcessingFiles() = { + if (!ignoreMessages) { + ignoreMessages = true + sender ! EngineActorProcessingFinished + } + } } //finding similarities between images @@ -281,165 +281,165 @@ case object EngineGetSimilarityResults case object EngineActorCompareImagesFinished class ConcurrentEngineSimilarityController extends Actor with ActorLogging { - val numOfRouters = { - val max = PropertiesService.get(PropertyEnum.ConcurrentSimilarityLimit.toString).toInt - val processors = Runtime.getRuntime.availableProcessors() - var threads = 0 - if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 - threads - } - val router = context.actorOf(Props[ConcurrentEngineSimilarityActor].withRouter(RoundRobinPool(nrOfInstances = numOfRouters))) - - val allSimilarImages = new mutable.MutableList[SimilarImages] - var toProcess = 0 - var processed = 0 - - var processorsFinished = 0 - - var listener = context.actorOf(Props[DefaultLoggingEngineListener], - name = "SimilarityEngineListener") - - override def preStart() = { - log.info("Staring the controller for processing similarities between images") - log.info("Using {} actors to process image similarities", numOfRouters) - } - - override def receive = { - case command: SetNewListener => setListener(command.listenerType) - case command: EngineCompareImages => findSimilarities(command) - case command: EngineCompareImagesComplete => similarityProcessed(command) - case EngineStart => startEngine() - case EngineNoMoreComparisons => requestWrapup() - case EngineActorCompareImagesFinished => actorProcessingFinished() - case EngineIsSimilarityFinished => checkIfProcessingIsFinished() - case EngineGetSimilarityResults => checkForResults() - case _ => log.info("received unknown message") - } - - def setListener(newListener: ActorRef) = { - //remove the old listener - this.listener ! PoisonPill - //setup the new listener - this.listener = newListener - } - - def startEngine() = { - router ! Broadcast(EngineActorReactivate) - } - - def findSimilarities(command: EngineCompareImages) = { - log.debug(s"Finding similarities between {} and {} images", command.image1.imagePath, command.images.length) - toProcess += 1 - if (toProcess % 250 == 0) { - //log.info("Sent {} images to be processed for similarites", toProcess) - listener ! SubmitMessage(s"Sent $toProcess images to be processed for similarites") - } - //just relay the command to our workers - router ! command - } - - def similarityProcessed(command: EngineCompareImagesComplete) = { - processed += 1 - if (processed % 25 == 0 || processed == toProcess) { - //log.info(s"Processed $processed/$toProcess") - listener ! ScannedFileCount(processed, toProcess) - } - if (command.similarImages != null) { - log.debug(s"Found similar images: ${command.similarImages}") - allSimilarImages += command.similarImages - } - } - - def requestWrapup() = { - router ! Broadcast(EngineNoMoreComparisons) - } - - /* - * Record that a processor is done - */ - def actorProcessingFinished() = { - processorsFinished += 1 - log.debug("Similarity Processor Reported Finished") - } - - /* - * Check if processing is done - */ - def checkIfProcessingIsFinished() = { - try { - log.debug("Processors finished {}/{}", processorsFinished, numOfRouters) - if (processorsFinished >= numOfRouters) sender ! true else sender ! false - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } - } - - /* - * Get the results of the processing - */ - def checkForResults() = { - try { - listener ! SubmitMessage(s"Finished scanning $processed/$processed images") - processorsFinished = 0 - toProcess = 0 - processed = 0 - sender ! allSimilarImages.toList - allSimilarImages.clear() - } catch { - case e: Exception ⇒ - sender ! akka.actor.Status.Failure(e) - throw e - } - } - - override def postStop() = { - super.postStop() - this.listener ! PoisonPill - } + val numOfRouters = { + val max = PropertiesService.get(PropertyEnum.ConcurrentSimilarityLimit.toString).toInt + val processors = Runtime.getRuntime.availableProcessors() + var threads = 0 + if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1 + threads + } + val router = context.actorOf(Props[ConcurrentEngineSimilarityActor].withRouter(RoundRobinPool(nrOfInstances = numOfRouters))) + + val allSimilarImages = new mutable.MutableList[SimilarImages] + var toProcess = 0 + var processed = 0 + + var processorsFinished = 0 + + var listener = context.actorOf(Props[DefaultLoggingEngineListener], + name = "SimilarityEngineListener") + + override def preStart() = { + log.info("Staring the controller for processing similarities between images") + log.info("Using {} actors to process image similarities", numOfRouters) + } + + override def receive = { + case command: SetNewListener => setListener(command.listenerType) + case command: EngineCompareImages => findSimilarities(command) + case command: EngineCompareImagesComplete => similarityProcessed(command) + case EngineStart => startEngine() + case EngineNoMoreComparisons => requestWrapup() + case EngineActorCompareImagesFinished => actorProcessingFinished() + case EngineIsSimilarityFinished => checkIfProcessingIsFinished() + case EngineGetSimilarityResults => checkForResults() + case _ => log.info("received unknown message") + } + + def setListener(newListener: ActorRef) = { + //remove the old listener + this.listener ! PoisonPill + //setup the new listener + this.listener = newListener + } + + def startEngine() = { + router ! Broadcast(EngineActorReactivate) + } + + def findSimilarities(command: EngineCompareImages) = { + log.debug(s"Finding similarities between {} and {} images", command.image1.imagePath, command.images.length) + toProcess += 1 + if (toProcess % 250 == 0) { + //log.info("Sent {} images to be processed for similarites", toProcess) + listener ! SubmitMessage(s"Sent $toProcess images to be processed for similarites") + } + //just relay the command to our workers + router ! command + } + + def similarityProcessed(command: EngineCompareImagesComplete) = { + processed += 1 + if (processed % 25 == 0 || processed == toProcess) { + //log.info(s"Processed $processed/$toProcess") + listener ! ScannedFileCount(processed, toProcess) + } + if (command.similarImages != null) { + log.debug(s"Found similar images: ${command.similarImages}") + allSimilarImages += command.similarImages + } + } + + def requestWrapup() = { + router ! Broadcast(EngineNoMoreComparisons) + } + + /* + * Record that a processor is done + */ + def actorProcessingFinished() = { + processorsFinished += 1 + log.debug("Similarity Processor Reported Finished") + } + + /* + * Check if processing is done + */ + def checkIfProcessingIsFinished() = { + try { + log.debug("Processors finished {}/{}", processorsFinished, numOfRouters) + if (processorsFinished >= numOfRouters) sender ! true else sender ! false + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e + } + } + + /* + * Get the results of the processing + */ + def checkForResults() = { + try { + listener ! SubmitMessage(s"Finished scanning $processed/$processed images") + processorsFinished = 0 + toProcess = 0 + processed = 0 + sender ! allSimilarImages.toList + allSimilarImages.clear() + } catch { + case e: Exception ⇒ + sender ! akka.actor.Status.Failure(e) + throw e + } + } + + override def postStop() = { + super.postStop() + this.listener ! PoisonPill + } } class ConcurrentEngineSimilarityActor extends Actor with ActorLogging { - var ignoreMessages = false - - override def receive = { - case command: EngineCompareImages => compareImages(command) - case EngineNoMoreComparisons => finishedComparisons() - case EngineActorReactivate => ignoreMessages = false - case _ => log.info("received unknown message") - } - - def compareImages(command: EngineCompareImages) = { - if (!ignoreMessages) { - val similarImages = new mutable.MutableList[Image]() - for (image <- command.images) { - if (!command.image1.equals(image)) { - val image1Hashes = ImageService.convertToImageHashDTO(command.image1.hashes) - val imageHashes = ImageService.convertToImageHashDTO(image.hashes) - if (HashService.areImageHashesSimilar(PropertiesService.aHashSettings, PropertiesService.dHashSettings, PropertiesService.pHashSettings, image1Hashes, imageHashes)) { - similarImages += image - } - } - } - //only send a message if we find similar images - if (similarImages.length >= 1) { - similarImages += command.image1 - val similarImage = new SimilarImages(similarImages.toSet) - log.debug(s"Found ${similarImage.similarImages.size} similar images to ${command.image1}") - sender ! EngineCompareImagesComplete(similarImage) - } else { - log.debug(s"Found no similar images to ${command.image1}") - sender ! EngineCompareImagesComplete(null) - } - } - } - - def finishedComparisons() = { - if (!ignoreMessages) { - ignoreMessages = true - log.debug("Finished processing comparisons") - sender ! EngineActorCompareImagesFinished - } - } + var ignoreMessages = false + + override def receive = { + case command: EngineCompareImages => compareImages(command) + case EngineNoMoreComparisons => finishedComparisons() + case EngineActorReactivate => ignoreMessages = false + case _ => log.info("received unknown message") + } + + def compareImages(command: EngineCompareImages) = { + if (!ignoreMessages) { + val similarImages = new mutable.MutableList[Image]() + for (image <- command.images) { + if (!command.image1.equals(image)) { + val image1Hashes = ImageService.convertToImageHashDTO(command.image1.hashes) + val imageHashes = ImageService.convertToImageHashDTO(image.hashes) + if (HashService.areImageHashesSimilar(PropertiesService.aHashSettings, PropertiesService.dHashSettings, PropertiesService.pHashSettings, image1Hashes, imageHashes)) { + similarImages += image + } + } + } + //only send a message if we find similar images + if (similarImages.length >= 1) { + similarImages += command.image1 + val similarImage = new SimilarImages(similarImages.toSet) + log.debug(s"Found ${similarImage.similarImages.size} similar images to ${command.image1}") + sender ! EngineCompareImagesComplete(similarImage) + } else { + log.debug(s"Found no similar images to ${command.image1}") + sender ! EngineCompareImagesComplete(null) + } + } + } + + def finishedComparisons() = { + if (!ignoreMessages) { + ignoreMessages = true + log.debug("Finished processing comparisons") + sender ! EngineActorCompareImagesFinished + } + } } \ No newline at end of file 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 c4df2e0..b6bb36a 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/Engine.scala @@ -12,133 +12,135 @@ import scala.collection.mutable import scala.util.control.Breaks._ /** - * Engine definition - * - * Created by drew on 1/26/14. - */ + * Engine definition + * + * Created by drew on 1/26/14. + */ abstract class Engine extends Logging { - val system = ActorSystem("EngineActorSystem") - 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) - - def getAllImageFiles(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[File] = { - val fileList = new mutable.MutableList[File]() - if (directoryPath != null && directoryPath != "") { - val directory: File = new File(directoryPath) - val imageFilter = new ImageFilter - if (directory.isDirectory) { - val files = directory.listFiles(imageFilter) - if (files != null) { - fileList ++= files - debug(s"Found ${files.length} files that are images in directory: $directoryPath") - if (recursive) { - val directoryFilter = new DirectoryFilter - val directories = directory.listFiles(directoryFilter) - for (directory <- directories) { - fileList ++= getAllImageFiles(directory.getAbsolutePath, recursive, recursiveDepth - 1) - this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") - } - } else { - this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") - } - } - } - } - fileList.toList - } - - /** - * Get all images for a directory with hashes - */ - def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] - - /** - * 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) - } - } - - /** - * Go through the list of similarities and group them so that they represent actual similarities - * - * For example. A is similar to B and C is similar to B but A is was not considered similar to C. Therefore A B and C should be considered similar unless otherwise noted. - * - * @param similarList a list of detected similar images - * @return a grouped and combined list of similar images - */ - def processSimilarities(similarList: List[SimilarImages]): List[SimilarImages] = { - //process the result into a list we want in cleanedSimilarImages - /* - Go through all the images. If a similar image for the image doesn't exist, create it. if it does, - add it to that similar image. The end result is that all images should be grouped according to - their similar images and images that are similar to them. - */ - var count = 0 - // similar image mapping to map known images back to their similar set - val similarImageMap = new mutable.HashMap[Image, SimilarImages]() - - // List of the actual similar image sets - val cleanedSimilarImages = new mutable.MutableList[SimilarImages]() - - // loop over all of the similar image sets - for (similarImages <- similarList) { - count += 1 - if (count % 25 == 0 || count == similarList.length) { - debug(s"Cleaning similar images. $count/${similarList.length} ${similarList.length - count} left to clean") - this.searchedListener ! SubmitMessage(s"Cleaning similar images. $count/${similarList.length}") - } - var foundSimilarity = false - var similarity:SimilarImages = null - breakable { for (similarImage <- similarImages.similarImages) { - if (similarImageMap.contains(similarImage)) { - similarity = similarImageMap(similarImage) - foundSimilarity = true - break() - } - } } - - //if no similarity was found, one should be created - if (!foundSimilarity) { - similarity = new SimilarImages(new HashSet[Image]) - // the created similarity is added to the cleaned list - cleanedSimilarImages += similarity - } - - // all images should be added to this new similarity - similarity.similarImages = similarity.similarImages ++ similarImages.similarImages - similarImages.similarImages.foreach(img => similarImageMap.put(img, similarity)) - } - - //get a count of similar images found - var totalCount = 0 - cleanedSimilarImages.foreach(img => totalCount += img.similarImages.size) - debug(s"Found $totalCount images with similarities") - this.searchedListener ! SubmitMessage(s"Found $totalCount images with similarities") - - // Sort the similarImages by ?!?!?!? and return - cleanedSimilarImages.toList.sorted(SimilarImagesOrdering) - } + val system = ActorSystem("EngineActorSystem") + 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) + + def getAllImageFiles(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[File] = { + val fileList = new mutable.MutableList[File]() + if (directoryPath != null && directoryPath != "") { + val directory: File = new File(directoryPath) + val imageFilter = new ImageFilter + if (directory.isDirectory) { + val files = directory.listFiles(imageFilter) + if (files != null) { + fileList ++= files + debug(s"Found ${files.length} files that are images in directory: $directoryPath") + if (recursive) { + val directoryFilter = new DirectoryFilter + val directories = directory.listFiles(directoryFilter) + for (directory <- directories) { + fileList ++= getAllImageFiles(directory.getAbsolutePath, recursive, recursiveDepth - 1) + this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") + } + } else { + this.searchedListener ! SubmitMessage(s"Found ${fileList.length} files to process") + } + } + } + } + fileList.toList + } + + /** + * Get all images for a directory with hashes + */ + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] + + /** + * Get all similar images for a directory with hashes + */ + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] + + def deleteImages(images: List[Image]): Unit = { + for (image <- images) { + deleteImage(image) + } + } + + def deleteImage(image: Image): Unit = { + ImageService.deleteImage(image) + } + + /** + * Go through the list of similarities and group them so that they represent actual similarities + * + * For example. A is similar to B and C is similar to B but A is was not considered similar to C. Therefore A B and C should be considered similar unless otherwise noted. + * + * @param similarList a list of detected similar images + * @return a grouped and combined list of similar images + */ + def processSimilarities(similarList: List[SimilarImages]): List[SimilarImages] = { + //process the result into a list we want in cleanedSimilarImages + /* + Go through all the images. If a similar image for the image doesn't exist, create it. if it does, + add it to that similar image. The end result is that all images should be grouped according to + their similar images and images that are similar to them. + */ + var count = 0 + // similar image mapping to map known images back to their similar set + val similarImageMap = new mutable.HashMap[Image, SimilarImages]() + + // List of the actual similar image sets + val cleanedSimilarImages = new mutable.MutableList[SimilarImages]() + + // loop over all of the similar image sets + for (similarImages <- similarList) { + count += 1 + if (count % 25 == 0 || count == similarList.length) { + debug(s"Cleaning similar images. $count/${similarList.length} ${similarList.length - count} left to clean") + this.searchedListener ! SubmitMessage(s"Cleaning similar images. $count/${similarList.length}") + } + var foundSimilarity = false + var similarity: SimilarImages = null + breakable { + for (similarImage <- similarImages.similarImages) { + if (similarImageMap.contains(similarImage)) { + similarity = similarImageMap(similarImage) + foundSimilarity = true + break() + } + } + } + + //if no similarity was found, one should be created + if (!foundSimilarity) { + similarity = new SimilarImages(new HashSet[Image]) + // the created similarity is added to the cleaned list + cleanedSimilarImages += similarity + } + + // all images should be added to this new similarity + similarity.similarImages = similarity.similarImages ++ similarImages.similarImages + similarImages.similarImages.foreach(img => similarImageMap.put(img, similarity)) + } + + //get a count of similar images found + var totalCount = 0 + cleanedSimilarImages.foreach(img => totalCount += img.similarImages.size) + debug(s"Found $totalCount images with similarities") + this.searchedListener ! SubmitMessage(s"Found $totalCount images with similarities") + + // Sort the similarImages by ?!?!?!? and return + cleanedSimilarImages.toList.sorted(SimilarImagesOrdering) + } } case class SubmitMessage(message: String) @@ -148,62 +150,62 @@ case class ScannedFileCount(count: Integer, total: Integer, message: String = nu case class ComparedFileCount(count: Integer, total: Integer, message: String = null) abstract class EngineListener extends Actor with ActorLogging { - override def receive: Actor.Receive = { - case command: SubmitMessage => handleMessage(command) - case command: ScannedFileCount => handleScannedFileCount(command) - case command: ComparedFileCount => handleComparedFileCount(command) - case _ => log.info("received unknown message") - } + override def receive: Actor.Receive = { + case command: SubmitMessage => handleMessage(command) + case command: ScannedFileCount => handleScannedFileCount(command) + case command: ComparedFileCount => handleComparedFileCount(command) + case _ => log.info("received unknown message") + } - def handleMessage(command: SubmitMessage) + def handleMessage(command: SubmitMessage) - def handleScannedFileCount(command: ScannedFileCount) + def handleScannedFileCount(command: ScannedFileCount) - def handleComparedFileCount(command: ComparedFileCount) + def handleComparedFileCount(command: ComparedFileCount) } /** - * Actor for logging output information - */ + * Actor for logging output information + */ class DefaultLoggingEngineListener extends EngineListener with ActorLogging { - override def handleComparedFileCount(command: ComparedFileCount): Unit = { - if (command.message != null) { - log.info(command.message) - } - log.info("Processed {}/{}", command.count, command.total) - } - - override def handleScannedFileCount(command: ScannedFileCount): Unit = { - if (command.message != null) { - log.info(command.message) - } - log.info("Scanned {}/{} For Similarities", command.count, command.total) - } - - override def handleMessage(command: SubmitMessage): Unit = { - log.info(command.message) - } + override def handleComparedFileCount(command: ComparedFileCount): Unit = { + if (command.message != null) { + log.info(command.message) + } + log.info("Processed {}/{}", command.count, command.total) + } + + override def handleScannedFileCount(command: ScannedFileCount): Unit = { + if (command.message != null) { + log.info(command.message) + } + log.info("Scanned {}/{} For Similarities", command.count, command.total) + } + + override def handleMessage(command: SubmitMessage): Unit = { + log.info(command.message) + } } /** - * Actor for writing progress out to the commandline - */ + * Actor for writing progress out to the commandline + */ class CLIEngineListener extends EngineListener with ActorLogging { - override def handleComparedFileCount(command: ComparedFileCount): Unit = { - if (command.message != null) { - System.out.println(command.message) - } - System.out.println(s"Processed ${command.count}/${command.total}") - } - - override def handleScannedFileCount(command: ScannedFileCount): Unit = { - if (command.message != null) { - System.out.println(command.message) - } - System.out.println(s"Scanned ${command.count}/${command.total} For Similarities") - } - - override def handleMessage(command: SubmitMessage): Unit = { - System.out.println(command.message) - } + override def handleComparedFileCount(command: ComparedFileCount): Unit = { + if (command.message != null) { + System.out.println(command.message) + } + System.out.println(s"Processed ${command.count}/${command.total}") + } + + override def handleScannedFileCount(command: ScannedFileCount): Unit = { + if (command.message != null) { + System.out.println(command.message) + } + System.out.println(s"Scanned ${command.count}/${command.total} For Similarities") + } + + override def handleMessage(command: SubmitMessage): Unit = { + System.out.println(command.message) + } } \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala index a12daa5..90a1805 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala @@ -9,84 +9,84 @@ import grizzled.slf4j.Logging import scala.collection.mutable /** - * Engine that works sequentially - * Very Slow, but consistent. Excellent for testing - * - * Created by drew on 1/26/14. - */ + * Engine that works sequentially + * Very Slow, but consistent. Excellent for testing + * + * Created by drew on 1/26/14. + */ class SequentialEngine extends Engine with Logging { - var processedListener = system.actorOf(Props[DefaultLoggingEngineListener], - name = "ProcessedEngineListener") - var similarityListener = system.actorOf(Props[DefaultLoggingEngineListener], - name = "SimilarityEngineListener") + var processedListener = system.actorOf(Props[DefaultLoggingEngineListener], + name = "ProcessedEngineListener") + var similarityListener = system.actorOf(Props[DefaultLoggingEngineListener], + name = "SimilarityEngineListener") - override def setProcessedListener(listenerRef: ActorRef) = { - this.processedListener = listenerRef - } + override def setProcessedListener(listenerRef: ActorRef) = { + this.processedListener = listenerRef + } - override def setSimilarityListener(listenerRef: ActorRef) = { - this.similarityListener = listenerRef - } + override def setSimilarityListener(listenerRef: ActorRef) = { + this.similarityListener = listenerRef + } - def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { - debug(s"Looking for similar images in directory: $directoryPath") - val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) - info(s"Searching ${images.length} images for similarities") - val ignoreSet = new mutable.HashSet[Image]() - val allSimilarImages = new mutable.MutableList[SimilarImages]() - var processedCount = 0 - var similarCount = 0 - for (rootImage <- images) { - if (!ignoreSet.contains(rootImage)) { - if (processedCount % 25 == 0) { - //info(s"Processed ${processedCount}/${images.length - ignoreSet.size} About ${images.length - - // processedCount} images to go") - similarityListener ! ScannedFileCount(processedCount, images.length - ignoreSet.size) - } - debug(s"Looking for images similar to: ${rootImage.imagePath}") - ignoreSet += rootImage - val similarImages = new mutable.MutableList[Image]() - for (image <- images) { - if (!ignoreSet.contains(image)) { - if (rootImage.isSimilarTo(image)) { - debug(s"Image: ${image.imagePath} is similar") - similarImages += image - ignoreSet += image - similarCount += 1 - } - } - } - if (similarImages.length > 1) { - similarImages += rootImage - val similar = new SimilarImages(similarImages.toSet) - debug(s"Found similar images: ${similar.toString}") - allSimilarImages += similar - } - processedCount += 1 - } - } - info(s"Finished processing ${images.size} images. Found $similarCount similar images") - this.processSimilarities(allSimilarImages.toList) - } + def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { + debug(s"Looking for similar images in directory: $directoryPath") + val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) + info(s"Searching ${images.length} images for similarities") + val ignoreSet = new mutable.HashSet[Image]() + val allSimilarImages = new mutable.MutableList[SimilarImages]() + var processedCount = 0 + var similarCount = 0 + for (rootImage <- images) { + if (!ignoreSet.contains(rootImage)) { + if (processedCount % 25 == 0) { + //info(s"Processed ${processedCount}/${images.length - ignoreSet.size} About ${images.length - + // processedCount} images to go") + similarityListener ! ScannedFileCount(processedCount, images.length - ignoreSet.size) + } + debug(s"Looking for images similar to: ${rootImage.imagePath}") + ignoreSet += rootImage + val similarImages = new mutable.MutableList[Image]() + for (image <- images) { + if (!ignoreSet.contains(image)) { + if (rootImage.isSimilarTo(image)) { + debug(s"Image: ${image.imagePath} is similar") + similarImages += image + ignoreSet += image + similarCount += 1 + } + } + } + if (similarImages.length > 1) { + similarImages += rootImage + val similar = new SimilarImages(similarImages.toSet) + debug(s"Found similar images: ${similar.toString}") + allSimilarImages += similar + } + processedCount += 1 + } + } + info(s"Finished processing ${images.size} images. Found $similarCount similar images") + this.processSimilarities(allSimilarImages.toList) + } - def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { - debug(s"Looking for images in directory: $directoryPath") - val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() - val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val directory: File = new File(directoryPath) - var count = 0 - for (file <- imageFiles) { - count += 1 - if (count % 25 == 0) { - //info(s"Processed ${count}/${imageFiles.size}") - processedListener ! ScannedFileCount(count, imageFiles.size) - } - val image = ImageService.getImage(file) - if (image != null) { - images += image - } - } - images.toList - } -} + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { + debug(s"Looking for images in directory: $directoryPath") + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) + val directory: File = new File(directoryPath) + var count = 0 + for (file <- imageFiles) { + count += 1 + if (count % 25 == 0) { + //info(s"Processed ${count}/${imageFiles.size}") + processedListener ! ScannedFileCount(count, imageFiles.size) + } + val image = ImageService.getImage(file) + if (image != null) { + images += image + } + } + images.toList + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala index f9daab1..df7b502 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala @@ -8,35 +8,33 @@ import org.hibernate.cfg.Configuration import org.hibernate.service.ServiceRegistry /** - * Utility class to interface with hibernate - * - * Created by drew on 2/8/14. - */ + * Utility class to interface with hibernate + * + * Created by drew on 2/8/14. + */ object HibernateUtil extends Logging { - private val sessionFactory: SessionFactory = buildSessionFactory() - private var serviceRegistry: ServiceRegistry = null + private val sessionFactory: SessionFactory = buildSessionFactory() + private var serviceRegistry: ServiceRegistry = null - def getSessionFactory: SessionFactory = { - sessionFactory - } + def getSessionFactory: SessionFactory = { + sessionFactory + } - private def buildSessionFactory(): SessionFactory = { - try { - // Create the SessionFactory from hibernate.cfg.xml - val configuration = new Configuration().configure("hibernate.cfg.xml") - //set the database location - info(s"Connecting to database at: \'${PropertiesService.get(PropertyEnum.DatabaseConnectionURL.toString)}\'") - configuration.setProperty("hibernate.connection.url", PropertiesService.get(PropertyEnum.DatabaseConnectionURL.toString)) - serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties).build - configuration.buildSessionFactory(serviceRegistry) - } catch { - case ex: Throwable => - // Make sure you log the exception, as it might be swallowed - error("Initial SessionFactory creation failed.", ex) - throw new ExceptionInInitializerError(ex) - } - } - - -} + private def buildSessionFactory(): SessionFactory = { + try { + // Create the SessionFactory from hibernate.cfg.xml + val configuration = new Configuration().configure("hibernate.cfg.xml") + //set the database location + info(s"Connecting to database at: \'${PropertiesService.get(PropertyEnum.DatabaseConnectionURL.toString)}\'") + configuration.setProperty("hibernate.connection.url", PropertiesService.get(PropertyEnum.DatabaseConnectionURL.toString)) + serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties).build + configuration.buildSessionFactory(serviceRegistry) + } catch { + case ex: Throwable => + // Make sure you log the exception, as it might be swallowed + error("Initial SessionFactory creation failed.", ex) + throw new ExceptionInInitializerError(ex) + } + } +} \ No newline at end of file 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 1a72bb8..0c51fa9 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 @@ -4,41 +4,40 @@ import com.sothr.imagetools.engine.image.Image import org.hibernate.{Session, SessionFactory} /** - * Interact with stored images - * - * Created by drew on 2/8/14. - */ + * Interact with stored images + * + * Created by drew on 2/8/14. + */ class ImageDAO { - private val sessionFactory: SessionFactory = HibernateUtil.getSessionFactory - - def find(path: String): Image = { - val session: Session = sessionFactory.getCurrentSession - session.getTransaction.begin() - val result = session.get(classOf[Image], path).asInstanceOf[Image] - session.getTransaction.commit() - result - } - - def save(image: Image) = { - val session: Session = sessionFactory.getCurrentSession - session.getTransaction.begin() - session.saveOrUpdate(image) - session.getTransaction.commit() - } - - def save(images: List[Image]) = { - val session: Session = sessionFactory.getCurrentSession - session.getTransaction.begin() - for (image <- images) session.saveOrUpdate(image) - session.getTransaction.commit() - } - - def delete(image: Image) = { - val session: Session = sessionFactory.getCurrentSession - session.getTransaction.begin() - session.delete(image) - session.getTransaction.commit() - } - -} + private val sessionFactory: SessionFactory = HibernateUtil.getSessionFactory + + def find(path: String): Image = { + val session: Session = sessionFactory.getCurrentSession + session.getTransaction.begin() + val result = session.get(classOf[Image], path).asInstanceOf[Image] + session.getTransaction.commit() + result + } + + def save(image: Image) = { + val session: Session = sessionFactory.getCurrentSession + session.getTransaction.begin() + session.saveOrUpdate(image) + session.getTransaction.commit() + } + + def save(images: List[Image]) = { + val session: Session = sessionFactory.getCurrentSession + session.getTransaction.begin() + for (image <- images) session.saveOrUpdate(image) + session.getTransaction.commit() + } + + def delete(image: Image) = { + val session: Session = sessionFactory.getCurrentSession + session.getTransaction.begin() + session.delete(image) + session.getTransaction.commit() + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala index de02a54..e0a9e52 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala @@ -11,101 +11,100 @@ import grizzled.slf4j.Logging @Table(name = "Image") class Image(val image: String, val thumbnail: String, val size: (Int, Int), val imageHashes: ImageHashVO = null) extends Serializable with Logging { - @Id - var imagePath: String = image - var thumbnailPath: String = thumbnail - var width: Int = size._1 - var height: Int = size._2 - var hashes: ImageHashVO = imageHashes - @transient - var imageSize: (Int, Int) = { - new Tuple2(width, height) - } - @transient - var imageName: String = "" - var imageType: ImageType = ImageType.SingleFrameImage - - def this() = this("", "", (0, 0), null) - - def getThumbnailPath: String = thumbnailPath - - def setThumbnailPath(path: String) = { - thumbnailPath = path - } - - def getWidth: Int = width - - def setWidth(size: Int) = { - width = size - } - - def getHeight: Int = height - - def setHeight(size: Int) = { - height = size - } - - def getHashes: ImageHashVO = hashes - - def setHashes(newHashes: ImageHashVO) = { - hashes = newHashes - } - - def getName: String = { - if (this.imageName.length < 1) { - this.imageName = this.getImagePath.split('/').last - } - this.imageName - } - - def getImagePath: String = imagePath - - def setImagePath(path: String) = { - imagePath = path - } - - def isSimilarTo(otherImage: Image): Boolean = { - //debug(s"Checking $imagePath for similarities with ${otherImage.imagePath}") - HashService.areImageHashesSimilar(PropertiesService.aHashSettings, - PropertiesService.dHashSettings, - PropertiesService.pHashSettings, - ImageService.convertToImageHashDTO(this.hashes), - ImageService.convertToImageHashDTO(otherImage.hashes)) - } - - def getSimilarity(otherImage: Image): Float = { - HashService.getWeightedHashSimilarity(PropertiesService.aHashSettings, - PropertiesService.dHashSettings, - PropertiesService.pHashSettings, - ImageService.convertToImageHashDTO(this.hashes), - ImageService.convertToImageHashDTO(otherImage.hashes)) - } - - /*def getSimilar(otherImages:Traversable[Image]):Traversable[Image] = { - - }*/ - - def cloneImage: Image = { - new Image(imagePath, thumbnailPath, imageSize, hashes.cloneHashes) - } - - override def toString: String = { - s"Image: $imagePath Thumbnail: $thumbnailPath Image Size: ${imageSize._1}x${imageSize._2} Hashes: $hashes" - } - - override def equals(obj: Any) = { - obj match { - case that: Image => - that.hashCode.equals(this.hashCode) - case _ => false - } - } - - override def hashCode: Int = { - var result = 365 - result = 37 * result + imagePath.hashCode - result = 41 * result + hashes.hashCode() - result - } - -} + @Id + var imagePath: String = image + var thumbnailPath: String = thumbnail + var width: Int = size._1 + var height: Int = size._2 + var hashes: ImageHashVO = imageHashes + @transient + var imageSize: (Int, Int) = { + new Tuple2(width, height) + } + @transient + var imageName: String = "" + var imageType: ImageType = ImageType.SingleFrameImage + + def this() = this("", "", (0, 0), null) + + def getThumbnailPath: String = thumbnailPath + + def setThumbnailPath(path: String) = { + thumbnailPath = path + } + + def getWidth: Int = width + + def setWidth(size: Int) = { + width = size + } + + def getHeight: Int = height + + def setHeight(size: Int) = { + height = size + } + + def getHashes: ImageHashVO = hashes + + def setHashes(newHashes: ImageHashVO) = { + hashes = newHashes + } + + def getName: String = { + if (this.imageName.length < 1) { + this.imageName = this.getImagePath.split('/').last + } + this.imageName + } + + def getImagePath: String = imagePath + + def setImagePath(path: String) = { + imagePath = path + } + + def isSimilarTo(otherImage: Image): Boolean = { + //debug(s"Checking $imagePath for similarities with ${otherImage.imagePath}") + HashService.areImageHashesSimilar(PropertiesService.aHashSettings, + PropertiesService.dHashSettings, + PropertiesService.pHashSettings, + ImageService.convertToImageHashDTO(this.hashes), + ImageService.convertToImageHashDTO(otherImage.hashes)) + } + + def getSimilarity(otherImage: Image): Float = { + HashService.getWeightedHashSimilarity(PropertiesService.aHashSettings, + PropertiesService.dHashSettings, + PropertiesService.pHashSettings, + ImageService.convertToImageHashDTO(this.hashes), + ImageService.convertToImageHashDTO(otherImage.hashes)) + } + + /*def getSimilar(otherImages:Traversable[Image]):Traversable[Image] = { + + }*/ + + def cloneImage: Image = { + new Image(imagePath, thumbnailPath, imageSize, hashes.cloneHashes) + } + + override def toString: String = { + s"Image: $imagePath Thumbnail: $thumbnailPath Image Size: ${imageSize._1}x${imageSize._2} Hashes: $hashes" + } + + override def equals(obj: Any) = { + obj match { + case that: Image => + that.hashCode.equals(this.hashCode) + case _ => false + } + } + + override def hashCode: Int = { + var result = 365 + result = 37 * result + imagePath.hashCode + result = 41 * result + hashes.hashCode() + result + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala index f5a8fd7..60588d4 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageFilter.scala @@ -5,19 +5,19 @@ import java.io.{File, FilenameFilter} import scala.collection.immutable.HashSet /** - * Filter for file names - * - * Used to detect image files based on extension - * - * Created by drew on 1/26/14. - */ + * Filter for file names + * + * Used to detect image files based on extension + * + * Created by drew on 1/26/14. + */ class ImageFilter extends FilenameFilter { - private val extensions: HashSet[String] = new HashSet[String]() ++ Array("png", "bmp", "gif", "jpg", "jpeg") + private val extensions: HashSet[String] = new HashSet[String]() ++ Array("png", "bmp", "gif", "jpg", "jpeg") - def accept(dir: File, name: String): Boolean = { - val splitName = name.split('.') - val extension = if (splitName.length > 1) splitName(splitName.length - 1) else "" - if (extensions.contains(extension)) true else false - } -} + def accept(dir: File, name: String): Boolean = { + val splitName = name.split('.') + val extension = if (splitName.length > 1) splitName(splitName.length - 1) else "" + if (extensions.contains(extension)) true else false + } +} \ No newline at end of file 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 7862747..573d89f 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 @@ -8,148 +8,148 @@ import com.sothr.imagetools.engine.AppConfig import com.sothr.imagetools.engine.dao.ImageDAO import com.sothr.imagetools.engine.util.{PropertiesService, PropertyEnum} import com.sothr.imagetools.engine.vo.ImageHashVO -import com.sothr.imagetools.hash.{HashService, ImageHash} import com.sothr.imagetools.hash.util.ImageUtil +import com.sothr.imagetools.hash.{HashService, ImageHash} import grizzled.slf4j.Logging import net.sf.ehcache.Element import org.hibernate.HibernateException object ImageService extends Logging { - val imageCache = AppConfig.cacheManager.getCache("images") - private val imageDAO = new ImageDAO() + val imageCache = AppConfig.cacheManager.getCache("images") + private val imageDAO = new ImageDAO() - def getImage(file: File): Image = { - try { - val image = lookupImage(file) - if (image != null) { - debug(s"${file.getAbsolutePath} was already processed") - return image - } else { - debug(s"Processing image: ${file.getAbsolutePath}") - val bufferedImage = ImageIO.read(file) - val hashes = HashService.getImageHashes( - PropertiesService.aHashSettings, - PropertiesService.dHashSettings, - PropertiesService.pHashSettings, - bufferedImage, - file.getAbsolutePath) + def getImage(file: File): Image = { + try { + val image = lookupImage(file) + if (image != null) { + debug(s"${file.getAbsolutePath} was already processed") + return image + } else { + debug(s"Processing image: ${file.getAbsolutePath}") + val bufferedImage = ImageIO.read(file) + val hashes = HashService.getImageHashes( + PropertiesService.aHashSettings, + PropertiesService.dHashSettings, + PropertiesService.pHashSettings, + bufferedImage, + file.getAbsolutePath) - var thumbnailPath = lookupThumbnailPath(hashes.fileHash) - if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.fileHash) - val imageSize = { - (bufferedImage.getWidth, bufferedImage.getHeight) - } - val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, ImageService.convertToImageHashVO(hashes)) - debug(s"Created image: $image") - return saveImage(image) - } - } catch { - case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}... ${ioe.getMessage}") - case ex: Exception => error(s"Error processing ${file.getAbsolutePath}... ${ex.getMessage}", ex) - } - null - } + var thumbnailPath = lookupThumbnailPath(hashes.fileHash) + if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.fileHash) + val imageSize = { + (bufferedImage.getWidth, bufferedImage.getHeight) + } + val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, ImageService.convertToImageHashVO(hashes)) + debug(s"Created image: $image") + return saveImage(image) + } + } catch { + 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 convertToImageHashDTO(imageHashVO: ImageHashVO): ImageHash = { - new ImageHash(imageHashVO.ahash, imageHashVO.dhash, imageHashVO.phash, imageHashVO.fileHash) - } + def convertToImageHashVO(imageHashDTO: ImageHash): ImageHashVO = { + new ImageHashVO(imageHashDTO.ahash, imageHashDTO.dhash, imageHashDTO.phash, imageHashDTO.fileHash) + } - def convertToImageHashVO(imageHashDTO: ImageHash): ImageHashVO = { - new ImageHashVO(imageHashDTO.ahash, imageHashDTO.dhash, imageHashDTO.phash, imageHashDTO.fileHash) - } + private def lookupImage(file: File): Image = { + var image: Image = null + var found = false + //get from memory cache if possible + try { + if (imageCache.isKeyInCache(file.getAbsolutePath)) { + image = imageCache.get(file.getAbsolutePath).getObjectValue.asInstanceOf[Image] + found = true + } + } catch { + case npe: NullPointerException => debug(s"\'${file.getAbsolutePath}\' was supposed to be in the cache, but was not") + } + //get from datastore if possible + if (!found) { + try { + val tempImage = imageDAO.find(file.getAbsolutePath) + if (tempImage != null) image = tempImage + } catch { + case ex: Exception => error(s"Error looking up \'${file.getAbsolutePath}\' was supposed to be in the database, but was not", ex) + } + } + image + } - private def lookupImage(file: File): Image = { - var image: Image = null - var found = false - //get from memory cache if possible - try { - if (imageCache.isKeyInCache(file.getAbsolutePath)) { - image = imageCache.get(file.getAbsolutePath).getObjectValue.asInstanceOf[Image] - found = true - } - } catch { - case npe: NullPointerException => debug(s"\'${file.getAbsolutePath}\' was supposed to be in the cache, but was not") - } - //get from datastore if possible - if (!found) { - try { - val tempImage = imageDAO.find(file.getAbsolutePath) - if (tempImage != null) image = tempImage - } catch { - case ex: Exception => error(s"Error looking up \'${file.getAbsolutePath}\' was supposed to be in the database, but was not", ex) - } - } - image - } + private def saveImage(image: Image): Image = { + //save to cache + imageCache.put(new Element(image.imagePath, image)) + //save to datastore + try { + imageDAO.save(image) + } catch { + case ex: Exception => error(s"Error saving \'${image.imagePath}\' to database", ex) + } + image + } - private def saveImage(image: Image): Image = { - //save to cache - imageCache.put(new Element(image.imagePath, image)) - //save to datastore - try { - imageDAO.save(image) - } catch { - case ex: Exception => error(s"Error saving \'${image.imagePath}\' to database", ex) - } - image - } + def lookupThumbnailPath(md5: String): String = { + var thumbPath: String = null + if (md5 != null) { + //check for the actual file + val checkPath = calculateThumbPath(md5) + if (new File(checkPath).exists) thumbPath = checkPath + } else { + error("Null md5 passed in") + } + thumbPath + } - def lookupThumbnailPath(md5: String): String = { - var thumbPath: String = null - if (md5 != null) { - //check for the actual file - val checkPath = calculateThumbPath(md5) - if (new File(checkPath).exists) thumbPath = checkPath - } else { - error("Null md5 passed in") - } - thumbPath - } + def calculateThumbPath(md5: String): String = { + //break the path down into 4 char parts + val subPath = md5.substring(0, 3) + var path: String = s"${PropertiesService.get(PropertyEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertyEnum.ThumbnailSize.toString)}/$subPath/" + try { + val dir = new File(path) + if (!dir.exists()) dir.mkdirs() + } catch { + case ioe: IOException => error(s"Unable to create dirs for path: \'$path\'", ioe) + } + path += md5 + ".jpg" + path + } - def calculateThumbPath(md5: String): String = { - //break the path down into 4 char parts - val subPath = md5.substring(0, 3) - var path: String = s"${PropertiesService.get(PropertyEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertyEnum.ThumbnailSize.toString)}/$subPath/" - try { - val dir = new File(path) - if (!dir.exists()) dir.mkdirs() - } catch { - case ioe: IOException => error(s"Unable to create dirs for path: \'$path\'", ioe) - } - path += md5 + ".jpg" - path - } + def getThumbnail(image: BufferedImage, md5: String): String = { + //create thumbnail + val thumb = ImageUtil.resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced = false) + //calculate path + val path = calculateThumbPath(md5) + // save thumbnail to path + try { + ImageIO.write(thumb, "png", new File(path)) + debug(s"Wrote thumbnail to $path") + } catch { + case ioe: IOException => error(s"Unable to save thumbnail to $path", ioe) + } + // return path + path + } - def getThumbnail(image: BufferedImage, md5: String): String = { - //create thumbnail - val thumb = ImageUtil.resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced = false) - //calculate path - val path = calculateThumbPath(md5) - // save thumbnail to path - try { - ImageIO.write(thumb, "png", new File(path)) - debug(s"Wrote thumbnail to $path") - } catch { - case ioe: IOException => error(s"Unable to save thumbnail to $path", ioe) - } - // return path - path - } + def convertToImageHashDTO(imageHashVO: ImageHashVO): ImageHash = { + new ImageHash(imageHashVO.ahash, imageHashVO.dhash, imageHashVO.phash, imageHashVO.fileHash) + } - 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 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) + } + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala index 86a75dc..7aa19c0 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala @@ -3,41 +3,41 @@ package com.sothr.imagetools.engine.image import grizzled.slf4j.Logging /** - * Similar Image payload class - * - * Created by drew on 1/26/14. - */ + * Similar Image payload class + * + * Created by drew on 1/26/14. + */ class SimilarImages(var similarImages: Set[Image]) extends Logging { - override def hashCode: Int = { - val prime = 7 - var result = prime * similarImages.size - for (similarImage <- similarImages) { - result = prime * result + similarImage.hashCode - } - result - } - - override def toString: String = { - s"""Similar Images: + override def hashCode: Int = { + val prime = 7 + var result = prime * similarImages.size + for (similarImage <- similarImages) { + result = prime * result + similarImage.hashCode + } + result + } + + override def toString: String = { + s"""Similar Images: $getPrettySimilarImagesList""".stripMargin - } + } - protected def getPrettySimilarImagesList: String = { - val sb = new StringBuilder() - for (image <- similarImages) { - sb.append(image.imagePath) - sb.append(System.lineSeparator()) - } - sb.toString() - } + protected def getPrettySimilarImagesList: String = { + val sb = new StringBuilder() + for (image <- similarImages) { + sb.append(image.imagePath) + sb.append(System.lineSeparator()) + } + sb.toString() + } - def ordering() = { - 1 * similarImages.size - } + def ordering() = { + 1 * similarImages.size + } } object SimilarImagesOrdering extends Ordering[SimilarImages] { - def compare(a:SimilarImages, b:SimilarImages) = a.ordering() compare b.ordering() -} + def compare(a: SimilarImages, b: SimilarImages) = a.ordering() compare b.ordering() +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/DirectoryFilter.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/DirectoryFilter.scala index 66e25fd..9d4a04c 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/DirectoryFilter.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/DirectoryFilter.scala @@ -3,13 +3,13 @@ package com.sothr.imagetools.engine.util import java.io.{File, FilenameFilter} /** - * Filter directories - * - * Created by drew on 1/26/14. - */ + * Filter directories + * + * Created by drew on 1/26/14. + */ class DirectoryFilter extends FilenameFilter { - def accept(dir: File, name: String): Boolean = { - new File(dir, name).isDirectory - } -} + def accept(dir: File, name: String): Boolean = { + new File(dir, name).isDirectory + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/Hamming.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Hamming.scala index 466ed51..87a45f5 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Hamming.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Hamming.scala @@ -2,17 +2,17 @@ package com.sothr.imagetools.engine.util object Hamming { - /** - * Calculate the hamming distance between two longs - * - * @param hash1 The first hash to compare - * @param hash2 The second hash to compare - * @return - */ - def getDistance(hash1: Long, hash2: Long): Int = { - //The XOR of hash1 and hash2 is converted to a binary string - //then the number of '1's is counted. This is the hamming distance - (hash1 ^ hash2).toBinaryString.count(_ == '1') - } + /** + * Calculate the hamming distance between two longs + * + * @param hash1 The first hash to compare + * @param hash2 The second hash to compare + * @return + */ + def getDistance(hash1: Long, hash2: Long): Int = { + //The XOR of hash1 and hash2 is converted to a binary string + //then the number of '1's is counted. This is the hamming distance + (hash1 ^ hash2).toBinaryString.count(_ == '1') + } } \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala index 65cdfa9..a588e52 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala @@ -12,118 +12,118 @@ import grizzled.slf4j.Logging */ object PropertiesService extends Logging { - //OS information - val OS: String = System.getProperty("os.name", "UNKNOWN") - val OS_VERSION: String = System.getProperty("os.version", "UNKNOWN") - val OS_ARCH: String = System.getProperty("os.arch", "UNKNOWN") - private val newUserConf: Properties = new Properties() - private val configRenderOptions = ConfigRenderOptions.concise().setFormatted(true) - //specific highly used properties - var TimingEnabled: Boolean = false - var aHashSettings = new HashSetting("AHash", false, 0, 0, 0.0f) - var dHashSettings = new HashSetting("DHash", false, 0, 0, 0.0f) - var pHashSettings = new HashSetting("PHash", false, 0, 0, 0.0f) - private var defaultConf: Config = _ - private var userConf: Config = _ - private var version: Version = _ - - def getVersion: Version = this.version - - /* - * Load the properties file from the specified location - */ - def loadProperties(defaultLocation: String, userLocation: String = null) = { - info(s"Attempting to load properties from: $defaultLocation") - defaultConf = ConfigFactory.load(defaultLocation) - if (userLocation != null) { - userConf = ConfigFactory.parseFile(new File(userLocation)) - } else { - userConf = ConfigFactory.empty - info("No user properties file exists to load from") - } - version = new Version(get(PropertyEnum.Version.toString)) - info(s"Detected Version: $version") - - //load special properties - TimingEnabled = get(PropertyEnum.Timed.toString).toBoolean - - aHashSettings = new HashSetting( - "AHash", - get(PropertyEnum.UseAhash.toString).toBoolean, - get(PropertyEnum.AhashPrecision.toString).toInt, - get(PropertyEnum.AhashTolerance.toString).toInt, - get(PropertyEnum.AhashWeight.toString).toFloat - ) - - dHashSettings = new HashSetting( - "DHash", - get(PropertyEnum.UseDhash.toString).toBoolean, - get(PropertyEnum.DhashPrecision.toString).toInt, - get(PropertyEnum.DhashTolerance.toString).toInt, - get(PropertyEnum.DhashWeight.toString).toFloat - ) - - pHashSettings = new HashSetting( - "PHash", - get(PropertyEnum.UsePhash.toString).toBoolean, - get(PropertyEnum.PhashPrecision.toString).toInt, - get(PropertyEnum.PhashTolerance.toString).toInt, - get(PropertyEnum.PhashWeight.toString).toFloat - ) - info("Loaded Special Properties") - } - - def get(key: String, defaultValue: String = null): String = { - var result: String = defaultValue - //check the latest properties - if (newUserConf.containsKey(key)) { - result = newUserConf.getProperty(key) - } - //check the loaded user properties - else if (userConf.hasPath(key)) { - result = userConf.getString(key) - } - //check the default properties - else if (defaultConf.hasPath(key)) { - result = defaultConf.getString(key) - } - result - } - - def saveConf(location: String) = { - info(s"Saving user properties to $location") - val out: PrintStream = new PrintStream(new FileOutputStream(location, false)) - val userConfToSave = getCleanedMergedUserConf - //print to the output stream - out.print(userConfToSave.root().render(configRenderOptions)) - out.flush() - out.close() - } - - private def getCleanedMergedUserConf: Config = { - ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf - } - - private def cleanAndPrepareNewUserProperties(): Properties = { - //insert special keys here - newUserConf.setProperty(PropertyEnum.PreviousVersion.toString, version.parsableToString()) - //remove special keys here - newUserConf.remove(PropertyEnum.Version.toString) - newUserConf - } - - def has(key: String): Boolean = { - var result = false - if (newUserConf.containsKey(key) - || userConf.hasPath(key) - || defaultConf.hasPath(key)) { - result = true - } - result - } - - def set(key: String, value: String) = { - newUserConf.setProperty(key, value) - } + //OS information + val OS: String = System.getProperty("os.name", "UNKNOWN") + val OS_VERSION: String = System.getProperty("os.version", "UNKNOWN") + val OS_ARCH: String = System.getProperty("os.arch", "UNKNOWN") + private val newUserConf: Properties = new Properties() + private val configRenderOptions = ConfigRenderOptions.concise().setFormatted(true) + //specific highly used properties + var TimingEnabled: Boolean = false + var aHashSettings = new HashSetting("AHash", false, 0, 0, 0.0f) + var dHashSettings = new HashSetting("DHash", false, 0, 0, 0.0f) + var pHashSettings = new HashSetting("PHash", false, 0, 0, 0.0f) + private var defaultConf: Config = _ + private var userConf: Config = _ + private var version: Version = _ + + def getVersion: Version = this.version + + /* + * Load the properties file from the specified location + */ + def loadProperties(defaultLocation: String, userLocation: String = null) = { + info(s"Attempting to load properties from: $defaultLocation") + defaultConf = ConfigFactory.load(defaultLocation) + if (userLocation != null) { + userConf = ConfigFactory.parseFile(new File(userLocation)) + } else { + userConf = ConfigFactory.empty + info("No user properties file exists to load from") + } + version = new Version(get(PropertyEnum.Version.toString)) + info(s"Detected Version: $version") + + //load special properties + TimingEnabled = get(PropertyEnum.Timed.toString).toBoolean + + aHashSettings = new HashSetting( + "AHash", + get(PropertyEnum.UseAhash.toString).toBoolean, + get(PropertyEnum.AhashPrecision.toString).toInt, + get(PropertyEnum.AhashTolerance.toString).toInt, + get(PropertyEnum.AhashWeight.toString).toFloat + ) + + dHashSettings = new HashSetting( + "DHash", + get(PropertyEnum.UseDhash.toString).toBoolean, + get(PropertyEnum.DhashPrecision.toString).toInt, + get(PropertyEnum.DhashTolerance.toString).toInt, + get(PropertyEnum.DhashWeight.toString).toFloat + ) + + pHashSettings = new HashSetting( + "PHash", + get(PropertyEnum.UsePhash.toString).toBoolean, + get(PropertyEnum.PhashPrecision.toString).toInt, + get(PropertyEnum.PhashTolerance.toString).toInt, + get(PropertyEnum.PhashWeight.toString).toFloat + ) + info("Loaded Special Properties") + } + + def get(key: String, defaultValue: String = null): String = { + var result: String = defaultValue + //check the latest properties + if (newUserConf.containsKey(key)) { + result = newUserConf.getProperty(key) + } + //check the loaded user properties + else if (userConf.hasPath(key)) { + result = userConf.getString(key) + } + //check the default properties + else if (defaultConf.hasPath(key)) { + result = defaultConf.getString(key) + } + result + } + + def saveConf(location: String) = { + info(s"Saving user properties to $location") + val out: PrintStream = new PrintStream(new FileOutputStream(location, false)) + val userConfToSave = getCleanedMergedUserConf + //print to the output stream + out.print(userConfToSave.root().render(configRenderOptions)) + out.flush() + out.close() + } + + private def getCleanedMergedUserConf: Config = { + ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf + } + + private def cleanAndPrepareNewUserProperties(): Properties = { + //insert special keys here + newUserConf.setProperty(PropertyEnum.PreviousVersion.toString, version.parsableToString()) + //remove special keys here + newUserConf.remove(PropertyEnum.Version.toString) + newUserConf + } + + def has(key: String): Boolean = { + var result = false + if (newUserConf.containsKey(key) + || userConf.hasPath(key) + || defaultConf.hasPath(key)) { + result = true + } + result + } + + def set(key: String, value: String) = { + newUserConf.setProperty(key, value) + } } \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertyEnum.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertyEnum.scala index ef53c34..6b71879 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertyEnum.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertyEnum.scala @@ -1,32 +1,32 @@ package com.sothr.imagetools.engine.util object PropertyEnum extends Enumeration { - type PropertiesEnum = Value - val Version = Value("app.version.current") - val PreviousVersion = Value("app.version.previous") - //default app settings - val Timed = Value("app.timed") - //default engine concurrency settings - val ConcurrentSimilarityLimit = Value("app.engine.concurrent.similarity.limit") - val ConcurrentProcessingLimit = Value("app.engine.concurrent.processing.limit") - //default image settings - val ImageDifferenceThreshold = Value("app.image.differenceThreshold") - val HashPrecision = Value("app.image.hash.precision") - val UseAhash = Value("app.image.ahash.use") - val AhashWeight = Value("app.image.ahash.weight") - val AhashPrecision = Value("app.image.ahash.precision") - val AhashTolerance = Value("app.image.ahash.tolerance") - val UseDhash = Value("app.image.dhash.use") - val DhashWeight = Value("app.image.dhash.weight") - val DhashPrecision = Value("app.image.dhash.precision") - val DhashTolerance = Value("app.image.dhash.tolerance") - val UsePhash = Value("app.image.phash.use") - val PhashWeight = Value("app.image.phash.weight") - val PhashPrecision = Value("app.image.phash.precision") - val PhashTolerance = Value("app.image.phash.tolerance") - //Default Thumbnail Settings - val ThumbnailDirectory = Value("app.thumbnail.directory") - val ThumbnailSize = Value("app.thumbnail.size") - //Default Database Settings - val DatabaseConnectionURL = Value("app.database.connectionURL") + type PropertiesEnum = Value + val Version = Value("app.version.current") + val PreviousVersion = Value("app.version.previous") + //default app settings + val Timed = Value("app.timed") + //default engine concurrency settings + val ConcurrentSimilarityLimit = Value("app.engine.concurrent.similarity.limit") + val ConcurrentProcessingLimit = Value("app.engine.concurrent.processing.limit") + //default image settings + val ImageDifferenceThreshold = Value("app.image.differenceThreshold") + val HashPrecision = Value("app.image.hash.precision") + val UseAhash = Value("app.image.ahash.use") + val AhashWeight = Value("app.image.ahash.weight") + val AhashPrecision = Value("app.image.ahash.precision") + val AhashTolerance = Value("app.image.ahash.tolerance") + val UseDhash = Value("app.image.dhash.use") + val DhashWeight = Value("app.image.dhash.weight") + val DhashPrecision = Value("app.image.dhash.precision") + val DhashTolerance = Value("app.image.dhash.tolerance") + val UsePhash = Value("app.image.phash.use") + val PhashWeight = Value("app.image.phash.weight") + val PhashPrecision = Value("app.image.phash.precision") + val PhashTolerance = Value("app.image.phash.tolerance") + //Default Thumbnail Settings + val ThumbnailDirectory = Value("app.thumbnail.directory") + val ThumbnailSize = Value("app.thumbnail.size") + //Default Database Settings + val DatabaseConnectionURL = Value("app.database.connectionURL") } \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala index 041a8d0..67e860e 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Timing.scala @@ -4,32 +4,31 @@ import grizzled.slf4j.Logging trait Timing extends Logging { - def time[R](block: => R): R = { - val t0 = System.currentTimeMillis - val result = block // call-by-name - val t1 = System.currentTimeMillis - debug("Elapsed time: " + (t1 - t0) + "ms") - result - } + def time[R](block: => R): R = { + val t0 = System.currentTimeMillis + val result = block // call-by-name + val t1 = System.currentTimeMillis + debug("Elapsed time: " + (t1 - t0) + "ms") + result + } - def getTime[R](block: => R): Long = { - val t0 = System.currentTimeMillis - val result = block // call-by-name - val t1 = System.currentTimeMillis - debug("Elapsed time: " + (t1 - t0) + "ms") - t1 - t0 - } + def getTime[R](block: => R): Long = { + val t0 = System.currentTimeMillis + val result = block // call-by-name + val t1 = System.currentTimeMillis + debug("Elapsed time: " + (t1 - t0) + "ms") + t1 - t0 + } - def getMean(times: Long*): Long = { - getMean(times.toArray[Long]) - } - - def getMean(times: Array[Long]): Long = { - var ag = 0L - for (i <- times.indices) { - ag += times(i) - } - ag / times.length - } + def getMean(times: Long*): Long = { + getMean(times.toArray[Long]) + } + def getMean(times: Array[Long]): Long = { + var ag = 0L + for (i <- times.indices) { + ag += times(i) + } + ag / times.length + } } \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala index 06a2d64..5e1a8ef 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala @@ -3,90 +3,90 @@ package com.sothr.imagetools.engine.util import grizzled.slf4j.Logging /** - * Class to handle version detection and evaluation - * - * Created by drew on 1/6/14. - */ + * Class to handle version detection and evaluation + * + * Created by drew on 1/6/14. + */ class Version(val versionString: String) extends Logging { - //parse version into parts - //typical version string i.e. 0.1.0-DEV-27-060aec7 - val (major, minor, patch, buildTag, buildNumber, buildHash) = { - var version: (Int, Int, Int, String, Int, String) = (0, 0, 0, "DEV", 0, "asdfzxcv") - try { - val splitVersion = versionString.split( """\.""") - val splitType = splitVersion(splitVersion.length - 1).split( """-""") - version = (splitVersion(0).toInt, splitVersion(1).toInt, splitType(0).toInt, splitType(1), splitType(2).toInt, splitType(3)) - } catch { - case nfe: NumberFormatException => error(s"Error parsing number from version string '$versionString'", nfe) - case e: Exception => error(s"Unexpected error parsing version string '$versionString'", e) - } - version - } + //parse version into parts + //typical version string i.e. 0.1.0-DEV-27-060aec7 + val (major, minor, patch, buildTag, buildNumber, buildHash) = { + var version: (Int, Int, Int, String, Int, String) = (0, 0, 0, "DEV", 0, "asdfzxcv") + try { + val splitVersion = versionString.split( """\.""") + val splitType = splitVersion(splitVersion.length - 1).split( """-""") + version = (splitVersion(0).toInt, splitVersion(1).toInt, splitType(0).toInt, splitType(1), splitType(2).toInt, splitType(3)) + } catch { + case nfe: NumberFormatException => error(s"Error parsing number from version string '$versionString'", nfe) + case e: Exception => error(s"Unexpected error parsing version string '$versionString'", e) + } + version + } - /* - * -3 = this.patch < that.patch - * -2 = this.minor < that.minor - * -1 = this.major < that.major - * 0 = Identical Versions - * 1 = this.major > that.major - * 2 = this.minor > that.minor - * 3 = this.patch > that.patch - * 4 = this.buildTag != that.buildTag - */ - def compare(that: Version): Integer = { - //Identical Versions - if (this.hashCode == that.hashCode) { - 0 - // This is at least a major version ahead - } else if (this.major > that.major) { - 1 - // This is at least a major version behind - } else if (this.major < that.major) { - -1 - // major is the same - } else { - // This is at least a minor version ahead - if (this.minor > that.minor) { - 2 - // This is at least a minor version behind - } else if (this.minor < that.minor) { - -2 - // major.minor are the same - } else { - // This is at least a patch version ahead - if (this.patch > that.patch) { - 3 - // This is at least a patch version version - } else if (this.patch < that.patch) { - -3 - //major.minor.patch are all the same - } else { - // This is a different build - if (this.buildTag != that.buildTag) { - 4 - } - //should be caught by the first if, but in case not - 0 - } - } - } - } + /* + * -3 = this.patch < that.patch + * -2 = this.minor < that.minor + * -1 = this.major < that.major + * 0 = Identical Versions + * 1 = this.major > that.major + * 2 = this.minor > that.minor + * 3 = this.patch > that.patch + * 4 = this.buildTag != that.buildTag + */ + def compare(that: Version): Integer = { + //Identical Versions + if (this.hashCode == that.hashCode) { + 0 + // This is at least a major version ahead + } else if (this.major > that.major) { + 1 + // This is at least a major version behind + } else if (this.major < that.major) { + -1 + // major is the same + } else { + // This is at least a minor version ahead + if (this.minor > that.minor) { + 2 + // This is at least a minor version behind + } else if (this.minor < that.minor) { + -2 + // major.minor are the same + } else { + // This is at least a patch version ahead + if (this.patch > that.patch) { + 3 + // This is at least a patch version version + } else if (this.patch < that.patch) { + -3 + //major.minor.patch are all the same + } else { + // This is a different build + if (this.buildTag != that.buildTag) { + 4 + } + //should be caught by the first if, but in case not + 0 + } + } + } + } - override def hashCode(): Int = { - val prime: Int = 37 - val result: Int = 255 - var hash: Int = major - hash += minor - hash += patch - hash += buildTag.hashCode - prime * result + hash - } + override def hashCode(): Int = { + val prime: Int = 37 + val result: Int = 255 + var hash: Int = major + hash += minor + hash += patch + hash += buildTag.hashCode + prime * result + hash + } - def parsableToString(): String = { - s"$major.$minor.$patch-$buildTag-$buildNumber-$buildHash" - } + def parsableToString(): String = { + s"$major.$minor.$patch-$buildTag-$buildNumber-$buildHash" + } - override def toString: String = { - s"$major.$minor.$patch-$buildTag build:$buildNumber code:$buildHash" - } -} + override def toString: String = { + s"$major.$minor.$patch-$buildTag build:$buildNumber code:$buildHash" + } +} \ No newline at end of file diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/vo/ImageHashVO.scala b/engine/src/main/scala/com/sothr/imagetools/engine/vo/ImageHashVO.scala index d514b5c..2aeecad 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/vo/ImageHashVO.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/vo/ImageHashVO.scala @@ -8,55 +8,55 @@ import grizzled.slf4j.Logging @Table(name = "ImageHash") class ImageHashVO(var ahash: Long, var dhash: Long, var phash: Long, var fileHash: String) extends Serializable with Logging { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - var id: Int = _ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Int = _ - def this() = this(0l, 0l, 0l, "") + def this() = this(0l, 0l, 0l, "") - def getId: Int = id + def getId: Int = id - def setId(newId: Int) = { - id = newId - } + def setId(newId: Int) = { + id = newId + } - def getAhash: Long = ahash + def getAhash: Long = ahash - def setAhash(hash: Long) = { - ahash = hash - } + def setAhash(hash: Long) = { + ahash = hash + } - def getDhash: Long = dhash + def getDhash: Long = dhash - def setDhash(hash: Long) = { - dhash = hash - } + def setDhash(hash: Long) = { + dhash = hash + } - def getPhash: Long = phash + def getPhash: Long = phash - def setPhash(hash: Long) = { - phash = hash - } + def setPhash(hash: Long) = { + phash = hash + } - def getFileHash: String = fileHash + def getFileHash: String = fileHash - def setFileHash(hash: String) = { - fileHash = hash - } + def setFileHash(hash: String) = { + fileHash = hash + } - def cloneHashes: ImageHashVO = { - new ImageHashVO(ahash, dhash, phash, fileHash) - } + def cloneHashes: ImageHashVO = { + new ImageHashVO(ahash, dhash, phash, fileHash) + } - override def hashCode(): Int = { - var result = 365 - result = 41 * result + (this.ahash ^ (this.ahash >>> 32)).toInt - result = 37 * result + (this.dhash ^ (this.dhash >>> 32)).toInt - result = 2 * result + (this.phash ^ (this.phash >>> 32)).toInt - result - } + override def hashCode(): Int = { + var result = 365 + result = 41 * result + (this.ahash ^ (this.ahash >>> 32)).toInt + result = 37 * result + (this.dhash ^ (this.dhash >>> 32)).toInt + result = 2 * result + (this.phash ^ (this.phash >>> 32)).toInt + result + } - override def toString: String = { - s"fileHash: $fileHash ahash: $ahash dhash: $dhash phash: $phash" - } -} + override def toString: String = { + s"fileHash: $fileHash ahash: $ahash dhash: $dhash phash: $phash" + } +} \ No newline at end of file diff --git a/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java b/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java index 2a3cdba..5b41cda 100644 --- a/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java +++ b/engine/src/test/java/com/sothr/imagetools/engine/AppTest.java @@ -8,26 +8,26 @@ import junit.framework.TestSuite; * Unit test for simple App. */ public class AppTest extends TestCase { - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest(String testName) { - super(testName); - } + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest(String testName) { + super(testName); + } - /** - * @return the suite of tests being tested - */ - public static Test suite() { - return new TestSuite(AppTest.class); - } + /** + * @return the suite of tests being tested + */ + public static Test suite() { + return new TestSuite(AppTest.class); + } - /** - * Rigourous Test :-) - */ - public void testApp() { - assertTrue(true); - } -} + /** + * Rigourous Test :-) + */ + public void testApp() { + assertTrue(true); + } +} \ No newline at end of file diff --git a/engine/src/test/resources/ehcache.xml b/engine/src/test/resources/ehcache.xml index 1e29df4..b7b28e4 100644 --- a/engine/src/test/resources/ehcache.xml +++ b/engine/src/test/resources/ehcache.xml @@ -1,8 +1,8 @@ - - - - + + + + diff --git a/engine/src/test/resources/hibernate.cfg.xml b/engine/src/test/resources/hibernate.cfg.xml index a1a6f1c..cc413a8 100644 --- a/engine/src/test/resources/hibernate.cfg.xml +++ b/engine/src/test/resources/hibernate.cfg.xml @@ -1,48 +1,48 @@ + "-//Hibernate/Hibernate Configuration DTD//EN" + "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> - - + + - - org.h2.Driver - org.hibernate.dialect.H2Dialect - true - - java:comp/UserTransaction + + org.h2.Driver + org.hibernate.dialect.H2Dialect + true + + java:comp/UserTransaction - create + create - - thread + + thread - - org.hibernate.cache.ehcache.EhCacheRegionFactory - - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory - - true + + org.hibernate.cache.ehcache.EhCacheRegionFactory + + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + + true - 1 - 100 - - 50 - 0 - 5 - 100 - + 1 + 100 + + 50 + 0 + 5 + 100 + - - - + + + - - - + + + \ No newline at end of file diff --git a/engine/src/test/resources/hibernate/Image.hbm.xml b/engine/src/test/resources/hibernate/Image.hbm.xml index bbe679b..c662c7a 100644 --- a/engine/src/test/resources/hibernate/Image.hbm.xml +++ b/engine/src/test/resources/hibernate/Image.hbm.xml @@ -1,17 +1,17 @@ + "-//Hibernate/Hibernate Mapping DTD//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - - - This class contains the image hashes and meta data - - - - - - - + + + This class contains the image hashes and meta data + + + + + + + \ No newline at end of file diff --git a/engine/src/test/resources/hibernate/ImageHash.hbm.xml b/engine/src/test/resources/hibernate/ImageHash.hbm.xml index 3d79acb..7cac328 100644 --- a/engine/src/test/resources/hibernate/ImageHash.hbm.xml +++ b/engine/src/test/resources/hibernate/ImageHash.hbm.xml @@ -1,18 +1,18 @@ + "-//Hibernate/Hibernate Mapping DTD//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - - - This class contains the image hashes - - - - - - - - - + + + This class contains the image hashes + + + + + + + + + \ No newline at end of file diff --git a/engine/src/test/resources/logback-minimum-config.xml b/engine/src/test/resources/logback-minimum-config.xml index ca251cc..eba6025 100644 --- a/engine/src/test/resources/logback-minimum-config.xml +++ b/engine/src/test/resources/logback-minimum-config.xml @@ -1,59 +1,59 @@ - - ImageTools.debug - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - DEBUG - - - 1 - ImageTools.debug.%i - - - 5MB - - - - ImageTools.info - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - INFO - - - 1 - ImageTools.info.%i - - - 500KB - - - - ImageTools.err - - false - [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - ERROR - - - 1 - ImageTools.err.%i - - - 500KB - - - - - - - + + ImageTools.debug + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + DEBUG + + + 1 + ImageTools.debug.%i + + + 5MB + + + + ImageTools.info + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + INFO + + + 1 + ImageTools.info.%i + + + 500KB + + + + ImageTools.err + + false + [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + ERROR + + + 1 + ImageTools.err.%i + + + 500KB + + + + + + + \ No newline at end of file diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/BaseTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/BaseTest.scala index aabfaff..d75b523 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/BaseTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/BaseTest.scala @@ -6,8 +6,8 @@ import org.scalatest.{BeforeAndAfter, FunSuite, Inside, Inspectors, Matchers, Op abstract class BaseTest extends FunSuite with Matchers with OptionValues with Inside with Inspectors with BeforeAndAfter with Logging with Timing { - before { - AppConfig.configureApp() - } + before { + AppConfig.configureApp() + } -} +} \ No newline at end of file diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala index 6e9cbad..6c2d8c3 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/EngineTest.scala @@ -1,44 +1,44 @@ package com.sothr.imagetools.engine /** - * Basic Test of the engines - * - * Created by drew on 1/26/14. - */ + * Basic Test of the engines + * + * Created by drew on 1/26/14. + */ class EngineTest extends BaseTest { - test("SequentialEngine Test getImagesForDirectory for sample directory") { - val engine: Engine = new SequentialEngine() - assertResult(3) { - engine.getImagesForDirectory("sample").length - } - } + test("SequentialEngine Test getImagesForDirectory for sample directory") { + val engine: Engine = new SequentialEngine() + assertResult(3) { + engine.getImagesForDirectory("sample").length + } + } - test("SequentialEngine Test getSimilarImagesForDirectory for sample directory") { - val engine = new SequentialEngine() - val similarImages = engine.getSimilarImagesForDirectory("sample") - assertResult(1) { - similarImages.length - } - assertResult(3) { - similarImages(0).similarImages.size - } - } + test("SequentialEngine Test getSimilarImagesForDirectory for sample directory") { + val engine = new SequentialEngine() + val similarImages = engine.getSimilarImagesForDirectory("sample") + assertResult(1) { + similarImages.length + } + assertResult(3) { + similarImages(0).similarImages.size + } + } - test("ConcurrentEngine Test getImagesForDirectory for sample directory") { - val engine: Engine = new ConcurrentEngine() - assertResult(3) { - engine.getImagesForDirectory("sample").length - } - } + test("ConcurrentEngine Test getImagesForDirectory for sample directory") { + val engine: Engine = new ConcurrentEngine() + assertResult(3) { + engine.getImagesForDirectory("sample").length + } + } - test("ConcurrentEngine Test getSimilarImagesForDirectory for sample directory") { - val engine = new ConcurrentEngine() - val similarImages = engine.getSimilarImagesForDirectory("sample") - assertResult(1) { - similarImages.length - } - assertResult(3) { - similarImages(0).similarImages.size - } - } -} + test("ConcurrentEngine Test getSimilarImagesForDirectory for sample directory") { + val engine = new ConcurrentEngine() + val similarImages = engine.getSimilarImagesForDirectory("sample") + assertResult(1) { + similarImages.length + } + assertResult(3) { + similarImages(0).similarImages.size + } + } +} \ No newline at end of file diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/ScalaAppTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/ScalaAppTest.scala index 404417e..145c669 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/ScalaAppTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/ScalaAppTest.scala @@ -2,8 +2,8 @@ package com.sothr.imagetools.engine class ScalaAppTest extends BaseTest { - test("I Do Nothing Just Make Sure The Framework Works") { - assert(true) - } + test("I Do Nothing Just Make Sure The Framework Works") { + assert(true) + } -} +} \ No newline at end of file diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala b/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala index b82d2eb..c0bfa74 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/TestParams.scala @@ -1,7 +1,7 @@ package com.sothr.imagetools.engine object TestParams { - val LargeSampleImage1 = "sample/sample_01_large.jpg" - val MediumSampleImage1 = "sample/sample_01_medium.jpg" - val SmallSampleImage1 = "sample/sample_01_small.jpg" + val LargeSampleImage1 = "sample/sample_01_large.jpg" + val MediumSampleImage1 = "sample/sample_01_medium.jpg" + val SmallSampleImage1 = "sample/sample_01_small.jpg" } \ No newline at end of file diff --git a/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala b/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala index 8966525..be8995b 100644 --- a/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala +++ b/engine/src/test/scala/com/sothr/imagetools/engine/image/ImageFilterTest.scala @@ -5,38 +5,38 @@ import java.io.File import com.sothr.imagetools.engine.BaseTest /** - * Test to make sure that the image filters work - * - * Created by drew on 1/26/14. - */ + * Test to make sure that the image filters work + * + * Created by drew on 1/26/14. + */ class ImageFilterTest extends BaseTest { - test("Confirm ImageFilter Works") { - val filter: ImageFilter = new ImageFilter() - val bogusDirectory = new File(".") - assert(filter.accept(bogusDirectory, "test.png")) - assert(filter.accept(bogusDirectory, "test.bmp")) - assert(filter.accept(bogusDirectory, "test.gif")) - assert(filter.accept(bogusDirectory, "test.jpg")) - assert(filter.accept(bogusDirectory, "test.jpeg")) - assert(filter.accept(bogusDirectory, "test.jpeg.jpg")) - } + test("Confirm ImageFilter Works") { + val filter: ImageFilter = new ImageFilter() + val bogusDirectory = new File(".") + assert(filter.accept(bogusDirectory, "test.png")) + assert(filter.accept(bogusDirectory, "test.bmp")) + assert(filter.accept(bogusDirectory, "test.gif")) + assert(filter.accept(bogusDirectory, "test.jpg")) + assert(filter.accept(bogusDirectory, "test.jpeg")) + assert(filter.accept(bogusDirectory, "test.jpeg.jpg")) + } - test("Confirm ImageFiler Fails") { - val filter: ImageFilter = new ImageFilter() - val bogusDirectory = new File(".") - assertResult(false) { - filter.accept(bogusDirectory, "test") - } - assertResult(false) { - filter.accept(bogusDirectory, "test.mp4") - } - assertResult(false) { - filter.accept(bogusDirectory, "test.gif.mp4") - } - assertResult(false) { - filter.accept(bogusDirectory, "") - } - } + test("Confirm ImageFiler Fails") { + val filter: ImageFilter = new ImageFilter() + val bogusDirectory = new File(".") + assertResult(false) { + filter.accept(bogusDirectory, "test") + } + assertResult(false) { + filter.accept(bogusDirectory, "test.mp4") + } + assertResult(false) { + filter.accept(bogusDirectory, "test.gif.mp4") + } + assertResult(false) { + filter.accept(bogusDirectory, "") + } + } -} +} \ No newline at end of file diff --git a/gui/pom.xml b/gui/pom.xml index 3d6cbf8..7de0573 100644 --- a/gui/pom.xml +++ b/gui/pom.xml @@ -1,126 +1,126 @@ - 4.0.0 + 4.0.0 - - com.sothr.imagetools - parent - 1.0.1 - ../parent - + + com.sothr.imagetools + parent + 1.0.1 + ../parent + - gui - 0.1.1 - jar + gui + 0.1.1 + jar - ImageTools-GUI - The Graphical User Interface for Image-Tools - http://imagetools.sothr.com - - Sothr Software - + ImageTools-GUI + The Graphical User Interface for Image-Tools + http://imagetools.sothr.com + + Sothr Software + - - - com.sothr.imagetools - engine - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-access - - - org.slf4j - slf4j-api - - - org.clapper - grizzled-slf4j_${scala.binary.version} - - - org.scala-lang - scala-library - - - org.commonjava.googlecode.markdown4j - markdown4j - - + + + com.sothr.imagetools + engine + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-access + + + org.slf4j + slf4j-api + + + org.clapper + grizzled-slf4j_${scala.binary.version} + + + org.scala-lang + scala-library + + + org.commonjava.googlecode.markdown4j + markdown4j + + - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - package - - jar - - - - - true - lib/ - com.sothr.imagetools.ui.App - - - - ${project.build.directory}/release - - - - - - - maven-antrun-plugin - 1.4 - - - prepare - process-resources - - - - - - - - - - run - - - - package - package - - - - - - - - run - - - - - - + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + package + + jar + + + + + true + lib/ + com.sothr.imagetools.ui.App + + + + ${project.build.directory}/release + + + + + + + maven-antrun-plugin + 1.4 + + + prepare + process-resources + + + + + + + + + + run + + + + package + package + + + + + + + + run + + + + + + diff --git a/gui/src/includes/logback.xml b/gui/src/includes/logback.xml index 077b4d1..845280a 100644 --- a/gui/src/includes/logback.xml +++ b/gui/src/includes/logback.xml @@ -1,75 +1,75 @@ - - - - - - false - [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n - - - INFO - - - - - ImageTools.debug - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - DEBUG - - - 1 - ImageTools.debug.%i - - - 5MB - - - - - ImageTools.info - - false - [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - INFO - - - 1 - ImageTools.info.%i - - - 500KB - - - - - ImageTools.err - - false - [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n - - - ERROR - - - 1 - ImageTools.err.%i - - - 500KB - - - - - - - - + + + + + + false + [%date{HH:mm:ss}] %-5level [%c{16}] - %message%n + + + INFO + + + + + ImageTools.debug + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + DEBUG + + + 1 + ImageTools.debug.%i + + + 5MB + + + + + ImageTools.info + + false + [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + INFO + + + 1 + ImageTools.info.%i + + + 500KB + + + + + ImageTools.err + + false + [%.16thread] [%date{yy-MM-dd HH:mm:ss}] %-5level [%c{16}] - %message%n + + + ERROR + + + 1 + ImageTools.err.%i + + + 500KB + + + + + + + + \ No newline at end of file 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 0f7bb32..4dbfc97 100644 --- a/gui/src/main/java/com/sothr/imagetools/ui/App.java +++ b/gui/src/main/java/com/sothr/imagetools/ui/App.java @@ -24,78 +24,78 @@ import java.util.List; */ public class App extends Application { - private static final String MAINGUI_FXML = "fxml/mainapp/MainApp.fxml"; - private static Logger logger; + private static final String MAINGUI_FXML = "fxml/mainapp/MainApp.fxml"; + private static Logger logger; - public static void main(String[] args) { - AppConfig.configureApp(); + public static void main(String[] args) { + AppConfig.configureApp(); - try { - //try to run the UI - launch(args); - } catch (Exception ex) { - logger.error("A fatal error has occurred: ", ex); - //show popup about the error to the user then exit - } - } + try { + //try to run the UI + launch(args); + } catch (Exception ex) { + logger.error("A fatal error has occurred: ", ex); + //show popup about the error to the user then exit + } + } - @Override - public void init() throws Exception { - AppConfig.configureApp(); - logger = LoggerFactory.getLogger(this.getClass()); - logger.info("Initializing Image Tools"); - List parameters = this.getParameters().getRaw(); - logger.info(String.format("Application was called with '%s' parameters", parameters.toString())); - super.init(); - } + @Override + public void init() throws Exception { + AppConfig.configureApp(); + logger = LoggerFactory.getLogger(this.getClass()); + logger.info("Initializing Image Tools"); + List parameters = this.getParameters().getRaw(); + logger.info(String.format("Application was called with '%s' parameters", parameters.toString())); + super.init(); + } - @Override - public void start(Stage primaryStage) throws Exception { - logger.info("Image-Tools is starting"); - logger.info(String.format("Launching GUI with FXML file %s", MAINGUI_FXML)); - //store the primary stage globally for reference in popups and the like - AppConfig.setPrimaryStage(primaryStage); - try { - //Confirm we have the plugin for JPEG color fixes - Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); - while (readers.hasNext()) { - logger.info("Image Reader Plugin: [{}] Available", new Object[]{readers.next()}); - } + @Override + public void start(Stage primaryStage) throws Exception { + logger.info("Image-Tools is starting"); + logger.info(String.format("Launching GUI with FXML file %s", MAINGUI_FXML)); + //store the primary stage globally for reference in popups and the like + AppConfig.setPrimaryStage(primaryStage); + try { + //Confirm we have the plugin for JPEG color fixes + Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); + while (readers.hasNext()) { + logger.info("Image Reader Plugin: [{}] Available", new Object[]{readers.next()}); + } - 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"); - primaryStage.setMinHeight(600.0); - primaryStage.setMinWidth(800.0); - primaryStage.setResizable(true); - //show main scene - primaryStage.show(); - } catch (IOException ioe) { - String message = String.format("Unable to load FXML file: %s", MAINGUI_FXML); - ImageToolsException ite = new ImageToolsException(message, ioe); - logger.error(message, ioe); - throw ite; - } catch (Exception ex) { - String message = "An unhandled exception was thrown by the GUI"; - ImageToolsException ite = new ImageToolsException(message, ex); - logger.error(message, ex); - throw ite; - } - } + 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"); + primaryStage.setMinHeight(600.0); + primaryStage.setMinWidth(800.0); + primaryStage.setResizable(true); + //show main scene + primaryStage.show(); + } catch (IOException ioe) { + String message = String.format("Unable to load FXML file: %s", MAINGUI_FXML); + ImageToolsException ite = new ImageToolsException(message, ioe); + logger.error(message, ioe); + throw ite; + } catch (Exception ex) { + String message = "An unhandled exception was thrown by the GUI"; + ImageToolsException ite = new ImageToolsException(message, ex); + logger.error(message, ex); + throw ite; + } + } - @Override - public void stop() throws Exception { - logger.info("Image-Tools is shutting down"); - AppConfig.shutdown(); - super.stop(); - //force the JVM to close - System.exit(0); - } -} + @Override + public void stop() throws Exception { + logger.info("Image-Tools is shutting down"); + AppConfig.shutdown(); + super.stop(); + //force the JVM to close + System.exit(0); + } +} \ No newline at end of file diff --git a/gui/src/main/resources/fxml/mainapp/MainApp.fxml b/gui/src/main/resources/fxml/mainapp/MainApp.fxml index 4e6afb4..ca6099b 100644 --- a/gui/src/main/resources/fxml/mainapp/MainApp.fxml +++ b/gui/src/main/resources/fxml/mainapp/MainApp.fxml @@ -2,196 +2,215 @@ - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +