f
This commit is contained in:
48
CLI.py
48
CLI.py
@@ -32,7 +32,6 @@ if not os.environ.get("MM_DEBUG"):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@@ -395,7 +394,7 @@ class CmdletCompleter(Completer):
|
|||||||
for idx, tok in enumerate(tokens):
|
for idx, tok in enumerate(tokens):
|
||||||
low = str(tok or "").strip().lower()
|
low = str(tok or "").strip().lower()
|
||||||
if "=" in low:
|
if "=" in low:
|
||||||
head, val = low.split("=", 1)
|
head, _ = low.split("=", 1)
|
||||||
if head in want:
|
if head in want:
|
||||||
return tok.split("=", 1)[1]
|
return tok.split("=", 1)[1]
|
||||||
if low in want and idx + 1 < len(tokens):
|
if low in want and idx + 1 < len(tokens):
|
||||||
@@ -1482,51 +1481,6 @@ class CLI:
|
|||||||
def repl() -> None:
|
def repl() -> None:
|
||||||
self.run_repl()
|
self.run_repl()
|
||||||
|
|
||||||
@app.command("remote-server")
|
|
||||||
def remote_server(
|
|
||||||
storage_path: str = typer.Argument(
|
|
||||||
None, help="Path to the storage root"
|
|
||||||
),
|
|
||||||
port: int = typer.Option(None, "--port", help="Port to run the server on"),
|
|
||||||
api_key: str | None = typer.Option(None, "--api-key", help="API key for authentication"),
|
|
||||||
host: str = "0.0.0.0",
|
|
||||||
debug_server: bool = False,
|
|
||||||
background: bool = False,
|
|
||||||
) -> None:
|
|
||||||
"""Start the remote storage server.
|
|
||||||
|
|
||||||
NOTE: The legacy local storage server has been removed. Use HydrusNetwork
|
|
||||||
integrations instead.
|
|
||||||
"""
|
|
||||||
print(
|
|
||||||
"Error: remote-server is no longer available because legacy local storage has been removed.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"Starting remote storage server at http://{host}:{port}, storage: {storage}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if background:
|
|
||||||
try:
|
|
||||||
from werkzeug.serving import make_server
|
|
||||||
import threading
|
|
||||||
|
|
||||||
server = make_server(host, port, app_obj)
|
|
||||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
||||||
thread.start()
|
|
||||||
print(f"Server started in background (thread id={thread.ident})")
|
|
||||||
return
|
|
||||||
except Exception as exc:
|
|
||||||
print("Failed to start background server, falling back to foreground:", exc, file=sys.stderr)
|
|
||||||
|
|
||||||
# Foreground run blocks the CLI until server exits
|
|
||||||
try:
|
|
||||||
app_obj.run(host=host, port=port, debug=debug_server, use_reloader=False, threaded=True)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Remote server stopped by user")
|
|
||||||
|
|
||||||
@app.callback(invoke_without_command=True)
|
@app.callback(invoke_without_command=True)
|
||||||
def main_callback(ctx: typer.Context) -> None:
|
def main_callback(ctx: typer.Context) -> None:
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import json
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional, List
|
from typing import Any, Dict, Optional, List
|
||||||
from SYS.logger import log
|
from SYS.logger import log
|
||||||
@@ -15,6 +17,8 @@ DEFAULT_CONFIG_FILENAME = "config.conf"
|
|||||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
|
||||||
_CONFIG_CACHE: Dict[str, Dict[str, Any]] = {}
|
_CONFIG_CACHE: Dict[str, Dict[str, Any]] = {}
|
||||||
|
_CONFIG_SAVE_MAX_RETRIES = 5
|
||||||
|
_CONFIG_SAVE_RETRY_DELAY = 0.15
|
||||||
|
|
||||||
|
|
||||||
def global_config() -> List[Dict[str, Any]]:
|
def global_config() -> List[Dict[str, Any]]:
|
||||||
@@ -52,26 +56,36 @@ def get_hydrus_instance(
|
|||||||
) -> Optional[Dict[str, Any]]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
"""Get a specific Hydrus instance config by name.
|
"""Get a specific Hydrus instance config by name.
|
||||||
|
|
||||||
Supports multiple formats:
|
Supports modern config plus a fallback when no exact match exists.
|
||||||
- Current: config["store"]["hydrusnetwork"][instance_name]
|
|
||||||
- Legacy: config["storage"]["hydrus"][instance_name]
|
|
||||||
- Old: config["HydrusNetwork"][instance_name]
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: Configuration dict
|
|
||||||
instance_name: Name of the Hydrus instance (default: "home")
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict with access key and URL, or None if not found
|
|
||||||
"""
|
"""
|
||||||
# Canonical: config["store"]["hydrusnetwork"]["home"]
|
|
||||||
store = config.get("store", {})
|
store = config.get("store", {})
|
||||||
if isinstance(store, dict):
|
if not isinstance(store, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
hydrusnetwork = store.get("hydrusnetwork", {})
|
hydrusnetwork = store.get("hydrusnetwork", {})
|
||||||
if isinstance(hydrusnetwork, dict):
|
if not isinstance(hydrusnetwork, dict) or not hydrusnetwork:
|
||||||
|
return None
|
||||||
|
|
||||||
instance = hydrusnetwork.get(instance_name)
|
instance = hydrusnetwork.get(instance_name)
|
||||||
if isinstance(instance, dict):
|
if isinstance(instance, dict):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
target = str(instance_name or "").lower()
|
||||||
|
for name, conf in hydrusnetwork.items():
|
||||||
|
if isinstance(conf, dict) and str(name).lower() == target:
|
||||||
|
return conf
|
||||||
|
|
||||||
|
keys = sorted(hydrusnetwork.keys())
|
||||||
|
for key in keys:
|
||||||
|
if not str(key or "").startswith("new_"):
|
||||||
|
candidate = hydrusnetwork.get(key)
|
||||||
|
if isinstance(candidate, dict):
|
||||||
|
return candidate
|
||||||
|
first_key = keys[0]
|
||||||
|
candidate = hydrusnetwork.get(first_key)
|
||||||
|
if isinstance(candidate, dict):
|
||||||
|
return candidate
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -349,7 +363,9 @@ def load_config(
|
|||||||
def reload_config(
|
def reload_config(
|
||||||
config_dir: Optional[Path] = None, filename: str = DEFAULT_CONFIG_FILENAME
|
config_dir: Optional[Path] = None, filename: str = DEFAULT_CONFIG_FILENAME
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
cache_key = _make_cache_key(config_dir, filename, None)
|
base_dir = config_dir or SCRIPT_DIR
|
||||||
|
config_path = base_dir / filename
|
||||||
|
cache_key = _make_cache_key(config_dir, filename, config_path)
|
||||||
_CONFIG_CACHE.pop(cache_key, None)
|
_CONFIG_CACHE.pop(cache_key, None)
|
||||||
return load_config(config_dir=config_dir, filename=filename)
|
return load_config(config_dir=config_dir, filename=filename)
|
||||||
|
|
||||||
@@ -359,42 +375,61 @@ def save_config(
|
|||||||
config: Dict[str, Any],
|
config: Dict[str, Any],
|
||||||
config_dir: Optional[Path] = None,
|
config_dir: Optional[Path] = None,
|
||||||
filename: str = DEFAULT_CONFIG_FILENAME,
|
filename: str = DEFAULT_CONFIG_FILENAME,
|
||||||
) -> None:
|
) -> int:
|
||||||
base_dir = config_dir or SCRIPT_DIR
|
base_dir = config_dir or SCRIPT_DIR
|
||||||
config_path = base_dir / filename
|
config_path = base_dir / filename
|
||||||
|
|
||||||
# 1. Save to Database
|
def _write_entries() -> int:
|
||||||
try:
|
count = 0
|
||||||
from SYS.database import db, save_config_value
|
|
||||||
|
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
# Replace the table contents so removed entries disappear from the DB.
|
|
||||||
db.execute("DELETE FROM config")
|
db.execute("DELETE FROM config")
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
if key in ('store', 'provider', 'tool'):
|
if key in ('store', 'provider', 'tool'):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
for subtype, instances in value.items():
|
for subtype, instances in value.items():
|
||||||
if isinstance(instances, dict):
|
if isinstance(instances, dict):
|
||||||
# provider/tool are usually config[cat][subtype][key]
|
|
||||||
# but store is config['store'][subtype][name][key]
|
|
||||||
if key == 'store':
|
if key == 'store':
|
||||||
for name, settings in instances.items():
|
for name, settings in instances.items():
|
||||||
if isinstance(settings, dict):
|
if isinstance(settings, dict):
|
||||||
for k, v in settings.items():
|
for k, v in settings.items():
|
||||||
save_config_value(key, subtype, name, k, v)
|
save_config_value(key, subtype, name, k, v)
|
||||||
|
count += 1
|
||||||
else:
|
else:
|
||||||
for k, v in instances.items():
|
for k, v in instances.items():
|
||||||
save_config_value(key, subtype, "default", k, v)
|
save_config_value(key, subtype, "default", k, v)
|
||||||
|
count += 1
|
||||||
else:
|
else:
|
||||||
# global settings
|
if not key.startswith("_") and value is not None:
|
||||||
if not key.startswith("_"):
|
|
||||||
save_config_value("global", "none", "none", key, value)
|
save_config_value("global", "none", "none", key, value)
|
||||||
except Exception as e:
|
count += 1
|
||||||
log(f"Failed to save config to database: {e}")
|
return count
|
||||||
|
|
||||||
|
saved_entries = 0
|
||||||
|
attempts = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
saved_entries = _write_entries()
|
||||||
|
log(f"Saved {saved_entries} configuration entries to database.")
|
||||||
|
break
|
||||||
|
except sqlite3.OperationalError as exc:
|
||||||
|
attempts += 1
|
||||||
|
locked_error = "locked" in str(exc).lower()
|
||||||
|
if not locked_error or attempts >= _CONFIG_SAVE_MAX_RETRIES:
|
||||||
|
log(f"CRITICAL: Failed to save config to database: {exc}")
|
||||||
|
raise
|
||||||
|
delay = _CONFIG_SAVE_RETRY_DELAY * attempts
|
||||||
|
log(
|
||||||
|
f"Database busy locking medios.db (attempt {attempts}/{_CONFIG_SAVE_MAX_RETRIES}); retrying in {delay:.2f}s."
|
||||||
|
)
|
||||||
|
time.sleep(delay)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"CRITICAL: Failed to save config to database: {exc}")
|
||||||
|
raise
|
||||||
|
|
||||||
cache_key = _make_cache_key(config_dir, filename, config_path)
|
cache_key = _make_cache_key(config_dir, filename, config_path)
|
||||||
clear_config_cache()
|
clear_config_cache()
|
||||||
_CONFIG_CACHE[cache_key] = config
|
_CONFIG_CACHE[cache_key] = config
|
||||||
|
return saved_entries
|
||||||
|
|
||||||
|
|
||||||
def load() -> Dict[str, Any]:
|
def load() -> Dict[str, Any]:
|
||||||
@@ -402,6 +437,6 @@ def load() -> Dict[str, Any]:
|
|||||||
return load_config()
|
return load_config()
|
||||||
|
|
||||||
|
|
||||||
def save(config: Dict[str, Any]) -> None:
|
def save(config: Dict[str, Any]) -> int:
|
||||||
"""Persist *config* back to disk."""
|
"""Persist *config* back to disk."""
|
||||||
save_config(config)
|
return save_config(config)
|
||||||
|
|||||||
@@ -3,14 +3,44 @@ from __future__ import annotations
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
|
import os
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from SYS.logger import log
|
||||||
|
|
||||||
# The database is located in the project root
|
# The database is located in the project root (prefer explicit repo hints).
|
||||||
ROOT_DIR = Path(__file__).resolve().parent.parent
|
def _resolve_root_dir() -> Path:
|
||||||
DB_PATH = ROOT_DIR / "medios.db"
|
env_root = (
|
||||||
|
os.environ.get("MM_REPO")
|
||||||
|
or os.environ.get("MM_ROOT")
|
||||||
|
or os.environ.get("REPO")
|
||||||
|
)
|
||||||
|
if env_root:
|
||||||
|
try:
|
||||||
|
candidate = Path(env_root).expanduser().resolve()
|
||||||
|
if candidate.exists():
|
||||||
|
return candidate
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
cwd = Path.cwd().resolve()
|
||||||
|
for base in [cwd, *cwd.parents]:
|
||||||
|
if (base / "medios.db").exists():
|
||||||
|
return base
|
||||||
|
if (base / "CLI.py").exists():
|
||||||
|
return base
|
||||||
|
if (base / "config.conf").exists():
|
||||||
|
return base
|
||||||
|
if (base / "scripts").exists() and (base / "SYS").exists():
|
||||||
|
return base
|
||||||
|
|
||||||
|
return Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_DIR = _resolve_root_dir()
|
||||||
|
DB_PATH = (ROOT_DIR / "medios.db").resolve()
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
_instance: Optional[Database] = None
|
_instance: Optional[Database] = None
|
||||||
@@ -22,8 +52,15 @@ class Database:
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def _init_db(self):
|
def _init_db(self):
|
||||||
|
self.db_path = DB_PATH
|
||||||
|
db_existed = self.db_path.exists()
|
||||||
|
if db_existed:
|
||||||
|
log(f"Opening existing medios.db at {self.db_path}")
|
||||||
|
else:
|
||||||
|
log(f"Creating medios.db at {self.db_path}")
|
||||||
|
|
||||||
self.conn = sqlite3.connect(
|
self.conn = sqlite3.connect(
|
||||||
str(DB_PATH),
|
str(self.db_path),
|
||||||
check_same_thread=False,
|
check_same_thread=False,
|
||||||
timeout=30.0 # Increase timeout to 30s to avoid locking issues
|
timeout=30.0 # Increase timeout to 30s to avoid locking issues
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from textual.widgets import Static, Button, Input, Label, ListView, ListItem, Ru
|
|||||||
from textual import on, work
|
from textual import on, work
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from SYS.config import load_config, save_config, global_config
|
from SYS.config import load_config, save_config, reload_config, global_config
|
||||||
|
from SYS.logger import log
|
||||||
from Store.registry import _discover_store_classes, _required_keys_for
|
from Store.registry import _discover_store_classes, _required_keys_for
|
||||||
from ProviderCore.registry import list_providers
|
from ProviderCore.registry import list_providers
|
||||||
from TUI.modalscreen.selection_modal import SelectionModal
|
from TUI.modalscreen.selection_modal import SelectionModal
|
||||||
@@ -526,11 +527,15 @@ class ConfigModal(ModalScreen):
|
|||||||
self.editing_item_type = None
|
self.editing_item_type = None
|
||||||
self.refresh_view()
|
self.refresh_view()
|
||||||
elif bid == "save-btn":
|
elif bid == "save-btn":
|
||||||
|
self._synchronize_inputs_to_config()
|
||||||
if not self.validate_current_editor():
|
if not self.validate_current_editor():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.save_all()
|
saved = self.save_all()
|
||||||
self.notify("Configuration saved!")
|
msg = f"Configuration saved ({saved} entries)"
|
||||||
|
if saved == 0:
|
||||||
|
msg = "Configuration saved (no rows changed)"
|
||||||
|
self.notify(msg)
|
||||||
# Return to the main list view within the current category
|
# Return to the main list view within the current category
|
||||||
self.editing_item_name = None
|
self.editing_item_name = None
|
||||||
self.editing_item_type = None
|
self.editing_item_type = None
|
||||||
@@ -557,8 +562,11 @@ class ConfigModal(ModalScreen):
|
|||||||
removed = True
|
removed = True
|
||||||
if removed:
|
if removed:
|
||||||
try:
|
try:
|
||||||
self.save_all()
|
saved = self.save_all()
|
||||||
self.notify("Configuration saved!")
|
msg = f"Configuration saved ({saved} entries)"
|
||||||
|
if saved == 0:
|
||||||
|
msg = "Configuration saved (no rows changed)"
|
||||||
|
self.notify(msg)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.notify(f"Save failed: {exc}", severity="error", timeout=10)
|
self.notify(f"Save failed: {exc}", severity="error", timeout=10)
|
||||||
self.refresh_view()
|
self.refresh_view()
|
||||||
@@ -707,8 +715,19 @@ class ConfigModal(ModalScreen):
|
|||||||
|
|
||||||
key = self._input_id_map[widget_id]
|
key = self._input_id_map[widget_id]
|
||||||
|
|
||||||
|
# Try to preserve boolean/integer types
|
||||||
|
processed_value = value
|
||||||
|
if isinstance(value, str):
|
||||||
|
low = value.lower()
|
||||||
|
if low == "true":
|
||||||
|
processed_value = True
|
||||||
|
elif low == "false":
|
||||||
|
processed_value = False
|
||||||
|
elif value.isdigit():
|
||||||
|
processed_value = int(value)
|
||||||
|
|
||||||
if widget_id.startswith("global-"):
|
if widget_id.startswith("global-"):
|
||||||
self.config_data[key] = value
|
self.config_data[key] = processed_value
|
||||||
elif widget_id.startswith("item-") and self.editing_item_name:
|
elif widget_id.startswith("item-") and self.editing_item_name:
|
||||||
it = str(self.editing_item_type or "")
|
it = str(self.editing_item_type or "")
|
||||||
inm = str(self.editing_item_name or "")
|
inm = str(self.editing_item_name or "")
|
||||||
@@ -722,14 +741,41 @@ class ConfigModal(ModalScreen):
|
|||||||
self.config_data["store"][stype] = {}
|
self.config_data["store"][stype] = {}
|
||||||
if inm not in self.config_data["store"][stype]:
|
if inm not in self.config_data["store"][stype]:
|
||||||
self.config_data["store"][stype][inm] = {}
|
self.config_data["store"][stype][inm] = {}
|
||||||
self.config_data["store"][stype][inm][key] = value
|
|
||||||
|
# Special case: Renaming the store via the NAME field
|
||||||
|
if key.upper() == "NAME" and processed_value and str(processed_value) != inm:
|
||||||
|
new_inm = str(processed_value)
|
||||||
|
# Move the whole dictionary to the new key
|
||||||
|
self.config_data["store"][stype][new_inm] = self.config_data["store"][stype].pop(inm)
|
||||||
|
# Update editing_item_name so further changes to this screen hit the new key
|
||||||
|
self.editing_item_name = new_inm
|
||||||
|
inm = new_inm
|
||||||
|
|
||||||
|
self.config_data["store"][stype][inm][key] = processed_value
|
||||||
else:
|
else:
|
||||||
# Provider or other top-level sections
|
# Provider or other top-level sections
|
||||||
if it not in self.config_data:
|
if it not in self.config_data:
|
||||||
self.config_data[it] = {}
|
self.config_data[it] = {}
|
||||||
if inm not in self.config_data[it]:
|
if inm not in self.config_data[it]:
|
||||||
self.config_data[it][inm] = {}
|
self.config_data[it][inm] = {}
|
||||||
self.config_data[it][inm][key] = value
|
self.config_data[it][inm][key] = processed_value
|
||||||
|
|
||||||
|
def _synchronize_inputs_to_config(self) -> None:
|
||||||
|
"""Capture current input/select values before saving."""
|
||||||
|
widgets = list(self.query(Input)) + list(self.query(Select))
|
||||||
|
for widget in widgets:
|
||||||
|
widget_id = widget.id
|
||||||
|
if not widget_id or widget_id not in self._input_id_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(widget, Select):
|
||||||
|
if widget.value == Select.BLANK:
|
||||||
|
continue
|
||||||
|
value = widget.value
|
||||||
|
else:
|
||||||
|
value = widget.value
|
||||||
|
|
||||||
|
self._update_config_value(widget_id, value)
|
||||||
|
|
||||||
@on(Input.Changed)
|
@on(Input.Changed)
|
||||||
def on_input_changed(self, event: Input.Changed) -> None:
|
def on_input_changed(self, event: Input.Changed) -> None:
|
||||||
@@ -743,8 +789,12 @@ class ConfigModal(ModalScreen):
|
|||||||
if event.value != Select.BLANK:
|
if event.value != Select.BLANK:
|
||||||
self._update_config_value(event.select.id, event.value)
|
self._update_config_value(event.select.id, event.value)
|
||||||
|
|
||||||
def save_all(self) -> None:
|
def save_all(self) -> int:
|
||||||
save_config(self.config_data)
|
self._synchronize_inputs_to_config()
|
||||||
|
entries = save_config(self.config_data)
|
||||||
|
self.config_data = reload_config()
|
||||||
|
log(f"ConfigModal saved {entries} configuration entries")
|
||||||
|
return entries
|
||||||
|
|
||||||
def validate_current_editor(self) -> bool:
|
def validate_current_editor(self) -> bool:
|
||||||
"""Ensure all required fields for the current item are filled."""
|
"""Ensure all required fields for the current item are filled."""
|
||||||
|
|||||||
@@ -752,25 +752,36 @@ def main() -> int:
|
|||||||
try:
|
try:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
with sqlite3.connect(str(db_path), timeout=30.0) as conn:
|
with sqlite3.connect(str(db_path), timeout=30.0) as conn:
|
||||||
# We want to set store.hydrusnetwork.hydrus.<key>
|
conn.row_factory = sqlite3.Row
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
# Check if hydrusnetwork store exists
|
|
||||||
cur.execute("SELECT 1 FROM config WHERE category='store' AND subtype='hydrusnetwork'")
|
# Find all existing hydrusnetwork store names
|
||||||
if cur.fetchone():
|
cur.execute(
|
||||||
|
"SELECT DISTINCT item_name FROM config WHERE category='store' AND subtype='hydrusnetwork'"
|
||||||
|
)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
item_names = [r[0] for r in rows if r[0]]
|
||||||
|
|
||||||
|
if not item_names:
|
||||||
|
# Only create if none exist. Use a sensible name from the path if possible.
|
||||||
|
# We don't have the hydrus_path here easily, but we can try to find it.
|
||||||
|
# For now, if we are in bootstrap, we might just be setting a global.
|
||||||
|
# But this function is specifically for store settings.
|
||||||
|
# Let's use 'home' instead of 'hydrus' as it's the standard default.
|
||||||
|
item_name = "home"
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
||||||
('store', 'hydrusnetwork', 'hydrus', key, value)
|
('store', 'hydrusnetwork', item_name, 'NAME', item_name)
|
||||||
)
|
)
|
||||||
else:
|
item_names = [item_name]
|
||||||
# Create the section
|
|
||||||
|
# Update all existing instances with this key/value
|
||||||
|
for name in item_names:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
||||||
('store', 'hydrusnetwork', 'hydrus', 'name', 'hydrus')
|
('store', 'hydrusnetwork', name, key, value)
|
||||||
)
|
|
||||||
cur.execute(
|
|
||||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
('store', 'hydrusnetwork', 'hydrus', key, value)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user