Package elisa :: Package core :: Module media_uri
[hide private]
[frames] | no frames]

Source Code for Module elisa.core.media_uri

  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  URI parsing support 
 19  """ 
 20   
 21  __maintainer__ = 'Philippe Normand <philippe@fluendo.com>' 
 22   
 23   
 24  import os, re, urllib, platform, copy 
 25  from elisa.core.utils import classinit 
 26   
 27  # for i18n support 
 28  from elisa.extern.translation import Translatable 
 29   
 30  # Compiled regex used to parse URI. 
 31  URI_RE = None 
 32   
33 -class ParseException(Exception):
34
35 - def __init__(self, msg):
36 self.msg = msg
37
38 - def __str__(self):
39 return self.msg
40
41 -def quote(data, not_quote=''):
42 """ 43 Make strings, lists and dictionaries containing strings quoted using 44 urllib.quote. Return value has the same type as the parameter given 45 to this function. If the data is not a string, list or a dictionary, it is 46 simply returned. 47 48 Warning: If you want to quote a path containing '/' as seperators, you 49 have to add the slash to not_quote, or it will be quoted also!!! 50 51 @param data: unquoted data which can contain unsafe characters like &= 52 @type data: string, list or dict 53 @param not_quote: characters, which shouldn't be quoted. 54 @type not_quote: string 55 @rtype: string, list or dict 56 """ 57 if isinstance(data, basestring): 58 if isinstance(data, unicode): 59 # urllib.quote doesn't support unicode objects very well 60 data = data.encode('utf-8') 61 quoted = urllib.quote(data, not_quote) 62 elif isinstance(data, dict): 63 quoted = {} 64 for k, v in data.iteritems(): 65 if isinstance(v, unicode): 66 # urllib.quote doesn't support unicode objects very well 67 v = v.encode('utf-8') 68 quoted[k] = urllib.quote(v, not_quote) 69 elif isinstance(data, list): 70 quoted = [] 71 for entry in data: 72 if isinstance(entry, unicode): 73 entry = entry.encode('utf-8') 74 quoted.append(urllib.quote(entry, not_quote)) 75 elif isinstance(data, int): 76 ## FIXME: do we need this/should we do this ? 77 quoted = str(data) 78 else: 79 quoted = data 80 return quoted
81
82 -def unquote(data):
83 """ 84 Unquote strings, lists and dictionaries containing strings quoted values 85 using urllib.unquote. Return value has the same type as the parameter given 86 to this function. If the data is not a string, list or a dictionary, it is 87 simply returned. 88 89 @param data: quoted data 90 @type data: string, list or dict 91 @rtype: string, list or dict of unicodes 92 """ 93 if isinstance(data, basestring): 94 if isinstance(data, unicode): 95 # urllib.quote doesn't support unicode objects very well 96 data = data.encode('utf-8') 97 unquoted = urllib.unquote(data).decode('utf-8') 98 elif isinstance(data, dict): 99 unquoted = {} 100 for k, v in data.iteritems(): 101 if isinstance(v, unicode): 102 # urllib.quote doesn't support unicode objects very well 103 v = v.encode('utf-8') 104 unquoted[k] = urllib.unquote(v).decode('utf-8') 105 elif isinstance(data, list): 106 unquoted = [] 107 for entry in data: 108 if isinstance(entry, unicode): 109 entry = entry.encode('utf-8') 110 unquoted.append(urllib.unquote(entry).decode('utf-8')) 111 else: 112 unquoted = data 113 return unquoted
114
115 -def _unicode(data):
116 """ 117 Convert data to unicode using utf-8 encoding instead of ascii. 118 119 @param data: data to convert 120 @type data: string, unicode or other object 121 @rtype: unicode 122 """ 123 if isinstance(data, unicode): 124 result = data 125 elif isinstance(data, str): 126 result = unicode(data, 'utf-8') 127 else: 128 result = unicode(str(data), 'utf-8') 129 return result
130 131 132
133 -class MediaUri:
134 """ Media URI management 135 136 An URI is structured like this:: 137 138 scheme://[user:password@]host[:port]/path[/][?params][#fragment] 139 140 This class is able to parse that and wrap access to all found 141 attributes. When I'm parsing file:// URIs I'm allowed to replace 142 paths like:: 143 144 ./path/to/foo 145 146 With the following:: 147 148 "%s/path/to/foo" % os.curdir 149 150 @ivar scheme: the URI scheme 151 @type scheme: string 152 @ivar user: the optional username 153 @type user: string 154 @ivar password: the optional password 155 @type password: string 156 @ivar host: the URI hostname 157 @type host: string 158 @ivar port: URI optional port. Set to 0 if not found 159 @type port: int 160 @ivar path: the URI path, delimitted by slashes (/) 161 @type path: string 162 @ivar fragment: optional URI fragment 163 @type fragment: string 164 165 @ivar extension: the extension of the uri or empty 166 @type extension: unicode 167 168 @ivar filename: the filename of the uri, means the part behind the last 169 slash. Could be empty! 170 @type filename: unicode 171 172 @ivar label: the label for this uri (per default the same as the filename) 173 @type label: unicode or L{elisa.extern.translator.Translateable} 174 175 @ivar parent: the parent uri (means the last part of the path is removed) 176 @type parent: L{MediaUri} 177 """ 178 179 # Allows property fget/fset/fdel/doc overriding 180 __metaclass__ = classinit.ClassInitMeta 181 __classinit__ = classinit.build_properties 182
183 - def __init__(self, data):
184 """ 185 Create an Uri from various representations. 186 187 Representation can be either a string containing the uri or 188 the components of the uri stored in a dictionary. 189 190 @param data: An uri stored in a unicode string or described by 191 its components (scheme, user, password, host, port, 192 path, params, fragment), each of them being a string 193 @type data: string or dict 194 @raises TypeError: If none of location or parts has been provided 195 @raises ParseException: If the location wasn't correctly parsed 196 """ 197 location = None 198 uri_parts = None 199 200 self._label = u'' 201 self._params = {} 202 203 if isinstance(data, MediaUri): 204 # copycat, avoid another parse 205 if data.path.find('\\') != -1: 206 path = data.path.replace('\\', '/') 207 else: 208 path = data.path 209 210 uri_parts = {'scheme': data.scheme, 'user': data.user, 211 'password': data.password, 'host': data.host, 212 'port': data.port, 'path': path, 213 'fragment': data.fragment, 214 'params': copy.copy(data._params)} 215 self._label = data._label 216 elif isinstance(data, dict): 217 # FIXME: scheme, host and path cannot be empty according to the docstring 218 if data.has_key('path') and data['path'].find('\\') != -1: 219 data['path'] = data['path'].replace('\\', '/') 220 uri_parts = data 221 elif isinstance(data, str): 222 223 if data.find('\\') != -1: 224 data = data.replace('\\', '/') 225 location = unicode(data, 'utf-8') 226 elif isinstance(data, unicode): 227 location = data 228 229 if uri_parts: 230 # FIXME: scheme cannot be empty according to the docstring 231 self.scheme = _unicode(uri_parts['scheme']) 232 self.user = _unicode(uri_parts.get('user', '')) 233 self.password = _unicode(uri_parts.get('password', '')) 234 # FIXME: host cannot be empty according to the docstring 235 #self.host = unicode(uri_parts['host']) 236 self.host = _unicode(uri_parts.get('host', '')) 237 # port is an integer, according to the docstring 238 self.port = uri_parts.get('port', 0) 239 240 self.fragment = _unicode(uri_parts.get('fragment', '')) 241 # FIXME: convert params to unicode 242 self._params = uri_parts.get('params', {}) 243 244 path = uri_parts['path'] 245 # path should always start with / if it's not null 246 # FIXME: why: Sorry, but this unusefull and not at is used in other cases. For e.g. the cdda-uri of gstreamer is: cdda://track-num. This is not working correctly... 247 if path and not path.startswith('/'): 248 path = "/%s" % path 249 250 self._path = _unicode(path) 251 elif location: 252 self._parse(location) 253 else: 254 raise TypeError("No location nor uri parts provided")
255
256 - def _uri_re(self):
257 # compile the regex only once 258 global URI_RE 259 if not URI_RE: 260 URI_RE = re.compile("^(([^:/?#]+):)?(//(([^:]+)\:([^@]*)@)?)?" 261 "([^?#/]*)([^?#]*)" 262 "(\?([^#]*))?(#(.*))?", re.UNICODE) 263 return URI_RE
264
265 - def path__get(self):
266 if platform.system() == 'Windows' and len(self._path) > 0 and self._path[0] == '/' and self.scheme == 'file': 267 return self._path[1:] 268 return self._path
269
270 - def path__set(self, value):
271 self._path = value
272
273 - def _parse(self, location):
274 275 system_name = platform.system() 276 match = self._uri_re().search(location) 277 if match: 278 port = 0 279 fscheme, scheme, auth, user_pass, user, passwd, host, path, fparams, params, ffragment, fragment = match.groups() 280 281 if scheme: 282 self.scheme = scheme 283 else: 284 self.scheme = u'' 285 286 if user: 287 self.user = user 288 else: 289 self.user = u'' 290 291 if passwd: 292 self.password = passwd 293 else: 294 self.password = u'' 295 296 297 host_port = host.split(':') 298 if len(host_port) == 2: 299 host, port = host_port 300 try: 301 port = int(port) 302 except: 303 port = -1 304 305 if host: 306 self.host = host 307 else: 308 self.host = u'' 309 310 self.port = port 311 312 if fragment: 313 self.fragment = fragment 314 else: 315 self.fragment = u'' 316 317 if scheme == 'file': 318 # file scheme case: should disallow user/passwd/host 319 if self.host == '.': 320 # file://./ special case 321 self.host = u'' 322 if path.startswith('/'): 323 path = path[1:] 324 path = os.path.join(os.getcwd(), path) 325 #For windows system 326 if system_name == 'Windows': 327 path = path.replace('\\', '/') 328 329 elif self.host: 330 # transfer host to beginning of path 331 path = "%s%s" % (self.host, path) 332 self.host = u'' 333 334 if not path.startswith('/'): 335 # path should always start with / 336 path = "/%s" % path 337 338 #remove double // in path 339 path = path.replace('//','/') 340 341 self._path = _unicode(path) 342 343 if params: 344 key_values = params.split('&') 345 346 # ?foo special case 347 if key_values[0].find('=') == -1: 348 key_values = [ "%s=" % key_values[0], ] 349 elif key_values[0].rfind('=') != key_values[0].find('='): 350 # special case: params comma separated instead of 351 # &-separated 352 key_values = params.split(',') 353 354 try: 355 self._params = dict([map(_unicode, i.split('=')) 356 for i in key_values]) 357 except Exception, e: 358 msg = "URI parameters were not quoted in %s" % location 359 raise ParseException(msg) 360 else: 361 self._params = {} 362 363 else: 364 raise ParseException("URI not parseable: %s" % location)
365
366 - def set_params(self, values):
367 """ 368 Set a lot of parameters at one time. Attention: it simply 369 overrides already existing values! 370 @param values: a dictionary, where the parameter names are pointing to 371 the unquoted values. 372 @type values: dict 373 """ 374 for key, value in values.iteritems(): 375 self.set_param(key, value)
376
377 - def set_param(self, name, value):
378 """ 379 Set the parameter 'name' to 'value'. If this parameter is already 380 existing it is overritten. The value shouldn't be quoted yet, because 381 it is quoted here. That might lead to very bad parameter values! 382 383 @param name: the name of the paramter 384 @type name: Unicode 385 @param value: unquoted value for the parameter 386 @type value: Unicode 387 """ 388 quoted = quote(value) 389 self._params[name] = quoted
390
391 - def get_param(self, name, default=u''):
392 """ 393 Get the value for the parameter 'name'. If there is none found, return 394 the value of 'default'. If 'default' is not set it is an 395 empty unicode. 396 397 @param name: the name of the parameter 398 @type name: Unicode 399 @param default: value that should be returned, if the parameter is not 400 found (per default that is an empty Unicode) 401 @type default: Unicode 402 403 @rtype: unquoted Unicode 404 @return: paramter or the value of default, if the paramter was 405 not found 406 """ 407 return unquote(self._params.get(name, default))
408
409 - def get_params(self):
410 """ 411 Get all params as a dict. 412 @rtype: dict 413 @return: of quoted key value pairs 414 """ 415 return self._params
416
417 - def get_params_string(self):
418 """ 419 Get the params as a one-line string (excluding a leading '?'). 420 @rtype: unicode 421 @return: key1=value1&key2=value2 422 """ 423 return u'&'.join('='.join((k,v)) for k,v in self._params.iteritems())
424
425 - def del_param(self, name):
426 """ 427 Delete the paramter with the name 'name' (and it's value), if it is 428 found in the list of parameters. 429 430 @param name: the name of the paramter to delete 431 @type name: Unicode 432 """ 433 if name in self._params.keys(): 434 del self._params[name]
435
436 - def extension__get(self):
437 # FIXME: is os.path.splitext doing any encoding conversion ? 438 dummy, ext = os.path.splitext(self._path) 439 if ext: 440 # strip '.' prefix 441 ext = ext[1:].lower() 442 return _unicode(ext)
443
444 - def filename__get(self):
445 """ Return the filename of the Uri. 446 447 Returns last path component label parameter like I{uri://path/to/foo} 448 then 'foo' is returned. 449 450 If there is no path, like in I{uri://host:port/} or in I{uri://} an empty 451 unicode is returned 452 453 """ 454 filename = '' 455 456 if self._path and self._path != '/': 457 idx = self._path.rfind('/') 458 if idx != -1: 459 filename = self._path[idx+1:] 460 461 return _unicode(filename)
462
463 - def label__set(self, value):
464 if isinstance(value, str): 465 self._label = _unicode(value) 466 elif isinstance(value, unicode): 467 self._label = value 468 elif isinstance(value, Translatable): 469 self._label = value
470
471 - def label__get(self):
472 """ Return a displayable string designing the Uri. Return last path 473 component if the URI has no predetermined label instance attribute set. 474 """ 475 label = self._label 476 if not label: 477 path = self._path 478 479 if path == '/': 480 label = _unicode(path) 481 elif path == '': 482 label = self.host 483 else: 484 if path.endswith('/'): 485 path = path[:-1] 486 487 idx = path.rfind('/') 488 if idx != -1: 489 label = _unicode(unquote(path[idx+1:])) 490 491 return label
492 493
494 - def parent__get(self):
495 """ Return the parent URI. 496 497 If the URI is like I{uri://path/to/foo} return I{uri://path/to/} 498 """ 499 p = self._path 500 if p.endswith('/'): 501 p = p[:-1] 502 s = p.split('/')[:-1] 503 p = '/'.join(s) + '/' 504 505 path = _unicode(p) 506 507 uri_parts = {'scheme': self.scheme, 508 'user': self.user, 509 'password': self.password, 'host': self.host, 510 'port': self.port, 'path': path} 511 uri = MediaUri(uri_parts) 512 return uri
513 514
515 - def join(self, path):
516 """ Append the given path to my path attribute 517 518 @param path: the path to append at the end of my path 519 @type path: string 520 @rtype: L{MediaUri} 521 """ 522 new_path = self._path 523 if not new_path.endswith('/'): 524 new_path += '/' 525 new_path += path 526 527 new_path = _unicode(new_path) 528 529 uri_parts = {'scheme': self.scheme, 530 'user': self.user, 531 'password': self.password, 'host': self.host, 532 'port': self.port, 'path': new_path, 533 'fragment': self.fragment, 'params': self._params} 534 535 uri = MediaUri(uri_parts) 536 return uri
537
538 - def _to_unicode(self):
539 """ Textual representation of the URI 540 541 @rtype: unicode 542 """ 543 ret = u'%s://' % self.scheme 544 if self.user: 545 ret += u'%s:%s@' % (self.user, self.password) 546 if self.host: 547 ret += self.host 548 if self.port: 549 ret += u':%s' % self.port 550 551 ret += self._path 552 if len(self._params): 553 ret += u'?' 554 if type(self._params) != dict: 555 d = self._params.asDict() 556 else: 557 d = self._params 558 ret += u'&'.join('='.join((k,v)) for k,v in d.iteritems()) 559 560 if self.fragment: 561 ret += u'#%s' % self.fragment 562 563 assert(isinstance(ret, unicode)) 564 return ret
565 566 __unicode__ = _to_unicode 567
568 - def __str__(self):
569 """ Byte string representation of the URI 570 571 @rtype: string 572 """ 573 r = self._to_unicode() 574 r = r.encode('utf-8') 575 return r
576 577 __repr__ = __str__ 578
579 - def __cmp__(self, other_uri):
580 """ Compare myself with another uri. 581 582 @see help of builtin cmp() 583 @param other_uri: The URI I'm comparing myself with 584 @type other_uri: L{MediaUri} 585 @rtype: int 586 @raise TypeError: When trying to compare with non-MediaUri object 587 """ 588 if not isinstance(other_uri, MediaUri): 589 raise TypeError("You can't compare MediaURI with other kind of objects") 590 return cmp(self._to_unicode(), other_uri._to_unicode())
591
592 - def __eq__(self, other_uri):
593 """ 594 @raise TypeError: When trying to compare with non-MediaUri object 595 """ 596 597 if other_uri is None: 598 return False 599 if not isinstance(other_uri, MediaUri): 600 raise TypeError("You can't compare MediaURI with other kind of objects") 601 return self._to_unicode() == other_uri._to_unicode()
602
603 - def __ne__(self, other_uri):
604 """ 605 @raise TypeError: When trying to compare with non-MediaUri object 606 """ 607 608 if not isinstance(other_uri, MediaUri): 609 return True 610 return self._to_unicode() != other_uri._to_unicode()
611
612 - def __nonzero__(self):
613 return len(self.scheme) != 0
614
615 - def __contains__(self, component):
616 return component in self._to_unicode()
617
618 - def __add__(self, path):
619 return self.join(path)
620
621 - def endswith(self, character):
622 return self._to_unicode().endswith(character)
623
624 - def __getslice__(self, i, j):
625 return self._to_unicode()[i:j]
626 627 #if __name__ == '__main__': 628 # import sys 629 # for uri in sys.argv[1:]: 630 # u = MediaUri(uri) 631 # print u, u.label 632 # assert str(u) == uri, "%r != %r" % (u, uri) 633