Browse Source

Moved logging primarily over to the typesafe:config system which uses JSON style nested configs. Also fixed some logging issues.

master
Drew Short 11 years ago
parent
commit
abdb1af77f
  1. 9
      pom.xml
  2. 2
      src/includes/logback.xml
  3. 1
      src/main/java/com/sothr/imagetools/AppCLI.java
  4. 6
      src/main/java/com/sothr/imagetools/AppConfig.java
  5. 75
      src/main/resources/application.conf
  6. 38
      src/main/scala/com/sothr/imagetools/util/PropertiesEnum.scala
  7. 143
      src/main/scala/com/sothr/imagetools/util/PropertiesService.scala
  8. 82
      src/test/resources/application.conf
  9. 15
      todo

9
pom.xml

@ -44,6 +44,8 @@
<lib.commons-cli.version>1.2</lib.commons-cli.version>
<lib.commons-codec.version>1.9</lib.commons-codec.version>
<lib.jtransforms.version>2.4.0</lib.jtransforms.version>
<lib.typesafe-config.version>1.2.0</lib.typesafe-config.version>
<lib.thumbnailator.version>[0.4, 0.5)</lib.thumbnailator.version>
</properties>
<dependencies>
@ -92,7 +94,12 @@
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>[0.4, 0.5)</version>
<version>${lib.thumbnailator.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>${lib.typesafe-config.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jtransforms</groupId>

2
src/includes/logback.xml

@ -4,7 +4,7 @@
<encoder>
<!-- Sorry Windows Users -->
<withJansi>false</withJansi>
<pattern>[%-5level [%c{16}] - %message%n</pattern>
<pattern>[%date{HH:mm:ss}] %-5level [%c{16}] - %message%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>

1
src/main/java/com/sothr/imagetools/AppCLI.java

@ -26,6 +26,7 @@ class AppCLI {
CommandLineParser parser = new BasicParser();
CommandLine cmd = parser.parse(options, args);
process(cmd);
AppConfig.saveProperties();
System.exit(0);
} catch (Exception ex) {
logger.error("Unhandled exception in AppCLI",ex);

6
src/main/java/com/sothr/imagetools/AppConfig.java

@ -26,8 +26,8 @@ public class AppConfig {
private static Boolean configuredLogging = false;
//Properties defaults
private static final String DEFAULTPROPERTIESFILE = "default.properties";
private static final String USERPROPERTIESFILE = "./config.xml";
private static final String DEFAULTPROPERTIESFILE = "application.conf";
private static final String USERPROPERTIESFILE = "user.conf";
private static Boolean loadedProperties = false;
//Cache defaults
@ -110,7 +110,7 @@ public class AppConfig {
}
public static void saveProperties() {
PropertiesService.saveXMLProperties(USERPROPERTIESFILE);
PropertiesService.saveConf(USERPROPERTIESFILE);
logger.debug("Saved properties");
}

75
src/main/resources/application.conf

@ -1,6 +1,5 @@
//Default Properties File
//Image Tools version: ${project.version}
version = "${build-version}"
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
@ -9,12 +8,8 @@ akka {
//Default App Settings
app {
version.current = "${build-version}"
timed = false
log {
debug = false
info = false
error = true
}
engine {
//Concurrency Settings
concurrent {
@ -22,41 +17,39 @@ app {
processing.limit = 15
}
}
}
#Default Image Settings
image {
//images must be 90% similar
differenceThreshold = 0.90
//control generation of hashes for new images.
hash {
precision=64
}
ahash {
use = true
weight = 0.70
precision = 8
tolerence = 8
}
dhash {
use = true
weight = 0.85
precision = 8
tolerence = 8
#Default Image Settings
image {
//images must be 90% similar
differenceThreshold = 0.90
//control generation of hashes for new images.
hash {
precision=64
}
ahash {
use = true
weight = 0.70
precision = 8
tolerence = 8
}
dhash {
use = true
weight = 0.85
precision = 8
tolerence = 8
}
phash {
//set to false if hashing images is taking too long
use = true
weight = 1.0
precision = 32
tolerence = 8
}
}
phash {
//set to false if hashing images is taking too long
use = true
weight = 1.0
precision = 32
tolerence = 8
//Default Thumbnail Settings
thumbnail {
//Directory where to store thumbnails
directory = ".cache/thumbnails/"
//Size of the thumbnail to generate and store
size = 128
}
}
//Default Thumbnail Settings
thumbnail {
//Directory where to store thumbnails
directory = ".cache/thumbnails/"
//Size of the thumbnail to generate and store
size = 128
}

38
src/main/scala/com/sothr/imagetools/util/PropertiesEnum.scala

@ -2,31 +2,29 @@ package com.sothr.imagetools.util
object PropertiesEnum extends Enumeration {
type PropertiesEnum = Value
val Version = Value("version")
val Version = Value("app.version.current")
val PreviousVersion = Value("app.version.previous")
//default app settings
val LogDebug = Value("app.log.debug")
val LogInfo = Value("app.log.info")
val LogError = Value("app.log.error")
val Timed = Value("app.timed")
//default engine concurrency settings
val ConcurrentSimiliartyLimit = Value("app.engine.concurrent.similarity.limit")
val ConcurrentProcessingLimit = Value("app.engine.concurrent.processing.limit")
//default image settings
val ImageDifferenceThreshold = Value("image.differenceThreshold")
val HashPrecision = Value("image.hash.precision")
val UseAhash = Value("image.ahash.use")
val AhashWeight = Value("image.ahash.weight")
val AhashPrecision = Value("image.ahash.precision")
val AhashTolerance = Value("image.ahash.tolerence")
val UseDhash = Value("image.dhash.use")
val DhashWeight = Value("image.dhash.weight")
val DhashPrecision = Value("image.dhash.precision")
val DhashTolerance = Value("image.dhash.tolerence")
val UsePhash = Value("image.phash.use")
val PhashWeight = Value("image.phash.weight")
val PhashPrecision = Value("image.phash.precision")
val PhashTolerance = Value("image.phash.tolerence")
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.tolerence")
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.tolerence")
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.tolerence")
//Default Thumbnail Settings
val ThumbnailDirectory = Value("thumbnail.directory")
val ThumbnailSize = Value("thumbnail.size")
val ThumbnailDirectory = Value("app.thumbnail.directory")
val ThumbnailSize = Value("app.thumbnail.size")
}

143
src/main/scala/com/sothr/imagetools/util/PropertiesService.scala

@ -1,8 +1,9 @@
package com.sothr.imagetools.util
import java.util.Properties
import com.typesafe.config.{Config, ConfigFactory}
import grizzled.slf4j.Logging
import java.io.{FileInputStream, FileOutputStream, OutputStream}
import java.io.{File, PrintStream, FileOutputStream}
import java.util.Properties
import scala.collection.JavaConversions._
/*
@ -10,107 +11,109 @@ import scala.collection.JavaConversions._
*/
object PropertiesService extends Logging {
private val properties:Properties = new Properties()
private var defaultConf:Config = null
private var userConf:Config = null
private var newUserConf:Properties = new Properties()
private var version:Version = null
def getVersion:Version = this.version
private val propertiesToClean:Array[String] = Array("version")
//specific highly used properties
var TimingEnabled:Boolean = false
//ahash
var aHashPrecision = 0
var aHashTolerance = 0
var aHashWeight = 0.0f
var useAhash = false
//dhash
var dHashPrecision = 0
var dHashTolerance = 0
var dHashWeight = 0.0f
var useDhash = false
//phash
var pHashPrecision = 0
var pHashTolerance = 0
var pHashWeight = 0.0f
var usePhash = false
/*
* Load the properties file from the specified location
*/
def loadProperties(defaultLocation:String, userLocation:String = null) = {
info(s"Attempting to load properties from: $defaultLocation")
val defaultInputStream = ResourceLoader.get.getResourceStream(defaultLocation)
properties.load(defaultInputStream)
defaultConf = ConfigFactory.load(defaultLocation);
if (userLocation != null) {
val userInputStream = new FileInputStream(userLocation)
val userProperties = new Properties();
userProperties.loadFromXML(userInputStream)
for (propertyName:String <- userProperties.stringPropertyNames()) {
properties.setProperty(propertyName, userProperties.getProperty(propertyName));
}
userConf = ConfigFactory.parseFile(new File(userLocation));
} else {
userConf = ConfigFactory.empty
info("No user properties file exists to load from")
}
version = new Version(properties.getProperty("version"));
version = new Version(get(PropertiesEnum.Version.toString));
info(s"Detected Version: $version")
//load special properties
DebugLogEnabled = get(PropertiesEnum.LogDebug.toString).toBoolean
InfoLogEnabled = get(PropertiesEnum.LogInfo.toString).toBoolean
ErrorLogEnabled = get(PropertiesEnum.LogError.toString).toBoolean
TimingEnabled = get(PropertiesEnum.Timed.toString).toBoolean
//ahash
aHashPrecision = PropertiesService.get(PropertiesEnum.AhashPrecision.toString).toInt
aHashTolerance = PropertiesService.get(PropertiesEnum.AhashTolerance.toString).toInt
aHashWeight = PropertiesService.get(PropertiesEnum.AhashWeight.toString).toFloat
useAhash = PropertiesService.get(PropertiesEnum.UseAhash.toString).toBoolean
aHashPrecision = get(PropertiesEnum.AhashPrecision.toString).toInt
aHashTolerance = get(PropertiesEnum.AhashTolerance.toString).toInt
aHashWeight = get(PropertiesEnum.AhashWeight.toString).toFloat
useAhash = get(PropertiesEnum.UseAhash.toString).toBoolean
//dhash
dHashPrecision = PropertiesService.get(PropertiesEnum.DhashPrecision.toString).toInt
dHashTolerance = PropertiesService.get(PropertiesEnum.DhashTolerance.toString).toInt
dHashWeight = PropertiesService.get(PropertiesEnum.DhashWeight.toString).toFloat
useDhash = PropertiesService.get(PropertiesEnum.UseDhash.toString).toBoolean
dHashPrecision = get(PropertiesEnum.DhashPrecision.toString).toInt
dHashTolerance = get(PropertiesEnum.DhashTolerance.toString).toInt
dHashWeight = get(PropertiesEnum.DhashWeight.toString).toFloat
useDhash = get(PropertiesEnum.UseDhash.toString).toBoolean
//phash
pHashPrecision = PropertiesService.get(PropertiesEnum.PhashPrecision.toString).toInt
pHashTolerance = PropertiesService.get(PropertiesEnum.PhashTolerance.toString).toInt
pHashWeight = PropertiesService.get(PropertiesEnum.PhashWeight.toString).toFloat
usePhash = PropertiesService.get(PropertiesEnum.UsePhash.toString).toBoolean
pHashPrecision = get(PropertiesEnum.PhashPrecision.toString).toInt
pHashTolerance = get(PropertiesEnum.PhashTolerance.toString).toInt
pHashWeight = get(PropertiesEnum.PhashWeight.toString).toFloat
usePhash = get(PropertiesEnum.UsePhash.toString).toBoolean
info("Loaded Special Properties")
}
/**
* Gets a properties object that is cleaned of things that are expected to change. i.e. version
*/
private def getCleanProperties():Properties = {
val cleanProperties:Properties = properties.clone().asInstanceOf[Properties]
//Remove properties to be cleaned
for (key <- propertiesToClean) {
cleanProperties.remove(key)
}
return cleanProperties
private def cleanAndPrepareNewUserProperties():Properties = {
//insert special keys here
newUserConf.setProperty(PropertiesEnum.PreviousVersion.toString, version.parsableToString())
//remove special keys here
newUserConf.remove(PropertiesEnum.Version.toString)
newUserConf
}
def saveXMLProperties(location:String) = {
private def getCleanedMergedUserConf():Config = {
ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback(userConf)
}
def saveConf(location:String) = {
info(s"Saving user properties to $location")
val out:OutputStream = new FileOutputStream(location, false)
val cleanProperties = getCleanProperties
//insert special keys here
cleanProperties.setProperty("version.previous", version.parsableToString())
cleanProperties.storeToXML(out, "User Properties")
val out:PrintStream = new PrintStream(new FileOutputStream(location, false))
val userConfToSave = getCleanedMergedUserConf
//print to the output stream
out.print(userConfToSave.root.render)
out.flush()
out.close()
}
def get(key:String):String = {
return properties.getProperty(key)
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)
}
return result
}
def set(key:String, value:String) = {
properties.setProperty(key, value)
newUserConf.setProperty(key, value)
}
//specific highly used properties
var DebugLogEnabled:Boolean = false
var InfoLogEnabled:Boolean = false
var ErrorLogEnabled:Boolean = false
var TimingEnabled:Boolean = false
//ahash
var aHashPrecision = 0
var aHashTolerance = 0
var aHashWeight = 0.0f
var useAhash = false
//dhash
var dHashPrecision = 0
var dHashTolerance = 0
var dHashWeight = 0.0f
var useDhash = false
//phash
var pHashPrecision = 0
var pHashTolerance = 0
var pHashWeight = 0.0f
var usePhash = false
}

82
src/test/resources/application.conf

@ -1,63 +1,55 @@
//Default Properties File
//Image Tools version: ${project.version}
version = "${build-version}"
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
loglevel = "INFO"
}
//Default App Settings
app {
version.current = "${build-version}"
timed = false
log {
debug = true
info = true
error = true
}
engine {
//Concurrency Settings
concurrent {
similarity.limit = 15
processing.limit = 15
similarity.limit = 2
processing.limit = 2
}
}
}
#Default Image Settings
image {
//images must be 90% similar
differenceThreshold = 0.90
//control generation of hashes for new images.
hash {
precision=64
}
ahash {
use = true
weight = 0.70
precision = 8
tolerence = 8
}
dhash {
use = true
weight = 0.85
precision = 8
tolerence = 8
#Default Image Settings
image {
//images must be 90% similar
differenceThreshold = 0.90
//control generation of hashes for new images.
hash {
precision=64
}
ahash {
use = true
weight = 0.70
precision = 8
tolerence = 8
}
dhash {
use = true
weight = 0.85
precision = 8
tolerence = 8
}
phash {
//set to false if hashing images is taking too long
use = true
weight = 1.0
precision = 32
tolerence = 8
}
}
phash {
//set to false if hashing images is taking too long
use = true
weight = 1.0
precision = 32
tolerence = 8
//Default Thumbnail Settings
thumbnail {
//Directory where to store thumbnails
directory = ".cache/thumbnails/"
//Size of the thumbnail to generate and store
size = 128
}
}
//Default Thumbnail Settings
thumbnail {
//Directory where to store thumbnails
directory = ".cache/thumbnails/"
//Size of the thumbnail to generate and store
size = 128
}

15
todo

@ -1,18 +1,9 @@
Move over to Logback for the backend
-Convert the configuration file
-Convert the AppConfig to configure basic logging
Profile and improve performance for the ConcurrentEngine when looking for similarities.
-SynchronousEngine is about 2.5x faster currently for finding similarities
-ConcurrentEngine actors for finding similarities are not CPU constrained and need to be
Convert app configuration format from a java properties to a typesafehub:config
-Rewrite properties in new format (JSON)
-Rewrite PropertiesLoader to use the new format
Add functionality to both engines
-Recursive directory scanning
-Determine how to handle similarites there... All images or just per folder
-Move files to new locations
-Rename new files based on their MD5
Add functionality to ImageService
-Cache thumbnails
-Generate thumbnails
-Move caching of images into the ImageService
Improve functionality of the PropertiesService
-Getters and Setters for sepcific values with proper cascading and type awareness
-Nicer debugging?
Loading…
Cancel
Save