Files
herolib/lib/vfs/vfs_db/vfs_implementation.v
2025-03-13 04:34:10 +01:00

344 lines
9.8 KiB
V

module vfs_db
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.core.texttools
import arrays
import log
import os
import time
// Implementation of VFSImplementation interface
pub fn (mut fs DatabaseVFS) root_get() !vfs.FSEntry {
return fs.root_get_as_dir()!
}
pub fn (mut self DatabaseVFS) file_create(path_ string) !vfs.FSEntry {
path := '/${path_.trim_left('/').trim_right('/')}'
// Get parent directory
parent_path := os.dir(path)
file_name := os.base(path)
log.info('[DatabaseVFS] Creating file ${file_name} in ${parent_path} for ${path_}')
mut parent_dir := self.get_directory(parent_path) or {
return error('Failed to get parent directory ${parent_path}: ${err}')
}
entry := self.directory_touch(mut parent_dir, file_name)!
log.info('[DatabaseVFS] Created file ${file_name} in ${parent_path}')
return entry
}
pub fn (mut self DatabaseVFS) file_read(path_ string) ![]u8 {
path := texttools.path_fix(path_)
log.info('[DatabaseVFS] Reading file ${path}')
mut file := self.get_entry(path)!
log.info('[DatabaseVFS] Got file ${path}')
if mut file is File {
mut file_data := []u8{}
// log.debug('[DatabaseVFS] Got database chunk ids ${chunk_ids}')
for id in file.chunk_ids {
log.debug('[DatabaseVFS] Getting chunk ${id}')
// there were chunk ids stored with file so file has data
if chunk_bytes := self.db_data.get(id) {
file_data << chunk_bytes
} else {
return error('Failed to fetch file data: ${err}')
}
}
return file_data
}
return error('Not a file: ${path}')
}
pub fn (mut self DatabaseVFS) file_write(path_ string, data []u8) ! {
path := texttools.path_fix(path_)
if mut entry := self.get_entry(path) {
if mut entry is File {
log.info('[DatabaseVFS] Writing ${data.len} bytes to ${path}')
self.save_file(entry, data) or {
return error('Failed to save file: ${err}')
}
} else {
panic('handle error')
}
} else {
self.file_create(path) or {
return error('Failed to create file: ${err}')
}
self.file_write(path, data)!
}
}
pub fn (mut self DatabaseVFS) file_concatenate(path_ string, data []u8) ! {
path := texttools.path_fix(path_)
if data.len == 0 {
return // Nothing to append
}
if mut entry := self.get_entry(path) {
if mut entry is File {
log.info('[DatabaseVFS] Appending ${data.len} bytes to ${path}')
// Split new data into chunks of 64 KB
chunks := arrays.chunk(data, (64 * 1024) - 1)
mut chunk_ids := entry.chunk_ids.clone() // Start with existing chunk IDs
// Add new chunks
for chunk in chunks {
chunk_id := self.db_data.set(data: chunk) or {
return error('Failed to save file data chunk: ${err}')
}
chunk_ids << chunk_id
log.debug('[DatabaseVFS] Added chunk ${chunk_id} to ${path}')
}
// Update the file with new chunk IDs and updated size
updated_file := File{
metadata: vfs.Metadata{
...entry.metadata
size: entry.metadata.size + u64(data.len)
modified_at: time.now().unix()
}
chunk_ids: chunk_ids
parent_id: entry.parent_id
}
// Encode the file with all its metadata
metadata_bytes := updated_file.encode()
// Save the metadata_bytes to metadata_db
metadata_db_id := self.db_metadata.set(data: metadata_bytes) or {
return error('Failed to save file metadata on id:${entry.metadata.id}: ${err}')
}
self.id_table[entry.metadata.id] = metadata_db_id
} else {
return error('Not a file: ${path}')
}
} else {
// If file doesn't exist, create it first
self.file_create(path) or {
return error('Failed to create file: ${err}')
}
// Then write data to it
self.file_write(path, data)!
}
}
pub fn (mut self DatabaseVFS) file_delete(path string) ! {
log.info('[DatabaseVFS] Deleting file ${path}')
parent_path := os.dir(path)
file_name := os.base(path)
mut parent_dir := self.get_directory(parent_path)!
self.directory_rm(mut parent_dir, file_name) or {
log.error(err.msg())
return err
}
}
pub fn (mut self DatabaseVFS) dir_create(path_ string) !vfs.FSEntry {
path := '/${path_.trim_left('/').trim_right('/')}'
log.info('[DatabaseVFS] Creating Directory ${path}')
parent_path := os.dir(path)
file_name := os.base(path)
mut parent_dir := self.get_directory(parent_path)!
return self.directory_mkdir(mut parent_dir, file_name)!
}
pub fn (mut self DatabaseVFS) dir_list(path string) ![]vfs.FSEntry {
log.info('[DatabaseVFS] Listing Directory ${path}')
mut dir := self.get_directory(path)!
mut entries := []vfs.FSEntry{}
for child in self.directory_children(mut dir, false)! {
if child is File {
entries << vfs.FSEntry(child)
} else if child is Directory {
entries << vfs.FSEntry(child)
} else if child is Symlink {
entries << vfs.FSEntry(child)
}
}
return entries
}
pub fn (mut self DatabaseVFS) dir_delete(path string) ! {
log.info('[DatabaseVFS] Deleting Directory ${path}')
parent_path := os.dir(path)
dir_name := os.base(path)
mut parent_dir := self.get_directory(parent_path)!
self.directory_rm(mut parent_dir, dir_name)!
}
pub fn (mut self DatabaseVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
log.info('[DatabaseVFS] Creating link ${target_path}')
parent_path := os.dir(link_path)
link_name := os.base(link_path)
mut parent_dir := self.get_directory(parent_path)!
mut symlink := Symlink{
metadata: vfs.Metadata{
id: self.get_next_id()
name: link_name
file_type: .symlink
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
mode: 0o777
owner: 'user'
group: 'user'
}
target: target_path
parent_id: parent_dir.metadata.id
}
self.directory_add_symlink(mut parent_dir, mut symlink)!
return symlink
}
pub fn (mut self DatabaseVFS) link_read(path string) !string {
log.info('[DatabaseVFS] Reading link ${path}')
mut entry := self.get_entry(path)!
if mut entry is Symlink {
return entry.get_target()!
}
return error('Not a symlink: ${path}')
}
pub fn (mut self DatabaseVFS) link_delete(path string) ! {
log.info('[DatabaseVFS] Deleting link ${path}')
parent_path := os.dir(path)
file_name := os.base(path)
mut parent_dir := self.get_directory(parent_path)!
self.directory_rm(mut parent_dir, file_name)!
}
pub fn (mut self DatabaseVFS) exists(path_ string) bool {
path := if !path_.starts_with('/') {
'/${path_}'
} else {
path_
}
if path == '/' {
return true
}
// self.print() or {panic(err)}
log.debug('[DatabaseVFS] Checking path exists ${path}')
self.get_entry(path) or { return false }
return true
}
pub fn (mut fs DatabaseVFS) get(path_ string) !vfs.FSEntry {
path := texttools.path_fix(path_)
log.info('[DatabaseVFS] Getting filesystem entry ${path}')
return fs.get_entry(path)!
}
pub fn (mut self DatabaseVFS) rename(old_path string, new_path string) !vfs.FSEntry {
log.info('[DatabaseVFS] Renaming ${old_path} to ${new_path}')
src_parent_path := os.dir(old_path)
src_name := os.base(old_path)
dst_name := os.base(new_path)
mut src_parent_dir := self.get_directory(src_parent_path)!
return self.directory_rename(src_parent_dir, src_name, dst_name)!
}
pub fn (mut self DatabaseVFS) copy(src_path_ string, dst_path_ string) !vfs.FSEntry {
src_path := texttools.path_fix_absolute(src_path_)
dst_path := texttools.path_fix_absolute(dst_path_)
log.info('[DatabaseVFS] Copying ${src_path} to ${dst_path}')
src_parent_path := os.dir(src_path)
dst_parent_path := os.dir(dst_path)
if !self.exists(src_parent_path) {
return error('${src_parent_path} does not exist')
}
if !self.exists(dst_parent_path) {
return error('${dst_parent_path} does not exist')
}
src_name := os.base(src_path)
dst_name := os.base(dst_path)
mut src_parent_dir := self.get_directory(src_parent_path)!
mut dst_parent_dir := self.get_directory(dst_parent_path)!
if src_parent_dir == dst_parent_dir && src_name == dst_name {
return error('Moving to the same path not supported')
}
return self.directory_copy(mut src_parent_dir,
src_entry_name: src_name
dst_entry_name: dst_name
dst_parent_dir: dst_parent_dir
)!
}
// copy_file creates a copy of a file
pub fn (mut self DatabaseVFS) copy_file(file File) !&File {
log.info('[DatabaseVFS] Copying file ${file.metadata.name}')
// Save the file with its metadata and data
self.save_file(file, [])!
// Load the file from the database
mut entry := self.load_entry(file.metadata.id)!
if mut entry is File {
return &entry
}
return error('Failed to copy file: entry is not a file')
}
pub fn (mut self DatabaseVFS) move(src_path string, dst_path string) !vfs.FSEntry {
log.info('[DatabaseVFS] Moving ${texttools.path_fix(src_path)} to ${texttools.path_fix(dst_path)}')
src_parent_path := os.dir(texttools.path_fix_absolute(src_path))
dst_parent_path := os.dir(texttools.path_fix_absolute(dst_path))
if !self.exists(src_parent_path) {
return error('${src_parent_path} does not exist')
}
if !self.exists(dst_parent_path) {
return error('${dst_parent_path} does not exist')
}
src_name := os.base(src_path)
dst_name := os.base(dst_path)
mut src_parent_dir := self.get_directory(src_parent_path)!
mut dst_parent_dir := self.get_directory(dst_parent_path)!
if src_parent_dir == dst_parent_dir && src_name == dst_name {
return error('Moving to the same path not supported')
}
return self.directory_move(src_parent_dir,
src_entry_name: src_name
dst_entry_name: dst_name
dst_parent_dir: dst_parent_dir
)!
}
pub fn (mut self DatabaseVFS) delete(path_ string) ! {
if path_ == '/' || path_ == '' || path_ == '.' {
return error('cant delete root')
}
path := '/${path_.trim_left('/').trim_right('/')}'
parent_path := os.dir(path)
file_name := os.base(path)
mut parent_dir := self.get_directory(parent_path)!
self.directory_rm(mut parent_dir, file_name) or {
log.error('[DatabaseVFS] Failed to remove ${file_name} from ${parent_dir.metadata.name}\n${err}')
return err
}
}
pub fn (mut self DatabaseVFS) destroy() ! {
// Nothing to do as the core VFS handles cleanup
}