mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
On device indication, temporary implementation
This commit is contained in:
commit
6b6dcf8c40
@ -55,7 +55,13 @@ class JETBOOK(USBMS):
|
||||
au = mi.format_authors()
|
||||
if not au:
|
||||
au = 'Unknown'
|
||||
return '%s#%s%s' % (au, title, fileext)
|
||||
suffix = ''
|
||||
if getattr(mi, 'application_id', None) is not None:
|
||||
base = fname.rpartition('.')[0]
|
||||
suffix = '_%s'%mi.application_id
|
||||
if base.endswith(suffix):
|
||||
suffix = ''
|
||||
return '%s#%s%s%s' % (au, title, fileext, suffix)
|
||||
|
||||
@classmethod
|
||||
def metadata_from_path(cls, path):
|
||||
|
@ -60,11 +60,6 @@ class KINDLE(USBMS):
|
||||
'replace')
|
||||
return mi
|
||||
|
||||
def filename_callback(self, fname, mi):
|
||||
if fname.startswith('.'):
|
||||
return 'x'+fname[1:]
|
||||
return fname
|
||||
|
||||
def get_annotations(self, path_map):
|
||||
MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt']
|
||||
mbp_formats = set(MBP_FORMATS)
|
||||
|
@ -121,14 +121,6 @@ class PRS505(CLI, Device):
|
||||
self.report_progress(1.0, _('Getting list of books on device...'))
|
||||
return bl
|
||||
|
||||
def filename_callback(self, fname, mi):
|
||||
if getattr(mi, 'application_id', None) is not None:
|
||||
base = fname.rpartition('.')[0]
|
||||
suffix = '_%s'%mi.application_id
|
||||
if not base.endswith(suffix):
|
||||
fname = base + suffix + '.' + fname.rpartition('.')[-1]
|
||||
return fname
|
||||
|
||||
def upload_books(self, files, names, on_card=None, end_session=True,
|
||||
metadata=None):
|
||||
|
||||
|
@ -46,6 +46,12 @@ class Book(object):
|
||||
return self.title.encode('utf-8') + " by " + \
|
||||
self.authors.encode('utf-8') + " at " + self.path.encode('utf-8')
|
||||
|
||||
@property
|
||||
def db_id(self):
|
||||
'''The database id in the application database that this file corresponds to'''
|
||||
match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0])
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
|
||||
class BookList(_BookList):
|
||||
|
||||
|
@ -784,8 +784,14 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
def filename_callback(self, default, mi):
|
||||
'''
|
||||
Callback to allow drivers to change the default file name
|
||||
set by :method:`create_upload_path`.
|
||||
set by :method:`create_upload_path`. By default, add the DB_ID
|
||||
to the end of the string. Helps with ondevice doc matching
|
||||
'''
|
||||
if getattr(mi, 'application_id', None) is not None:
|
||||
base = default.rpartition('.')[0]
|
||||
suffix = '_%s'%mi.application_id
|
||||
if not base.endswith(suffix):
|
||||
default = base + suffix + '.' + default.rpartition('.')[-1]
|
||||
return default
|
||||
|
||||
def sanitize_path_components(self, components):
|
||||
@ -826,7 +832,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
if not isinstance(template, unicode):
|
||||
template = template.decode('utf-8')
|
||||
app_id = str(getattr(mdata, 'application_id', ''))
|
||||
# The SONY readers need to have the db id in the created filename
|
||||
# The db id will be in the created filename
|
||||
extra_components = get_components(template, mdata, fname,
|
||||
length=250-len(app_id)-1)
|
||||
if not extra_components:
|
||||
@ -835,6 +841,9 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
else:
|
||||
extra_components[-1] = sanitize(self.filename_callback(extra_components[-1]+ext, mdata))
|
||||
|
||||
if extra_components[-1] and extra_components[-1][0] in ('.', '_'):
|
||||
extra_components[-1] = 'x' + extra_components[-1][1:]
|
||||
|
||||
if special_tag is not None:
|
||||
name = extra_components[-1]
|
||||
extra_components = []
|
||||
|
@ -25,7 +25,7 @@ NONE = QVariant() #: Null value to return from the data function of item models
|
||||
UNDEFINED_QDATE = QDate(UNDEFINED_DATE)
|
||||
|
||||
ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher',
|
||||
'tags', 'series', 'pubdate']
|
||||
'tags', 'series', 'pubdate', 'ondevice']
|
||||
|
||||
def _config():
|
||||
c = Config('gui', 'preferences for the calibre GUI')
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, traceback, Queue, time, socket, cStringIO
|
||||
import os, traceback, Queue, time, socket, cStringIO, re
|
||||
from threading import Thread, RLock
|
||||
from itertools import repeat
|
||||
from functools import partial
|
||||
@ -978,3 +978,69 @@ class DeviceGUI(object):
|
||||
getattr(f, 'close', lambda : True)()
|
||||
if memory and memory[1]:
|
||||
self.library_view.model().delete_books_by_id(memory[1])
|
||||
|
||||
def book_on_device(self, index, format=None, reset=False):
|
||||
loc = [None, None, None]
|
||||
|
||||
if reset:
|
||||
self.book_on_device_cache = None
|
||||
return
|
||||
|
||||
if self.book_on_device_cache is None:
|
||||
self.book_on_device_cache = []
|
||||
for i, l in enumerate(self.booklists()):
|
||||
self.book_on_device_cache.append({})
|
||||
for book in l:
|
||||
book_title = book.title.lower() if book.title else ''
|
||||
book_title = re.sub('(?u)\W|[_]', '', book_title)
|
||||
if book_title not in self.book_on_device_cache[i]:
|
||||
self.book_on_device_cache[i][book_title] = \
|
||||
{'authors':set(), 'db_ids':set()}
|
||||
book_authors = authors_to_string(book.authors).lower()
|
||||
book_authors = re.sub('(?u)\W|[_]', '', book_authors)
|
||||
self.book_on_device_cache[i][book_title]['authors'].add(book_authors)
|
||||
self.book_on_device_cache[i][book_title]['db_ids'].add(book.db_id)
|
||||
|
||||
db_title = self.library_view.model().db.title(index, index_is_id=True).lower()
|
||||
db_title = re.sub('(?u)\W|[_]', '', db_title)
|
||||
au = self.library_view.model().db.authors(index, index_is_id=True)
|
||||
db_authors = au.lower() if au else ''
|
||||
db_authors = re.sub('(?u)\W|[_]', '', db_authors)
|
||||
for i, l in enumerate(self.booklists()):
|
||||
d = self.book_on_device_cache[i].get(db_title, None)
|
||||
if d and (index in d['db_ids'] or db_authors in d['authors']):
|
||||
loc[i] = True
|
||||
break
|
||||
return loc
|
||||
|
||||
def set_books_in_library(self, booklist, reset = False):
|
||||
if reset:
|
||||
self.book_in_library_cache = None
|
||||
return
|
||||
|
||||
# First build a self.book_in_library_cache of the library, so the search isn't On**2
|
||||
self.book_in_library_cache = {}
|
||||
for id, title in self.library_view.model().db.all_titles():
|
||||
title = re.sub('(?u)\W|[_]', '', title.lower())
|
||||
if title not in self.book_in_library_cache:
|
||||
self.book_in_library_cache[title] = {'authors':set(), 'db_ids':set()}
|
||||
au = self.library_view.model().db.authors(id, index_is_id=True)
|
||||
authors = au.lower() if au else ''
|
||||
authors = re.sub('(?u)\W|[_]', '', authors)
|
||||
self.book_in_library_cache[title]['authors'].add(authors)
|
||||
self.book_in_library_cache[title]['db_ids'].add(id)
|
||||
|
||||
# Now iterate through all the books on the device, setting the in_library field
|
||||
for book in booklist:
|
||||
book_title = book.title.lower() if book.title else ''
|
||||
book_title = re.sub('(?u)\W|[_]', '', book_title)
|
||||
book.in_library = False
|
||||
d = self.book_in_library_cache.get(book_title, None)
|
||||
if d is not None:
|
||||
if book.db_id in d['db_ids']:
|
||||
book.in_library = True
|
||||
continue
|
||||
book_authors = authors_to_string(book.authors).lower() if book.authors else ''
|
||||
book_authors = re.sub('(?u)\W|[_]', '', book_authors)
|
||||
if book_authors in d['authors']:
|
||||
book.in_library = True
|
||||
|
@ -319,11 +319,13 @@ class BooksModel(QAbstractTableModel):
|
||||
'publisher' : _("Publisher"),
|
||||
'tags' : _("Tags"),
|
||||
'series' : _("Series"),
|
||||
'ondevice' : _("On Device"),
|
||||
}
|
||||
|
||||
def __init__(self, parent=None, buffer=40):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
self.db = None
|
||||
self.book_on_device = None
|
||||
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
|
||||
'tags', 'series', 'timestamp', 'pubdate']
|
||||
self.default_image = QImage(I('book.svg'))
|
||||
@ -362,6 +364,9 @@ class BooksModel(QAbstractTableModel):
|
||||
self.reset()
|
||||
self.emit(SIGNAL('columns_sorted()'))
|
||||
|
||||
def set_book_on_device_func(self, func):
|
||||
self.book_on_device = func
|
||||
|
||||
def set_database(self, db):
|
||||
self.db = db
|
||||
self.custom_columns = self.db.custom_column_label_map
|
||||
@ -802,6 +807,8 @@ class BooksModel(QAbstractTableModel):
|
||||
'series' : functools.partial(series,
|
||||
idx=self.db.FIELD_MAP['series'],
|
||||
siix=self.db.FIELD_MAP['series_index']),
|
||||
'ondevice' : functools.partial(text_type,
|
||||
idx=self.db.FIELD_MAP['ondevice'], mult=False),
|
||||
}
|
||||
self.dc_decorator = {}
|
||||
|
||||
@ -1258,6 +1265,7 @@ class DeviceBooksModel(BooksModel):
|
||||
self.marked_for_deletion = {}
|
||||
self.search_engine = OnDeviceSearch(self)
|
||||
self.editable = True
|
||||
self.book_in_library = None
|
||||
|
||||
def mark_for_deletion(self, job, rows):
|
||||
self.marked_for_deletion[job] = self.indices(rows)
|
||||
@ -1345,8 +1353,11 @@ class DeviceBooksModel(BooksModel):
|
||||
def tagscmp(x, y):
|
||||
x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags)
|
||||
return cmp(x, y)
|
||||
def libcmp(x, y):
|
||||
x, y = self.db[x].in_library, self.db[y].in_library
|
||||
return cmp(x, y)
|
||||
fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \
|
||||
sizecmp if col == 2 else datecmp if col == 3 else tagscmp
|
||||
sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp
|
||||
self.map.sort(cmp=fcmp, reverse=descending)
|
||||
if len(self.map) == len(self.db):
|
||||
self.sorted_map = list(self.map)
|
||||
@ -1360,7 +1371,7 @@ class DeviceBooksModel(BooksModel):
|
||||
def columnCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
return 0
|
||||
return 5
|
||||
return 6
|
||||
|
||||
def rowCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
@ -1401,7 +1412,6 @@ class DeviceBooksModel(BooksModel):
|
||||
'''
|
||||
return [ self.map[r.row()] for r in rows]
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
row, col = index.row(), index.column()
|
||||
@ -1417,7 +1427,7 @@ class DeviceBooksModel(BooksModel):
|
||||
if role == Qt.EditRole:
|
||||
return QVariant(au)
|
||||
authors = string_to_authors(au)
|
||||
return QVariant("\n".join(authors))
|
||||
return QVariant(" & ".join(authors))
|
||||
elif col == 2:
|
||||
size = self.db[self.map[row]].size
|
||||
return QVariant(BooksView.human_readable(size))
|
||||
@ -1429,6 +1439,9 @@ class DeviceBooksModel(BooksModel):
|
||||
tags = self.db[self.map[row]].tags
|
||||
if tags:
|
||||
return QVariant(', '.join(tags))
|
||||
elif col == 5:
|
||||
return QVariant(_('Yes')) \
|
||||
if self.db[self.map[row]].in_library else QVariant(_('No'))
|
||||
elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
|
||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
||||
elif role == Qt.ToolTipRole and index.isValid():
|
||||
@ -1449,6 +1462,7 @@ class DeviceBooksModel(BooksModel):
|
||||
elif section == 2: text = _("Size (MB)")
|
||||
elif section == 3: text = _("Date")
|
||||
elif section == 4: text = _("Tags")
|
||||
elif section == 5: text = _("In Library")
|
||||
return QVariant(text)
|
||||
else:
|
||||
return QVariant(section+1)
|
||||
@ -1482,4 +1496,3 @@ class DeviceBooksModel(BooksModel):
|
||||
|
||||
def set_search_restriction(self, s):
|
||||
pass
|
||||
|
||||
|
@ -520,7 +520,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||
self.hide_windows()
|
||||
self.stack.setCurrentIndex(0)
|
||||
self.book_on_device(None, reset=True)
|
||||
db.set_book_on_device_func(self.book_on_device)
|
||||
self.library_view.set_database(db)
|
||||
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
||||
prefs['library_path'] = self.library_path
|
||||
self.library_view.restore_sort_at_startup(dynamic.get('sort_history', [('timestamp', Qt.DescendingOrder)]))
|
||||
if not self.library_view.restore_column_widths():
|
||||
@ -956,6 +959,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.status_bar.reset_info()
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
self.eject_action.setEnabled(False)
|
||||
self.refresh_ondevice_info (clear_info = True)
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
@ -988,12 +992,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
else:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
self.set_books_in_library(None, reset=True)
|
||||
mainlist, cardalist, cardblist = job.result
|
||||
self.memory_view.set_database(mainlist)
|
||||
self.set_books_in_library(mainlist)
|
||||
self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
self.card_a_view.set_database(cardalist)
|
||||
self.set_books_in_library(cardalist)
|
||||
self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
self.card_b_view.set_database(cardblist)
|
||||
self.set_books_in_library(cardblist)
|
||||
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
for view in (self.memory_view, self.card_a_view, self.card_b_view):
|
||||
view.sortByColumn(3, Qt.DescendingOrder)
|
||||
@ -1001,8 +1009,19 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
if not view.restore_column_widths():
|
||||
view.resizeColumnsToContents()
|
||||
view.resize_on_select = not view.isVisible()
|
||||
if view.model().rowCount(None) > 1:
|
||||
view.resizeRowToContents(0)
|
||||
height = view.rowHeight(0)
|
||||
view.verticalHeader().setDefaultSectionSize(height)
|
||||
self.sync_news()
|
||||
self.sync_catalogs()
|
||||
self.refresh_ondevice_info()
|
||||
|
||||
############################################################################
|
||||
### Force the library view to refresh, taking into consideration books information
|
||||
def refresh_ondevice_info(self, clear_flags = False):
|
||||
self.book_on_device(None, reset=True)
|
||||
self.library_view.model().refresh()
|
||||
############################################################################
|
||||
|
||||
######################### Fetch annotations ################################
|
||||
@ -2228,7 +2247,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
def library_moved(self, newloc):
|
||||
if newloc is None: return
|
||||
db = LibraryDatabase2(newloc)
|
||||
self.book_on_device(None, reset=True)
|
||||
db.set_book_on_device_func(self.book_on_device)
|
||||
self.library_view.set_database(db)
|
||||
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
||||
self.status_bar.clearMessage()
|
||||
self.search.clear_to_help()
|
||||
self.status_bar.reset_info()
|
||||
|
@ -370,7 +370,7 @@ class ResultCache(SearchQueryParser):
|
||||
location += 's'
|
||||
|
||||
all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series',
|
||||
'formats', 'isbn', 'rating', 'cover')
|
||||
'formats', 'isbn', 'rating', 'cover', 'ondevice')
|
||||
MAP = {}
|
||||
|
||||
for x in all: # get the db columns for the standard searchables
|
||||
@ -518,6 +518,7 @@ class ResultCache(SearchQueryParser):
|
||||
try:
|
||||
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
|
||||
self._data[id].append(db.has_cover(id, index_is_id=True))
|
||||
self._data[id].append(db.book_on_device_string(id))
|
||||
except IndexError:
|
||||
return None
|
||||
try:
|
||||
@ -533,6 +534,7 @@ class ResultCache(SearchQueryParser):
|
||||
for id in ids:
|
||||
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
|
||||
self._data[id].append(db.has_cover(id, index_is_id=True))
|
||||
self._data[id].append(db.book_on_device_string(id))
|
||||
self._map[0:0] = ids
|
||||
self._map_filtered[0:0] = ids
|
||||
|
||||
@ -553,6 +555,7 @@ class ResultCache(SearchQueryParser):
|
||||
for item in self._data:
|
||||
if item is not None:
|
||||
item.append(db.has_cover(item[0], index_is_id=True))
|
||||
item.append(db.book_on_device_string(item[0]))
|
||||
self._map = [i[0] for i in self._data if i is not None]
|
||||
if field is not None:
|
||||
self.sort(field, ascending)
|
||||
|
@ -1070,6 +1070,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
return [ (i[0], i[1]) for i in \
|
||||
self.conn.get('SELECT id, name FROM tags')]
|
||||
|
||||
def all_titles(self):
|
||||
return [ (i[0], i[1]) for i in \
|
||||
self.conn.get('SELECT id, title FROM books')]
|
||||
|
||||
def conversion_options(self, id, format):
|
||||
data = self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper()), all=False)
|
||||
|
@ -222,6 +222,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.FIELD_MAP[col] = base = base+1
|
||||
|
||||
self.FIELD_MAP['cover'] = base+1
|
||||
self.FIELD_MAP['ondevice'] = base+2
|
||||
|
||||
script = '''
|
||||
DROP VIEW IF EXISTS meta2;
|
||||
@ -233,6 +234,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.conn.executescript(script)
|
||||
self.conn.commit()
|
||||
|
||||
self.book_on_device_func = None
|
||||
self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map)
|
||||
self.search = self.data.search
|
||||
self.refresh = functools.partial(self.data.refresh, self)
|
||||
@ -468,6 +470,27 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
im = PILImage.open(f)
|
||||
im.convert('RGB').save(path, 'JPEG')
|
||||
|
||||
def book_on_device(self, index):
|
||||
if callable(self.book_on_device_func):
|
||||
return self.book_on_device_func(index)
|
||||
return None
|
||||
|
||||
def book_on_device_string(self, index):
|
||||
loc = []
|
||||
on = self.book_on_device(index)
|
||||
if on is not None:
|
||||
m, a, b = on
|
||||
if m is not None:
|
||||
loc.append(_('Main'))
|
||||
if a is not None:
|
||||
loc.append(_('Card A'))
|
||||
if b is not None:
|
||||
loc.append(_('Card B'))
|
||||
return ', '.join(loc)
|
||||
|
||||
def set_book_on_device_func(self, func):
|
||||
self.book_on_device_func = func
|
||||
|
||||
def all_formats(self):
|
||||
formats = self.conn.get('SELECT DISTINCT format from data')
|
||||
if not formats:
|
||||
|
@ -100,6 +100,7 @@ class SearchQueryParser(object):
|
||||
'search',
|
||||
'date',
|
||||
'pubdate',
|
||||
'ondevice',
|
||||
'all',
|
||||
]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user