137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
|
|
import os
|
||
|
|
import shutil
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
from typing import List
|
||
|
|
|
||
|
|
class NoteServer(ABC):
|
||
|
|
@abstractmethod
|
||
|
|
def list_notes(self, directory: str) -> List[str]:
|
||
|
|
"""List all markdown files in the directory."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def read_note(self, file_path: str) -> str:
|
||
|
|
"""Read content of a note."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def move_note(self, file_path: str, target_folder: str) -> str:
|
||
|
|
"""Move a note to a target folder."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
|
||
|
|
"""Append a rewrite tag and reason to the note."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def list_subfolders(self, directory: str) -> List[str]:
|
||
|
|
"""List immediate subdirectories."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
class FileSystemServer(NoteServer):
|
||
|
|
def list_subfolders(self, directory: str) -> List[str]:
|
||
|
|
if not os.path.exists(directory):
|
||
|
|
return []
|
||
|
|
try:
|
||
|
|
return [d for d in os.listdir(directory) if os.path.isdir(os.path.join(directory, d)) and not d.startswith(".")]
|
||
|
|
except Exception:
|
||
|
|
return []
|
||
|
|
|
||
|
|
def list_notes(self, directory: str) -> List[str]:
|
||
|
|
notes = []
|
||
|
|
if not os.path.exists(directory):
|
||
|
|
return []
|
||
|
|
for root, _, files in os.walk(directory):
|
||
|
|
for file in files:
|
||
|
|
if file.endswith(".md"):
|
||
|
|
# Only return absolute paths to keep things simple
|
||
|
|
notes.append(os.path.abspath(os.path.join(root, file)))
|
||
|
|
return notes
|
||
|
|
|
||
|
|
def read_note(self, file_path: str) -> str:
|
||
|
|
try:
|
||
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
||
|
|
return f.read()
|
||
|
|
except Exception as e:
|
||
|
|
return f"Error reading file: {str(e)}"
|
||
|
|
|
||
|
|
def move_note(self, file_path: str, target_folder: str) -> str:
|
||
|
|
try:
|
||
|
|
filename = os.path.basename(file_path)
|
||
|
|
|
||
|
|
# Ensure target folder exists
|
||
|
|
if not os.path.exists(target_folder):
|
||
|
|
os.makedirs(target_folder)
|
||
|
|
|
||
|
|
new_path = os.path.join(target_folder, filename)
|
||
|
|
|
||
|
|
# Prevent overwriting by appending timestamp if exists, or just fail safely
|
||
|
|
if os.path.exists(new_path):
|
||
|
|
return f"Error: File {filename} already exists in {target_folder}"
|
||
|
|
|
||
|
|
shutil.move(file_path, new_path)
|
||
|
|
return f"Moved {filename} to {target_folder}"
|
||
|
|
except Exception as e:
|
||
|
|
return f"Error moving file: {str(e)}"
|
||
|
|
|
||
|
|
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
|
||
|
|
try:
|
||
|
|
# Check if file ends with newline to avoid appending on same line
|
||
|
|
with open(file_path, "r+", encoding="utf-8") as f:
|
||
|
|
content = f.read()
|
||
|
|
prefix = "\n" if content and not content.endswith("\n") else ""
|
||
|
|
f.write(f"{prefix}\n{rewrite_tag} {reason}\n")
|
||
|
|
return f"Flagged {os.path.basename(file_path)} for rewrite: {reason}"
|
||
|
|
except Exception as e:
|
||
|
|
return f"Error flagging file: {str(e)}"
|
||
|
|
|
||
|
|
from .couch_manager import CouchDBManager
|
||
|
|
|
||
|
|
class CouchDBNoteServer(NoteServer):
|
||
|
|
def __init__(self, url, user, password, db_name):
|
||
|
|
self.manager = CouchDBManager(url, user, password, db_name)
|
||
|
|
|
||
|
|
def list_notes(self, directory: str) -> List[str]:
|
||
|
|
# Directory here acts as a prefix filter
|
||
|
|
files_dict = self.manager.list_files(prefix_filter=directory)
|
||
|
|
return list(files_dict.keys())
|
||
|
|
|
||
|
|
def list_subfolders(self, directory: str) -> List[str]:
|
||
|
|
# CouchDB is flat. We simulate folders by looking at paths.
|
||
|
|
# This is expensive (scan all), but accurate.
|
||
|
|
all_files_dict = self.manager.list_files()
|
||
|
|
subfolders = set()
|
||
|
|
|
||
|
|
# If directory is "(All Notes)", we look at root folders
|
||
|
|
# If directory is "Inbox", we look at "Inbox/Subfolder"
|
||
|
|
|
||
|
|
prefix = directory if directory != "(All Notes)" else ""
|
||
|
|
prefix = prefix.strip("/")
|
||
|
|
|
||
|
|
for doc_id, path in all_files_dict.items():
|
||
|
|
|
||
|
|
# Normalize path
|
||
|
|
path = path.replace("\\", "/")
|
||
|
|
|
||
|
|
if prefix and not path.startswith(prefix + "/"):
|
||
|
|
continue
|
||
|
|
|
||
|
|
# Strip prefix
|
||
|
|
relative_path = path[len(prefix)+1:] if prefix else path
|
||
|
|
|
||
|
|
if "/" in relative_path:
|
||
|
|
top_level = relative_path.split("/")[0]
|
||
|
|
subfolders.add(top_level)
|
||
|
|
|
||
|
|
return sorted(list(subfolders))
|
||
|
|
|
||
|
|
def read_note(self, file_path: str) -> str:
|
||
|
|
return self.manager.read_file_content(file_path)
|
||
|
|
|
||
|
|
def move_note(self, file_path: str, target_folder: str) -> str:
|
||
|
|
return self.manager.move_file(file_path, target_folder)
|
||
|
|
|
||
|
|
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
|
||
|
|
return self.manager.flag_rewrite(file_path, reason, rewrite_tag)
|