dfdfdf
This commit is contained in:
125
API/folder.py
125
API/folder.py
@@ -231,11 +231,13 @@ class API_folder_store:
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
hash TEXT PRIMARY KEY NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
note TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (hash) REFERENCES files(hash) ON DELETE CASCADE
|
||||
FOREIGN KEY (hash) REFERENCES files(hash) ON DELETE CASCADE,
|
||||
PRIMARY KEY (hash, name)
|
||||
)
|
||||
""")
|
||||
|
||||
@@ -261,6 +263,11 @@ class API_folder_store:
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_worker_type ON worker(worker_type)")
|
||||
|
||||
self._migrate_metadata_schema(cursor)
|
||||
self._migrate_notes_schema(cursor)
|
||||
|
||||
# Notes indices (after migration so columns exist)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_notes_hash ON notes(hash)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_notes_name ON notes(name)")
|
||||
self.connection.commit()
|
||||
logger.debug("Database tables created/verified")
|
||||
|
||||
@@ -448,6 +455,42 @@ class API_folder_store:
|
||||
self.connection.commit()
|
||||
except Exception as e:
|
||||
logger.debug(f"Note: Schema import/migration completed with status: {e}")
|
||||
|
||||
def _migrate_notes_schema(self, cursor) -> None:
|
||||
"""Migrate legacy notes schema (hash PRIMARY KEY, note) to named notes (hash,name PRIMARY KEY)."""
|
||||
try:
|
||||
cursor.execute("PRAGMA table_info(notes)")
|
||||
cols = [row[1] for row in cursor.fetchall()]
|
||||
if not cols:
|
||||
return
|
||||
if "name" in cols:
|
||||
return
|
||||
|
||||
logger.info("Migrating legacy notes table to named notes schema")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS notes_new (
|
||||
hash TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
note TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (hash) REFERENCES files(hash) ON DELETE CASCADE,
|
||||
PRIMARY KEY (hash, name)
|
||||
)
|
||||
""")
|
||||
|
||||
# Copy existing notes into the default key
|
||||
cursor.execute("""
|
||||
INSERT INTO notes_new (hash, name, note, created_at, updated_at)
|
||||
SELECT hash, 'default', note, created_at, updated_at
|
||||
FROM notes
|
||||
""")
|
||||
|
||||
cursor.execute("DROP TABLE notes")
|
||||
cursor.execute("ALTER TABLE notes_new RENAME TO notes")
|
||||
self.connection.commit()
|
||||
except Exception as exc:
|
||||
logger.debug(f"Notes schema migration skipped/failed: {exc}")
|
||||
|
||||
def _update_metadata_modified_time(self, file_hash: str) -> None:
|
||||
"""Update the time_modified timestamp for a file's metadata."""
|
||||
@@ -1052,40 +1095,78 @@ class API_folder_store:
|
||||
return []
|
||||
|
||||
def get_note(self, file_hash: str) -> Optional[str]:
|
||||
"""Get note for a file by hash."""
|
||||
"""Get the default note for a file by hash."""
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT n.note FROM notes n
|
||||
WHERE n.hash = ?
|
||||
""", (file_hash,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
notes = self.get_notes(file_hash)
|
||||
if not notes:
|
||||
return None
|
||||
return notes.get("default")
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting note for hash {file_hash}: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_notes(self, file_hash: str) -> Dict[str, str]:
|
||||
"""Get all notes for a file by hash."""
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute(
|
||||
"SELECT name, note FROM notes WHERE hash = ? ORDER BY name ASC",
|
||||
(file_hash,),
|
||||
)
|
||||
out: Dict[str, str] = {}
|
||||
for name, note in cursor.fetchall() or []:
|
||||
if not name:
|
||||
continue
|
||||
out[str(name)] = str(note or "")
|
||||
return out
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting notes for hash {file_hash}: {e}", exc_info=True)
|
||||
return {}
|
||||
|
||||
def save_note(self, file_path: Path, note: str) -> None:
|
||||
"""Save note for a file."""
|
||||
"""Save the default note for a file."""
|
||||
self.set_note(file_path, "default", note)
|
||||
|
||||
def set_note(self, file_path: Path, name: str, note: str) -> None:
|
||||
"""Set a named note for a file."""
|
||||
try:
|
||||
note_name = str(name or "").strip()
|
||||
if not note_name:
|
||||
raise ValueError("Note name is required")
|
||||
|
||||
file_hash = self.get_or_create_file_entry(file_path)
|
||||
cursor = self.connection.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO notes (hash, note)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(hash) DO UPDATE SET
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO notes (hash, name, note)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(hash, name) DO UPDATE SET
|
||||
note = excluded.note,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
""", (file_hash, note))
|
||||
|
||||
""",
|
||||
(file_hash, note_name, note),
|
||||
)
|
||||
self.connection.commit()
|
||||
logger.debug(f"Saved note for {file_path}")
|
||||
logger.debug(f"Saved note '{note_name}' for {file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving note for {file_path}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def delete_note(self, file_hash: str, name: str) -> None:
|
||||
"""Delete a named note for a file by hash."""
|
||||
try:
|
||||
note_name = str(name or "").strip()
|
||||
if not note_name:
|
||||
raise ValueError("Note name is required")
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute(
|
||||
"DELETE FROM notes WHERE hash = ? AND name = ?",
|
||||
(file_hash, note_name),
|
||||
)
|
||||
self.connection.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting note '{name}' for hash {file_hash}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def search_by_tag(self, tag: str, limit: int = 100) -> List[tuple]:
|
||||
"""Search for files with a specific tag. Returns list of (hash, file_path) tuples."""
|
||||
@@ -2027,7 +2108,7 @@ def migrate_tags_to_db(library_root: Path, db: API_folder_store) -> int:
|
||||
try:
|
||||
for tags_file in library_root.rglob("*.tag"):
|
||||
try:
|
||||
base_path = tags_file.with_suffix("")
|
||||
base_path = tags_file.with_suffix("")
|
||||
tags_text = tags_file.read_text(encoding='utf-8')
|
||||
tags = [line.strip() for line in tags_text.splitlines() if line.strip()]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user