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. 11
      src/main/resources/application.conf
  6. 38
      src/main/scala/com/sothr/imagetools/util/PropertiesEnum.scala
  7. 141
      src/main/scala/com/sothr/imagetools/util/PropertiesService.scala
  8. 18
      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-cli.version>1.2</lib.commons-cli.version>
<lib.commons-codec.version>1.9</lib.commons-codec.version> <lib.commons-codec.version>1.9</lib.commons-codec.version>
<lib.jtransforms.version>2.4.0</lib.jtransforms.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> </properties>
<dependencies> <dependencies>
@ -92,7 +94,12 @@
<dependency> <dependency>
<groupId>net.coobird</groupId> <groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId> <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>
<dependency> <dependency>
<groupId>net.sourceforge.jtransforms</groupId> <groupId>net.sourceforge.jtransforms</groupId>

2
src/includes/logback.xml

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

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

@ -26,6 +26,7 @@ class AppCLI {
CommandLineParser parser = new BasicParser(); CommandLineParser parser = new BasicParser();
CommandLine cmd = parser.parse(options, args); CommandLine cmd = parser.parse(options, args);
process(cmd); process(cmd);
AppConfig.saveProperties();
System.exit(0); System.exit(0);
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Unhandled exception in AppCLI",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; private static Boolean configuredLogging = false;
//Properties defaults //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; private static Boolean loadedProperties = false;
//Cache defaults //Cache defaults
@ -110,7 +110,7 @@ public class AppConfig {
} }
public static void saveProperties() { public static void saveProperties() {
PropertiesService.saveXMLProperties(USERPROPERTIESFILE);
PropertiesService.saveConf(USERPROPERTIESFILE);
logger.debug("Saved properties"); logger.debug("Saved properties");
} }

11
src/main/resources/application.conf

@ -1,6 +1,5 @@
//Default Properties File //Default Properties File
//Image Tools version: ${project.version} //Image Tools version: ${project.version}
version = "${build-version}"
akka { akka {
loggers = ["akka.event.slf4j.Slf4jLogger"] loggers = ["akka.event.slf4j.Slf4jLogger"]
@ -9,12 +8,8 @@ akka {
//Default App Settings //Default App Settings
app { app {
version.current = "${build-version}"
timed = false timed = false
log {
debug = false
info = false
error = true
}
engine { engine {
//Concurrency Settings //Concurrency Settings
concurrent { concurrent {
@ -22,8 +17,6 @@ app {
processing.limit = 15 processing.limit = 15
} }
} }
}
#Default Image Settings #Default Image Settings
image { image {
//images must be 90% similar //images must be 90% similar
@ -52,7 +45,6 @@ image {
tolerence = 8 tolerence = 8
} }
} }
//Default Thumbnail Settings //Default Thumbnail Settings
thumbnail { thumbnail {
//Directory where to store thumbnails //Directory where to store thumbnails
@ -60,3 +52,4 @@ thumbnail {
//Size of the thumbnail to generate and store //Size of the thumbnail to generate and store
size = 128 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 { object PropertiesEnum extends Enumeration {
type PropertiesEnum = Value type PropertiesEnum = Value
val Version = Value("version")
val Version = Value("app.version.current")
val PreviousVersion = Value("app.version.previous")
//default app settings //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") val Timed = Value("app.timed")
//default engine concurrency settings //default engine concurrency settings
val ConcurrentSimiliartyLimit = Value("app.engine.concurrent.similarity.limit") val ConcurrentSimiliartyLimit = Value("app.engine.concurrent.similarity.limit")
val ConcurrentProcessingLimit = Value("app.engine.concurrent.processing.limit") val ConcurrentProcessingLimit = Value("app.engine.concurrent.processing.limit")
//default image settings //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 //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")
} }

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

@ -1,8 +1,9 @@
package com.sothr.imagetools.util package com.sothr.imagetools.util
import java.util.Properties
import com.typesafe.config.{Config, ConfigFactory}
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import java.io.{FileInputStream, FileOutputStream, OutputStream}
import java.io.{File, PrintStream, FileOutputStream}
import java.util.Properties
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
/* /*
@ -10,107 +11,109 @@ import scala.collection.JavaConversions._
*/ */
object PropertiesService extends Logging { 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 private var version:Version = null
def getVersion:Version = this.version 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 * Load the properties file from the specified location
*/ */
def loadProperties(defaultLocation:String, userLocation:String = null) = { def loadProperties(defaultLocation:String, userLocation:String = null) = {
info(s"Attempting to load properties from: $defaultLocation") info(s"Attempting to load properties from: $defaultLocation")
val defaultInputStream = ResourceLoader.get.getResourceStream(defaultLocation)
properties.load(defaultInputStream)
defaultConf = ConfigFactory.load(defaultLocation);
if (userLocation != null) { 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 { } else {
userConf = ConfigFactory.empty
info("No user properties file exists to load from") 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") info(s"Detected Version: $version")
//load special properties //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 TimingEnabled = get(PropertiesEnum.Timed.toString).toBoolean
//ahash //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 //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 //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") 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)
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
} }
return cleanProperties
private def getCleanedMergedUserConf():Config = {
ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback(userConf)
} }
def saveXMLProperties(location:String) = {
def saveConf(location:String) = {
info(s"Saving user properties to $location") 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.flush()
out.close() 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) = { 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
} }

18
src/test/resources/application.conf

@ -1,30 +1,22 @@
//Default Properties File //Default Properties File
//Image Tools version: ${project.version} //Image Tools version: ${project.version}
version = "${build-version}"
akka { akka {
loggers = ["akka.event.slf4j.Slf4jLogger"] loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
loglevel = "INFO"
} }
//Default App Settings //Default App Settings
app { app {
version.current = "${build-version}"
timed = false timed = false
log {
debug = true
info = true
error = true
}
engine { engine {
//Concurrency Settings //Concurrency Settings
concurrent { concurrent {
similarity.limit = 15
processing.limit = 15
}
similarity.limit = 2
processing.limit = 2
} }
} }
#Default Image Settings #Default Image Settings
image { image {
//images must be 90% similar //images must be 90% similar
@ -53,7 +45,6 @@ image {
tolerence = 8 tolerence = 8
} }
} }
//Default Thumbnail Settings //Default Thumbnail Settings
thumbnail { thumbnail {
//Directory where to store thumbnails //Directory where to store thumbnails
@ -61,3 +52,4 @@ thumbnail {
//Size of the thumbnail to generate and store //Size of the thumbnail to generate and store
size = 128 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 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 -Move files to new locations
-Rename new files based on their MD5 -Rename new files based on their MD5
Add functionality to ImageService Add functionality to ImageService
-Cache thumbnails -Cache thumbnails
-Generate 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