Files
cell/cellfs.cm
2026-01-21 00:52:18 -06:00

469 lines
11 KiB
Plaintext

var cellfs = {}
// CellFS: A filesystem implementation using miniz and raw OS filesystem
// Supports mounting multiple sources (fs, zip) and named mounts (@name)
var fd = use('fd')
var miniz = use('miniz')
var qop = use('qop')
var wildstar = use('wildstar')
// Internal state
var mounts = [] // Array of {source, type, handle, name}
var writepath = "."
// Helper to normalize paths
function normalize_path(path) {
if (!path) return ""
// Remove leading/trailing slashes and normalize
return replace(path, /^\/+|\/+$/, "")
}
// Check if a file exists in a specific mount
function mount_exists(mount, path) {
if (mount.type == 'zip') {
try {
mount.handle.mod(path)
return true
} catch (e) {
return false
}
} else if (mount.type == 'qop') {
try {
return mount.handle.stat(path) != null
} catch (e) {
return false
}
} else { // fs
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isFile || st.isDirectory
} catch (e) {
return false
}
}
}
// Check if a path refers to a directory in a specific mount
function is_directory(path) {
var res = resolve(path)
var mount = res.mount
if (mount.type == 'zip') {
try {
return mount.handle.is_directory(path);
} catch (e) {
return false;
}
} else if (mount.type == 'qop') {
try {
return mount.handle.is_directory(path);
} catch (e) {
return false;
}
} else { // fs
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isDirectory
} catch (e) {
return false
}
}
}
// Resolve a path to a specific mount and relative path
// Returns { mount, path } or throws/returns null
function resolve(path, must_exist) {
path = normalize_path(path)
// Check for named mount
if (starts_with(path, "@")) {
var idx = search(path, "/")
var mount_name = ""
var rel_path = ""
if (idx == null) {
mount_name = text(path, 1)
rel_path = ""
} else {
mount_name = text(path, 1, idx)
rel_path = text(path, idx + 1)
}
// Find named mount
var mount = null
arrfor(mounts, function(m) {
if (m.name == mount_name) {
mount = m
return true
}
}, false, true)
if (!mount) {
throw Error("Unknown mount point: @" + mount_name)
}
return { mount: mount, path: rel_path }
}
// Search path
var found_mount = null
arrfor(mounts, function(mount) {
if (mount_exists(mount, path)) {
found_mount = { mount: mount, path: path }
return true
}
}, false, true)
if (found_mount) {
return found_mount
}
if (must_exist) {
throw Error("File not found in any mount: " + path)
}
}
// Mount a source
function mount(source, name) {
// Check if source exists
var st = fd.stat(source)
var mount_info = {
source: source,
name: name || null,
type: 'fs',
handle: null,
zip_blob: null
}
if (st.isDirectory) {
mount_info.type = 'fs'
} else if (st.isFile) {
var blob = fd.slurp(source)
// Try QOP first (it's likely faster to fail?) or Zip?
// QOP open checks magic.
var qop_archive = null
try {
qop_archive = qop.open(blob)
} catch(e) {}
if (qop_archive) {
mount_info.type = 'qop'
mount_info.handle = qop_archive
mount_info.zip_blob = blob // keep blob alive
} else {
var zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) {
throw Error("Invalid archive file (not zip or qop): " + source)
}
mount_info.type = 'zip'
mount_info.handle = zip
mount_info.zip_blob = blob // keep blob alive
}
} else {
throw Error("Unsupported mount source type: " + source)
}
push(mounts, mount_info)
}
// Unmount
function unmount(name_or_source) {
mounts = filter(mounts, function(mount) {
return mount.name != name_or_source && mount.source != name_or_source
})
}
// Read file
function slurp(path) {
var res = resolve(path, true)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
return res.mount.handle.slurp(res.path)
} else if (res.mount.type == 'qop') {
var data = res.mount.handle.read(res.path)
if (!data) throw Error("File not found in qop: " + path)
return data
} else {
var full_path = fd.join_paths(res.mount.source, res.path)
return fd.slurp(full_path)
}
}
// Write file
function slurpwrite(path, data) {
var full_path = writepath + "/" + path
var f = fd.open(full_path, 'w')
fd.write(f, data)
fd.close(f)
}
// Check existence
function exists(path) {
var res = resolve(path, false)
if (starts_with(path, "@")) {
return mount_exists(res.mount, res.path)
}
return res != null
}
// Stat
function stat(path) {
var res = resolve(path, true)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
var mod = res.mount.handle.mod(res.path)
return {
filesize: 0,
modtime: mod * 1000,
isDirectory: false
}
} else if (res.mount.type == 'qop') {
var s = res.mount.handle.stat(res.path)
if (!s) throw Error("File not found in qop: " + path)
return {
filesize: s.size,
modtime: s.modtime,
isDirectory: s.isDirectory
}
} else {
var full_path = fd.join_paths(res.mount.source, res.path)
var s = fd.stat(full_path)
return {
filesize: s.size,
modtime: s.mtime,
isDirectory: s.isDirectory
}
}
}
// Get search paths
function searchpath() {
return array(mounts)
}
// Mount a package using the shop system
function mount_package(name) {
if (name == null) {
mount('.', null)
return
}
var shop = use('internal/shop')
var dir = shop.get_package_dir(name)
if (!dir) {
throw Error("Package not found: " + name)
}
mount(dir, name)
}
// New functions for qjs_io compatibility
function match(str, pattern) {
return wildstar.match(pattern, str, wildstar.WM_PATHNAME | wildstar.WM_PERIOD | wildstar.WM_WILDSTAR)
}
function rm(path) {
var res = resolve(path, true)
if (res.mount.type != 'fs') throw Error("Cannot delete from non-fs mount")
var full_path = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full_path)
if (st.isDirectory) fd.rmdir(full_path)
else fd.unlink(full_path)
}
function mkdir(path) {
var full = fd.join_paths(writepath, path)
fd.mkdir(full)
}
function set_writepath(path) {
writepath = path
}
function basedir() {
return fd.getcwd()
}
function prefdir(org, app) {
return "./"
}
function realdir(path) {
var res = resolve(path, false)
if (!res) return null
return fd.join_paths(res.mount.source, res.path)
}
function enumerate(path, recurse) {
if (path == null) path = ""
var res = resolve(path, true)
var results = []
function visit(curr_full, rel_prefix) {
var list = fd.readdir(curr_full)
if (!list) return
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
push(results, item_rel)
if (recurse) {
var st = fd.stat(fd.join_paths(curr_full, item))
if (st.isDirectory) {
visit(fd.join_paths(curr_full, item), item_rel)
}
}
})
}
if (res.mount.type == 'fs') {
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = length(prefix)
// Use a set to avoid duplicates if we are simulating directories
var seen = {}
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!recurse) {
var slash = search(rel, '/')
if (slash != null) {
rel = text(rel, 0, slash)
}
}
if (!seen[rel]) {
seen[rel] = true
push(results, rel)
}
}
})
}
return results
}
function globfs(globs, dir) {
if (dir == null) dir = ""
var res = resolve(dir, true)
var results = []
function check_neg(path) {
var result = false
arrfor(globs, function(g) {
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function check_pos(path) {
var result = false
arrfor(globs, function(g) {
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function visit(curr_full, rel_prefix) {
if (rel_prefix && check_neg(rel_prefix)) return
var list = fd.readdir(curr_full)
if (!list) return
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_full = fd.join_paths(curr_full, item)
var st = fd.stat(child_full)
if (st.isDirectory) {
if (!check_neg(item_rel)) {
visit(child_full, item_rel)
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
push(results, item_rel)
}
}
})
}
if (res.mount.type == 'fs') {
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = length(prefix)
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!check_neg(rel) && check_pos(rel)) {
push(results, rel)
}
}
})
}
return results
}
// Exports
cellfs.mount = mount
cellfs.mount_package = mount_package
cellfs.unmount = unmount
cellfs.slurp = slurp
cellfs.slurpwrite = slurpwrite
cellfs.exists = exists
cellfs.is_directory = is_directory
cellfs.stat = stat
cellfs.searchpath = searchpath
cellfs.match = match
cellfs.enumerate = enumerate
cellfs.globfs = globfs
cellfs.rm = rm
cellfs.mkdir = mkdir
cellfs.writepath = set_writepath
cellfs.basedir = basedir
cellfs.prefdir = prefdir
cellfs.realdir = realdir
cellfs.mount('.')
return cellfs