mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
When deleting books from the calibre library, fix empty author folders not being deleted if they contain system generated cache files, like .DS_Store or Thumbs.db. Fixes #1313452 [[Enhancement] Delete author folders upon removal of only book](https://bugs.launchpad.net/calibre/+bug/1313452)
This commit is contained in:
parent
7c3354caf9
commit
cebe46bc0e
@ -27,7 +27,8 @@ from calibre.utils.icu import sort_key
|
||||
from calibre.utils.config import to_json, from_json, prefs, tweaks
|
||||
from calibre.utils.date import utcfromtimestamp, parse_date
|
||||
from calibre.utils.filenames import (
|
||||
is_case_sensitive, samefile, hardlink_file, ascii_filename, WindowsAtomicFolderMove, atomic_rename)
|
||||
is_case_sensitive, samefile, hardlink_file, ascii_filename,
|
||||
WindowsAtomicFolderMove, atomic_rename, remove_dir_if_empty)
|
||||
from calibre.utils.magick.draw import save_cover_data_to
|
||||
from calibre.utils.formatter_functions import load_user_template_functions
|
||||
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
|
||||
@ -1184,8 +1185,7 @@ class DB(object):
|
||||
Read all data from the db into the python in-memory tables
|
||||
'''
|
||||
|
||||
with self.conn: # Use a single transaction, to ensure nothing modifies
|
||||
# the db while we are reading
|
||||
with self.conn: # Use a single transaction, to ensure nothing modifies the db while we are reading
|
||||
for table in self.tables.itervalues():
|
||||
try:
|
||||
table.read(self)
|
||||
@ -1538,11 +1538,7 @@ class DB(object):
|
||||
if permanent:
|
||||
for path in paths:
|
||||
self.rmtree(path)
|
||||
try:
|
||||
os.rmdir(os.path.dirname(path))
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOTEMPTY:
|
||||
raise
|
||||
remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True)
|
||||
else:
|
||||
delete_service().delete_books(paths, self.library_path)
|
||||
|
||||
@ -1666,6 +1662,4 @@ class DB(object):
|
||||
self.execute('UPDATE books SET path=? WHERE id=?', (path.replace(os.sep, '/'), book_id))
|
||||
vals = [(book_id, fmt, size, name) for fmt, size, name in formats]
|
||||
self.executemany('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', vals)
|
||||
# }}}
|
||||
|
||||
|
||||
# }}}
|
||||
|
@ -11,6 +11,7 @@ from threading import Thread
|
||||
from Queue import Queue
|
||||
|
||||
from calibre.ptempfile import remove_dir
|
||||
from calibre.utils.filenames import remove_dir_if_empty
|
||||
from calibre.utils.recycle_bin import delete_tree, delete_file
|
||||
|
||||
class DeleteService(Thread):
|
||||
@ -93,10 +94,10 @@ class DeleteService(Thread):
|
||||
time.sleep(1)
|
||||
shutil.move(path, dest)
|
||||
if delete_empty_parent:
|
||||
self.remove_dir_if_empty(os.path.dirname(path))
|
||||
remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True)
|
||||
requests.append(dest)
|
||||
if not requests:
|
||||
self.remove_dir_if_empty(tdir)
|
||||
remove_dir_if_empty(tdir)
|
||||
else:
|
||||
self.requests.put(tdir)
|
||||
|
||||
|
@ -231,6 +231,8 @@ class AddRemoveTest(BaseTest):
|
||||
fmtpath = cache.format_abspath(1, 'FMT1')
|
||||
bookpath = os.path.dirname(fmtpath)
|
||||
authorpath = os.path.dirname(bookpath)
|
||||
os.mkdir(os.path.join(authorpath, '.DS_Store'))
|
||||
open(os.path.join(authorpath, 'Thumbs.db'), 'wb').close()
|
||||
item_id = {v:k for k, v in cache.fields['#series'].table.id_map.iteritems()}['My Series Two']
|
||||
cache.remove_books((1,), permanent=True)
|
||||
for x in (fmtpath, bookpath, authorpath):
|
||||
|
@ -459,3 +459,34 @@ def atomic_rename(oldpath, newpath):
|
||||
time.sleep(1)
|
||||
else:
|
||||
os.rename(oldpath, newpath)
|
||||
|
||||
def remove_dir_if_empty(path, ignore_metadata_caches=False):
|
||||
''' Remove a directory if it is empty or contains only the folder metadata
|
||||
caches from different OSes. To delete the folder if it contains only
|
||||
metadata caches, set ignore_metadata_caches to True.'''
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOTEMPTY or len(os.listdir(path)) > 0:
|
||||
# Some linux systems appear to raise an EPERM instead of an
|
||||
# ENOTEMPTY, see https://bugs.launchpad.net/bugs/1240797
|
||||
if ignore_metadata_caches:
|
||||
try:
|
||||
found = False
|
||||
for x in os.listdir(path):
|
||||
if x.lower() in {'.ds_store', 'thumbs.db'}:
|
||||
found = True
|
||||
x = os.path.join(path, x)
|
||||
if os.path.isdir(x):
|
||||
import shutil
|
||||
shutil.rmtree(x)
|
||||
else:
|
||||
os.remove(x)
|
||||
except Exception: # We could get an error, if, for example, windows has locked Thumbs.db
|
||||
found = False
|
||||
if found:
|
||||
remove_dir_if_empty(path)
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user