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>'
4
5
Device driver for the SONY PRS-505
6
import sys, os, shutil, time, subprocess, re
7
8
from itertools import cycle
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
16
def __init__(self, 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):
26
self.name = os.path.basename(path)
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'
14
from calibre import __appname__
16
class PRS505(CLI, Device):
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']
35
23
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
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
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')
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')
38
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
39
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
37
41
MEDIA_XML = 'database/cache/media.xml'
38
42
CACHE_XML = 'Sony Reader/database/cache.xml'
40
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
41
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
43
OSX_NAME = 'Sony PRS-505'
45
44
CARD_PATH_PREFIX = __appname__
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>
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>
77
'''.replace('%(app)s', __appname__)
80
def __init__(self, log_packets=False):
81
self._main_prefix = self._card_prefix = None
85
return cls.FDI_TEMPLATE%dict(
86
deviceclass=cls.__name__,
87
vendor_id=hex(cls.VENDOR_ID),
88
product_id=hex(cls.PRODUCT_ID),
90
main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
91
storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
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:
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:
109
def get_osx_mountpoints(cls, raw=None):
111
ioreg = '/usr/sbin/ioreg'
112
if not os.access(ioreg, os.X_OK):
114
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
115
stdout=subprocess.PIPE).communicate()[0]
116
lines = raw.splitlines()
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:]:
123
if line.endswith('}'):
125
match = re.search(r'"BSD Name"\s+=\s+"(.*?)"', line)
126
if match is not None:
127
names[loc] = match.group(1)
129
if len(names.keys()) == 3:
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
149
def open_windows(self):
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:
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))
168
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
170
drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
171
self._main_prefix = drives[0][1]
173
self._card_prefix = drives[1][1]
176
def open_linux(self):
178
bus = dbus.SystemBus()
179
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
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')
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)+'/'
194
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
196
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
197
self._main_prefix = None
200
self._main_prefix = conditional_mount(dev)+os.sep
202
except dbus.exceptions.DBusException:
206
if not self._main_prefix:
207
raise DeviceError('Could not open device for reading. Try a reboot.')
209
self._card_prefix = None
210
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
213
keys.append(int('UC_SD' in bus.get_object("org.freedesktop.Hal", card).GetPropertyString('info.parent', dbus_interface='org.freedesktop.Hal.Device')))
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]
221
self._card_prefix = conditional_mount(dev, False)+os.sep
46
SUPPORTS_SUB_DIRS = True
47
MUST_READ_METADATA = True
231
self._main_prefix = self._card_prefix = None
250
if self._card_prefix is not None:
252
cachep = os.path.join(self._card_prefix, self.CACHE_XML)
52
def write_cache(prefix):
54
cachep = os.path.join(prefix, self.CACHE_XML)
253
55
if not os.path.exists(cachep):
255
57
os.makedirs(os.path.dirname(cachep), mode=0777)
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">
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">
266
self._card_prefix = None
268
69
traceback.print_exc()
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
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__, '', '', '')
276
def card_prefix(self, end_session=True):
277
return self._card_prefix
280
def _windows_space(cls, prefix):
283
win32file = __import__('win32file', globals(), locals(), [], -1)
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
290
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
291
win32file.GetDiskFreeSpace(prefix[:-1])
293
mult = sectors_per_cluster * bytes_per_sector
294
return total_clusters * mult, free_clusters * mult
296
def total_space(self, end_session=True):
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)
306
msz = self._windows_space(self._main_prefix)[0]
307
csz = self._windows_space(self._card_prefix)[0]
311
def free_space(self, end_session=True):
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
321
msz = self._windows_space(self._main_prefix)[1]
322
csz = self._windows_space(self._card_prefix)[1]
326
def books(self, oncard=False, end_session=True):
327
if oncard and self._card_prefix is None:
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...'))
88
elif oncard == 'cardb' and not self._card_b_prefix:
89
self.report_progress(1.0, _('Getting list of books on device...'))
91
elif oncard and oncard != 'carda' and oncard != 'cardb':
92
self.report_progress(1.0, _('Getting list of books on device...'))
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):
103
self.report_progress(1.0, _('Getting list of books on device...'))
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])
347
def mkdir(self, path, end_session=True):
348
""" Make directory """
349
path = self.munge_path(path)
352
def list(self, path, recurse=False, end_session=True, munge=True):
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)
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)
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)
380
def rm(self, path, end_session=True):
381
path = self.munge_path(path)
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):
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,
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.'))
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)
120
path = os.path.join(self._main_prefix, 'database', 'media', 'books')
398
122
def get_size(obj):
399
123
if hasattr(obj, 'seek'):
404
128
return os.path.getsize(obj)
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()
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 " +\
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"))
418
140
paths, ctimes = [], []
420
142
names = iter(names)
143
metadata = iter(metadata)
144
for i, infile in enumerate(files):
423
146
if not hasattr(infile, 'read'):
424
147
infile, close = open(infile, 'rb'), True
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]))
150
mdata, fname = metadata.next(), names.next()
151
filepath = self.create_upload_path(path, mdata, fname)
153
paths.append(filepath)
430
155
self.put_file(infile, paths[-1], replace_file=True)
433
159
ctimes.append(os.path.getctime(paths[-1]))
161
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
163
self.report_progress(1.0, _('Transferring books to device...'))
434
165
return zip(paths, sizes, ctimes, cycle([on_card]))
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:
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
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, '')
184
name = name.replace('\\', '/')
445
185
name = name.replace('//', '/')
446
booklists[on_card].add_book(info, name, *location[1:-1])
186
if name.startswith('/'):
189
booklists[blist].add_book(info, name, *location[1:-1])
447
190
fix_ids(*booklists)
449
192
def delete_books(self, paths, end_session=True):
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):
198
os.removedirs(os.path.dirname(path))
201
self.report_progress(1.0, _('Removing books from device...'))
455
204
def remove_books_from_metadata(cls, paths, booklists):