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.config import to_json, from_json, prefs, tweaks
|
||||||
from calibre.utils.date import utcfromtimestamp, parse_date
|
from calibre.utils.date import utcfromtimestamp, parse_date
|
||||||
from calibre.utils.filenames import (
|
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.magick.draw import save_cover_data_to
|
||||||
from calibre.utils.formatter_functions import load_user_template_functions
|
from calibre.utils.formatter_functions import load_user_template_functions
|
||||||
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
|
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
|
Read all data from the db into the python in-memory tables
|
||||||
'''
|
'''
|
||||||
|
|
||||||
with self.conn: # Use a single transaction, to ensure nothing modifies
|
with self.conn: # Use a single transaction, to ensure nothing modifies the db while we are reading
|
||||||
# the db while we are reading
|
|
||||||
for table in self.tables.itervalues():
|
for table in self.tables.itervalues():
|
||||||
try:
|
try:
|
||||||
table.read(self)
|
table.read(self)
|
||||||
@ -1538,11 +1538,7 @@ class DB(object):
|
|||||||
if permanent:
|
if permanent:
|
||||||
for path in paths:
|
for path in paths:
|
||||||
self.rmtree(path)
|
self.rmtree(path)
|
||||||
try:
|
remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True)
|
||||||
os.rmdir(os.path.dirname(path))
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno != errno.ENOTEMPTY:
|
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
delete_service().delete_books(paths, self.library_path)
|
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))
|
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]
|
vals = [(book_id, fmt, size, name) for fmt, size, name in formats]
|
||||||
self.executemany('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', vals)
|
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 Queue import Queue
|
||||||
|
|
||||||
from calibre.ptempfile import remove_dir
|
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
|
from calibre.utils.recycle_bin import delete_tree, delete_file
|
||||||
|
|
||||||
class DeleteService(Thread):
|
class DeleteService(Thread):
|
||||||
@ -93,10 +94,10 @@ class DeleteService(Thread):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
shutil.move(path, dest)
|
shutil.move(path, dest)
|
||||||
if delete_empty_parent:
|
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)
|
requests.append(dest)
|
||||||
if not requests:
|
if not requests:
|
||||||
self.remove_dir_if_empty(tdir)
|
remove_dir_if_empty(tdir)
|
||||||
else:
|
else:
|
||||||
self.requests.put(tdir)
|
self.requests.put(tdir)
|
||||||
|
|
||||||
|
@ -231,6 +231,8 @@ class AddRemoveTest(BaseTest):
|
|||||||
fmtpath = cache.format_abspath(1, 'FMT1')
|
fmtpath = cache.format_abspath(1, 'FMT1')
|
||||||
bookpath = os.path.dirname(fmtpath)
|
bookpath = os.path.dirname(fmtpath)
|
||||||
authorpath = os.path.dirname(bookpath)
|
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']
|
item_id = {v:k for k, v in cache.fields['#series'].table.id_map.iteritems()}['My Series Two']
|
||||||
cache.remove_books((1,), permanent=True)
|
cache.remove_books((1,), permanent=True)
|
||||||
for x in (fmtpath, bookpath, authorpath):
|
for x in (fmtpath, bookpath, authorpath):
|
||||||
|
@ -459,3 +459,34 @@ def atomic_rename(oldpath, newpath):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
os.rename(oldpath, newpath)
|
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