UNPKG

nufatfs

Version:

A new async-friendly library for accessing FAT16 and FAT32 filesystems

215 lines (214 loc) 9.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FatFSFileHandle = exports.FatFilesystem = void 0; const low_level_1 = require("./low-level"); const types_1 = require("./types"); const utils_1 = require("./utils"); const constructors_1 = require("./constructors"); // This class aims to contain the demons stored in LowLevelFatFilesystem. class FatFilesystem { constructor(fat) { this.fat = fat; } static async create(driver, bypassCoherencyCheck = false, forceFSType) { const fat = await low_level_1.LowLevelFatFilesystem._create(driver, bypassCoherencyCheck, forceFSType); return new FatFilesystem(fat); } async open(path, writable = false) { if (writable && !this.fat.isWritable) { throw new low_level_1.FatError("Cannot open a file for writing on a read-only volume!"); } const tree = await this.fat.traverseEntries(path); if (!tree) return null; const [parent, entry] = tree.slice(-2); if (!parent || !entry || entry instanceof low_level_1.CachedDirectory) return null; const chain = this.fat.constructClusterChain(entry._firstCluster, true, entry.fileSize); return new FatFSFileHandle(this.fat, chain, writable, entry, parent); } async create(path) { // Make sure the file we're about to create doesn't already exist. const existingEntry = await this.fat.traverse(path); if (existingEntry) { return null; } let lastSlash = path.lastIndexOf("/"); let parent; if (lastSlash === -1) { parent = this.fat.root; } else { const parentPath = path.slice(0, lastSlash); parent = await this.fat.traverse(parentPath); if (!parent || !(parent instanceof low_level_1.CachedDirectory)) { return null; } } const name = path.slice(lastSlash + 1); const encName = (0, utils_1.nameNormalTo83)(name); const entry = (0, constructors_1.newFatFSDirectoryEntry)(encName, types_1.FatFSDirectoryEntryAttributes.None, 0, 0); await parent.getEntries(); // Load entry from driver parent.rawDirectoryEntries.push(entry); this.fat.markAsAltered(parent); return new FatFSFileHandle(this.fat, this.fat.constructClusterChain(0, true), true, entry, parent); } async listDir(path) { const entry = await this.fat.traverse(path); if (!entry || !(entry instanceof low_level_1.CachedDirectory)) return null; return entry.listDir(); } async getSizeOf(path) { const entry = await this.fat.traverse(path); if (!entry || (entry instanceof low_level_1.CachedDirectory)) return null; return entry.fileSize; } getStats() { const totalClusters = this.fat.allocator.freemap.length; const freeClusters = this.fat.allocator.freemap.filter(e => e).length; return { totalClusters, freeClusters, totalBytes: totalClusters * this.fat.clusterSizeInBytes, freeBytes: freeClusters * this.fat.clusterSizeInBytes, }; } async delete(path) { const tree = await this.fat.traverseEntries(path); if (!tree) return null; const [parent, entry] = tree.slice(-2); if (entry instanceof low_level_1.CachedDirectory) { if ((await entry.getEntries()).length > 2) { // 2 entries - '.' and '..' throw new low_level_1.FatError("Cannot delete a non-empty directory."); } } if (!entry || !parent || !(parent instanceof low_level_1.CachedDirectory)) { throw new low_level_1.FatError("File not found!"); } let rawEntry = entry instanceof low_level_1.CachedDirectory ? entry.underlying : entry; // Mark as deleted rawEntry.filename[0] = low_level_1.FAT_MARKER_DELETED; rawEntry._filenameStr = ''; // Ask the main driver to update this entry's parent this.fat.markAsAltered(parent); // Remove the file from parent's cache parent.rawDirectoryEntries.splice(parent.rawDirectoryEntries.indexOf(entry), 1); // Construct a chain, then free it const cluster = rawEntry._firstCluster; const chain = this.fat.getClusterChainFromFAT(cluster); this.fat.allocator.addClusterListToFreelist(chain); // Zero out the chain in FAT for (let e of chain) { this.fat.writeFATClusterEntry(e, 0); } } async rename(path, newPath) { const existingNewTree = await this.fat.traverseEntries(newPath); if (existingNewTree) { throw new low_level_1.FatError("File already exists!"); } let lastSlash = newPath.lastIndexOf("/"); const newParentPath = newPath.includes("/") ? newPath.slice(0, lastSlash) : null; const newName = newPath.slice(lastSlash + 1); const newParent = newParentPath ? await this.fat.traverse(newParentPath) : this.fat.root; const entry = await this.fat.traverse(path); lastSlash = newPath.lastIndexOf("/"); let oldParentPath = path.slice(0, lastSlash); const oldParent = await this.fat.traverse(oldParentPath); if (!(newParent instanceof low_level_1.CachedDirectory) || !(newParent instanceof low_level_1.CachedDirectory)) { throw new low_level_1.FatError("Cannot move an entry into a file, not a directory!"); } if (!entry) throw new low_level_1.FatError("File doens't exist!"); let rawEntry = entry instanceof low_level_1.CachedDirectory ? entry.underlying : entry; // Update name rawEntry._filenameStr = (0, utils_1.nameNormalTo83)(newName); // Renaming will strip LFNs, since we can't encode them yet. // Cache await oldParent.getEntries(); let thisEntryIndex = oldParent.rawDirectoryEntries.indexOf(entry); // Remove both the actual entry reference, and the LFNs that point to it oldParent.rawDirectoryEntries.splice(thisEntryIndex - rawEntry._lfns, 1 + rawEntry._lfns); rawEntry._lfns = 0; // Mark oldParent as in need of a flush this.fat.markAsAltered(oldParent); // Cache await newParent.getEntries(); newParent.rawDirectoryEntries.push(entry); // Mark newParent as in need of a flush this.fat.markAsAltered(newParent); } async mkdir(path) { let lastSlash = path.lastIndexOf("/"); const parentPath = path.includes("/") ? path.slice(0, lastSlash) : null; const name = (0, utils_1.nameNormalTo83)(path.slice(lastSlash + 1)); const parent = parentPath ? await this.fat.traverse(parentPath) : this.fat.root; if (await parent.findEntry(name)) return; const rootCluster = this.fat.allocator.allocate(null, 1)[0]; if (!rootCluster || (this.fat.isFat16Or12 && rootCluster.index > 0xFFFF)) { throw new Error("Cannot allocate next cluster!"); } // Create the directory entry const dirEntry = (0, constructors_1.newFatFSDirectoryEntry)(name, types_1.FatFSDirectoryEntryAttributes.Directory, rootCluster.index, 0); // Create the underlying low-level structures. const ownDirEntry = (0, constructors_1.newFatFSDirectoryEntry)(". ", types_1.FatFSDirectoryEntryAttributes.Directory, rootCluster.index, 0); const ownParentEntry = (0, constructors_1.newFatFSDirectoryEntry)(".. ", types_1.FatFSDirectoryEntryAttributes.Directory, parent.initialCluster === -1 ? 0 : parent.initialCluster, 0); // 'format' the cluster await rootCluster.write(new Uint8Array(rootCluster.length).fill(0)); // Create the cached entry const cachedEntry = low_level_1.CachedDirectory.readyMade(this.fat, [ownDirEntry, ownParentEntry], rootCluster.index, dirEntry); // Cache await parent.getEntries(); parent.rawDirectoryEntries.push(cachedEntry); this.fat.markAsAltered(parent); this.fat.markAsAltered(cachedEntry); } async flushMetadataChanges() { return this.fat.flush(); } getUnderlying() { return this.fat; } } exports.FatFilesystem = FatFilesystem; class FatFSFileHandle { get length() { return this.chain.getTotalLength(); } constructor(fat, chain, writable, underlying, parent) { this.fat = fat; this.chain = chain; this.writable = writable; this.underlying = underlying; this.parent = parent; } async seek(to) { await this.chain.seek(to); } async read(bytes) { return this.chain.read(bytes); } async readAll() { return this.chain.readAll(); } async close() { if (!this.writable) return; await this.chain.flushChanges(); this.underlying.fileSize = this.chain.getTotalLength(); if (this.underlying._firstCluster === 0 && this.underlying.fileSize) { // This was an empty file before, but is not anymore const rootCluster = this.chain.links[0].index; this.underlying._firstCluster = rootCluster; } this.fat.markAsAltered(this.parent); } async write(data) { return this.chain.write(data); } } exports.FatFSFileHandle = FatFSFileHandle;