diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index a9fc342059..aa6c003114 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -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 diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index 9e7e9d5862..c3a4fa94b0 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -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) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py old mode 100755 new mode 100644 index 44690655a0..cb2f25d2f9 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -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 diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 21790e3c46..0ad01e7493 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -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 @@ -179,16 +187,18 @@ class Device(object): 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() diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py old mode 100755 new mode 100644 index a5775dec8a..d598e2a503 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -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( diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py old mode 100755 new mode 100644 diff --git a/src/calibre/devices/prs505/books.py b/src/calibre/devices/prs505/books.py index eb34bff0e7..3fdb8a8432 100644 --- a/src/calibre/devices/prs505/books.py +++ b/src/calibre/devices/prs505/books.py @@ -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)) \ No newline at end of file + main.set_next_id(str(main.max_id()+1)) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 6e21c60d1b..efc48a2dff 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -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'] + + 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 = \ -''' - - - - - - - %(main_memory)s - %(deviceclass)s - - - - - - - - - - - - - %(storage_card)s - %(deviceclass)s - - - - - - -'''.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('') 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) diff --git a/src/calibre/devices/prs700/driver.py b/src/calibre/devices/prs700/driver.py index 5db60ef506..2b82eb3e34 100644 --- a/src/calibre/devices/prs700/driver.py +++ b/src/calibre/devices/prs700/driver.py @@ -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' + diff --git a/src/calibre/devices/usbms/cli.py b/src/calibre/devices/usbms/cli.py new file mode 100644 index 0000000000..40e2225486 --- /dev/null +++ b/src/calibre/devices/usbms/cli.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +from __future__ import with_statement + +__license__ = 'GPL 3' +__copyright__ = '2009, John Schember ' +__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) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 5a1b5ef40d..63dabe001a 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -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): + + + + + %(BCD_start)s + + %(storage_card)s + %(deviceclass)s + + %(BCD_end)s + + + + ''' FDI_BCD_TEMPLATE = '' 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('') 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('') 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('') 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('') 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() diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 0a66b78014..bb7a104fa4 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -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]) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index c2a61e7648..201ab02dd1 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -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: diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index b198814c93..86d1b013e3 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -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() diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index fbae01d3e6..24ba2a1c7a 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -288,7 +288,7 @@ - 0 + 3 @@ -417,10 +417,48 @@ - + - + + + + 10 + 10 + + + + true + + + true + + + false + + + QAbstractItemView::DragDrop + + + true + + + QAbstractItemView::SelectRows + + + false + + + false + + + + + + + + + 10 diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py index e948360338..8d9801e306 100644 --- a/src/calibre/gui2/viewer/printing.py +++ b/src/calibre/gui2/viewer/printing.py @@ -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 diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 535ec4251f..886320aedb 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -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):