diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 1ad6c03fc2..6865954440 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -455,7 +455,7 @@ from calibre.devices.edge.driver import EDGE from calibre.devices.teclast.driver import TECLAST_K3 from calibre.devices.sne.driver import SNE from calibre.devices.misc import PALMPRE, KOBO, AVANT -from calibre.devices.htc_td2.driver import HTC_TD2 +from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon from calibre.library.catalog import CSV_XML, EPUB_MOBI @@ -540,7 +540,7 @@ plugins += [ PALMPRE, KOBO, AZBOOKA, - HTC_TD2, + FOLDER_DEVICE_FOR_CONFIG, AVANT, ] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/devices/folder_device/driver.py b/src/calibre/devices/folder_device/driver.py index 700b7f3eec..31da69d49a 100644 --- a/src/calibre/devices/folder_device/driver.py +++ b/src/calibre/devices/folder_device/driver.py @@ -4,36 +4,48 @@ Created on 15 May 2010 @author: charles ''' import os -import time -from calibre.customize.ui import available_output_formats from calibre.devices.usbms.driver import USBMS, BookList -from calibre.devices.interface import DevicePlugin -from calibre.devices.usbms.deviceconfig import DeviceConfig -from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to + +# This class is added to the standard device plugin chain, so that it can +# be configured. It has invalid vendor_id etc, so it will never match a +# device. The 'real' FOLDER_DEVICE will use the config from it. +class FOLDER_DEVICE_FOR_CONFIG(USBMS): + name = 'Folder Device Interface' + gui_name = 'Folder Device' + description = _('Use an arbitrary folder as a device.') + author = 'John Schember/Charles Haley' + supported_platforms = ['windows', 'osx', 'linux'] + FORMATS = ['epub', 'fb2', 'mobi', 'lrf', 'tcr', 'pmlz', 'lit', 'rtf', 'rb', 'pdf', 'oeb', 'txt', 'pdb'] class FOLDER_DEVICE(USBMS): type = _('Device Interface') - # Ordered list of supported formats + name = 'Folder Device Interface' + gui_name = 'Folder Device' + description = _('Use an arbitrary folder as a device.') + author = 'John Schember/Charles Haley' + supported_platforms = ['windows', 'osx', 'linux'] FORMATS = ['epub', 'fb2', 'mobi', 'lrf', 'tcr', 'pmlz', 'lit', 'rtf', 'rb', 'pdf', 'oeb', 'txt', 'pdb'] THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device - # Whether the metadata on books can be set via the GUI. + CAN_SET_METADATA = True SUPPORTS_SUB_DIRS = True - DELETE_EXTS = [] - #: Path separator for paths to books on device - path_sep = os.sep + #: Icon for this device - icon = I('reader.svg') + icon = I('sd.svg') METADATA_CACHE = '.metadata.calibre' - _main_prefix = None + _main_prefix = '' _card_a_prefix = None _card_b_prefix = None + is_connected = False + def __init__(self, path): + if not os.path.isdir(path): + raise IOError, 'Path is not a folder' self._main_prefix = path self.booklist_class = BookList self.is_connected = True @@ -47,6 +59,7 @@ class FOLDER_DEVICE(USBMS): return cls.name def disconnect_from_folder(self): + self._main_prefix = '' self.is_connected = False def is_usb_connected(self, devices_on_system, debug=False, @@ -54,8 +67,8 @@ class FOLDER_DEVICE(USBMS): return self.is_connected, self def open(self): - if self._main_prefix is None: - raise NotImplementedError() + if not self._main_prefix: + return False return True def set_progress_reporter(self, report_progress): @@ -64,11 +77,9 @@ class FOLDER_DEVICE(USBMS): def card_prefix(self, end_session=True): return (None, None) - def total_space(self, end_session=True): - return (1024*1024*1024, 0, 0) - - def free_space(self, end_session=True): - return (1024*1024*1024, 0, 0) - def get_main_ebook_dir(self): return '' + + @classmethod + def settings(self): + return FOLDER_DEVICE_FOR_CONFIG._config().parse() diff --git a/src/calibre/devices/htc_td2/__init__.py b/src/calibre/devices/htc_td2/__init__.py deleted file mode 100644 index 3d1a86922e..0000000000 --- a/src/calibre/devices/htc_td2/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import with_statement - -__license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - - - diff --git a/src/calibre/devices/htc_td2/driver.py b/src/calibre/devices/htc_td2/driver.py deleted file mode 100644 index 41eccfa0b2..0000000000 --- a/src/calibre/devices/htc_td2/driver.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- - -__license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from calibre.devices.usbms.driver import USBMS - -class HTC_TD2(USBMS): - - name = 'HTC TD2 Phone driver' - gui_name = 'HTC TD2' - description = _('Communicate with HTC TD2 phones.') - author = 'Charles Haley' - supported_platforms = ['osx', 'linux'] - - # Ordered list of supported formats - FORMATS = ['epub', 'pdf'] - - VENDOR_ID = { - # HTC -# 0x0bb4 : { 0x0c30 : [0x000]}, - 0xFbb4 : { 0x0c30 : [0x000]}, - } - EBOOK_DIR_MAIN = ['EBooks'] - EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to ' - 'send e-books to on the device. The first one that exists will ' - 'be used') - EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN) - - VENDOR_NAME = [''] - WINDOWS_MAIN_MEM = [''] - - MAIN_MEMORY_VOLUME_LABEL = 'HTC Phone Internal Memory' - - SUPPORTS_SUB_DIRS = True - - def post_open_callback(self): - opts = self.settings() - dirs = opts.extra_customization - if not dirs: - dirs = self.EBOOK_DIR_MAIN - else: - dirs = [x.strip() for x in dirs.split(',')] - self.EBOOK_DIR_MAIN = dirs diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 1b048d1bb6..249733b4e3 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -113,15 +113,17 @@ class Device(DeviceConfig, DevicePlugin): def _windows_space(cls, prefix): if not prefix: return 0, 0 + if prefix.endswith(os.sep): + prefix = prefix[:-1] win32file = __import__('win32file', globals(), locals(), [], -1) try: sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ - win32file.GetDiskFreeSpace(prefix[:-1]) + win32file.GetDiskFreeSpace(prefix) 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]) + win32file.GetDiskFreeSpace(prefix) else: raise mult = sectors_per_cluster * bytes_per_sector return total_clusters * mult, free_clusters * mult diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 1703e4a644..d6f1a7a205 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -428,23 +428,24 @@ class DeviceMenu(QMenu): if opts.accounts: self.addSeparator() self.addMenu(self.email_to_menu) + + self.addSeparator() + mitem = self.addAction(_('Connect to folder')) + mitem.setEnabled(True) + mitem.triggered.connect(lambda x : self.connect_to_folder.emit()) + self.connect_to_folder_action = mitem + + mitem = self.addAction(_('Disconnect from folder')) + mitem.setEnabled(False) + mitem.triggered.connect(lambda x : self.disconnect_from_folder.emit()) + self.disconnect_from_folder_action = mitem + self.addSeparator() annot = self.addAction(_('Fetch annotations (experimental)')) annot.setEnabled(False) annot.triggered.connect(lambda x : self.fetch_annotations.emit()) self.annotation_action = annot - - mitem = self.addAction(_('Connect to folder (experimental)')) - mitem.setEnabled(True) - mitem.triggered.connect(lambda x : self.connect_to_folder.emit()) - self.connect_to_folder_action = mitem - - mitem = self.addAction(_('Disconnect from folder (experimental)')) - mitem.setEnabled(False) - mitem.triggered.connect(lambda x : self.disconnect_from_folder.emit()) - self.disconnect_from_folder_action = mitem - self.enable_device_actions(False) def change_default_action(self, action): @@ -1089,8 +1090,17 @@ class DeviceGUI(object): # First build a cache of the library, so the search isn't On**2 self.db_book_title_cache = {} self.db_book_uuid_cache = set() - for idx in range(self.library_view.model().db.count()): - mi = self.library_view.model().db.get_metadata(idx, index_is_id=False) + db = self.library_view.model().db + # The following is a terrible hack, made necessary because the db + # result_cache will always use the results filtered by the current + # search. We need all the db entries here. Choice was to either + # cache the search results so we can use the entire db, to duplicate + # large parts of the get_metadata code, or to use db_ids and pay the + # large performance penalty of zillions of SQL queries. Choice: + # save/restore the search state. + state = db.get_state_before_scan() + for idx in range(db.count()): + mi = db.get_metadata(idx, index_is_id=False) title = re.sub('(?u)\W|[_]', '', mi.title.lower()) if title not in self.db_book_title_cache: self.db_book_title_cache[title] = {'authors':{}, 'db_ids':{}} @@ -1099,12 +1109,13 @@ class DeviceGUI(object): self.db_book_title_cache[title]['authors'][authors] = mi self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi self.db_book_uuid_cache.add(mi.uuid) + db.restore_state_after_scan(state) - # Now iterate through all the books on the device, setting the in_library field - # Fastest and most accurate key is the uuid. Second is the application_id, which - # is really the db key, but as this can accidentally match across libraries we - # also verify the title. The db_id exists on Sony devices. Fallback is title - # and author match + # Now iterate through all the books on the device, setting the + # in_library field Fastest and most accurate key is the uuid. Second is + # the application_id, which is really the db key, but as this can + # accidentally match across libraries we also verify the title. The + # db_id exists on Sony devices. Fallback is title and author match resend_metadata = False for booklist in booklists: for book in booklist: @@ -1135,5 +1146,5 @@ class DeviceGUI(object): resend_metadata = True if resend_metadata: # Correcting metadata cache on device. - if self.device_manager.is_connected: + if self.device_manager.is_device_connected: self.device_manager.sync_booklists(None, booklists) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 9bb89dec68..23a0490f14 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -669,15 +669,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def connect_to_folder(self): dir = choose_dir(self, 'Select Device Folder', 'Select folder to open') if dir is not None: - print dir self.device_manager.connect_to_folder(dir) - self._sync_menu.connect_to_folder_action.setEnabled(False) self._sync_menu.disconnect_from_folder_action.setEnabled(True) def disconnect_from_folder(self): self.device_manager.disconnect_folder() - self._sync_menu.connect_to_folder_action.setEnabled(True) - self._sync_menu.disconnect_from_folder_action.setEnabled(False) def create_device_menu(self): self._sync_menu = DeviceMenu(self) @@ -965,6 +961,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.refresh_ondevice_info (device_connected = True, reset_only = True) else: self._sync_menu.connect_to_folder_action.setEnabled(True) + self._sync_menu.disconnect_from_folder_action.setEnabled(False) self.save_device_view_settings() self.device_connected = False self._sync_menu.enable_device_actions(False) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index acc8eaffb6..73faa6f1ab 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -553,6 +553,14 @@ class ResultCache(SearchQueryParser): if item is not None: item[ondevice_col] = db.book_on_device_string(item[0]) + def get_state_before_scan(self): + retval = self._map_filtered + self._map_filtered = self._map + return retval + + def restore_state_after_scan(self, map_filtered): + self._map_filtered = map_filtered + def refresh(self, db, field=None, ascending=True): temp = db.conn.get('SELECT * FROM meta2') self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else [] diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 5971333078..063538656f 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -245,6 +245,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.has_id = self.data.has_id self.count = self.data.count + self.get_state_before_scan = self.data.get_state_before_scan + self.restore_state_after_scan = self.data.restore_state_after_scan self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self) self.refresh()