mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-04 19:31:13 +01:00
Rename Booklore to Grimmory (#791)
This commit is contained in:
@@ -179,18 +179,18 @@ def test_booklore_connection(current_values: dict[str, Any] | None = None) -> di
|
||||
password = _get_value("BOOKLORE_PASSWORD", "") or ""
|
||||
|
||||
if not base_url:
|
||||
return {"success": False, "message": "Booklore URL is required"}
|
||||
return {"success": False, "message": "Grimmory URL is required"}
|
||||
if not username:
|
||||
return {"success": False, "message": "Booklore username is required"}
|
||||
return {"success": False, "message": "Grimmory username is required"}
|
||||
if not password:
|
||||
return {"success": False, "message": "Booklore password is required"}
|
||||
return {"success": False, "message": "Grimmory password is required"}
|
||||
|
||||
try:
|
||||
library_options, _ = _get_booklore_select_options(base_url, username, password)
|
||||
|
||||
message = "Connected to Booklore"
|
||||
message = "Connected to Grimmory"
|
||||
if library_options:
|
||||
message = f"Connected to Booklore ({len(library_options)} libraries)"
|
||||
message = f"Connected to Grimmory ({len(library_options)} libraries)"
|
||||
|
||||
return {"success": True, "message": message}
|
||||
except BookloreError as exc:
|
||||
|
||||
@@ -385,7 +385,7 @@ def general_settings():
|
||||
TextField(
|
||||
key="CALIBRE_WEB_URL",
|
||||
label="Library URL",
|
||||
description="Adds a navigation button to your book library (Calibre-Web Automated, Booklore, etc).",
|
||||
description="Adds a navigation button to your book library (Calibre-Web Automated, Grimmory, etc).",
|
||||
placeholder="http://calibre-web:8083",
|
||||
),
|
||||
TextField(
|
||||
@@ -873,8 +873,8 @@ def download_settings():
|
||||
},
|
||||
{
|
||||
"value": "booklore",
|
||||
"label": "Booklore (API)",
|
||||
"description": "Upload files directly to Booklore",
|
||||
"label": "Grimmory (API)",
|
||||
"description": "Upload files directly to Grimmory",
|
||||
},
|
||||
],
|
||||
default="folder",
|
||||
@@ -957,14 +957,14 @@ def download_settings():
|
||||
),
|
||||
HeadingField(
|
||||
key="booklore_heading",
|
||||
title="Booklore",
|
||||
description="Upload books directly to Booklore via API. Audiobooks always use folder mode.",
|
||||
title="Grimmory",
|
||||
description="Upload books directly to Grimmory (Formerly Booklore) via API. Audiobooks always use folder mode.",
|
||||
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
||||
),
|
||||
TextField(
|
||||
key="BOOKLORE_HOST",
|
||||
label="Booklore URL",
|
||||
description="Base URL of your Booklore instance",
|
||||
label="Grimmory URL",
|
||||
description="Base URL of your Grimmory instance",
|
||||
placeholder="http://booklore:6060",
|
||||
required=True,
|
||||
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
||||
@@ -972,14 +972,14 @@ def download_settings():
|
||||
TextField(
|
||||
key="BOOKLORE_USERNAME",
|
||||
label="Username",
|
||||
description="Booklore account username",
|
||||
description="Grimmory account username",
|
||||
required=True,
|
||||
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
||||
),
|
||||
PasswordField(
|
||||
key="BOOKLORE_PASSWORD",
|
||||
label="Password",
|
||||
description="Booklore account password",
|
||||
description="Grimmory account password",
|
||||
required=True,
|
||||
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
||||
),
|
||||
@@ -1005,7 +1005,7 @@ def download_settings():
|
||||
SelectField(
|
||||
key="BOOKLORE_LIBRARY_ID",
|
||||
label="Library",
|
||||
description="Booklore library to upload into.",
|
||||
description="Grimmory library to upload into.",
|
||||
options=get_booklore_library_options,
|
||||
required=True,
|
||||
user_overridable=True,
|
||||
@@ -1017,7 +1017,7 @@ def download_settings():
|
||||
SelectField(
|
||||
key="BOOKLORE_PATH_ID",
|
||||
label="Path",
|
||||
description="Booklore library path for uploads.",
|
||||
description="Grimmory library path for uploads.",
|
||||
options=get_booklore_path_options,
|
||||
required=True,
|
||||
filter_by_field="BOOKLORE_LIBRARY_ID",
|
||||
@@ -1030,7 +1030,7 @@ def download_settings():
|
||||
ActionButton(
|
||||
key="test_booklore",
|
||||
label="Test Connection",
|
||||
description="Verify your Booklore configuration",
|
||||
description="Verify your Grimmory configuration",
|
||||
style="primary",
|
||||
callback=test_booklore_connection,
|
||||
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
||||
|
||||
@@ -24,6 +24,7 @@ BOOKLORE_SUPPORTED_EXTENSIONS = {".azw", ".azw3", ".cb7", ".cbr", ".cbz", ".epub
|
||||
BOOKLORE_SUPPORTED_FORMATS_LABEL = ", ".join(
|
||||
ext.lstrip(".").upper() for ext in sorted(BOOKLORE_SUPPORTED_EXTENSIONS)
|
||||
)
|
||||
BOOKLORE_DISPLAY_NAME = "Grimmory"
|
||||
|
||||
|
||||
class BookloreError(Exception):
|
||||
@@ -67,11 +68,11 @@ def build_booklore_config(
|
||||
password = values.get("BOOKLORE_PASSWORD", "") or ""
|
||||
|
||||
if not base_url:
|
||||
raise BookloreError("Booklore URL is required")
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} URL is required")
|
||||
if not username:
|
||||
raise BookloreError("Booklore username is required")
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} username is required")
|
||||
if not password:
|
||||
raise BookloreError("Booklore password is required")
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} password is required")
|
||||
|
||||
destination = _parse_destination(
|
||||
values.get("BOOKLORE_DESTINATION", BOOKLORE_DESTINATION_LIBRARY)
|
||||
@@ -97,8 +98,8 @@ def build_booklore_config(
|
||||
library_id_val = values.get("BOOKLORE_LIBRARY_ID")
|
||||
path_id_val = values.get("BOOKLORE_PATH_ID")
|
||||
|
||||
library_id = _parse_int(library_id_val, "Booklore library ID")
|
||||
path_id = _parse_int(path_id_val, "Booklore path ID")
|
||||
library_id = _parse_int(library_id_val, f"{BOOKLORE_DISPLAY_NAME} library ID")
|
||||
path_id = _parse_int(path_id_val, f"{BOOKLORE_DISPLAY_NAME} path ID")
|
||||
|
||||
return BookloreConfig(
|
||||
base_url=base_url.rstrip("/"),
|
||||
@@ -119,28 +120,28 @@ def booklore_login(booklore_config: BookloreConfig) -> str:
|
||||
try:
|
||||
response = requests.post(url, json=payload, timeout=30, verify=booklore_config.verify_tls)
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
raise BookloreError("Could not connect to Booklore") from exc
|
||||
raise BookloreError(f"Could not connect to {BOOKLORE_DISPLAY_NAME}") from exc
|
||||
except requests.exceptions.Timeout as exc:
|
||||
raise BookloreError("Booklore connection timed out") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} connection timed out") from exc
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise BookloreError(f"Booklore login failed: {exc}") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} login failed: {exc}") from exc
|
||||
|
||||
if response.status_code in {401, 403}:
|
||||
raise BookloreError("Booklore authentication failed")
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} authentication failed")
|
||||
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as exc:
|
||||
raise BookloreError(f"Booklore login failed ({response.status_code})") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} login failed ({response.status_code})") from exc
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError as exc:
|
||||
raise BookloreError("Invalid Booklore login response") from exc
|
||||
raise BookloreError(f"Invalid {BOOKLORE_DISPLAY_NAME} login response") from exc
|
||||
|
||||
token = data.get("accessToken")
|
||||
if not token:
|
||||
raise BookloreError("Booklore did not return an access token")
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} did not return an access token")
|
||||
|
||||
return token
|
||||
|
||||
@@ -153,12 +154,12 @@ def booklore_list_libraries(booklore_config: BookloreConfig, token: str) -> list
|
||||
response = requests.get(url, headers=headers, timeout=30, verify=booklore_config.verify_tls)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise BookloreError(f"Failed to fetch Booklore libraries: {exc}") from exc
|
||||
raise BookloreError(f"Failed to fetch {BOOKLORE_DISPLAY_NAME} libraries: {exc}") from exc
|
||||
|
||||
try:
|
||||
return response.json()
|
||||
except ValueError as exc:
|
||||
raise BookloreError("Invalid Booklore libraries response") from exc
|
||||
raise BookloreError(f"Invalid {BOOKLORE_DISPLAY_NAME} libraries response") from exc
|
||||
|
||||
|
||||
def booklore_upload_file(booklore_config: BookloreConfig, token: str, file_path: Path) -> None:
|
||||
@@ -189,13 +190,13 @@ def booklore_upload_file(booklore_config: BookloreConfig, token: str, file_path:
|
||||
if message:
|
||||
message = f": {message[:200]}"
|
||||
status_code = response.status_code if response is not None else "unknown"
|
||||
raise BookloreError(f"Booklore upload failed ({status_code}){message}") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} upload failed ({status_code}){message}") from exc
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
raise BookloreError("Could not connect to Booklore") from exc
|
||||
raise BookloreError(f"Could not connect to {BOOKLORE_DISPLAY_NAME}") from exc
|
||||
except requests.exceptions.Timeout as exc:
|
||||
raise BookloreError("Booklore upload timed out") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} upload timed out") from exc
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise BookloreError(f"Booklore upload failed: {exc}") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} upload failed: {exc}") from exc
|
||||
|
||||
|
||||
def booklore_refresh_library(booklore_config: BookloreConfig, token: str) -> None:
|
||||
@@ -206,7 +207,7 @@ def booklore_refresh_library(booklore_config: BookloreConfig, token: str) -> Non
|
||||
response = requests.put(url, headers=headers, timeout=30, verify=booklore_config.verify_tls)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as exc:
|
||||
raise BookloreError(f"Booklore refresh failed: {exc}") from exc
|
||||
raise BookloreError(f"{BOOKLORE_DISPLAY_NAME} refresh failed: {exc}") from exc
|
||||
|
||||
|
||||
def _supports_booklore(task: DownloadTask) -> bool:
|
||||
@@ -231,7 +232,7 @@ def _booklore_format_error(rejected_files: List[Path]) -> str:
|
||||
rejected_exts = sorted(set(f.suffix.lower() for f in rejected_files))
|
||||
rejected_list = ", ".join(rejected_exts)
|
||||
return (
|
||||
f"Booklore does not support {rejected_list}. "
|
||||
f"{BOOKLORE_DISPLAY_NAME} does not support {rejected_list}. "
|
||||
f"Supported formats: {BOOKLORE_SUPPORTED_FORMATS_LABEL}"
|
||||
)
|
||||
|
||||
@@ -267,7 +268,7 @@ def _post_process_booklore(
|
||||
status_callback("error", str(e))
|
||||
return None
|
||||
|
||||
status_callback("resolving", "Preparing Booklore upload")
|
||||
status_callback("resolving", f"Preparing {BOOKLORE_DISPLAY_NAME} upload")
|
||||
|
||||
stage_action = STAGE_NONE
|
||||
if is_managed_workspace_path(temp_file):
|
||||
@@ -314,7 +315,7 @@ def _post_process_booklore(
|
||||
if cancel_flag.is_set():
|
||||
logger.info("Task %s: cancelled during Booklore upload", task.task_id)
|
||||
return None
|
||||
status_callback("resolving", f"Uploading to Booklore ({index}/{len(prepared.files)})")
|
||||
status_callback("resolving", f"Uploading to {BOOKLORE_DISPLAY_NAME} ({index}/{len(prepared.files)})")
|
||||
booklore_upload_file(booklore_config, token, file_path)
|
||||
|
||||
if booklore_config.refresh_after_upload:
|
||||
@@ -361,9 +362,9 @@ def _post_process_booklore(
|
||||
if not maybe_run_custom_script(script_context, status_callback=status_callback):
|
||||
return None
|
||||
|
||||
message = "Uploaded to Booklore"
|
||||
message = f"Uploaded to {BOOKLORE_DISPLAY_NAME}"
|
||||
if len(prepared.files) > 1:
|
||||
message = f"Uploaded to Booklore ({len(prepared.files)} files)"
|
||||
message = f"Uploaded to {BOOKLORE_DISPLAY_NAME} ({len(prepared.files)} files)"
|
||||
status_callback("complete", message)
|
||||
success = True
|
||||
return f"booklore://{task.task_id}"
|
||||
@@ -374,7 +375,7 @@ def _post_process_booklore(
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error_trace("Task %s: unexpected error uploading to Booklore: %s", task.task_id, e)
|
||||
status_callback("error", f"Booklore upload failed: {e}")
|
||||
status_callback("error", f"{BOOKLORE_DISPLAY_NAME} upload failed: {e}")
|
||||
return None
|
||||
finally:
|
||||
cleanup_output_staging(
|
||||
|
||||
@@ -20,7 +20,7 @@ interface UserOverridesSectionProps {
|
||||
const modeOptions = [
|
||||
{ value: 'folder', label: 'Folder' },
|
||||
{ value: 'email', label: 'Email (SMTP)' },
|
||||
{ value: 'booklore', label: 'BookLore (API)' },
|
||||
{ value: 'booklore', label: 'Grimmory (API)' },
|
||||
];
|
||||
|
||||
const fallbackOutputModeField: SelectFieldConfig = {
|
||||
@@ -53,7 +53,7 @@ const fallbackBookloreLibraryField: SelectFieldConfig = {
|
||||
type: 'SelectField',
|
||||
key: 'BOOKLORE_LIBRARY_ID',
|
||||
label: 'Library',
|
||||
description: 'BookLore library to upload into.',
|
||||
description: 'Grimmory library to upload into.',
|
||||
value: '',
|
||||
options: [],
|
||||
};
|
||||
@@ -62,7 +62,7 @@ const fallbackBooklorePathField: SelectFieldConfig = {
|
||||
type: 'SelectField',
|
||||
key: 'BOOKLORE_PATH_ID',
|
||||
label: 'Path',
|
||||
description: 'BookLore library path for uploads.',
|
||||
description: 'Grimmory library path for uploads.',
|
||||
value: '',
|
||||
options: [],
|
||||
filterByField: 'BOOKLORE_LIBRARY_ID',
|
||||
|
||||
@@ -257,7 +257,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
const result = await executeSettingsAction(tabName, actionKey, currentValues);
|
||||
|
||||
// Re-fetch settings after successful action to pick up updated options
|
||||
// (e.g., BookLore "Test Connection" refreshes library/path lists)
|
||||
// (e.g., Grimmory "Test Connection" refreshes library/path lists)
|
||||
if (result.success) {
|
||||
await fetchSettings({ silent: true, preserveDirtyValues: true });
|
||||
}
|
||||
|
||||
@@ -59,6 +59,23 @@ def test_download_settings_booklore_destination_field_defaults_to_library():
|
||||
assert option_values == {"library", "bookdrop"}
|
||||
|
||||
|
||||
def test_download_settings_grimmory_copy_is_exposed_in_ui_metadata():
|
||||
from shelfmark.config.settings import download_settings
|
||||
|
||||
fields = download_settings()
|
||||
|
||||
output_mode_field = next(field for field in fields if getattr(field, "key", None) == "BOOKS_OUTPUT_MODE")
|
||||
grimmory_option = next(option for option in output_mode_field.options if option["value"] == "booklore")
|
||||
heading_field = next(field for field in fields if getattr(field, "key", None) == "booklore_heading")
|
||||
url_field = next(field for field in fields if getattr(field, "key", None) == "BOOKLORE_HOST")
|
||||
|
||||
assert grimmory_option["label"] == "Grimmory (API)"
|
||||
assert grimmory_option["description"] == "Upload files directly to Grimmory"
|
||||
assert heading_field.title == "Grimmory"
|
||||
assert "(Formerly Booklore)" in heading_field.description
|
||||
assert url_field.label == "Grimmory URL"
|
||||
|
||||
|
||||
def test_download_settings_booklore_library_and_path_depend_on_library_destination():
|
||||
from shelfmark.config.settings import download_settings
|
||||
|
||||
|
||||
@@ -675,7 +675,7 @@ def test_booklore_mode_uploads_and_cleans_staging(tmp_path):
|
||||
assert uploaded_files
|
||||
assert not temp_file.exists()
|
||||
assert list(staging.iterdir()) == []
|
||||
assert any("Booklore" in (message or "") for _, message in statuses)
|
||||
assert any("Grimmory" in (message or "") for _, message in statuses)
|
||||
|
||||
|
||||
def test_booklore_mode_rejects_unsupported_files(tmp_path):
|
||||
@@ -723,7 +723,7 @@ def test_booklore_mode_rejects_unsupported_files(tmp_path):
|
||||
|
||||
errors = [call for call in status_cb.call_args_list if call.args[0] == "error"]
|
||||
assert errors
|
||||
assert "Booklore does not support" in errors[-1].args[1]
|
||||
assert "Grimmory does not support" in errors[-1].args[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("organization", ["none", "rename", "organize"])
|
||||
|
||||
Reference in New Issue
Block a user