2 card support move SONY drivers to USBMS infrastructure.

This commit is contained in:
Kovid Goyal 2009-04-15 14:49:27 -07:00
commit b14f2be686
17 changed files with 441 additions and 571 deletions

View File

@ -165,6 +165,7 @@ class TXTMetadataReader(MetadataReaderPlugin):
name = 'Read TXT metadata' name = 'Read TXT metadata'
file_types = set(['txt']) file_types = set(['txt'])
description = _('Read metadata from %s files') % 'TXT' description = _('Read metadata from %s files') % 'TXT'
author = 'John Schember'
def get_metadata(self, stream, ftype): def get_metadata(self, stream, ftype):
from calibre.ebooks.metadata.txt import get_metadata from calibre.ebooks.metadata.txt import get_metadata

View File

@ -8,7 +8,7 @@ import os, shutil
from itertools import cycle from itertools import cycle
from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata import authors_to_string
from calibre.devices.errors import FreeSpaceError from calibre.devices.errors import DeviceError, FreeSpaceError
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
import calibre.devices.cybookg3.t2b as t2b import calibre.devices.cybookg3.t2b as t2b
@ -23,28 +23,34 @@ class CYBOOKG3(USBMS):
VENDOR_NAME = 'BOOKEEN' VENDOR_NAME = 'BOOKEEN'
WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD' WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD'
WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD' WINDOWS_CARD_A_MEM = 'CYBOOK_GEN3__-SD'
OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media'
OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' OSX_CARD_A_MEM = 'Bookeen Cybook Gen3 -SD Media'
MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR_MAIN = "eBooks" EBOOK_DIR_MAIN = "eBooks"
EBOOK_DIR_CARD = "eBooks" EBOOK_DIR_CARD_A = "eBooks"
THUMBNAIL_HEIGHT = 144 THUMBNAIL_HEIGHT = 144
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None): metadata=None):
if on_card and not self._card_prefix: if on_card == 'carda' and not self._card_a_prefix:
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card in this slot.'))
elif on_card == 'cardb' and not self._card_b_prefix:
raise ValueError(_('The reader has no storage card in this slot.'))
elif on_card and on_card not in ('carda', 'cardb'):
raise DeviceError(_('The reader has no storage card in this slot.'))
if not on_card: if on_card == 'carda':
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) path = os.path.join(self._card_a_prefix, self.EBOOK_DIR_CARD_A)
if on_card == 'cardb':
path = os.path.join(self._card_b_prefix, self.EBOOK_DIR_CARD_B)
else: else:
path = os.path.join(self._card_prefix, self.EBOOK_DIR_CARD) path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
def get_size(obj): def get_size(obj):
if hasattr(obj, 'seek'): if hasattr(obj, 'seek'):
@ -57,10 +63,12 @@ class CYBOOKG3(USBMS):
sizes = [get_size(f) for f in files] sizes = [get_size(f) for f in files]
size = sum(sizes) size = sum(sizes)
if on_card and size > self.free_space()[2] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
if not on_card and size > self.free_space()[0] - 2*1024*1024: if not on_card and size > self.free_space()[0] - 2*1024*1024:
raise FreeSpaceError(_("There is insufficient free space in main memory")) raise FreeSpaceError(_("There is insufficient free space in main memory"))
if on_card == 'carda' and size > self.free_space()[1] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
if on_card == 'cardb' and size > self.free_space()[2] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
paths = [] paths = []
names = iter(names) names = iter(names)

10
src/calibre/devices/eb600/driver.py Executable file → Normal file
View File

@ -17,24 +17,24 @@ class EB600(USBMS):
VENDOR_NAME = 'NETRONIX' VENDOR_NAME = 'NETRONIX'
WINDOWS_MAIN_MEM = 'EBOOK' WINDOWS_MAIN_MEM = 'EBOOK'
WINDOWS_CARD_MEM = 'EBOOK' WINDOWS_CARD_A_MEM = 'EBOOK'
OSX_MAIN_MEM = 'EB600 Internal Storage Media' OSX_MAIN_MEM = 'EB600 Internal Storage Media'
OSX_CARD_MEM = 'EB600 Card Storage Media' OSX_CARD_A_MEM = 'EB600 Card Storage Media'
MAIN_MEMORY_VOLUME_LABEL = 'EB600 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'EB600 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'EB600 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'EB600 Storage Card'
EBOOK_DIR_MAIN = '' EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD = '' EBOOK_DIR_CARD_A = ''
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def windows_sort_drives(self, drives): def windows_sort_drives(self, drives):
main = drives['main'] main = drives['main']
card = drives['card'] card = drives['carda']
if card and main and card < main: if card and main and card < main:
drives['main'] = card drives['main'] = card
drives['card'] = main drives['carda'] = main
return drives return drives

View File

@ -87,7 +87,13 @@ class Device(object):
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
''' '''
Return prefix to paths on the card or '' if no cards present. Return a 2 element list of the prefix to paths on the cards.
If no card is present None is set for the card's prefix.
E.G.
('/place', '/place2')
(None, 'place2')
('place', None)
(None, None)
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -95,8 +101,8 @@ class Device(object):
""" """
Get total space available on the mountpoints: Get total space available on the mountpoints:
1. Main memory 1. Main memory
2. Memory Stick 2. Memory Card A
3. SD Card 3. Memory Card B
@return: A 3 element list with total space in bytes of (1, 2, 3). If a @return: A 3 element list with total space in bytes of (1, 2, 3). If a
particular device doesn't have any of these locations it should return 0. particular device doesn't have any of these locations it should return 0.
@ -115,24 +121,25 @@ class Device(object):
""" """
raise NotImplementedError() raise NotImplementedError()
def books(self, oncard=False, end_session=True): def books(self, oncard=None, end_session=True):
""" """
Return a list of ebooks on the device. Return a list of ebooks on the device.
@param oncard: If True return a list of ebooks on the storage card, @param oncard: If 'carda' or 'cardb' return a list of ebooks on the
otherwise return list of ebooks in main memory of device. specific storage card, otherwise return list of ebooks
If True and no books on card return empty list. in main memory of device. If a card is specified and no
books are on the card return empty list.
@return: A BookList. @return: A BookList.
""" """
raise NotImplementedError() raise NotImplementedError()
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None): metadata=None):
''' '''
Upload a list of books to the device. If a file already Upload a list of books to the device. If a file already
exists on the device, it should be replaced. exists on the device, it should be replaced.
This method should raise a L{FreeSpaceError} if there is not enough This method should raise a L{FreeSpaceError} if there is not enough
free space on the device. The text of the FreeSpaceError must contain the free space on the device. The text of the FreeSpaceError must contain the
word "card" if C{on_card} is True otherwise it must contain the word "memory". word "card" if C{on_card} is not None otherwise it must contain the word "memory".
@param files: A list of paths and/or file-like objects. @param files: A list of paths and/or file-like objects.
@param names: A list of file names that the books should have @param names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files) once uploaded to the device. len(names) == len(files)
@ -163,7 +170,8 @@ class Device(object):
another dictionary that maps tag names to lists of book ids. The ids are another dictionary that maps tag names to lists of book ids. The ids are
ids from the book database. ids from the book database.
@param booklists: A tuple containing the result of calls to @param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)). (L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
''' '''
raise NotImplementedError raise NotImplementedError
@ -179,16 +187,18 @@ class Device(object):
Remove books from the metadata list. This function must not communicate Remove books from the metadata list. This function must not communicate
with the device. with the device.
@param paths: paths to books on the device. @param paths: paths to books on the device.
@param booklists: A tuple containing the result of calls to @param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)). (L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
''' '''
raise NotImplementedError() raise NotImplementedError()
def sync_booklists(self, booklists, end_session=True): def sync_booklists(self, booklists, end_session=True):
''' '''
Update metadata on device. Update metadata on device.
@param booklists: A tuple containing the result of calls to @param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)). (L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
''' '''
raise NotImplementedError() raise NotImplementedError()

6
src/calibre/devices/kindle/driver.py Executable file → Normal file
View File

@ -18,16 +18,16 @@ class KINDLE(USBMS):
VENDOR_NAME = 'KINDLE' VENDOR_NAME = 'KINDLE'
WINDOWS_MAIN_MEM = 'INTERNAL_STORAGE' WINDOWS_MAIN_MEM = 'INTERNAL_STORAGE'
WINDOWS_CARD_MEM = 'CARD_STORAGE' WINDOWS_CARD_A_MEM = 'CARD_STORAGE'
OSX_MAIN_MEM = 'Kindle Internal Storage Media' OSX_MAIN_MEM = 'Kindle Internal Storage Media'
OSX_CARD_MEM = 'Kindle Card Storage Media' OSX_CARD_A_MEM = 'Kindle Card Storage Media'
MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card'
EBOOK_DIR_MAIN = "documents" EBOOK_DIR_MAIN = "documents"
EBOOK_DIR_CARD = "documents" EBOOK_DIR_CARD_A = "documents"
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
WIRELESS_FILE_NAME_PATTERN = re.compile( WIRELESS_FILE_NAME_PATTERN = re.compile(

0
src/calibre/devices/prs500/driver.py Executable file → Normal file
View File

View File

@ -380,14 +380,16 @@ class BookList(_BookList):
item.setAttribute('id', str(map[id])) item.setAttribute('id', str(map[id]))
pl.appendChild(item) pl.appendChild(item)
def fix_ids(main, card): def fix_ids(main, carda, cardb):
''' '''
Adjust ids the XML databases. Adjust ids the XML databases.
''' '''
if hasattr(main, 'purge_empty_playlists'): if hasattr(main, 'purge_empty_playlists'):
main.purge_empty_playlists() main.purge_empty_playlists()
if hasattr(card, 'purge_empty_playlists'): if hasattr(carda, 'purge_empty_playlists'):
card.purge_empty_playlists() carda.purge_empty_playlists()
if hasattr(cardb, 'purge_empty_playlists'):
cardb.purge_empty_playlists()
def regen_ids(db): def regen_ids(db):
if not hasattr(db, 'root_element'): if not hasattr(db, 'root_element'):
@ -413,6 +415,7 @@ def fix_ids(main, card):
db.reorder_playlists() db.reorder_playlists()
regen_ids(main) regen_ids(main)
regen_ids(card) regen_ids(carda)
regen_ids(cardb)
main.set_next_id(str(main.max_id()+1)) main.set_next_id(str(main.max_id()+1))

View File

@ -6,250 +6,43 @@ Device driver for the SONY PRS-505
import sys, os, shutil, time, subprocess, re import sys, os, shutil, time, subprocess, re
from itertools import cycle from itertools import cycle
from calibre.devices.interface import Device from calibre.devices.usbms.cli import CLI
from calibre.devices.usbms.device import Device
from calibre.devices.errors import DeviceError, FreeSpaceError from calibre.devices.errors import DeviceError, FreeSpaceError
from calibre.devices.prs505.books import BookList, fix_ids from calibre.devices.prs505.books import BookList, fix_ids
from calibre import iswindows, islinux, isosx, __appname__ from calibre import iswindows, islinux, isosx, __appname__
from calibre.devices.errors import PathError from calibre.devices.errors import PathError
class File(object): class PRS505(CLI, Device):
def __init__(self, path):
stats = os.stat(path)
self.is_dir = os.path.isdir(path)
self.is_readonly = not os.access(path, os.W_OK)
self.ctime = stats.st_ctime
self.wtime = stats.st_mtime
self.size = stats.st_size
if path.endswith(os.sep):
path = path[:-1]
self.path = path
self.name = os.path.basename(path)
class PRS505(Device):
VENDOR_ID = 0x054c #: SONY Vendor Id
PRODUCT_ID = 0x031e #: Product Id for the PRS-505
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY'
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt'] FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
VENDOR_ID = [0x054c] #: SONY Vendor Id
PRODUCT_ID = [0x031e] #: Product Id for the PRS-505
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
MEDIA_XML = 'database/cache/media.xml' VENDOR_NAME = 'SONY'
CACHE_XML = 'Sony Reader/database/cache.xml' WINDOWS_MAIN_MEM = 'PRS-505'
WINDOWS_CARD_A_MEM = 'PRS-505/UC:MS'
WINDOWS_CARD_B_MEM = 'PRS-505/UC:SD'
OSX_MAIN_MEM = 'Sony PRS-505/UC Media'
OSX_CARD_A_MEM = 'Sony PRS-505/UC:MS Media'
OSX_CARD_B_MEM = 'Sony PRS-505/UC:SD'
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
OSX_NAME = 'Sony PRS-505' MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'
CARD_PATH_PREFIX = __appname__ CARD_PATH_PREFIX = __appname__
FDI_TEMPLATE = \
'''
<device>
<match key="info.category" string="volume">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">
<match key="volume.is_partition" bool="false">
<merge key="volume.label" type="string">%(main_memory)s</merge>
<merge key="%(app)s.mainvolume" type="string">%(deviceclass)s</merge>
</match>
</match>
</match>
</match>
</match>
</device>
<device>
<match key="info.category" string="volume">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">
<match key="volume.is_partition" bool="true">
<merge key="volume.label" type="string">%(storage_card)s</merge>
<merge key="%(app)s.cardvolume" type="string">%(deviceclass)s</merge>
</match>
</match>
</match>
</match>
</match>
</device>
'''.replace('%(app)s', __appname__)
def __init__(self, log_packets=False):
self._main_prefix = self._card_prefix = None
@classmethod
def get_fdi(cls):
return cls.FDI_TEMPLATE%dict(
deviceclass=cls.__name__,
vendor_id=hex(cls.VENDOR_ID),
product_id=hex(cls.PRODUCT_ID),
bcd=hex(cls.BCD[0]),
main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
)
@classmethod
def is_device(cls, device_id):
device_id = device_id.upper()
if 'VEN_'+cls.VENDOR_NAME in device_id and \
'PROD_'+cls.PRODUCT_NAME in device_id:
return True
vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:]
if len(vid) < 4: vid = '0'+vid
if len(pid) < 4: pid = '0'+pid
if 'VID_'+vid in device_id and \
'PID_'+pid in device_id:
return True
return False
@classmethod
def get_osx_mountpoints(cls, raw=None):
if raw is None:
ioreg = '/usr/sbin/ioreg'
if not os.access(ioreg, os.X_OK):
ioreg = 'ioreg'
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
stdout=subprocess.PIPE).communicate()[0]
lines = raw.splitlines()
names = {}
for i, line in enumerate(lines):
if line.strip().endswith('<class IOMedia>') and cls.OSX_NAME in line:
loc = 'stick' if ':MS' in line else 'card' if ':SD' in line else 'main'
for line in lines[i+1:]:
line = line.strip()
if line.endswith('}'):
break
match = re.search(r'"BSD Name"\s+=\s+"(.*?)"', line)
if match is not None:
names[loc] = match.group(1)
break
if len(names.keys()) == 3:
break
return names
def open_osx(self):
mount = subprocess.Popen('mount', shell=True,
stdout=subprocess.PIPE).stdout.read()
names = self.get_osx_mountpoints()
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
if 'main' not in names.keys():
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
main_pat = dev_pat%names['main']
self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
card_pat = names['stick'] if 'stick' in names.keys() else names['card'] if 'card' in names.keys() else None
if card_pat is not None:
card_pat = dev_pat%card_pat
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
def open_windows(self):
time.sleep(6)
drives = []
wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI()
for drive in c.Win32_DiskDrive():
if self.__class__.is_device(str(drive.PNPDeviceID)):
if drive.Partitions == 0:
continue
try:
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
prefix = logical_disk.DeviceID+os.sep
drives.append((drive.Index, prefix))
except IndexError:
continue
if not drives:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
self._main_prefix = drives[0][1]
if len(drives) > 1:
self._card_prefix = drives[1][1]
def open_linux(self):
import dbus
bus = dbus.SystemBus()
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
def conditional_mount(dev, main_mem=True):
mmo = bus.get_object("org.freedesktop.Hal", dev)
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device')
mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device')
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
if is_mounted:
return str(mount_point)
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
dbus_interface='org.freedesktop.Hal.Device.Volume')
return os.path.normpath('/media/'+label)+'/'
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
self._main_prefix = None
for dev in mm:
try:
self._main_prefix = conditional_mount(dev)+os.sep
break
except dbus.exceptions.DBusException:
continue
if not self._main_prefix:
raise DeviceError('Could not open device for reading. Try a reboot.')
self._card_prefix = None
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
keys = []
for card in cards:
keys.append(int('UC_SD' in bus.get_object("org.freedesktop.Hal", card).GetPropertyString('info.parent', dbus_interface='org.freedesktop.Hal.Device')))
cards = zip(cards, keys)
cards.sort(cmp=lambda x, y: cmp(x[1], y[1]))
cards = [i[0] for i in cards]
for dev in cards:
try:
self._card_prefix = conditional_mount(dev, False)+os.sep
break
except:
import traceback
print traceback
continue
def open(self): def open(self):
time.sleep(5) Device.open(self)
self._main_prefix = self._card_prefix = None
if islinux: def write_cache(prefix):
try: try:
self.open_linux() cachep = os.path.join(prefix, self.CACHE_XML)
except DeviceError:
time.sleep(3)
self.open_linux()
if iswindows:
try:
self.open_windows()
except DeviceError:
time.sleep(3)
self.open_windows()
if isosx:
try:
self.open_osx()
except DeviceError:
time.sleep(3)
self.open_osx()
if self._card_prefix is not None:
try:
cachep = os.path.join(self._card_prefix, self.CACHE_XML)
if not os.path.exists(cachep): if not os.path.exists(cachep):
os.makedirs(os.path.dirname(cachep), mode=0777) os.makedirs(os.path.dirname(cachep), mode=0777)
f = open(cachep, 'wb') f = open(cachep, 'wb')
@ -263,133 +56,47 @@ class PRS505(Device):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def set_progress_reporter(self, pr): if self._card_a_prefix is not None:
self.report_progress = pr write_cache(self._card_a_prefix)
if self._card_b_prefix is not None:
write_cache(self._card_b_prefix)
def get_device_information(self, end_session=True): def get_device_information(self, end_session=True):
return (self.__class__.__name__, '', '', '') return (self.__class__.__name__, '', '', '')
def card_prefix(self, end_session=True): def books(self, oncard=None, end_session=True):
return self._card_prefix if oncard == 'carda' and not self._card_a_prefix:
@classmethod
def _windows_space(cls, prefix):
if prefix is None:
return 0, 0
win32file = __import__('win32file', globals(), locals(), [], -1)
try:
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix[:-1])
except Exception, err:
if getattr(err, 'args', [None])[0] == 21: # Disk not ready
time.sleep(3)
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix[:-1])
else: raise
mult = sectors_per_cluster * bytes_per_sector
return total_clusters * mult, free_clusters * mult
def total_space(self, end_session=True):
msz = csz = 0
if not iswindows:
if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
if self._card_prefix is not None:
stats = os.statvfs(self._card_prefix)
csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
else:
msz = self._windows_space(self._main_prefix)[0]
csz = self._windows_space(self._card_prefix)[0]
return (msz, 0, csz)
def free_space(self, end_session=True):
msz = csz = 0
if not iswindows:
if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * stats.f_bavail
if self._card_prefix is not None:
stats = os.statvfs(self._card_prefix)
csz = stats.f_frsize * stats.f_bavail
else:
msz = self._windows_space(self._main_prefix)[1]
csz = self._windows_space(self._card_prefix)[1]
return (msz, 0, csz)
def books(self, oncard=False, end_session=True):
if oncard and self._card_prefix is None:
return [] return []
elif oncard == 'cardb' and not self._card_b_prefix:
return []
elif oncard and oncard != 'carda' and oncard != 'cardb':
return []
db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML
prefix = self._card_prefix if oncard else self._main_prefix prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix
bl = BookList(open(prefix + db, 'rb'), prefix) bl = BookList(open(prefix + db, 'rb'), prefix)
paths = bl.purge_corrupted_files() paths = bl.purge_corrupted_files()
for path in paths: for path in paths:
path = os.path.join(self._card_prefix if oncard else self._main_prefix, path) path = os.path.join(prefix, path)
if os.path.exists(path): if os.path.exists(path):
os.unlink(path) os.unlink(path)
return bl return bl
def munge_path(self, path): def upload_books(self, files, names, on_card=None, end_session=True,
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
(self._card_prefix and path.startswith(self._card_prefix))):
path = self._main_prefix + path[1:]
elif path.startswith('card:'):
path = path.replace('card:', self._card_prefix[:-1])
return path
def mkdir(self, path, end_session=True):
""" Make directory """
path = self.munge_path(path)
os.mkdir(path)
def list(self, path, recurse=False, end_session=True, munge=True):
if munge:
path = self.munge_path(path)
if os.path.isfile(path):
return [(os.path.dirname(path), [File(path)])]
entries = [File(os.path.join(path, f)) for f in os.listdir(path)]
dirs = [(path, entries)]
for _file in entries:
if recurse and _file.is_dir:
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
return dirs
def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
src = open(path, 'rb')
shutil.copyfileobj(src, outfile, 10*1024*1024)
def put_file(self, infile, path, replace_file=False, end_session=True):
path = self.munge_path(path)
if os.path.isdir(path):
path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path):
raise PathError('File already exists: '+path)
dest = open(path, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024)
dest.flush()
dest.close()
def rm(self, path, end_session=True):
path = self.munge_path(path)
os.unlink(path)
def touch(self, path, end_session=True):
path = self.munge_path(path)
if not os.path.exists(path):
open(path, 'w').close()
if not os.path.isdir(path):
os.utime(path, None)
def upload_books(self, files, names, on_card=False, end_session=True,
metadata=None): metadata=None):
if on_card and not self._card_prefix: if on_card == 'carda' and not self._card_a_prefix:
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card in this slot.'))
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \ elif on_card == 'cardb' and not self._card_b_prefix:
else os.path.join(self._main_prefix, 'database', 'media', 'books') raise ValueError(_('The reader has no storage card in this slot.'))
elif on_card and on_card not in ('carda', 'cardb'):
raise DeviceError(_('The reader has no storage card in this slot.'))
if on_card == 'carda':
path = os.path.join(self._card_a_prefix, self.CARD_PATH_PREFIX)
elif on_card == 'cardb':
path = os.path.join(self._card_b_prefix, self.CARD_PATH_PREFIX)
else:
path = os.path.join(self._main_prefix, 'database', 'media', 'books')
def get_size(obj): def get_size(obj):
if hasattr(obj, 'seek'): if hasattr(obj, 'seek'):
@ -399,17 +106,15 @@ class PRS505(Device):
return size return size
return os.path.getsize(obj) return os.path.getsize(obj)
sizes = map(get_size, files) sizes = [get_size(f) for f in files]
size = sum(sizes) size = sum(sizes)
space = self.free_space()
mspace = space[0] if not on_card and size > self.free_space()[0] - 2*1024*1024:
cspace = space[2] raise FreeSpaceError(_("There is insufficient free space in main memory"))
if on_card and size > cspace - 1024*1024: if on_card == 'carda' and size > self.free_space()[1] - 1024*1024:
raise FreeSpaceError("There is insufficient free space "+\ raise FreeSpaceError(_("There is insufficient free space on the storage card"))
"on the storage card") if on_card == 'cardb' and size > self.free_space()[2] - 1024*1024:
if not on_card and size > mspace - 2*1024*1024: raise FreeSpaceError(_("There is insufficient free space on the storage card"))
raise FreeSpaceError("There is insufficient free space " +\
"in main memory")
paths, ctimes = [], [] paths, ctimes = [], []
@ -435,11 +140,11 @@ class PRS505(Device):
for location in locations: for location in locations:
info = metadata.next() info = metadata.next()
path = location[0] path = location[0]
on_card = 1 if location[3] else 0 blist = 2 if location[3] == 'cardb' else 1 if location[3] == 'carda' else 0
name = path.rpartition(os.sep)[2] name = path.rpartition(os.sep)[2]
name = (cls.CARD_PATH_PREFIX+'/' if on_card else 'database/media/books/') + name name = (cls.CARD_PATH_PREFIX+'/' if blist else 'database/media/books/') + name
name = name.replace('//', '/') name = name.replace('//', '/')
booklists[on_card].add_book(info, name, *location[1:-1]) booklists[blist].add_book(info, name, *location[1:-1])
fix_ids(*booklists) fix_ids(*booklists)
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
@ -462,18 +167,13 @@ class PRS505(Device):
f = open(self._main_prefix + self.__class__.MEDIA_XML, 'wb') f = open(self._main_prefix + self.__class__.MEDIA_XML, 'wb')
booklists[0].write(f) booklists[0].write(f)
f.close() f.close()
if self._card_prefix is not None and hasattr(booklists[1], 'write'):
if not os.path.exists(self._card_prefix): def write_card_prefix(prefix, listid):
os.makedirs(self._card_prefix) if prefix is not None and hasattr(booklists[listid], 'write'):
f = open(self._card_prefix + self.__class__.CACHE_XML, 'wb') if not os.path.exists(prefix):
booklists[1].write(f) os.makedirs(prefix)
f.close() f = open(prefix + self.__class__.CACHE_XML, 'wb')
booklists[listid].write(f)
f.close()
write_card_prefix(self._card_a_prefix, 1)
write_card_prefix(self._card_b_prefix, 2)
def main(args=sys.argv):
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -10,6 +10,12 @@ from calibre.devices.prs505.driver import PRS505
class PRS700(PRS505): class PRS700(PRS505):
BCD = [0x31a] BCD = [0x31a]
PRODUCT_NAME = 'PRS-700'
OSX_NAME = 'Sony PRS-700' WINDOWS_MAIN_MEM = 'PRS-700'
WINDOWS_CARD_A_MEM = 'PRS-700/UC:MS'
WINDOWS_CARD_B_MEM = 'PRS-700/UC:SD'
OSX_MAIN_MEM = 'Sony PRS-700/UC Media'
OSX_CARD_A_MEM = 'Sony PRS-700/UC:MS Media'
OSX_CARD_B_MEM = 'Sony PRS-700/UC:SD'

View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
__license__ = 'GPL 3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import os, shutil
from calibre.devices.errors import PathError
class File(object):
def __init__(self, path):
stats = os.stat(path)
self.is_dir = os.path.isdir(path)
self.is_readonly = not os.access(path, os.W_OK)
self.ctime = stats.st_ctime
self.wtime = stats.st_mtime
self.size = stats.st_size
if path.endswith(os.sep):
path = path[:-1]
self.path = path
self.name = os.path.basename(path)
class CLI(object):
def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
with open(path, 'rb') as src:
shutil.copyfileobj(src, outfile, 10*1024*1024)
def put_file(self, infile, path, replace_file=False, end_session=True):
path = self.munge_path(path)
if os.path.isdir(path):
path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path):
raise PathError('File already exists: ' + path)
dest = open(path, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024)
dest.flush()
dest.close()
def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
(self._card_a_prefix and path.startswith(self._card_a_prefix)) or \
(self._card_b_prefix and path.startswith(self._card_b_prefix))):
path = self._main_prefix + path[1:]
elif path.startswith('carda:'):
path = path.replace('carda:', self._card_prefix[:-1])
elif path.startswith('cardb:'):
path = path.replace('cardb:', self._card_prefix[:-1])
return path
def list(self, path, recurse=False, end_session=True, munge=True):
if munge:
path = self.munge_path(path)
if os.path.isfile(path):
return [(os.path.dirname(path), [File(path)])]
entries = [File(os.path.join(path, f)) for f in os.listdir(path)]
dirs = [(path, entries)]
for _file in entries:
if recurse and _file.is_dir:
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
return dirs
def mkdir(self, path, end_session=True):
if self.SUPPORTS_SUB_DIRS:
path = self.munge_path(path)
os.mkdir(path)
def rm(self, path, end_session=True):
path = self.munge_path(path)
self.delete_books([path])
def touch(self, path, end_session=True):
path = self.munge_path(path)
if not os.path.exists(path):
open(path, 'w').close()
if not os.path.isdir(path):
os.utime(path, None)

View File

@ -25,10 +25,12 @@ class Device(_Device):
VENDOR_NAME = None VENDOR_NAME = None
WINDOWS_MAIN_MEM = None WINDOWS_MAIN_MEM = None
WINDOWS_CARD_MEM = None WINDOWS_CARD_A_MEM = None
WINDOWS_CARD_B_MEM = None
OSX_MAIN_MEM = None OSX_MAIN_MEM = None
OSX_CARD_MEM = None OSX_CARD_A_MEM = None
OSX_CARD_B_MEM = None
MAIN_MEMORY_VOLUME_LABEL = '' MAIN_MEMORY_VOLUME_LABEL = ''
STORAGE_CARD_VOLUME_LABEL = '' STORAGE_CARD_VOLUME_LABEL = ''
@ -63,12 +65,26 @@ class Device(_Device):
</match> </match>
</match> </match>
</device> </device>
<device>
<match key="info.category" string="volume">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="%(vendor_id)s">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="%(product_id)s">
%(BCD_start)s
<match key="@info.parent:storage.lun" int="2">
<merge key="volume.label" type="string">%(storage_card)s</merge>
<merge key="%(app)s.cardvolume" type="string">%(deviceclass)s</merge>
</match>
%(BCD_end)s
</match>
</match>
</match>
</device>
''' '''
FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">' FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">'
def __init__(self, key='-1', log_packets=False, report_progress=None) : def __init__(self, key='-1', log_packets=False, report_progress=None) :
self._main_prefix = self._card_prefix = None self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
@classmethod @classmethod
def get_fdi(cls): def get_fdi(cls):
@ -102,7 +118,7 @@ class Device(_Device):
self.report_progress = report_progress self.report_progress = report_progress
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
return self._card_prefix return (self._card_a_prefix, self._card_b_prefix)
@classmethod @classmethod
def _windows_space(cls, prefix): def _windows_space(cls, prefix):
@ -122,34 +138,41 @@ class Device(_Device):
return total_clusters * mult, free_clusters * mult return total_clusters * mult, free_clusters * mult
def total_space(self, end_session=True): def total_space(self, end_session=True):
msz = csz = 0 msz = casz = cbsz = 0
if not iswindows: if not iswindows:
if self._main_prefix is not None: if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix) stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
if self._card_prefix is not None: if self._card_a_prefix is not None:
stats = os.statvfs(self._card_prefix) stats = os.statvfs(self._card_a_prefix)
csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) casz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
if self._card_b_prefix is not None:
stats = os.statvfs(self._card_b_prefix)
cbsz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
else: else:
msz = self._windows_space(self._main_prefix)[0] msz = self._windows_space(self._main_prefix)[0]
csz = self._windows_space(self._card_prefix)[0] casz = self._windows_space(self._card_a_prefix)[0]
cbsz = self._windows_space(self._card_b_prefix)[0]
return (msz, 0, csz) return (msz, casz, cbsz)
def free_space(self, end_session=True): def free_space(self, end_session=True):
msz = csz = 0 msz = casz = cbsz = 0
if not iswindows: if not iswindows:
if self._main_prefix is not None: if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix) stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * stats.f_bavail msz = stats.f_frsize * stats.f_bavail
if self._card_prefix is not None: if self._card_a_prefix is not None:
stats = os.statvfs(self._card_prefix) stats = os.statvfs(self._card_a_prefix)
csz = stats.f_frsize * stats.f_bavail casz = stats.f_frsize * stats.f_bavail
if self._card_b_prefix is not None:
stats = os.statvfs(self._card_b_prefix)
cbsz = stats.f_frsize * stats.f_bavail
else: else:
msz = self._windows_space(self._main_prefix)[1] msz = self._windows_space(self._main_prefix)[1]
csz = self._windows_space(self._card_prefix)[1] csz = self._windows_space(self._card_prefix)[1]
return (msz, 0, csz) return (msz, casz, cbsz)
def windows_match_device(self, pnp_id, device_id): def windows_match_device(self, pnp_id, device_id):
pnp_id = pnp_id.upper() pnp_id = pnp_id.upper()
@ -190,15 +213,18 @@ class Device(_Device):
for drive in c.Win32_DiskDrive(): for drive in c.Win32_DiskDrive():
if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM): if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
drives['main'] = self.windows_get_drive_prefix(drive) drives['main'] = self.windows_get_drive_prefix(drive)
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM): elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_A_MEM):
drives['card'] = self.windows_get_drive_prefix(drive) drives['carda'] = self.windows_get_drive_prefix(drive)
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_B_MEM):
drives['cardb'] = self.windows_get_drive_prefix(drive)
if 'main' in drives.keys() and 'card' in drives.keys(): if 'main' in drives.keys() and 'carda' in drives.keys() and 'cardb' in drives.keys():
break break
drives = self.windows_sort_drives(drives) drives = self.windows_sort_drives(drives)
self._main_prefix = drives.get('main') self._main_prefix = drives.get('main')
self._card_prefix = drives.get('card') self._card_a_prefix = drives.get('carda')
self._card_b_prefix = drives.get('cardb')
if not self._main_prefix: if not self._main_prefix:
raise DeviceError( raise DeviceError(
@ -228,9 +254,11 @@ class Device(_Device):
for i, line in enumerate(lines): for i, line in enumerate(lines):
if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line: if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
get_dev_node(lines[i+1:], 'main') get_dev_node(lines[i+1:], 'main')
if self.OSX_CARD_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_CARD_MEM in line: if self.OSX_CARD_A_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_CARD_A_MEM in line:
get_dev_node(lines[i+1:], 'card') get_dev_node(lines[i+1:], 'carda')
if len(names.keys()) == 2: if self.OSX_CARD_B_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_CARD_B_MEM in line:
get_dev_node(lines[i+1:], 'cardb')
if len(names.keys()) == 3:
break break
return names return names
@ -242,10 +270,18 @@ class Device(_Device):
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
main_pat = dev_pat % names['main'] main_pat = dev_pat % names['main']
self._main_prefix = re.search(main_pat, mount).group(2) + os.sep self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
card_pat = names['card'] if 'card' in names.keys() else None card_a_pat = names['carda'] if 'carda' in names.keys() else None
if card_pat is not None: card_b_pat = names['cardb'] if 'cardb' in names.keys() else None
card_pat = dev_pat % card_pat
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep def get_card_prefix(pat):
if pat is not None:
pat = dev_pat % pat
return re.search(pat, mount).group(2) + os.sep
else:
return None
self._card_a_prefix = get_card_prefix(card_a_pat)
self._card_b_prefix = get_card_prefix(card_b_pat)
def open_linux(self): def open_linux(self):
import dbus import dbus
@ -278,21 +314,24 @@ class Device(_Device):
if not self._main_prefix: if not self._main_prefix:
raise DeviceError('Could not open device for reading. Try a reboot.') raise DeviceError('Could not open device for reading. Try a reboot.')
self._card_prefix = None self._card_a_prefix = self._card_b_prefix = None
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
for dev in cards: def mount_card(dev):
try: try:
self._card_prefix = conditional_mount(dev)+os.sep return conditional_mount(dev)+os.sep
break
except: except:
import traceback import traceback
print traceback print traceback
continue
if len(cards) >= 1:
self._card_a_prefix = mount_card(cards[0])
if len(cards) >=2:
self._card_b_prefix = mount_card(cards[1])
def open(self): def open(self):
time.sleep(5) time.sleep(5)
self._main_prefix = self._card_prefix = None self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
if islinux: if islinux:
try: try:
self.open_linux() self.open_linux()

View File

@ -12,28 +12,19 @@ from itertools import cycle
from calibre.ebooks.metadata.meta import metadata_from_formats, path_to_ext from calibre.ebooks.metadata.meta import metadata_from_formats, path_to_ext
from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata import authors_to_string
from calibre.devices.usbms.cli import CLI
from calibre.devices.usbms.device import Device from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book from calibre.devices.usbms.books import BookList, Book
from calibre.devices.errors import FreeSpaceError, PathError from calibre.devices.errors import DeviceError, FreeSpaceError
from calibre.devices.mime import mime_type_ext from calibre.devices.mime import mime_type_ext
class File(object): # CLI must come before Device as it implments the CLI functions that
def __init__(self, path): # are inherited from the device interface in Device.
stats = os.stat(path) class USBMS(CLI, Device):
self.is_dir = os.path.isdir(path)
self.is_readonly = not os.access(path, os.W_OK)
self.ctime = stats.st_ctime
self.wtime = stats.st_mtime
self.size = stats.st_size
if path.endswith(os.sep):
path = path[:-1]
self.path = path
self.name = os.path.basename(path)
class USBMS(Device):
FORMATS = [] FORMATS = []
EBOOK_DIR_MAIN = '' EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD = '' EBOOK_DIR_CARD_A = ''
EBOOK_DIR_CARD_B = ''
SUPPORTS_SUB_DIRS = False SUPPORTS_SUB_DIRS = False
CAN_SET_METADATA = False CAN_SET_METADATA = False
@ -48,14 +39,18 @@ class USBMS(Device):
""" """
return (self.__class__.__name__, '', '', '') return (self.__class__.__name__, '', '', '')
def books(self, oncard=False, end_session=True): def books(self, oncard=None, end_session=True):
bl = BookList() bl = BookList()
if oncard and self._card_prefix is None: if oncard == 'carda' and not self._card_a_prefix:
return bl
elif oncard == 'cardb' and not self._card_b_prefix:
return bl
elif oncard and oncard != 'carda' and oncard != 'cardb':
return bl return bl
prefix = self._card_prefix if oncard else self._main_prefix prefix = self._card_a_prefix if oncard == 'carda' else self._card_b_prefix if oncard == 'cardb' else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN ebook_dir = self.EBOOK_DIR_CARD_A if oncard == 'carda' else self.EBOOK_DIR_CARD_B if oncard == 'cardb' else self.EBOOK_DIR_MAIN
# Get all books in the ebook_dir directory # Get all books in the ebook_dir directory
if self.SUPPORTS_SUB_DIRS: if self.SUPPORTS_SUB_DIRS:
@ -71,15 +66,21 @@ class USBMS(Device):
bl.append(self.__class__.book_from_path(os.path.join(path, filename))) bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
return bl return bl
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None): metadata=None):
if on_card and not self._card_prefix: if on_card == 'carda' and not self._card_a_prefix:
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card in this slot.'))
elif on_card == 'cardb' and not self._card_b_prefix:
raise ValueError(_('The reader has no storage card in this slot.'))
elif on_card and on_card not in ('carda', 'cardb'):
raise DeviceError(_('The reader has no storage card in this slot.'))
if not on_card: if on_card == 'carda':
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) path = os.path.join(self._card_a_prefix, self.EBOOK_DIR_CARD_A)
if on_card == 'cardb':
path = os.path.join(self._card_b_prefix, self.EBOOK_DIR_CARD_B)
else: else:
path = os.path.join(self._card_prefix, self.EBOOK_DIR_CARD) path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
def get_size(obj): def get_size(obj):
if hasattr(obj, 'seek'): if hasattr(obj, 'seek'):
@ -92,10 +93,12 @@ class USBMS(Device):
sizes = [get_size(f) for f in files] sizes = [get_size(f) for f in files]
size = sum(sizes) size = sum(sizes)
if on_card and size > self.free_space()[2] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
if not on_card and size > self.free_space()[0] - 2*1024*1024: if not on_card and size > self.free_space()[0] - 2*1024*1024:
raise FreeSpaceError(_("There is insufficient free space in main memory")) raise FreeSpaceError(_("There is insufficient free space in main memory"))
if on_card == 'carda' and size > self.free_space()[1] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
if on_card == 'cardb' and size > self.free_space()[2] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
paths = [] paths = []
names = iter(names) names = iter(names)
@ -147,12 +150,12 @@ class USBMS(Device):
def add_books_to_metadata(cls, locations, metadata, booklists): def add_books_to_metadata(cls, locations, metadata, booklists):
for location in locations: for location in locations:
path = location[0] path = location[0]
on_card = 1 if location[1] else 0 blist = 2 if location[1] == 'cardb' else 1 if location[1] == 'carda' else 0
book = cls.book_from_path(path) book = cls.book_from_path(path)
if not book in booklists[on_card]: if not book in booklists[blist]:
booklists[on_card].append(book) booklists[blist].append(book)
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
@ -180,58 +183,6 @@ class USBMS(Device):
# the Sony Readers. # the Sony Readers.
pass pass
def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
with open(path, 'rb') as src:
shutil.copyfileobj(src, outfile, 10*1024*1024)
def put_file(self, infile, path, replace_file=False, end_session=True):
path = self.munge_path(path)
if os.path.isdir(path):
path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path):
raise PathError('File already exists: ' + path)
dest = open(path, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024)
dest.flush()
dest.close()
def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
(self._card_prefix and path.startswith(self._card_prefix))):
path = self._main_prefix + path[1:]
elif path.startswith('card:'):
path = path.replace('card:', self._card_prefix[:-1])
return path
def list(self, path, recurse=False, end_session=True, munge=True):
if munge:
path = self.munge_path(path)
if os.path.isfile(path):
return [(os.path.dirname(path), [File(path)])]
entries = [File(os.path.join(path, f)) for f in os.listdir(path)]
dirs = [(path, entries)]
for _file in entries:
if recurse and _file.is_dir:
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
return dirs
def mkdir(self, path, end_session=True):
if self.SUPPORTS_SUB_DIRS:
path = self.munge_path(path)
os.mkdir(path)
def rm(self, path, end_session=True):
path = self.munge_path(path)
self.delete_books([path])
def touch(self, path, end_session=True):
path = self.munge_path(path)
if not os.path.exists(path):
open(path, 'w').close()
if not os.path.isdir(path):
os.utime(path, None)
@classmethod @classmethod
def metadata_from_path(cls, path): def metadata_from_path(cls, path):
return metadata_from_formats([path]) return metadata_from_formats([path])

View File

@ -139,9 +139,10 @@ class DeviceManager(Thread):
def _books(self): def _books(self):
'''Get metadata from device''' '''Get metadata from device'''
mainlist = self.device.books(oncard=False, end_session=False) mainlist = self.device.books(oncard=None, end_session=False)
cardlist = self.device.books(oncard=True) cardalist = self.device.books(oncard='carda')
return (mainlist, cardlist) cardblist = self.device.books(oncard='cardb')
return (mainlist, cardalist, cardblist)
def books(self, done): def books(self, done):
'''Return callable that returns the list of books on device as two booklists''' '''Return callable that returns the list of books on device as two booklists'''
@ -156,12 +157,12 @@ class DeviceManager(Thread):
return self.create_job(self._sync_booklists, done, args=[booklists], return self.create_job(self._sync_booklists, done, args=[booklists],
description=_('Send metadata to device')) description=_('Send metadata to device'))
def _upload_books(self, files, names, on_card=False, metadata=None): def _upload_books(self, files, names, on_card=None, metadata=None):
'''Upload books to device: ''' '''Upload books to device: '''
return self.device.upload_books(files, names, on_card, return self.device.upload_books(files, names, on_card,
metadata=metadata, end_session=False) metadata=metadata, end_session=False)
def upload_books(self, done, files, names, on_card=False, titles=None, def upload_books(self, done, files, names, on_card=None, titles=None,
metadata=None): metadata=None):
desc = _('Upload %d books to device')%len(names) desc = _('Upload %d books to device')%len(names)
if titles: if titles:
@ -197,6 +198,7 @@ class DeviceManager(Thread):
def _view_book(self, path, target): def _view_book(self, path, target):
f = open(target, 'wb') f = open(target, 'wb')
print self.device
self.device.get_file(path, f) self.device.get_file(path, f)
f.close() f.close()
return target return target
@ -256,24 +258,27 @@ class DeviceMenu(QMenu):
self.connect(action2, SIGNAL('a_s(QAction)'), self.connect(action2, SIGNAL('a_s(QAction)'),
self.action_triggered) self.action_triggered)
_actions = [ _actions = [
('main:', False, False, ':/images/reader.svg', ('main:', False, False, ':/images/reader.svg',
_('Send to main memory')), _('Send to main memory')),
('card:0', False, False, ':/images/sd.svg', ('carda:0', False, False, ':/images/sd.svg',
_('Send to storage card')), _('Send to storage card A')),
('cardb:0', False, False, ':/images/sd.svg',
_('Send to storage card B')),
'-----', '-----',
('main:', True, False, ':/images/reader.svg', ('main:', True, False, ':/images/reader.svg',
_('Send to main memory')), _('Send to main memory')),
('card:0', True, False, ':/images/sd.svg', ('carda:0', True, False, ':/images/sd.svg',
_('Send to storage card')), _('Send to storage card A')),
('cardb:0', True, False, ':/images/sd.svg',
_('Send to storage card B')),
'-----', '-----',
('main:', False, True, ':/images/reader.svg', ('main:', False, True, ':/images/reader.svg',
_('Send specific format to main memory')), _('Send specific format to main memory')),
('card:0', False, True, ':/images/sd.svg', ('carda:0', False, True, ':/images/sd.svg',
_('Send specific format to storage card')), _('Send specific format to storage card A')),
('cardb:0', False, True, ':/images/sd.svg',
_('Send specific format to storage card B')),
] ]
if default_account is not None: if default_account is not None:
@ -335,7 +340,7 @@ class DeviceMenu(QMenu):
def enable_device_actions(self, enable): def enable_device_actions(self, enable):
for action in self.actions: for action in self.actions:
if action.dest[:4] in ('main', 'card'): if action.dest in ('main:', 'carda:0', 'cardb:0'):
action.setEnabled(enable) action.setEnabled(enable)
class Emailer(Thread): class Emailer(Thread):
@ -412,16 +417,23 @@ class DeviceGUI(object):
d.exec_() d.exec_()
fmt = d.format().lower() fmt = d.format().lower()
dest, sub_dest = dest.split(':') dest, sub_dest = dest.split(':')
if dest in ('main', 'card'): if dest in ('main', 'carda', 'cardb'):
if not self.device_connected or not self.device_manager: if not self.device_connected or not self.device_manager:
error_dialog(self, _('No device'), error_dialog(self, _('No device'),
_('Cannot send: No device is connected')).exec_() _('Cannot send: No device is connected')).exec_()
return return
on_card = dest == 'card' if dest == 'carda' and not self.device_manager.has_card():
if on_card and not self.device_manager.has_card():
error_dialog(self, _('No card'), error_dialog(self, _('No card'),
_('Cannot send: Device has no storage card')).exec_() _('Cannot send: Device has no storage card')).exec_()
return return
if dest == 'cardb' and not self.device_manager.has_card():
error_dialog(self, _('No card'),
_('Cannot send: Device has no storage card')).exec_()
return
if dest == 'main':
on_card = None
else:
on_card = dest
self.sync_to_device(on_card, delete, fmt) self.sync_to_device(on_card, delete, fmt)
elif dest == 'mail': elif dest == 'mail':
to, fmts = sub_dest.split(';') to, fmts = sub_dest.split(';')
@ -680,7 +692,7 @@ class DeviceGUI(object):
cp, fs = job.result cp, fs = job.result
self.location_view.model().update_devices(cp, fs) self.location_view.model().update_devices(cp, fs)
def upload_books(self, files, names, metadata, on_card=False, memory=None): def upload_books(self, files, names, metadata, on_card=None, memory=None):
''' '''
Upload books to device. Upload books to device.
:param files: List of either paths to files or file like objects :param files: List of either paths to files or file like objects
@ -719,7 +731,7 @@ class DeviceGUI(object):
self.upload_booklists() self.upload_booklists()
view = self.card_view if on_card else self.memory_view view = self.card_a_view if on_card == 'carda' else self.card_b_view if on_card == 'cardb' else self.memory_view
view.model().resort(reset=False) view.model().resort(reset=False)
view.model().research() view.model().research()
for f in files: for f in files:

View File

@ -305,7 +305,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
similar_menu=similar_menu) similar_menu=similar_menu)
self.memory_view.set_context_menu(None, None, None, self.memory_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None) self.action_view, self.action_save, None, None)
self.card_view.set_context_menu(None, None, None, self.card_a_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None)
self.card_b_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None) self.action_view, self.action_save, None, None)
QObject.connect(self.library_view, QObject.connect(self.library_view,
SIGNAL('files_dropped(PyQt_PyObject)'), SIGNAL('files_dropped(PyQt_PyObject)'),
@ -315,11 +317,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
('connect_to_book_display', ('connect_to_book_display',
self.status_bar.book_info.show_data), self.status_bar.book_info.show_data),
]: ]:
for view in (self.library_view, self.memory_view, self.card_view): for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
getattr(view, func)(target) getattr(view, func)(target)
self.memory_view.connect_dirtied_signal(self.upload_booklists) self.memory_view.connect_dirtied_signal(self.upload_booklists)
self.card_view.connect_dirtied_signal(self.upload_booklists) self.card_a_view.connect_dirtied_signal(self.upload_booklists)
self.card_b_view.connect_dirtied_signal(self.upload_booklists)
self.show() self.show()
if self.system_tray_icon.isVisible() and opts.start_in_tray: if self.system_tray_icon.isVisible() and opts.start_in_tray:
@ -582,10 +585,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
if idx == 1: if idx == 1:
return self.memory_view return self.memory_view
if idx == 2: if idx == 2:
return self.card_view return self.card_a_view
if idx == 3:
return self.card_b_view
def booklists(self): def booklists(self):
return self.memory_view.model().db, self.card_view.model().db return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
@ -647,12 +652,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
else: else:
self.device_job_exception(job) self.device_job_exception(job)
return return
mainlist, cardlist = job.result mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist) self.memory_view.set_database(mainlist)
self.memory_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA) self.memory_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
self.card_view.set_database(cardlist) self.card_a_view.set_database(cardalist)
self.card_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA) self.card_a_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
for view in (self.memory_view, self.card_view): self.card_b_view.set_database(cardblist)
self.card_b_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
for view in (self.memory_view, self.card_a_view, self.card_b_view):
view.sortByColumn(3, Qt.DescendingOrder) view.sortByColumn(3, Qt.DescendingOrder)
if not view.restore_column_widths(): if not view.restore_column_widths():
view.resizeColumnsToContents() view.resizeColumnsToContents()
@ -793,8 +800,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
return return
view.model().delete_books(rows) view.model().delete_books(rows)
else: else:
view = self.memory_view if self.stack.currentIndex() == 1 \ if self.stack.currentIndex() == 1:
else self.card_view view = self.memory_view
elif self.stack.currentIndex() == 2:
view = self.card_a_view
else:
view = self.card_b_view
paths = view.model().paths(rows) paths = view.model().paths(rows)
job = self.remove_paths(paths) job = self.remove_paths(paths)
self.delete_memory[job] = (paths, view.model()) self.delete_memory[job] = (paths, view.model())
@ -809,7 +820,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
''' '''
Called once deletion is done on the device Called once deletion is done on the device
''' '''
for view in (self.memory_view, self.card_view): for view in (self.memory_view, self.card_a_view, self.card_b_view):
view.model().deletion_done(job, bool(job.exception)) view.model().deletion_done(job, bool(job.exception))
if job.exception is not None: if job.exception is not None:
self.device_job_exception(job) self.device_job_exception(job)
@ -1318,10 +1329,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
''' '''
Called when a location icon is clicked (e.g. Library) Called when a location icon is clicked (e.g. Library)
''' '''
page = 0 if location == 'library' else 1 if location == 'main' else 2 page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3
self.stack.setCurrentIndex(page) self.stack.setCurrentIndex(page)
view = self.memory_view if page == 1 else \ view = self.memory_view if page == 1 else \
self.card_view if page == 2 else None self.card_a_view if page == 2 else \
self.card_b_view if page == 3 else None
if view: if view:
if view.resize_on_select: if view.resize_on_select:
view.resizeRowsToContents() view.resizeRowsToContents()

View File

@ -288,7 +288,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex" > <property name="currentIndex" >
<number>0</number> <number>3</number>
</property> </property>
<widget class="QWidget" name="library" > <widget class="QWidget" name="library" >
<layout class="QVBoxLayout" name="verticalLayout_2" > <layout class="QVBoxLayout" name="verticalLayout_2" >
@ -417,10 +417,48 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page" > <widget class="QWidget" name="card_a_memory" >
<layout class="QGridLayout" > <layout class="QGridLayout" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="DeviceBooksView" name="card_view" > <widget class="DeviceBooksView" name="card_a_view" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Preferred" >
<horstretch>10</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops" >
<bool>true</bool>
</property>
<property name="dragEnabled" >
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode" >
<bool>false</bool>
</property>
<property name="dragDropMode" >
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
<property name="wordWrap" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="card_b_memory" >
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="DeviceBooksView" name="card_b_view" >
<property name="sizePolicy" > <property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Preferred" > <sizepolicy vsizetype="Expanding" hsizetype="Preferred" >
<horstretch>10</horstretch> <horstretch>10</horstretch>

View File

@ -8,7 +8,7 @@ import os, sys, traceback, urlparse
from BeautifulSoup import BeautifulSoup, Tag from BeautifulSoup import BeautifulSoup, Tag
from calibre.ebooks.epub.iterator import EbookIterator from calibre.ebooks.oeb.iterator import EbookIterator
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from PyQt4 import QtCore from PyQt4 import QtCore

View File

@ -171,17 +171,20 @@ class LocationModel(QAbstractListModel):
QAbstractListModel.__init__(self, parent) QAbstractListModel.__init__(self, parent)
self.icons = [QVariant(QIcon(':/library')), self.icons = [QVariant(QIcon(':/library')),
QVariant(QIcon(':/images/reader.svg')), QVariant(QIcon(':/images/reader.svg')),
QVariant(QIcon(':/images/sd.svg')),
QVariant(QIcon(':/images/sd.svg'))] QVariant(QIcon(':/images/sd.svg'))]
self.text = [_('Library\n%d\nbooks'), self.text = [_('Library\n%d\nbooks'),
_('Reader\n%s\navailable'), _('Reader\n%s\navailable'),
_('Card\n%s\navailable')] _('Card A\n%s\navailable'),
self.free = [-1, -1] _('Card B\n%s\navailable')]
self.free = [-1, -1, -1]
self.count = 0 self.count = 0
self.highlight_row = 0 self.highlight_row = 0
self.tooltips = [ self.tooltips = [
_('Click to see the list of books available on your computer'), _('Click to see the list of books available on your computer'),
_('Click to see the list of books in the main memory of your reader'), _('Click to see the list of books in the main memory of your reader'),
_('Click to see the list of books on the storage card in your reader') _('Click to see the list of books on storage card A in your reader'),
_('Click to see the list of books on storage card B in your reader')
] ]
def rowCount(self, parent): def rowCount(self, parent):
@ -218,9 +221,14 @@ class LocationModel(QAbstractListModel):
def update_devices(self, cp=None, fs=[-1, -1, -1]): def update_devices(self, cp=None, fs=[-1, -1, -1]):
self.free[0] = fs[0] self.free[0] = fs[0]
self.free[1] = max(fs[1:]) self.free[1] = fs[1]
if cp == None: self.free[2] = fs[2]
if cp != None:
self.free[1] = fs[1] if fs[1] else -1
self.free[2] = fs[2] if fs[2] else -1
else:
self.free[1] = -1 self.free[1] = -1
self.free[2] = -1
self.reset() self.reset()
def location_changed(self, row): def location_changed(self, row):
@ -244,12 +252,12 @@ class LocationView(QListView):
def current_changed(self, current, previous): def current_changed(self, current, previous):
if current.isValid(): if current.isValid():
i = current.row() i = current.row()
location = 'library' if i == 0 else 'main' if i == 1 else 'card' location = 'library' if i == 0 else 'main' if i == 1 else 'carda' if i == 2 else 'cardb'
self.emit(SIGNAL('location_selected(PyQt_PyObject)'), location) self.emit(SIGNAL('location_selected(PyQt_PyObject)'), location)
self.model().location_changed(i) self.model().location_changed(i)
def location_changed(self, row): def location_changed(self, row):
if 0 <= row and row <= 2: if 0 <= row and row <= 3:
self.model().location_changed(row) self.model().location_changed(row)
class JobsView(TableView): class JobsView(TableView):