1
1
__license__ = 'GPL v3'
2
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
4
This module contains the logic for dealing with XML book lists found
4
This module contains the logic for dealing with XML book lists found
7
7
import xml.dom.minidom as dom
8
8
from base64 import b64decode as decode
26
26
class book_metadata_field(object):
27
27
""" Represents metadata stored as an attribute """
28
def __init__(self, attr, formatter=None, setter=None):
28
def __init__(self, attr, formatter=None, setter=None):
30
30
self.formatter = formatter
31
31
self.setter = setter
33
33
def __get__(self, obj, typ=None):
34
34
""" Return a string. String may be empty if self.attr is absent """
35
35
return self.formatter(obj.elem.getAttribute(self.attr)) if \
36
36
self.formatter else obj.elem.getAttribute(self.attr).strip()
38
38
def __set__(self, obj, val):
39
39
""" Set the attribute """
40
40
val = self.setter(val) if self.setter else val
45
45
class Book(object):
46
46
""" Provides a view onto the XML element that represents a book """
48
48
title = book_metadata_field("title")
49
49
authors = book_metadata_field("author", \
50
50
formatter=lambda x: x if x and x.strip() else "Unknown")
55
55
size = book_metadata_field("size", formatter=int)
56
56
# When setting this attribute you must use an epoch
57
57
datetime = book_metadata_field("date", formatter=strptime, setter=strftime)
59
def title_sorter(self):
60
60
doc = '''String to sort the title. If absent, title is returned'''
62
62
src = self.elem.getAttribute('titleSorter').strip()
66
66
def fset(self, val):
67
67
self.elem.setAttribute('titleSorter', sortable_title(unicode(val)))
68
68
return property(doc=doc, fget=fget, fset=fset)
74
The thumbnail. Should be a height 68 image.
74
The thumbnail. Should be a height 68 image.
75
75
Setting is not supported.
85
85
for node in th.childNodes:
86
if node.nodeType == node.TEXT_NODE:
86
if node.nodeType == node.TEXT_NODE:
89
89
return property(fget=fget, doc=doc)
93
93
doc = """ Absolute path to book on device. Setting not supported. """
95
95
return self.root + self.rpath
96
96
return property(fget=fget, doc=doc)
100
100
doc = '''The database id in the application database that this file corresponds to'''
102
102
match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0])
104
104
return int(match.group(1))
105
105
return property(fget=fget, doc=doc)
107
107
def __init__(self, node, tags=[], prefix="", root="/Data/media/"):
109
109
self.prefix = prefix
113
113
def __str__(self):
114
114
""" Return a utf-8 encoded string with title author and path information """
115
115
return self.title.encode('utf-8') + " by " + \
116
116
self.authors.encode('utf-8') + " at " + self.path.encode('utf-8')
119
def fix_ids(media, cache):
119
def fix_ids(media, cache, *args):
121
121
Adjust ids in cache to correspond with media.
131
131
child.setAttribute("id", str(cid))
133
133
media.set_next_id(str(cid))
136
136
class BookList(_BookList):
138
A list of L{Book}s. Created from an XML file. Can write list
138
A list of L{Book}s. Created from an XML file. Can write list
141
141
__getslice__ = None
142
142
__setslice__ = None
144
144
def __init__(self, root="/Data/media/", sfile=None):
145
145
_BookList.__init__(self)
146
146
self.tag_order = {}
164
164
self.prefix = 'xs1:'
165
165
self.root = records[0]
168
168
for book in self.document.getElementsByTagName(self.prefix + "text"):
169
169
id = book.getAttribute('id')
170
170
pl = [i.getAttribute('title') for i in self.get_playlists(id)]
171
171
self.append(Book(book, root=root, prefix=self.prefix, tags=pl))
173
173
def supports_tags(self):
174
174
return bool(self.prefix)
176
176
def playlists(self):
177
177
return self.root.getElementsByTagName(self.prefix+'playlist')
179
def playlist_items(self):
179
def playlist_items(self):
181
181
for pl in self.playlists():
182
182
plitems.extend(pl.getElementsByTagName(self.prefix+'item'))
185
185
def purge_corrupted_files(self):
186
186
if not self.root:
193
193
c.parentNode.removeChild(c)
197
197
def purge_empty_playlists(self):
198
198
''' Remove all playlist entries that have no children. '''
199
199
for pl in self.playlists():
200
200
if not pl.getElementsByTagName(self.prefix + 'item'):
201
201
pl.parentNode.removeChild(pl)
204
204
def _delete_book(self, node):
205
205
nid = node.getAttribute('id')
206
206
node.parentNode.removeChild(node)
208
208
self.remove_from_playlists(nid)
211
211
def delete_book(self, cid):
213
213
Remove DOM node corresponding to book with C{id == cid}.
214
214
Also remove book from any collections it is part of.
216
216
for book in self:
217
217
if str(book.id) == str(cid):
218
218
self.remove(book)
219
self._delete_book(book.elem)
219
self._delete_book(book.elem)
222
222
def remove_book(self, path):
224
224
Remove DOM node corresponding to book with C{path == path}.
227
227
for book in self:
228
228
if path.endswith(book.rpath):
229
229
self.remove(book)
230
self._delete_book(book.elem)
230
self._delete_book(book.elem)
233
233
def next_id(self):
234
234
return self.document.documentElement.getAttribute('nextID')
236
236
def set_next_id(self, id):
237
237
self.document.documentElement.setAttribute('nextID', str(id))
239
239
def max_id(self):
241
241
for child in self.root.childNodes:
243
243
nid = int(child.getAttribute('id'))
248
248
def book_by_path(self, path):
249
249
for child in self.root.childNodes:
250
250
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("path"):
251
251
if path == child.getAttribute('path'):
255
255
def add_book(self, info, name, size, ctime):
256
256
""" Add a node into DOM tree representing a book """
257
257
book = self.book_by_path(name)
262
262
cid = self.max_id()+1
263
263
sourceid = str(self[0].sourceid) if len(self) else "1"
265
"title" : info["title"],
265
"title" : info["title"],
266
266
'titleSorter' : sortable_title(info['title']),
267
267
"author" : info["authors"] if info['authors'] else 'Unknown', \
268
268
"page":"0", "part":"0", "scale":"0", \
269
269
"sourceid":sourceid, "id":str(cid), "date":"", \
270
270
"mime":mime, "path":name, "size":str(size)
272
272
for attr in attrs.keys():
273
273
node.setAttributeNode(self.document.createAttribute(attr))
274
node.setAttribute(attr, attrs[attr])
274
node.setAttribute(attr, attrs[attr])
276
w, h, data = info["cover"]
276
w, h, data = info["cover"]
277
277
except TypeError:
278
278
w, h, data = None, None, None
281
th = self.document.createElement(self.prefix + "thumbnail")
281
th = self.document.createElement(self.prefix + "thumbnail")
282
282
th.setAttribute("width", str(w))
283
283
th.setAttribute("height", str(h))
284
284
jpeg = self.document.createElement(self.prefix + "jpeg")
294
294
if info.has_key('tag order'):
295
295
self.tag_order.update(info['tag order'])
296
296
self.set_playlists(book.id, info['tags'])
299
299
def playlist_by_title(self, title):
300
300
for pl in self.playlists():
301
301
if pl.getAttribute('title').lower() == title.lower():
304
304
def add_playlist(self, title):
305
cid = self.max_id()+1
305
cid = self.max_id()+1
306
306
pl = self.document.createElement(self.prefix+'playlist')
307
307
pl.setAttribute('sourceid', '0')
308
308
pl.setAttribute('id', str(cid))
316
316
except AttributeError:
321
321
def remove_from_playlists(self, id):
322
322
for pli in self.playlist_items():
323
323
if pli.getAttribute('id') == str(id):
324
324
pli.parentNode.removeChild(pli)
327
327
def set_tags(self, book, tags):
329
329
self.set_playlists(book.id, tags)
331
331
def set_playlists(self, id, collections):
332
332
self.remove_from_playlists(id)
333
333
for collection in set(collections):
337
337
item = self.document.createElement(self.prefix+'item')
338
338
item.setAttribute('id', str(id))
339
339
coll.appendChild(item)
341
341
def get_playlists(self, id):
343
343
for pl in self.playlists():
350
350
def book_by_id(self, id):
351
351
for book in self:
352
352
if str(book.id) == str(id):
355
355
def reorder_playlists(self):
356
356
for title in self.tag_order.keys():
357
357
pl = self.playlist_by_title(title)
365
365
pl_book_ids = [i for i in pl_book_ids if i is not None]
366
366
ordered_ids = [i for i in self.tag_order[title] if i in pl_book_ids]
368
368
if len(ordered_ids) < len(pl.childNodes):
370
370
children = [i for i in pl.childNodes if hasattr(i, 'getAttribute')]
374
374
for id in ordered_ids:
375
375
item = self.document.createElement(self.prefix+'item')
376
376
item.setAttribute('id', str(map[id]))
379
379
def write(self, stream):
380
380
""" Write XML representation of DOM tree to C{stream} """
381
381
stream.write(self.document.toxml('utf-8'))