~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/devices/prs500/books.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mto: This revision was merged to the branch mainline in revision 13.
  • Revision ID: james.westby@ubuntu.com-20090730124941-kviipg9ypwgppulc
Tags: upstream-0.6.3+dfsg
ImportĀ upstreamĀ versionĀ 0.6.3+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
__license__   = 'GPL v3'
2
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
3
 
""" 
4
 
This module contains the logic for dealing with XML book lists found 
5
 
in the reader cache. 
 
3
"""
 
4
This module contains the logic for dealing with XML book lists found
 
5
in the reader cache.
6
6
"""
7
7
import xml.dom.minidom as dom
8
8
from base64 import b64decode as decode
25
25
 
26
26
class book_metadata_field(object):
27
27
    """ Represents metadata stored as an attribute """
28
 
    def __init__(self, attr, formatter=None, setter=None): 
29
 
        self.attr = attr 
 
28
    def __init__(self, attr, formatter=None, setter=None):
 
29
        self.attr = attr
30
30
        self.formatter = formatter
31
31
        self.setter = setter
32
 
    
 
32
 
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()
37
 
    
 
37
 
38
38
    def __set__(self, obj, val):
39
39
        """ Set the attribute """
40
40
        val = self.setter(val) if self.setter else val
44
44
 
45
45
class Book(object):
46
46
    """ Provides a view onto the XML element that represents a book """
47
 
    
 
47
 
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)
58
 
    @apply
59
 
    def title_sorter():
 
58
    @dynamic_property
 
59
    def title_sorter(self):
60
60
        doc = '''String to sort the title. If absent, title is returned'''
61
61
        def fget(self):
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)
69
 
    
70
 
    @apply
71
 
    def thumbnail():
 
69
 
 
70
    @dynamic_property
 
71
    def thumbnail(self):
72
72
        doc = \
73
 
        """ 
74
 
        The thumbnail. Should be a height 68 image. 
 
73
        """
 
74
        The thumbnail. Should be a height 68 image.
75
75
        Setting is not supported.
76
76
        """
77
77
        def fget(self):
83
83
                        break
84
84
                rc = ""
85
85
                for node in th.childNodes:
86
 
                    if node.nodeType == node.TEXT_NODE: 
 
86
                    if node.nodeType == node.TEXT_NODE:
87
87
                        rc += node.data
88
88
                return decode(rc)
89
89
        return property(fget=fget, doc=doc)
90
 
    
91
 
    @apply
92
 
    def path():
 
90
 
 
91
    @dynamic_property
 
92
    def path(self):
93
93
        doc = """ Absolute path to book on device. Setting not supported. """
94
 
        def fget(self):  
 
94
        def fget(self):
95
95
            return self.root + self.rpath
96
96
        return property(fget=fget, doc=doc)
97
 
    
98
 
    @apply
99
 
    def db_id():
 
97
 
 
98
    @dynamic_property
 
99
    def db_id(self):
100
100
        doc = '''The database id in the application database that this file corresponds to'''
101
101
        def fget(self):
102
102
            match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0])
103
103
            if match:
104
104
                return int(match.group(1))
105
105
        return property(fget=fget, doc=doc)
106
 
    
 
106
 
107
107
    def __init__(self, node, tags=[], prefix="", root="/Data/media/"):
108
108
        self.elem   = node
109
109
        self.prefix = prefix
110
110
        self.root   = root
111
111
        self.tags   = tags
112
 
    
 
112
 
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')
117
117
 
118
118
 
119
 
def fix_ids(media, cache):
 
119
def fix_ids(media, cache, *args):
120
120
    '''
121
121
    Adjust ids in cache to correspond with media.
122
122
    '''
131
131
                child.setAttribute("id", str(cid))
132
132
                cid += 1
133
133
        media.set_next_id(str(cid))
134
 
    
135
 
    
 
134
 
 
135
 
136
136
class BookList(_BookList):
137
 
    """ 
138
 
    A list of L{Book}s. Created from an XML file. Can write list 
 
137
    """
 
138
    A list of L{Book}s. Created from an XML file. Can write list
139
139
    to an XML file.
140
140
    """
141
141
    __getslice__ = None
142
142
    __setslice__ = None
143
 
    
 
143
 
144
144
    def __init__(self, root="/Data/media/", sfile=None):
145
145
        _BookList.__init__(self)
146
146
        self.tag_order = {}
163
163
            if records:
164
164
                self.prefix = 'xs1:'
165
165
                self.root = records[0]
166
 
            self.proot = root            
167
 
            
 
166
            self.proot = root
 
167
 
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))
172
 
                
 
172
 
173
173
    def supports_tags(self):
174
174
        return bool(self.prefix)
175
 
    
 
175
 
176
176
    def playlists(self):
177
177
        return self.root.getElementsByTagName(self.prefix+'playlist')
178
 
    
179
 
    def playlist_items(self):        
 
178
 
 
179
    def playlist_items(self):
180
180
        plitems = []
181
181
        for pl in self.playlists():
182
182
            plitems.extend(pl.getElementsByTagName(self.prefix+'item'))
183
183
        return plitems
184
 
        
 
184
 
185
185
    def purge_corrupted_files(self):
186
186
        if not self.root:
187
187
            return []
193
193
            c.parentNode.removeChild(c)
194
194
            c.unlink()
195
195
        return paths
196
 
    
 
196
 
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)
202
202
                pl.unlink()
203
 
    
 
203
 
204
204
    def _delete_book(self, node):
205
205
        nid = node.getAttribute('id')
206
206
        node.parentNode.removeChild(node)
207
207
        node.unlink()
208
208
        self.remove_from_playlists(nid)
209
 
        
210
 
    
 
209
 
 
210
 
211
211
    def delete_book(self, cid):
212
 
        ''' 
 
212
        '''
213
213
        Remove DOM node corresponding to book with C{id == cid}.
214
214
        Also remove book from any collections it is part of.
215
215
        '''
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)
220
220
                break
221
 
        
 
221
 
222
222
    def remove_book(self, path):
223
223
        '''
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)
231
231
                break
232
 
    
 
232
 
233
233
    def next_id(self):
234
234
        return self.document.documentElement.getAttribute('nextID')
235
 
    
 
235
 
236
236
    def set_next_id(self, id):
237
237
        self.document.documentElement.setAttribute('nextID', str(id))
238
 
        
 
238
 
239
239
    def max_id(self):
240
240
        max = 0
241
241
        for child in self.root.childNodes:
243
243
                nid = int(child.getAttribute('id'))
244
244
                if nid > max:
245
245
                    max = nid
246
 
        return max 
247
 
    
 
246
        return max
 
247
 
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'):
252
252
                    return child
253
253
        return None
254
 
    
 
254
 
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"
264
264
        attrs = {
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)
271
 
                 } 
 
271
                 }
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])
275
275
        try:
276
 
            w, h, data = info["cover"] 
 
276
            w, h, data = info["cover"]
277
277
        except TypeError:
278
278
            w, h, data = None, None, None
279
 
        
 
279
 
280
280
        if data:
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'])
297
 
                
298
 
    
 
297
 
 
298
 
299
299
    def playlist_by_title(self, title):
300
300
        for pl in self.playlists():
301
301
            if pl.getAttribute('title').lower() == title.lower():
302
302
                return pl
303
 
    
 
303
 
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:
317
317
                continue
318
318
        return pl
319
 
    
320
 
    
 
319
 
 
320
 
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)
325
325
                pli.unlink()
326
 
    
 
326
 
327
327
    def set_tags(self, book, tags):
328
328
        book.tags = tags
329
329
        self.set_playlists(book.id, tags)
330
 
    
 
330
 
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)
340
 
                    
 
340
 
341
341
    def get_playlists(self, id):
342
342
        ans = []
343
343
        for pl in self.playlists():
346
346
                    ans.append(pl)
347
347
                    continue
348
348
        return ans
349
 
        
 
349
 
350
350
    def book_by_id(self, id):
351
351
        for book in self:
352
352
            if str(book.id) == str(id):
353
353
                return book
354
 
    
 
354
 
355
355
    def reorder_playlists(self):
356
356
        for title in self.tag_order.keys():
357
357
            pl = self.playlist_by_title(title)
364
364
                map[i] = j
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]
367
 
            
 
367
 
368
368
            if len(ordered_ids) < len(pl.childNodes):
369
369
                continue
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]))
377
 
                pl.appendChild(item)    
378
 
    
 
377
                pl.appendChild(item)
 
378
 
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'))