mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
formats API
This commit is contained in:
parent
9ec8aac3f4
commit
3e47e065ee
@ -8,7 +8,7 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
# Imports {{{
|
||||
import os, shutil, uuid, json, glob, time, cPickle
|
||||
import os, shutil, uuid, json, glob, time, cPickle, hashlib
|
||||
from functools import partial
|
||||
|
||||
import apsw
|
||||
@ -17,7 +17,9 @@ from calibre import isbytestring, force_unicode, prints
|
||||
from calibre.constants import (iswindows, filesystem_encoding,
|
||||
preferred_encoding)
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.db import SPOOL_SIZE
|
||||
from calibre.db.schema_upgrades import SchemaUpgrade
|
||||
from calibre.db.errors import NoSuchFormat
|
||||
from calibre.library.field_metadata import FieldMetadata
|
||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||
from calibre.utils.icu import sort_key
|
||||
@ -926,6 +928,19 @@ class DB(object):
|
||||
shutil.copyfile(candidates[0], fmt_path)
|
||||
return fmt_path
|
||||
|
||||
def format_hash(self, book_id, fmt, fname, path):
|
||||
path = self.format_abspath(book_id, fmt, fname, path)
|
||||
if path is None:
|
||||
raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt))
|
||||
sha = hashlib.sha256()
|
||||
with lopen(path, 'rb') as f:
|
||||
while True:
|
||||
raw = f.read(SPOOL_SIZE)
|
||||
sha.update(raw)
|
||||
if len(raw) < SPOOL_SIZE:
|
||||
break
|
||||
return sha.hexdigest()
|
||||
|
||||
def format_metadata(self, book_id, fmt, fname, path):
|
||||
path = self.format_abspath(book_id, fmt, fname, path)
|
||||
ans = {}
|
||||
|
@ -408,7 +408,16 @@ class Cache(object):
|
||||
return {aid:af.author_data(aid) for aid in author_ids if aid in af.table.id_map}
|
||||
|
||||
@read_api
|
||||
def format_metadata(self, book_id, fmt, allow_cache=True):
|
||||
def format_hash(self, book_id, fmt):
|
||||
try:
|
||||
name = self.fields['formats'].format_fname(book_id, fmt)
|
||||
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||
except:
|
||||
raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt))
|
||||
return self.backend.format_hash(book_id, fmt, name, path)
|
||||
|
||||
@api
|
||||
def format_metadata(self, book_id, fmt, allow_cache=True, update_db=False):
|
||||
if not fmt:
|
||||
return {}
|
||||
fmt = fmt.upper()
|
||||
@ -416,6 +425,7 @@ class Cache(object):
|
||||
x = self.format_metadata_cache[book_id].get(fmt, None)
|
||||
if x is not None:
|
||||
return x
|
||||
with self.read_lock:
|
||||
try:
|
||||
name = self.fields['formats'].format_fname(book_id, fmt)
|
||||
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||
@ -426,8 +436,19 @@ class Cache(object):
|
||||
if path and name:
|
||||
ans = self.backend.format_metadata(book_id, fmt, name, path)
|
||||
self.format_metadata_cache[book_id][fmt] = ans
|
||||
if update_db and 'size' in ans:
|
||||
with self.write_lock:
|
||||
max_size = self.fields['formats'].table.update_fmt(book_id, fmt, name, ans['size'], self.backend)
|
||||
self.fields['size'].table.update_sizes({book_id: max_size})
|
||||
|
||||
return ans
|
||||
|
||||
@read_api
|
||||
def format_files(self, book_id):
|
||||
field = self.fields['formats']
|
||||
fmts = field.table.book_col_map.get(book_id, ())
|
||||
return {fmt:field.format_fname(book_id, fmt) for fmt in fmts}
|
||||
|
||||
@read_api
|
||||
def pref(self, name, default=None):
|
||||
return self.backend.prefs.get(name, default)
|
||||
@ -524,6 +545,7 @@ class Cache(object):
|
||||
the path is different from the current path (taking case sensitivity
|
||||
into account).
|
||||
'''
|
||||
fmt = (fmt or '').upper()
|
||||
try:
|
||||
name = self.fields['formats'].format_fname(book_id, fmt)
|
||||
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||
@ -544,6 +566,7 @@ class Cache(object):
|
||||
Apart from the viewer, I don't believe any of the others do any file
|
||||
I/O with the results of this call.
|
||||
'''
|
||||
fmt = (fmt or '').upper()
|
||||
try:
|
||||
name = self.fields['formats'].format_fname(book_id, fmt)
|
||||
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||
@ -555,6 +578,7 @@ class Cache(object):
|
||||
@read_api
|
||||
def has_format(self, book_id, fmt):
|
||||
'Return True iff the format exists on disk'
|
||||
fmt = (fmt or '').upper()
|
||||
try:
|
||||
name = self.fields['formats'].format_fname(book_id, fmt)
|
||||
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||
@ -601,6 +625,7 @@ class Cache(object):
|
||||
this means that repeated calls yield the same
|
||||
temp file (which is re-created each time)
|
||||
'''
|
||||
fmt = (fmt or '').upper()
|
||||
ext = ('.'+fmt.lower()) if fmt else ''
|
||||
if as_path:
|
||||
if preserve_filename:
|
||||
|
@ -17,6 +17,7 @@ from calibre.db.adding import (
|
||||
import_book_directory, recursive_import, add_catalog, add_news)
|
||||
from calibre.db.backend import DB
|
||||
from calibre.db.cache import Cache
|
||||
from calibre.db.errors import NoSuchFormat
|
||||
from calibre.db.categories import CATEGORY_SORTS
|
||||
from calibre.db.view import View
|
||||
from calibre.db.write import clean_identifier
|
||||
@ -436,6 +437,43 @@ class LibraryDatabase(object):
|
||||
def has_id(self, book_id):
|
||||
return book_id in self.new_api.all_book_ids()
|
||||
|
||||
def format(self, index, fmt, index_is_id=False, as_file=False, mode='r+b', as_path=False, preserve_filename=False):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
return self.new_api.format(book_id, fmt, as_file=as_file, as_path=as_path, preserve_filename=preserve_filename)
|
||||
|
||||
def format_abspath(self, index, fmt, index_is_id=False):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
return self.new_api.format_abspath(book_id, fmt)
|
||||
|
||||
def format_path(self, index, fmt, index_is_id=False):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
ans = self.new_api.format_abspath(book_id, fmt)
|
||||
if ans is None:
|
||||
raise NoSuchFormat('Record %d has no format: %s'%(book_id, fmt))
|
||||
return ans
|
||||
|
||||
def format_files(self, index, index_is_id=False):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
return [(v, k) for k, v in self.new_api.format_files(book_id).iteritems()]
|
||||
|
||||
def format_metadata(self, book_id, fmt, allow_cache=True, update_db=False, commit=False):
|
||||
return self.new_api.format_metadata(book_id, fmt, allow_cache=allow_cache, update_db=update_db)
|
||||
|
||||
def format_last_modified(self, book_id, fmt):
|
||||
m = self.format_metadata(book_id, fmt)
|
||||
if m:
|
||||
return m['mtime']
|
||||
|
||||
def formats(self, index, index_is_id=False, verify_formats=True):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
ans = self.new_api.formats(book_id, verify_formats=verify_formats)
|
||||
if ans:
|
||||
return ','.join(ans)
|
||||
|
||||
def has_format(self, index, fmt, index_is_id=False):
|
||||
book_id = index if index_is_id else self.id(index)
|
||||
return self.new_api.has_format(book_id, fmt)
|
||||
|
||||
# Private interface {{{
|
||||
def __iter__(self):
|
||||
for row in self.data.iterall():
|
||||
@ -463,6 +501,7 @@ for prop in ('author_sort', 'authors', 'comment', 'comments', 'publisher',
|
||||
return func
|
||||
setattr(LibraryDatabase, prop, MT(getter(prop)))
|
||||
|
||||
LibraryDatabase.format_hash = MT(lambda self, book_id, fmt:self.new_api.format_hash(book_id, fmt))
|
||||
LibraryDatabase.index = MT(lambda self, book_id, cache=False:self.data.id_to_index(book_id))
|
||||
LibraryDatabase.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id))
|
||||
LibraryDatabase.get_tags = MT(lambda self, book_id:set(self.new_api.field_for('tags', book_id)))
|
||||
|
@ -11,6 +11,7 @@ from io import BytesIO
|
||||
from repr import repr
|
||||
from functools import partial
|
||||
from tempfile import NamedTemporaryFile
|
||||
from operator import itemgetter
|
||||
|
||||
from calibre.db.tests.base import BaseTest
|
||||
|
||||
@ -159,6 +160,11 @@ class LegacyTest(BaseTest):
|
||||
|
||||
for meth, args in {
|
||||
'get_next_series_num_for': [('A Series One',)],
|
||||
'format':[(1, 'FMT1', True), (2, 'FMT1', True), (0, 'xxxxxx')],
|
||||
'has_format':[(1, 'FMT1', True), (2, 'FMT1', True), (0, 'xxxxxx')],
|
||||
'@format_files':[(0,),(1,),(2,)],
|
||||
'formats':[(0,),(1,),(2,)],
|
||||
'format_hash':[(1, 'FMT1'),(1, 'FMT2'), (2, 'FMT1')],
|
||||
'author_sort_from_authors': [(['Author One', 'Author Two', 'Unknown'],)],
|
||||
'has_book':[(Metadata('title one'),), (Metadata('xxxx1111'),)],
|
||||
'has_id':[(1,), (2,), (3,), (9999,)],
|
||||
@ -330,6 +336,7 @@ class LegacyTest(BaseTest):
|
||||
'author_id', # replaced by get_author_id
|
||||
'books_for_author', # broken
|
||||
'books_in_old_database', # unused
|
||||
'migrate_old', # no longer supported
|
||||
|
||||
# Internal API
|
||||
'clean_user_categories', 'cleanup_tags', 'books_list_filter', 'conn', 'connect', 'construct_file_name',
|
||||
@ -337,6 +344,7 @@ class LegacyTest(BaseTest):
|
||||
'run_import_plugins', 'vacuum', 'set_path', 'row', 'row_factory', 'rows', 'rmtree', 'series_index_pat',
|
||||
'import_old_database', 'dirtied_lock', 'dirtied_cache', 'dirty_queue_length', 'dirty_books_referencing',
|
||||
'windows_check_if_files_in_use', 'get_metadata_for_dump', 'get_a_dirtied_book', 'dirtied_sequence',
|
||||
'format_filename_cache', 'format_metadata_cache', 'filter', 'create_version1',
|
||||
}
|
||||
SKIP_ARGSPEC = {
|
||||
'__init__',
|
||||
@ -411,6 +419,9 @@ class LegacyTest(BaseTest):
|
||||
ndb = self.init_legacy(self.cloned_library)
|
||||
db = self.init_old(self.cloned_library)
|
||||
run_funcs(self, db, ndb, (
|
||||
('+format_metadata', 1, 'FMT1', itemgetter('size')),
|
||||
('+format_metadata', 1, 'FMT2', itemgetter('size')),
|
||||
('+format_metadata', 2, 'FMT1', itemgetter('size')),
|
||||
('get_tags', 0), ('get_tags', 1), ('get_tags', 2),
|
||||
('is_tag_used', 'News'), ('is_tag_used', 'xchkjgfh'),
|
||||
('bulk_modify_tags', (1,), ['t1'], ['News']),
|
||||
|
Loading…
x
Reference in New Issue
Block a user