""" HCFS Filesystem - FUSE-based virtual filesystem layer. """ import os import stat import errno import time from typing import Dict, Optional from pathlib import Path import pyfuse3 from pyfuse3 import FUSEError from .context_db import ContextDatabase, Context class HCFSFilesystem(pyfuse3.Operations): """ HCFS FUSE filesystem implementation. Maps directory navigation to context scope and provides virtual files for context access. """ def __init__(self, context_db: ContextDatabase, mount_point: str): super().__init__() self.context_db = context_db self.mount_point = mount_point self._inode_counter = 1 self._inode_to_path: Dict[int, str] = {1: "/"} # Root inode self._path_to_inode: Dict[str, int] = {"/": 1} # Virtual files self.CONTEXT_FILE = ".context" self.CONTEXT_LIST_FILE = ".context_list" self.CONTEXT_PUSH_FILE = ".context_push" def _get_inode(self, path: str) -> int: """Get or create inode for path.""" if path in self._path_to_inode: return self._path_to_inode[path] self._inode_counter += 1 inode = self._inode_counter self._inode_to_path[inode] = path self._path_to_inode[path] = inode return inode def _get_path(self, inode: int) -> str: """Get path for inode.""" return self._inode_to_path.get(inode, "/") def _is_virtual_file(self, path: str) -> bool: """Check if path is a virtual context file.""" basename = os.path.basename(path) return basename in [self.CONTEXT_FILE, self.CONTEXT_LIST_FILE, self.CONTEXT_PUSH_FILE] async def getattr(self, inode: int, ctx=None) -> pyfuse3.EntryAttributes: """Get file attributes.""" path = self._get_path(inode) entry = pyfuse3.EntryAttributes() entry.st_ino = inode entry.st_uid = os.getuid() entry.st_gid = os.getgid() entry.st_atime_ns = int(time.time() * 1e9) entry.st_mtime_ns = int(time.time() * 1e9) entry.st_ctime_ns = int(time.time() * 1e9) if self._is_virtual_file(path): # Virtual files are readable text files entry.st_mode = stat.S_IFREG | 0o644 entry.st_size = 1024 # Placeholder size else: # Directories entry.st_mode = stat.S_IFDIR | 0o755 entry.st_size = 0 return entry async def lookup(self, parent_inode: int, name: bytes, ctx=None) -> pyfuse3.EntryAttributes: """Look up a directory entry.""" parent_path = self._get_path(parent_inode) child_path = os.path.join(parent_path, name.decode('utf-8')) # Normalize path if child_path.startswith("//"): child_path = child_path[1:] child_inode = self._get_inode(child_path) return await self.getattr(child_inode, ctx) async def opendir(self, inode: int, ctx=None) -> int: """Open directory.""" return inode async def readdir(self, inode: int, start_id: int, token) -> None: """Read directory contents.""" path = self._get_path(inode) # Always show virtual context files in every directory entries = [ (self.CONTEXT_FILE, await self.getattr(self._get_inode(os.path.join(path, self.CONTEXT_FILE)))), (self.CONTEXT_LIST_FILE, await self.getattr(self._get_inode(os.path.join(path, self.CONTEXT_LIST_FILE)))), (self.CONTEXT_PUSH_FILE, await self.getattr(self._get_inode(os.path.join(path, self.CONTEXT_PUSH_FILE)))), ] # Add subdirectories (you might want to make this dynamic based on context paths) # For now, allowing any directory to be created by navigation for i, (name, attr) in enumerate(entries): if i >= start_id: if not pyfuse3.readdir_reply(token, name.encode('utf-8'), attr, i + 1): break async def open(self, inode: int, flags: int, ctx=None) -> int: """Open file.""" path = self._get_path(inode) if not self._is_virtual_file(path): raise FUSEError(errno.EISDIR) return inode async def read(self, fh: int, offset: int, size: int) -> bytes: """Read from virtual files.""" path = self._get_path(fh) basename = os.path.basename(path) dir_path = os.path.dirname(path) if basename == self.CONTEXT_FILE: # Return aggregated context for current directory contexts = self.context_db.get_context_by_path(dir_path, depth=1) content = "\\n".join(f"[{ctx.path}] {ctx.content}" for ctx in contexts) elif basename == self.CONTEXT_LIST_FILE: # List contexts at current path contexts = self.context_db.list_contexts_at_path(dir_path) content = "\\n".join(f"ID: {ctx.id}, Path: {ctx.path}, Author: {ctx.author}, Created: {ctx.created_at}" for ctx in contexts) elif basename == self.CONTEXT_PUSH_FILE: # Instructions for pushing context content = f"Write to this file to push context to path: {dir_path}\\nFormat: " else: content = "Unknown virtual file" content_bytes = content.encode('utf-8') return content_bytes[offset:offset + size] async def write(self, fh: int, offset: int, data: bytes) -> int: """Write to virtual files (context_push only).""" path = self._get_path(fh) basename = os.path.basename(path) dir_path = os.path.dirname(path) if basename == self.CONTEXT_PUSH_FILE: # Push new context to current directory content = data.decode('utf-8').strip() context = Context( id=None, path=dir_path, content=content, author="fuse_user" ) self.context_db.store_context(context) return len(data) else: raise FUSEError(errno.EACCES) async def mkdir(self, parent_inode: int, name: bytes, mode: int, ctx=None) -> pyfuse3.EntryAttributes: """Create directory (virtual - just for navigation).""" parent_path = self._get_path(parent_inode) new_path = os.path.join(parent_path, name.decode('utf-8')) if new_path.startswith("//"): new_path = new_path[1:] new_inode = self._get_inode(new_path) return await self.getattr(new_inode, ctx)