API for renaming extra files

This commit is contained in:
Kovid Goyal 2023-08-02 18:22:28 +05:30
parent 5ee255c3e5
commit 3742aa4364
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 44 additions and 0 deletions

View File

@ -1933,6 +1933,22 @@ class DB:
with src:
yield relpath, src, stat_result
def rename_extra_file(self, relpath, newrelpath, book_path, replace=True):
bookdir = os.path.join(self.library_path, book_path)
src = os.path.abspath(os.path.join(bookdir, relpath))
dest = os.path.abspath(os.path.join(bookdir, newrelpath))
src, dest = make_long_path_useable(src), make_long_path_useable(dest)
if src == dest or not os.path.exists(src):
return False
if not replace and os.path.exists(dest) and not os.path.samefile(src, dest):
return False
try:
os.replace(src, dest)
except FileNotFoundError:
os.makedirs(os.path.dirname(dest), exist_ok=True)
os.replace(src, dest)
return True
def add_extra_file(self, relpath, stream, book_path, replace=True, auto_rename=False):
bookdir = os.path.join(self.library_path, book_path)
dest = os.path.abspath(os.path.join(bookdir, relpath))

View File

@ -3093,6 +3093,17 @@ class Cache:
self._clear_extra_files_cache(book_id)
return added
@write_api
def rename_extra_files(self, book_id, map_of_relpath_to_new_relpath, replace=False):
' Rename extra data files '
path = self._field_for('path', book_id).replace('/', os.sep)
renamed = set()
for relpath, newrelpath in map_of_relpath_to_new_relpath.items():
if self.backend.rename_extra_file(relpath, newrelpath, path, replace):
renamed.add(relpath)
self._clear_extra_files_cache(book_id)
return renamed
@write_api
def merge_extra_files(self, dest_id, src_ids, replace=False):
' Merge the extra files from src_ids into dest_id. Conflicting files are auto-renamed unless replace=True in which case they are replaced. '

View File

@ -137,6 +137,23 @@ class FilesystemTest(BaseTest):
if x.is_dir():
self.assertTrue(os.listdir(x.path))
def test_rename_of_extra_files(self):
cl = self.cloned_library
cache = self.init_cache(cl)
cache.add_extra_files(1, {'a': BytesIO(b'aaa'), 'b': BytesIO(b'bbb')})
def relpaths():
return {e.relpath for e in cache.list_extra_files(1)}
self.assertEqual(relpaths(), {'a', 'b'})
self.assertEqual(cache.rename_extra_files(1, {'a': 'data/c'}), {'a'})
self.assertEqual(relpaths(), {'data/c', 'b'})
self.assertEqual(cache.rename_extra_files(1, {'b': 'B'}), {'b'})
self.assertEqual(relpaths(), {'data/c', 'B'})
self.assertEqual(cache.rename_extra_files(1, {'B': 'data/c'}), set())
self.assertEqual(cache.rename_extra_files(1, {'B': 'data/c'}, replace=True), {'B'})
@unittest.skipUnless(iswindows, 'Windows only')
def test_windows_atomic_move(self):
'Test book file open in another process when changing metadata'