Package elisa :: Package plugins :: Package good :: Package fspot :: Module fspot_media
[hide private]
[frames] | no frames]

Source Code for Module elisa.plugins.good.fspot.fspot_media

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17  # Based on work by Jan Jokela <janjokela@gmail.com> 
 18   
 19  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 20   
 21  from elisa.base_components.media_provider import MediaProvider 
 22  from elisa.core import media_uri, component, common, db_backend, log 
 23  from elisa.core.utils import misc 
 24  from elisa.core.bus import bus_message 
 25  from elisa.core.observers.dict import DictObservable 
 26   
 27  from twisted.internet import threads 
 28   
 29  import os, sys, threading 
 30   
 31  DEFAULT_DB_PATH = os.path.join(os.path.expanduser('~'), 
 32                                 '.gnome2', 'f-spot', 'photos.db') 
 33   
34 -class FspotDB(log.Loggable):
35 """ Retrieves tags and videos from the F-spot sqlite database 36 """ 37 log_category = 'fspot_db' 38
39 - def __init__(self, db_path=DEFAULT_DB_PATH):
40 """ Attempt a connection to the database 41 """ 42 log.Loggable.__init__(self) 43 44 if not os.path.exists(db_path): 45 raise db_backend.DBBackendError("FSpot DB not found at %r" % db_path) 46 47 options = {'db_backend': 'sqlite', 'database': db_path} 48 self._backend = db_backend.DBBackend(**options) 49 self._version = None
50
51 - def get_version(self):
52 if self._version is None: 53 rows = self._backend.sql_execute("select data from meta where name=?", 54 "F-Spot Database Version") 55 if rows: 56 self._version = int(rows[0].data) 57 else: 58 self._version = 0 59 60 return self._version
61
62 - def get_tag_with_id(self, tag_id):
63 tag = None 64 query = "SELECT id, name, category_id as parent_id from tags where id=?" 65 rows = self._backend.sql_execute(query, tag_id) 66 if len(rows): 67 tag = rows[0] 68 return tag
69
70 - def get_tags(self, tag_id=None):
71 """ Return a list of tags [(tag id, tag name), (..)] 72 @rtype: list 73 """ 74 if tag_id == None: 75 query = "SELECT id, name FROM tags WHERE category_id=0 ORDER BY name" 76 params = () 77 else: 78 query = "SELECT id, name FROM tags WHERE category_id=? ORDER BY name" 79 params = (tag_id,) 80 rows = self._backend.sql_execute(query, *params) 81 return rows
82
83 - def get_photos(self, tag):
84 """ Return a list of photos with the given tag 85 [(diectory_path, filename), (..)] 86 87 @param tag: the tag id 88 @type tag: int 89 @rtype: list 90 """ 91 if self.get_version() < 8: 92 query = "SELECT id, directory_path, name FROM photos, photo_tags WHERE "\ 93 "photos.id=photo_tags.photo_id AND photo_tags.tag_id=? "\ 94 "ORDER BY photos.time" 95 else: 96 query = "SELECT id, uri FROM photos, photo_tags WHERE "\ 97 "photos.id=photo_tags.photo_id AND photo_tags.tag_id=? "\ 98 "ORDER BY photos.time" 99 100 rows = self._backend.sql_execute(query, tag) 101 return rows
102 103
104 -class FspotMedia(MediaProvider):
105 """ The Fspot MediaProvider component 106 """ 107 108 default_config = {'db_path': DEFAULT_DB_PATH} 109 config_doc = {'db_path': 'absolute path to f-spot photos.db file'} 110
111 - def initialize(self):
112 self._db_lock = threading.Lock() 113 self._db_connections = {} 114 self._cache = {} 115 db_path = self.config.get('db_path', DEFAULT_DB_PATH) 116 117 if os.path.dirname(db_path) in ('','..'): 118 app_config = common.application.config 119 conf_path = app_config.get_config_dir() 120 self.debug("Using %r to search F-Spot DB", conf_path) 121 db_path = os.path.join(conf_path, db_path) 122 123 self._db_path = db_path 124 125 # Some rudimentary database checks 126 try: 127 db = FspotDB(self._db_path) 128 # if not, we can't initialize our component 129 except db_backend.DBBackendError, error: 130 raise component.InitializeFailure(self.name, str(error)) 131 else: 132 version = db.get_version() 133 if version == 0: 134 msg = "%r is not F-Spot DB" % self._db_path 135 raise component.InitializeFailure(self.name, msg) 136 137 self.debug("Found a F-Spot DB with version %s at %r", version, 138 self._db_path) 139 140 # Lets make sure the user doesn't need to manually add Fspot 141 # to the image activity locations 142 uri = "fspot:///" 143 action_type = bus_message.MediaLocation.ActionType.LOCATION_ADDED 144 msg = bus_message.ForeignApplication(action_type, "F-Spot", 'fspot', 145 uri, media_types=['image',], 146 theme_icon='fspot' 147 ) 148 common.application.bus.send_message(msg)
149
150 - def db__get(self):
151 db = None 152 153 try: 154 self._db_lock.acquire() 155 current_thread = threading.currentThread() 156 thread_id = id(current_thread) 157 db = self._db_connections.get(thread_id) 158 if not db: 159 db = FspotDB(self._db_path) 160 self._db_connections[thread_id] = db 161 finally: 162 self._db_lock.release() 163 164 return db
165
167 return []
168
170 return { 'fspot': 0 }
171
172 - def get_real_uri(self, uri):
173 # our photos are files, so let's convert our uri's 174 # to 'file' scheme 175 if uri.host == '.': 176 dirname = os.path.dirname(self._db_path) 177 else: 178 dirname = uri.host 179 real_uri = media_uri.MediaUri("file://%s%s" % (dirname,uri.path)) 180 self.debug("%r is real URI of %r", real_uri, uri) 181 return real_uri
182
183 - def _blocking_get_media_type(self, uri):
184 """ 185 @param uri: the URI to analyze 186 @type uri: L{elisa.core.media_uri.MediaUri} 187 @rtype: dict 188 """ 189 file_type = '' 190 mime_type = '' 191 if not self._blocking_is_directory(uri): 192 # ok, it's a file, let's ask for a mime and file type 193 mime_type, file_type = misc.get_media_infos_from_mime(uri) 194 if None in (mime_type, file_type): 195 self.warning("Unknown media: %r", uri) 196 else: 197 # directories don't have mime_types 198 file_type = 'directory' 199 return {'file_type': file_type, 'mime_type': mime_type}
200
201 - def _blocking_is_directory(self, uri):
202 """ 203 return True if a directory 204 205 @param uri: the URI to analyze 206 @type uri: L{elisa.core.media_uri.MediaUri} 207 @rtype: bool 208 """ 209 if uri.path == '/': 210 # yup, the sheme root dir is a .. dir 211 return True 212 elif uri.get_param('id'): 213 # this means it's a tag, we can assume tags are dirs 214 return True 215 else: return False
216
217 - def has_children_with_types(self, uri, media_types):
218 return threads.deferToThread(self._blocking_has_children_with_types, 219 uri, media_types)
220
221 - def _blocking_has_children_with_types(self, uri, media_types):
222 """ 223 @param uri: the URI to scan 224 @type uri: L{elisa.core.media_uri.MediaUri} 225 @param media_types: the media_types to look for on the directory 226 @type media_types: list of strings 227 @rtype: bool 228 """ 229 has_children = False 230 if 'image' in media_types or 'directory' in media_types: 231 db = self.db 232 if self._blocking_is_directory(uri): 233 if 'directory' in media_types: 234 category_id = int(uri.get_param('id','-1')) 235 if category_id != -1: 236 has_children = len(db.get_tags(category_id)) > 0 237 if not has_children and 'image' in media_types: 238 has_children = len(db.get_photos(category_id)) > 0 239 else: 240 has_children = True 241 elif uri.path == '/': 242 tags = db.get_tags() 243 if tags != []: 244 has_children = True 245 elif len(uri.path): 246 photos = db.get_photos(int(uri.get_param('id'))) 247 if photos != []: 248 has_children = True 249 return has_children
250
251 - def get_direct_children(self, uri, l):
252 d = threads.deferToThread(self._blocking_get_direct_children, uri, l) 253 return d
254
255 - def _blocking_get_direct_children(self, uri, list_of_children):
256 db = self.db 257 # if the uri path is /, we have to retrieve the tags from the fspot 258 # database 259 if uri.path == '/': 260 # add to the children list a MediaUri representing each tag 261 tags = db.get_tags() 262 for tag in tags: 263 t = media_uri.MediaUri("fspot:///%s" % tag.name) 264 t.set_param('id', int(tag.id)) 265 t.set_param('tag_id', -1) 266 list_of_children.append((t, {})) 267 268 elif len(uri.path): 269 # it's a tag, lets get our child tags and child photos 270 271 # strip leading / 272 path = uri.path[1:] 273 274 tag_id = int(uri.get_param('id')) 275 276 # add child tags 277 tags = db.get_tags(tag_id) 278 for tag in tags: 279 t = media_uri.MediaUri("fspot:///%s" % tag.name) 280 t.set_param('id', int(tag.id)) 281 t.set_param('tag_id', tag_id) 282 list_of_children.append((t, {})) 283 284 # add to the children list a MediaUri representing each video 285 photos = db.get_photos(tag_id) 286 for photo in photos: 287 if photo.has_key('uri'): 288 p = media_uri.MediaUri(photo.uri) 289 p.scheme = 'fspot' 290 else: 291 photo_path = os.path.join(photo.directory_path, photo.name) 292 p = media_uri.MediaUri("fspot://%s" % photo_path) 293 # we want the photo filename 294 label = photo.name.decode("utf-8") 295 p.label = label 296 p.fragment = photo.id 297 p.set_param('tag_id', tag_id) 298 d = DictObservable({'default_image': self.get_real_uri(p)}) 299 list_of_children.append((p, d)) 300 301 return list_of_children
302
303 - def _blocking_next_location(self, from_uri, root=None):
304 next_uri = None 305 306 def from_cache(uri, node_id): 307 parent = None 308 for dir_uri in self._cache.keys(): 309 if dir_uri.get_param('id', None) == node_id: 310 parent = dir_uri 311 break 312 if not parent: 313 # cache wasn't filled, let's fill it a bit more 314 tag = self.db.get_tag_with_id(node_id) 315 u = 'fspot:///%s?id=%s&tag_id=%s' % (tag.name, node_id, 316 tag.parent_id) 317 parent = media_uri.MediaUri(u) 318 children = self._blocking_get_direct_children(parent, []) 319 self._cache[parent] = [ c[0] for c in children ] 320 321 return parent
322 323 def get_parent(uri): 324 parent = None 325 parent_id = uri.get_param('tag_id', None) 326 if parent_id is not None: 327 if int(parent_id) == -1: 328 # parent is root 329 parent = media_uri.MediaUri('fspot:///') 330 else: 331 parent = from_cache(uri, parent_id) 332 self.debug("Parent of %r : %r", uri, parent) 333 return parent
334 335 def find_from_parent(uri, root): 336 next_uri = None 337 parent = get_parent(uri) 338 if parent == root: 339 parent = uri 340 items = self._cache.get(root, []) 341 else: 342 parent_parent = get_parent(parent) 343 if not parent_parent: 344 parent_parent = root 345 self.debug("Parent of %r : %r", parent, parent_parent) 346 items = [] 347 for item, children in self._cache.iteritems(): 348 if str(item) == str(parent_parent): 349 items = children 350 break 351 352 if parent in items: 353 index = items.index(parent) 354 if index < len(items) -1: 355 next_uri = items[index+1] 356 return next_uri 357 358 if self._blocking_is_directory(from_uri): 359 children = self._blocking_get_direct_children(from_uri, []) 360 cached_items = [ child[0] for child in children ] 361 self._cache[from_uri] = cached_items 362 if cached_items: 363 next_uri = cached_items[0] 364 else: 365 next_uri = find_from_parent(from_uri, root) 366 else: 367 for dir_uri, items in self._cache.copy().iteritems(): 368 if from_uri in items: 369 index = items.index(from_uri) 370 if index < len(items)-1: 371 next_uri = items[index+1] 372 else: 373 next_uri = find_from_parent(from_uri, root) 374 375 if next_uri and self._blocking_is_directory(next_uri): 376 next_uri = self._blocking_next_location(next_uri, root=root) 377 378 self.info("Next of %r : %r", from_uri,next_uri) 379 return next_uri 380
381 - def _blocking_open(self, uri, mode='r'):
382 # we can't open directories 383 if self._blocking_is_directory(uri): 384 return None 385 386 # lets convert the uri from our scheme to a file scheme 387 # and ask the media_manager to provide us a capable 388 # component 389 uri = self.get_real_uri(uri) 390 media_manager = common.application.media_manager 391 if media_manager.enabled: 392 media = media_manager.open(uri, mode, block) 393 else: 394 media = None 395 return media
396