22
22
from coherence.upnp.core.DIDLLite import classChooser, Container, Resource
23
23
from coherence.upnp.core.DIDLLite import DIDLElement
24
from coherence.upnp.core.DIDLLite import simple_dlna_tags
24
25
from coherence.upnp.core.soap_service import errorCode
26
27
from coherence.upnp.core import utils
29
30
from coherence.extern.inotify import IN_CREATE, IN_DELETE, IN_MOVED_FROM, IN_MOVED_TO, IN_ISDIR
30
31
from coherence.extern.inotify import IN_CHANGED
33
from coherence.extern.xdg import xdg_content
34
from coherence.extern.simple_plugin import Plugin
36
from coherence import log
38
class FSItem(log.Loggable):
37
from coherence.backend import BackendItem, BackendStore
39
class FSItem(BackendItem):
39
40
logCategory = 'fs_item'
41
def __init__(self, id, parent, path, mimetype, urlbase, UPnPClass,update=False):
42
def __init__(self, object_id, parent, path, mimetype, urlbase, UPnPClass,update=False):
43
44
self.parent = parent
45
46
parent.add_child(self,update=update)
47
48
self.location = unicode(path)
49
50
if mimetype == 'item' and path is None:
50
path = os.path.join(parent.get_path(),str(self.id))
51
path = os.path.join(parent.get_path(),unicode(self.id))
51
52
self.location = FilePath(unicode(path))
52
53
self.mimetype = mimetype
53
54
if urlbase[-1] != '/':
61
62
parent_id = parent.get_id()
63
self.item = UPnPClass(id, parent_id, self.get_name())
64
self.item = UPnPClass(object_id, parent_id, self.get_name())
64
65
if isinstance(self.item, Container):
65
66
self.item.childCount = 0
66
67
self.child_count = 0
72
73
#self.item.searchable = True
73
74
#self.item.searchClass = 'object'
74
self.check_for_cover_art()
75
if hasattr(self, 'cover'):
76
_,ext = os.path.splitext(self.cover)
77
""" add the cover image extension to help clients not reacting on
79
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext))
75
if(isinstance(self.location,FilePath) and
76
self.location.isdir() == True):
77
self.check_for_cover_art()
78
if hasattr(self, 'cover'):
79
_,ext = os.path.splitext(self.cover)
80
""" add the cover image extension to help clients not reacting on
82
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext))
81
if hasattr(parent, 'cover'):
82
_,ext = os.path.splitext(parent.cover)
83
""" add the cover image extension to help clients not reacting on
85
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext))
84
if self.mimetype.startswith('audio/'):
85
if hasattr(parent, 'cover'):
86
_,ext = os.path.splitext(parent.cover)
87
""" add the cover image extension to help clients not reacting on
89
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext))
89
91
_,host_port,_,_,_ = urlsplit(urlbase)
90
92
if host_port.find(':') != -1:
111
113
self.item.res.append(res)
116
""" if this item is an image and we want to add a thumbnail for it
117
we have to follow these rules:
119
create a new Resource object, at least a 'http-get'
120
and maybe an 'internal' one too
122
for an JPG this looks like that
124
res = Resource(url_for_thumbnail,
125
'http-get:*:image/jpg:%s'% ';'.join(simple_dlna_tags+('DLNA.ORG_PN=JPEG_TN',)))
126
res.size = size_of_thumbnail
127
self.item.res.append(res)
129
and for a PNG the Resource creation is like that
131
res = Resource(url_for_thumbnail,
132
'http-get:*:image/png:%s'% ';'.join(simple_dlna_tags+('DLNA.ORG_PN=PNG_TN',)))
134
if not hasattr(self.item, 'attachments'):
135
self.item.attachments = {}
136
self.item.attachments[key] = utils.StaticFile(filename_of_thumbnail)
139
if self.mimetype in ('image/jpeg', 'image/png'):
140
path = self.get_path()
141
thumbnail = os.path.join(os.path.dirname(path),'.thumbs',os.path.basename(path))
142
if os.path.exists(thumbnail):
143
mimetype,_ = mimetypes.guess_type(thumbnail, strict=False)
144
if mimetype in ('image/jpeg','image/png'):
145
if mimetype == 'image/jpeg':
146
dlna_pn = 'DLNA.ORG_PN=JPEG_TN'
148
dlna_pn = 'DLNA.ORG_PN=PNG_TN'
150
hash_from_path = str(id(thumbnail))
151
new_res = Resource(self.url+'?attachment='+hash_from_path,
152
'http-get:*:%s:%s' % (mimetype, ';'.join(simple_dlna_tags+(dlna_pn,))))
153
new_res.size = os.path.getsize(thumbnail)
154
self.item.res.append(new_res)
155
if not hasattr(self.item, 'attachments'):
156
self.item.attachments = {}
157
self.item.attachments[hash_from_path] = utils.StaticFile(thumbnail)
114
161
# FIXME: getmtime is deprecated in Twisted 2.6
115
162
self.item.date = datetime.fromtimestamp(self.location.getmtime())
135
182
self.item.albumArtURI = ''.join((urlbase,str(self.id),'?cover',ext))
139
184
_,host_port,_,_,_ = urlsplit(urlbase)
140
185
if host_port.find(':') != -1:
141
186
host,port = tuple(host_port.split(':'))
173
218
or png if the jpg search fails, and take the first one
174
219
that comes around
176
jpgs = [i.path for i in self.location.children() if i.splitext()[1] in ('.jpg', '.JPG')]
180
pngs = [i.path for i in self.location.children() if i.splitext()[1] in ('.png', '.PNG')]
222
jpgs = [i.path for i in self.location.children() if i.splitext()[1] in ('.jpg', '.JPG')]
183
225
except IndexError:
226
pngs = [i.path for i in self.location.children() if i.splitext()[1] in ('.png', '.PNG')]
231
except UnicodeDecodeError:
232
self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", self.location.path)
186
234
def remove(self):
187
235
#print "FSItem remove", self.id, self.get_name(), self.parent
272
320
def __repr__(self):
273
321
return 'id: ' + str(self.id) + ' @ ' + self.location.basename()
275
class FSStore(log.Loggable,Plugin):
323
class FSStore(BackendStore):
276
324
logCategory = 'fs_store'
278
326
implements = ['MediaServer']
280
wmc_mapping = {'4':1000, '8':1000}
282
328
def __init__(self, server, **kwargs):
329
BackendStore.__init__(self)
283
330
self.next_id = 1000
284
331
self.name = kwargs.get('name','my media')
285
self.content = kwargs.get('content','tests/content')
332
self.content = kwargs.get('content',None)
333
if self.content != None:
334
if isinstance(self.content,basestring):
335
self.content = [self.content]
337
for a in self.content:
341
self.content = xdg_content()
342
if self.content == None:
343
self.content = 'tests/content'
286
344
if not isinstance( self.content, list):
287
345
self.content = [self.content]
288
346
self.urlbase = kwargs.get('urlbase','')
307
365
parent = self.store[id] = FSItem( id, parent, 'media', 'root', self.urlbase, UPnPClass, update=True)
309
367
for path in self.content:
368
if isinstance(path,(list,tuple)):
310
370
if self.ignore_file_pattern.match(path):
312
372
self.walk(path, parent, self.ignore_file_pattern)
351
411
containers.append(parent)
352
412
while len(containers)>0:
353
413
container = containers.pop()
354
for child in container.location.children():
355
if ignore_file_pattern.match(child.basename()) != None:
357
new_container = self.append(child.path,container)
358
if new_container != None:
359
containers.append(new_container)
415
for child in container.location.children():
416
if ignore_file_pattern.match(child.basename()) != None:
418
new_container = self.append(child.path,container)
419
if new_container != None:
420
containers.append(new_container)
421
except UnicodeDecodeError:
422
self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", container.get_path())
361
424
def create(self, mimetype, path, parent):
362
425
UPnPClass = classChooser(mimetype)
385
448
def append(self,path,parent):
386
449
#print "append", path
387
mimetype,_ = mimetypes.guess_type(path, strict=False)
389
if os.path.isdir(path) or os.path.islink(path):
390
mimetype = 'directory'
394
id = self.create(mimetype,path,parent)
396
if mimetype == 'directory':
397
if self.inotify is not None:
398
mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED
399
self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id))
400
return self.store[id]
451
mimetype,_ = mimetypes.guess_type(path, strict=False)
453
if os.path.isdir(path) or os.path.islink(path):
454
mimetype = 'directory'
458
id = self.create(mimetype,path,parent)
460
if mimetype == 'directory':
461
if self.inotify is not None:
462
mask = IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CHANGED
463
self.inotify.watch(path, mask=mask, auto_add=False, callbacks=(self.notify,id))
464
return self.store[id]
466
""" seems we have some permissions issues along the content path """
467
self.warning("path %r isn't accessible, error %r", path, msg)
526
593
return {'TransferID': transfer_id}
528
595
def upnp_CreateObject(self, *args, **kwargs):
529
ContainerID = int(kwargs['ContainerID'])
596
if kwargs['ContainerID'] == 'DLNA.ORG_AnyContainer':
597
""" for the moment """
598
return failure.Failure(errorCode(712))
600
ContainerID = kwargs['ContainerID']
530
601
Elements = kwargs['Elements']
532
603
parent_item = self.get_by_id(ContainerID)