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 @@ import os, shutil
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 @@ class CYBOOKG3(USBMS):
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 @@ class Device(object):
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 @@ class Device(object):
"""
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 @@ class Device(object):
"""
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 @@ class Device(object):
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
@ -180,7 +188,8 @@ class Device(object):
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)).
(L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
'''
raise NotImplementedError()
@ -188,7 +197,8 @@ class Device(object):
'''
Update metadata on device.
@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()

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 @@ class BookList(_BookList):
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 fix_ids(main, card):
db.reorder_playlists()
regen_ids(main)
regen_ids(card)
regen_ids(carda)
regen_ids(cardb)
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
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']
MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'
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
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 @@ class PRS505(Device):
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 @@ class PRS505(Device):
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 @@ class PRS505(Device):
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 @@ class PRS505(Device):
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 @@ from calibre.devices.prs505.driver import PRS505
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 @@ class Device(_Device):
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 @@ class Device(_Device):
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 @@ class Device(_Device):
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 @@ class Device(_Device):
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 @@ class Device(_Device):
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 @@ class Device(_Device):
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 itertools import cycle
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 @@ class USBMS(Device):
"""
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 @@ class USBMS(Device):
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 @@ class USBMS(Device):
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 @@ class USBMS(Device):
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 @@ class USBMS(Device):
# 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 @@ class DeviceManager(Thread):
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 @@ class DeviceManager(Thread):
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 @@ class DeviceManager(Thread):
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 @@ class DeviceMenu(QMenu):
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 @@ class DeviceMenu(QMenu):
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 @@ class DeviceGUI(object):
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 @@ class DeviceGUI(object):
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 @@ class DeviceGUI(object):
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
('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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
'''
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 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
'''
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 @@ import os, sys, traceback, urlparse
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 @@ class LocationModel(QAbstractListModel):
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 @@ class LocationModel(QAbstractListModel):
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 @@ class LocationView(QListView):
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):