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:
Kovid Goyal 2014-05-01 10:13:37 +05:30
parent 7c3354caf9
commit cebe46bc0e
4 changed files with 41 additions and 13 deletions

View File

@ -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)
# }}}
# }}}

View File

@ -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)

View File

@ -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):

View File

@ -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