Blob Blame History Raw
From 3a6ff39aef0d08211f4c8945f1db60f31b1aec97 Mon Sep 17 00:00:00 2001
From: Kovid Goyal <kovid@kovidgoyal.net>
Date: Sun, 11 Aug 2019 19:23:50 +0530
Subject: [PATCH 56/71] Speed up restoring original format by doing a rename
 rather than a copy and re-add. Fixes #1839733 [Restore pre conversion
 originals very slow](https://bugs.launchpad.net/calibre/+bug/1839733)

---
 src/calibre/db/backend.py      | 10 ++++++++++
 src/calibre/db/cache.py        | 20 +++++++++++++-------
 src/calibre/db/tests/legacy.py | 14 --------------
 3 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py
index 9e5ae66f1e..b7719d925f 100644
--- a/src/calibre/db/backend.py
+++ b/src/calibre/db/backend.py
@@ -1341,6 +1341,16 @@ class DB(object):
     def has_format(self, book_id, fmt, fname, path):
         return self.format_abspath(book_id, fmt, fname, path) is not None
 
+    def is_format_accessible(self, book_id, fmt, fname, path):
+        fpath = self.format_abspath(book_id, fmt, fname, path)
+        return fpath and os.access(fpath, os.R_OK | os.W_OK)
+
+    def rename_format_file(self, book_id, src_fname, src_fmt, dest_fname, dest_fmt, path):
+        src_path = self.format_abspath(book_id, src_fmt, src_fname, path)
+        dest_path = self.format_abspath(book_id, dest_fmt, dest_fname, path)
+        atomic_rename(src_path, dest_path)
+        return os.path.getsize(dest_path)
+
     def remove_formats(self, remove_map):
         paths = []
         for book_id, removals in iteritems(remove_map):
diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index e17787412c..11e02d083b 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -795,18 +795,24 @@ class Cache(object):
             nfmt = 'ORIGINAL_'+fmt
             return self.add_format(book_id, nfmt, fmtfile, run_hooks=False)
 
-    @api
+    @write_api
     def restore_original_format(self, book_id, original_fmt):
         ''' Restore the specified format from the previously saved
         ORIGINAL_FORMAT, if any. Return True on success. The ORIGINAL_FORMAT is
         deleted after a successful restore. '''
         original_fmt = original_fmt.upper()
-        fmtfile = self.format(book_id, original_fmt, as_file=True)
-        if fmtfile is not None:
-            fmt = original_fmt.partition('_')[2]
-            with fmtfile:
-                self.add_format(book_id, fmt, fmtfile, run_hooks=False)
-            self.remove_formats({book_id:(original_fmt,)})
+        fmt = original_fmt.partition('_')[2]
+        try:
+            ofmt_name = self.fields['formats'].format_fname(book_id, original_fmt)
+            path = self._field_for('path', book_id).replace('/', os.sep)
+        except Exception:
+            return False
+        if self.backend.is_format_accessible(book_id, original_fmt, ofmt_name, path):
+            self.add_format(book_id, fmt, BytesIO(), run_hooks=False)
+            fmt_name = self.fields['formats'].format_fname(book_id, fmt)
+            file_size = self.backend.rename_format_file(book_id, ofmt_name, original_fmt, fmt_name, fmt, path)
+            self.fields['formats'].table.update_fmt(book_id, fmt, fmt_name, file_size, self.backend)
+            self._remove_formats({book_id:(original_fmt,)})
             return True
         return False
 
diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py
index 6394c02fe1..bfe9cf61a3 100644
--- a/src/calibre/db/tests/legacy.py
+++ b/src/calibre/db/tests/legacy.py
@@ -793,20 +793,6 @@ class LegacyTest(BaseTest):
         self.assertEqual(ndb.new_api.field_for('#series_index', 1), 9)
     # }}}
 
-    def test_legacy_original_fmt(self):  # {{{
-        db, ndb = self.init_old(), self.init_legacy()
-        run_funcs(self, db, ndb, (
-            ('original_fmt', 1, 'FMT1'),
-            ('save_original_format', 1, 'FMT1'),
-            ('original_fmt', 1, 'FMT1'),
-            ('restore_original_format', 1, 'ORIGINAL_FMT1'),
-            ('original_fmt', 1, 'FMT1'),
-            ('%formats', 1, True),
-        ))
-        db.close()
-
-    # }}}
-
     def test_legacy_saved_search(self):  # {{{
         ' Test legacy saved search API '
         db, ndb = self.init_old(), self.init_legacy()