Phase 2 build initial
This commit is contained in:
179
hcfs-python/hcfs/core/filesystem.py
Normal file
179
hcfs-python/hcfs/core/filesystem.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
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: <content>"
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user