mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
8b197ebd66
commit
70a3207906
@ -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 \
|
||||||
|
@ -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()
|
||||||
|
@ -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'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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 []
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user