diff --git a/calibre-plugin/crosspoint_reader/CrossPointReaderCalibrePlugin.zip b/calibre-plugin/crosspoint_reader/CrossPointReaderCalibrePlugin.zip deleted file mode 100644 index bf7f55d0..00000000 Binary files a/calibre-plugin/crosspoint_reader/CrossPointReaderCalibrePlugin.zip and /dev/null differ diff --git a/calibre-plugin/crosspoint_reader/README.md b/calibre-plugin/crosspoint_reader/README.md deleted file mode 100644 index 04f925f0..00000000 --- a/calibre-plugin/crosspoint_reader/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# CrossPoint Reader Calibre Plugin - -This plugin adds CrossPoint Reader as a wireless device in Calibre. It uploads -EPUB files over WebSocket to the CrossPoint web server. - -Protocol: -- Connect to ws://:/ -- Send: START::: -- Wait for READY -- Send binary frames with file content -- Wait for DONE (or ERROR:) - -Default settings: -- Auto-discover device via UDP -- Host fallback: 192.168.4.1 -- Port: 81 -- Upload path: / - -Install: -1. Zip the contents of `calibre-plugin/crosspoint_reader/plugin`. -2. In Calibre: Preferences > Plugins > Load plugin from file. -3. The device should appear in Calibre once it is discoverable on the network. - -No configuration needed. The plugin auto-discovers the device via UDP and -falls back to 192.168.4.1:81. diff --git a/calibre-plugin/crosspoint_reader/plugin/__init__.py b/calibre-plugin/crosspoint_reader/plugin/__init__.py deleted file mode 100644 index 9aaedbda..00000000 --- a/calibre-plugin/crosspoint_reader/plugin/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .driver import CrossPointDevice - - -class CrossPointReaderDevice(CrossPointDevice): - pass diff --git a/calibre-plugin/crosspoint_reader/plugin/config.py b/calibre-plugin/crosspoint_reader/plugin/config.py deleted file mode 100644 index 18d4fc94..00000000 --- a/calibre-plugin/crosspoint_reader/plugin/config.py +++ /dev/null @@ -1,91 +0,0 @@ -from calibre.utils.config import JSONConfig -from qt.core import ( - QCheckBox, - QDialog, - QDialogButtonBox, - QFormLayout, - QHBoxLayout, - QLineEdit, - QPlainTextEdit, - QPushButton, - QSpinBox, - QVBoxLayout, - QWidget, -) - -from .log import get_log_text - - -PREFS = JSONConfig('plugins/crosspoint_reader') -PREFS.defaults['host'] = '192.168.4.1' -PREFS.defaults['port'] = 81 -PREFS.defaults['path'] = '/' -PREFS.defaults['chunk_size'] = 2048 -PREFS.defaults['debug'] = False -PREFS.defaults['fetch_metadata'] = False - - -class CrossPointConfigWidget(QWidget): - def __init__(self): - super().__init__() - layout = QFormLayout(self) - self.host = QLineEdit(self) - self.port = QSpinBox(self) - self.port.setRange(1, 65535) - self.path = QLineEdit(self) - self.chunk_size = QSpinBox(self) - self.chunk_size.setRange(512, 65536) - self.debug = QCheckBox('Enable debug logging', self) - self.fetch_metadata = QCheckBox('Fetch metadata (slower device list)', self) - - self.host.setText(PREFS['host']) - self.port.setValue(PREFS['port']) - self.path.setText(PREFS['path']) - self.chunk_size.setValue(PREFS['chunk_size']) - self.debug.setChecked(PREFS['debug']) - self.fetch_metadata.setChecked(PREFS['fetch_metadata']) - - layout.addRow('Host', self.host) - layout.addRow('Port', self.port) - layout.addRow('Upload path', self.path) - layout.addRow('Chunk size', self.chunk_size) - layout.addRow('', self.debug) - layout.addRow('', self.fetch_metadata) - - self.log_view = QPlainTextEdit(self) - self.log_view.setReadOnly(True) - self.log_view.setPlaceholderText('Discovery log will appear here when debug is enabled.') - self._refresh_logs() - - refresh_btn = QPushButton('Refresh Log', self) - refresh_btn.clicked.connect(self._refresh_logs) - log_layout = QHBoxLayout() - log_layout.addWidget(refresh_btn) - - layout.addRow('Log', self.log_view) - layout.addRow('', log_layout) - - def save(self): - PREFS['host'] = self.host.text().strip() or PREFS.defaults['host'] - PREFS['port'] = int(self.port.value()) - PREFS['path'] = self.path.text().strip() or PREFS.defaults['path'] - PREFS['chunk_size'] = int(self.chunk_size.value()) - PREFS['debug'] = bool(self.debug.isChecked()) - PREFS['fetch_metadata'] = bool(self.fetch_metadata.isChecked()) - - def _refresh_logs(self): - self.log_view.setPlainText(get_log_text()) - - -class CrossPointConfigDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle('CrossPoint Reader') - self.widget = CrossPointConfigWidget() - layout = QVBoxLayout(self) - layout.addWidget(self.widget) - buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | - QDialogButtonBox.StandardButton.Cancel) - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) - layout.addWidget(buttons) diff --git a/calibre-plugin/crosspoint_reader/plugin/driver.py b/calibre-plugin/crosspoint_reader/plugin/driver.py deleted file mode 100644 index bca803d3..00000000 --- a/calibre-plugin/crosspoint_reader/plugin/driver.py +++ /dev/null @@ -1,367 +0,0 @@ -import os -import time -import urllib.parse -import urllib.request - -from calibre.devices.errors import ControlError -from calibre.devices.interface import DevicePlugin -from calibre.devices.usbms.deviceconfig import DeviceConfig -from calibre.devices.usbms.books import Book -from calibre.ebooks.metadata.book.base import Metadata - -from . import ws_client -from .config import CrossPointConfigWidget, PREFS -from .log import add_log - - -class CrossPointDevice(DeviceConfig, DevicePlugin): - name = 'CrossPoint Reader' - gui_name = 'CrossPoint Reader' - description = 'CrossPoint Reader wireless device' - supported_platforms = ['windows', 'osx', 'linux'] - author = 'CrossPoint Reader' - version = (0, 1, 0) - - # Invalid USB vendor info to avoid USB scans matching. - VENDOR_ID = [0xFFFF] - PRODUCT_ID = [0xFFFF] - BCD = [0xFFFF] - - FORMATS = ['epub'] - ALL_FORMATS = ['epub'] - SUPPORTS_SUB_DIRS = True - 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) - self.is_connected = False - self.device_host = None - self.device_port = None - self.last_discovery = 0.0 - self.report_progress = lambda x, y: x - self._debug_enabled = False - - def _log(self, message): - add_log(message) - if self._debug_enabled: - try: - self.report_progress(0.0, message) - except Exception: - pass - - # Device discovery / presence - def _discover(self): - now = time.time() - if now - self.last_discovery < 2.0: - return None, None - self.last_discovery = now - host, port = ws_client.discover_device( - timeout=1.0, - debug=PREFS['debug'], - logger=self._log, - extra_hosts=[PREFS['host']], - ) - if host and port: - return host, port - return None, None - - def detect_managed_devices(self, devices_on_system, force_refresh=False): - if self.is_connected: - return self - debug = PREFS['debug'] - self._debug_enabled = debug - if debug: - self._log('[CrossPoint] detect_managed_devices') - host, port = self._discover() - if host: - if debug: - self._log(f'[CrossPoint] discovered {host} {port}') - self.device_host = host - self.device_port = port - self.is_connected = True - return self - if debug: - self._log('[CrossPoint] discovery failed') - return None - - def open(self, connected_device, library_uuid): - if not self.is_connected: - raise ControlError(desc='Attempt to open a closed device') - return True - - def get_device_information(self, end_session=True): - host = self.device_host or PREFS['host'] - device_info = { - 'device_store_uuid': 'crosspoint-' + host.replace('.', '-'), - 'device_name': 'CrossPoint Reader', - 'device_version': '1', - } - return (self.gui_name, '1', '1', '', {'main': device_info}) - - def reset(self, key='-1', log_packets=False, report_progress=None, detected_device=None): - self.set_progress_reporter(report_progress) - - def set_progress_reporter(self, report_progress): - if report_progress is None: - self.report_progress = lambda x, y: x - else: - self.report_progress = report_progress - - def _http_base(self): - host = self.device_host or PREFS['host'] - return f'http://{host}' - - def _http_get_json(self, path, params=None, timeout=5): - url = self._http_base() + path - if params: - url += '?' + urllib.parse.urlencode(params) - try: - with urllib.request.urlopen(url, timeout=timeout) as resp: - data = resp.read().decode('utf-8', 'ignore') - except Exception as exc: - raise ControlError(desc=f'HTTP request failed: {exc}') - try: - import json - return json.loads(data) - except Exception as exc: - raise ControlError(desc=f'Invalid JSON response: {exc}') - - def _http_post_form(self, path, data, timeout=5): - url = self._http_base() + path - body = urllib.parse.urlencode(data).encode('utf-8') - req = urllib.request.Request(url, data=body, method='POST') - try: - with urllib.request.urlopen(req, timeout=timeout) as resp: - return resp.status, resp.read().decode('utf-8', 'ignore') - except Exception as exc: - raise ControlError(desc=f'HTTP request failed: {exc}') - - def config_widget(self): - return CrossPointConfigWidget() - - def save_settings(self, config_widget): - config_widget.save() - - def books(self, oncard=None, end_session=True): - if oncard is not None: - return [] - entries = self._http_get_json('/api/files', params={'path': '/'}) - books = [] - fetch_metadata = PREFS['fetch_metadata'] - for entry in entries: - if entry.get('isDirectory'): - continue - if not entry.get('isEpub'): - continue - name = entry.get('name', '') - if not name: - continue - size = entry.get('size', 0) - lpath = '/' + name if not name.startswith('/') else name - title = os.path.splitext(os.path.basename(name))[0] - meta = Metadata(title, []) - if fetch_metadata: - try: - from calibre.customize.ui import quick_metadata - from calibre.ebooks.metadata.meta import get_metadata - with self._download_temp(lpath) as tf: - with quick_metadata: - m = get_metadata(tf, stream_type='epub', force_read_metadata=True) - if m is not None: - meta = m - except Exception as exc: - self._log(f'[CrossPoint] metadata read failed for {lpath}: {exc}') - book = Book('', lpath, size=size, other=meta) - books.append(book) - return books - - def sync_booklists(self, booklists, end_session=True): - # No on-device metadata sync supported. - return None - - def card_prefix(self, end_session=True): - return None, None - - def total_space(self, end_session=True): - return 10 * 1024 * 1024 * 1024, 0, 0 - - def free_space(self, end_session=True): - return 10 * 1024 * 1024 * 1024, 0, 0 - - def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): - host = self.device_host or PREFS['host'] - port = self.device_port or PREFS['port'] - upload_path = PREFS['path'] - chunk_size = PREFS['chunk_size'] - if chunk_size > 2048: - self._log(f'[CrossPoint] chunk_size capped to 2048 (was {chunk_size})') - chunk_size = 2048 - debug = PREFS['debug'] - - paths = [] - total = len(files) - for i, (infile, name) in enumerate(zip(files, names)): - if hasattr(infile, 'read'): - filepath = getattr(infile, 'name', None) - if not filepath: - raise ControlError(desc='In-memory uploads are not supported') - 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: - self.report_progress((i + sent / float(size)) / float(total), - 'Transferring books to device...') - - ws_client.upload_file( - host, - port, - upload_path, - filename, - filepath, - chunk_size=chunk_size, - debug=debug, - progress_cb=_progress, - logger=self._log, - ) - 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 - - def delete_books(self, paths, end_session=True): - for path in paths: - 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): - 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' - params = urllib.parse.urlencode({'path': path}) - try: - with urllib.request.urlopen(url + '?' + params, timeout=10) as resp: - while True: - chunk = resp.read(65536) - if not chunk: - break - outfile.write(chunk) - except Exception as exc: - raise ControlError(desc=f'Failed to download {path}: {exc}') - - def _download_temp(self, path): - from calibre.ptempfile import PersistentTemporaryFile - tf = PersistentTemporaryFile(suffix='.epub') - self.get_file(path, tf) - tf.flush() - tf.seek(0) - return tf - - - def eject(self): - self.is_connected = False - - def is_dynamically_controllable(self): - return 'crosspoint' - - def start_plugin(self): - return None - - def stop_plugin(self): - self.is_connected = False diff --git a/calibre-plugin/crosspoint_reader/plugin/log.py b/calibre-plugin/crosspoint_reader/plugin/log.py deleted file mode 100644 index 9c58acd4..00000000 --- a/calibre-plugin/crosspoint_reader/plugin/log.py +++ /dev/null @@ -1,17 +0,0 @@ -import time - - -_LOG = [] -_MAX_LINES = 200 - - -def add_log(message): - timestamp = time.strftime('%H:%M:%S') - line = f'[{timestamp}] {message}' - _LOG.append(line) - if len(_LOG) > _MAX_LINES: - _LOG[:len(_LOG) - _MAX_LINES] = [] - - -def get_log_text(): - return '\n'.join(_LOG) diff --git a/calibre-plugin/crosspoint_reader/plugin/ws_client.py b/calibre-plugin/crosspoint_reader/plugin/ws_client.py deleted file mode 100644 index d87fa0b2..00000000 --- a/calibre-plugin/crosspoint_reader/plugin/ws_client.py +++ /dev/null @@ -1,294 +0,0 @@ -import base64 -import os -import select -import socket -import struct -import time - - -class WebSocketError(RuntimeError): - pass - - -class WebSocketClient: - def __init__(self, host, port, timeout=10, debug=False, logger=None): - self.host = host - self.port = port - self.timeout = timeout - self.debug = debug - self.logger = logger - self.sock = None - - def _log(self, *args): - if self.debug: - msg = '[CrossPoint WS] ' + ' '.join(str(a) for a in args) - if self.logger: - self.logger(msg) - else: - print(msg) - - def connect(self): - self.sock = socket.create_connection((self.host, self.port), self.timeout) - key = base64.b64encode(os.urandom(16)).decode('ascii') - req = ( - 'GET / HTTP/1.1\r\n' - f'Host: {self.host}:{self.port}\r\n' - 'Upgrade: websocket\r\n' - 'Connection: Upgrade\r\n' - f'Sec-WebSocket-Key: {key}\r\n' - 'Sec-WebSocket-Version: 13\r\n' - '\r\n' - ) - self.sock.sendall(req.encode('ascii')) - data = self._read_http_response() - if b' 101 ' not in data.split(b'\r\n', 1)[0]: - raise WebSocketError('Handshake failed: ' + data.split(b'\r\n', 1)[0].decode('ascii', 'ignore')) - self._log('Handshake OK') - - def _read_http_response(self): - self.sock.settimeout(self.timeout) - data = b'' - while b'\r\n\r\n' not in data: - chunk = self.sock.recv(1024) - if not chunk: - break - data += chunk - return data - - def close(self): - if not self.sock: - return - try: - self._send_frame(0x8, b'') - except Exception: - pass - try: - self.sock.close() - finally: - self.sock = None - - def send_text(self, text): - self._send_frame(0x1, text.encode('utf-8')) - - def send_binary(self, payload): - self._send_frame(0x2, payload) - - def _send_frame(self, opcode, payload): - if self.sock is None: - raise WebSocketError('Socket not connected') - fin = 0x80 - first = fin | (opcode & 0x0F) - mask_bit = 0x80 - length = len(payload) - header = bytearray([first]) - if length <= 125: - header.append(mask_bit | length) - elif length <= 65535: - header.append(mask_bit | 126) - header.extend(struct.pack('!H', length)) - else: - header.append(mask_bit | 127) - header.extend(struct.pack('!Q', length)) - - mask = os.urandom(4) - header.extend(mask) - masked = bytearray(payload) - for i in range(length): - masked[i] ^= mask[i % 4] - self.sock.sendall(header + masked) - - def read_text(self): - deadline = time.time() + self.timeout - while True: - if time.time() > deadline: - raise WebSocketError('Timed out waiting for text frame') - opcode, payload = self._read_frame() - if opcode == 0x8: - code = None - reason = '' - if len(payload) >= 2: - code = struct.unpack('!H', payload[:2])[0] - reason = payload[2:].decode('utf-8', 'ignore') - self._log('Server closed connection', code, reason) - raise WebSocketError('Connection closed') - if opcode == 0x9: - # Ping -> respond with Pong - self._send_frame(0xA, payload) - continue - if opcode == 0xA: - # Pong -> ignore - continue - if opcode != 0x1: - self._log('Ignoring non-text opcode', opcode, len(payload)) - continue - return payload.decode('utf-8', 'ignore') - - def _read_frame(self): - if self.sock is None: - raise WebSocketError('Socket not connected') - hdr = self._recv_exact(2) - b1, b2 = hdr[0], hdr[1] - opcode = b1 & 0x0F - masked = (b2 & 0x80) != 0 - length = b2 & 0x7F - if length == 126: - length = struct.unpack('!H', self._recv_exact(2))[0] - elif length == 127: - length = struct.unpack('!Q', self._recv_exact(8))[0] - mask = b'' - if masked: - mask = self._recv_exact(4) - payload = self._recv_exact(length) if length else b'' - if masked: - payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload)) - return opcode, payload - - def _recv_exact(self, n): - data = b'' - while len(data) < n: - chunk = self.sock.recv(n - len(data)) - if not chunk: - raise WebSocketError('Socket closed') - data += chunk - return data - - def drain_messages(self): - if self.sock is None: - return [] - messages = [] - while True: - r, _, _ = select.select([self.sock], [], [], 0) - if not r: - break - opcode, payload = self._read_frame() - if opcode == 0x1: - messages.append(payload.decode('utf-8', 'ignore')) - elif opcode == 0x8: - raise WebSocketError('Connection closed') - return messages - - -def _log(logger, debug, message): - if not debug: - return - if logger: - logger(message) - else: - print(message) - - -def _broadcast_from_host(host): - parts = host.split('.') - if len(parts) != 4: - return None - try: - _ = [int(p) for p in parts] - except Exception: - return None - parts[-1] = '255' - return '.'.join(parts) - - -def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None): - ports = [8134, 54982, 48123, 39001, 44044, 59678] - local_port = 0 - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.settimeout(0.5) - try: - sock.bind(('', local_port)) - except Exception: - _log(logger, debug, '[CrossPoint WS] discovery bind failed') - pass - - msg = b'hello' - try: - addr, port = sock.getsockname() - _log(logger, debug, f'[CrossPoint WS] discovery local {addr} {port}') - except Exception: - pass - - targets = [] - for port in ports: - targets.append(('255.255.255.255', port)) - for host in extra_hosts or []: - if not host: - continue - for port in ports: - targets.append((host, port)) - bcast = _broadcast_from_host(host) - if bcast: - for port in ports: - targets.append((bcast, port)) - - for _ in range(3): - for host, port in targets: - try: - sock.sendto(msg, (host, port)) - except Exception as exc: - _log(logger, debug, f'[CrossPoint WS] discovery send failed {host}:{port} {exc}') - pass - start = time.time() - while time.time() - start < timeout: - try: - data, addr = sock.recvfrom(256) - except Exception: - break - _log(logger, debug, f'[CrossPoint WS] discovery {addr} {data}') - try: - text = data.decode('utf-8', 'ignore') - except Exception: - continue - semi = text.find(';') - port = 81 - if semi != -1: - try: - port = int(text[semi + 1:].strip().split(',')[0]) - except Exception: - port = 81 - return addr[0], port - return None, None - - -def upload_file(host, port, upload_path, filename, filepath, chunk_size=16384, debug=False, progress_cb=None, - logger=None): - client = WebSocketClient(host, port, timeout=10, debug=debug, logger=logger) - try: - client.connect() - size = os.path.getsize(filepath) - start = f'START:{filename}:{size}:{upload_path}' - client._log('Sending START', start) - client.send_text(start) - - msg = client.read_text() - client._log('Received', msg) - if not msg: - raise WebSocketError('Unexpected response: ') - if msg.startswith('ERROR'): - raise WebSocketError(msg) - if msg != 'READY': - raise WebSocketError('Unexpected response: ' + msg) - - sent = 0 - with open(filepath, 'rb') as f: - while True: - chunk = f.read(chunk_size) - if not chunk: - break - client.send_binary(chunk) - sent += len(chunk) - if progress_cb: - progress_cb(sent, size) - client.drain_messages() - - # Wait for DONE or ERROR - while True: - msg = client.read_text() - client._log('Received', msg) - if msg == 'DONE': - return - if msg.startswith('ERROR'): - raise WebSocketError(msg) - finally: - client.close() diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index b96c0458..ea26ad91 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 21; +constexpr uint8_t SETTINGS_COUNT = 22; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace diff --git a/src/activities/settings/CategorySettingsActivity.cpp b/src/activities/settings/CategorySettingsActivity.cpp index a6182b5c..7fd5ef5f 100644 --- a/src/activities/settings/CategorySettingsActivity.cpp +++ b/src/activities/settings/CategorySettingsActivity.cpp @@ -103,7 +103,7 @@ void CategorySettingsActivity::toggleCurrentSetting() { updateRequired = true; })); xSemaphoreGive(renderingMutex); - } else if (strcmp(setting.name, "Calibre Settings") == 0) { + } else if (strcmp(setting.name, "OPDS Browser") == 0) { xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {