diff --git a/src/calibre/gui2/viewer/convert_book.py b/src/calibre/gui2/viewer/convert_book.py index 5022e5036b..a1f75479f9 100644 --- a/src/calibre/gui2/viewer/convert_book.py +++ b/src/calibre/gui2/viewer/convert_book.py @@ -90,21 +90,46 @@ def expire_cache(path, instances, max_age): instances.remove(instance) -def expire_cache_and_temp(temp_path, finished_path, metadata, max_age): +def expire_old_versions(path, instances): + instances = filter(lambda x: x['status'] == 'finished', instances) + remove = sorted(instances, key=lambda x: x['atime'], reverse=True)[1:] + for instance in remove: + if robust_rmtree(os.path.join(path, instance['path'])): + yield instance + + +def expire_cache_and_temp(temp_path, finished_path, metadata, max_age, force_expire): now = time.time() - if now - metadata['last_clear_at'] < DAY and max_age >= 0: + if now - metadata['last_clear_at'] < DAY and max_age >= 0 and not force_expire: return clear_temp(temp_path) entries = metadata['entries'] + path_key_map = {} for key, instances in tuple(entries.items()): if instances: expire_cache(finished_path, instances, max_age) if not instances: del entries[key] + else: + for x in instances: + book_path = x.get('book_path') + if book_path: + path_key_map.setdefault(book_path, []).append(key) + for keys in path_key_map.values(): + instances = [] + for key in keys: + instances += entries[key] + if len(instances) > 1: + removed = tuple(expire_old_versions(finished_path, instances)) + if removed: + for r in removed: + entries[r['key']].remove(r) + if not entries[r['key']]: + del entries[r['key']] metadata['last_clear_at'] = now -def prepare_convert(temp_path, key, st): +def prepare_convert(temp_path, key, st, book_path): tdir = tempfile.mkdtemp(dir=temp_path) now = time.time() return { @@ -117,6 +142,7 @@ def prepare_convert(temp_path, key, st): 'file_mtime': st.st_mtime, 'file_size': st.st_size, 'cache_size': 0, + 'book_path': book_path, } @@ -170,7 +196,7 @@ def save_metadata(metadata, f): f.seek(0), f.truncate(), f.write(as_bytes(json.dumps(metadata, indent=2))) -def prepare_book(path, convert_func=do_convert, max_age=30 * DAY, force=False, prepare_notify=None): +def prepare_book(path, convert_func=do_convert, max_age=30 * DAY, force=False, prepare_notify=None, force_expire=False): st = os.stat(path) key = book_hash(path, st.st_size, st.st_mtime) finished_path = safe_makedirs(os.path.join(book_cache_dir(), 'f')) @@ -194,7 +220,7 @@ def prepare_book(path, convert_func=do_convert, max_age=30 * DAY, force=False, p return os.path.join(finished_path, instance['path']) if prepare_notify: prepare_notify() - instance = prepare_convert(temp_path, key, st) + instance = prepare_convert(temp_path, key, st, path) instances.append(instance) save_metadata(metadata, f) convert_func(path, temp_path, key, instance) @@ -220,7 +246,7 @@ def prepare_book(path, convert_func=do_convert, max_age=30 * DAY, force=False, p if q['id'] == instance['id']: q.update(instance) break - expire_cache_and_temp(temp_path, finished_path, metadata, max_age) + expire_cache_and_temp(temp_path, finished_path, metadata, max_age, force_expire) save_metadata(metadata, f) return ans @@ -304,6 +330,23 @@ def find_tests(): prepare_book(book_src, convert_func=convert_mock, max_age=-1000) self.ae([], os.listdir(os.path.join(book_cache_dir(), 'f'))) + # Test modifying a book and opening it repeatedly leaves only + # a single entry for it in the cache + prepare_book(book_src, convert_func=convert_mock, force_expire=True) + finished_entries = os.listdir(os.path.join(book_cache_dir(), 'f')) + self.ae(len(finished_entries), 1) + open(book_src, 'wb').write(b'bcde') + prepare_book(book_src, convert_func=convert_mock, force_expire=True) + new_finished_entries = os.listdir(os.path.join(book_cache_dir(), 'f')) + self.ae(len(new_finished_entries), 1) + self.assertNotEqual(finished_entries, new_finished_entries) + open(book_src, 'wb').write(b'bcdef') + prepare_book(book_src, convert_func=convert_mock, max_age=-1000, force_expire=True) + self.ae([], os.listdir(os.path.join(book_cache_dir(), 'f'))) + with cache_lock() as f: + metadata = json.loads(f.read()) + self.assertEqual(metadata['entries'], {}) + # Test updating cached book book_src = os.path.join(self.tdir, 'book2.epub') open(book_src, 'wb').write(b'bb')