diff --git a/src/calibre/devices/mtp/books.py b/src/calibre/devices/mtp/books.py index 73e483f19e..a72fc1f84e 100644 --- a/src/calibre/devices/mtp/books.py +++ b/src/calibre/devices/mtp/books.py @@ -22,6 +22,22 @@ class BookList(BL): def supports_collections(self): return False + def add_book(self, book, replace_metadata=True): + try: + b = self.index(book) + except (ValueError, IndexError): + b = None + if b is None: + self.append(book) + return book + if replace_metadata: + self[b].smart_update(book, replace_metadata=True) + return self[b] + return None + + def remove_book(self, book): + self.remove(book) + class Book(Metadata): def __init__(self, storage_id, lpath, other=None): @@ -36,6 +52,17 @@ class Book(Metadata): return (self.storage_id == mtp_file.storage_id and self.mtp_relpath == mtp_file.mtp_relpath) + def __eq__(self, other): + return (isinstance(other, self.__class__) and (self.storage_id == + other.storage_id and self.mtp_relpath == other.mtp_relpath)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.storage_id, self.mtp_relpath)) + + class JSONCodec(JsonCodec): pass diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index 21419960ae..68539f334b 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -191,6 +191,7 @@ class MTP_DEVICE(BASE): self.put_file(storage, self.METADATA_CACHE, stream, size) def sync_booklists(self, booklists, end_session=True): + debug('sync_booklists() called') for bl in booklists: if getattr(bl, 'storage_id', None) is None: continue @@ -198,6 +199,7 @@ class MTP_DEVICE(BASE): if storage is None: continue self.write_metadata_cache(storage, bl) + debug('sync_booklists() ended') # }}} @@ -249,15 +251,117 @@ class MTP_DEVICE(BASE): # TODO: Implement this return 'calibre' + def ensure_parent(self, storage, path): + parent = storage + pos = list(path)[:-1] + while pos: + name = pos[0] + pos = pos[1:] + parent = self.create_folder(parent, name) + return parent + def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): + debug('upload_books() called') from calibre.devices.utils import sanity_check sanity_check(on_card, files, self.card_prefix(), self.free_space()) prefix = self.prefix_for_location(on_card) + sid = {'carda':self._carda_id, 'cardb':self._cardb_id}.get(on_card, + self._main_id) + bl_idx = {'carda':1, 'cardb':2}.get(on_card, 0) + storage = self.filesystem_cache.storage(sid) + + ans = [] + self.report_progress(0, _('Transferring books to device...')) + i, total = 0, len(files) + for infile, fname, mi in izip(files, names, metadata): path = self.create_upload_path(prefix, mi, fname) - print (1111111, path) - raise NotImplementedError() + parent = self.ensure_parent(storage, path) + if hasattr(infile, 'read'): + pos = infile.tell() + infile.seek(0, 2) + sz = infile.tell() + infile.seek(pos) + stream = infile + close = False + else: + sz = os.path.getsize(infile) + stream = lopen(infile, 'rb') + close = True + try: + mtp_file = self.put_file(parent, path[-1], stream, sz) + finally: + if close: + stream.close() + ans.append((mtp_file, bl_idx)) + i += 1 + self.report_progress(i/total, _('Transferred %s to device')%mi.title) + + self.report_progress(1, _('Transfer to device finished...')) + debug('upload_books() ended') + return ans + + def add_books_to_metadata(self, mtp_files, metadata, booklists): + debug('add_books_to_metadata() called') + from calibre.devices.mtp.books import Book + + i, total = 0, len(mtp_files) + self.report_progress(0, _('Adding books to device metadata listing...')) + for x, mi in izip(mtp_files, metadata): + mtp_file, bl_idx = x + bl = booklists[bl_idx] + book = Book(mtp_file.storage_id, '/'.join(mtp_file.mtp_relpath), + other=mi) + book = bl.add_book(book, replace_metadata=True) + if book is not None: + book.size = mtp_file.size + book.datetime = mtp_file.last_modified.timetuple() + book.path = mtp_file.mtp_id_path + i += 1 + self.report_progress(i/total, _('Added %s')%mi.title) + + self.report_progress(1, _('Adding complete')) + debug('add_books_to_metadata() ended') + + # }}} + + # Removing books from the device {{{ + def recursive_delete(self, obj): + parent = self.delete_file_or_folder(obj) + if parent.empty and parent.can_delete and not parent.is_system: + try: + self.recursive_delete(parent) + except: + prints('Failed to delete parent: %s, ignoring'%( + '/'.join(parent.full_path))) + + def delete_books(self, paths, end_session=True): + self.report_progress(0, _('Deleting books from device...')) + + for i, path in enumerate(paths): + f = self.filesystem_cache.resolve_mtp_id_path(path) + self.recursive_delete(f) + self.report_progress((i+1) / float(len(paths)), + _('Deleted %s')%path) + self.report_progress(1, _('All books deleted')) + + def remove_books_from_metadata(self, paths, booklists): + self.report_progress(0, _('Removing books from metadata')) + class NextPath(Exception): pass + + for i, path in enumerate(paths): + try: + for bl in booklists: + for book in bl: + if book.path == path: + bl.remove_book(book) + raise NextPath('') + except NextPath: + pass + self.report_progress((i+1)/len(paths), _('Removed %s')%path) + + self.report_progress(1, _('All books removed')) # }}} diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 2f215f6353..b59ec22110 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -306,6 +306,7 @@ class MTP_DEVICE(MTPDeviceBase): raise DeviceError('Failed to delete %s with error: %s'% (obj.full_path, self.format_errorstack(errs))) parent.remove_child(obj) + return parent def develop(): from calibre.devices.scanner import DeviceScanner diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index 7c15797ef6..63fedfaf66 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -338,6 +338,7 @@ class MTP_DEVICE(MTPDeviceBase): parent = obj.parent self.dev.delete_object(obj.object_id) parent.remove_child(obj) + return parent @same_thread def put_file(self, parent, name, stream, size, callback=None, replace=True): diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 8eb4df3fbd..4cc4c0fb5f 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1495,8 +1495,12 @@ class DeviceMixin(object): # {{{ self.device_job_exception(job) return - self.device_manager.add_books_to_metadata(job.result, - metadata, self.booklists()) + try: + self.device_manager.add_books_to_metadata(job.result, + metadata, self.booklists()) + except: + traceback.print_exc() + raise books_to_be_deleted = [] if memory and memory[1]: