--- ./Makefile.am	2006/07/05 07:47:24	1.1
+++ ./Makefile.am	2006/07/05 07:47:39
@@ -3,6 +3,7 @@
 plugindir = $(PLUGINDIR)/artdisplay
 plugin_PYTHON = 			\
 	AmazonCoverArtSearch.py		\
+	LocalCoverArtSearch.py		\
 	CoverArtDatabase.py		\
 	Loader.py			\
 	__init__.py
--- ./CoverArtDatabase.py	2006/07/02 00:56:53	1.1
+++ ./CoverArtDatabase.py	2006/07/05 10:08:39
@@ -21,17 +21,55 @@ import os
 import gtk
 
 from AmazonCoverArtSearch import AmazonCoverArtSearch
+from LocalCoverArtSearch import LocalCoverArtSearch
 from Loader import Loader
 
+ART_SEARCHES_LOCAL = [LocalCoverArtSearch]
+ART_SEARCHES_REMOTE = [AmazonCoverArtSearch]
 ART_FOLDER = '~/.gnome2/rhythmbox/covers'
 
+class ImageSearcher:
+	def __init__ (self, loader, db, entry, callback):
+		self.loader = loader
+		self.db = db
+		self.entry = entry
+		self.callback = callback
+		
+	def iterate_engines (self, engines):
+		if engines:
+			engine = engines[0] (self.loader)
+			engine.search (self.db, self.entry, self._on_results, engines[1:])
+		else:
+			self.callback (None)
+
+	def _on_results (self, engine, entry, results, engines):
+		if results:
+			urls = list (engine.get_best_match_urls (results))
+			self._iterate_urls (engine, urls, engines)
+		else:
+			self.iterate_engines (engines)
+	
+	def _iterate_urls (self, engine, urls, engines):
+		if urls:
+			self.loader.get_url (str (urls[0]), self._on_image_data_received, engine, urls[1:], engines)
+		elif not engine.search_next ():
+			self.iterate_engines (engines)
+
+	def _on_image_data_received (self, image_data, engine, urls, engines):
+		if image_data and len (image_data) >= 1000:
+			pbl = gtk.gdk.PixbufLoader ()
+			try:
+				if pbl.write (image_data) and pbl.close ():
+					self.callback (pbl.get_pixbuf ())
+					return
+			except GError:
+				pass
+		self._iterate_urls (engine, urls, engines)
+
 class CoverArtDatabase (object):
 	def __init__ (self):
 		self.loader = Loader()
 
-	def create_search (self):
-		return AmazonCoverArtSearch (self.loader)
-
 	def build_art_cache_filename (self, album, artist, extension):
 		art_folder = os.path.expanduser (ART_FOLDER)
 		if not os.path.exists (art_folder):
@@ -74,67 +112,16 @@ class CoverArtDatabase (object):
 		if os.path.exists (art_location):
 			pixbuf = gtk.gdk.pixbuf_new_from_file (art_location)	
 			callback (entry, pixbuf)
-		# Check for unsuccessful previous image download to prevent overhead search
-		elif os.path.exists (blist_location):
-			callback (entry, None)
-		else:
-			# Otherwise spawn (online) search-engine search
-			se = self.create_search ()
-			se.search (db, entry, self.on_search_engine_results, callback)
-
-	def on_search_engine_results (self, search_engine, entry, results, callback):
-		if results is None:
-			self._do_blacklist_and_callback (search_engine, callback)
-			return
-
-		# Get best match from results
-		best_match = search_engine.get_best_match (results)
-
-		if best_match is None:
-			self._do_blacklist_and_callback (search_engine, callback)
-			return
-
-		# Attempt to download image for best match
-		pic_url = str (best_match.ImageUrlLarge)
-		self.loader.get_url (pic_url, self.on_image_data_received, search_engine, "large", callback, best_match)
-
-	def _do_blacklist_and_callback (self, search_engine, callback):
-		self._create_blacklist (search_engine.st_artist, search_engine.st_album)
-		callback (search_engine.entry, None)
-
-	def _create_blacklist (self, artist, album):
-		location = self.build_art_cache_filename (album, artist, "rb-blist")
-		f = file (location, 'w')
-		f.close ()
-		return location
-
-	def _create_artwork (self, artist, album, image_data):
-		location = self.build_art_cache_filename (album, artist, "jpg")
-		f = file (location, 'wb')
-		f.write (image_data)
-		f.close ()
-		return location
-
-	def on_image_data_received (self, image_data, search_engine, image_version, callback, best_match):
-		if image_data is None:
-			res = search_engine.search_next ()
-			if not res:
-				self._do_blacklist_and_callback (search_engine, callback)
-			return
-
-		if len (image_data) < 1000:
-			if image_version == "large" and best_match is not None:
-				# Fallback and try to load medium one
-				pic_url = str (best_match.ImageUrlMedium)
-				self.loader.get_url (pic_url, self.on_image_data_received, search_engine, "medium", callback, best_match)
-				return
-
-			res = search_engine.search_next ()
-			if not res:
-				# only write the blist if there are no more queries to try
-				self._do_blacklist_and_callback (search_engine, callback)
-
 		else:
-			location = self._create_artwork (search_engine.st_artist, search_engine.st_album, image_data)
-			pixbuf = gtk.gdk.pixbuf_new_from_file (location)
-			callback (search_engine.entry, pixbuf)
+			def on_pixbuf(pixbuf):
+				if pixbuf:
+					pixbuf.save (art_location, "jpeg", {"quality": "100"})
+				else:
+					file (blist_location, 'w').close ()
+				callback (entry, pixbuf)
+
+			searcher = ImageSearcher (self.loader, db, entry, on_pixbuf)
+			if os.path.exists (blist_location):
+				searcher.iterate_engines (ART_SEARCHES_LOCAL)
+			else:
+				searcher.iterate_engines (ART_SEARCHES_LOCAL + ART_SEARCHES_REMOTE)
--- ./AmazonCoverArtSearch.py	2006/07/05 05:29:27	1.1
+++ ./AmazonCoverArtSearch.py	2006/07/05 10:07:46
@@ -219,7 +219,7 @@ class AmazonCoverArtSearch (object):
 
 		return s
 
-	def get_best_match (self, search_results):
+	def get_best_match_urls (self, search_results):
 		# Default to "no match", our results must match our criteria
 		best_match = None
 
@@ -259,7 +259,10 @@ class AmazonCoverArtSearch (object):
 						if hit:
 							break
 
-			return best_match
+			if best_match: 
+				return [best_match.ImageUrlLarge, best_match.ImageUrlMedium]
+			else:
+				return []
 
 		except TypeError:
-			return None
+			return []
--- ./LocalCoverArtSearch.py	2006/07/05 07:47:18	1.1
+++ ./LocalCoverArtSearch.py	2006/07/05 09:58:21
@@ -0,0 +1,58 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 
+#
+# Copyright (C) 2006 - Ed Catmur <ed@catmur.co.uk>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+import os
+import rhythmdb
+import gnomevfs
+
+IMAGE_NAMES = ["cover", "album", "albumart", ".folder"]
+LOAD_DIRECTORY_FLAGS = gnomevfs.FILE_INFO_GET_MIME_TYPE | gnomevfs.FILE_INFO_FORCE_FAST_MIME_TYPE
+
+def file_root (f_name):
+	return os.path.splitext (f_name)[0].lower ()
+
+class LocalCoverArtSearch:
+	def __init__ (self, loader):
+		self.loader = loader
+
+	def _load_dir_cb (self, handle, files, exc, (parent, contents, cb)):
+		for f in files:
+			if f.mime_type.startswith ("image/") and f.permissions & gnomevfs.PERM_USER_READ:
+				contents.append ((file_root (f.name), 
+					parent.append_file_name (f.name).path))
+		if exc:
+			cb (contents)
+			if not issubclass (exc, gnomevfs.EOFError):
+				print "Error reading \"%s\": %s" % (parent, exc)
+
+	def search (self, db, entry, on_search_completed_cb, *args):
+		uri = gnomevfs.URI (db.entry_get (entry,rhythmdb.PROP_LOCATION))
+		self.track_root = file_root (uri.short_name)
+		def on_finished (results):
+			on_search_completed_cb (self, entry, results, *args)
+		gnomevfs.async.load_directory (uri.parent, self._load_dir_cb, 
+				LOAD_DIRECTORY_FLAGS, 
+				data=(uri.parent, [], on_finished))
+
+	def search_next (self):
+		return False
+
+	def get_best_match_urls (self, results):
+		for name in [self.track_root] + IMAGE_NAMES:
+			for f_root, f_path in results:
+				if f_root == name: yield f_path
