Package elisa :: Package base_components :: Module media_provider
[hide private]
[frames] | no frames]

Source Code for Module elisa.base_components.media_provider

  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   
 18  """ 
 19  Media data access support 
 20  """ 
 21   
 22   
 23  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 24   
 25   
 26  from elisa.core.component import Component 
 27  from elisa.extern import enum 
 28  from twisted.internet import defer 
 29   
30 -class UriNotMonitorable(Exception):
31
32 - def __init__(self, uri):
33 self.uri = uri
34
35 - def __str__(self):
36 return str(self.uri)
37 38 # ADDED: guess what, a new directory or file was added 39 # REMOVED: guess what, a directory or file was removed 40 # MODIFIED: guess what, a directory or file was updated 41 NotifyEvent = enum.Enum('ADDED', 'REMOVED', 'MODIFIED') 42 43 media_types = ('directory', 'audio', 'video', 'image') 44
45 -class MediaProvider(Component):
46 """ 47 Medias are all accessible using URIs. MediaProvider components are 48 responsible to support one or many URI schemes. 49 50 MediaProviders are able to parse media trees of supported URI(s) 51 scheme(s), they can also provide limited information about parsed 52 files and directories. 53 54 MediaProviders can optionnally monitor media locations and notify 55 registered components of data updates (new/updated/deleted 56 files/directories). 57 58 TODO: 59 60 - blocking_{copy,move,delete} ? 61 62 @cvar scannable_uri_schemes: DOCME 63 @type scannable_uri_schemes: list 64 @cvar supported_uri_schemes: DOCME 65 @type supported_uri_schemes: dict mapping string to int 66 67 """ 68
70 """ 71 Retrieve the URI schemes that can be scanned by the 72 media_scanner. Since media scanning can be an heavy and long 73 task the MediaProvider developer can choose to make the 74 media_scanner skip URIs with scheme not appearing in returned 75 list. 76 77 By default the return value of this method corresponds to the 78 keys of L{supported_uri_schemes__get} return value. 79 80 # FIXME: this should be documented in the class docstring as a class 81 # variable 82 """ 83 return self.supported_uri_schemes.keys()
84
86 """ 87 Retrieve the URI schemes supported by the provider, for each 88 scheme there's a priority. Higher priority == 0 means the 89 provider will always be used to read data from a given scheme. 90 91 This function is called by the MediaManager to know which 92 media provider it has to use to access a specified URI. You 93 should return a dict containing the uri scheme (such as 94 'file', 'cdda', ...) and its priority between 0 to 255 (0 95 being the topmost priority). The prority is used by the 96 MediaManager to know which media provider it should use in 97 case there are more than one who support the desired uri 98 scheme. You might have for example a component which supports 99 more than one scheme, but the support of one of them is not 100 very efficient compared to what it could be. In this case you 101 could modify its priority to tell the MediaManager that 102 another component should be used instead of it to access this 103 scheme. 104 105 example: { 'file': 0, 'smb': 10 } 106 107 # FIXME: this should be documented in the class docstring as a class 108 # variable 109 """ 110 return {}
111
112 - def get_real_uri(self, uri):
113 """ 114 Returns the original uri (acesable) from a virtual 115 uri representation. 116 117 @param uri: the URI to validate 118 @type uri: L{elisa.core.media_uri.MediaUri} 119 @rtype: L{elisa.core.media_uri.MediaUri} 120 """ 121 return uri
122 123
124 - def get_media_type(self, uri):
125 """ 126 Same as L{blocking_get_media_type} but without blocking (in 127 theory). This method by default triggers a succeeded callback 128 on a Twisted deferred, using L{blocking_get_media_type} result. 129 130 @param uri: the URI to analyze 131 @type uri: L{elisa.core.media_uri.MediaUri} 132 @rtype: L{twisted.internet.defer.Deferred} 133 """ 134 return defer.succeed(self._blocking_get_media_type(uri))
135
136 - def _blocking_get_media_type(self, uri):
137 """ 138 Try to guess the maximum information from the media located 139 at given uri by looking at eventual file extension. Will 140 return something like:: 141 142 {'file_type': string (values: one of media_provider.media_types) 143 'mime_type': string (example: 'audio/mpeg' for .mp3 uris. can be 144 empty string if unguessable) 145 } 146 147 'file_type' and 'mime_type' can be empty strings if it failed 148 recognizing them. 149 150 @param uri: the URI to analyze 151 @type uri: L{elisa.core.media_uri.MediaUri} 152 @rtype: dict 153 """ 154 raise NotImplementedError
155
156 - def is_directory(self, uri):
157 """ 158 Same as L{_blocking_is_directory} but without blocking (in 159 theory). This method by default triggers a succeeded callback 160 on a Twisted deferred, using L{_blocking_is_directory} result. 161 162 @param uri: the URI to analyze 163 @type uri: L{elisa.core.media_uri.MediaUri} 164 @rtype: L{twisted.internet.defer.Deferred} 165 """ 166 return defer.succeed(self._blocking_is_directory(uri))
167
168 - def _blocking_is_directory(self, uri):
169 """ 170 return True if a directory 171 172 @param uri: the URI to analyze 173 @type uri: L{elisa.core.media_uri.MediaUri} 174 @rtype: bool 175 """ 176 raise NotImplementedError
177
178 - def has_children_with_types(self, uri, media_types):
179 """ 180 Same as L{_blocking_has_children_with_types} but without blocking (in 181 theory). This method by default triggers a succeeded callback 182 on a Twisted deferred, using L{_blocking_has_children_with_types} result. 183 184 @param uri: the URI to scan 185 @type uri: L{elisa.core.media_uri.MediaUri} 186 @param media_types: the media_types to look for on the directory 187 @type media_types: list of strings 188 @rtype: L{twisted.internet.defer.Deferred} 189 """ 190 return defer.succeed(self._blocking_has_children_with_types(uri, 191 media_types))
192
193 - def _blocking_has_children_with_types(self, uri, media_types):
194 """ 195 Detect whether the given uri has children for given media 196 types which can be one of media_provider.media_types. 197 Implies the URI is a directory as well. 198 199 @param uri: the URI to scan 200 @type uri: L{elisa.core.media_uri.MediaUri} 201 @param media_types: the media_types to look for on the directory 202 @type media_types: list of strings 203 @rtype: bool 204 """ 205 raise NotImplementedError
206
207 - def get_direct_children(self, uri, children_with_info):
208 """ 209 Same as L{_blocking_get_direct_children} but without blocking (in 210 theory). This method by default triggers a succeeded callback 211 on a Twisted deferred, using L{_blocking_get_direct_children} result. 212 213 @param uri: the URI to analyze 214 @type uri: L{elisa.core.media_uri.MediaUri} 215 @param children_with_info: List where the children will be appended 216 @type children_with_info: list 217 @rtype: L{twisted.internet.defer.Deferred} 218 """ 219 return defer.succeed(self._blocking_get_direct_children(uri, 220 children_with_info))
221
222 - def _blocking_get_direct_children(self, uri, children_with_info):
223 """ 224 Scan the data located at given uri and return informations 225 about its children. Fills children_with_info. 226 227 Typemap of filled result:: 228 229 [ 230 (uri : media_uri.MediaUri, 231 additional info: dict), 232 ... 233 ] 234 235 If you supply additional info, they should be stored in a 236 L{elisa.core.observers.dict.DictObservable} instead of a 237 normal dictionnary. Valid keys:: 238 239 ['default_image', 'artist', 'album', 'song', 'song_artist', 240 'song_album'] 241 242 243 @param uri: the URI to analyze 244 @type uri: L{elisa.core.media_uri.MediaUri} 245 @param children_with_info: List where the children will be appended 246 @type children_with_info: list 247 @rtype: list 248 """ 249 return children_with_info
250
251 - def open(self, uri, mode=None):
252 """ 253 Same as L{_blocking_open} but without blocking (in 254 theory). This method by default triggers a succeeded callback 255 on a Twisted deferred, using L{_blocking_open} result. 256 257 @note: It's not allowed to open directories, it's up to the 258 developer to check that the URI to open doesn't represent a 259 directory. 260 261 @param uri: the URI to open 262 @type uri: L{elisa.core.media_uri.MediaUri} 263 @keyword mode: how to open the file -- see manual of builtin open() 264 @type mode: string or None 265 @rtype: L{twisted.internet.defer.Deferred} 266 """ 267 return defer.succeed(self._blocking_open(uri, mode=mode))
268
269 - def _blocking_open(self, uri, mode=None):
270 """ 271 Open an uri and return MediaFile. 272 273 @note: It's not allowed to open directories, it's up to the 274 developer to check that the URI to open doesn't represent a 275 directory. 276 277 278 @param uri: the URI to open 279 @type uri: L{elisa.core.media_uri.MediaUri} 280 @keyword mode: how to open the file -- see manual of builtin open() 281 @type mode: string or None 282 @rtype: L{elisa.core.media_file.MediaFile} 283 """ 284 raise NotImplementedError
285
286 - def close(self, media_file):
287 """ 288 Same as L{_blocking_close} but without blocking (in 289 theory). This method by default triggers a succeeded callback 290 on a Twisted deferred, using L{_blocking_close} result. 291 292 @param media_file: the file to close 293 @type media_file: L{elisa.core.media_file.MediaFile} 294 @rtype: L{twisted.internet.defer.Deferred} 295 """ 296 return defer.succeed(self._blocking_close(media_file))
297
298 - def _blocking_close(self, media_file):
299 """ 300 Close a MediaFile 301 302 @param media_file: the file to close 303 @type media_file: L{elisa.core.media_file.MediaFile} 304 """ 305 if media_file: 306 media_file.descriptor.close()
307
308 - def seek(self, media_file, offset, whence=0):
309 """ 310 Same as L{_blocking_seek} but without blocking (in 311 theory). This method by default triggers a succeeded callback 312 on a Twisted deferred, using L{_blocking_seek} result. 313 314 @param media_file: the file to seek in 315 @type media_file: L{elisa.core.media_file.MediaFile} 316 @param offset: how many bytes to seek 317 @type offset: int 318 @keyword whence: from where to seek 319 @type whence: int (default=0) 320 @rtype: L{twisted.internet.defer.Deferred} 321 """ 322 return defer.succeed(self._blocking_seek(media_file, offset, 323 whence=whence))
324
325 - def _blocking_seek(self, media_file, offset, whence=0):
326 """ 327 Seek data in a MediaFile 328 329 @param media_file: the file to seek in 330 @type media_file: L{elisa.core.media_file.MediaFile} 331 @param offset: how many bytes to seek 332 @type offset: int 333 @keyword whence: from where to seek 334 @type whence: int (default=0) 335 """ 336 if media_file: 337 media_file.descriptor.seek(offset, whence)
338
339 - def read(self, media_file, size=-1):
340 """ 341 Same as L{_blocking_seek} but without blocking (in 342 theory). This method by default triggers a succeeded callback 343 on a Twisted deferred, using L{_blocking_seek} result. 344 345 @param media_file: the file to read data from 346 @type media_file: L{elisa.core.media_file.MediaFile} 347 @keyword size: how many data we should try to read 348 @type size: int (default: -1 == all) 349 @rtype: L{twisted.internet.defer.Deferred} 350 """ 351 return defer.succeed(self._blocking_read(media_file, size=size))
352
353 - def _blocking_read(self, media_file, size=-1):
354 """ 355 Read data from a MediaFile 356 357 @param media_file: the file to read data from 358 @type media_file: L{elisa.core.media_file.MediaFile} 359 @keyword size: how many data we should try to read 360 @type size: int (default: -1 == all) 361 @rtype: string 362 """ 363 data = '' 364 if media_file: 365 if size == -1: 366 data = media_file.descriptor.read() 367 else: 368 data = media_file.descriptor.read(size) 369 return data
370
371 - def next_location(self, uri, root=None):
372 """ 373 Same as L{_blocking_next_location} but without blocking (in 374 theory). This method by default triggers a succeeded callback 375 on a Twisted deferred, using L{_blocking_next_location} result. 376 377 @param uri: the URI representing the file or directory from 378 where to move on 379 @type uri: L{elisa.core.media_uri.MediaUri} 380 @param root: root URI 381 @type root: L{elisa.core.media_uri.MediaUri} 382 @rtype: L{twisted.internet.defer.Deferred} 383 """ 384 return defer.succeed(self._blocking_next_location(uri, root=root))
385
386 - def _blocking_next_location(self, uri, root=None):
387 """ 388 Return the uri just next to given uri. 389 390 @param uri: the URI representing the file or directory from 391 where to move on 392 @type uri: L{elisa.core.media_uri.MediaUri} 393 @param root: root URI 394 @type root: L{elisa.core.media_uri.MediaUri} 395 @rtype: L{elisa.core.media_uri.MediaUri} 396 """ 397 raise NotImplementedError
398
399 - def previous_location(self, uri):
400 """ 401 Same as L{_blocking_previous_location} but without blocking (in 402 theory). This method by default triggers a succeeded callback 403 on a Twisted deferred, using L{_blocking_previous_location} result. 404 405 @param uri: the URI representing the file or directory prior 406 to uri 407 @type uri: L{elisa.core.media_uri.MediaUri} 408 @rtype: L{twisted.internet.defer.Deferred} 409 """ 410 return defer.succeed(self._blocking_next_location(uri, root=root))
411
412 - def _blocking_previous_location(self, uri):
413 """ 414 Return the uri found before given uri 415 416 @param uri: the URI representing the file or directory prior to uri 417 @type uri: L{elisa.core.media_uri.MediaUri} 418 @rtype: L{elisa.core.media_uri.MediaUri} 419 """ 420 raise NotImplementedError
421
422 - def monitor_uri(self, uri, callback):
423 """ 424 Start monitoring given uri for modification and call a 425 function in case of any change happening on `uri` Raises 426 UriNotMonitorable(uri) if uri can't be monitored 427 428 @param uri: URI representing the file or directory to monitor 429 @type uri: L{elisa.core.media_uri.MediaUri} 430 @param callback: a callable taking the event that occured and the uri 431 of the file on which the event applies to 432 prototype: callable(uri, metadata, event) 433 type uri: L{elisa.core.media_uri.MediaUri} 434 type metadata: dict 435 type event: L{elisa.base_components.media_provider.NotifyEvent} 436 437 @raise UriNotMonitorable : if the uri cannot be monitored 438 """ 439 raise NotImplementedError
440
441 - def unmonitor_uri(self, uri):
442 """ 443 Stop monitoring given uri. 444 445 @param uri: the URI representing the file or directory to monitor 446 @type uri: L{elisa.core.media_uri.MediaUri} 447 """ 448 raise NotImplementedError
449
450 - def uri_is_monitorable(self, uri):
451 """ 452 Check if the uri is monitorable for modification 453 454 @param uri: the URI representing the file or directory for 455 which we would like to know if it is monitorable or not 456 @type uri: L{elisa.core.media_uri.MediaUri} 457 @rtype: bool 458 """ 459 return False
460
461 - def uri_is_monitored(self, uri):
462 """ 463 Check if the uri is currently monitored for modification 464 465 @param uri: the URI representing the file or directory for 466 which we would like to know if it is currently 467 monitored or not 468 @type uri: L{elisa.core.media_uri.MediaUri} 469 @rtype: bool 470 """ 471 raise NotImplementedError
472
473 - def copy(self, orig_uri, dest_uri, recursive=False):
474 """ 475 Copy one location to another. If both URIs represent a 476 directory and recursive flag is set to True I will recursively 477 copy the directory to the destination URI. 478 479 @param orig_uri: the URI to copy, can represent either a directory or 480 a file 481 @type orig_uri: L{elisa.core.media_uri.MediaUri} 482 @param dest_uri: the destination URI, can represent either a directory 483 or a file 484 @type dest_uri: L{elisa.core.media_uri.MediaUri} 485 @param recursive: if orig_uri represents a directory, should I copy it 486 recursively to dest_uri? 487 @type recursive: bool 488 @rtype: L{twisted.internet.defer.Deferred} 489 """ 490 raise NotImplementedError
491
492 - def move(self, orig_uri, dest_uri):
493 """ 494 Move data located at given URI to another URI. If orig_uri 495 represents a directory it will recusively be moved to 496 dest_uri. In the case where orig_uri is a directory, dest_uri 497 can't be a file. 498 499 @param orig_uri: the URI to move, can represent either a directory or 500 a file 501 @type orig_uri: L{elisa.core.media_uri.MediaUri} 502 @param dest_uri: the destination URI, can represent either a directory 503 or a file 504 @type dest_uri: L{elisa.core.media_uri.MediaUri} 505 @rtype: L{twisted.internet.defer.Deferred} 506 """ 507 raise NotImplementedError
508
509 - def delete(self, uri, recursive=False):
510 """ 511 Delete a resource located at given URI. If that URI represents 512 a directory and recursive flag is set to True I will 513 recursively remove the directory. 514 515 @param uri: the URI representing the file or directory for 516 which we would like to know if it is currently 517 monitored or not 518 @type uri: L{elisa.core.media_uri.MediaUri} 519 @param recursive: if orig_uri represents a directory, should I copy it 520 recursively to dest_uri? 521 @type recursive: bool 522 @rtype: L{twisted.internet.defer.Deferred} 523 """ 524 raise NotImplementedError
525