From 4fecafc890614156690d2b4aef5091d4776da013 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Fri, 16 Jan 2026 18:55:47 -0500 Subject: [PATCH] list books by file name as metadata is slow --- .../CrossPointReaderCalibrePlugin.zip | Bin 8855 -> 9536 bytes .../crosspoint_reader/plugin/driver.py | 95 ++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/calibre-plugin/crosspoint_reader/CrossPointReaderCalibrePlugin.zip b/calibre-plugin/crosspoint_reader/CrossPointReaderCalibrePlugin.zip index 61ec171d9f8876c92c7204e50db004a606c299ae..bf7f55d010ae5dd10c68ab23fcbac32b15b5a8f6 100644 GIT binary patch delta 3809 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 diff --git a/calibre-plugin/crosspoint_reader/plugin/driver.py b/calibre-plugin/crosspoint_reader/plugin/driver.py index 846206ff..bca803d3 100644 --- a/calibre-plugin/crosspoint_reader/plugin/driver.py +++ b/calibre-plugin/crosspoint_reader/plugin/driver.py @@ -33,6 +33,10 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): MUST_READ_METADATA = False MANAGES_DEVICE_PRESENCE = True DEVICE_PLUGBOARD_NAME = 'CROSSPOINT_READER' + MUST_READ_METADATA = False + SUPPORTS_DEVICE_DB = False + # Disable Calibre's device cache so we always refresh from device. + device_is_usb_mass_storage = False def __init__(self, path): super().__init__(path) @@ -210,6 +214,15 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): else: filepath = infile filename = os.path.basename(name) + lpath = upload_path + if not lpath.startswith('/'): + lpath = '/' + lpath + if lpath != '/' and lpath.endswith('/'): + lpath = lpath[:-1] + if lpath == '/': + lpath = '/' + filename + else: + lpath = lpath + '/' + filename def _progress(sent, size): if size > 0: @@ -227,11 +240,21 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): progress_cb=_progress, logger=self._log, ) - paths.append((filename, os.path.getsize(filepath))) + paths.append((lpath, os.path.getsize(filepath))) self.report_progress(1.0, 'Transferring books to device...') return paths + def add_books_to_metadata(self, locations, metadata, booklists): + metadata = iter(metadata) + for location in locations: + info = next(metadata) + lpath = location[0] + length = location[1] + book = Book('', lpath, size=length, other=info) + if booklists: + booklists[0].add_book(book, replace_metadata=True) + def add_books_to_metadata(self, locations, metadata, booklists): # No on-device catalog to update yet. return @@ -241,13 +264,73 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): status, body = self._http_post_form('/delete', {'path': path, 'type': 'file'}) if status != 200: raise ControlError(desc=f'Delete failed for {path}: {body}') + self._log(f'[CrossPoint] deleted {path}') def remove_books_from_metadata(self, paths, booklists): - for path in paths: - for bl in booklists: - for book in tuple(bl): - if path == book.path or path == book.lpath: - bl.remove_book(book) + def norm(p): + if not p: + return '' + p = p.replace('\\', '/') + if not p.startswith('/'): + p = '/' + p + return p + + def norm_name(p): + if not p: + return '' + name = os.path.basename(p) + try: + import unicodedata + name = unicodedata.normalize('NFKC', name) + except Exception: + pass + name = name.replace('\u2019', "'").replace('\u2018', "'") + return name.casefold() + + device_names = set() + try: + entries = self._http_get_json('/api/files', params={'path': '/'}) + on_device = set() + for entry in entries: + if entry.get('isDirectory'): + continue + name = entry.get('name', '') + if not name: + continue + on_device.add(norm(name)) + on_device.add(norm('/' + name)) + device_names.add(norm_name(name)) + self._log(f'[CrossPoint] on-device list: {sorted(on_device)}') + except Exception as exc: + self._log(f'[CrossPoint] refresh list failed: {exc}') + on_device = None + + removed = 0 + for bl in booklists: + for book in tuple(bl): + bpath = norm(getattr(book, 'path', '')) + blpath = norm(getattr(book, 'lpath', '')) + self._log(f'[CrossPoint] book paths: {bpath} | {blpath}') + should_remove = False + if on_device is not None: + if device_names: + if norm_name(bpath) not in device_names and norm_name(blpath) not in device_names: + should_remove = True + elif bpath and bpath not in on_device and blpath and blpath not in on_device: + should_remove = True + else: + for path in paths: + target = norm(path) + target_name = os.path.basename(target) + if target == bpath or target == blpath: + should_remove = True + elif target_name and (os.path.basename(bpath) == target_name or os.path.basename(blpath) == target_name): + should_remove = True + if should_remove: + bl.remove_book(book) + removed += 1 + if removed: + self._log(f'[CrossPoint] removed {removed} items from device list') def get_file(self, path, outfile, end_session=True, this_book=None, total_books=None): url = self._http_base() + '/download'