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)