272 lines
5.9 KiB
Plaintext
272 lines
5.9 KiB
Plaintext
var cellfs = this
|
|
|
|
// 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')
|
|
|
|
// Internal state
|
|
var mounts = [] // Array of {source, type, handle, name}
|
|
|
|
// Helper to normalize paths
|
|
function normalize_path(path) {
|
|
if (!path) return ""
|
|
// Remove leading/trailing slashes and normalize
|
|
return path.replace(/^\/+|\/+$/g, "")
|
|
}
|
|
|
|
// Helper to get directory from path
|
|
function dirname(path) {
|
|
var idx = path.lastIndexOf("/")
|
|
if (idx == -1) return ""
|
|
return path.substring(0, idx)
|
|
}
|
|
|
|
// Helper to get basename from path
|
|
function basename(path) {
|
|
var idx = path.lastIndexOf("/")
|
|
if (idx == -1) return path
|
|
return path.substring(idx + 1)
|
|
}
|
|
|
|
// Helper to join paths
|
|
function join_paths(base, rel) {
|
|
base = base.replace(/\/+$/, "")
|
|
rel = rel.replace(/^\/+/, "")
|
|
if (!base) return rel
|
|
if (!rel) return base
|
|
return base + "/" + rel
|
|
}
|
|
|
|
// 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 { // fs
|
|
var full_path = join_paths(mount.source, path)
|
|
try {
|
|
var st = fd.stat(full_path)
|
|
return st.isFile
|
|
} 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 (path.startsWith("@")) {
|
|
var idx = path.indexOf("/")
|
|
var mount_name = ""
|
|
var rel_path = ""
|
|
|
|
if (idx == -1) {
|
|
mount_name = path.substring(1)
|
|
rel_path = ""
|
|
} else {
|
|
mount_name = path.substring(1, idx)
|
|
rel_path = path.substring(idx + 1)
|
|
}
|
|
|
|
// Find named mount
|
|
var mount = null
|
|
for (var m of mounts) {
|
|
if (m.name == mount_name) {
|
|
mount = m
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!mount) {
|
|
throw new Error("Unknown mount point: @" + mount_name)
|
|
}
|
|
|
|
return { mount: mount, path: rel_path }
|
|
}
|
|
|
|
// Search path
|
|
for (var mount of mounts) {
|
|
if (mount_exists(mount, path)) {
|
|
return { mount: mount, path: path }
|
|
}
|
|
}
|
|
|
|
if (must_exist) {
|
|
// throw new Error("File not found in any mount: " + path)
|
|
return null
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// Mount a source
|
|
function mount(source, name) {
|
|
// Check if source exists
|
|
var st = null
|
|
try {
|
|
st = fd.stat(source)
|
|
} catch (e) {
|
|
throw new Error("Mount source not found: " + source)
|
|
}
|
|
|
|
var mount_info = {
|
|
source: source,
|
|
name: name || null,
|
|
type: 'fs',
|
|
handle: null
|
|
}
|
|
|
|
if (st.isDirectory) {
|
|
mount_info.type = 'fs'
|
|
} else if (st.isFile) {
|
|
// Assume zip
|
|
var zip_data = null
|
|
// Always read as bytes for zip
|
|
var f = fd.open(source, 'r')
|
|
var s = fd.fstat(f)
|
|
zip_data = fd.read(f, s.size)
|
|
fd.close(f)
|
|
|
|
var zip = miniz.read(zip_data)
|
|
if (!zip || typeof zip.count != 'function') {
|
|
throw new Error("Invalid ZIP file: " + source)
|
|
}
|
|
|
|
mount_info.type = 'zip'
|
|
mount_info.handle = zip
|
|
} else {
|
|
throw new Error("Unsupported mount source type: " + source)
|
|
}
|
|
|
|
mounts.push(mount_info)
|
|
|
|
log.console(`Mounted ${source} ${name ? 'as @' + name : ''}`)
|
|
}
|
|
|
|
// Unmount
|
|
function unmount(name_or_source) {
|
|
for (var i = 0; i < mounts.length; i++) {
|
|
if (mounts[i].name == name_or_source || mounts[i].source == name_or_source) {
|
|
mounts.splice(i, 1)
|
|
return
|
|
}
|
|
}
|
|
throw new Error("Mount not found: " + name_or_source)
|
|
}
|
|
|
|
// Read file
|
|
function slurp(path) {
|
|
var res = resolve(path, true)
|
|
if (!res) throw new Error("File not found: " + path)
|
|
|
|
if (res.mount.type == 'zip') {
|
|
return res.mount.handle.slurp(res.path)
|
|
} else {
|
|
var full_path = join_paths(res.mount.source, res.path)
|
|
return fd.slurp(full_path)
|
|
}
|
|
}
|
|
|
|
// Read file as bytes
|
|
function slurpbytes(path) {
|
|
var res = resolve(path, true)
|
|
if (!res) throw new Error("File not found: " + path)
|
|
|
|
if (res.mount.type == 'zip') {
|
|
return res.mount.handle.slurp(res.path)
|
|
} else {
|
|
var full_path = join_paths(res.mount.source, res.path)
|
|
var f = fd.open(full_path, 'r')
|
|
var s = fd.fstat(f)
|
|
var data = fd.read(f, s.size)
|
|
fd.close(f)
|
|
return data
|
|
}
|
|
}
|
|
|
|
// Write file
|
|
function slurpwrite(path, data) {
|
|
if (!path.startsWith("@")) {
|
|
throw new Error("slurpwrite requires a named mount (e.g. @name/file.txt)")
|
|
}
|
|
|
|
var res = resolve(path, false)
|
|
// For named mounts, resolve returns the mount even if file doesn't exist
|
|
|
|
if (res.mount.type == 'zip') {
|
|
throw new Error("Cannot write to zip mount: @" + res.mount.name)
|
|
}
|
|
|
|
var full_path = join_paths(res.mount.source, res.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 (path.startsWith("@")) {
|
|
return mount_exists(res.mount, res.path)
|
|
}
|
|
return res != null
|
|
}
|
|
|
|
// Stat
|
|
function stat(path) {
|
|
var res = resolve(path, true)
|
|
if (!res) throw new 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 {
|
|
var full_path = 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() {
|
|
var paths = []
|
|
for (var mount of mounts) {
|
|
paths.push(mount.source)
|
|
}
|
|
return paths
|
|
}
|
|
|
|
// Initialize
|
|
mount('.', 'cwd')
|
|
|
|
// Exports
|
|
cellfs.mount = mount
|
|
cellfs.unmount = unmount
|
|
cellfs.slurp = slurp
|
|
cellfs.slurpbytes = slurpbytes
|
|
cellfs.slurpwrite = slurpwrite
|
|
cellfs.exists = exists
|
|
cellfs.stat = stat
|
|
cellfs.searchpath = searchpath
|
|
|
|
return cellfs
|