Initial commit

- Partial re-write of some of the core filer implementation.
- Makes use of ES6/ES7.
- Core support for VFS and block storage.
This commit is contained in:
Alan K 2018-07-05 13:29:05 -04:00
commit 47676be805
38 changed files with 4210 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
bower_components
.env
*~
dist/filer-issue225.js

1554
dist/filer.js vendored Normal file

File diff suppressed because one or more lines are too long

459
package-lock.json generated Normal file
View File

@ -0,0 +1,459 @@
{
"name": "filer",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0-beta.49",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.49.tgz",
"integrity": "sha1-vs2AVIJzREDJ0TfkbXc0DmTX9Rs=",
"dev": true,
"requires": {
"@babel/highlight": "7.0.0-beta.49"
}
},
"@babel/highlight": {
"version": "7.0.0-beta.49",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.49.tgz",
"integrity": "sha1-lr3GtD4TSCASumaRsQGEktOWIsw=",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^3.0.0"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"@types/node": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.0.tgz",
"integrity": "sha512-hWzNviaVFIr1TqcRA8ou49JaSHp+Rfabmnqg2kNvusKqLhPU0rIsGPUj5WJJ7ld4Bb7qdgLmIhLfCD1qS08IVA==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base-x": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.1.0.tgz",
"integrity": "sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w="
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
},
"buffer": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz",
"integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"buffer-from": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
"integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ=="
},
"builtin-modules": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz",
"integrity": "sha512-3U5kUA5VPsRUA3nofm/BXX7GVHKfxz0hOBAPxXrIvHzlDRkQVqEn6yi8QJegxl4LzOHLdvb7XF5dVawa/VVYBg==",
"dev": true
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"dev": true,
"requires": {
"color-name": "^1.1.1"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"he": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
},
"ieee754": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
"integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
"dev": true
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
}
},
"mocha": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
"integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
"requires": {
"browser-stdout": "1.3.1",
"commander": "2.15.1",
"debug": "3.1.0",
"diff": "3.5.0",
"escape-string-regexp": "1.0.5",
"glob": "7.1.2",
"growl": "1.10.5",
"he": "1.1.1",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"supports-color": "5.4.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
"dev": true
},
"querystringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw=="
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
"dev": true,
"requires": {
"path-parse": "^1.0.5"
}
},
"rollup": {
"version": "0.59.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-0.59.4.tgz",
"integrity": "sha512-ISiMqq/aJa+57QxX2MRcvLESHdJ7wSavmr6U1euMr+6UgFe6KM+3QANrYy8LQofwhTC1I7BcAdlLnDiaODs1BA==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"@types/node": "*"
}
},
"rollup-plugin-node-resolve": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz",
"integrity": "sha512-9zHGr3oUJq6G+X0oRMYlzid9fXicBdiydhwGChdyeNRGPcN/majtegApRKHLR5drboUvEWU+QeUmGTyEZQs3WA==",
"dev": true,
"requires": {
"builtin-modules": "^2.0.0",
"is-module": "^1.0.0",
"resolve": "^1.1.6"
}
},
"rollup-plugin-uglify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-4.0.0.tgz",
"integrity": "sha512-f6W31EQLzxSEYfN3x6/lyljHqXSoCjXKcTsnwz3evQvHgU1+qTzU2SE0SIG7tbAvaCewp2UaZ5x3k6nYsxOP9A==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0-beta.47",
"uglify-js": "^3.3.25"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
},
"should": {
"version": "13.2.1",
"resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz",
"integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==",
"requires": {
"should-equal": "^2.0.0",
"should-format": "^3.0.3",
"should-type": "^1.4.0",
"should-type-adaptors": "^1.0.1",
"should-util": "^1.0.0"
}
},
"should-equal": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
"integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
"requires": {
"should-type": "^1.4.0"
}
},
"should-format": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
"integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
"requires": {
"should-type": "^1.3.0",
"should-type-adaptors": "^1.0.1"
}
},
"should-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
"integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM="
},
"should-type-adaptors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
"integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
"requires": {
"should-type": "^1.3.0",
"should-util": "^1.0.0"
}
},
"should-util": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
"integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-support": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz",
"integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "^3.0.0"
}
},
"uglify-js": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.0.tgz",
"integrity": "sha512-Jcf5naPkX3rVPSQpRn9Vm6Rr572I1gTtR9LnqKgXjmOgfYQ/QS0V2WRStFR53Bdj520M66aCZqt9uzYXgtGrJQ==",
"dev": true,
"requires": {
"commander": "~2.15.0",
"source-map": "~0.6.1"
}
},
"url-parse": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.1.tgz",
"integrity": "sha512-x95Td74QcvICAA0+qERaVkRpTGKyBHHYdwL2LXZm5t/gBtCB9KQSO/0zQgSTYEV1p0WcvSg79TLNPSvd5IDJMQ==",
"requires": {
"querystringify": "^2.0.0",
"requires-port": "^1.0.0"
}
},
"uuid-base62": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/uuid-base62/-/uuid-base62-0.1.0.tgz",
"integrity": "sha1-oqhTuYvguq7k917kG8PY5aFcD34=",
"requires": {
"base-x": "^1.0.0",
"node-uuid": "^1.4.3"
}
},
"uuid-parse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.0.0.tgz",
"integrity": "sha1-9GV3F2JLDkuIrzb5jYlYmlu+5Wk="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "filer",
"description": "Node-like file system for browsers",
"keywords": [
"fs",
"node",
"file",
"system",
"browser",
"indexeddb",
"idb",
"websql"
],
"version": "1.0.0",
"author": "Alan K <ack@modeswitch.org> (http://blog.modeswitch.org)",
"homepage": "http://filerjs.github.io/filer",
"bugs": "https://github.com/filerjs/filer/issues",
"license": "BSD",
"scripts": {
"build": "rollup -c --environment=development",
"test": "node node_modules/mocha/bin/mocha test/"
},
"repository": {
"type": "git",
"url": "https://github.com/filerjs/filer.git"
},
"dependencies": {
"base64-arraybuffer": "^0.1.2",
"buffer": "^5.1.0",
"debug": "^3.1.0",
"minimatch": "^3.0.4",
"mocha": "^5.2.0",
"should": "^13.2.1",
"source-map-support": "^0.5.6",
"url-parse": "^1.4.1",
"uuid-base62": "^0.1.0",
"uuid-parse": "^1.0.0"
},
"devDependencies": {
"rollup": "^0.59.4",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-uglify": "^4.0.0",
"semver": "^5.5.0"
},
"main": "dist/filer.js",
"module": "src/index.js"
}

13
rollup.config.js Normal file
View File

@ -0,0 +1,13 @@
// import pkg from "./package.json";
export default [
{
input: "src/index.js",
output: {
name: "Filer",
file: "dist/filer.js",
format: "umd",
sourcemap: "inline",
},
},
];

76
src/common/buffer.js Normal file
View File

@ -0,0 +1,76 @@
const INSPECT_MAX_BYTES = 50;
const K_MAX_LENGTH = 0x7fffffff;
class Buffer extends Uint8Array
{
constructor(arg, encodingOrOffset, length)
{
if (typeof arg === "number") {
if (typeof encodingOrOffset === "string") {
throw new TypeError(`The "string" argument must be of type string. Received type number`);
}
return allocUnsafe(arg);
}
return from(arg, encodingOrOffset, length);
}
static get INSPECT_MAX_BYTES() { return 50 }
static get K_MAX_LENGTH() { return 0x7fffffff }
static isSupported()
{
try {
var arr = new Uint8Array(1)
arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}
return arr.foo() === 42
} catch (e) {
return false
}
}
static from(value, encodingOrOffset, length)
{
if (typeof value === 'string') {
return fromString(value, encodingOrOffset)
}
if (ArrayBuffer.isView(value)) {
return fromArrayLike(value)
}
if (value == null) {
throw TypeError(`The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ${typeof value}`);
}
if (isInstance(value, ArrayBuffer) ||
(value && isInstance(value.buffer, ArrayBuffer))) {
return fromArrayBuffer(value, encodingOrOffset, length)
}
if (typeof value === 'number') {
throw new TypeError(
'The "value" argument must not be of type number. Received type number'
)
}
var valueOf = value.valueOf && value.valueOf();
if(valueOf != null && valueOf !== value) {
return Buffer.from(valueOf, encodingOrOffset, length);
}
var b = fromObject(value);
if(b) {
return b;
}
if(typeof Symbol !== "undefined" && Symbol.toPrimitive != null &&
typeof value[Symbol.toPrimitive] === "function") {
return Buffer.from(value[Symbol.toPrimitive]("string"), encodingOrOffset, length);
}
throw new TypeError(`The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ${typeof value}`);
}
}
export default Buffer;

28
src/common/constants.js Normal file
View File

@ -0,0 +1,28 @@
import Buffer from "./buffer";
export const SUPER_NODE_ID = "0000000000000000000000";
export const MODE_FILE = "FILE";
export const MODE_DIRECTORY = "DIRECTORY";
export const MODE_SYMBOLIC_LINK = "MODE_SYMBOLIC_LINK";
export const MODE_META = "META";
export const MODE_SOCKET = "SOCKET";
export const MODE_FIFO = "FIFO";
export const MODE_CHARACTER_DEVICE = "CHARACTER_DEVICE";
export const MODE_BLOCK_DEVICE = "BLOCK_DEVICE";
export const ROOT_DIRECTORY_NAME = "/"; // basename(normalize(path))
export const STDIN = 0;
export const STDOUT = 1;
export const STDERR = 2;
export const FIRST_DESCRIPTOR = 3;
export const N_VFS_DESCRIPTORS = 1024;
export const DATA_BLOCK_SEPARATOR = "#";
export const MNT_READ_ONLY = "READ_ONLY";
export const SYMLOOP_MAX = 10;

26
src/common/crypto.js Normal file
View File

@ -0,0 +1,26 @@
import Platform from "./platform";
import E from "./errors";
let Crypto;
if(Platform.supportsWebCrypto()) {
Crypto = class Crypto
{
static randomBytes(arrayBuffer)
{
return window.crypto.getRandomValues(arrayBuffer);
}
}
} else if(Platform.supportsNodeCrypto()) {
let nodeCrypto = require("crypto");
Crypto = class Crypto
{
static randomBytes(arrayBuffer)
{
return nodeCrypto.randomFillSync(arrayBuffer);
}
}
} else {
throw new E.ENOTSUPPORTED("crypto support is not available on this platform");
}
export default Crypto;

68
src/common/errors.js Normal file
View File

@ -0,0 +1,68 @@
class FilerError extends Error
{
constructor(message, path = null)
{
super(message);
this.path = path;
}
}
const errors = {};
const errorDefinitions =
[
{ errno: -1, name: "UNKNOWN", text: "unknown error" },
{ errno: 0, name: "OK", text: "success" },
{ errno: 1, name: "EOF", text: "end of file" },
{ errno: 9, name: "EBADF", text: "bad file descriptor" },
{ errno: 10, name: "EBUSY", text: "resource busy or locked" },
{ errno: 18, name: "EINVAL", text: "invalid argument" },
{ errno: 27, name: "ENOTDIR", text: "not a directory" },
{ errno: 28, name: "EISDIR", text: "illegal operation on directory" },
{ errno: 34, name: "ENOENT", text: "no such file or directory" },
{ errno: 47, name: "EEXIST", text: "file already exists" },
{ errno: 50, name: "EPERM", text: "operation not permitted" },
{ errno: 51, name: "ELOOP", text: "too many symbolic links encountered" },
{ errno: 53, name: "ENOTEMPTY", text: "directory not empty" },
{ errno: 55, name: "EIO", text: "i/o error" },
{ errno: 56, name: "EROFS", text: "read-only file system" },
{ errno: 57, name: "ENODEV", text: "no such device" },
{ errno: 58, name: "ECANCELED", text: "operation canceled" },
{ errno: 1000, name: "ENOTSUPPORTED", text: "platform is not supported" },
]
for (let error of errorDefinitions) {
errors[error.errno] = errors[error.name] = class extends FilerError {
constructor(message, path)
{
super(message || error.text, path);
}
get name() { return error.name }
get code() { return error.name }
get errno() { return error.errno }
get message() { return this.message }
get stack() { return (new Error(this.message)).stack }
get toString() {
pathInfo = this.path ? (', \'' + this.path + '\'') : '';
return `${this.name}: ${this.message}${pathInfo}`;
}
}
}
export default errors;

0
src/common/index.js Normal file
View File

204
src/common/path.js Normal file
View File

@ -0,0 +1,204 @@
import E from "./errors";
function normalizeArray(parts, allowAboveRoot) {
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = parts.length - 1; i >= 0; i--) {
var last = parts[i];
if (last === '.') {
parts.splice(i, 1);
} else if (last === '..') {
parts.splice(i, 1);
up++;
} else if (up) {
parts.splice(i, 1);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (allowAboveRoot) {
for (; up--; up) {
parts.unshift('..');
}
}
return parts;
}
// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
var splitPathRe =
/^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
var splitPath = function(filename) {
var result = splitPathRe.exec(filename);
return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
};
// path.resolve([from ...], to)
export function resolve() {
var resolvedPath = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
// XXXfiler: we don't have process.cwd() so we use '/' as a fallback
var path = (i >= 0) ? arguments[i] : '/';
// Skip empty and invalid entries
if (typeof path !== 'string' || !path) {
continue;
}
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path.charAt(0) === '/';
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
// Normalize the path
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
return !!p;
}), !resolvedAbsolute).join('/');
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
}
// path.normalize(path)
export function normalize(path) {
var isAbsolute = path.charAt(0) === '/',
trailingSlash = path.substr(-1) === '/';
// Normalize the path
path = normalizeArray(path.split('/').filter(function(p) {
return !!p;
}), !isAbsolute).join('/');
if (!path && !isAbsolute) {
path = '.';
}
/*
if (path && trailingSlash) {
path += '/';
}
*/
return (isAbsolute ? '/' : '') + path;
}
export function join() {
var paths = Array.prototype.slice.call(arguments, 0);
return normalize(paths.filter(function(p, index) {
return p && typeof p === 'string';
}).join('/'));
}
// path.relative(from, to)
export function relative(from, to) {
from = resolve(from).substr(1);
to = resolve(to).substr(1);
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end - start + 1);
}
var fromParts = trim(from.split('/'));
var toParts = trim(to.split('/'));
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var outputParts = [];
for (var i = samePartsLength; i < fromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('/');
}
export function dirname(path) {
var result = splitPath(path),
root = result[0],
dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
}
export function basename(path, ext) {
var f = splitPath(path)[2];
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
// XXXfiler: node.js just does `return f`
return f === "" ? "/" : f;
}
export function extname(path) {
return splitPath(path)[3];
}
export function isAbsolute(path) {
if(path.charAt(0) === '/') {
return true;
}
return false;
}
export function isNull(path) {
if (('' + path).indexOf('\u0000') !== -1) {
return true;
}
return false;
}
// Make sure we don't double-add a trailing slash (e.g., '/' -> '//')
export function addTrailing(path) {
return path.replace(/\/*$/, '/');
}
// Deal with multiple slashes at the end, one, or none
// and make sure we don't return the empty string.
export function removeTrailing(path) {
path = path.replace(/\/*$/, '');
return path === '' ? '/' : path;
}
export function check(path) {
if(!path) {
throw new E.EINVAL('path must be a string', path);
} else if(isNull(path)) {
throw new E.EINVAL('path must be a string without null bytes', path);
} else if(!isAbsolute(path)) {
throw new E.EINVAL('path must be absolute', path);
}
}

24
src/common/platform.js Normal file
View File

@ -0,0 +1,24 @@
class Platform
{
static supportsWebCrypto()
{
return ("undefined" !== typeof window &&
"undefined" !== typeof window.crypto &&
"function" === typeof window.crypto.getRandomValues);
}
static supportsNodeCrypto()
{
if("undefined" !== typeof process) {
try {
require.resolve("crypto");
return true;
} catch(e) {
}
}
return false;
}
}
export default Platform;

117
src/common/url.js Normal file
View File

@ -0,0 +1,117 @@
const __ = new WeakMap();
const URL_REGEX = /^((\w+)\+(\w+):)?(\/\/((\w+)?(:(\w+))?@)?([^\/\?:]+)(:(\d+))?)?(\/?([^\/\?#][^\?#]*)?)?(\?([^#]+))?(#(\w*))?/i;
class URL
{
constructor(urlString)
{
__.set(this, {
});
const self = __.get(this);
let match = urlString.match(URL_REGEX);
self.originalURL = match[0];
if(match[2]) {
self.protocol = match[2];
}
if(match[3]) {
self.subprotocol = match[3];
}
if(match[6]) {
self.username = match[6];
}
if(match[8]) {
self.password = match[8];
}
if(match[9]) {
self.host = match[9];
} else {
self.host = "";
}
if(match[11]) {
self.port = match[11];
}
if(match[12]) {
self.path = match[12];
} else {
self.path = "";
}
if(match[15]) {
let queryList = match[15].split("&");
let query = {};
for(let item of queryList) {
let [key, value] = item.split("=");
if(!(query.hasOwnProperty(key))) {
query[key] = [];
}
if(value) {
query[key].push(value);
}
}
self.query = query;
} else {
self.query = {};
}
if(match[17]) {
self.fragment = match[17];
} else {
self.fragment = "";
}
}
get protocol() { return __.get(this).protocol }
set protocol(value) { return __.get(this).protocol = value }
get subprotocol() { return __.get(this).subprotocol }
set subprotocol(value) { return __.get(this).subprotocol = value }
get username() { return __.get(this).username }
set username(value) { return __.get(this).username = value }
get password() { return __.get(this).password }
set password(value) { return __.get(this).password = value }
get host() { return __.get(this).host }
set host(value) { return __.get(this).host = value }
get port() { return __.get(this).port }
set port(value) { return __.get(this).port = value }
get path() { return __.get(this).path }
set path(value) { return __.get(this).path = value }
get query() { return __.get(this).query }
set query(value) { return __.get(this).query = value }
get fragment() { return __.get(this).fragment }
set fragment(value) { return __.get(this).fragment = value }
toJSON()
{
return {
protocol: this.protocol,
subprotocol: this.subprotocol,
username: this.username,
password: this.password,
host: this.host,
port: this.port,
path: this.path,
query: this.query,
fragment: this.fragment,
};
}
}
export default URL;

63
src/common/uuid.js Normal file
View File

@ -0,0 +1,63 @@
import Crypto from "./crypto";
const UUID_SHORT_REGEX = /^[0-9a-zA-Z]{22}$/;
const BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split('');
const BASE_MAP = {};
for (var z = 0; z < BASE.length; z += 1) {
var x = BASE[z];
if (BASE_MAP[x] !== undefined) throw new TypeError(`${x} is ambiguous`)
BASE_MAP[x] = z;
}
function encode(source) {
if (source.length === 0) return ''
var digits = [0]
for (var i = 0; i < source.length; ++i) {
for (var j = 0, carry = source[i]; j < digits.length; ++j) {
carry += digits[j] << 8
digits[j] = carry % BASE.length
carry = (carry / BASE.length) | 0
}
while (carry > 0) {
digits.push(carry % BASE.length)
carry = (carry / BASE.length) | 0
}
}
var string = "";
for (var k = 0; source[k] === 0 && k < source.length - 1; ++k)
string += BASE[0];
for (var q = digits.length - 1; q >= 0; --q)
string += BASE[digits[q]];
return string
}
class UUID {
static v4()
{
let buffer = new Uint8Array(16);
Crypto.randomBytes(buffer);
buffer[6] &= 0b00001111;
buffer[6] |= 0b01000000;
buffer[8] &= 0b00111111;
buffer[8] |= 0b10000000;
return encode(buffer);
}
static short()
{
return this.v4();
}
}
export default UUID;

29
src/fs/file.js Normal file
View File

@ -0,0 +1,29 @@
const { EBADF } = require("../common/errors");
const __ = new WeakMap();
/* An open file. */
class File
{
constructor(path, id, flags, position)
{
__.set(this, {
path: path,
id: id,
flags: flags,
position: position,
});
}
read()
{
}
write()
{
}
}
export default File;

40
src/fs/fs.js Normal file
View File

@ -0,0 +1,40 @@
import UUID from "../common/uuid";
const __ = new WeakMap();
class FS
{
constructor(superNode, options)
{
let { proxy, revoke } = Proxy.revocable(this, {});
__.set(proxy, {
id: UUID.short(), // instance ID
revoke: revoke,
});
return proxy;
}
get id()
{
return __.get(this).id;
}
static async mount(dev, flags=[], options={})
{
}
async umount()
{
__.get(this).revoke();
}
toString()
{
return this.id;
}
}
export default FS;

3
src/fs/index.js Normal file
View File

@ -0,0 +1,3 @@
import FS from "./fs";
export default FS;

230
src/fs/node.js Normal file
View File

@ -0,0 +1,230 @@
import UUID from "../common/uuid";
import { MODE_FILE, MODE_DIRECTORY, MODE_SYMBOLIC_LINK } from "../common/constants";
import Buffer from "../common/buffer";
import E from "../common/errors";
const __ = new WeakMap();
class NodeData
{
constructor({ mode, size = 0, atime, mtime, ctime, version = UUID.short(), flags, xattrs, nlinks, blksize, nblocks, blkid = UUID.short() })
{
__.set(this, {
mode: mode, // node type (file, directory, etc)
size: size,
atime: atime || Date.now(), // access time (will mirror ctime after creation)
mtime: mtime || Date.now(), // creation/change time
ctime: ctime || Date.now(), // modified time
version: version || UUID.short(),
flags: flags || [],
xattrs: xattrs || {},
nlinks: nlinks || 0,
blksize: blksize || 4096,
nblocks: nblocks || 0,
blkid: blkid,
});
}
get mode() { return __.get(this).mode }
get atime() { return __.get(this).atime }
set atime(value) { return __.get(this).atime = value }
get mtime() { return __.get(this).mtime }
set mtime(value) { return __.get(this).mtime = value }
get ctime() { return __.get(this).ctime }
set ctime(value) { return __.get(this).ctime = value }
get version() { return __.get(this).version }
set version(value) { return __.get(this).version = value }
get flags() { return __.get(this).flags }
get xattrs() { return __.get(this).xattrs }
get nlinks() { return __.get(this).nlinks }
set nlinks(value) { return __.get(this).nlinks = value }
get blksize() { return __.get(this).blksize }
get nblocks() { return __.get(this).nblocks }
set nblocks(value) { return __.get(this).nblocks = value }
get blkid() { return __.get(this).blkid }
set blkid(value) { return __.get(this).blkid = value }
get size() { return __.get(this).size }
set size(value) { return __.get(this).size = value }
toJSON()
{
return {
mode: this.mode,
size: this.size,
atime: this.atime,
mtime: this.mtime,
ctime: this.ctime,
nlinks: this.nlinks,
version: this.version,
blksize: this.blksize,
nblocks: this.nblocks,
blkid: this.blkid,
flags: this.flags,
xattrs: this.xattrs,
};
}
}
class Node
{
constructor({ fs, id = UUID.short(), data } = {})
{
__.set(this, {
fs: fs,
id: id,
data: new NodeData(data),
});
}
get fs() { return __.get(this).fs }
get id() { return __.get(this).id }
get size() { return __.get(this).data.size }
set size(value) { return __.get(this).data.size = value }
get nlinks() { return __.get(this).data.nlinks }
set nlinks(value) { return __.get(this).data.nlinks = value }
get version() { return __.get(this).data.version }
set version(value) { return __.get(this).data.version = value }
get blksize() { return __.get(this).data.blksize }
get nblocks() { return __.get(this).data.nblocks }
set nblocks(value) { return __.get(this).data.nblocks = value }
get atime() { return __.get(this).data.atime }
set atime(value) { return __.get(this).data.atime = value }
get mtime() { return __.get(this).data.mtime }
set mtime(value) { return __.get(this).data.mtime = value }
get ctime() { return __.get(this).data.ctime }
set ctime(value) { return __.get(this).data.ctime = value }
get mode() { return __.get(this).data.mode }
get blkid() { return __.get(this).data.blkid }
set blkid(value) { return __.get(this).data.blkid = value }
get flags() { return __.get(this).data.flags }
get xattrs() { return __.get(this).data.xattrs }
get data() { return __.get(this).data.toJSON() }
isFile()
{
return MODE_FILE == this.mode;
}
isDirectory()
{
return MODE_DIRECTORY == this.mode;
}
isSymbolicLink()
{
return MODE_SYMBOLIC_LINK == this.mode;
}
isSocket()
{
return MODE_SOCKET == this.mode;
}
isFIFO()
{
return MODE_FIFO == this.mode;
}
isCharacterDevice()
{
return MODE_CHARACTER_DEVICE == this.mode;
}
isBlockDevice()
{
return MODE_BLOCK_DEVICE == this.mode;
}
toString()
{
return JSON.stringify(this.toJSON());
}
static hash(fs, id)
{
return `${fs.id}${id}`;
}
hash()
{
return Node.hash(this.fs, this.id);
}
static async read(fs, id)
{
let data = await fs.readNode(id);
return new Node({ fs: fs, id: id, data: data });
}
async read()
{
let data = await this.fs.readNode(this.id);
__.get(this).data = new NodeData(data);
}
async write()
{
this.version = UUID.short();
return await this.fs.writeNode(this.id, this.data);
}
async readData(block=0)
{
let data = await this.fs.readData(this.blkid, block);
return data;
}
async writeData(block=0, data)
{
this.nblocks = block + 1;
await this.fs.writeData(this.blkid, block, data);
}
async validate()
{
}
toJSON()
{
return {
fs: this.fs.id,
id: this.id,
data: __.get(this).data.toJSON(),
}
}
toString()
{
return JSON.stringify(this.toJSON());
}
}
export default Node;

View File

@ -0,0 +1,7 @@
import RootFS from "./root-fs";
import MemFS from "./mem-fs";
export default {
[RootFS.type]: RootFS,
[MemFS.type]: MemFS,
};

View File

@ -0,0 +1,58 @@
import FS from "../index";
import SuperNode from "../super-node";
import Node from "../node";
import UUID from "../../common/uuid";
import E from "../../common/errors";
import { MODE_FILE, MODE_DIRECTORY, DATA_BLOCK_SEPARATOR } from "../../common/constants";
import Buffer from "../../common/buffer";
const __ = new WeakMap();
class IDBFS extends FS
{
constructor(superNode, options={})
{
}
static get type()
{
return "idbfs";
}
static async mount(dev=UUID.short(), flags=[], options={})
{
}
async umount()
{
super.umount();
}
async readNode(id)
{
}
async writeNode(id, node)
{
}
async readData(id, block=0, mode=MODE_FILE)
{
}
async writeData(id, block, data)
{
}
async fsync()
{
}
async validate(id)
{
}
}
export default IDBFS;

View File

@ -0,0 +1,92 @@
import FS from "../index";
import SuperNode from "../super-node";
import Node from "../node";
import UUID from "../../common/uuid";
import E from "../../common/errors";
import { MODE_FILE, MODE_DIRECTORY, DATA_BLOCK_SEPARATOR } from "../../common/constants";
import Buffer from "../../common/buffer";
const __ = new WeakMap();
class MemFS extends FS
{
constructor(options={})
{
super(options);
let storage = new Map();
let superNode = new SuperNode({ fs: this });
storage.set(superNode.id, superNode);
let rootNode = new Node({ fs: this, data: { mode: MODE_DIRECTORY } });
storage.set(rootNode.id, rootNode);
superNode.rnode = rootNode.id;
__.set(this, {
storage: storage,
});
}
static get type()
{
return "memfs";
}
static async mount(dev=UUID.short(), flags=[], options={})
{
let fs = new MemFS();
return fs;
}
async umount()
{
super.umount();
}
async readNode(id)
{
let node = __.get(this).storage.get(id);
if(!node) {
throw new E.ENOENT();
}
return node;
}
async writeNode(id, node)
{
__.get(this).storage.set(id, node);
}
async readData(id, block=0)
{
let data = __.get(this).storage.get(`${id}${DATA_BLOCK_SEPARATOR}${block}`);
if(!data) {
throw new E.EIO();
}
return data;
}
async writeData(id, block, data)
{
__.get(this).storage.set(`${id}${DATA_BLOCK_SEPARATOR}${block}`, data);
}
async fsync()
{
}
async validate(id)
{
}
}
export default MemFS;

View File

@ -0,0 +1,96 @@
import FS from "../fs";
import SuperNode from "../super-node";
import Node from "../node";
import UUID from "../../common/uuid";
import { MODE_DIRECTORY, MODE_FILE, DATA_BLOCK_SEPARATOR } from "../../common/constants";
import { SUPER_NODE_ID } from "../../common/constants";
import E from "../../common/errors";
const __ = new WeakMap();
/*
RootFS is a read-only file system containing exactly one
directory node. It is created automatically by the VFS
layer. Its only purpose is to allow the VFS to mount another
file system on top of its only node.
*/
class RootFS extends FS
{
constructor(options={})
{
super(options);
let superNode = new SuperNode({ fs: this, data: { dev: UUID.short() } });
let rootNode = new Node({ fs: this, data: { mode: MODE_DIRECTORY, nlinks: 1 } });
superNode.rnode = rootNode.id;
let storage = new Map();
storage.set(superNode.id, superNode.data);
storage.set(rootNode.id, rootNode.data);
__.set(this, {
storage: storage,
});
}
static get type()
{
return "rootfs";
}
static async mount()
{
throw new E.UNKNOWN("mount operation not available for rootfs");
}
async umount()
{
throw new E.UNKNOWN("umount operation not available for rootfs");
}
async readNode(id)
{
let node = __.get(this).storage.get(id);
if(!node) {
throw new E.ENOENT();
}
return node;
}
async writeNode(id, node)
{
throw new E.EROFS();
}
async readData(id, block=0)
{
let data = __.get(this).storage.get(`${id}${DATA_BLOCK_SEPARATOR}${block}`);
if(!data) {
throw new E.EIO();
}
return data;
}
async writeData(id, block, data)
{
throw new E.EROFS();
}
async fsync()
{
}
async validate(id)
{
}
}
export default RootFS;

97
src/fs/stats.js Normal file
View File

@ -0,0 +1,97 @@
const { MODE_FILE, MODE_DIRECTORY, MODE_SYMBOLIC_LINK } = require(`../common/constants`);
const __ = new WeakMap();
class Stats
{
constructor(fileNode, deviceName)
{
__.set(this, {
node: fileNode.id,
dev: deviceName,
size: fileNode.size,
nlinks: fileNode.nlinks,
atime: fileNode.atime,
mtime: fileNode.mtime,
ctime: fileNode.ctime,
type: fileNode.mode,
});
}
get node()
{
return __.get(this).node;
}
get dev()
{
return __.get(this).dev;
}
get size()
{
return __.get(this).size;
}
get nlinks()
{
return __.get(this).nlinks;
}
get atime()
{
return __.get(this).atime;
}
get mtime()
{
return __.get(this).mtime;
}
get ctime()
{
return __.get(this).ctime;
}
get type()
{
return __.get(this).type;
}
get isFile()
{
return MODE_FILE == this.type;
}
get isDirectory()
{
return MODE_DIRECTORY == this.type;
}
get isSymbolicLink()
{
return MODE_SYMBOLIC_LINK == this.type;
}
get isSocket()
{
return MODE_SOCKET == this.type;
}
get isFIFO()
{
return MODE_FIFO == this.type;
}
get isCharacterDevice()
{
return MODE_CHARACTER_DEVICE == this.type;
}
get isBlockDevice()
{
return MODE_BLOCK_DEVICE == this.type;
}
}
export default Stats;

124
src/fs/super-node.js Normal file
View File

@ -0,0 +1,124 @@
import { MODE_META } from "../common/constants";
import { SUPER_NODE_ID } from "../common/constants";
import UUID from "../common/uuid";
const __ = new WeakMap();
class SuperNodeData
{
constructor({ dev, atime = Date.now(), mtime = Date.now(), ctime = Date.now(), rnode, version = UUID.short() } = {})
{
__.set(this, {
dev: dev,
mode: MODE_META,
atime: atime || Date.now(), // access time (will mirror ctime after creation)
mtime: mtime || Date.now(), // creation/change time
ctime: ctime || Date.now(), // modified time
rnode: rnode, // root node
version: version,
});
}
get dev() { return __.get(this).dev }
get mode() { return __.get(this).mode }
get atime() { return __.get(this).atime }
set atime(value) { return __.get(this).atime = value }
get mtime() { return __.get(this).mtime }
set mtime(value) { return __.get(this).mtime = value }
get ctime() { return __.get(this).ctime }
set ctime(value) { return __.get(this).ctime = value }
get version() { return __.get(this).version }
set version(value) { return __.get(this).version = value }
get rnode() { return __.get(this).rnode }
set rnode(value) { return __.get(this).rnode = value }
toJSON()
{
return {
dev: this.dev,
mode: this.mode,
atime: this.atime,
mtime: this.mtime,
ctime: this.ctime,
rnode: this.rnode,
version: this.version,
};
}
}
class SuperNode
{
constructor({ fs, data } = {})
{
__.set(this, {
fs: fs,
id: SUPER_NODE_ID,
data: new SuperNodeData(data),
});
}
get id() { return __.get(this).id }
get fs() { return __.get(this).fs }
get dev() { return __.get(this).data.dev }
get mode() { return __.get(this).data.mode }
get atime() { return __.get(this).data.atime }
set atime(value) { return __.get(this).data.atime = value }
get mtime() { return __.get(this).data.mtime }
set mtime(value) { return __.get(this).data.mtime = value }
get ctime() { return __.get(this).data.ctime }
set ctime(value) { return __.get(this).data.ctime = value }
get rnode() { return __.get(this).data.rnode }
set rnode(value) { return __.get(this).data.rnode = value }
get version() { return __.get(this).data.version }
set version(value) { return __.get(this).data.version = value }
get data() { return __.get(this).data.toJSON() }
static async read(fs)
{
let data = await fs.readNode(SUPER_NODE_ID);
return new SuperNode({ fs: fs, data: data });
}
async read()
{
let data = await this.fs.readNode(this.id);
__.get(this).data = new SuperNodeData(data);
}
async write()
{
this.version = UUID.short();
await fs.writeNode(this.id, this.data);
}
toJSON()
{
return {
id: this.id,
data: __.get(this).data.toJSON(),
}
}
toString()
{
return JSON.stringify(this.toJSON());
}
}
export default SuperNode;

15
src/index.js Normal file
View File

@ -0,0 +1,15 @@
import FS from "./fs/index";
import VFS from "./vfs/index";
import Providers from "./fs/providers/index";
import UUID from "./common/uuid";
import FilerBuffer from "./common/buffer";
import Crypto from "./common/crypto";
import URL from "./common/url";
export default {
FS: FS,
VFS: VFS,
UUID: UUID,
Buffer: FilerBuffer,
Crypto: Crypto,
};

12
src/sh/index.js Normal file
View File

@ -0,0 +1,12 @@
const __ = new WeakMap();
class Shell
{
constructor()
{
__.set(this, {
});
}
}
export default Shell;

View File

@ -0,0 +1,20 @@
import { MODE_FILE } from "../common/constants";
const __ = new WeakMap();
class DirectoryEntry
{
constructor({ id, type=MODE_FILE } = {})
{
__.set(this, {
id: id,
type: type,
});
}
get id() { return __.get(this).id }
get type() { return __.get(this).type }
}
export default DirectoryEntry;

49
src/vfs/fdmap.js Normal file
View File

@ -0,0 +1,49 @@
import { FIRST_DESCRIPTOR, N_VFS_DESCRIPTORS, STDIN, STDOUT, STDERR } from "../common/constants";
const __ = new WeakMap();
class FDMap
{
constructor(size=N_VFS_DESCRIPTORS)
{
const map = new Array(size).fill(0);
map[STDIN] = 1;
map[STDOUT] = 1;
map[STDERR] = 1;
__.set(this, {
map: map,
next: FIRST_DESCRIPTOR,
});
}
claimUnused()
{
const map = __.get(this).map;
let next = __.get(this).next;
for(let i = 0; i < map.length; ++ i)
{
let fd = (next+i) % map.length;
if(0 == map[fd]) {
this.claim(fd);
return fd;
}
}
throw new Error(`unable to allocate file descriptor`);
}
claim(fd)
{
__.get(this).map[fd] = 1;
}
release(fd)
{
__.get(this).map[fd] = 0;
}
}
export default FDMap;

3
src/vfs/index.js Normal file
View File

@ -0,0 +1,3 @@
import VFS from "./vfs";
export default VFS;

65
src/vfs/node-cache.js Normal file
View File

@ -0,0 +1,65 @@
const __ = new WeakMap();
class LRUEntry
{
constructor(key, value)
{
__.set(this, {
key: key,
value: value,
older: null,
newer: null,
});
}
get key()
{
return __.get(this).key;
}
get value()
{
return __.get(this).value;
}
}
class LRUMap
{
constructor(limit)
{
__.set(this, {
size: 0,
limit: limit,
oldest: null,
keyMap: new Map(),
});
}
}
class NodeCache
{
constructor(limit=1024)
{
__.set(this, {
nodes: {},
lru: [],
});
}
insert(node, hash)
{
__.get(this).nodes[hash] = node;
}
remove(hash)
{
delete __.get(this).nodes[hash];
}
find(hash)
{
return __.get(this).nodes[hash] || null;
}
}
export default NodeCache;

View File

@ -0,0 +1,23 @@
const __ = new WeakMap();
class VFSMountTable()
{
constructor()
{
__.set(this, {
mounts: {},
});
}
add(vfsmount)
{
}
remove()
{
}
}
export default VFSMountTable;

386
src/vfs/vfs.js Normal file
View File

@ -0,0 +1,386 @@
import FDMap from "./fdmap";
import VFSMount from "./vfsmount";
import RootFS from "../fs/providers/root-fs";
import { check as pathCheck, normalize, basename, dirname } from "../common/path";
import { ROOT_DIRECTORY_NAME } from "../common/constants";
import Providers from "../fs/providers/index";
import E from "../common/errors";
import { SUPER_NODE_ID } from "../common/constants";
import { MNT_READ_ONLY } from "../common/constants";
import { SYMLOOP_MAX } from "../common/constants";
import { MODE_FILE, MODE_DIRECTORY, MODE_SYMBOLIC_LINK } from "../common/constants";
import UUID from "../common/uuid";
import DirectoryEntry from "./directory-entry";
import Node from "../fs/node";
import SuperNode from "../fs/super-node";
import URL from "../common/url";
const __ = new WeakMap();
class InternalVFS
{
constructor()
{
const rootFS = new RootFS();
const rootFSVFSMount = new VFSMount({ fs: rootFS, flags: [ MNT_READ_ONLY ] })
const fsVFSMounts = new WeakMap();
fsVFSMounts.set(rootFS, rootFSVFSMount);
__.set(this, {
fdMap: new FDMap(),
vfsMountsRoot: rootFSVFSMount,
fsVFSMounts: fsVFSMounts,
vfsMounts: new Map(),
});
}
async findNode({path, followSymlinks = true} = {}, context)
{
const self = __.get(this);
if(!context) {
context = { symlinksFollowed: 0 };
}
path = normalize(path);
if(!path) {
throw new E.ENOENT("path is an empty string");
}
let name = basename(path);
let parentPath = dirname(path);
let fs;
let nodeId;
if(ROOT_DIRECTORY_NAME == name) {
fs = self.vfsMountsRoot.fs;
let superNode = await SuperNode.read(fs);
nodeId = superNode.rnode;
} else {
let parentDirectoryNode = await this.findNode({ path: parentPath }, context);
fs = parentDirectoryNode.fs;
if(parentDirectoryNode.mode !== MODE_DIRECTORY) {
throw new E.ENOTDIR("a component of the path prefix is not a directory", path);
}
let parentDirectoryData;
try {
parentDirectoryData = await parentDirectoryNode.readData();
} catch(error) {
parentDirectoryData = new Object();
}
if(!parentDirectoryData.hasOwnProperty(name)) {
throw new E.ENOENT(null, path);
}
let directoryEntry = new DirectoryEntry(parentDirectoryData[name]);
nodeId = directoryEntry.id;
}
// Follow all vfsMounts on this node.
let nodeHash = Node.hash(fs, nodeId);
while(self.vfsMounts.has(nodeHash)) {
let vfsMount = (self.vfsMounts.get(nodeHash))[0];
fs = vfsMount.fs;
if(vfsMount.rnode) {
nodeId = vfsMount.rnode;
} else {
let superNode = await SuperNode.read(fs);
nodeId = superNode.rnode;
}
nodeHash = Node.hash(fs, nodeId);
}
let node = await Node.read(fs, nodeId);
if(node.mode == MODE_SYMBOLIC_LINK) {
context.symlinksFollowed += 1;
if(context.symlinksFollowed > SYMLOOP_MAX) {
throw new E.ELOOP(null, path);
}
let symlinkPath = await node.readData();
node = await this.findNode({ path: symlinkPath }, context);
}
return node;
}
async mount(fsURL, mountPath, flags, options)
{
const self = __.get(this);
let mountPoint = await this.findNode({ path: mountPath });
if(!mountPoint) {
throw new E.ENOENT("mount target does not exist");
}
let url = new URL(fsURL);
if("filer" !== url.protocol) {
throw new E.UNKNOWN("expecting filer protocol");
}
let dev = url.path.slice(1);
let type = url.subprotocol;
if(!(type in Providers)) {
throw new E.UNKNOWN("unknown file system type");
}
let fs = await Providers[type].mount(dev, flags, options);
let superNode = await fs.readNode(SUPER_NODE_ID);
let rootNode = await fs.readNode(superNode.rnode);
let vfsMount = new VFSMount({ parent: self.fsVFSMounts.get(mountPoint.fs), flags: flags, fs: fs });
self.fsVFSMounts.set(fs, vfsMount);
if(!self.vfsMounts.has(mountPoint.hash())) {
self.vfsMounts.set(mountPoint.hash(), new Array());
}
self.vfsMounts.get(mountPoint.hash()).unshift(vfsMount);
}
async umount(path)
{
const self = __.get(this);
let mountNode = await this.findNode({ path: path });
let fs = mountNode.fs;
}
open(path, flags, mode, callback)
{
}
close(fd, callback)
{
}
mknod(path, mode, callback)
{
}
async mkdir(path, mode)
{
path = normalize(path);
let name = basename(path);
let parentPath = dirname(path);
let directoryNode;
try {
directoryNode = await this.findNode({ path: path });
} catch(error) {
directoryNode = null;
}
if(directoryNode) {
console.log(directoryNode.toJSON());
throw new E.EEXIST(null, path);
}
let parentDirectoryNode = await this.findNode({ path: parentPath });
let fs = parentDirectoryNode.fs;
let parentDirectoryData
try {
parentDirectoryData = await parentDirectoryNode.readData();
} catch(error) {
parentDirectoryData = new Object();
}
directoryNode = new Node({ fs: fs, data: { mode: MODE_DIRECTORY, nlinks: 1, data: UUID.short() } });
directoryNode.write();
let directoryData = new Object();
await directoryNode.writeData(0, directoryData);
// ! update node a/c/m times
parentDirectoryData[name] = new DirectoryEntry({ id: directoryNode.id, type: MODE_DIRECTORY });
await parentDirectoryNode.writeData(0, parentDirectoryData);
parentDirectoryNode.size = Object.keys(parentDirectoryData).length;
await parentDirectoryNode.write();
}
async readdir(path)
{
pathCheck(path);
let directoryNode = await this.findNode({ path: path });
let directoryData;
try {
directoryData = await directoryNode.readData();
} catch(error) {
if(error instanceof E.EIO)
directoryData = new Object();
}
let files = Object.keys(directoryData);
return files;
}
rmdir(path, callback)
{
}
stat(path, callback)
{
}
fstat(fd, callback)
{
}
link(oldpath, newpath, callback)
{
}
unlink(path, callback)
{
}
read(fd, buffer, offset, length, position, callback)
{
}
readFile(path, options, callback)
{
}
write(fd, buffer, offset, length, position, callback)
{
}
writeFile(path, data, options, callback)
{
}
appendFile(path, data, options, callback)
{
}
exists(path, callback)
{
}
getxattr(path, name, callback)
{
}
fgetxattr(fd, name, callback)
{
}
setxattr(path, name, value, flag, callback)
{
}
fsetxattr(fd, name, value, flag, callback)
{
}
removexattr(path, name, callback)
{
}
fremovexattr(fd, name, callback)
{
}
lseek(fd, offset, whence, callback)
{
}
utimes(path, atime, mtime, callback)
{
}
futimes(fd, atime, mtime, callback)
{
}
rename(oldpath, newpath, callback)
{
}
symlink(srcpath, dstpath, type, callback)
{
}
readlink(path, callback)
{
}
lstat(path, callback)
{
}
truncate(path, length, callback)
{
}
ftruncate(fd, length, callback)
{
}
};
class VFS
{
constructor()
{
__.set(this, {
vfs: new InternalVFS(),
});
}
async mount(...args) { return await __.get(this).vfs.mount(...args); }
async umount(...args) { return await __.get(this).vfs.umount(...args); }
async mkdir(...args) { return await __.get(this).vfs.mkdir(...args); }
async readdir(...args) { return await __.get(this).vfs.readdir(...args); }
}
export default VFS;

53
src/vfs/vfsmount.js Normal file
View File

@ -0,0 +1,53 @@
const __ = new WeakMap();
class VFSMount
{
constructor({ parentVFSMount = null, flags = [], fs, rnode = null } = {})
{
__.set(this, {
flags: flags,
fs: fs,
rnode: rnode,
parent: parentVFSMount,
children: new Set(),
});
if(parentVFSMount) {
parentVFSMount.insertChild(this);
}
}
get fs() { return __.get(this).fs }
get rnode() { return __.get(this).rnode }
get flags() { return __.get(this).flags }
get parent() { return __.get(this).parent }
get children() { return __.get(this).children }
hasChildren()
{
const self = __.get(this);
return this.children.size > 0;
}
insertChild(vfsMount)
{
const self = __.get(this);
self.children.add(vfsMount);
}
removeChild(vfsMount)
{
const self = __.get(this);
self.children.delete(vfsMount);
}
};
export default VFSMount;

39
test/browser-test.html Normal file
View File

@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<script>
const log = (text) => {
let pNode = document.createElement("p");
let textNode = document.createTextNode(text);
pNode.appendChild(textNode);
document.getElementById("log-container").appendChild(pNode);
}
</script>
<script src="../dist/filer.js"></script>
<script>
let vfs = new Filer.VFS();
(async () => {
let rootDirContents = await vfs.readdir("/");
log(`root directory contents: ${JSON.stringify(rootDirContents)}`);
await vfs.mount(`filer+memfs:///${Filer.UUID.v4()}`, "/");
rootDirContents = await vfs.readdir("/");
log(`root directory contents: ${JSON.stringify(rootDirContents)}`);
await vfs.mkdir("/test1");
rootDirContents = await vfs.readdir("/");
log(`root directory contents: ${JSON.stringify(rootDirContents)}`);
await vfs.mkdir("/test2");
rootDirContents = await vfs.readdir("/");
log(`root directory contents: ${JSON.stringify(rootDirContents)}`);
})();
</script>
<body>
<div id="log-container">
</div>
</body>
</html>

19
test/compare.js Normal file
View File

@ -0,0 +1,19 @@
class X
{
constructor(a, b)
{
this.a = a;
this.b = b;
}
valueOf()
{
return `${a}${b}`;
}
}
let obj1 = new X(1, 2);
let obj2 = new X(1, 2);
let obj3 = new X(2, 3);
console.log(obj1 === obj2);

22
test/index.js Normal file
View File

@ -0,0 +1,22 @@
const Filer = require("../dist/filer.js");
const should = require("should");
describe("Filer", () => {
it("should be an object", async () => {
(Filer).should.be.an.Object();
});
});
describe("Filer.VFS", () => {
it("should be a constructor", async () => {
(Filer.VFS).should.be.a.Function();
let vfs = new Filer.VFS();
});
it("should create a basic root file system on construction", async () => {
let vfs = new Filer.VFS();
let rootDirectoryEntries = await vfs.readdir("/");
(rootDirectoryEntries).should.deepEqual([]);
});
});

29
test/node-test.js Normal file
View File

@ -0,0 +1,29 @@
const log = console.log;
const Filer = require("../dist/filer");
let vfs = new Filer.VFS();
(async () => {
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mount(`filer+memfs:///${Filer.UUID.v4()}`, "/");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mkdir("/test1");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mkdir("/test2");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mount(`filer+memfs:///${Filer.UUID.v4()}`, "/");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mkdir("/test3");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.mkdir("/test4");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
await vfs.umount("/");
log(`root directory contents: ${JSON.stringify(await vfs.readdir("/"))}`);
})();

15
test/uuid-test.js Normal file
View File

@ -0,0 +1,15 @@
const crypto = require("crypto");
const uuidparse = require("uuid-parse");
let buffer = new Uint8Array(16);
crypto.randomFillSync(buffer);
console.log(buffer);
buffer[6] &= 0b00001111;
buffer[6] |= 0b01000000;
buffer[8] &= 0b00111111;
buffer[8] |= 0b10000000;
console.log(buffer);
console.log(uuidparse.unparse(buffer));