| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
|
|---|
| 3 |
# Licensed under the MIT license |
|---|
| 4 |
# http://opensource.org/licenses/mit-license.php |
|---|
| 5 |
|
|---|
| 6 |
# Copyright 2006, Frank Scholz <coherence@beebits.net> |
|---|
| 7 |
|
|---|
| 8 |
import os, stat |
|---|
| 9 |
import tempfile |
|---|
| 10 |
import shutil |
|---|
| 11 |
import time |
|---|
| 12 |
import re |
|---|
| 13 |
from datetime import datetime |
|---|
| 14 |
import urllib |
|---|
| 15 |
|
|---|
| 16 |
from sets import Set |
|---|
| 17 |
|
|---|
| 18 |
import mimetypes |
|---|
| 19 |
mimetypes.init() |
|---|
| 20 |
mimetypes.add_type('audio/x-m4a', '.m4a') |
|---|
| 21 |
mimetypes.add_type('video/mp4', '.mp4') |
|---|
| 22 |
mimetypes.add_type('video/mpegts', '.ts') |
|---|
| 23 |
mimetypes.add_type('video/divx', '.divx') |
|---|
| 24 |
mimetypes.add_type('video/divx', '.avi') |
|---|
| 25 |
mimetypes.add_type('video/x-matroska', '.mkv') |
|---|
| 26 |
|
|---|
| 27 |
from urlparse import urlsplit |
|---|
| 28 |
|
|---|
| 29 |
from twisted.python.filepath import FilePath |
|---|
| 30 |
from twisted.python import failure |
|---|
| 31 |
|
|---|
| 32 |
from coherence.upnp.core.DIDLLite import classChooser, Container, Resource |
|---|
| 33 |
from coherence.upnp.core.DIDLLite import DIDLElement |
|---|
| 34 |
from coherence.upnp.core.DIDLLite import simple_dlna_tags |
|---|
| 35 |
from coherence.upnp.core.soap_service import errorCode |
|---|
| 36 |
|
|---|
| 37 |
from coherence.upnp.core import utils |
|---|
| 38 |
|
|---|
| 39 |
try: |
|---|
| 40 |
from coherence.extern.inotify import INotify |
|---|
| 41 |
from coherence.extern.inotify import IN_CREATE, IN_DELETE, IN_MOVED_FROM, IN_MOVED_TO, IN_ISDIR |
|---|
| 42 |
from coherence.extern.inotify import IN_CHANGED |
|---|
| 43 |
haz_inotify = True |
|---|
| 44 |
except Exception,msg: |
|---|
| 45 |
haz_inotify = False |
|---|
| 46 |
no_inotify_reason = msg |
|---|
| 47 |
|
|---|
| 48 |
from coherence.extern.xdg import xdg_content |
|---|
| 49 |
|
|---|
| 50 |
import coherence.extern.louie as louie |
|---|
| 51 |
|
|---|
| 52 |
from coherence.backend import BackendItem, BackendStore |
|---|
| 53 |
|
|---|
| 54 |
## Sorting helpers |
|---|
| 55 |
NUMS = re.compile('([0-9]+)') |
|---|
| 56 |
def _natural_key(s): |
|---|
| 57 |
# strip the spaces |
|---|
| 58 |
s = s.get_name().strip() |
|---|
| 59 |
return [ part.isdigit() and int(part) or part.lower() for part in NUMS.split(s) ] |
|---|
| 60 |
|
|---|
| 61 |
class FSItem(BackendItem): |
|---|
| 62 |
logCategory = 'fs_item' |
|---|
| 63 |
|
|---|
| 64 |
def __init__(self, object_id, parent, path, mimetype, urlbase, UPnPClass,update=False,store=None): |
|---|
| 65 |
self.id = object_id |
|---|
| 66 |
self.parent = parent |
|---|
| 67 |
if parent: |
|---|
| 68 |
parent.add_child(self,update=update) |
|---|
| 69 |
if mimetype == 'root': |
|---|
| 70 |
self.location = unicode(path) |
|---|
| 71 |
else: |
|---|
| 72 |
if mimetype == 'item' and path is None: |
|---|
| 73 |
path = os.path.join(parent.get_realpath(),unicode(self.id)) |
|---|
| 74 |
#self.location = FilePath(unicode(path)) |
|---|
| 75 |
self.location = FilePath(path) |
|---|
| 76 |
self.mimetype = mimetype |
|---|
| 77 |
if urlbase[-1] != '/': |
|---|
| 78 |
urlbase += '/' |
|---|
| 79 |
self.url = urlbase + str(self.id) |
|---|
| 80 |
|
|---|
| 81 |
self.store = store |
|---|
| 82 |
|
|---|
| 83 |
if parent == None: |
|---|
| 84 |
parent_id = -1 |
|---|
| 85 |
else: |
|---|
| 86 |
parent_id = parent.get_id() |
|---|
| 87 |
|
|---|
| 88 |
self.item = UPnPClass(object_id, parent_id, self.get_name()) |
|---|
| 89 |
if isinstance(self.item, Container): |
|---|
| 90 |
self.item.childCount = 0 |
|---|
| 91 |
self.child_count = 0 |
|---|
| 92 |
self.children = [] |
|---|
| 93 |
self.sorted = False |
|---|
| 94 |
self.caption = None |
|---|
| 95 |
|
|---|
| 96 |
|
|---|
| 97 |
if mimetype in ['directory','root']: |
|---|
| 98 |
self.update_id = 0 |
|---|
| 99 |
self.get_url = lambda : self.url |
|---|
| 100 |
self.get_path = lambda : None |
|---|
| 101 |
#self.item.searchable = True |
|---|
| 102 |
#self.item.searchClass = 'object' |
|---|
| 103 |
if(isinstance(self.location,FilePath) and |
|---|
| 104 |
self.location.isdir() == True): |
|---|
| 105 |
self.check_for_cover_art() |
|---|
| 106 |
if hasattr(self, 'cover'): |
|---|
| 107 |
_,ext = os.path.splitext(self.cover) |
|---|
| 108 |
""" add the cover image extension to help clients not reacting on |
|---|
| 109 |
the mimetype """ |
|---|
| 110 |
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) |
|---|
| 111 |
else: |
|---|
| 112 |
self.get_url = lambda : self.url |
|---|
| 113 |
|
|---|
| 114 |
if self.mimetype.startswith('audio/'): |
|---|
| 115 |
if hasattr(parent, 'cover'): |
|---|
| 116 |
_,ext = os.path.splitext(parent.cover) |
|---|
| 117 |
""" add the cover image extension to help clients not reacting on |
|---|
| 118 |
the mimetype """ |
|---|
| 119 |
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) |
|---|
| 120 |
|
|---|
| 121 |
_,host_port,_,_,_ = urlsplit(urlbase) |
|---|
| 122 |
if host_port.find(':') != -1: |
|---|
| 123 |
host,port = tuple(host_port.split(':')) |
|---|
| 124 |
else: |
|---|
| 125 |
host = host_port |
|---|
| 126 |
|
|---|
| 127 |
try: |
|---|
| 128 |
size = self.location.getsize() |
|---|
| 129 |
except: |
|---|
| 130 |
size = 0 |
|---|
| 131 |
|
|---|
| 132 |
if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': |
|---|
| 133 |
if self.mimetype in ('application/ogg','audio/ogg', |
|---|
| 134 |
'audio/x-wav', |
|---|
| 135 |
'audio/x-m4a', |
|---|
| 136 |
'application/x-flac'): |
|---|
| 137 |
new_res = Resource(self.url+'/transcoded.mp3', |
|---|
| 138 |
'http-get:*:%s:*' % 'audio/mpeg') |
|---|
| 139 |
new_res.size = None |
|---|
| 140 |
#self.item.res.append(new_res) |
|---|
| 141 |
|
|---|
| 142 |
if mimetype != 'item': |
|---|
| 143 |
res = Resource('file://'+ urllib.quote(self.get_path()), 'internal:%s:%s:*' % (host,self.mimetype)) |
|---|
| 144 |
res.size = size |
|---|
| 145 |
self.item.res.append(res) |
|---|
| 146 |
|
|---|
| 147 |
if mimetype != 'item': |
|---|
| 148 |
res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) |
|---|
| 149 |
else: |
|---|
| 150 |
res = Resource(self.url, 'http-get:*:*:*') |
|---|
| 151 |
|
|---|
| 152 |
res.size = size |
|---|
| 153 |
self.item.res.append(res) |
|---|
| 154 |
|
|---|
| 155 |
""" if this item is of type audio and we want to add a transcoding rule for it, |
|---|
| 156 |
this is the way to do it: |
|---|
| 157 |
|
|---|
| 158 |
create a new Resource object, at least a 'http-get' |
|---|
| 159 |
and maybe an 'internal' one too |
|---|
| 160 |
|
|---|
| 161 |
for transcoding to wav this looks like that |
|---|
| 162 |
|
|---|
| 163 |
res = Resource(url_for_transcoded audio, |
|---|
| 164 |
'http-get:*:audio/x-wav:%s'% ';'.join(['DLNA.ORG_PN=JPEG_TN']+simple_dlna_tags)) |
|---|
| 165 |
res.size = None |
|---|
| 166 |
self.item.res.append(res) |
|---|
| 167 |
""" |
|---|
| 168 |
|
|---|
| 169 |
if self.store.server.coherence.config.get('transcoding', 'no') == 'yes': |
|---|
| 170 |
if self.mimetype in ('audio/mpeg', |
|---|
| 171 |
'application/ogg','audio/ogg', |
|---|
| 172 |
'audio/x-wav', |
|---|
| 173 |
'audio/x-m4a', |
|---|
| 174 |
'audio/flac', |
|---|
| 175 |
'application/x-flac'): |
|---|
| 176 |
dlna_pn = 'DLNA.ORG_PN=LPCM' |
|---|
| 177 |
dlna_tags = simple_dlna_tags[:] |
|---|
| 178 |
#dlna_tags[1] = 'DLNA.ORG_OP=00' |
|---|
| 179 |
dlna_tags[2] = 'DLNA.ORG_CI=1' |
|---|
| 180 |
new_res = Resource(self.url+'?transcoded=lpcm', |
|---|
| 181 |
'http-get:*:%s:%s' % ('audio/L16;rate=44100;channels=2', ';'.join([dlna_pn]+dlna_tags))) |
|---|
| 182 |
new_res.size = None |
|---|
| 183 |
#self.item.res.append(new_res) |
|---|
| 184 |
|
|---|
| 185 |
if self.mimetype != 'audio/mpeg': |
|---|
| 186 |
new_res = Resource(self.url+'?transcoded=mp3', |
|---|
| 187 |
'http-get:*:%s:*' % 'audio/mpeg') |
|---|
| 188 |
new_res.size = None |
|---|
| 189 |
#self.item.res.append(new_res) |
|---|
| 190 |
|
|---|
| 191 |
""" if this item is an image and we want to add a thumbnail for it |
|---|
| 192 |
we have to follow these rules: |
|---|
| 193 |
|
|---|
| 194 |
create a new Resource object, at least a 'http-get' |
|---|
| 195 |
and maybe an 'internal' one too |
|---|
| 196 |
|
|---|
| 197 |
for an JPG this looks like that |
|---|
| 198 |
|
|---|
| 199 |
res = Resource(url_for_thumbnail, |
|---|
| 200 |
'http-get:*:image/jpg:%s'% ';'.join(['DLNA.ORG_PN=JPEG_TN']+simple_dlna_tags)) |
|---|
| 201 |
res.size = size_of_thumbnail |
|---|
| 202 |
self.item.res.append(res) |
|---|
| 203 |
|
|---|
| 204 |
and for a PNG the Resource creation is like that |
|---|
| 205 |
|
|---|
| 206 |
res = Resource(url_for_thumbnail, |
|---|
| 207 |
'http-get:*:image/png:%s'% ';'.join(simple_dlna_tags+['DLNA.ORG_PN=PNG_TN'])) |
|---|
| 208 |
|
|---|
| 209 |
if not hasattr(self.item, 'attachments'): |
|---|
| 210 |
self.item.attachments = {} |
|---|
| 211 |
self.item.attachments[key] = utils.StaticFile(filename_of_thumbnail) |
|---|
| 212 |
""" |
|---|
| 213 |
|
|---|
| 214 |
if self.mimetype in ('image/jpeg', 'image/png'): |
|---|
| 215 |
path = self.get_path() |
|---|
| 216 |
thumbnail = os.path.join(os.path.dirname(path),'.thumbs',os.path.basename(path)) |
|---|
| 217 |
if os.path.exists(thumbnail): |
|---|
| 218 |
mimetype,_ = mimetypes.guess_type(thumbnail, strict=False) |
|---|
| 219 |
if mimetype in ('image/jpeg','image/png'): |
|---|
| 220 |
if mimetype == 'image/jpeg': |
|---|
| 221 |
dlna_pn = 'DLNA.ORG_PN=JPEG_TN' |
|---|
| 222 |
else: |
|---|
| 223 |
dlna_pn = 'DLNA.ORG_PN=PNG_TN' |
|---|
| 224 |
|
|---|
| 225 |
dlna_tags = simple_dlna_tags[:] |
|---|
| 226 |
dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' |
|---|
| 227 |
|
|---|
| 228 |
hash_from_path = str(id(thumbnail)) |
|---|
| 229 |
new_res = Resource(self.url+'?attachment='+hash_from_path, |
|---|
| 230 |
'http-get:*:%s:%s' % (mimetype, ';'.join([dlna_pn]+dlna_tags))) |
|---|
| 231 |
new_res.size = os.path.getsize(thumbnail) |
|---|
| 232 |
self.item.res.append(new_res) |
|---|
| 233 |
if not hasattr(self.item, 'attachments'): |
|---|
| 234 |
self.item.attachments = {} |
|---|
| 235 |
self.item.attachments[hash_from_path] = utils.StaticFile(thumbnail) |
|---|
| 236 |
|
|---|
| 237 |
if self.mimetype.startswith('video/'): |
|---|
| 238 |
path = self.get_path() |
|---|
| 239 |
caption,_ = os.path.splitext(path) |
|---|
| 240 |
caption = caption + '.srt' |
|---|
| 241 |
if os.path.exists(caption): |
|---|
| 242 |
hash_from_path = str(id(caption)) |
|---|
| 243 |
mimetype = 'smi/caption' |
|---|
| 244 |
new_res = Resource(self.url+'?attachment='+hash_from_path, |
|---|
| 245 |
'http-get:*:%s:%s' % (mimetype, '*')) |
|---|
| 246 |
new_res.size = os.path.getsize(caption) |
|---|
| 247 |
self.caption = new_res.data |
|---|
| 248 |
self.item.res.append(new_res) |
|---|
| 249 |
if not hasattr(self.item, 'attachments'): |
|---|
| 250 |
self.item.attachments = {} |
|---|
| 251 |
self.item.attachments[hash_from_path] = utils.StaticFile(urllib.quote(caption)) |
|---|
| 252 |
|
|---|
| 253 |
chemin = urllib.unquote(path) |
|---|
| 254 |
thumbnail = os.path.join(os.path.dirname(chemin),'.thumbs',os.path.basename(chemin)+'.jpg') |
|---|
| 255 |
if os.path.exists(thumbnail): |
|---|
| 256 |
dlna_pn = 'DLNA.ORG_PN=JPEG_TN' |
|---|
| 257 |
dlna_tags = simple_dlna_tags[:] |
|---|
| 258 |
dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' |
|---|
| 259 |
|
|---|
| 260 |
hash_from_path = str(id(thumbnail)) |
|---|
| 261 |
new_res = Resource(self.url+'?attachment='+hash_from_path, |
|---|
| 262 |
'http-get:*:%s:%s' % (mimetype, ';'.join([dlna_pn]+dlna_tags))) |
|---|
| 263 |
new_res.size = os.path.getsize(thumbnail) |
|---|
| 264 |
self.item.res.append(new_res) |
|---|
| 265 |
if not hasattr(self.item, 'attachments'): |
|---|
| 266 |
self.item.attachments = {} |
|---|
| 267 |
self.item.attachments[hash_from_path] = utils.StaticFile(thumbnail) |
|---|
| 268 |
|
|---|
| 269 |
thumbnail = os.path.join(os.path.dirname(chemin),'.thumbs',os.path.basename(chemin)+'.png') |
|---|
| 270 |
if os.path.exists(thumbnail): |
|---|
| 271 |
dlna_pn = 'DLNA.ORG_PN=PNG_TN' |
|---|
| 272 |
dlna_tags = simple_dlna_tags[:] |
|---|
| 273 |
dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' |
|---|
| 274 |
|
|---|
| 275 |
hash_from_path = str(id(thumbnail)) |
|---|
| 276 |
new_res = Resource(self.url+'?attachment='+hash_from_path, |
|---|
| 277 |
'http-get:*:%s:%s' % (mimetype, ';'.join([dlna_pn]+dlna_tags))) |
|---|
| 278 |
new_res.size = os.path.getsize(thumbnail) |
|---|
| 279 |
self.item.res.append(new_res) |
|---|
| 280 |
if not hasattr(self.item, 'attachments'): |
|---|
| 281 |
self.item.attachments = {} |
|---|
| 282 |
self.item.attachments[hash_from_path] = utils.StaticFile(thumbnail) |
|---|
| 283 |
|
|---|
| 284 |
try: |
|---|
| 285 |
# FIXME: getmtime is deprecated in Twisted 2.6 |
|---|
| 286 |
self.item.date = datetime.fromtimestamp(self.location.getmtime()) |
|---|
| 287 |
except: |
|---|
| 288 |
self.item.date = None |
|---|
| 289 |
|
|---|
| 290 |
def rebuild(self, urlbase): |
|---|
| 291 |
#print "rebuild", self.mimetype |
|---|
| 292 |
if self.mimetype != 'item': |
|---|
| 293 |
return |
|---|
| 294 |
#print "rebuild for", self.get_path() |
|---|
| 295 |
mimetype,_ = mimetypes.guess_type(self.get_path(),strict=False) |
|---|
| 296 |
if mimetype == None: |
|---|
| 297 |
return |
|---|
| 298 |
self.mimetype = mimetype |
|---|
| 299 |
#print "rebuild", self.mimetype |
|---|
| 300 |
UPnPClass = classChooser(self.mimetype) |
|---|
| 301 |
self.item = UPnPClass(self.id, self.parent.id, self.get_name()) |
|---|
| 302 |
if hasattr(self.parent, 'cover'): |
|---|
| 303 |
_,ext = os.path.splitext(self.parent.cover) |
|---|
| 304 |
""" add the cover image extension to help clients not reacting on |
|---|
| 305 |
the mimetype """ |
|---|
| 306 |
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext)) |
|---|
| 307 |
|
|---|
| 308 |
_,host_port,_,_,_ = urlsplit(urlbase) |
|---|
| 309 |
if host_port.find(':') != -1: |
|---|
| 310 |
host,port = tuple(host_port.split(':')) |
|---|
| 311 |
else: |
|---|
| 312 |
host = host_port |
|---|
| 313 |
|
|---|
| 314 |
res = Resource('file://'+urllib.quote(self.get_path()), 'internal:%s:%s:*' % (host,self.mimetype)) |
|---|
| 315 |
try: |
|---|
| 316 |
res.size = self.location.getsize() |
|---|
| 317 |
except: |
|---|
| 318 |
res.size = 0 |
|---|
| 319 |
self.item.res.append(res) |
|---|
| 320 |
res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) |
|---|
| 321 |
|
|---|
| 322 |
try: |
|---|
| 323 |
res.size = self.location.getsize() |
|---|
| 324 |
except: |
|---|
| 325 |
res.size = 0 |
|---|
| 326 |
self.item.res.append(res) |
|---|
| 327 |
|
|---|
| 328 |
try: |
|---|
| 329 |
# FIXME: getmtime is deprecated in Twisted 2.6 |
|---|
| 330 |
self.item.date = datetime.fromtimestamp(self.location.getmtime()) |
|---|
| 331 |
except: |
|---|
| 332 |
self.item.date = None |
|---|
| 333 |
|
|---|
| 334 |
self.parent.update_id += 1 |
|---|
| 335 |
|
|---|
| 336 |
def check_for_cover_art(self): |
|---|
| 337 |
""" let's try to find in the current directory some jpg file, |
|---|
| 338 |
or png if the jpg search fails, and take the first one |
|---|
| 339 |
that comes around |
|---|
| 340 |
""" |
|---|
| 341 |
try: |
|---|
| 342 |
jpgs = [i.path for i in self.location.children() if i.splitext()[1] in ('.jpg', '.JPG')] |
|---|
| 343 |
try: |
|---|
| 344 |
self.cover = jpgs[0] |
|---|
| 345 |
except IndexError: |
|---|
| 346 |
pngs = [i.path for i in self.location.children() if i.splitext()[1] in ('.png', '.PNG')] |
|---|
| 347 |
try: |
|---|
| 348 |
self.cover = pngs[0] |
|---|
| 349 |
except IndexError: |
|---|
| 350 |
return |
|---|
| 351 |
except UnicodeDecodeError: |
|---|
| 352 |
self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", self.location.path) |
|---|
| 353 |
|
|---|
| 354 |
def remove(self): |
|---|
| 355 |
#print "FSItem remove", self.id, self.get_name(), self.parent |
|---|
| 356 |
if self.parent: |
|---|
| 357 |
self.parent.remove_child(self) |
|---|
| 358 |
del self.item |
|---|
| 359 |
|
|---|
| 360 |
def add_child(self, child, update=False): |
|---|
| 361 |
self.children.append(child) |
|---|
| 362 |
self.child_count += 1 |
|---|
| 363 |
if isinstance(self.item, Container): |
|---|
| 364 |
self.item.childCount += 1 |
|---|
| 365 |
if update == True: |
|---|
| 366 |
self.update_id += 1 |
|---|
| 367 |
self.sorted = False |
|---|
| 368 |
|
|---|
| 369 |
def remove_child(self, child): |
|---|
| 370 |
#print "remove_from %d (%s) child %d (%s)" % (self.id, self.get_name(), child.id, child.get_name()) |
|---|
| 371 |
if child in self.children: |
|---|
| 372 |
self.child_count -= 1 |
|---|
| 373 |
if isinstance(self.item, Container): |
|---|
| 374 |
self.item.childCount -= 1 |
|---|
| 375 |
self.children.remove(child) |
|---|
| 376 |
self.update_id += 1 |
|---|
| 377 |
self.sorted = False |
|---|
| 378 |
|
|---|
| 379 |
def get_children(self,start=0,request_count=0): |
|---|
| 380 |
if self.sorted == False: |
|---|
| 381 |
self.children.sort(key=_natural_key) |
|---|
| 382 |
self.sorted = True |
|---|
| 383 |
if request_count == 0: |
|---|
| 384 |
return self.children[start:] |
|---|
| 385 |
else: |
|---|
| 386 |
return self.children[start:request_count] |
|---|
| 387 |
|
|---|
| 388 |
def get_child_count(self): |
|---|
| 389 |
return self.child_count |
|---|
| 390 |
|
|---|
| 391 |
def get_id(self): |
|---|
| 392 |
return self.id |
|---|
| 393 |
|
|---|
| 394 |
def get_update_id(self): |
|---|
| 395 |
if hasattr(self, 'update_id'): |
|---|
| 396 |
return self.update_id |
|---|
| 397 |
else: |
|---|
| 398 |
return None |
|---|
| 399 |
|
|---|
| 400 |
def get_path(self): |
|---|
| 401 |
if isinstance( self.location,FilePath): |
|---|
| 402 |
return self.location.path |
|---|
| 403 |
else: |
|---|
| 404 |
self.location |
|---|
| 405 |
|
|---|
| 406 |
def get_realpath(self): |
|---|
| 407 |
if isinstance( self.location,FilePath): |
|---|
| 408 |
return self.location.path |
|---|
| 409 |
else: |
|---|
| 410 |
self.location |
|---|
| 411 |
|
|---|
| 412 |
def set_path(self,path=None,extension=None): |
|---|
| 413 |
if path is None: |
|---|
| 414 |
path = self.get_path() |
|---|
| 415 |
if extension is not None: |
|---|
| 416 |
path,old_ext = os.path.splitext(path) |
|---|
| 417 |
path = ''.join((path,extension)) |
|---|
| 418 |
if isinstance( self.location,FilePath): |
|---|
| 419 |
self.location = FilePath(path) |
|---|
| 420 |
else: |
|---|
| 421 |
self.location = path |
|---|
| 422 |
|
|---|
| 423 |
def get_name(self): |
|---|
| 424 |
if isinstance(self.location,FilePath): |
|---|
| 425 |
name = self.location.basename().decode("utf-8", "replace") |
|---|
| 426 |
else: |
|---|
| 427 |
name = self.location.decode("utf-8", "replace") |
|---|
| 428 |
return name |
|---|
| 429 |
|
|---|
| 430 |
def get_cover(self): |
|---|
| 431 |
try: |
|---|
| 432 |
return self.cover |
|---|
| 433 |
except: |
|---|
| 434 |
try: |
|---|
| 435 |
return self.parent.cover |
|---|
| 436 |
except: |
|---|
| 437 |
return '' |
|---|
| 438 |
|
|---|
| 439 |
def get_parent(self): |
|---|
| 440 |
return self.parent |
|---|
| 441 |
|
|---|
| 442 |
def get_item(self): |
|---|
| 443 |
return self.item |
|---|
| 444 |
|
|---|
| 445 |
def get_xml(self): |
|---|
| 446 |
return self.item.toString() |
|---|
| 447 |
|
|---|
| 448 |
def __repr__(self): |
|---|
| 449 |
return 'id: ' + str(self.id) + ' @ ' + self.get_name().encode('ascii','xmlcharrefreplace') |
|---|
| 450 |
|
|---|
| 451 |
class FSStore(BackendStore): |
|---|
| 452 |
logCategory = 'fs_store' |
|---|
| 453 |
|
|---|
| 454 |
implements = ['MediaServer'] |
|---|
| 455 |
|
|---|
| 456 |
description = """MediaServer exporting files from the file-system""" |
|---|
| 457 |
|
|---|
| 458 |
options = [{'option':'name','type':'string','default':'my media','help': 'the name under this MediaServer shall show up with on other UPnP clients'}, |
|---|
| 459 |
{'option':'version','type':'int','default':2,'enum': (2,1),'help': 'the highest UPnP version this MediaServer shall support','level':'advance'}, |
|---|
| 460 |
{'option':'uuid','type':'string','help':'the unique (UPnP) identifier for this MediaServer, usually automatically set','level':'advance'}, |
|---|
| 461 |
{'option':'content','type':'string','default':xdg_content(),'help':'the path(s) this MediaServer shall export'}, |
|---|
| 462 |
{'option':'ignore_patterns','type':'string','help':'list of regex patterns, matching filenames will be ignored'}, |
|---|
| 463 |
{'option':'enable_destroy','type':'string','default':'no','help':'enable deleting a file via an UPnP method'}, |
|---|
| 464 |
{'option':'import_folder','type':'string','help':'The path to store files imported via an UPnP method, if empty the Import method is disabled'} |
|---|
| 465 |
] |
|---|
| 466 |
|
|---|
| 467 |
|
|---|
| 468 |
def __init__(self, server, **kwargs): |
|---|
| 469 |
BackendStore.__init__(self,server,**kwargs) |
|---|
| 470 |
self.next_id = 1000 |
|---|
| 471 |
self.name = kwargs.get('name','my media') |
|---|
| 472 |
self.content = kwargs.get('content',None) |
|---|
| 473 |
if self.content != None: |
|---|
| 474 |
if isinstance(self.content,basestring): |
|---|
| 475 |
self.content = [self.content] |
|---|
| 476 |
l = [] |
|---|
| 477 |
for a in self.content: |
|---|
| 478 |
l += a.split(',') |
|---|
| 479 |
self.content = l |
|---|
| 480 |
else: |
|---|
| 481 |
self.content = xdg_content() |
|---|
| 482 |
self.content = [x[0] for x in self.content] |
|---|
| 483 |
if self.content == None: |
|---|
| 484 |
self.content = 'tests/content' |
|---|
| 485 |
if not isinstance( self.content, list): |
|---|
| 486 |
self.content = [self.content] |
|---|
| 487 |
self.content = Set([os.path.abspath(x) for x in self.content]) |
|---|
| 488 |
ignore_patterns = kwargs.get('ignore_patterns',[]) |
|---|
| 489 |
self.store = {} |
|---|
| 490 |
|
|---|
| 491 |
self.inotify = None |
|---|
| 492 |
|
|---|
| 493 |
if haz_inotify == True: |
|---|
| 494 |
try: |
|---|
| 495 |
self.inotify = INotify() |
|---|
| 496 |
except Exception,msg: |
|---|
| 497 |
self.info("%s" %msg) |
|---|
| 498 |
else: |
|---|
| 499 |
self.info("%s" %no_inotify_reason) |
|---|
| 500 |
|
|---|
| 501 |
if kwargs.get('enable_destroy','no') == 'yes': |
|---|
| 502 |
self.upnp_DestroyObject = self.hidden_upnp_DestroyObject |
|---|
| 503 |
|
|---|
| 504 |
self.import_folder = kwargs.get('import_folder',None) |
|---|
| 505 |
if self.import_folder != None: |
|---|
| 506 |
self.import_folder = os.path.abspath(self.import_folder) |
|---|
| 507 |
if not os.path.isdir(self.import_folder): |
|---|
| 508 |
self.import_folder = None |
|---|
| 509 |
|
|---|
| 510 |
self.ignore_file_pattern = re.compile('|'.join(['^\..*'] + list(ignore_patterns))) |
|---|
| 511 |
parent = None |
|---|
| 512 |
self.update_id = 0 |
|---|
| 513 |
if(len(self.content)>1 or |
|---|
| 514 |
utils.means_true(kwargs.get('create_root',False)) or |
|---|
| 515 |
self.import_folder != None): |
|---|
| 516 |
UPnPClass = classChooser('root') |
|---|
| 517 |
id = str(self.getnextID()) |
|---|
| 518 |
parent = self.store[id] = FSItem( id, parent, 'media', 'root', self.urlbase, UPnPClass, update=True,store=self) |
|---|
| 519 |
|
|---|
| 520 |
if self.import_folder != None: |
|---|
| 521 |
id = str(self.getnextID()) |
|---|
| 522 |
self.store[id] = FSItem( id, parent, self.import_folder, 'directory', self.urlbase, UPnPClass, update=True,store=self) |
|---|
| 523 |
self.import_folder_id = id |
|---|
| 524 |
|
|---|
| 525 |
for path in self.content: |
|---|
| 526 |
if isinstance(path,(list,tuple)): |
|---|
| 527 |
path = path[0] |
|---|
| 528 |
if self.ignore_file_pattern.match(path): |
|---|
| 529 |
continue |
|---|
| 530 |
try: |
|---|
| 531 |
self.walk(path, parent, self.ignore_file_pattern) |
|---|
| 532 |
except Exception,msg: |
|---|
| 533 |
self.warning('on walk of %r: %r' % (path,msg)) |
|---|
| 534 |
import traceback |
|---|
| 535 |
self.debug(traceback.format_exc()) |
|---|
| 536 |
|
|---|
| 537 |
self.wmc_mapping.update({'14': '0', |
|---|
| 538 |
'15': '0', |
|---|
| 539 |
'16': '0', |
|---|
| 540 |
'17': '0' |
|---|
| 541 |
}) |
|---|
| 542 |
|
|---|
| 543 |
louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self) |
|---|
| 544 |
|
|---|
| 545 |
def __repr__(self): |
|---|
| 546 |
return str(self.__class__).split('.')[-1] |
|---|
| 547 |
|
|---|
| 548 |
def release(self): |
|---|
| 549 |
if self.inotify != None: |
|---|
| 550 |
self.inotify.release() |
|---|
| 551 |
|
|---|
| 552 |
def len(self): |
|---|
| 553 |
return len(self.store) |
|---|
| 554 |
|
|---|
| 555 |
def get_by_id(self,id): |
|---|
| 556 |
#print "get_by_id", id, type(id) |
|---|
| 557 |
# we have referenced ids here when we are in WMC mapping mode |
|---|
| 558 |
if isinstance(id, basestring): |
|---|
| 559 |
id = id.split('@',1) |
|---|
| 560 |
id = id[0] |
|---|
| 561 |
#try: |
|---|
| 562 |
# id = int(id) |
|---|
| 563 |
#except ValueError: |
|---|
| 564 |
# id = 1000 |
|---|
| 565 |
|
|---|
| 566 |
if id == '0': |
|---|
| 567 |
id = '1000' |
|---|
| 568 |
#print "get_by_id 2", id |
|---|
| 569 |
try: |
|---|
| 570 |
r = self.store[id] |
|---|
| 571 |
except: |
|---|
| 572 |
r = None |
|---|
| 573 |
#print "get_by_id 3", r |
|---|
| 574 |
return r |
|---|
| 575 |
|
|---|
| 576 |
def get_id_by_name(self, parent='0', name=''): |
|---|
| 577 |
self.info('get_id_by_name %r (%r) %r' % (parent, type(parent), name)) |
|---|
| 578 |
try: |
|---|
| 579 |
parent = self.store[parent] |
|---|
| 580 |
self.debug("%r %d" % (parent,len(parent.children))) |
|---|
| 581 |
for child in parent.children: |
|---|
| 582 |
#if not isinstance(name, unicode): |
|---|
| 583 |
# name = name.decode("utf8") |
|---|
| 584 |
self.debug("%r %r %r" % (child.get_name(),child.get_realpath(), name == child.get_realpath())) |
|---|
| 585 |
if name == child.get_realpath(): |
|---|
| 586 |
return child.id |
|---|
| 587 |
except: |
|---|
| 588 |
import traceback |
|---|
| 589 |
self.info(traceback.format_exc()) |
|---|
| 590 |
self.debug('get_id_by_name not found') |
|---|
| 591 |
|
|---|
| 592 |
return None |
|---|
| 593 |
|
|---|
| 594 |
def get_url_by_name(self,parent='0',name=''): |
|---|
| 595 |
self.info('get_url_by_name %r %r' % (parent, name)) |
|---|
| 596 |
id = self.get_id_by_name(parent,name) |
|---|
| 597 |
#print 'get_url_by_name', id |
|---|
| 598 |
if id == None: |
|---|
| 599 |
return '' |
|---|
| 600 |
return self.store[id].url |
|---|
| 601 |
|
|---|
| 602 |
|
|---|
| 603 |
def update_config(self,**kwargs): |
|---|
| 604 |
print "update_config", kwargs |
|---|
| 605 |
if 'content' in kwargs: |
|---|
| 606 |
new_content = kwargs['content'] |
|---|
| 607 |
new_content = Set([os.path.abspath(x) for x in new_content.split(',')]) |
|---|
| 608 |
new_folders = new_content.difference(self.content) |
|---|
| 609 |
obsolete_folders = self.content.difference(new_content) |
|---|
| 610 |
print new_folders, obsolete_folders |
|---|
| 611 |
for folder in obsolete_folders: |
|---|
| 612 |
self.remove_content_folder(folder) |
|---|
| 613 |
for folder in new_folders: |
|---|
| 614 |
self.add_content_folder(folder) |
|---|
| 615 |
self.content = new_content |
|---|
| 616 |
|
|---|
| 617 |
def add_content_folder(self,path): |
|---|
| 618 |
path = os.path.abspath(path) |
|---|
| 619 |
if path not in self.content: |
|---|
| 620 |
self.content.add(path) |
|---|
| 621 |
self.walk(path, self.store['1000'], self.ignore_file_pattern) |
|---|
| 622 |
|
|---|
| 623 |
def remove_content_folder(self,path): |
|---|
| 624 |
path = os.path.abspath(path) |
|---|
| 625 |
if path in self.content: |
|---|
| 626 |
id = self.get_id_by_name('1000', path) |
|---|
| 627 |
self.remove(id) |
|---|
| 628 |
self.content.remove(path) |
|---|
| 629 |
|
|---|
| 630 |
def walk(self, path, parent=None, ignore_file_pattern=''): |
|---|
| 631 |
self.debug("walk %r" % path) |
|---|
| 632 |
containers = [] |
|---|
| 633 |
parent = self.append(path,parent) |
|---|
| 634 |
if parent != None: |
|---|
| 635 |
containers.append(parent) |
|---|
| 636 |
while len(containers)>0: |
|---|
| 637 |
container = containers.pop() |
|---|
| 638 |
try: |
|---|
| 639 |
self.debug('adding %r' % container.location) |
|---|
| 640 |
for child in container.location.children(): |
|---|
| 641 |
if ignore_file_pattern.match(child.basename()) != None: |
|---|
| 642 |
continue |
|---|
| 643 |
new_container = self.append(child.path,container) |
|---|
| 644 |
if new_container != None: |
|---|
| 645 |
containers.append(new_container) |
|---|
| 646 |
except UnicodeDecodeError: |
|---|
| 647 |
self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", container.get_path()) |
|---|
| 648 |
|
|---|
| 649 |
def create(self, mimetype, path, parent): |
|---|
| 650 |
self.debug("create ", mimetype, path, type(path), parent) |
|---|
| 651 |
UPnPClass = classChooser(mimetype) |
|---|
| 652 |
if UPnPClass == None: |
|---|
| 653 |
return None |
|---|
| 654 |
|
|---|
| 655 |
id = self.getnextID() |
|---|
| 656 |
if mimetype in ('root','directory'): |
|---|
| 657 |
id = str(id) |
|---|
| 658 |
else: |
|---|
| 659 |
_,ext = os.path.splitext(path) |
|---|
| 660 |
id = str(id) + ext.lower() |
|---|
| 661 |
update = False |
|---|
| 662 |
if hasattr(self, 'update_id'): |
|---|
| 663 |
update = True |
|---|
| 664 |
|
|---|
| 665 |
self.store[id] = FSItem( id, parent, path, mimetype, self.urlbase, UPnPClass, update=True,store=self) |
|---|
| 666 |
if hasattr(self, 'update_id'): |
|---|
| 667 |
self.update_id += 1 |
|---|
| 668 |
#print self.update_id |
|---|
| 669 |
if self.server: |
|---|
| 670 |
if hasattr(self.server,'content_directory_server'): |
|---|
| 671 |
self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) |
|---|
| 672 |
if parent is not None: |
|---|
| 673 |
value = (parent.get_id(),parent.get_update_id()) |
|---|
| 674 |
if self.server: |
|---|
| 675 |
if hasattr(self.server,'content_directory_server'): |
|---|
| 676 |
self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) |
|---|
| 677 |
|
|---|
| 678 |
return id |
|---|
| 679 |
|
|---|
| 680 |
def append(self,path,parent): |
|---|
| 681 |
self.debug("append ", path, type(path), parent) |
|---|
| 682 |
if os.path.exists(path) == False: |
|---|
| 683 |
self.warning("path %r not available - ignored", path) |
|---|
| 684 |
return None |
|---|
| 685 |
|
|---|
| 686 |
if stat.S_ISFIFO(os.stat(path).st_mode): |
|---|
| 687 |
self.warning("path %r is a FIFO - ignored", path) |
|---|
| 688 |
return None |
|---|
| 689 |
|
|---|
| 690 |
try: |
|---|
| 691 |
mimetype,_ = mimetypes.guess_type(path, strict=False) |
|---|
| 692 |
if mimetype == None: |
|---|
| 693 |
if os.path.isdir(path): |
|---|
| 694 |
mimetype = 'directory' |
|---|
| 695 |
if mimetype == None: |
|---|
| 696 |
return None |
|---|
| 697 |
|
|---|
| 698 |
id = self.create(mimetype,path,parent) |
|---|
| 699 |
|
|---|
| 700 |
if mimetype == 'directory': |
|---|
| 701 |
if self.inotify is not None: |
|---|
| 702 |
mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED |
|---|
| 703 |
self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id)) |
|---|
| 704 |
return self.store[id] |
|---|
| 705 |
except OSError, msg: |
|---|
| 706 |
""" seems we have some permissions issues along the content path """ |
|---|
| 707 |
self.warning("path %r isn't accessible, error %r", path, msg) |
|---|
| 708 |
|
|---|
| 709 |
return None |
|---|
| 710 |
|
|---|
| 711 |
def remove(self, id): |
|---|
| 712 |
print 'FSSTore remove id', id |
|---|
| 713 |
try: |
|---|
| 714 |
item = self.store[id] |
|---|
| 715 |
parent = item.get_parent() |
|---|
| 716 |
item.remove() |
|---|
| 717 |
del self.store[id] |
|---|
| 718 |
if hasattr(self, 'update_id'): |
|---|
| 719 |
self.update_id += 1 |
|---|
| 720 |
if self.server: |
|---|
| 721 |
self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) |
|---|
| 722 |
#value = '%d,%d' % (parent.get_id(),parent_get_update_id()) |
|---|
| 723 |
value = (parent.get_id(),parent.get_update_id()) |
|---|
| 724 |
if self.server: |
|---|
| 725 |
self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) |
|---|
| 726 |
|
|---|
| 727 |
except: |
|---|
| 728 |
pass |
|---|
| 729 |
|
|---|
| 730 |
|
|---|
| 731 |
def notify(self, iwp, filename, mask, parameter=None): |
|---|
| 732 |
self.info("Event %s on %s %s - parameter %r" % ( |
|---|
| 733 |
', '.join(self.inotify.flag_to_human(mask)), iwp.path, filename, parameter)) |
|---|
| 734 |
|
|---|
| 735 |
path = iwp.path |
|---|
| 736 |
if filename: |
|---|
| 737 |
path = os.path.join(path, filename) |
|---|
| 738 |
|
|---|
| 739 |
if mask & IN_CHANGED: |
|---|
| 740 |
# FIXME react maybe on access right changes, loss of read rights? |
|---|
| 741 |
#print '%s was changed, parent %d (%s)' % (path, parameter, iwp.path) |
|---|
| 742 |
pass |
|---|
| 743 |
|
|---|
| 744 |
if(mask & IN_DELETE or mask & IN_MOVED_FROM): |
|---|
| 745 |
self.info('%s was deleted, parent %r (%s)' % (path, parameter, iwp.path)) |
|---|
| 746 |
id = self.get_id_by_name(parameter,os.path.join(iwp.path,filename)) |
|---|
| 747 |
if id != None: |
|---|
| 748 |
self.remove(id) |
|---|
| 749 |
if(mask & IN_CREATE or mask & IN_MOVED_TO): |
|---|
| 750 |
if mask & IN_ISDIR: |
|---|
| 751 |
self.info('directory %s was created, parent %r (%s)' % (path, parameter, iwp.path)) |
|---|
| 752 |
else: |
|---|
| 753 |
self.info('file %s was created, parent %r (%s)' % (path, parameter, iwp.path)) |
|---|
| 754 |
if self.get_id_by_name(parameter,os.path.join(iwp.path,filename)) is None: |
|---|
| 755 |
if os.path.isdir(path): |
|---|
| 756 |
self.walk(path, self.get_by_id(parameter), self.ignore_file_pattern) |
|---|
| 757 |
else: |
|---|
| 758 |
if self.ignore_file_pattern.match(filename) == None: |
|---|
| 759 |
self.append(path, self.get_by_id(parameter)) |
|---|
| 760 |
|
|---|
| 761 |
def getnextID(self): |
|---|
| 762 |
ret = self.next_id |
|---|
| 763 |
self.next_id += 1 |
|---|
| 764 |
return ret |
|---|
| 765 |
|
|---|
| 766 |
def backend_import(self,item,data): |
|---|
| 767 |
try: |
|---|
| 768 |
f = open(item.get_path(), 'w+b') |
|---|
| 769 |
if hasattr(data,'read'): |
|---|
| 770 |
data = data.read() |
|---|
| 771 |
f.write(data) |
|---|
| 772 |
f.close() |
|---|
| 773 |
item.rebuild(self.urlbase) |
|---|
| 774 |
return 200 |
|---|
| 775 |
except IOError: |
|---|
| 776 |
self.warning("import of file %s failed" % item.get_path()) |
|---|
| 777 |
except Exception,msg: |
|---|
| 778 |
import traceback |
|---|
| 779 |
self.warning(traceback.format_exc()) |
|---|
| 780 |
return 500 |
|---|
| 781 |
|
|---|
| 782 |
def upnp_init(self): |
|---|
| 783 |
self.current_connection_id = None |
|---|
| 784 |
if self.server: |
|---|
| 785 |
self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', |
|---|
| 786 |
[#'http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01700000000000000000000000000000', |
|---|
| 787 |
#'http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01700000000000000000000000000000', |
|---|
| 788 |
#'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', |
|---|
| 789 |
#'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', |
|---|
| 790 |
#'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', |
|---|
| 791 |
#'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=00f00000000000000000000000000000', |
|---|
| 792 |
#'http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000', |
|---|
| 793 |
#'http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000', |
|---|
| 794 |
'internal:%s:audio/mpeg:*' % self.server.coherence.hostname, |
|---|
| 795 |
'http-get:*:audio/mpeg:*', |
|---|
| 796 |
'internal:%s:video/mp4:*' % self.server.coherence.hostname, |
|---|
| 797 |
'http-get:*:video/mp4:*', |
|---|
| 798 |
'internal:%s:application/ogg:*' % self.server.coherence.hostname, |
|---|
| 799 |
'http-get:*:application/ogg:*', |
|---|
| 800 |
'internal:%s:video/x-msvideo:*' % self.server.coherence.hostname, |
|---|
| 801 |
'http-get:*:video/x-msvideo:*', |
|---|
| 802 |
'internal:%s:video/mpeg:*' % self.server.coherence.hostname, |
|---|
| 803 |
'http-get:*:video/mpeg:*', |
|---|
| 804 |
'internal:%s:video/avi:*' % self.server.coherence.hostname, |
|---|
| 805 |
'http-get:*:video/avi:*', |
|---|
| 806 |
'internal:%s:video/divx:*' % self.server.coherence.hostname, |
|---|
| 807 |
'http-get:*:video/divx:*', |
|---|
| 808 |
'internal:%s:video/quicktime:*' % self.server.coherence.hostname, |
|---|
| 809 |
'http-get:*:video/quicktime:*', |
|---|
| 810 |
'internal:%s:image/gif:*' % self.server.coherence.hostname, |
|---|
| 811 |
'http-get:*:image/gif:*', |
|---|
| 812 |
'internal:%s:image/jpeg:*' % self.server.coherence.hostname, |
|---|
| 813 |
'http-get:*:image/jpeg:*'], |
|---|
| 814 |
default=True) |
|---|
| 815 |
self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) |
|---|
| 816 |
#self.server.content_directory_server.set_variable(0, 'SortCapabilities', '*') |
|---|
| 817 |
|
|---|
| 818 |
|
|---|
| 819 |
def upnp_ImportResource(self, *args, **kwargs): |
|---|
| 820 |
SourceURI = kwargs['SourceURI'] |
|---|
| 821 |
DestinationURI = kwargs['DestinationURI'] |
|---|
| 822 |
|
|---|
| 823 |
if DestinationURI.endswith('?import'): |
|---|
| 824 |
id = DestinationURI.split('/')[-1] |
|---|
| 825 |
id = id[:-7] # remove the ?import |
|---|
| 826 |
else: |
|---|
| 827 |
return failure.Failure(errorCode(718)) |
|---|
| 828 |
|
|---|
| 829 |
item = self.get_by_id(id) |
|---|
| 830 |
if item == None: |
|---|
| 831 |
return failure.Failure(errorCode(718)) |
|---|
| 832 |
|
|---|
| 833 |
def gotPage(headers): |
|---|
| 834 |
#print "gotPage", headers |
|---|
| 835 |
content_type = headers.get('content-type',[]) |
|---|
| 836 |
if not isinstance(content_type, list): |
|---|
| 837 |
content_type = list(content_type) |
|---|
| 838 |
if len(content_type) > 0: |
|---|
| 839 |
extension = mimetypes.guess_extension(content_type[0], strict=False) |
|---|
| 840 |
item.set_path(None,extension) |
|---|
| 841 |
shutil.move(tmp_path, item.get_path()) |
|---|
| 842 |
item.rebuild(self.urlbase) |
|---|
| 843 |
if hasattr(self, 'update_id'): |
|---|
| 844 |
self.update_id += 1 |
|---|
| 845 |
if self.server: |
|---|
| 846 |
if hasattr(self.server,'content_directory_server'): |
|---|
| 847 |
self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) |
|---|
| 848 |
if item.parent is not None: |
|---|
| 849 |
value = (item.parent.get_id(),item.parent.get_update_id()) |
|---|
| 850 |
if self.server: |
|---|
| 851 |
if hasattr(self.server,'content_directory_server'): |
|---|
| 852 |
self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) |
|---|
| 853 |
|
|---|
| 854 |
def gotError(error, url): |
|---|
| 855 |
self.warning("error requesting", url) |
|---|
| 856 |
self.info(error) |
|---|
| 857 |
os.unlink(tmp_path) |
|---|
| 858 |
return failure.Failure(errorCode(718)) |
|---|
| 859 |
|
|---|
| 860 |
tmp_fp, tmp_path = tempfile.mkstemp() |
|---|
| 861 |
os.close(tmp_fp) |
|---|
| 862 |
|
|---|
| 863 |
utils.downloadPage(SourceURI, |
|---|
| 864 |
tmp_path).addCallbacks(gotPage, gotError, None, None, [SourceURI], None) |
|---|
| 865 |
|
|---|
| 866 |
transfer_id = 0 #FIXME |
|---|
| 867 |
|
|---|
| 868 |
return {'TransferID': transfer_id} |
|---|
| 869 |
|
|---|
| 870 |
def upnp_CreateObject(self, *args, **kwargs): |
|---|
| 871 |
#print "CreateObject", kwargs |
|---|
| 872 |
if kwargs['ContainerID'] == 'DLNA.ORG_AnyContainer': |
|---|
| 873 |
if self.import_folder != None: |
|---|
| 874 |
ContainerID = self.import_folder_id |
|---|
| 875 |
else: |
|---|
| 876 |
return failure.Failure(errorCode(712)) |
|---|
| 877 |
else: |
|---|
| 878 |
ContainerID = kwargs['ContainerID'] |
|---|
| 879 |
Elements = kwargs['Elements'] |
|---|
| 880 |
|
|---|
| 881 |
parent_item = self.get_by_id(ContainerID) |
|---|
| 882 |
if parent_item == None: |
|---|
| 883 |
return failure.Failure(errorCode(710)) |
|---|
| 884 |
if parent_item.item.restricted: |
|---|
| 885 |
return failure.Failure(errorCode(713)) |
|---|
| 886 |
|
|---|
| 887 |
if len(Elements) == 0: |
|---|
| 888 |
return failure.Failure(errorCode(712)) |
|---|
| 889 |
|
|---|
| 890 |
elt = DIDLElement.fromString(Elements) |
|---|
| 891 |
if elt.numItems() != 1: |
|---|
| 892 |
return failure.Failure(errorCode(712)) |
|---|
| 893 |
|
|---|
| 894 |
item = elt.getItems()[0] |
|---|
| 895 |
if item.parentID == 'DLNA.ORG_AnyContainer': |
|---|
| 896 |
item.parentID = ContainerID |
|---|
| 897 |
if(item.id != '' or |
|---|
| 898 |
item.parentID != ContainerID or |
|---|
| 899 |
item.restricted == True or |
|---|
| 900 |
item.title == ''): |
|---|
| 901 |
return failure.Failure(errorCode(712)) |
|---|
| 902 |
|
|---|
| 903 |
if('..' in item.title or |
|---|
| 904 |
'~' in item.title or |
|---|
| 905 |
os.sep in item.title): |
|---|
| 906 |
return failure.Failure(errorCode(712)) |
|---|
| 907 |
|
|---|
| 908 |
if item.upnp_class == 'object.container.storageFolder': |
|---|
| 909 |
if len(item.res) != 0: |
|---|
| 910 |
return failure.Failure(errorCode(712)) |
|---|
| 911 |
path = os.path.join(parent_item.get_path(),item.title) |
|---|
| 912 |
id = self.create('directory',path,parent_item) |
|---|
| 913 |
try: |
|---|
| 914 |
os.mkdir(path) |
|---|
| 915 |
except: |
|---|
| 916 |
self.remove(id) |
|---|
| 917 |
return failure.Failure(errorCode(712)) |
|---|
| 918 |
|
|---|
| 919 |
if self.inotify is not None: |
|---|
| 920 |
mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED |
|---|
| 921 |
self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id)) |
|---|
| 922 |
|
|---|
| 923 |
new_item = self.get_by_id(id) |
|---|
| 924 |
didl = DIDLElement() |
|---|
| 925 |
didl.addItem(new_item.item) |
|---|
| 926 |
return {'ObjectID': id, 'Result': didl.toString()} |
|---|
| 927 |
|
|---|
| 928 |
if item.upnp_class.startswith('object.item'): |
|---|
| 929 |
_,_,content_format,_ = item.res[0].protocolInfo.split(':') |
|---|
| 930 |
extension = mimetypes.guess_extension(content_format, strict=False) |
|---|
| 931 |
path = os.path.join(parent_item.get_realpath(),item.title+extension) |
|---|
| 932 |
id = self.create('item',path,parent_item) |
|---|
| 933 |
|
|---|
| 934 |
new_item = self.get_by_id(id) |
|---|
| 935 |
for res in new_item.item.res: |
|---|
| 936 |
res.importUri = new_item.url+'?import' |
|---|
| 937 |
res.data = None |
|---|
| 938 |
didl = DIDLElement() |
|---|
| 939 |
didl.addItem(new_item.item) |
|---|
| 940 |
return {'ObjectID': id, 'Result': didl.toString()} |
|---|
| 941 |
|
|---|
| 942 |
return failure.Failure(errorCode(712)) |
|---|
| 943 |
|
|---|
| 944 |
def hidden_upnp_DestroyObject(self, *args, **kwargs): |
|---|
| 945 |
ObjectID = kwargs['ObjectID'] |
|---|
| 946 |
|
|---|
| 947 |
item = self.get_by_id(ObjectID) |
|---|
| 948 |
if item == None: |
|---|
| 949 |
return failure.Failure(errorCode(701)) |
|---|
| 950 |
|
|---|
| 951 |
print "upnp_DestroyObject", item.location |
|---|
| 952 |
try: |
|---|
| 953 |
item.location.remove() |
|---|
| 954 |
except Exception, msg: |
|---|
| 955 |
print Exception, msg |
|---|
| 956 |
return failure.Failure(errorCode(715)) |
|---|
| 957 |
|
|---|
| 958 |
return {} |
|---|
| 959 |
|
|---|
| 960 |
|
|---|
| 961 |
if __name__ == '__main__': |
|---|
| 962 |
|
|---|
| 963 |
from twisted.internet import reactor |
|---|
| 964 |
|
|---|
| 965 |
p = 'tests/content' |
|---|
| 966 |
f = FSStore(None,name='my media',content=p, urlbase='http://localhost/xyz') |
|---|
| 967 |
|
|---|
| 968 |
print f.len() |
|---|
| 969 |
print f.get_by_id(1000).child_count, f.get_by_id(1000).get_xml() |
|---|
| 970 |
print f.get_by_id(1001).child_count, f.get_by_id(1001).get_xml() |
|---|
| 971 |
print f.get_by_id(1002).child_count, f.get_by_id(1002).get_xml() |
|---|
| 972 |
print f.get_by_id(1003).child_count, f.get_by_id(1003).get_xml() |
|---|
| 973 |
print f.get_by_id(1004).child_count, f.get_by_id(1004).get_xml() |
|---|
| 974 |
print f.get_by_id(1005).child_count, f.get_by_id(1005).get_xml() |
|---|
| 975 |
print f.store[1000].get_children(0,0) |
|---|
| 976 |
#print f.upnp_Search(ContainerID ='4', |
|---|
| 977 |
# Filter ='dc:title,upnp:artist', |
|---|
| 978 |
# RequestedCount = '1000', |
|---|
| 979 |
# StartingIndex = '0', |
|---|
| 980 |
# SearchCriteria = '(upnp:class = "object.container.album.musicAlbum")', |
|---|
| 981 |
# SortCriteria = '+dc:title') |
|---|
| 982 |
|
|---|
| 983 |
f.upnp_ImportResource(SourceURI='http://spiegel.de',DestinationURI='ttt') |
|---|
| 984 |
|
|---|
| 985 |
reactor.run() |
|---|