filefive 1.0.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.
- package/CHANGELOG.md +2 -0
- package/LICENSE +674 -0
- package/README.md +53 -0
- package/dist/App.js +69 -0
- package/dist/Connection.js +193 -0
- package/dist/FileSystem.js +13 -0
- package/dist/FileWatcher.js +44 -0
- package/dist/Local.js +86 -0
- package/dist/LocalWatcher.js +48 -0
- package/dist/Password.js +51 -0
- package/dist/Queue.js +236 -0
- package/dist/RemoteWatcher.js +44 -0
- package/dist/Session.js +21 -0
- package/dist/commands/clear.js +22 -0
- package/dist/commands/connect.js +87 -0
- package/dist/commands/copy.js +65 -0
- package/dist/commands/disconnect.js +12 -0
- package/dist/commands/duplicate.js +44 -0
- package/dist/commands/getConnection.js +17 -0
- package/dist/commands/index.js +42 -0
- package/dist/commands/mkdir.js +42 -0
- package/dist/commands/read.js +13 -0
- package/dist/commands/remove.js +56 -0
- package/dist/commands/rename.js +21 -0
- package/dist/commands/resolve.js +14 -0
- package/dist/commands/saveConnection.js +50 -0
- package/dist/commands/saveSettings.js +21 -0
- package/dist/commands/settings.js +33 -0
- package/dist/commands/unwatch.js +14 -0
- package/dist/commands/watch.js +25 -0
- package/dist/commands/write.js +17 -0
- package/dist/fs/Ftp.js +188 -0
- package/dist/fs/Local.js +68 -0
- package/dist/fs/SFtp.js +268 -0
- package/dist/index.js +69 -0
- package/dist/keybindings.json +58 -0
- package/dist/log.js +147 -0
- package/dist/options.js +5 -0
- package/dist/public/76e5e3fe6b35d5e6a980.woff2 +0 -0
- package/dist/public/assets/android-chrome-144x144.png +0 -0
- package/dist/public/assets/android-chrome-192x192.png +0 -0
- package/dist/public/assets/android-chrome-256x256.png +0 -0
- package/dist/public/assets/android-chrome-36x36.png +0 -0
- package/dist/public/assets/android-chrome-384x384.png +0 -0
- package/dist/public/assets/android-chrome-48x48.png +0 -0
- package/dist/public/assets/android-chrome-512x512.png +0 -0
- package/dist/public/assets/android-chrome-72x72.png +0 -0
- package/dist/public/assets/android-chrome-96x96.png +0 -0
- package/dist/public/assets/apple-touch-icon-1024x1024.png +0 -0
- package/dist/public/assets/apple-touch-icon-114x114.png +0 -0
- package/dist/public/assets/apple-touch-icon-120x120.png +0 -0
- package/dist/public/assets/apple-touch-icon-144x144.png +0 -0
- package/dist/public/assets/apple-touch-icon-152x152.png +0 -0
- package/dist/public/assets/apple-touch-icon-167x167.png +0 -0
- package/dist/public/assets/apple-touch-icon-180x180.png +0 -0
- package/dist/public/assets/apple-touch-icon-57x57.png +0 -0
- package/dist/public/assets/apple-touch-icon-60x60.png +0 -0
- package/dist/public/assets/apple-touch-icon-72x72.png +0 -0
- package/dist/public/assets/apple-touch-icon-76x76.png +0 -0
- package/dist/public/assets/apple-touch-icon-precomposed.png +0 -0
- package/dist/public/assets/apple-touch-icon.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1125x2436.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1136x640.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1170x2532.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1179x2556.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1242x2208.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1242x2688.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1284x2778.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1290x2796.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1334x750.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1488x2266.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1536x2048.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1620x2160.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1640x2160.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1668x2224.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1668x2388.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-1792x828.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2048x1536.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2048x2732.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2160x1620.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2160x1640.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2208x1242.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2224x1668.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2266x1488.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2388x1668.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2436x1125.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2532x1170.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2556x1179.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2688x1242.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2732x2048.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2778x1284.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-2796x1290.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-640x1136.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-750x1334.png +0 -0
- package/dist/public/assets/apple-touch-startup-image-828x1792.png +0 -0
- package/dist/public/assets/browserconfig.xml +12 -0
- package/dist/public/assets/favicon-16x16.png +0 -0
- package/dist/public/assets/favicon-32x32.png +0 -0
- package/dist/public/assets/favicon-48x48.png +0 -0
- package/dist/public/assets/favicon.ico +0 -0
- package/dist/public/assets/manifest.webmanifest +68 -0
- package/dist/public/assets/mstile-144x144.png +0 -0
- package/dist/public/assets/mstile-150x150.png +0 -0
- package/dist/public/assets/mstile-310x150.png +0 -0
- package/dist/public/assets/mstile-310x310.png +0 -0
- package/dist/public/assets/mstile-70x70.png +0 -0
- package/dist/public/assets/yandex-browser-50x50.png +0 -0
- package/dist/public/assets/yandex-browser-manifest.json +9 -0
- package/dist/public/ceb05254cedfa895b512.ttf +0 -0
- package/dist/public/f72efa639bb45d29e62f.ttf +0 -0
- package/dist/public/index.html +1 -0
- package/dist/public/index.js +2 -0
- package/dist/public/index.js.LICENSE.txt +43 -0
- package/dist/queues/Copy.js +69 -0
- package/dist/queues/Download.js +30 -0
- package/dist/queues/Duplicate.js +27 -0
- package/dist/queues/Queue.js +214 -0
- package/dist/queues/Remove.js +82 -0
- package/dist/queues/Upload.js +57 -0
- package/dist/transform.js +24 -0
- package/dist/types.js +46 -0
- package/dist/utils/ReferenceCountMap.js +61 -0
- package/dist/utils/URI.js +44 -0
- package/dist/utils/filter.js +24 -0
- package/dist/utils/path.js +12 -0
- package/dist/utils/uniqid.js +6 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://github.com/miroshnikov/filefive/blob/main/frontend/src/assets/logo.svg" width="64" alt="FileFive" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# FileFive: SFTP/FTP client and dual-panel file manager for macOS and Linux
|
|
7
|
+
FileFive is a free open-source SFTP/FTP client and file manager with intuitive and modern dual-panel interface.
|
|
8
|
+
|
|
9
|
+
It is installed a Node.js package and works in the browser.
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://github.com/miroshnikov/filefive/blob/main/screenshot.png" alt="FileFive" />
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
```shell
|
|
17
|
+
npm install -g filefive
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
Run `f5` and FileFive will be opened in the default browser. Press <kbd>ctrl</kbd> + <kbd>c</kbd> to quit the program.
|
|
22
|
+
```
|
|
23
|
+
> f5 --help
|
|
24
|
+
Usage: F5 [options]
|
|
25
|
+
|
|
26
|
+
SFTP/FTP client, dual-panel file manager in the browser
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-V, --version output the version number
|
|
30
|
+
-p, --port <number> port number (default: "3113")
|
|
31
|
+
--log prints the log information
|
|
32
|
+
-h, --help display help for command
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
- Supports SSH File Transfer Protocol (SFTP) and FTP
|
|
37
|
+
- Cross-platform, runs on Mac OS and Linux
|
|
38
|
+
- Minimal and intuitive UI, mimicing the VSCode Explorer view
|
|
39
|
+
- Search/filter files using JavaScript Regular Expressions
|
|
40
|
+
- Synchronized browsing
|
|
41
|
+
- Connections/servers are plain files stored in a folder on your filesystem
|
|
42
|
+
- You can use Git or any VCS to store connections and settings
|
|
43
|
+
- Drag & drop support
|
|
44
|
+
- Open files and folders in Visual Studio Code
|
|
45
|
+
- Uses browser tabs to browse more than one server or transfer files simultaneously
|
|
46
|
+
|
|
47
|
+
## Feedbacks
|
|
48
|
+
To support its development, [star FileFive on GitHub](https://github.com/miroshnikov/filefive/stargazers)!
|
|
49
|
+
|
|
50
|
+
[Feedback, suggestion, improvements](https://github.com/miroshnikov/filefive/discussions) or [bugs](https://github.com/miroshnikov/filefive/issues) are welcome.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
[GPL-3.0 License](LICENSE)
|
package/dist/App.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const Connection_1 = __importDefault(require("./Connection"));
|
|
11
|
+
const LocalWatcher_1 = __importDefault(require("./LocalWatcher"));
|
|
12
|
+
const FileWatcher_1 = __importDefault(require("./FileWatcher"));
|
|
13
|
+
const RemoteWatcher_1 = __importDefault(require("./RemoteWatcher"));
|
|
14
|
+
const Queue_1 = require("./queues/Queue");
|
|
15
|
+
const Password_1 = __importDefault(require("./Password"));
|
|
16
|
+
const commands_1 = require("./commands");
|
|
17
|
+
const transform_1 = __importDefault(require("./transform"));
|
|
18
|
+
const Local_1 = require("./Local");
|
|
19
|
+
const URI_1 = require("./utils/URI");
|
|
20
|
+
class App {
|
|
21
|
+
static async bootstrap(handle, emitter, opener) {
|
|
22
|
+
const dataPath = (0, node_path_1.join)((0, node_os_1.homedir)(), '.f5');
|
|
23
|
+
const connPath = (0, node_path_1.join)(dataPath, 'connections');
|
|
24
|
+
(0, promises_1.mkdir)(connPath, { recursive: true });
|
|
25
|
+
(0, Local_1.touch)((0, node_path_1.join)(dataPath, 'credentials.json'), JSON.stringify([]));
|
|
26
|
+
const settingsPath = (0, node_path_1.join)(dataPath, 'settings.json');
|
|
27
|
+
if (!(0, Local_1.stat)(settingsPath)) {
|
|
28
|
+
await (0, Local_1.touch)(settingsPath);
|
|
29
|
+
commands_1.commands.saveSettings(settingsPath, await commands_1.commands.getSettings(settingsPath));
|
|
30
|
+
}
|
|
31
|
+
Password_1.default.load(dataPath, id => App.onError({ type: types_1.FailureType.Unauthorized, id }));
|
|
32
|
+
Connection_1.default.initialize();
|
|
33
|
+
Object.entries({
|
|
34
|
+
getapp: () => commands_1.commands.getSettings(settingsPath),
|
|
35
|
+
saveapp: ({ settings }) => commands_1.commands.saveSettings(settingsPath, settings),
|
|
36
|
+
connect: ({ file }) => commands_1.commands.connect(file, (id, { message }) => this.onError({ type: types_1.FailureType.RemoteError, id, message })),
|
|
37
|
+
login: ({ id, password, remember }) => Password_1.default.set(id, password, remember),
|
|
38
|
+
disconnect: ({ id, sid }) => commands_1.commands.disconnect(id, sid),
|
|
39
|
+
watch: ({ dir }) => commands_1.commands.watch(dir, this.localWatcher, this.remoteWatcher, this.fileWatcher),
|
|
40
|
+
unwatch: ({ dir }) => commands_1.commands.unwatch(dir, this.localWatcher, this.remoteWatcher, this.fileWatcher),
|
|
41
|
+
refresh: ({ dir }) => this.remoteWatcher.refresh(dir),
|
|
42
|
+
copy: ({ src, dest, move, filter, sid }) => commands_1.commands.copy(src, dest, move, filter, sid),
|
|
43
|
+
duplicate: ({ src, filter }) => commands_1.commands.duplicate(src, filter),
|
|
44
|
+
remove: ({ files, force }) => commands_1.commands.remove(files, force, connPath),
|
|
45
|
+
clear: ({ file, force }) => commands_1.commands.clear(file, force),
|
|
46
|
+
open: ({ file }) => opener(file),
|
|
47
|
+
mkdir: ({ name, parent }) => commands_1.commands.mkdir(name, parent),
|
|
48
|
+
read: ({ file }) => commands_1.commands.read(file),
|
|
49
|
+
write: ({ path, content }) => commands_1.commands.write(path, content),
|
|
50
|
+
rename: ({ path, name }) => commands_1.commands.rename(path, name),
|
|
51
|
+
get: ({ path }) => commands_1.commands.getConnection(path),
|
|
52
|
+
save: ({ path, settings }) => commands_1.commands.saveConnection(path, settings),
|
|
53
|
+
resolve: ({ id, action, forAll, sid }) => commands_1.commands.resolve(id, action, forAll, sid),
|
|
54
|
+
stop: ({ id }) => Queue_1.queues.get(id)?.stop()
|
|
55
|
+
}).forEach(([name, handler]) => handle(name, handler));
|
|
56
|
+
const emitError = emitter('error');
|
|
57
|
+
this.onError = (error) => emitError(error);
|
|
58
|
+
const emitDir = emitter('dir');
|
|
59
|
+
const sendDirContent = (uri, files) => emitDir({ uri, files });
|
|
60
|
+
this.localWatcher = new LocalWatcher_1.default((path, files) => sendDirContent((0, URI_1.createURI)(types_1.LocalFileSystemID, path), files.map(f => ({ ...f, URI: (0, URI_1.createURI)(types_1.LocalFileSystemID, f.path) }))), path => this.onError({ type: types_1.FailureType.MissingDir, uri: (0, URI_1.createURI)(types_1.LocalFileSystemID, path) }));
|
|
61
|
+
this.remoteWatcher = new RemoteWatcher_1.default(sendDirContent, uri => this.onError({ type: types_1.FailureType.MissingDir, uri }), transform_1.default);
|
|
62
|
+
const emitFile = emitter('file');
|
|
63
|
+
const sendFileStat = (path, stat) => emitFile({ path, stat });
|
|
64
|
+
this.fileWatcher = new FileWatcher_1.default(sendFileStat);
|
|
65
|
+
const emitQueue = emitter('queue');
|
|
66
|
+
this.onQueueUpdate = (id, event) => emitQueue({ id, event });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.default = App;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap"));
|
|
40
|
+
const types_1 = require("./types");
|
|
41
|
+
const URI_1 = require("./utils/URI");
|
|
42
|
+
const Password_1 = __importDefault(require("./Password"));
|
|
43
|
+
const uniqid_1 = __importDefault(require("./utils/uniqid"));
|
|
44
|
+
const log_1 = __importStar(require("./log"));
|
|
45
|
+
const Local_1 = __importDefault(require("./fs/Local"));
|
|
46
|
+
const SFtp_1 = __importStar(require("./fs/SFtp"));
|
|
47
|
+
const Ftp_1 = __importStar(require("./fs/Ftp"));
|
|
48
|
+
const options_1 = __importDefault(require("./options"));
|
|
49
|
+
class default_1 {
|
|
50
|
+
static initialize() {
|
|
51
|
+
this.shared.set(types_1.LocalFileSystemID, new Local_1.default);
|
|
52
|
+
}
|
|
53
|
+
static async open(scheme, user, host, port) {
|
|
54
|
+
const id = (0, URI_1.connectionID)(scheme, user, host, port);
|
|
55
|
+
const attrs = (scheme == 'sftp') ? SFtp_1.ATTRIBUTES : Ftp_1.ATTRIBUTES;
|
|
56
|
+
if (this.shared.inc(id)) {
|
|
57
|
+
return attrs;
|
|
58
|
+
}
|
|
59
|
+
const conn = await this.create(id, scheme, user, host, port);
|
|
60
|
+
await conn.open();
|
|
61
|
+
this.shared.set(id, conn);
|
|
62
|
+
return attrs;
|
|
63
|
+
}
|
|
64
|
+
static get(id) {
|
|
65
|
+
return this.shared.get(id);
|
|
66
|
+
}
|
|
67
|
+
static close(id) {
|
|
68
|
+
this.shared.dec(id)?.close();
|
|
69
|
+
}
|
|
70
|
+
static async list(id, path) {
|
|
71
|
+
const files = await this.get(id)?.ls(path) || [];
|
|
72
|
+
return files.map(f => ({ ...f, URI: (0, URI_1.createURI)(id, f.path) }));
|
|
73
|
+
}
|
|
74
|
+
static async transmit(id) {
|
|
75
|
+
if (id == types_1.LocalFileSystemID) {
|
|
76
|
+
return [new Local_1.default, () => { }];
|
|
77
|
+
}
|
|
78
|
+
const conn = await this.hold(id);
|
|
79
|
+
if (conn) {
|
|
80
|
+
return [conn[0], () => this.release(id, conn[1])];
|
|
81
|
+
}
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const onRelease = (poolId) => {
|
|
84
|
+
const { fs } = this.pools.get(id).get(poolId);
|
|
85
|
+
resolve([fs, () => this.release(id, poolId)]);
|
|
86
|
+
};
|
|
87
|
+
id in this.pending ? this.pending[id].push(onRelease) : (this.pending[id] = [onRelease]);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
static async hold(id) {
|
|
91
|
+
!this.pools.has(id) && this.pools.set(id, new Map());
|
|
92
|
+
const pool = this.pools.get(id);
|
|
93
|
+
const conn = this.getIdle(id);
|
|
94
|
+
if (conn) {
|
|
95
|
+
return conn;
|
|
96
|
+
}
|
|
97
|
+
if (pool.size < this.getLimit(id)) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const connect = async () => {
|
|
100
|
+
const conn = this.getIdle(id);
|
|
101
|
+
if (conn) {
|
|
102
|
+
resolve(conn);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const poolId = (0, uniqid_1.default)();
|
|
106
|
+
try {
|
|
107
|
+
const onClose = () => this.pools.get(id)?.delete(poolId);
|
|
108
|
+
const fs = await this.createFromId(id, onClose);
|
|
109
|
+
await fs.open();
|
|
110
|
+
this.pools.get(id).set(poolId, { fs, idle: false });
|
|
111
|
+
resolve([fs, poolId]);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
const conn = this.getIdle(id);
|
|
115
|
+
if (conn) {
|
|
116
|
+
resolve(conn);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
resolve(null);
|
|
121
|
+
};
|
|
122
|
+
this.queue.push(connect);
|
|
123
|
+
if (this.queue.length == 1) {
|
|
124
|
+
this.connectNext();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
static getIdle(id) {
|
|
131
|
+
const conn = Array.from(this.pools.get(id)?.entries() || []).find(([, { idle }]) => idle !== false);
|
|
132
|
+
if (conn) {
|
|
133
|
+
conn[1].idle !== false && clearTimeout(conn[1].idle);
|
|
134
|
+
conn[1].idle = false;
|
|
135
|
+
return [conn[1].fs, conn[0]];
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
static async connectNext() {
|
|
140
|
+
this.queue
|
|
141
|
+
.splice(0, Math.max(this.maxStartups - this.numOfStartups, 0))
|
|
142
|
+
.map(connect => async () => { await connect(); this.numOfStartups--; this.connectNext(); })
|
|
143
|
+
.forEach(f => { this.numOfStartups++; f(); });
|
|
144
|
+
}
|
|
145
|
+
static release(id, poolId) {
|
|
146
|
+
const conn = this.pools.get(id)?.get(poolId);
|
|
147
|
+
if (conn) {
|
|
148
|
+
if (this.pending[id]?.length) {
|
|
149
|
+
this.pending[id].shift()(poolId);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
conn.idle = setTimeout(() => {
|
|
153
|
+
conn.fs.close();
|
|
154
|
+
this.pools.get(id)?.delete(poolId);
|
|
155
|
+
}, 2000);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
static getLimit(id) {
|
|
160
|
+
return this.limits.has(id) ? this.limits.get(id) : 1024; // for vsftpd max_per_ip in /etc/vsftpd.conf
|
|
161
|
+
}
|
|
162
|
+
static async create(id, scheme, user, host, port, onClose = () => { }) {
|
|
163
|
+
if (options_1.default.log) {
|
|
164
|
+
return new log_1.LogFS(id, await this.createFS(scheme, user, host, port, await Password_1.default.get(id), onClose));
|
|
165
|
+
}
|
|
166
|
+
return this.createFS(scheme, user, host, port, await Password_1.default.get(id), onClose);
|
|
167
|
+
}
|
|
168
|
+
static async createFS(scheme, user, host, port, password, onClose) {
|
|
169
|
+
switch (scheme) {
|
|
170
|
+
case 'sftp': {
|
|
171
|
+
return new SFtp_1.default(host, user, password, port, error => log_1.default.error('SFTP error:', error), onClose);
|
|
172
|
+
}
|
|
173
|
+
case 'ftp': {
|
|
174
|
+
return new Ftp_1.default(host, user, password, port, error => log_1.default.error('FTP error:', error), onClose);
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
throw new Error(`Unsupported scheme ${scheme}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
static async createFromId(id, onClose = () => { }) {
|
|
181
|
+
const { scheme, user, host, port } = (0, URI_1.parseURI)(id);
|
|
182
|
+
return this.create(id, scheme, user, host, port, onClose);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
default_1.numOfStartups = 0;
|
|
186
|
+
default_1.maxStartups = 7; // for SFTP see MaxStartups in /etc/ssh/sshd_config
|
|
187
|
+
default_1.shared = new ReferenceCountMap_1.default;
|
|
188
|
+
// private static pools: Record<string, Record<string, {fs: FileSystem, idle: false|ReturnType<typeof setTimeout>}>> = {}
|
|
189
|
+
default_1.pools = new Map();
|
|
190
|
+
default_1.queue = [];
|
|
191
|
+
default_1.pending = {};
|
|
192
|
+
default_1.limits = new Map();
|
|
193
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileSystem = exports.FileAttributeType = void 0;
|
|
4
|
+
var FileAttributeType;
|
|
5
|
+
(function (FileAttributeType) {
|
|
6
|
+
FileAttributeType["String"] = "string";
|
|
7
|
+
FileAttributeType["Number"] = "number";
|
|
8
|
+
FileAttributeType["Date"] = "date";
|
|
9
|
+
FileAttributeType["Rights"] = "rights";
|
|
10
|
+
})(FileAttributeType || (exports.FileAttributeType = FileAttributeType = {}));
|
|
11
|
+
class FileSystem {
|
|
12
|
+
}
|
|
13
|
+
exports.FileSystem = FileSystem;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const Local_1 = require("./Local");
|
|
9
|
+
const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap"));
|
|
10
|
+
class default_1 {
|
|
11
|
+
constructor(listener) {
|
|
12
|
+
this.listener = listener;
|
|
13
|
+
this.watched = new ReferenceCountMap_1.default;
|
|
14
|
+
}
|
|
15
|
+
async watch(path) {
|
|
16
|
+
if (this.watched.inc(path)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const ac = new AbortController();
|
|
21
|
+
this.watched.set(path, ac);
|
|
22
|
+
for await (const { eventType, filename } of (0, promises_1.watch)(path, { signal: ac.signal })) {
|
|
23
|
+
let newPath = path;
|
|
24
|
+
if (eventType == "rename" && (0, node_path_1.basename)(path) !== filename) {
|
|
25
|
+
newPath = (0, node_path_1.join)((0, node_path_1.dirname)(path), filename);
|
|
26
|
+
this.watched.renameKey(path, newPath);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
this.listener(path, (0, Local_1.stat)(newPath), eventType);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
this.listener(path, null, eventType);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.error('Error while watch file', e);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
unwatch(dir) {
|
|
41
|
+
this.watched.dec(dir)?.abort();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.default = default_1;
|
package/dist/Local.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pwd = pwd;
|
|
4
|
+
exports.stat = stat;
|
|
5
|
+
exports.list = list;
|
|
6
|
+
exports.move = move;
|
|
7
|
+
exports.copy = copy;
|
|
8
|
+
exports.del = del;
|
|
9
|
+
exports.touch = touch;
|
|
10
|
+
exports.watch = watch;
|
|
11
|
+
exports.read = read;
|
|
12
|
+
const node_os_1 = require("node:os");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const promises_1 = require("node:fs/promises");
|
|
16
|
+
function pwd() {
|
|
17
|
+
return (0, node_os_1.homedir)();
|
|
18
|
+
}
|
|
19
|
+
function stat(path) {
|
|
20
|
+
path = (0, node_path_1.normalize)(path);
|
|
21
|
+
try {
|
|
22
|
+
let stat = (0, node_fs_1.lstatSync)(path);
|
|
23
|
+
let target;
|
|
24
|
+
if (stat.isSymbolicLink()) {
|
|
25
|
+
target = (0, node_fs_1.readlinkSync)(path);
|
|
26
|
+
if (!(0, node_path_1.isAbsolute)(target)) {
|
|
27
|
+
target = (0, node_path_1.normalize)((0, node_path_1.join)((0, node_path_1.dirname)(path), target));
|
|
28
|
+
}
|
|
29
|
+
stat = (0, node_fs_1.statSync)(target);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
path,
|
|
33
|
+
name: (0, node_path_1.basename)(path),
|
|
34
|
+
dir: stat.isDirectory(),
|
|
35
|
+
size: stat.size, // in bytes
|
|
36
|
+
modified: stat.mtime,
|
|
37
|
+
inode: stat.ino,
|
|
38
|
+
...(target ? { target } : {})
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function list(dir) {
|
|
46
|
+
return (0, node_fs_1.readdirSync)(dir).map(name => stat((0, node_path_1.join)(dir, name))).filter(f => f);
|
|
47
|
+
}
|
|
48
|
+
async function move(from, to, force = false) {
|
|
49
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(to), { recursive: true });
|
|
50
|
+
const existing = stat(to);
|
|
51
|
+
if (existing) {
|
|
52
|
+
if (force) {
|
|
53
|
+
await (0, promises_1.unlink)(to);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
return existing;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await (0, promises_1.rename)(from, to);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
async function copy(from, to, force = false) {
|
|
63
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(to), { recursive: true });
|
|
64
|
+
const existing = stat(to);
|
|
65
|
+
if (existing && !force) {
|
|
66
|
+
return existing;
|
|
67
|
+
}
|
|
68
|
+
await (0, promises_1.cp)(from, to, { recursive: true });
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
async function del(path) {
|
|
72
|
+
return (0, promises_1.rm)(path, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
async function touch(path, data) {
|
|
75
|
+
if (!stat(path)) {
|
|
76
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(path), { recursive: true });
|
|
77
|
+
data !== undefined ? await (0, promises_1.writeFile)(path, data) : await (await (0, promises_1.open)(path, 'a')).close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function watch(path, listener) {
|
|
81
|
+
const watcher = (0, node_fs_1.watch)(path, listener);
|
|
82
|
+
return () => watcher.close();
|
|
83
|
+
}
|
|
84
|
+
async function read(path) {
|
|
85
|
+
return await (0, promises_1.readFile)(path, { encoding: 'utf8' });
|
|
86
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const Local_1 = require("./Local");
|
|
8
|
+
const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap"));
|
|
9
|
+
class default_1 {
|
|
10
|
+
constructor(listener, onMissing, transform = (files) => files) {
|
|
11
|
+
this.listener = listener;
|
|
12
|
+
this.onMissing = onMissing;
|
|
13
|
+
this.transform = transform;
|
|
14
|
+
this.watched = new ReferenceCountMap_1.default;
|
|
15
|
+
}
|
|
16
|
+
watch(dir) {
|
|
17
|
+
try {
|
|
18
|
+
this.watched.inc(dir) || this.watched.set(dir, (0, Local_1.watch)(dir, (event, target) => {
|
|
19
|
+
if (event == 'rename') {
|
|
20
|
+
const child = (0, node_path_1.join)(dir, target);
|
|
21
|
+
if (this.watched.has(child) && !(0, Local_1.stat)(child)) {
|
|
22
|
+
this.watched.del(child);
|
|
23
|
+
this.onMissing(child);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
this.listener(dir, this.transform((0, Local_1.list)(dir)), event, target);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
this.watched.del(dir);
|
|
31
|
+
this.onMissing(dir);
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
const files = this.transform((0, Local_1.list)(dir));
|
|
35
|
+
this.listener(dir, files);
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
this.watched.del(dir);
|
|
40
|
+
this.onMissing(dir);
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
unwatch(dir) {
|
|
45
|
+
this.watched.dec(dir)?.();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.default = default_1;
|
package/dist/Password.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const promises_1 = require("node:fs/promises");
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
class Passwords {
|
|
6
|
+
static async load(dir, onMiss) {
|
|
7
|
+
this.saveFile = (0, node_path_1.join)(dir, 'credentials.json');
|
|
8
|
+
this.resolve = onMiss;
|
|
9
|
+
this.store = new Map(JSON.parse((await (0, promises_1.readFile)(this.saveFile)).toString()).map(([id, password]) => [id, [password, true]]));
|
|
10
|
+
}
|
|
11
|
+
static set(id, password, remember = false) {
|
|
12
|
+
if (password === false) {
|
|
13
|
+
this.pending.get(id)?.[1]();
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
this.pending.get(id)?.[0](password);
|
|
17
|
+
remember && this.store.set(id, [password, false]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
static save(id, password) {
|
|
21
|
+
this.store.set(id, [password, true]);
|
|
22
|
+
this.dump();
|
|
23
|
+
}
|
|
24
|
+
static async get(id, skipMissing = false) {
|
|
25
|
+
if (this.store.has(id)) {
|
|
26
|
+
return this.store.get(id)[0];
|
|
27
|
+
}
|
|
28
|
+
if (skipMissing) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
const p = new Promise((resolve, reject) => this.pending.set(id, [resolve, reject]));
|
|
32
|
+
this.resolve(id);
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
35
|
+
static delete(id, saved) {
|
|
36
|
+
const found = this.store.get(id);
|
|
37
|
+
if (found && found[1] == saved) {
|
|
38
|
+
this.store.delete(id);
|
|
39
|
+
found[1] == true && this.dump();
|
|
40
|
+
}
|
|
41
|
+
this.pending.delete(id);
|
|
42
|
+
}
|
|
43
|
+
static dump() {
|
|
44
|
+
(0, promises_1.writeFile)(this.saveFile, JSON.stringify(Array.from(this.store.entries())
|
|
45
|
+
.filter(([, [, save]]) => save)
|
|
46
|
+
.map(([id, [password,]]) => [id, password])));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
Passwords.saveFile = '';
|
|
50
|
+
Passwords.pending = new Map();
|
|
51
|
+
exports.default = Passwords;
|