mirror of
https://github.com/crosspoint-reader/calibre-plugins.git
synced 2026-04-29 10:26:59 -07:00
Merge branch 'master' into feature/send-to-root
This commit is contained in:
@@ -5,6 +5,7 @@ from qt.core import (
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
@@ -50,6 +51,12 @@ class CrossPointConfigWidget(QWidget):
|
||||
|
||||
layout.addRow('Host', self.host)
|
||||
layout.addRow('Port', self.port)
|
||||
|
||||
notice = QLabel('Host and port settings are fallback values used only when the device is not auto-discoverable by UDP broadcast.')
|
||||
notice.setWordWrap(True)
|
||||
notice.setStyleSheet('color: gray; font-style: italic;')
|
||||
layout.addRow('', notice)
|
||||
|
||||
layout.addRow('Upload path', self.path)
|
||||
layout.addRow('Chunk size', self.chunk_size)
|
||||
layout.addRow('', self.debug)
|
||||
|
||||
@@ -21,7 +21,7 @@ class CrossPointDevice(DeviceConfig, DevicePlugin):
|
||||
description = 'CrossPoint Reader wireless device'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
author = 'CrossPoint Reader'
|
||||
version = (0, 2, 0)
|
||||
version = (0, 1, 3)
|
||||
|
||||
# Invalid USB vendor info to avoid USB scans matching.
|
||||
VENDOR_ID = [0xFFFF]
|
||||
@@ -136,7 +136,8 @@ class CrossPointDevice(DeviceConfig, DevicePlugin):
|
||||
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')
|
||||
req = urllib.request.Request(url, data=body, method='POST',
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status, resp.read().decode('utf-8', 'ignore')
|
||||
@@ -250,7 +251,8 @@ class CrossPointDevice(DeviceConfig, DevicePlugin):
|
||||
"""
|
||||
url = self._http_base() + '/mkdir'
|
||||
body = urllib.parse.urlencode({'name': name, 'path': path}).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=body, method='POST')
|
||||
req = urllib.request.Request(url, data=body, method='POST',
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||
resp.read()
|
||||
@@ -353,11 +355,22 @@ class CrossPointDevice(DeviceConfig, DevicePlugin):
|
||||
|
||||
|
||||
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}')
|
||||
import json as _json
|
||||
self._log(f'[CrossPoint] deleting {len(paths)} books: {paths}')
|
||||
url = self._http_base() + '/delete'
|
||||
# Server expects form field 'paths' containing a JSON array string
|
||||
body = urllib.parse.urlencode({'paths': _json.dumps(list(paths))}).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=body, method='POST',
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
self._log(f'[CrossPoint] delete OK: {resp.status}')
|
||||
except urllib.error.HTTPError as exc:
|
||||
err_body = exc.read().decode('utf-8', 'ignore') if exc.fp else ''
|
||||
self._log(f'[CrossPoint] delete error {exc.code}: {err_body}')
|
||||
raise ControlError(desc=f'Delete failed: {exc.code} {err_body}')
|
||||
except Exception as exc:
|
||||
raise ControlError(desc=f'Delete failed: {exc}')
|
||||
|
||||
def remove_books_from_metadata(self, paths, booklists):
|
||||
def norm(p):
|
||||
|
||||
@@ -189,6 +189,21 @@ def _broadcast_from_host(host):
|
||||
return '.'.join(parts)
|
||||
|
||||
|
||||
def _local_broadcast_addrs():
|
||||
addrs = set()
|
||||
try:
|
||||
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
|
||||
ip = info[4][0]
|
||||
if ip.startswith('127.'):
|
||||
continue
|
||||
bcast = _broadcast_from_host(ip)
|
||||
if bcast:
|
||||
addrs.add(bcast)
|
||||
except Exception:
|
||||
pass
|
||||
return addrs
|
||||
|
||||
|
||||
def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None):
|
||||
ports = [8134, 54982, 48123, 39001, 44044, 59678]
|
||||
local_port = 0
|
||||
@@ -210,6 +225,11 @@ def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None):
|
||||
pass
|
||||
|
||||
targets = []
|
||||
for bcast in _local_broadcast_addrs():
|
||||
_log(logger, debug, f'[CrossPoint WS] discovery subnet broadcast {bcast}')
|
||||
for port in ports:
|
||||
targets.append((bcast, port))
|
||||
# 255.255.255.255 as fallback — works on Linux/Windows, silently fails on macOS
|
||||
for port in ports:
|
||||
targets.append(('255.255.255.255', port))
|
||||
for host in extra_hosts or []:
|
||||
@@ -240,6 +260,9 @@ def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None):
|
||||
text = data.decode('utf-8', 'ignore')
|
||||
except Exception:
|
||||
continue
|
||||
if not text.startswith('crosspoint'):
|
||||
_log(logger, debug, f'[CrossPoint WS] discovery ignoring non-crosspoint response: {text}')
|
||||
continue
|
||||
semi = text.find(';')
|
||||
port = 81
|
||||
if semi != -1:
|
||||
|
||||
Reference in New Issue
Block a user