-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlocal-fs.coffee
388 lines (342 loc) · 10.8 KB
/
local-fs.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
((root, factory) ->
if typeof define == 'function' and define.amd
# AMD. Register as an anonymous module.
define [], factory
else if typeof module == 'object' and module.exports
# Node. Does not work with strict CommonJS, but
# only CommonJS-like environments that support module.exports,
# like Node.
module.exports = factory()
else
# Browser globals (root is window)
root.returnExports = factory()
return
) this, ->
FILE = 0x8000
DIRECTORY = 0x4000
# Same as stats
class LFSNode
constructor: (@size, @mode, @atime, @mtime, @ctime) ->
# Returns whether something changed
update: ({size, mode, atime, mtime, ctime}) ->
hasChanged = false
hasChanged or= @size isnt size
@size = size
hasChanged or= @mode isnt mode
@mode = mode
hasChanged or= +@atime isnt +atime
@atime = atime
hasChanged or= +@mtime isnt +mtime
@mtime = mtime
hasChanged or= +@ctime isnt +ctime
@ctime = ctime
return hasChanged
isFile: ->
return @mode & 0xF000 == FILE
isDirectory: ->
return @mode & 0xF000 == DIRECTORY
class FileIndex
constructor: ->
# paths to DirectoryNodes
@_index = {}
# /**
# * Adds the given absolute path to the index if it is not already in the index.
# * Creates any needed parent directories.
# * @param [String] path The path to add to the index.
# * @param [BrowserFS.FileInode | BrowserFS.DirInode] inode The inode for the
# * path to add.
# * @return [Boolean] 'True' if it was added or already exists, 'false' if there
# * was an issue adding it (e.g. item in path is a file, item exists but is
# * different).
# * @todo If adding fails and implicitly creates directories, we do not clean up
# * the new empty directories.
# */
# addPath: (path, inode) ->
# if (inode == null) {
# throw new Error('Inode must be specified');
# }
# if (path[0] !== '/') {
# throw new Error('Path must be absolute, got: ' + path);
# }
# // Check if it already exists.
# if (this._index.hasOwnProperty(path)) {
# return this._index[path] === inode;
# }
# var splitPath = this._split_path(path);
# var dirpath = splitPath[0];
# var itemname = splitPath[1];
# // Try to add to its parent directory first.
# var parent = this._index[dirpath];
# if (parent === undefined && path !== '/') {
# // Create parent.
# parent = new DirInode();
# if (!this.addPath(dirpath, parent)) {
# return false;
# }
# }
# // Add myself to my parent.
# if (path !== '/') {
# if (!parent.addItem(itemname, inode)) {
# return false;
# }
# }
# // If I'm a directory, add myself to the index.
# if (!inode.isFile()) {
# this._index[path] = <DirInode> inode;
# }
# return true;
# }
# /**
# * Removes the given path. Can be a file or a directory.
# * @return [BrowserFS.FileInode | BrowserFS.DirInode | null] The removed item,
# * or null if it did not exist.
# */
# public removePath(path: string): Inode {
# var splitPath = this._split_path(path);
# var dirpath = splitPath[0];
# var itemname = splitPath[1];
# // Try to remove it from its parent directory first.
# var parent = this._index[dirpath];
# if (parent === undefined) {
# return null;
# }
# // Remove myself from my parent.
# var inode = parent.remItem(itemname);
# if (inode === null) {
# return null;
# }
# // If I'm a directory, remove myself from the index, and remove my children.
# if (!inode.isFile()) {
# var dirInode = <DirInode> inode;
# var children = dirInode.getListing();
# for (var i = 0; i < children.length; i++) {
# this.removePath(path + '/' + children[i]);
# }
# // Remove the directory from the index, unless it's the root.
# if (path !== '/') {
# delete this._index[path];
# }
# }
# return inode;
# }
# /**
# * Retrieves the directory listing of the given path.
# * @return [String[]] An array of files in the given path, or 'null' if it does
# * not exist.
# */
# public ls(path: string): string[] {
# var item = this._index[path];
# if (item === undefined) {
# return null;
# }
# return item.getListing();
# }
# /**
# * Returns the inode of the given item.
# * @param [String] path
# * @return [BrowserFS.FileInode | BrowserFS.DirInode | null] Returns null if
# * the item does not exist.
# */
# public getInode(path: string): Inode {
# var splitPath = this._split_path(path);
# var dirpath = splitPath[0];
# var itemname = splitPath[1];
# // Retrieve from its parent directory.
# var parent = this._index[dirpath];
# if (parent === undefined) {
# return null;
# }
# // Root case
# if (dirpath === path) {
# return parent;
# }
# return parent.getItem(itemname);
# }
# /**
# * Static method for constructing indices from a JSON listing.
# * @param [Object] listing Directory listing generated by tools/XHRIndexer.coffee
# * @return [BrowserFS.FileIndex] A new FileIndex object.
# */
# public static from_listing(listing): FileIndex {
# var idx = new FileIndex();
# // Add a root DirNode.
# var rootInode = new DirInode();
# idx._index['/'] = rootInode;
# var queue = [['', listing, rootInode]];
# while (queue.length > 0) {
# var inode;
# var next = queue.pop();
# var pwd = next[0];
# var tree = next[1];
# var parent = next[2];
# for (var node in tree) {
# var children = tree[node];
# var name = "" + pwd + "/" + node;
# if (children != null) {
# idx._index[name] = inode = new DirInode();
# queue.push([name, children, inode]);
# } else {
# // This inode doesn't have correct size information, noted with -1.
# inode = new FileInode<node_fs_stats.Stats>(new Stats(node_fs_stats.FileType.FILE, -1, 0x16D));
# }
# if (parent != null) {
# parent._ls[node] = inode;
# }
# }
# }
# return idx;
# }
# }
/**
* Generic interface for file/directory inodes.
* Note that Stats objects are what we use for file inodes.
*/
export interface Inode {
// Is this an inode for a file?
isFile(): boolean;
// Is this an inode for a directory?
isDir(): boolean;
}
/**
* Inode for a file. Stores an arbitrary (filesystem-specific) data payload.
*/
export class FileInode<T> implements Inode {
constructor(private data: T) { }
public isFile(): boolean { return true; }
public isDir(): boolean { return false; }
public getData(): T { return this.data; }
public setData(data: T): void { this.data = data; }
}
/**
* Inode for a directory. Currently only contains the directory listing.
*/
export class DirInode implements Inode {
private _ls: {[path: string]: Inode} = {};
/**
* Constructs an inode for a directory.
*/
constructor() {}
public isFile(): boolean {
return false;
}
public isDir(): boolean {
return true;
}
/**
* Return a Stats object for this inode.
* @todo Should probably remove this at some point. This isn't the
* responsibility of the FileIndex.
* @return [BrowserFS.node.fs.Stats]
*/
public getStats(): node_fs_stats.Stats {
return new Stats(node_fs_stats.FileType.DIRECTORY, 4096, 0x16D);
}
/**
* Returns the directory listing for this directory. Paths in the directory are
* relative to the directory's path.
* @return [String[]] The directory listing for this directory.
*/
public getListing(): string[] {
return Object.keys(this._ls);
}
/**
* Returns the inode for the indicated item, or null if it does not exist.
* @param [String] p Name of item in this directory.
* @return [BrowserFS.FileInode | BrowserFS.DirInode | null]
*/
public getItem(p: string): Inode {
var _ref;
return (_ref = this._ls[p]) != null ? _ref : null;
}
/**
* Add the given item to the directory listing. Note that the given inode is
* not copied, and will be mutated by the DirInode if it is a DirInode.
* @param [String] p Item name to add to the directory listing.
* @param [BrowserFS.FileInode | BrowserFS.DirInode] inode The inode for the
* item to add to the directory inode.
* @return [Boolean] True if it was added, false if it already existed.
*/
public addItem(p: string, inode: Inode): boolean {
if (p in this._ls) {
return false;
}
this._ls[p] = inode;
return true;
}
/**
* Removes the given item from the directory listing.
* @param [String] p Name of item to remove from the directory listing.
* @return [BrowserFS.FileInode | BrowserFS.DirInode | null] Returns the item
* removed, or null if the item did not exist.
*/
public remItem(p: string): Inode {
var item = this._ls[p];
if (item === undefined) {
return null;
}
delete this._ls[p];
return item;
}
}
var fs = {}
fs.renameSync = (oldPath, newPath) ->
nullCheck oldPath
nullCheck newPath
data = read oldPath
remove oldPath
write newPath, data
fs.statSync = (path) ->
nullCheck path
read path
fs.realpathSync = (path, cache) ->
nullCheck path
# TODO:
fs.rmdirSync = (path) ->
nullCheck path
remove path
fs.mkdirSync = (path, mode) ->
nullCheck path
# ignore mode
write path, dir()
fs.readdirSync = (path) ->
nullCheck path
# if not stats.isDirectory()
# throw new Error "ENOTDIR: not a directory, scandir #{path}"
children path
fs.utimesSync = (path, atime, mtime) ->
nullCheck path
data = read path
data.atime = atime
data.mtime = mtime
write path, data
# TODO: return? format?
fs.readFileSync = (filename, options) ->
nullCheck filename
# options are ignored and default to 'utf8'
data = read filename
data.content
fs.writeFileSync = (filename, data, options) ->
nullCheck filename
# data is string
# options are ignored and encoding defaults to 'utf8'
fileData = file()
fileData.content = data
write filename, fileData
fs.appendFileSync = (filename, data, options) ->
nullCheck filename
# data is string
# options are ignored and encoding defaults to 'utf8'
fs.writeFileSync (fs.readFileSync filename) + data
# TODO: maybe add watch??
#fs.watchFile(filename[, options], listener)
# Provide async versions
for name, fun of fs
fs[name[...-4]] = (args..., callback) ->
try
result = fun args...
catch e
error = e
callback error, result
# special
fs.existsSync(path)
{FileSystem}