1) folder device fixes

2) added configuration of the folder device
3) fixed set_books_in_library to save/restore search state, necessary because it must scan the entire database, not just the search results.
4) removed the HTC driver
This commit is contained in:
Charles Haley 2010-05-16 09:21:41 +01:00
parent 8b197ebd66
commit 70a3207906
9 changed files with 78 additions and 102 deletions

View File

@ -455,7 +455,7 @@ from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3 from calibre.devices.teclast.driver import TECLAST_K3
from calibre.devices.sne.driver import SNE from calibre.devices.sne.driver import SNE
from calibre.devices.misc import PALMPRE, KOBO, AVANT 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.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon
from calibre.library.catalog import CSV_XML, EPUB_MOBI from calibre.library.catalog import CSV_XML, EPUB_MOBI
@ -540,7 +540,7 @@ plugins += [
PALMPRE, PALMPRE,
KOBO, KOBO,
AZBOOKA, AZBOOKA,
HTC_TD2, FOLDER_DEVICE_FOR_CONFIG,
AVANT, AVANT,
] ]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \

View File

@ -4,36 +4,48 @@ Created on 15 May 2010
@author: charles @author: charles
''' '''
import os import os
import time
from calibre.customize.ui import available_output_formats
from calibre.devices.usbms.driver import USBMS, BookList from calibre.devices.usbms.driver import USBMS, BookList
from calibre.devices.interface import DevicePlugin
from calibre.devices.usbms.deviceconfig import DeviceConfig # This class is added to the standard device plugin chain, so that it can
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to # 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): class FOLDER_DEVICE(USBMS):
type = _('Device Interface') 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'] FORMATS = ['epub', 'fb2', 'mobi', 'lrf', 'tcr', 'pmlz', 'lit', 'rtf', 'rb', 'pdf', 'oeb', 'txt', 'pdb']
THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device
# Whether the metadata on books can be set via the GUI.
CAN_SET_METADATA = True CAN_SET_METADATA = True
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
DELETE_EXTS = []
#: Path separator for paths to books on device
path_sep = os.sep
#: Icon for this device #: Icon for this device
icon = I('reader.svg') icon = I('sd.svg')
METADATA_CACHE = '.metadata.calibre' METADATA_CACHE = '.metadata.calibre'
_main_prefix = None _main_prefix = ''
_card_a_prefix = None _card_a_prefix = None
_card_b_prefix = None _card_b_prefix = None
is_connected = False
def __init__(self, path): def __init__(self, path):
if not os.path.isdir(path):
raise IOError, 'Path is not a folder'
self._main_prefix = path self._main_prefix = path
self.booklist_class = BookList self.booklist_class = BookList
self.is_connected = True self.is_connected = True
@ -47,6 +59,7 @@ class FOLDER_DEVICE(USBMS):
return cls.name return cls.name
def disconnect_from_folder(self): def disconnect_from_folder(self):
self._main_prefix = ''
self.is_connected = False self.is_connected = False
def is_usb_connected(self, devices_on_system, debug=False, def is_usb_connected(self, devices_on_system, debug=False,
@ -54,8 +67,8 @@ class FOLDER_DEVICE(USBMS):
return self.is_connected, self return self.is_connected, self
def open(self): def open(self):
if self._main_prefix is None: if not self._main_prefix:
raise NotImplementedError() return False
return True return True
def set_progress_reporter(self, report_progress): def set_progress_reporter(self, report_progress):
@ -64,11 +77,9 @@ class FOLDER_DEVICE(USBMS):
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
return (None, None) 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): def get_main_ebook_dir(self):
return '' return ''
@classmethod
def settings(self):
return FOLDER_DEVICE_FOR_CONFIG._config().parse()

View File

@ -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 <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__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

View File

@ -113,15 +113,17 @@ class Device(DeviceConfig, DevicePlugin):
def _windows_space(cls, prefix): def _windows_space(cls, prefix):
if not prefix: if not prefix:
return 0, 0 return 0, 0
if prefix.endswith(os.sep):
prefix = prefix[:-1]
win32file = __import__('win32file', globals(), locals(), [], -1) win32file = __import__('win32file', globals(), locals(), [], -1)
try: try:
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix[:-1]) win32file.GetDiskFreeSpace(prefix)
except Exception, err: except Exception, err:
if getattr(err, 'args', [None])[0] == 21: # Disk not ready if getattr(err, 'args', [None])[0] == 21: # Disk not ready
time.sleep(3) time.sleep(3)
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix[:-1]) win32file.GetDiskFreeSpace(prefix)
else: raise else: raise
mult = sectors_per_cluster * bytes_per_sector mult = sectors_per_cluster * bytes_per_sector
return total_clusters * mult, free_clusters * mult return total_clusters * mult, free_clusters * mult

View File

@ -428,23 +428,24 @@ class DeviceMenu(QMenu):
if opts.accounts: if opts.accounts:
self.addSeparator() self.addSeparator()
self.addMenu(self.email_to_menu) 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() self.addSeparator()
annot = self.addAction(_('Fetch annotations (experimental)')) annot = self.addAction(_('Fetch annotations (experimental)'))
annot.setEnabled(False) annot.setEnabled(False)
annot.triggered.connect(lambda x : annot.triggered.connect(lambda x :
self.fetch_annotations.emit()) self.fetch_annotations.emit())
self.annotation_action = annot 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) self.enable_device_actions(False)
def change_default_action(self, action): 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 # First build a cache of the library, so the search isn't On**2
self.db_book_title_cache = {} self.db_book_title_cache = {}
self.db_book_uuid_cache = set() self.db_book_uuid_cache = set()
for idx in range(self.library_view.model().db.count()): db = self.library_view.model().db
mi = self.library_view.model().db.get_metadata(idx, index_is_id=False) # 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()) title = re.sub('(?u)\W|[_]', '', mi.title.lower())
if title not in self.db_book_title_cache: if title not in self.db_book_title_cache:
self.db_book_title_cache[title] = {'authors':{}, 'db_ids':{}} 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]['authors'][authors] = mi
self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi self.db_book_title_cache[title]['db_ids'][mi.application_id] = mi
self.db_book_uuid_cache.add(mi.uuid) 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 # Now iterate through all the books on the device, setting the
# Fastest and most accurate key is the uuid. Second is the application_id, which # in_library field Fastest and most accurate key is the uuid. Second is
# is really the db key, but as this can accidentally match across libraries we # the application_id, which is really the db key, but as this can
# also verify the title. The db_id exists on Sony devices. Fallback is title # accidentally match across libraries we also verify the title. The
# and author match # db_id exists on Sony devices. Fallback is title and author match
resend_metadata = False resend_metadata = False
for booklist in booklists: for booklist in booklists:
for book in booklist: for book in booklist:
@ -1135,5 +1146,5 @@ class DeviceGUI(object):
resend_metadata = True resend_metadata = True
if resend_metadata: if resend_metadata:
# Correcting metadata cache on device. # 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) self.device_manager.sync_booklists(None, booklists)

View File

@ -669,15 +669,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def connect_to_folder(self): def connect_to_folder(self):
dir = choose_dir(self, 'Select Device Folder', 'Select folder to open') dir = choose_dir(self, 'Select Device Folder', 'Select folder to open')
if dir is not None: if dir is not None:
print dir
self.device_manager.connect_to_folder(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) self._sync_menu.disconnect_from_folder_action.setEnabled(True)
def disconnect_from_folder(self): def disconnect_from_folder(self):
self.device_manager.disconnect_folder() 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): def create_device_menu(self):
self._sync_menu = DeviceMenu(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) self.refresh_ondevice_info (device_connected = True, reset_only = True)
else: else:
self._sync_menu.connect_to_folder_action.setEnabled(True) self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self.save_device_view_settings() self.save_device_view_settings()
self.device_connected = False self.device_connected = False
self._sync_menu.enable_device_actions(False) self._sync_menu.enable_device_actions(False)

View File

@ -553,6 +553,14 @@ class ResultCache(SearchQueryParser):
if item is not None: if item is not None:
item[ondevice_col] = db.book_on_device_string(item[0]) 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): def refresh(self, db, field=None, ascending=True):
temp = db.conn.get('SELECT * FROM meta2') temp = db.conn.get('SELECT * FROM meta2')
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else [] self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []

View File

@ -245,6 +245,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.has_id = self.data.has_id self.has_id = self.data.has_id
self.count = self.data.count 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_ondevice = functools.partial(self.data.refresh_ondevice, self)
self.refresh() self.refresh()