Preserve notes resource mtimes when importing notes db

This commit is contained in:
Kovid Goyal 2023-10-16 10:35:41 +05:30
parent 53f20a2ce6
commit 1e9a543e54
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 23 additions and 10 deletions

View File

@ -989,8 +989,8 @@ class DB:
id_val = self.tables[field].id_map[item_id] id_val = self.tables[field].id_map[item_id]
return self.notes.unretire(self.conn, field, item_id, id_val) return self.notes.unretire(self.conn, field, item_id, id_val)
def add_notes_resource(self, path_or_stream, name) -> int: def add_notes_resource(self, path_or_stream, name, mtime=None) -> int:
return self.notes.add_resource(self.conn, path_or_stream, name) return self.notes.add_resource(self.conn, path_or_stream, name, mtime=mtime)
def get_notes_resource(self, resource_hash) -> Optional[dict]: def get_notes_resource(self, resource_hash) -> Optional[dict]:
return self.notes.get_resource_data(self.conn, resource_hash) return self.notes.get_resource_data(self.conn, resource_hash)

View File

@ -20,7 +20,7 @@ from functools import partial, wraps
from io import DEFAULT_BUFFER_SIZE, BytesIO from io import DEFAULT_BUFFER_SIZE, BytesIO
from queue import Queue from queue import Queue
from threading import Lock from threading import Lock
from time import monotonic, sleep, time from time import mktime, monotonic, sleep, time
from typing import NamedTuple, Optional, Tuple from typing import NamedTuple, Optional, Tuple
from calibre import as_unicode, detect_ncpus, isbytestring from calibre import as_unicode, detect_ncpus, isbytestring
@ -724,9 +724,9 @@ class Cache:
return self.backend.set_notes_for(field, item_id, doc, searchable_text, resource_hashes, remove_unused_resources) return self.backend.set_notes_for(field, item_id, doc, searchable_text, resource_hashes, remove_unused_resources)
@write_api @write_api
def add_notes_resource(self, path_or_stream_or_data, name: str) -> int: def add_notes_resource(self, path_or_stream_or_data, name: str, mtime: float = None) -> int:
' Add the specified resource so it can be referenced by notes and return its content hash ' ' Add the specified resource so it can be referenced by notes and return its content hash '
return self.backend.add_notes_resource(path_or_stream_or_data, name) return self.backend.add_notes_resource(path_or_stream_or_data, name, mtime)
@read_api @read_api
def get_notes_resource(self, resource_hash) -> Optional[dict]: def get_notes_resource(self, resource_hash) -> Optional[dict]:
@ -3460,7 +3460,10 @@ def import_library(library_key, importer, library_path, progress=None, abort=Non
with closing(importer.start_file(metadata['notes.db'], 'notes.db for ' + library_path)) as stream: with closing(importer.start_file(metadata['notes.db'], 'notes.db for ' + library_path)) as stream:
stream.check_hash = False stream.check_hash = False
with zipfile.ZipFile(stream) as zf: with zipfile.ZipFile(stream) as zf:
zf.extractall(notes_dir) for zi in zf.infolist():
tpath = zf._extract_member(zi, notes_dir, None)
date_time = mktime(zi.date_time + (0, 0, -1))
os.utime(tpath, (date_time, date_time))
if abort is not None and abort.is_set(): if abort is not None and abort.is_set():
return return
cache = Cache(DB(library_path, load_user_formatter_functions=False)) cache = Cache(DB(library_path, load_user_formatter_functions=False))

View File

@ -306,7 +306,7 @@ class Notes:
for path in items[:extra]: for path in items[:extra]:
remove_with_retry(path, is_dir=True) remove_with_retry(path, is_dir=True)
def add_resource(self, conn, path_or_stream_or_data, name, update_name=True): def add_resource(self, conn, path_or_stream_or_data, name, update_name=True, mtime=None):
if isinstance(path_or_stream_or_data, bytes): if isinstance(path_or_stream_or_data, bytes):
data = path_or_stream_or_data data = path_or_stream_or_data
elif isinstance(path_or_stream_or_data, str): elif isinstance(path_or_stream_or_data, str):
@ -332,6 +332,9 @@ class Notes:
f = open(path, 'wb') f = open(path, 'wb')
with f: with f:
f.write(data) f.write(data)
if mtime is not None:
os.utime(f.name, (mtime, mtime))
name = sanitize_file_name(name) name = sanitize_file_name(name)
base_name, ext = os.path.splitext(name) base_name, ext = os.path.splitext(name)
c = 0 c = 0

View File

@ -264,8 +264,8 @@ class FilesystemTest(BaseTest):
bookdir = os.path.dirname(ic.format_abspath(1, '__COVER_INTERNAL__')) bookdir = os.path.dirname(ic.format_abspath(1, '__COVER_INTERNAL__'))
self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read())
self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read())
r1 = cache.add_notes_resource(b'res1', 'res.jpg') r1 = cache.add_notes_resource(b'res1', 'res.jpg', mtime=time.time()-113)
r2 = cache.add_notes_resource(b'res2', 'res.jpg') r2 = cache.add_notes_resource(b'res2', 'res.jpg', mtime=time.time()-1115)
cache.set_notes_for('authors', 2, 'some notes', resource_hashes=(r1, r2)) cache.set_notes_for('authors', 2, 'some notes', resource_hashes=(r1, r2))
cache.add_format(1, 'TXT', BytesIO(b'testing exim')) cache.add_format(1, 'TXT', BytesIO(b'testing exim'))
cache.fts_indexing_sleep_time = 0.001 cache.fts_indexing_sleep_time = 0.001
@ -285,7 +285,10 @@ class FilesystemTest(BaseTest):
ic = import_library('l', importer, idir) ic = import_library('l', importer, idir)
self.assertEqual(ic.fts_search('exim')[0]['id'], 1) self.assertEqual(ic.fts_search('exim')[0]['id'], 1)
self.assertEqual(cache.notes_for('authors', 2), ic.notes_for('authors', 2)) self.assertEqual(cache.notes_for('authors', 2), ic.notes_for('authors', 2))
self.assertEqual(cache.get_notes_resource(r1), ic.get_notes_resource(r1)) a, b = cache.get_notes_resource(r1), ic.get_notes_resource(r1)
at, bt, = a.pop('mtime'), b.pop('mtime')
self.assertEqual(a, b)
self.assertLess(abs(at-bt), 2)
def test_find_books_in_directory(self): def test_find_books_in_directory(self):
from calibre.db.adding import find_books_in_directory, compile_rule from calibre.db.adding import find_books_in_directory, compile_rule

View File

@ -151,6 +151,10 @@ def test_cache_api(self: 'NotesTest'):
self.assertGreater(note_id, 0) self.assertGreater(note_id, 0)
self.assertIn('<p>test simple exim <img', cache.notes_for('authors', author_id)) self.assertIn('<p>test simple exim <img', cache.notes_for('authors', author_id))
res2 = tuple(cache.get_notes_resource(x) for x in cache.notes_resources_used_by('authors', author_id)) res2 = tuple(cache.get_notes_resource(x) for x in cache.notes_resources_used_by('authors', author_id))
for x in res:
del x['mtime']
for x in res2:
del x['mtime']
self.ae(sorted(res, key=itemgetter('name')), sorted(res2, key=itemgetter('name'))) self.ae(sorted(res, key=itemgetter('name')), sorted(res2, key=itemgetter('name')))