hfs 0.27.2 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,5 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
26
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
27
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
5
  };
@@ -89,7 +66,7 @@ function serveProxied(port, uri) {
89
66
  return;
90
67
  console.debug('proxied on port', port);
91
68
  let proxy;
92
- Promise.resolve().then(() => __importStar(require('koa-better-http-proxy'))).then(lib => // dynamic import to avoid having this in final distribution
69
+ import('koa-better-http-proxy').then(lib => // dynamic import to avoid having this in final distribution
93
70
  proxy = lib.default('127.0.0.1:' + port, {
94
71
  proxyReqPathResolver: (ctx) => shouldServeApp(ctx) ? '/' : ctx.path,
95
72
  userResDecorator(res, data, ctx) {
package/src/update.js CHANGED
@@ -30,7 +30,7 @@ async function update() {
30
30
  throw "asset not found";
31
31
  const url = asset.browser_download_url;
32
32
  console.log("downloading", url);
33
- const bin = process.argv[0];
33
+ const bin = process.argv0;
34
34
  const binPath = (0, path_1.dirname)(bin);
35
35
  const binFile = (0, path_1.basename)(bin);
36
36
  const newBinFile = 'new-' + binFile;
@@ -64,7 +64,7 @@ async function update() {
64
64
  }
65
65
  exports.update = update;
66
66
  if (const_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
67
- const bin = process.argv[0];
67
+ const bin = process.argv0;
68
68
  (0, fs_1.renameSync)(bin, (0, path_1.join)((0, path_1.dirname)(bin), const_1.argv.updating));
69
69
  console.log("renamed binary file to", const_1.argv.updating);
70
70
  }
package/src/upload.js ADDED
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadWriter = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
7
+ const vfs_1 = require("./vfs");
8
+ const const_1 = require("./const");
9
+ const path_1 = require("path");
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const misc_1 = require("./misc");
12
+ const frontEndApis_1 = require("./frontEndApis");
13
+ const config_1 = require("./config");
14
+ const util_os_1 = require("./util-os");
15
+ exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after');
16
+ exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
17
+ const waitingToBeDeleted = {};
18
+ function uploadWriter(base, path, ctx) {
19
+ if (!base.source || !(0, vfs_1.hasPermission)(base, 'can_upload', ctx))
20
+ return fail(base.can_upload === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED);
21
+ const fullPath = (0, path_1.join)(base.source, path);
22
+ const dir = (0, path_1.dirname)(fullPath);
23
+ const min = exports.minAvailableMb.get() * (1 << 20);
24
+ const reqSize = Number(ctx.headers["content-length"]);
25
+ if (min && reqSize)
26
+ try {
27
+ if (reqSize > (0, util_os_1.getFreeDiskSync)(dir) - min)
28
+ return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
29
+ }
30
+ catch (e) {
31
+ console.warn("can't check disk size", String(e));
32
+ }
33
+ fs_1.default.mkdirSync(dir, { recursive: true });
34
+ const keepName = (0, path_1.basename)(fullPath).slice(-200);
35
+ let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
36
+ const resumable = fs_1.default.existsSync(tempName) && tempName;
37
+ if (resumable)
38
+ tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
39
+ const resume = Number(ctx.query.resume);
40
+ const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
41
+ if (size === undefined) // stat failed
42
+ return fail(const_1.HTTP_SERVER_ERROR);
43
+ if (resume > size)
44
+ return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
45
+ if (!resume && resumable) {
46
+ const timeout = 30;
47
+ (0, frontEndApis_1.notifyClient)(ctx, 'upload.resumable', { [path]: size, expires: Date.now() + timeout * 1000 });
48
+ delayedDelete(resumable, timeout, () => fs_1.default.rename(tempName, resumable, err => {
49
+ if (!err)
50
+ tempName = resumable;
51
+ }));
52
+ }
53
+ const resuming = resume && resumable;
54
+ const ret = resuming ? fs_1.default.createWriteStream(resumable, { flags: 'r+', start: resume })
55
+ : fs_1.default.createWriteStream(tempName);
56
+ if (resuming) {
57
+ fs_1.default.rm(tempName, () => { });
58
+ tempName = resumable;
59
+ }
60
+ cancelDeletion(tempName);
61
+ ret.on('close', () => {
62
+ if (!ctx.req.aborted)
63
+ return fs_1.default.rename(tempName, fullPath, err => {
64
+ err && console.error("couldn't rename temp to", fullPath, String(err));
65
+ if (resumable)
66
+ delayedDelete(resumable, 0);
67
+ });
68
+ if (resumable) // we don't want to be left with 2 temp files
69
+ return delayedDelete(tempName, 0);
70
+ const sec = exports.deleteUnfinishedUploadsAfter.get();
71
+ if (typeof sec !== 'number')
72
+ return;
73
+ delayedDelete(tempName, sec);
74
+ });
75
+ return ret;
76
+ function delayedDelete(path, secs, cb) {
77
+ clearTimeout(waitingToBeDeleted[path]);
78
+ waitingToBeDeleted[path] = setTimeout(() => {
79
+ delete waitingToBeDeleted[path];
80
+ fs_1.default.rm(path, () => cb === null || cb === void 0 ? void 0 : cb());
81
+ }, secs * 1000);
82
+ }
83
+ function cancelDeletion(path) {
84
+ clearTimeout(waitingToBeDeleted[path]);
85
+ delete waitingToBeDeleted[path];
86
+ }
87
+ function fail(status) {
88
+ ctx.status = status;
89
+ (0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
90
+ }
91
+ }
92
+ exports.uploadWriter = uploadWriter;
package/src/util-files.js CHANGED
@@ -4,14 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.prepareFolder = exports.unzip = exports.run = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
7
+ exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const misc_1 = require("./misc");
10
10
  const fs_1 = require("fs");
11
11
  const path_1 = require("path");
12
12
  const fast_glob_1 = __importDefault(require("fast-glob"));
13
13
  const const_1 = require("./const");
14
- const child_process_1 = require("child_process");
14
+ const util_os_1 = require("./util-os");
15
15
  const stream_1 = require("stream");
16
16
  // @ts-ignore
17
17
  const unzip_stream_1 = __importDefault(require("unzip-stream"));
@@ -115,21 +115,12 @@ async function* dirStream(path, deep) {
115
115
  async function getItemsToSkip(path) {
116
116
  if (!const_1.IS_WINDOWS)
117
117
  return;
118
- const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
118
+ const out = await (0, util_os_1.runCmd)('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
119
119
  .catch(() => ''); // error in case of no matching file
120
120
  return out.split('\r\n').slice(0, -1);
121
121
  }
122
122
  }
123
123
  exports.dirStream = dirStream;
124
- function run(cmd, args = []) {
125
- return new Promise((resolve, reject) => (0, child_process_1.execFile)('cmd', ['/c', cmd, ...args], (err, stdout) => {
126
- if (err)
127
- reject(err);
128
- else
129
- resolve(stdout);
130
- }));
131
- }
132
- exports.run = run;
133
124
  async function unzip(stream, cb) {
134
125
  let pending = Promise.resolve();
135
126
  return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
package/src/util-os.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCmd = exports.getDrives = exports.getFreeDiskSync = void 0;
4
+ const path_1 = require("path");
5
+ const child_process_1 = require("child_process");
6
+ const misc_1 = require("./misc");
7
+ const util_1 = require("util");
8
+ const const_1 = require("./const");
9
+ function getFreeDiskSync(path) {
10
+ var _a;
11
+ if (const_1.IS_WINDOWS) {
12
+ const drive = (0, path_1.resolve)(path).slice(0, 2).toUpperCase();
13
+ const out = (0, child_process_1.execSync)('wmic logicaldisk get FreeSpace,name /format:list').toString().replace(/\r/g, '');
14
+ const one = out.split(/\n\n+/).find(x => x.includes('Name=' + drive));
15
+ if (!one)
16
+ throw Error('miss');
17
+ return Number((_a = /FreeSpace=(\d+)/.exec(one)) === null || _a === void 0 ? void 0 : _a[1]);
18
+ }
19
+ const out = (0, misc_1.try_)(() => (0, child_process_1.execSync)(`df -k ${path}`).toString(), err => {
20
+ throw err.status === 1 ? Error('miss')
21
+ : err.status === 127 ? Error('unsupported')
22
+ : err;
23
+ });
24
+ if (!(out === null || out === void 0 ? void 0 : out.startsWith('Filesystem')))
25
+ throw Error('unsupported');
26
+ const one = out.split('\n')[1];
27
+ const free = Number(one.split(/\s+/)[3]);
28
+ return free * 1024;
29
+ }
30
+ exports.getFreeDiskSync = getFreeDiskSync;
31
+ async function getDrives() {
32
+ const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
33
+ return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
34
+ }
35
+ exports.getDrives = getDrives;
36
+ // execute win32 shell commands
37
+ async function runCmd(cmd, args = []) {
38
+ const { stdout, stderr } = await (0, util_1.promisify)(child_process_1.execFile)('cmd', ['/c', cmd, ...args]);
39
+ return stderr || stdout;
40
+ }
41
+ exports.runCmd = runCmd;
package/src/vfs.js CHANGED
@@ -103,11 +103,17 @@ function saveVfs() {
103
103
  }
104
104
  exports.saveVfs = saveVfs;
105
105
  function getNodeName(node) {
106
- return node.name
107
- || node.source && (/^[a-zA-Z]:\\?$/.test(node.source) && node.source.slice(0, 2)
108
- || (0, path_1.basename)(node.source)
109
- || node.source)
110
- || ''; // should happen only for root
106
+ const { name, source: s } = node;
107
+ if (name)
108
+ return name;
109
+ if (!s)
110
+ return ''; // should happen only for root
111
+ if (/^[a-zA-Z]:\\?$/.test(s))
112
+ return s.slice(0, 2); // exclude trailing slash
113
+ const base = (0, path_1.basename)(s);
114
+ if (/^[./\\]*$/.test(base)) // if empty or special-chars-only
115
+ return (0, path_1.basename)((0, path_1.resolve)(s)); // resolve to try to get more
116
+ return base;
111
117
  }
112
118
  exports.getNodeName = getNodeName;
113
119
  async function nodeIsDirectory(node) {
@@ -125,8 +131,7 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
125
131
  const { children, source } = parent;
126
132
  const took = prefixPath ? undefined : new Set();
127
133
  if (children)
128
- for (let idx = 0; idx < children.length; idx++) {
129
- const child = children[idx];
134
+ for (const child of children) {
130
135
  const name = prefixPath + getNodeName(child);
131
136
  took === null || took === void 0 ? void 0 : took.add(name);
132
137
  yield* workItem({
@@ -185,11 +190,11 @@ function inheritMasks(item, parent, virtualBasename) {
185
190
  if (!masks)
186
191
  return;
187
192
  const o = {};
188
- for (const k in masks)
193
+ for (const [k, v] of Object.entries(masks))
189
194
  if (k.startsWith('**/'))
190
- o[k.slice(3)] = masks[k];
195
+ o[k.slice(3)] = v;
191
196
  else if (k.startsWith(virtualBasename + '/'))
192
- o[k.slice(virtualBasename.length + 1)] = masks[k];
197
+ o[k.slice(virtualBasename.length + 1)] = v;
193
198
  if (Object.keys(o).length)
194
199
  item.masks = o;
195
200
  }
package/src/zip.js CHANGED
@@ -15,15 +15,17 @@ const path_1 = require("path");
15
15
  const serveFile_1 = require("./serveFile");
16
16
  const const_1 = require("./const");
17
17
  async function zipStreamFromFolder(node, ctx) {
18
+ var _a;
18
19
  ctx.status = const_1.HTTP_OK;
19
20
  ctx.mime = 'zip';
20
- const name = (0, vfs_1.getNodeName)(node);
21
- ctx.attachment((name || 'archive') + '.zip');
21
+ // ctx.query.list is undefined | string | string[]
22
+ const list = (_a = (0, misc_1.wantArray)(ctx.query.list)[0]) === null || _a === void 0 ? void 0 : _a.split('*'); // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
23
+ const name = (list === null || list === void 0 ? void 0 : list.length) === 1 ? (0, path_1.basename)(list[0]) : (0, vfs_1.getNodeName)(node);
24
+ ctx.attachment(((0, misc_1.isWindowsDrive)(name) ? name[0] : (name || 'archive')) + '.zip');
22
25
  const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
23
- const { list } = ctx.query;
24
26
  const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity)
25
27
  : (async function* () {
26
- for await (const el of String(list).split('*')) { // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
28
+ for await (const el of list) {
27
29
  const subNode = await (0, vfs_1.urlToNode)(el, ctx, node);
28
30
  if (!subNode || !(0, vfs_1.hasPermission)(subNode, 'can_read', ctx))
29
31
  continue;
@@ -1 +0,0 @@
1
- :root{--bg: #fff;--text: #555;--faint-contrast: #0002;--mild-contrast: #0005;--good-contrast: #000a;--button-bg: #68a;--button-text: #fff;--focus-color: #468}:root .theme-dark{--bg: #000;--text: #999;--faint-contrast: #fff2;--mild-contrast: #fff5;--good-contrast: #fffa;--button-bg: #345;--button-text: #999}:root .theme-dark body{color-scheme:dark}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-icon .icon{color:#aaa;margin-left:-1px;font-size:95%}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}:root .theme-dark .error-msg{color:#b88;background-color:#623}body{background:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input,select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box}input[type=checkbox]{margin:0 1.3em 0 .8em;transform:scale(1.7);accent-color:var(--button-bg)}.hidden{display:none!important}.icon{font-size:1.2em}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button.toggled{opacity:.6}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.error-msg{background-color:#faa;color:#833;padding:.5em 1em}header{position:sticky;top:0;background:var(--bg);padding:.2em;z-index:1}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats{font-size:90%;margin:.4em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}header input{width:100%;margin:.2em auto;box-sizing:border-box}#filter-bar{display:flex;gap:.3em;margin:.5em 0}#filter-bar input{flex:1}#filter-bar button{padding:0 .5em}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir li{display:block;list-style-type:none;margin-bottom:.3em;padding:.3em;border-top:1px solid var(--button-bg)}ul.dir li a{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a.container-folder:hover{text-decoration:underline}ul.dir li .entry-props{float:right;font-size:90%;margin-left:12px;margin-top:.2em}ul.dir li .entry-props .icon{margin:0 .3em}ul.dir li .entry-props .entry-size{display:inline-block}#menu-panel{margin-bottom:.2em}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:auto;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#menu-bar>a>button{width:100%}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog{--color: var(--button-bg)}#paging{display:flex;position:sticky;bottom:0;background:var(--bg);gap:.5em;overflow-x:auto}#paging>button{flex:1;background:var(--button-bg);text-align:center}.upload-list td:nth-child(1){width:0}.upload-list td:nth-child(2){text-align:right;width:0;white-space:nowrap;padding-left:.5em}.upload-list td:nth-child(3){padding:.2em .5em;word-break:break-word}*{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--faint-contrast)}*::-webkit-scrollbar{width:12px}*::-webkit-scrollbar-track{background:var(--faint-contrast)}*::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--faint-contrast)}@media (max-width: 42em){body,button,select{font-size:14pt}#menu-bar button label{display:none}#filter-bar{margin:.2em 0}#filter-bar label{display:none}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#888a;display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em)}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:1.8em;height:1.7em;text-align:center;border-radius:.8em 0}.dialog-title{margin-top:-.4em;font-size:110%}.dialog-icon~.dialog-title{text-align:center}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c99}.dialog-icon~.dialog-content{margin-top:2em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.7em}.dialog-content{overflow:auto;max-height:calc(100vh - 4em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 50em){.dialog-closer{font-size:120%}.dialog-icon~.dialog-content{margin-top:2em}}