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

« back to all changes in this revision

Viewing changes to src/calibre/devices/prs505/driver.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
__license__   = 'GPL v3'
2
 
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
 
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net> ' \
 
3
                '2009, John Schember <john at nachtimwald.com>'
3
4
'''
4
5
Device driver for the SONY PRS-505
5
6
'''
6
 
import sys, os, shutil, time, subprocess, re
 
7
import os, re, time
7
8
from itertools import cycle
8
9
 
9
 
from calibre.devices.interface import Device
 
10
from calibre.devices.usbms.cli import CLI
 
11
from calibre.devices.usbms.device import Device
10
12
from calibre.devices.errors import DeviceError, FreeSpaceError
11
13
from calibre.devices.prs505.books import BookList, fix_ids
12
 
from calibre import iswindows, islinux, isosx, __appname__
13
 
from calibre.devices.errors import PathError
14
 
 
15
 
class File(object):
16
 
    def __init__(self, path):
17
 
        stats = os.stat(path)
18
 
        self.is_dir = os.path.isdir(path)
19
 
        self.is_readonly = not os.access(path, os.W_OK)
20
 
        self.ctime = stats.st_ctime
21
 
        self.wtime = stats.st_mtime
22
 
        self.size  = stats.st_size
23
 
        if path.endswith(os.sep):
24
 
            path = path[:-1]
25
 
        self.path = path
26
 
        self.name = os.path.basename(path)
27
 
 
28
 
 
29
 
class PRS505(Device):
30
 
    VENDOR_ID    = 0x054c   #: SONY Vendor Id
31
 
    PRODUCT_ID   = 0x031e   #: Product Id for the PRS-505
32
 
    BCD          = [0x229]  #: Needed to disambiguate 505 and 700 on linux
33
 
    PRODUCT_NAME = 'PRS-505'
34
 
    VENDOR_NAME  = 'SONY'
 
14
from calibre import __appname__
 
15
 
 
16
class PRS505(CLI, Device):
 
17
 
 
18
    name           = 'PRS-505 Device Interface'
 
19
    description    = _('Communicate with the Sony PRS-505 eBook reader.')
 
20
    author         = _('Kovid Goyal and John Schember')
 
21
    supported_platforms = ['windows', 'osx', 'linux']
 
22
 
35
23
    FORMATS      = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
36
24
 
 
25
    VENDOR_ID    = [0x054c]   #: SONY Vendor Id
 
26
    PRODUCT_ID   = [0x031e]   #: Product Id for the PRS-505
 
27
    BCD          = [0x229, 0x1000]  #: Needed to disambiguate 505 and 700 on linux
 
28
 
 
29
    VENDOR_NAME        = 'SONY'
 
30
    WINDOWS_MAIN_MEM   = 'PRS-505'
 
31
    WINDOWS_CARD_A_MEM = re.compile(r'PRS-505/\S+:MS')
 
32
    WINDOWS_CARD_B_MEM = re.compile(r'PRS-505/\S+:SD')
 
33
 
 
34
    OSX_MAIN_MEM   = re.compile(r'Sony PRS-505/[^:]+ Media')
 
35
    OSX_CARD_A_MEM = re.compile(r'Sony PRS-505/[^:]+:MS Media')
 
36
    OSX_CARD_B_MEM = re.compile(r'Sony PRS-505/[^:]+:SD Media')
 
37
 
 
38
    MAIN_MEMORY_VOLUME_LABEL  = 'Sony Reader Main Memory'
 
39
    STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
 
40
 
37
41
    MEDIA_XML    = 'database/cache/media.xml'
38
42
    CACHE_XML    = 'Sony Reader/database/cache.xml'
39
43
 
40
 
    MAIN_MEMORY_VOLUME_LABEL  = 'Sony Reader Main Memory'
41
 
    STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
42
 
 
43
 
    OSX_NAME                  = 'Sony PRS-505'
44
 
 
45
44
    CARD_PATH_PREFIX          = __appname__
46
45
 
47
 
    FDI_TEMPLATE = \
48
 
'''
49
 
  <device>
50
 
      <match key="info.category" string="volume">
51
 
          <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
52
 
              <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
53
 
                  <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">
54
 
                      <match key="volume.is_partition" bool="false">
55
 
                          <merge key="volume.label" type="string">%(main_memory)s</merge>
56
 
                          <merge key="%(app)s.mainvolume" type="string">%(deviceclass)s</merge>
57
 
                      </match>
58
 
                  </match>
59
 
              </match>
60
 
          </match>
61
 
      </match>
62
 
  </device>
63
 
  <device>
64
 
      <match key="info.category" string="volume">
65
 
          <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
66
 
              <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
67
 
                  <match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">
68
 
                      <match key="volume.is_partition" bool="true">
69
 
                          <merge key="volume.label" type="string">%(storage_card)s</merge>
70
 
                          <merge key="%(app)s.cardvolume" type="string">%(deviceclass)s</merge>
71
 
                      </match>
72
 
                  </match>
73
 
              </match>
74
 
          </match>
75
 
      </match>
76
 
  </device>
77
 
'''.replace('%(app)s', __appname__)
78
 
 
79
 
 
80
 
    def __init__(self, log_packets=False):
81
 
        self._main_prefix = self._card_prefix = None
82
 
 
83
 
    @classmethod
84
 
    def get_fdi(cls):
85
 
        return cls.FDI_TEMPLATE%dict(
86
 
                                     deviceclass=cls.__name__,
87
 
                                     vendor_id=hex(cls.VENDOR_ID),
88
 
                                     product_id=hex(cls.PRODUCT_ID),
89
 
                                     bcd=hex(cls.BCD[0]),
90
 
                                     main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
91
 
                                     storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
92
 
                                     )
93
 
 
94
 
    @classmethod
95
 
    def is_device(cls, device_id):
96
 
        device_id = device_id.upper()
97
 
        if 'VEN_'+cls.VENDOR_NAME in device_id and \
98
 
               'PROD_'+cls.PRODUCT_NAME in device_id:
99
 
            return True
100
 
        vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:]
101
 
        if len(vid) < 4: vid = '0'+vid
102
 
        if len(pid) < 4: pid = '0'+pid
103
 
        if 'VID_'+vid in device_id and \
104
 
               'PID_'+pid in device_id:
105
 
            return True
106
 
        return False
107
 
 
108
 
    @classmethod
109
 
    def get_osx_mountpoints(cls, raw=None):
110
 
        if raw is None:
111
 
            ioreg = '/usr/sbin/ioreg'
112
 
            if not os.access(ioreg, os.X_OK):
113
 
                ioreg = 'ioreg'
114
 
            raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
115
 
                                   stdout=subprocess.PIPE).communicate()[0]
116
 
        lines = raw.splitlines()
117
 
        names = {}
118
 
        for i, line in enumerate(lines):
119
 
            if line.strip().endswith('<class IOMedia>') and cls.OSX_NAME in line:
120
 
                loc = 'stick' if ':MS' in line else 'card' if ':SD' in line else 'main'
121
 
                for line in lines[i+1:]:
122
 
                    line = line.strip()
123
 
                    if line.endswith('}'):
124
 
                        break
125
 
                    match = re.search(r'"BSD Name"\s+=\s+"(.*?)"', line)
126
 
                    if match is not None:
127
 
                        names[loc] = match.group(1)
128
 
                        break
129
 
            if len(names.keys()) == 3:
130
 
                break
131
 
        return names
132
 
 
133
 
 
134
 
    def open_osx(self):
135
 
        mount = subprocess.Popen('mount', shell=True,
136
 
                                 stdout=subprocess.PIPE).stdout.read()
137
 
        names = self.get_osx_mountpoints()
138
 
        dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
139
 
        if 'main' not in names.keys():
140
 
            raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
141
 
        main_pat = dev_pat%names['main']
142
 
        self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
143
 
        card_pat = names['stick'] if 'stick' in names.keys() else names['card'] if 'card' in names.keys() else None
144
 
        if card_pat is not None:
145
 
            card_pat = dev_pat%card_pat
146
 
            self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
147
 
 
148
 
 
149
 
    def open_windows(self):
150
 
        time.sleep(6)
151
 
        drives = []
152
 
        wmi = __import__('wmi', globals(), locals(), [], -1)
153
 
        c = wmi.WMI(find_classes=False)
154
 
        for drive in c.Win32_DiskDrive():
155
 
            if self.__class__.is_device(str(drive.PNPDeviceID)):
156
 
                if drive.Partitions == 0:
157
 
                    continue
158
 
                try:
159
 
                    partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
160
 
                    logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
161
 
                    prefix = logical_disk.DeviceID+os.sep
162
 
                    drives.append((drive.Index, prefix))
163
 
                except IndexError:
164
 
                    continue
165
 
 
166
 
 
167
 
        if not drives:
168
 
            raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
169
 
 
170
 
        drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
171
 
        self._main_prefix = drives[0][1]
172
 
        if len(drives) > 1:
173
 
            self._card_prefix = drives[1][1]
174
 
 
175
 
 
176
 
    def open_linux(self):
177
 
        import dbus
178
 
        bus = dbus.SystemBus()
179
 
        hm  = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
180
 
 
181
 
        def conditional_mount(dev, main_mem=True):
182
 
            mmo = bus.get_object("org.freedesktop.Hal", dev)
183
 
            label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
184
 
            is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device')
185
 
            mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device')
186
 
            fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
187
 
            if is_mounted:
188
 
                return str(mount_point)
189
 
            mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
190
 
                          dbus_interface='org.freedesktop.Hal.Device.Volume')
191
 
            return os.path.normpath('/media/'+label)+'/'
192
 
 
193
 
 
194
 
        mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
195
 
        if not mm:
196
 
            raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
197
 
        self._main_prefix = None
198
 
        for dev in mm:
199
 
            try:
200
 
                self._main_prefix = conditional_mount(dev)+os.sep
201
 
                break
202
 
            except dbus.exceptions.DBusException:
203
 
                continue
204
 
 
205
 
 
206
 
        if not self._main_prefix:
207
 
            raise DeviceError('Could not open device for reading. Try a reboot.')
208
 
 
209
 
        self._card_prefix = None
210
 
        cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
211
 
        keys = []
212
 
        for card in cards:
213
 
            keys.append(int('UC_SD' in bus.get_object("org.freedesktop.Hal", card).GetPropertyString('info.parent', dbus_interface='org.freedesktop.Hal.Device')))
214
 
 
215
 
        cards = zip(cards, keys)
216
 
        cards.sort(cmp=lambda x, y: cmp(x[1], y[1]))
217
 
        cards = [i[0] for i in cards]
218
 
 
219
 
        for dev in cards:
220
 
            try:
221
 
                self._card_prefix = conditional_mount(dev, False)+os.sep
222
 
                break
223
 
            except:
224
 
                import traceback
225
 
                print traceback
226
 
                continue
227
 
 
 
46
    SUPPORTS_SUB_DIRS = True
 
47
    MUST_READ_METADATA = True
228
48
 
229
49
    def open(self):
230
 
        time.sleep(5)
231
 
        self._main_prefix = self._card_prefix = None
232
 
        if islinux:
233
 
            try:
234
 
                self.open_linux()
235
 
            except DeviceError:
236
 
                time.sleep(3)
237
 
                self.open_linux()
238
 
        if iswindows:
239
 
            try:
240
 
                self.open_windows()
241
 
            except DeviceError:
242
 
                time.sleep(3)
243
 
                self.open_windows()
244
 
        if isosx:
245
 
            try:
246
 
                self.open_osx()
247
 
            except DeviceError:
248
 
                time.sleep(3)
249
 
                self.open_osx()
250
 
        if self._card_prefix is not None:
251
 
            try:
252
 
                cachep = os.path.join(self._card_prefix, self.CACHE_XML)
 
50
        Device.open(self)
 
51
 
 
52
        def write_cache(prefix):
 
53
            try:
 
54
                cachep = os.path.join(prefix, self.CACHE_XML)
253
55
                if not os.path.exists(cachep):
254
56
                    try:
255
57
                        os.makedirs(os.path.dirname(cachep), mode=0777)
256
58
                    except:
257
59
                        time.sleep(5)
258
60
                        os.makedirs(os.path.dirname(cachep), mode=0777)
259
 
                    f = open(cachep, 'wb')
260
 
                    f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
261
 
<cache xmlns="http://www.kinoma.com/FskCache/1">
262
 
</cache>
263
 
'''.encode('utf8'))
264
 
                    f.close()
 
61
                    with open(cachep, 'wb') as f:
 
62
                        f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
 
63
                            <cache xmlns="http://www.kinoma.com/FskCache/1">
 
64
                            </cache>
 
65
                            '''.encode('utf8'))
 
66
                return True
265
67
            except:
266
 
                self._card_prefix = None
267
68
                import traceback
268
69
                traceback.print_exc()
 
70
            return False
269
71
 
270
 
    def set_progress_reporter(self, pr):
271
 
        self.report_progress = pr
 
72
        if self._card_a_prefix is not None:
 
73
            if not write_cache(self._card_a_prefix):
 
74
                self._card_a_prefix = None
 
75
        if self._card_b_prefix is not None:
 
76
            if not write_cache(self._card_b_prefix):
 
77
                self._card_b_prefix = None
272
78
 
273
79
    def get_device_information(self, end_session=True):
 
80
        self.report_progress(1.0, _('Get device information...'))
274
81
        return (self.__class__.__name__, '', '', '')
275
82
 
276
 
    def card_prefix(self, end_session=True):
277
 
        return self._card_prefix
278
 
 
279
 
    @classmethod
280
 
    def _windows_space(cls, prefix):
281
 
        if prefix is None:
282
 
            return 0, 0
283
 
        win32file = __import__('win32file', globals(), locals(), [], -1)
284
 
        try:
285
 
            sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
286
 
                win32file.GetDiskFreeSpace(prefix[:-1])
287
 
        except Exception, err:
288
 
            if getattr(err, 'args', [None])[0] == 21: # Disk not ready
289
 
                time.sleep(3)
290
 
                sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
291
 
                    win32file.GetDiskFreeSpace(prefix[:-1])
292
 
            else: raise
293
 
        mult = sectors_per_cluster * bytes_per_sector
294
 
        return total_clusters * mult, free_clusters * mult
295
 
 
296
 
    def total_space(self, end_session=True):
297
 
        msz = csz = 0
298
 
        if not iswindows:
299
 
            if self._main_prefix is not None:
300
 
                stats = os.statvfs(self._main_prefix)
301
 
                msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
302
 
            if self._card_prefix is not None:
303
 
                stats = os.statvfs(self._card_prefix)
304
 
                csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
305
 
        else:
306
 
            msz = self._windows_space(self._main_prefix)[0]
307
 
            csz = self._windows_space(self._card_prefix)[0]
308
 
 
309
 
        return (msz, 0, csz)
310
 
 
311
 
    def free_space(self, end_session=True):
312
 
        msz = csz = 0
313
 
        if not iswindows:
314
 
            if self._main_prefix is not None:
315
 
                stats = os.statvfs(self._main_prefix)
316
 
                msz = stats.f_frsize * stats.f_bavail
317
 
            if self._card_prefix is not None:
318
 
                stats = os.statvfs(self._card_prefix)
319
 
                csz = stats.f_frsize * stats.f_bavail
320
 
        else:
321
 
            msz = self._windows_space(self._main_prefix)[1]
322
 
            csz = self._windows_space(self._card_prefix)[1]
323
 
 
324
 
        return (msz, 0, csz)
325
 
 
326
 
    def books(self, oncard=False, end_session=True):
327
 
        if oncard and self._card_prefix is None:
328
 
            return []
 
83
 
 
84
    def books(self, oncard=None, end_session=True):
 
85
        if oncard == 'carda' and not self._card_a_prefix:
 
86
            self.report_progress(1.0, _('Getting list of books on device...'))
 
87
            return []
 
88
        elif oncard == 'cardb' and not self._card_b_prefix:
 
89
            self.report_progress(1.0, _('Getting list of books on device...'))
 
90
            return []
 
91
        elif oncard and oncard != 'carda' and oncard != 'cardb':
 
92
            self.report_progress(1.0, _('Getting list of books on device...'))
 
93
            return []
 
94
 
329
95
        db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML
330
 
        prefix = self._card_prefix if oncard else self._main_prefix
331
 
        bl = BookList(open(prefix + db, 'rb'), prefix)
 
96
        prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix
 
97
        bl = BookList(open(prefix + db, 'rb'), prefix, self.report_progress)
332
98
        paths = bl.purge_corrupted_files()
333
99
        for path in paths:
334
 
            path = os.path.join(self._card_prefix if oncard else self._main_prefix, path)
 
100
            path = os.path.join(prefix, path)
335
101
            if os.path.exists(path):
336
102
                os.unlink(path)
 
103
        self.report_progress(1.0, _('Getting list of books on device...'))
337
104
        return bl
338
105
 
339
 
    def munge_path(self, path):
340
 
        if path.startswith('/') and not (path.startswith(self._main_prefix) or \
341
 
            (self._card_prefix and path.startswith(self._card_prefix))):
342
 
            path = self._main_prefix + path[1:]
343
 
        elif path.startswith('card:'):
344
 
            path = path.replace('card:', self._card_prefix[:-1])
345
 
        return path
346
 
 
347
 
    def mkdir(self, path, end_session=True):
348
 
        """ Make directory """
349
 
        path = self.munge_path(path)
350
 
        os.mkdir(path)
351
 
 
352
 
    def list(self, path, recurse=False, end_session=True, munge=True):
353
 
        if munge:
354
 
            path = self.munge_path(path)
355
 
        if os.path.isfile(path):
356
 
            return [(os.path.dirname(path), [File(path)])]
357
 
        entries = [File(os.path.join(path, f)) for f in os.listdir(path)]
358
 
        dirs = [(path, entries)]
359
 
        for _file in entries:
360
 
            if recurse and _file.is_dir:
361
 
                dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
362
 
        return dirs
363
 
 
364
 
    def get_file(self, path, outfile, end_session=True):
365
 
        path = self.munge_path(path)
366
 
        src = open(path, 'rb')
367
 
        shutil.copyfileobj(src, outfile, 10*1024*1024)
368
 
 
369
 
    def put_file(self, infile, path, replace_file=False, end_session=True):
370
 
        path = self.munge_path(path)
371
 
        if os.path.isdir(path):
372
 
            path = os.path.join(path, infile.name)
373
 
        if not replace_file and os.path.exists(path):
374
 
            raise PathError('File already exists: '+path)
375
 
        dest = open(path, 'wb')
376
 
        shutil.copyfileobj(infile, dest, 10*1024*1024)
377
 
        dest.flush()
378
 
        dest.close()
379
 
 
380
 
    def rm(self, path, end_session=True):
381
 
        path = self.munge_path(path)
382
 
        os.unlink(path)
383
 
 
384
 
    def touch(self, path, end_session=True):
385
 
        path = self.munge_path(path)
386
 
        if not os.path.exists(path):
387
 
            open(path, 'w').close()
388
 
        if not os.path.isdir(path):
389
 
            os.utime(path, None)
390
 
 
391
 
    def upload_books(self, files, names, on_card=False, end_session=True,
 
106
    def upload_books(self, files, names, on_card=None, end_session=True,
392
107
                     metadata=None):
393
 
        if on_card and not self._card_prefix:
394
 
            raise ValueError(_('The reader has no storage card connected.'))
395
 
        path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
396
 
               else os.path.join(self._main_prefix, 'database', 'media', 'books')
 
108
        if on_card == 'carda' and not self._card_a_prefix:
 
109
            raise ValueError(_('The reader has no storage card in this slot.'))
 
110
        elif on_card == 'cardb' and not self._card_b_prefix:
 
111
            raise ValueError(_('The reader has no storage card in this slot.'))
 
112
        elif on_card and on_card not in ('carda', 'cardb'):
 
113
            raise DeviceError(_('The reader has no storage card in this slot.'))
 
114
 
 
115
        if on_card == 'carda':
 
116
            path = os.path.join(self._card_a_prefix, self.CARD_PATH_PREFIX)
 
117
        elif on_card == 'cardb':
 
118
            path = os.path.join(self._card_b_prefix, self.CARD_PATH_PREFIX)
 
119
        else:
 
120
            path = os.path.join(self._main_prefix, 'database', 'media', 'books')
397
121
 
398
122
        def get_size(obj):
399
123
            if hasattr(obj, 'seek'):
403
127
                return size
404
128
            return os.path.getsize(obj)
405
129
 
406
 
        sizes = map(get_size, files)
 
130
        sizes = [get_size(f) for f in files]
407
131
        size = sum(sizes)
408
 
        space = self.free_space()
409
 
        mspace = space[0]
410
 
        cspace = space[2]
411
 
        if on_card and size > cspace - 1024*1024:
412
 
            raise FreeSpaceError("There is insufficient free space "+\
413
 
                                          "on the storage card")
414
 
        if not on_card and size > mspace - 2*1024*1024:
415
 
            raise FreeSpaceError("There is insufficient free space " +\
416
 
                                         "in main memory")
 
132
 
 
133
        if not on_card and size > self.free_space()[0] - 2*1024*1024:
 
134
            raise FreeSpaceError(_("There is insufficient free space in main memory"))
 
135
        if on_card == 'carda' and size > self.free_space()[1] - 1024*1024:
 
136
            raise FreeSpaceError(_("There is insufficient free space on the storage card"))
 
137
        if on_card == 'cardb' and size > self.free_space()[2] - 1024*1024:
 
138
            raise FreeSpaceError(_("There is insufficient free space on the storage card"))
417
139
 
418
140
        paths, ctimes = [], []
419
141
 
420
142
        names = iter(names)
421
 
        for infile in files:
 
143
        metadata = iter(metadata)
 
144
        for i, infile in enumerate(files):
422
145
            close = False
423
146
            if not hasattr(infile, 'read'):
424
147
                infile, close = open(infile, 'rb'), True
425
148
            infile.seek(0)
426
 
            name = names.next()
427
 
            paths.append(os.path.join(path, name))
428
 
            if not os.path.exists(os.path.dirname(paths[-1])):
429
 
                os.makedirs(os.path.dirname(paths[-1]))
 
149
 
 
150
            mdata, fname = metadata.next(), names.next()
 
151
            filepath = self.create_upload_path(path, mdata, fname)
 
152
 
 
153
            paths.append(filepath)
 
154
 
430
155
            self.put_file(infile, paths[-1], replace_file=True)
 
156
 
431
157
            if close:
432
158
                infile.close()
433
159
            ctimes.append(os.path.getctime(paths[-1]))
 
160
 
 
161
            self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
 
162
 
 
163
        self.report_progress(1.0, _('Transferring books to device...'))
 
164
 
434
165
        return zip(paths, sizes, ctimes, cycle([on_card]))
435
166
 
436
 
    @classmethod
437
 
    def add_books_to_metadata(cls, locations, metadata, booklists):
 
167
    def add_books_to_metadata(self, locations, metadata, booklists):
 
168
        if not locations or not metadata:
 
169
            return
 
170
 
438
171
        metadata = iter(metadata)
439
172
        for location in locations:
440
173
            info = metadata.next()
441
174
            path = location[0]
442
 
            on_card = 1 if location[3] else 0
443
 
            name = path.rpartition(os.sep)[2]
444
 
            name = (cls.CARD_PATH_PREFIX+'/' if on_card else 'database/media/books/') + name
 
175
            blist = 2 if location[3] == 'cardb' else 1 if location[3] == 'carda' else 0
 
176
 
 
177
            if self._main_prefix and path.startswith(self._main_prefix):
 
178
                name = path.replace(self._main_prefix, '')
 
179
            elif self._card_a_prefix and path.startswith(self._card_a_prefix):
 
180
                name = path.replace(self._card_a_prefix, '')
 
181
            elif self._card_b_prefix and path.startswith(self._card_b_prefix):
 
182
                name = path.replace(self._card_b_prefix, '')
 
183
 
 
184
            name = name.replace('\\', '/')
445
185
            name = name.replace('//', '/')
446
 
            booklists[on_card].add_book(info, name, *location[1:-1])
 
186
            if name.startswith('/'):
 
187
                name = name[1:]
 
188
 
 
189
            booklists[blist].add_book(info, name, *location[1:-1])
447
190
        fix_ids(*booklists)
448
191
 
449
192
    def delete_books(self, paths, end_session=True):
450
 
        for path in paths:
 
193
        for i, path in enumerate(paths):
 
194
            self.report_progress((i+1) / float(len(paths)), _('Removing books from device...'))
451
195
            if os.path.exists(path):
452
196
                os.unlink(path)
 
197
                try:
 
198
                    os.removedirs(os.path.dirname(path))
 
199
                except:
 
200
                    pass
 
201
        self.report_progress(1.0, _('Removing books from device...'))
453
202
 
454
203
    @classmethod
455
204
    def remove_books_from_metadata(cls, paths, booklists):
466
215
        f = open(self._main_prefix + self.__class__.MEDIA_XML, 'wb')
467
216
        booklists[0].write(f)
468
217
        f.close()
469
 
        if self._card_prefix is not None and hasattr(booklists[1], 'write'):
470
 
            if not os.path.exists(self._card_prefix):
471
 
                os.makedirs(self._card_prefix)
472
 
            f = open(self._card_prefix + self.__class__.CACHE_XML, 'wb')
473
 
            booklists[1].write(f)
474
 
            f.close()
475
 
 
476
 
 
477
 
 
478
 
 
479
 
def main(args=sys.argv):
480
 
    return 0
481
 
 
482
 
if __name__ == '__main__':
483
 
    sys.exit(main())
 
218
 
 
219
        def write_card_prefix(prefix, listid):
 
220
            if prefix is not None and hasattr(booklists[listid], 'write'):
 
221
                if not os.path.exists(prefix):
 
222
                    os.makedirs(prefix)
 
223
                f = open(prefix + self.__class__.CACHE_XML, 'wb')
 
224
                booklists[listid].write(f)
 
225
                f.close()
 
226
        write_card_prefix(self._card_a_prefix, 1)
 
227
        write_card_prefix(self._card_b_prefix, 2)
 
228
 
 
229
        self.report_progress(1.0, _('Sending metadata to device...'))