You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

441 lines
14 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. package com.sothr.imagetools.engine
  2. import java.io.File
  3. import java.util.concurrent.TimeUnit
  4. import akka.actor._
  5. import akka.pattern.ask
  6. import akka.routing.{Broadcast, RoundRobinRouter, SmallestMailboxRouter}
  7. import akka.util.Timeout
  8. import com.sothr.imagetools.engine.hash.HashService
  9. import com.sothr.imagetools.engine.image.{Image, ImageService, SimilarImages}
  10. import com.sothr.imagetools.engine.util._
  11. import scala.collection.mutable
  12. import scala.concurrent.Await
  13. class ConcurrentEngine extends Engine with grizzled.slf4j.Logging {
  14. val engineProcessingController = system.actorOf(Props[ConcurrentEngineProcessingController], name = "EngineProcessingController")
  15. val engineSimilarityController = system.actorOf(Props[ConcurrentEngineSimilarityController], name = "EngineSimilarityController")
  16. implicit val timeout = Timeout(30, TimeUnit.SECONDS)
  17. override def setSearchedListener(listenerRef: ActorRef) = {
  18. this.searchedListener = listenerRef;
  19. }
  20. override def setProcessedListener(listenerRef: ActorRef) = {
  21. engineProcessingController ! SetNewListener(listenerRef)
  22. }
  23. override def setSimilarityListener(listenerRef: ActorRef) = {
  24. engineSimilarityController ! SetNewListener(listenerRef)
  25. }
  26. //needs to be rebuilt
  27. def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = {
  28. debug(s"Looking for similar images in directory: $directoryPath")
  29. val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth)
  30. info(s"Searching ${images.length} images for similarities")
  31. // make sure the engine is listening
  32. engineSimilarityController ! EngineStart
  33. for (rootImage <- images) {
  34. debug(s"Looking for images similar to: ${rootImage.imagePath}")
  35. engineSimilarityController ! EngineCompareImages(rootImage, images)
  36. }
  37. //tell the comparison engine there's nothing left to compare
  38. engineSimilarityController ! EngineNoMoreComparisons
  39. var doneProcessing = false
  40. while (!doneProcessing) {
  41. val f = engineSimilarityController ? EngineIsSimilarityFinished
  42. val result = Await.result(f, timeout.duration).asInstanceOf[Boolean]
  43. result match {
  44. case true =>
  45. doneProcessing = true
  46. debug("Processing Complete")
  47. case false =>
  48. debug("Still Processing")
  49. //sleep thread
  50. Thread.sleep(5000L)
  51. //val future = Future { blocking(Thread.sleep(5000L)); "done" }
  52. }
  53. }
  54. val f = engineSimilarityController ? EngineGetSimilarityResults
  55. val result = Await.result(f, timeout.duration).asInstanceOf[List[SimilarImages]]
  56. val cleanedSimilarImages = this.processSimilarities(result)
  57. var similarCount = 0
  58. for (similarImage <- cleanedSimilarImages) {
  59. similarCount += 1 + similarImage.similarImages.size
  60. }
  61. info(s"Finished processing ${images.size} images. Found $similarCount similar images")
  62. cleanedSimilarImages
  63. }
  64. def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = {
  65. debug(s"Looking for images in directory: $directoryPath")
  66. val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth)
  67. val images: mutable.MutableList[Image] = new mutable.MutableList[Image]()
  68. // make sure the engine is listening
  69. engineProcessingController ! EngineStart
  70. for (file <- imageFiles) {
  71. engineProcessingController ! EngineProcessFile(file)
  72. }
  73. engineProcessingController ! EngineNoMoreFiles
  74. var doneProcessing = false
  75. while (!doneProcessing) {
  76. val f = engineProcessingController ? EngineIsProcessingFinished
  77. val result = Await.result(f, timeout.duration).asInstanceOf[Boolean]
  78. result match {
  79. case true =>
  80. doneProcessing = true
  81. debug("Processing Complete")
  82. case false =>
  83. debug("Still Processing")
  84. //sleep thread
  85. Thread.sleep(5000L)
  86. //val future = Future { blocking(Thread.sleep(5000L)); "done" }
  87. }
  88. }
  89. val f = engineProcessingController ? EngineGetProcessingResults
  90. val result = Await.result(f, timeout.duration).asInstanceOf[List[Image]]
  91. images ++= result
  92. images.toList
  93. }
  94. }
  95. // external cases //
  96. case class SetNewListener(listenerType: ActorRef)
  97. case object EngineStart
  98. // processing files into images
  99. case class EngineProcessFile(file: File)
  100. case object EngineNoMoreFiles
  101. case object EngineIsProcessingFinished
  102. case object EngineGetProcessingResults
  103. //internal cases
  104. case class EngineFileProcessed(image: Image)
  105. case object EngineActorProcessingFinished
  106. case object EngineActorReactivate
  107. class ConcurrentEngineProcessingController extends Actor with ActorLogging {
  108. val numOfRouters = {
  109. val max = PropertiesService.get(PropertyEnum.ConcurrentProcessingLimit.toString).toInt
  110. val processors = Runtime.getRuntime.availableProcessors()
  111. var threads = 0
  112. if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1
  113. threads
  114. }
  115. var router = context.actorOf(Props[ConcurrentEngineProcessingActor].withRouter(SmallestMailboxRouter(nrOfInstances = numOfRouters)))
  116. var images: mutable.MutableList[Image] = new mutable.MutableList[Image]()
  117. var toProcess = 0
  118. var processed = 0
  119. var processorsFinished = 0
  120. var listener = context.actorOf(Props[DefaultLoggingEngineListener],
  121. name = "ProcessedEngineListener")
  122. override def preStart() = {
  123. log.info("Staring the controller for processing images")
  124. log.info("Using {} actors to process images", numOfRouters)
  125. }
  126. override def receive = {
  127. case command: SetNewListener => setListener(command.listenerType)
  128. case command: EngineProcessFile => processFile(command)
  129. case command: EngineFileProcessed => fileProcessed(command)
  130. case EngineStart => startEngine()
  131. case EngineNoMoreFiles => requestWrapup()
  132. case EngineActorProcessingFinished => actorProcessingFinished()
  133. case EngineIsProcessingFinished => checkIfProcessingIsFinished()
  134. case EngineGetProcessingResults => checkForResults()
  135. case _ => log.info("received unknown message")
  136. }
  137. def setListener(newListener: ActorRef) = {
  138. //remove the old listener
  139. this.listener ! PoisonPill
  140. //setup the new listener
  141. this.listener = newListener
  142. }
  143. def startEngine() = {
  144. router ! Broadcast(EngineActorReactivate)
  145. }
  146. def processFile(command: EngineProcessFile) = {
  147. log.debug(s"Started evaluating ${command.file.getAbsolutePath}")
  148. toProcess += 1
  149. router ! command
  150. }
  151. def fileProcessed(command: EngineFileProcessed) = {
  152. processed += 1
  153. if (processed % 25 == 0 || processed == toProcess) {
  154. //log.info(s"Processed $processed/$toProcess")
  155. listener ! ComparedFileCount(processed, toProcess)
  156. }
  157. if (command.image != null) {
  158. log.debug(s"processed image: ${command.image.imagePath}")
  159. images += command.image
  160. }
  161. }
  162. def requestWrapup() = {
  163. router ! Broadcast(EngineNoMoreFiles)
  164. }
  165. /*
  166. * Record that a processor is done
  167. */
  168. def actorProcessingFinished() = {
  169. processorsFinished += 1
  170. }
  171. /*
  172. * Check if processing is done
  173. */
  174. def checkIfProcessingIsFinished() = {
  175. try {
  176. if (processorsFinished >= numOfRouters) sender ! true else sender ! false
  177. } catch {
  178. case e: Exception
  179. sender ! akka.actor.Status.Failure(e)
  180. throw e
  181. }
  182. }
  183. /*
  184. * Get the results of the processing
  185. */
  186. def checkForResults() = {
  187. try {
  188. listener ! SubmitMessage(s"Finished Processing $processed/$processed images")
  189. processorsFinished = 0
  190. toProcess = 0
  191. processed = 0
  192. sender ! images.toList
  193. images.clear()
  194. } catch {
  195. case e: Exception
  196. sender ! akka.actor.Status.Failure(e)
  197. throw e
  198. }
  199. }
  200. override def postStop() = {
  201. super.postStop()
  202. this.listener ! PoisonPill
  203. }
  204. }
  205. class ConcurrentEngineProcessingActor extends Actor with ActorLogging {
  206. var ignoreMessages = false
  207. override def receive = {
  208. case command: EngineProcessFile => processFile(command)
  209. case EngineNoMoreFiles => finishedProcessingFiles()
  210. case EngineActorReactivate => ignoreMessages = false
  211. case _ => log.info("received unknown message")
  212. }
  213. def processFile(command: EngineProcessFile) = {
  214. if (!ignoreMessages) {
  215. val image = ImageService.getImage(command.file)
  216. if (image != null) {
  217. sender ! EngineFileProcessed(image)
  218. } else {
  219. log.debug(s"Failed to process image: ${command.file.getAbsolutePath}")
  220. }
  221. }
  222. }
  223. def finishedProcessingFiles() = {
  224. if (!ignoreMessages) {
  225. ignoreMessages = true
  226. sender ! EngineActorProcessingFinished
  227. }
  228. }
  229. }
  230. //finding similarities between images
  231. case class EngineCompareImages(image1: Image, images: List[Image])
  232. case class EngineCompareImagesComplete(similarImages: SimilarImages)
  233. case object EngineNoMoreComparisons
  234. case object EngineIsSimilarityFinished
  235. case object EngineGetSimilarityResults
  236. case object EngineActorCompareImagesFinished
  237. class ConcurrentEngineSimilarityController extends Actor with ActorLogging {
  238. val numOfRouters = {
  239. val max = PropertiesService.get(PropertyEnum.ConcurrentSimilarityLimit.toString).toInt
  240. val processors = Runtime.getRuntime.availableProcessors()
  241. var threads = 0
  242. if (processors > max) threads = max else if (processors > 1) threads = processors - 1 else threads = 1
  243. threads
  244. }
  245. val router = context.actorOf(Props[ConcurrentEngineSimilarityActor].withRouter(RoundRobinRouter(nrOfInstances = numOfRouters)))
  246. val allSimilarImages = new mutable.MutableList[SimilarImages]
  247. var toProcess = 0
  248. var processed = 0
  249. var processorsFinished = 0
  250. var listener = context.actorOf(Props[DefaultLoggingEngineListener],
  251. name = "SimilarityEngineListener")
  252. override def preStart() = {
  253. log.info("Staring the controller for processing similarities between images")
  254. log.info("Using {} actors to process image similarities", numOfRouters)
  255. }
  256. override def receive = {
  257. case command: SetNewListener => setListener(command.listenerType)
  258. case command: EngineCompareImages => findSimilarities(command)
  259. case command: EngineCompareImagesComplete => similarityProcessed(command)
  260. case EngineStart => startEngine()
  261. case EngineNoMoreComparisons => requestWrapup()
  262. case EngineActorCompareImagesFinished => actorProcessingFinished()
  263. case EngineIsSimilarityFinished => checkIfProcessingIsFinished()
  264. case EngineGetSimilarityResults => checkForResults()
  265. case _ => log.info("received unknown message")
  266. }
  267. def setListener(newListener: ActorRef) = {
  268. //remove the old listener
  269. this.listener ! PoisonPill
  270. //setup the new listener
  271. this.listener = newListener
  272. }
  273. def startEngine() = {
  274. router ! Broadcast(EngineActorReactivate)
  275. }
  276. def findSimilarities(command: EngineCompareImages) = {
  277. log.debug(s"Finding similarities between {} and {} images", command.image1.imagePath, command.images.length)
  278. toProcess += 1
  279. if (toProcess % 250 == 0) {
  280. //log.info("Sent {} images to be processed for similarites", toProcess)
  281. listener ! SubmitMessage(s"Sent $toProcess images to be processed for similarites")
  282. }
  283. //just relay the command to our workers
  284. router ! command
  285. }
  286. def similarityProcessed(command: EngineCompareImagesComplete) = {
  287. processed += 1
  288. if (processed % 25 == 0 || processed == toProcess) {
  289. //log.info(s"Processed $processed/$toProcess")
  290. listener ! ScannedFileCount(processed, toProcess)
  291. }
  292. if (command.similarImages != null) {
  293. log.debug(s"Found similar images: ${command.similarImages}")
  294. allSimilarImages += command.similarImages
  295. }
  296. }
  297. def requestWrapup() = {
  298. router ! Broadcast(EngineNoMoreComparisons)
  299. }
  300. /*
  301. * Record that a processor is done
  302. */
  303. def actorProcessingFinished() = {
  304. processorsFinished += 1
  305. log.debug("Similarity Processor Reported Finished")
  306. }
  307. /*
  308. * Check if processing is done
  309. */
  310. def checkIfProcessingIsFinished() = {
  311. try {
  312. log.debug("Processors Finished {}/{}", processorsFinished, numOfRouters)
  313. if (processorsFinished >= numOfRouters) sender ! true else sender ! false
  314. } catch {
  315. case e: Exception
  316. sender ! akka.actor.Status.Failure(e)
  317. throw e
  318. }
  319. }
  320. /*
  321. * Get the results of the processing
  322. */
  323. def checkForResults() = {
  324. try {
  325. listener ! SubmitMessage(s"Finished Scanning $processed/$processed images")
  326. processorsFinished = 0
  327. toProcess = 0
  328. processed = 0
  329. sender ! allSimilarImages.toList
  330. allSimilarImages.clear()
  331. } catch {
  332. case e: Exception
  333. sender ! akka.actor.Status.Failure(e)
  334. throw e
  335. }
  336. }
  337. override def postStop() = {
  338. super.postStop()
  339. this.listener ! PoisonPill
  340. }
  341. }
  342. class ConcurrentEngineSimilarityActor extends Actor with ActorLogging {
  343. var ignoreMessages = false
  344. override def receive = {
  345. case command: EngineCompareImages => compareImages(command)
  346. case EngineNoMoreComparisons => finishedComparisons()
  347. case EngineActorReactivate => ignoreMessages = false
  348. case _ => log.info("received unknown message")
  349. }
  350. def compareImages(command: EngineCompareImages) = {
  351. if (!ignoreMessages) {
  352. val similarImages = new mutable.MutableList[Image]()
  353. for (image <- command.images) {
  354. if (!command.image1.equals(image)) {
  355. if (HashService.areImageHashesSimilar(command.image1.hashes, image.hashes)) {
  356. similarImages += image
  357. }
  358. }
  359. }
  360. //only send a message if we find similar images
  361. if (similarImages.length >= 1) {
  362. val similarImage = new SimilarImages(command.image1, similarImages.toList)
  363. log.debug(s"Found ${similarImage.similarImages.length} similar images to ${similarImage.rootImage}")
  364. sender ! EngineCompareImagesComplete(similarImage)
  365. } else {
  366. log.debug(s"Found no similar images to ${command.image1}")
  367. sender ! EngineCompareImagesComplete(null)
  368. }
  369. }
  370. }
  371. def finishedComparisons() = {
  372. if (!ignoreMessages) {
  373. ignoreMessages = true
  374. log.debug("Finished processing comparisons")
  375. sender ! EngineActorCompareImagesFinished
  376. }
  377. }
  378. }