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'
file_types = set(['txt'])
description = _('Read metadata from %s files') % 'TXT'
author = 'John Schember'
def get_metadata(self, stream, ftype):
from calibre.ebooks.metadata.txt import get_metadata

View file

@ -8,7 +8,7 @@
from itertools import cycle
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
import calibre.devices.cybookg3.t2b as t2b
@ -23,28 +23,34 @@ class CYBOOKG3(USBMS):
VENDOR_NAME = 'BOOKEEN'
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_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'
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR_MAIN = "eBooks"
EBOOK_DIR_CARD = "eBooks"
EBOOK_DIR_CARD_A = "eBooks"
THUMBNAIL_HEIGHT = 144
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):
if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.'))
if on_card == 'carda' and not self._card_a_prefix:
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:
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
if on_card == 'carda':
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:
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):
if hasattr(obj, 'seek'):
@ -57,10 +63,12 @@ def get_size(obj):
sizes = [get_size(f) for f in files]
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:
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 = []
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'
WINDOWS_MAIN_MEM = 'EBOOK'
WINDOWS_CARD_MEM = 'EBOOK'
WINDOWS_CARD_A_MEM = 'EBOOK'
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'
STORAGE_CARD_VOLUME_LABEL = 'EB600 Storage Card'
EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD = ''
EBOOK_DIR_CARD_A = ''
SUPPORTS_SUB_DIRS = True
def windows_sort_drives(self, drives):
main = drives['main']
card = drives['card']
card = drives['carda']
if card and main and card < main:
drives['main'] = card
drives['card'] = main
drives['carda'] = main
return drives

View file

@ -87,7 +87,13 @@ def get_device_information(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()
@ -95,8 +101,8 @@ def total_space(self, end_session=True):
"""
Get total space available on the mountpoints:
1. Main memory
2. Memory Stick
3. SD Card
2. Memory Card A
3. Memory Card B
@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.
@ -115,24 +121,25 @@ def free_space(self, end_session=True):
"""
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.
@param oncard: If True return a list of ebooks on the storage card,
otherwise return list of ebooks in main memory of device.
If True and no books on card return empty list.
@param oncard: If 'carda' or 'cardb' return a list of ebooks on the
specific storage card, otherwise return list of ebooks
in main memory of device. If a card is specified and no
books are on the card return empty list.
@return: A BookList.
"""
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):
'''
Upload a list of books to the device. If a file already
exists on the device, it should be replaced.
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
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 names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
@ -163,7 +170,8 @@ def add_books_to_metadata(cls, locations, metadata, booklists):
another dictionary that maps tag names to lists of book ids. The ids are
ids from the book database.
@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
@ -179,16 +187,18 @@ def remove_books_from_metadata(cls, paths, booklists):
Remove books from the metadata list. This function must not communicate
with the device.
@param paths: paths to books on the device.
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)).
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
'''
raise NotImplementedError()
def sync_booklists(self, booklists, end_session=True):
'''
Update metadata on device.
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)).
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
'''
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'
WINDOWS_MAIN_MEM = 'INTERNAL_STORAGE'
WINDOWS_CARD_MEM = 'CARD_STORAGE'
WINDOWS_CARD_A_MEM = 'CARD_STORAGE'
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'
STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card'
EBOOK_DIR_MAIN = "documents"
EBOOK_DIR_CARD = "documents"
EBOOK_DIR_CARD_A = "documents"
SUPPORTS_SUB_DIRS = True
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 @@ def reorder_playlists(self):
item.setAttribute('id', str(map[id]))
pl.appendChild(item)
def fix_ids(main, card):
def fix_ids(main, carda, cardb):
'''
Adjust ids the XML databases.
'''
if hasattr(main, 'purge_empty_playlists'):
main.purge_empty_playlists()
if hasattr(card, 'purge_empty_playlists'):
card.purge_empty_playlists()
if hasattr(carda, 'purge_empty_playlists'):
carda.purge_empty_playlists()
if hasattr(cardb, 'purge_empty_playlists'):
cardb.purge_empty_playlists()
def regen_ids(db):
if not hasattr(db, 'root_element'):
@ -413,6 +415,7 @@ def regen_ids(db):
db.reorder_playlists()
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 @@
import sys, os, shutil, time, subprocess, re
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.prs505.books import BookList, fix_ids
from calibre import iswindows, islinux, isosx, __appname__
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 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'
class PRS505(CLI, Device):
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'
CACHE_XML = 'Sony Reader/database/cache.xml'
VENDOR_NAME = 'SONY'
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'
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__
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):
time.sleep(5)
self._main_prefix = self._card_prefix = None
if islinux:
Device.open(self)
def write_cache(prefix):
try:
self.open_linux()
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)
cachep = os.path.join(prefix, self.CACHE_XML)
if not os.path.exists(cachep):
os.makedirs(os.path.dirname(cachep), mode=0777)
f = open(cachep, 'wb')
@ -263,133 +56,47 @@ def open(self):
import traceback
traceback.print_exc()
def set_progress_reporter(self, pr):
self.report_progress = pr
if self._card_a_prefix is not None:
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):
return (self.__class__.__name__, '', '', '')
def card_prefix(self, end_session=True):
return self._card_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:
def books(self, oncard=None, end_session=True):
if oncard == 'carda' and not self._card_a_prefix:
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
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)
paths = bl.purge_corrupted_files()
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):
os.unlink(path)
return bl
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 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,
def upload_books(self, files, names, on_card=None, end_session=True,
metadata=None):
if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.'))
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
else os.path.join(self._main_prefix, 'database', 'media', 'books')
if on_card == 'carda' and not self._card_a_prefix:
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 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):
if hasattr(obj, 'seek'):
@ -399,17 +106,15 @@ def get_size(obj):
return size
return os.path.getsize(obj)
sizes = map(get_size, files)
sizes = [get_size(f) for f in files]
size = sum(sizes)
space = self.free_space()
mspace = space[0]
cspace = space[2]
if on_card and size > cspace - 1024*1024:
raise FreeSpaceError("There is insufficient free space "+\
"on the storage card")
if not on_card and size > mspace - 2*1024*1024:
raise FreeSpaceError("There is insufficient free space " +\
"in main memory")
if not on_card and size > self.free_space()[0] - 2*1024*1024:
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, ctimes = [], []
@ -435,11 +140,11 @@ def add_books_to_metadata(cls, locations, metadata, booklists):
for location in locations:
info = metadata.next()
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 = (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('//', '/')
booklists[on_card].add_book(info, name, *location[1:-1])
booklists[blist].add_book(info, name, *location[1:-1])
fix_ids(*booklists)
def delete_books(self, paths, end_session=True):
@ -462,18 +167,13 @@ def sync_booklists(self, booklists, end_session=True):
f = open(self._main_prefix + self.__class__.MEDIA_XML, 'wb')
booklists[0].write(f)
f.close()
if self._card_prefix is not None and hasattr(booklists[1], 'write'):
if not os.path.exists(self._card_prefix):
os.makedirs(self._card_prefix)
f = open(self._card_prefix + self.__class__.CACHE_XML, 'wb')
booklists[1].write(f)
f.close()
def main(args=sys.argv):
return 0
if __name__ == '__main__':
sys.exit(main())
def write_card_prefix(prefix, listid):
if prefix is not None and hasattr(booklists[listid], 'write'):
if not os.path.exists(prefix):
os.makedirs(prefix)
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)

View file

@ -10,6 +10,12 @@
class PRS700(PRS505):
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
WINDOWS_MAIN_MEM = None
WINDOWS_CARD_MEM = None
WINDOWS_CARD_A_MEM = None
WINDOWS_CARD_B_MEM = None
OSX_MAIN_MEM = None
OSX_CARD_MEM = None
OSX_CARD_A_MEM = None
OSX_CARD_B_MEM = None
MAIN_MEMORY_VOLUME_LABEL = ''
STORAGE_CARD_VOLUME_LABEL = ''
@ -63,12 +65,26 @@ class Device(_Device):
</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">
%(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">'
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
def get_fdi(cls):
@ -102,7 +118,7 @@ def set_progress_reporter(self, report_progress):
self.report_progress = report_progress
def card_prefix(self, end_session=True):
return self._card_prefix
return (self._card_a_prefix, self._card_b_prefix)
@classmethod
def _windows_space(cls, prefix):
@ -122,34 +138,41 @@ def _windows_space(cls, prefix):
return total_clusters * mult, free_clusters * mult
def total_space(self, end_session=True):
msz = csz = 0
msz = casz = cbsz = 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)
if self._card_a_prefix is not None:
stats = os.statvfs(self._card_a_prefix)
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:
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):
msz = csz = 0
msz = casz = cbsz = 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
if self._card_a_prefix is not None:
stats = os.statvfs(self._card_a_prefix)
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:
msz = self._windows_space(self._main_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):
pnp_id = pnp_id.upper()
@ -190,15 +213,18 @@ def open_windows(self):
for drive in c.Win32_DiskDrive():
if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
drives['main'] = self.windows_get_drive_prefix(drive)
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM):
drives['card'] = self.windows_get_drive_prefix(drive)
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_A_MEM):
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
drives = self.windows_sort_drives(drives)
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:
raise DeviceError(
@ -228,9 +254,11 @@ def get_dev_node(lines, loc):
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:
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:
get_dev_node(lines[i+1:], 'card')
if len(names.keys()) == 2:
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:], 'carda')
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
return names
@ -242,10 +270,18 @@ def open_osx(self):
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['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
card_a_pat = names['carda'] if 'carda' in names.keys() else None
card_b_pat = names['cardb'] if 'cardb' in names.keys() else None
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):
import dbus
@ -278,21 +314,24 @@ def conditional_mount(dev):
if not self._main_prefix:
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__)
for dev in cards:
def mount_card(dev):
try:
self._card_prefix = conditional_mount(dev)+os.sep
break
return conditional_mount(dev)+os.sep
except:
import 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):
time.sleep(5)
self._main_prefix = self._card_prefix = None
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
if islinux:
try:
self.open_linux()

View file

@ -12,28 +12,19 @@
from calibre.ebooks.metadata.meta import metadata_from_formats, path_to_ext
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.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
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 USBMS(Device):
# CLI must come before Device as it implments the CLI functions that
# are inherited from the device interface in Device.
class USBMS(CLI, Device):
FORMATS = []
EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD = ''
EBOOK_DIR_CARD_A = ''
EBOOK_DIR_CARD_B = ''
SUPPORTS_SUB_DIRS = False
CAN_SET_METADATA = False
@ -48,14 +39,18 @@ def get_device_information(self, end_session=True):
"""
return (self.__class__.__name__, '', '', '')
def books(self, oncard=False, end_session=True):
def books(self, oncard=None, end_session=True):
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
prefix = self._card_prefix if oncard else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
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_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
if self.SUPPORTS_SUB_DIRS:
@ -71,15 +66,21 @@ def books(self, oncard=False, end_session=True):
bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
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):
if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.'))
if on_card == 'carda' and not self._card_a_prefix:
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:
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
if on_card == 'carda':
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:
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):
if hasattr(obj, 'seek'):
@ -92,10 +93,12 @@ def get_size(obj):
sizes = [get_size(f) for f in files]
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:
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 = []
names = iter(names)
@ -147,12 +150,12 @@ def get_size(obj):
def add_books_to_metadata(cls, locations, metadata, booklists):
for location in locations:
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)
if not book in booklists[on_card]:
booklists[on_card].append(book)
if not book in booklists[blist]:
booklists[blist].append(book)
def delete_books(self, paths, end_session=True):
@ -180,58 +183,6 @@ def sync_booklists(self, booklists, end_session=True):
# the Sony Readers.
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
def metadata_from_path(cls, path):
return metadata_from_formats([path])

View file

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

View file

@ -305,7 +305,9 @@ def __init__(self, single_instance, opts, actions, parent=None):
similar_menu=similar_menu)
self.memory_view.set_context_menu(None, 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)
QObject.connect(self.library_view,
SIGNAL('files_dropped(PyQt_PyObject)'),
@ -315,11 +317,12 @@ def __init__(self, single_instance, opts, actions, parent=None):
('connect_to_book_display',
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)
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()
if self.system_tray_icon.isVisible() and opts.start_in_tray:
@ -582,10 +585,12 @@ def current_view(self):
if idx == 1:
return self.memory_view
if idx == 2:
return self.card_view
return self.card_a_view
if idx == 3:
return self.card_b_view
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 @@ def metadata_downloaded(self, job):
else:
self.device_job_exception(job)
return
mainlist, cardlist = job.result
mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist)
self.memory_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
self.card_view.set_database(cardlist)
self.card_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
for view in (self.memory_view, self.card_view):
self.card_a_view.set_database(cardalist)
self.card_a_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
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)
if not view.restore_column_widths():
view.resizeColumnsToContents()
@ -793,8 +800,12 @@ def delete_books(self, checked):
return
view.model().delete_books(rows)
else:
view = self.memory_view if self.stack.currentIndex() == 1 \
else self.card_view
if self.stack.currentIndex() == 1:
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)
job = self.remove_paths(paths)
self.delete_memory[job] = (paths, view.model())
@ -809,7 +820,7 @@ def books_deleted(self, job):
'''
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))
if job.exception is not None:
self.device_job_exception(job)
@ -1318,10 +1329,11 @@ def location_selected(self, location):
'''
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)
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.resize_on_select:
view.resizeRowsToContents()

View file

@ -288,7 +288,7 @@
</sizepolicy>
</property>
<property name="currentIndex" >
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="library" >
<layout class="QVBoxLayout" name="verticalLayout_2" >
@ -417,10 +417,48 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page" >
<widget class="QWidget" name="card_a_memory" >
<layout class="QGridLayout" >
<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" >
<sizepolicy vsizetype="Expanding" hsizetype="Preferred" >
<horstretch>10</horstretch>

View file

@ -8,7 +8,7 @@
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 PyQt4 import QtCore

View file

@ -171,17 +171,20 @@ def __init__(self, parent):
QAbstractListModel.__init__(self, parent)
self.icons = [QVariant(QIcon(':/library')),
QVariant(QIcon(':/images/reader.svg')),
QVariant(QIcon(':/images/sd.svg')),
QVariant(QIcon(':/images/sd.svg'))]
self.text = [_('Library\n%d\nbooks'),
_('Reader\n%s\navailable'),
_('Card\n%s\navailable')]
self.free = [-1, -1]
_('Card A\n%s\navailable'),
_('Card B\n%s\navailable')]
self.free = [-1, -1, -1]
self.count = 0
self.highlight_row = 0
self.tooltips = [
_('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 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):
@ -218,9 +221,14 @@ def headerData(self, section, orientation, role):
def update_devices(self, cp=None, fs=[-1, -1, -1]):
self.free[0] = fs[0]
self.free[1] = max(fs[1:])
if cp == None:
self.free[1] = fs[1]
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[2] = -1
self.reset()
def location_changed(self, row):
@ -244,12 +252,12 @@ def count_changed(self, new_count):
def current_changed(self, current, previous):
if current.isValid():
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.model().location_changed(i)
def location_changed(self, row):
if 0 <= row and row <= 2:
if 0 <= row and row <= 3:
self.model().location_changed(row)
class JobsView(TableView):