browserfs
Version:
A filesystem in your browser!
372 lines (347 loc) • 9.62 kB
text/typescript
import file_system = require('../core/file_system');
import {default as Stats, FileType} from '../core/node_fs_stats';
import {FileFlag, ActionType} from '../core/file_flag';
import {BaseFile, File} from '../core/file';
import {uint8Array2Buffer, buffer2Uint8array} from '../core/util';
import {ApiError, ErrorCode, ErrorStrings} from '../core/api_error';
import {Stats as EmscriptenStats, EmscriptenFSNode} from '../generic/emscripten_fs';
interface EmscriptenError {
node: EmscriptenFSNode;
errno: number;
}
function convertError(e: EmscriptenError, path: string = ''): ApiError {
const errno = e.errno;
let parent = e.node;
let paths = [];
while (parent) {
paths.unshift(parent.name);
if (parent === parent.parent) {
break;
}
parent = parent.parent;
}
return new ApiError(errno, ErrorStrings[errno], paths.length > 0 ? '/' + paths.join('/') : path);
}
export class EmscriptenFile extends BaseFile implements File {
constructor(
private _fs: EmscriptenFileSystem,
private _FS: any,
private _path: string,
private _flag: FileFlag,
private _stream: any) {
super();
}
public getPos(): number {
return undefined;
}
public close(cb: (err?: ApiError) => void): void {
let err: ApiError = null;
try {
this.closeSync();
} catch (e) {
err = e;
} finally {
cb(err);
}
}
public closeSync(): void {
try {
this._FS.close(this._stream);
} catch (e) {
throw convertError(e, this._path);
}
}
public stat(cb: (err: ApiError, stats?: Stats) => any): void {
try {
cb(null, this.statSync());
} catch (e) {
cb(e);
}
}
public statSync(): Stats {
try {
return this._fs.statSync(this._path, false);
} catch (e) {
throw convertError(e, this._path);
}
}
public truncate(len: number, cb: (err?: ApiError) => void): void {
let err: ApiError = null;
try {
this.truncateSync(len);
} catch (e) {
err = e;
} finally {
cb(err);
}
}
public truncateSync(len: number): void {
try {
this._FS.ftruncate(this._stream.fd, len);
} catch (e) {
throw convertError(e, this._path);
}
}
public write(buffer: NodeBuffer, offset: number, length: number, position: number, cb: (err: ApiError, written?: number, buffer?: NodeBuffer) => any): void {
try {
cb(null, this.writeSync(buffer, offset, length, position), buffer);
} catch (e) {
cb(e);
}
}
public writeSync(buffer: NodeBuffer, offset: number, length: number, position: number): number {
try {
const u8 = buffer2Uint8array(buffer);
// Emscripten is particular about what position is set to.
if (position === null) {
position = undefined;
}
return this._FS.write(this._stream, u8, offset, length, position);
} catch (e) {
throw convertError(e, this._path);
}
}
public read(buffer: NodeBuffer, offset: number, length: number, position: number, cb: (err: ApiError, bytesRead?: number, buffer?: NodeBuffer) => void): void {
try {
cb(null, this.readSync(buffer, offset, length, position), buffer);
} catch (e) {
cb(e);
}
}
public readSync(buffer: NodeBuffer, offset: number, length: number, position: number): number {
try {
const u8 = buffer2Uint8array(buffer);
// Emscripten is particular about what position is set to.
if (position === null) {
position = undefined;
}
return this._FS.read(this._stream, u8, offset, length, position);
} catch (e) {
throw convertError(e, this._path);
}
}
public sync(cb: (e?: ApiError) => void): void {
// NOP.
cb();
}
public syncSync(): void {
// NOP.
}
public chown(uid: number, gid: number, cb: (e?: ApiError) => void): void {
let err: ApiError = null;
try {
this.chownSync(uid, gid);
} catch (e) {
err = e;
} finally {
cb(err);
}
}
public chownSync(uid: number, gid: number): void {
try {
this._FS.fchown(this._stream.fd, uid, gid);
} catch (e) {
throw convertError(e, this._path);
}
}
public chmod(mode: number, cb: (e?: ApiError) => void): void {
let err: ApiError = null;
try {
this.chmodSync(mode);
} catch (e) {
err = e;
} finally {
cb(err);
}
}
public chmodSync(mode: number): void {
try {
this._FS.fchmod(this._stream.fd, mode);
} catch (e) {
throw convertError(e, this._path);
}
}
public utimes(atime: Date, mtime: Date, cb: (e?: ApiError) => void): void {
let err: ApiError = null;
try {
this.utimesSync(atime, mtime);
} catch (e) {
err = e;
} finally {
cb(err);
}
}
public utimesSync(atime: Date, mtime: Date): void {
this._fs.utimesSync(this._path, atime, mtime);
}
}
/**
* A simple in-memory file system backed by an InMemoryStore.
*/
export default class EmscriptenFileSystem extends file_system.SynchronousFileSystem {
private _FS: any;
constructor(_FS: any) {
super();
this._FS = _FS;
}
public static isAvailable(): boolean { return true; }
public getName(): string { return this._FS.DB_NAME(); }
public isReadOnly(): boolean { return false; }
public supportsLinks(): boolean { return true; }
public supportsProps(): boolean { return true; }
public supportsSynch(): boolean { return true; }
public renameSync(oldPath: string, newPath: string): void {
try {
this._FS.rename(oldPath, newPath);
} catch (e) {
if (e.errno === ErrorCode.ENOENT) {
throw convertError(e, this.existsSync(oldPath) ? newPath : oldPath);
} else {
throw convertError(e);
}
}
}
public statSync(p: string, isLstat: boolean): Stats {
try {
const stats = isLstat ? this._FS.lstat(p) : this._FS.stat(p);
const item_type = this.modeToFileType(stats.mode);
return new Stats(
item_type,
stats.size,
stats.mode,
stats.atime,
stats.mtime,
stats.ctime
);
} catch (e) {
throw convertError(e, p);
}
}
private modeToFileType(mode: number): FileType {
if (this._FS.isDir(mode)) {
return FileType.DIRECTORY;
} else if (this._FS.isFile(mode)) {
return FileType.FILE;
} else if (this._FS.isLink(mode)) {
return FileType.SYMLINK;
}
}
/**
* Moved to separate function to avoid perf hit.
*/
private _tryStats(p: string): Stats {
try {
return this.statSync(p, false);
} catch (e) {
return null;
}
}
public openSync(p: string, flag: FileFlag, mode: number): EmscriptenFile {
try {
const stream = this._FS.open(p, flag.getFlagString(), mode);
if (this._FS.isDir(stream.node.mode)) {
this._FS.close(stream);
throw ApiError.EISDIR(p);
}
return new EmscriptenFile(this, this._FS, p, flag, stream);
} catch (e) {
throw convertError(e, p);
}
}
public unlinkSync(p: string): void {
try {
this._FS.unlink(p);
} catch (e) {
throw convertError(e, p);
}
}
public rmdirSync(p: string): void {
try {
this._FS.rmdir(p);
} catch (e) {
throw convertError(e, p);
}
}
public mkdirSync(p: string, mode: number): void {
try {
this._FS.mkdir(p, mode);
} catch (e) {
throw convertError(e, p);
}
}
public readdirSync(p: string): string[] {
try {
// Emscripten returns items for '.' and '..'. Node does not.
return this._FS.readdir(p).filter((p) => p !== '.' && p !== '..');
} catch (e) {
throw convertError(e, p);
}
}
public truncateSync(p: string, len: number): void {
try {
this._FS.truncate(p, len);
} catch (e) {
throw convertError(e, p);
}
}
public readFileSync(p: string, encoding: string, flag: FileFlag): any {
try {
const data: Uint8Array = this._FS.readFile(p, { flags: flag.getFlagString() })
const buff = uint8Array2Buffer(data);
if (encoding) {
return buff.toString(encoding);
} else {
return buff;
}
} catch (e) {
throw convertError(e, p);
}
}
public writeFileSync(p: string, data: any, encoding: string, flag: FileFlag, mode: number): void {
try {
if (encoding) {
data = new Buffer(data, encoding);
}
const u8 = buffer2Uint8array(data);
this._FS.writeFile(p, u8, { flags: flag.getFlagString(), encoding: 'binary' });
this._FS.chmod(p, mode);
} catch (e) {
throw convertError(e, p);
}
}
public chmodSync(p: string, isLchmod: boolean, mode: number) {
try {
isLchmod ? this._FS.lchmod(p, mode) : this._FS.chmod(p, mode);
} catch (e) {
throw convertError(e, p);
}
}
public chownSync(p: string, isLchown: boolean, uid: number, gid: number): void {
try {
isLchown ? this._FS.lchown(p, uid, gid) : this._FS.chown(p, uid, gid);
} catch (e) {
throw convertError(e, p);
}
}
public symlinkSync(srcpath: string, dstpath: string, type: string): void {
try {
this._FS.symlink(srcpath, dstpath);
} catch (e) {
throw convertError(e);
}
}
public readlinkSync(p: string): string {
try {
return this._FS.readlink(p);
} catch (e) {
throw convertError(e, p);
}
}
public utimesSync(p: string, atime: Date, mtime: Date): void {
try {
this._FS.utime(p, atime.getTime(), mtime.getTime());
} catch (e) {
throw convertError(e, p);
}
}
}