filefive 2.0.2 → 3.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/README.md +8 -0
- package/dist/App.js +9 -11
- package/dist/Connection.js +13 -12
- package/dist/FileWatcher.js +1 -1
- package/dist/Local.js +11 -5
- package/dist/LocalWatcher.js +5 -2
- package/dist/Password.js +2 -2
- package/dist/Queue.js +12 -7
- package/dist/RemoteFiles.js +7 -2
- package/dist/commands/clear.js +5 -1
- package/dist/commands/connect.js +16 -6
- package/dist/commands/copy.js +3 -3
- package/dist/commands/disconnect.js +2 -0
- package/dist/commands/duplicate.js +1 -1
- package/dist/commands/mkdir.js +10 -3
- package/dist/commands/remove.js +1 -1
- package/dist/commands/rename.js +1 -1
- package/dist/commands/saveConnection.js +3 -0
- package/dist/commands/write.js +1 -1
- package/dist/events.js +10 -0
- package/dist/fs/S3.js +8 -7
- package/dist/fs/SFtp.js +5 -3
- package/dist/index.js +6 -5
- package/dist/log.js +5 -2
- package/dist/plugins/mirror/commands.js +185 -0
- package/dist/plugins/mirror/sessions.js +26 -0
- package/dist/plugins/mirror/watchers.js +4 -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/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/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/index.js +2 -1
- package/dist/public/index.js.map +1 -0
- package/dist/queues/Duplicate.js +1 -1
- package/dist/queues/Queue.js +12 -14
- package/dist/queues/Remove.js +3 -1
- package/dist/transformers/git.js +54 -28
- package/dist/types.js +9 -1
- package/dist/utils/ReferenceCountMap.js +3 -3
- package/dist/utils/URI.js +5 -3
- package/dist/utils/filter.js +31 -0
- package/package.json +22 -19
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ It has a unique set of features and may be a free alternative to `WinSCP`, `File
|
|
|
10
10
|
|
|
11
11
|
FileFive is also a visual, no-code and easy-to-use backup and CI/CD tool for developers and system administrators with user-friendly UI interface, offering similar functionality to `rsync`, `rclone` and `sshfs`.
|
|
12
12
|
|
|
13
|
+
FileFive can [watch a local directories and automatically push changes to a remote server](#continuous-folder-synchronization).
|
|
14
|
+
|
|
13
15
|
<p align="center">
|
|
14
16
|
<img src="https://github.com/miroshnikov/filefive/blob/main/docs/screenshots/screenshot-1.png" alt="FileFive" />
|
|
15
17
|
</p>
|
|
@@ -63,6 +65,12 @@ Options:
|
|
|
63
65
|
- **Theming**: System preference, Light, Dark; a different color theme per connection; the most popular file and folder icon themes from VS Code
|
|
64
66
|
- Search On Type
|
|
65
67
|
|
|
68
|
+
### Continuous Folder Synchronization
|
|
69
|
+
FileFive can monitor a local directory for filesystem changes and maintains a synchronized copy on a remote server. Changes detected in the local directory are queued and propagated to the remote directory automatically. Synchronization can include file creation, modification, renaming, and deletion, with optional **filtering** and subfolder support.
|
|
70
|
+
|
|
71
|
+
It is a simple, user-friendly alternative to `WinSCP`, `Mutagen`, `Syncthing`, VS Code sync extensions, or the CLI-based combination of `lsyncd` and `rsync`, with a focus on simplicity and ease of configuration. Synchronization tasks can be configured entirely through an intuitive graphical interface - without manually creating or editing configuration files, writing scripts, or dealing with command-line options.
|
|
72
|
+
|
|
73
|
+
|
|
66
74
|
## Feedbacks
|
|
67
75
|
To support its development, [star FileFive on GitHub](https://github.com/miroshnikov/filefive/stargazers)!
|
|
68
76
|
|
package/dist/App.js
CHANGED
|
@@ -15,6 +15,7 @@ const commands_1 = require("./commands");
|
|
|
15
15
|
const Local_1 = require("./Local");
|
|
16
16
|
const URI_1 = require("./utils/URI");
|
|
17
17
|
const Local_2 = __importDefault(require("./transformers/Local"));
|
|
18
|
+
const commands_2 = require("./plugins/mirror/commands");
|
|
18
19
|
class App {
|
|
19
20
|
static async bootstrap(handle, emitter, opener) {
|
|
20
21
|
const dataPath = (0, posix_1.join)((0, Local_1.pwd)(), '.f5');
|
|
@@ -49,7 +50,9 @@ class App {
|
|
|
49
50
|
get: ({ path }) => commands_1.commands.getConnection(path),
|
|
50
51
|
save: ({ path, settings }) => commands_1.commands.saveConnection(path, settings),
|
|
51
52
|
resolve: ({ id, action, forAll, sid }) => commands_1.commands.resolve(id, action, forAll, sid),
|
|
52
|
-
stop: ({ id }) => Queue_1.queues.get(id)?.stop()
|
|
53
|
+
stop: ({ id }) => Queue_1.queues.get(id)?.stop(),
|
|
54
|
+
mirror: ({ local, remote, sid, recursive, del, filter }) => (0, commands_2.mirror)(local, remote, sid, recursive, del, filter, this.remoteWatcher, emitter('mirror')),
|
|
55
|
+
unmirror: ({ id }) => (0, commands_2.unmirror)(id)
|
|
53
56
|
}).forEach(([name, handler]) => handle(name, handler));
|
|
54
57
|
const emitError = emitter('error');
|
|
55
58
|
this.onError = (error) => emitError(error);
|
|
@@ -57,7 +60,7 @@ class App {
|
|
|
57
60
|
const sendDirContent = (uri, files) => emitDir({ uri, files });
|
|
58
61
|
this.localWatcher = new LocalWatcher_1.default(async (path, files) => {
|
|
59
62
|
const transformer = new Local_2.default();
|
|
60
|
-
sendDirContent((0, URI_1.createURI)(types_1.LocalFileSystemID, path), await transformer.transform(path, files.map(f => ({ ...f, URI: (0, URI_1.createURI)(types_1.LocalFileSystemID, f.path) }))));
|
|
63
|
+
sendDirContent((0, URI_1.createURI)(types_1.LocalFileSystemID, path), await transformer.transform(path, files.map((f) => ({ ...f, URI: (0, URI_1.createURI)(types_1.LocalFileSystemID, f.path) }))));
|
|
61
64
|
}, path => this.onError({ type: types_1.FailureType.MissingDir, uri: (0, URI_1.createURI)(types_1.LocalFileSystemID, path) }));
|
|
62
65
|
this.remoteWatcher = new RemoteWatcher_1.default(sendDirContent, uri => this.onError({ type: types_1.FailureType.MissingDir, uri }));
|
|
63
66
|
const emitFile = emitter('file');
|
|
@@ -69,18 +72,13 @@ class App {
|
|
|
69
72
|
const versions = await commands_1.commands.checkVer();
|
|
70
73
|
if (versions) {
|
|
71
74
|
this.onError({
|
|
72
|
-
type: types_1.FailureType.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<p>Current version: <em>${versions[0]}</em> → New version: <em>${versions[1]}</em></p>
|
|
76
|
-
<p>To update, run:</p>
|
|
77
|
-
<pre> npm update -g filefive</pre>
|
|
78
|
-
`
|
|
75
|
+
type: types_1.FailureType.UpdateAvailable,
|
|
76
|
+
currVer: versions[0],
|
|
77
|
+
newVer: versions[1]
|
|
79
78
|
});
|
|
80
79
|
}
|
|
81
80
|
};
|
|
82
|
-
setTimeout(notifyNewVer,
|
|
83
|
-
setInterval(notifyNewVer, 86400000);
|
|
81
|
+
setTimeout(notifyNewVer, 3000);
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
exports.default = App;
|
package/dist/Connection.js
CHANGED
|
@@ -59,6 +59,9 @@ class default_1 {
|
|
|
59
59
|
ftp: Ftp_1.ATTRIBUTES,
|
|
60
60
|
s3: S3_1.ATTRIBUTES
|
|
61
61
|
}[protocol];
|
|
62
|
+
if (!attrs) {
|
|
63
|
+
throw new Error(`Invalid protocol ${protocol}`);
|
|
64
|
+
}
|
|
62
65
|
if (this.shared.inc(id)) {
|
|
63
66
|
return attrs;
|
|
64
67
|
}
|
|
@@ -97,8 +100,8 @@ class default_1 {
|
|
|
97
100
|
}
|
|
98
101
|
return new Promise((resolve) => {
|
|
99
102
|
const onRelease = (poolId) => {
|
|
100
|
-
const
|
|
101
|
-
resolve([fs, () => this.release(id, poolId)]);
|
|
103
|
+
const pool = this.pools.get(id)?.get(poolId);
|
|
104
|
+
pool && resolve([pool.fs, () => this.release(id, poolId)]);
|
|
102
105
|
};
|
|
103
106
|
id in this.pending ? this.pending[id].push(onRelease) : (this.pending[id] = [onRelease]);
|
|
104
107
|
});
|
|
@@ -110,7 +113,7 @@ class default_1 {
|
|
|
110
113
|
if (conn) {
|
|
111
114
|
return conn;
|
|
112
115
|
}
|
|
113
|
-
if (pool.size < this.getLimit(id)) {
|
|
116
|
+
if (pool && pool.size < this.getLimit(id)) {
|
|
114
117
|
return new Promise((resolve) => {
|
|
115
118
|
const connect = async () => {
|
|
116
119
|
const conn = this.getIdle(id);
|
|
@@ -173,19 +176,17 @@ class default_1 {
|
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
static getLimit(id) {
|
|
176
|
-
return this.limits.has(id) ? this.limits.get(id) : 1024; // for vsftpd max_per_ip in /etc/vsftpd.conf
|
|
179
|
+
return this.limits.has(id) ? (this.limits.get(id) || 1024) : 1024; // for vsftpd max_per_ip in /etc/vsftpd.conf
|
|
177
180
|
}
|
|
178
181
|
static async create(id, onClose = () => { }) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
return this.createFS(id, onClose);
|
|
182
|
+
const fs = await this.createFS(id, onClose);
|
|
183
|
+
return options_1.default.log ? new log_1.LogFS(id, fs) : fs;
|
|
183
184
|
}
|
|
184
185
|
static async createFS(id, onClose) {
|
|
185
186
|
const { scheme, user, host, port } = (0, URI_1.parseURI)(id);
|
|
186
187
|
if (!this.credentials.has(id)) {
|
|
187
188
|
onClose?.();
|
|
188
|
-
|
|
189
|
+
throw new Error(`No credentials for ${id}`);
|
|
189
190
|
}
|
|
190
191
|
const [authType, credential] = this.credentials.get(id);
|
|
191
192
|
const protocol = this.protocols.get(id);
|
|
@@ -194,14 +195,14 @@ class default_1 {
|
|
|
194
195
|
}
|
|
195
196
|
switch (protocol) {
|
|
196
197
|
case 'sftp': {
|
|
197
|
-
return new SFtp_1.default(host, user, authType == 'password' ? credential : '', authType == 'key' ? credential : null, port, error => log_1.default.error('SFTP error:', error), onClose);
|
|
198
|
+
return new SFtp_1.default(host, user, authType == 'password' ? credential : '', authType == 'key' ? credential : null, port || 22, error => log_1.default.error('SFTP error:', error), onClose);
|
|
198
199
|
}
|
|
199
200
|
case 'ftp': {
|
|
200
|
-
return new Ftp_1.default(host, user, credential, port, error => log_1.default.error('FTP error:', error), onClose);
|
|
201
|
+
return new Ftp_1.default(host, user, credential, port || 21, error => log_1.default.error('FTP error:', error), onClose);
|
|
201
202
|
}
|
|
202
203
|
case 's3': {
|
|
203
204
|
return new S3_1.default(`${scheme}:\\${host}`, user, credential, 'us-east-1', // TODO
|
|
204
|
-
port, error => log_1.default.error('S3 error:', error), onClose);
|
|
205
|
+
port || 443, error => log_1.default.error('S3 error:', error), onClose);
|
|
205
206
|
}
|
|
206
207
|
default:
|
|
207
208
|
throw new Error(`Unsupported protocol ${protocol}`);
|
package/dist/FileWatcher.js
CHANGED
|
@@ -21,7 +21,7 @@ class default_1 {
|
|
|
21
21
|
for await (const { eventType, filename } of (0, Local_1.watchChanges)(path, ac)) {
|
|
22
22
|
let newPath = path;
|
|
23
23
|
if (eventType == "rename" && (0, posix_1.basename)(path) !== filename) {
|
|
24
|
-
newPath = (0, posix_1.join)((0, posix_1.dirname)(path), filename);
|
|
24
|
+
newPath = (0, posix_1.join)((0, posix_1.dirname)(path), filename ?? '');
|
|
25
25
|
this.watched.renameKey(path, newPath);
|
|
26
26
|
}
|
|
27
27
|
try {
|
package/dist/Local.js
CHANGED
|
@@ -17,15 +17,18 @@ exports.touch = touch;
|
|
|
17
17
|
exports.read = read;
|
|
18
18
|
exports.readInBuffer = readInBuffer;
|
|
19
19
|
exports.write = write;
|
|
20
|
+
exports.concurrentWrite = concurrentWrite;
|
|
20
21
|
exports.watch = watch;
|
|
21
22
|
exports.watchChanges = watchChanges;
|
|
22
23
|
const node_os_1 = require("node:os");
|
|
23
24
|
const posix_1 = require("node:path/posix");
|
|
24
25
|
const node_path_1 = __importDefault(require("node:path"));
|
|
25
26
|
const node_fs_1 = require("node:fs");
|
|
27
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
26
28
|
const promises_1 = require("node:fs/promises");
|
|
27
29
|
const os_1 = require("./utils/os");
|
|
28
30
|
const win_1 = require("./win");
|
|
31
|
+
const limit = (0, p_limit_1.default)(1);
|
|
29
32
|
function isWin() {
|
|
30
33
|
return (0, node_os_1.platform)() === 'win32';
|
|
31
34
|
}
|
|
@@ -43,7 +46,7 @@ function stat(path) {
|
|
|
43
46
|
const actualPath = osify(path);
|
|
44
47
|
try {
|
|
45
48
|
let stat = (0, node_fs_1.lstatSync)(actualPath);
|
|
46
|
-
let target;
|
|
49
|
+
let target = '';
|
|
47
50
|
if (stat.isSymbolicLink()) {
|
|
48
51
|
target = (0, node_fs_1.readlinkSync)(actualPath);
|
|
49
52
|
if (!(0, posix_1.isAbsolute)(target)) {
|
|
@@ -68,9 +71,9 @@ function stat(path) {
|
|
|
68
71
|
function list(dir) {
|
|
69
72
|
let actialDir = osify(dir);
|
|
70
73
|
if (actialDir == '\\') {
|
|
71
|
-
return (0, win_1.getDrives)().map(path => stat(unosify(path))).filter(f => f);
|
|
74
|
+
return (0, win_1.getDrives)().map(path => stat(unosify(path))).filter(f => f != null);
|
|
72
75
|
}
|
|
73
|
-
return (0, node_fs_1.readdirSync)(actialDir).map(name => stat((0, posix_1.join)(dir, name))).filter(f => f);
|
|
76
|
+
return (0, node_fs_1.readdirSync)(actialDir).map(name => stat((0, posix_1.join)(dir, name))).filter(f => f != null);
|
|
74
77
|
}
|
|
75
78
|
async function mkDirRecursive(path) {
|
|
76
79
|
return (0, promises_1.mkdir)(osify(path), { recursive: true });
|
|
@@ -116,8 +119,11 @@ async function readInBuffer(path) {
|
|
|
116
119
|
async function write(path, data) {
|
|
117
120
|
return (0, promises_1.writeFile)(osify(path), data);
|
|
118
121
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
122
|
+
async function concurrentWrite(path, data) {
|
|
123
|
+
return limit(() => write(path, data));
|
|
124
|
+
}
|
|
125
|
+
function watch(path, listener, onError, options = null) {
|
|
126
|
+
const watcher = (0, node_fs_1.watch)(osify(path), options, listener);
|
|
121
127
|
watcher.on('error', onError);
|
|
122
128
|
return () => watcher.close();
|
|
123
129
|
}
|
package/dist/LocalWatcher.js
CHANGED
|
@@ -16,15 +16,18 @@ class default_1 {
|
|
|
16
16
|
try {
|
|
17
17
|
try {
|
|
18
18
|
this.watched.inc(dir) || this.watched.set(dir, (0, Local_1.watch)(dir, (event, target) => {
|
|
19
|
+
if (target == '.git') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
19
22
|
if (event == 'rename') {
|
|
20
|
-
const child = (0, posix_1.join)(dir, target);
|
|
23
|
+
const child = (0, posix_1.join)(dir, target ?? '');
|
|
21
24
|
if (this.watched.has(child) && !(0, Local_1.stat)(child)) {
|
|
22
25
|
this.watched.del(child);
|
|
23
26
|
this.onMissing(child);
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
try {
|
|
27
|
-
this.listener(dir, (0, Local_1.list)(dir), event, target);
|
|
30
|
+
this.listener(dir, (0, Local_1.list)(dir), event, target ?? '');
|
|
28
31
|
}
|
|
29
32
|
catch (e) {
|
|
30
33
|
this.watched.del(dir);
|
package/dist/Password.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const Local_1 = require("./Local");
|
|
4
3
|
const posix_1 = require("node:path/posix");
|
|
4
|
+
const Local_1 = require("./Local");
|
|
5
5
|
class Passwords {
|
|
6
6
|
static async load(dir, onMiss) {
|
|
7
7
|
this.saveFile = (0, posix_1.join)(dir, 'credentials.json');
|
|
@@ -38,7 +38,7 @@ class Passwords {
|
|
|
38
38
|
this.pending.delete(id);
|
|
39
39
|
}
|
|
40
40
|
static dump() {
|
|
41
|
-
(0, Local_1.
|
|
41
|
+
(0, Local_1.concurrentWrite)(this.saveFile, JSON.stringify(Array.from(this.store.entries())
|
|
42
42
|
.filter(([, [, save]]) => save)
|
|
43
43
|
.map(([id, [password,]]) => [id, password])));
|
|
44
44
|
}
|
package/dist/Queue.js
CHANGED
|
@@ -42,7 +42,9 @@ class Queue {
|
|
|
42
42
|
resolve(action, forAll = false) {
|
|
43
43
|
forAll && (this.action = action);
|
|
44
44
|
const drained = !this.queue.length;
|
|
45
|
-
this.queue.push(...this.pending
|
|
45
|
+
this.queue.push(...this.pending
|
|
46
|
+
.splice(0, forAll ? this.pending.length : 1)
|
|
47
|
+
.map(f => ({ from: f.src, to: f.dest.path, action })));
|
|
46
48
|
drained && this.queue$.next(this.queue.shift());
|
|
47
49
|
if (this.pending.length) {
|
|
48
50
|
const { src, dest } = this.pending[0];
|
|
@@ -88,9 +90,10 @@ class Queue {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
const [fs, close] = await Connection_1.default.transmit(this.connId);
|
|
91
|
-
existing
|
|
92
|
-
this.applyAction(a, from, existing, transmit.bind(this, fs))
|
|
93
|
-
|
|
93
|
+
existing
|
|
94
|
+
? this.applyAction(a, from, existing, transmit.bind(this, fs))
|
|
95
|
+
.then(close)
|
|
96
|
+
: transmit(fs, from, to).then(close);
|
|
94
97
|
this.next();
|
|
95
98
|
});
|
|
96
99
|
this.next();
|
|
@@ -143,9 +146,10 @@ class Queue {
|
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
const [fs, close] = await Connection_1.default.transmit(this.connId);
|
|
146
|
-
existing
|
|
147
|
-
this.applyAction(a, from, existing, transmit.bind(this, fs))
|
|
148
|
-
|
|
149
|
+
existing
|
|
150
|
+
? this.applyAction(a, from, existing, transmit.bind(this, fs))
|
|
151
|
+
.then(close)
|
|
152
|
+
: transmit(fs, from, to).then(close);
|
|
149
153
|
this.next();
|
|
150
154
|
});
|
|
151
155
|
this.next();
|
|
@@ -213,6 +217,7 @@ class Queue {
|
|
|
213
217
|
case types_1.QueueActionType.Replace:
|
|
214
218
|
return transmit(from, to.path);
|
|
215
219
|
}
|
|
220
|
+
return Promise.resolve();
|
|
216
221
|
}
|
|
217
222
|
putOnHold(src, dest) {
|
|
218
223
|
this.pending.push({ src, dest });
|
package/dist/RemoteFiles.js
CHANGED
|
@@ -20,8 +20,13 @@ async function open(file, onLoad) {
|
|
|
20
20
|
try {
|
|
21
21
|
const tmpDir = await (0, promises_1.mkdtemp)((0, posix_1.join)((0, node_os_1.tmpdir)(), 'f5-'));
|
|
22
22
|
const tmpName = (0, posix_1.join)(tmpDir, (0, posix_1.basename)(path));
|
|
23
|
-
return commands_1.commands.copy([file], (0, URI_1.createURI)(types_1.LocalFileSystemID, tmpDir), false,
|
|
24
|
-
files.set(tmpName, {
|
|
23
|
+
return commands_1.commands.copy([file], (0, URI_1.createURI)(types_1.LocalFileSystemID, tmpDir), false, undefined, undefined, undefined, () => {
|
|
24
|
+
files.set(tmpName, {
|
|
25
|
+
file,
|
|
26
|
+
deletion: resetDeletion(tmpName, undefined),
|
|
27
|
+
sending: false,
|
|
28
|
+
changed: false
|
|
29
|
+
});
|
|
25
30
|
watcher.watch(tmpName);
|
|
26
31
|
onLoad(tmpName);
|
|
27
32
|
});
|
package/dist/commands/clear.js
CHANGED
|
@@ -11,7 +11,11 @@ const Connection_1 = __importDefault(require("../Connection"));
|
|
|
11
11
|
const App_1 = __importDefault(require("../App"));
|
|
12
12
|
async function default_1(file) {
|
|
13
13
|
const { id, path } = (0, URI_1.parseURI)(file);
|
|
14
|
-
|
|
14
|
+
const conn = Connection_1.default.get(id);
|
|
15
|
+
if (!conn) {
|
|
16
|
+
throw new Error(`Invalid connection id ${id}`);
|
|
17
|
+
}
|
|
18
|
+
await conn.write(path, '');
|
|
15
19
|
if (id != types_1.LocalFileSystemID) {
|
|
16
20
|
App_1.default.remoteWatcher.refresh((0, URI_1.createURI)(id, (0, posix_1.dirname)(path)));
|
|
17
21
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -13,24 +13,25 @@ const URI_1 = require("../utils/URI");
|
|
|
13
13
|
const Connection_1 = __importDefault(require("../Connection"));
|
|
14
14
|
const Password_1 = __importDefault(require("../Password"));
|
|
15
15
|
const Session_1 = __importDefault(require("../Session"));
|
|
16
|
+
const events_1 = require("../events");
|
|
16
17
|
const ramda_1 = require("ramda");
|
|
17
18
|
function explorerSettings(attributes, config) {
|
|
18
19
|
return {
|
|
19
20
|
columns: config ? [
|
|
20
|
-
...config.columns
|
|
21
|
+
...(config.columns ?? [])
|
|
21
22
|
.map(({ name, width }) => {
|
|
22
23
|
const attribute = attributes.find((0, ramda_1.whereEq)({ name }));
|
|
23
24
|
return attribute ? { ...attribute, visible: true, width } : null;
|
|
24
25
|
})
|
|
25
26
|
.filter(ramda_1.isNotNil),
|
|
26
27
|
...attributes
|
|
27
|
-
.filter(({ name }) => !config.columns.find(c => name == c.name))
|
|
28
|
+
.filter(({ name }) => !(config.columns ?? []).find(c => name == c.name))
|
|
28
29
|
.map(a => ({ ...a, visible: false, width: 250 }))
|
|
29
30
|
] : attributes.map(a => ({ ...a, visible: true, width: 250 })),
|
|
30
31
|
...({
|
|
31
32
|
sort: config?.sort ?? ['name', types_1.SortOrder.Asc],
|
|
32
33
|
history: config?.history ?? [],
|
|
33
|
-
filter: config?.filter
|
|
34
|
+
filter: config?.filter
|
|
34
35
|
})
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -70,7 +71,11 @@ async function default_1(file, onError) {
|
|
|
70
71
|
}
|
|
71
72
|
try {
|
|
72
73
|
const attributes = await Connection_1.default.open(config.scheme, config.user, config.host, config.port, password, config.privatekey);
|
|
73
|
-
const
|
|
74
|
+
const connection = Connection_1.default.get(id);
|
|
75
|
+
if (!connection) {
|
|
76
|
+
throw new Error(`Invalid connection: ${id}`);
|
|
77
|
+
}
|
|
78
|
+
const pwd = await connection.pwd();
|
|
74
79
|
const settings = {
|
|
75
80
|
name: (0, path_1.parse)(file).name,
|
|
76
81
|
attributes,
|
|
@@ -82,9 +87,14 @@ async function default_1(file, onError) {
|
|
|
82
87
|
local: config.path?.local,
|
|
83
88
|
remote: config.path?.remote ?? pwd
|
|
84
89
|
},
|
|
85
|
-
sync: config.sync ?? null
|
|
90
|
+
sync: config.sync ?? null,
|
|
91
|
+
mirrors: config.mirrors
|
|
86
92
|
};
|
|
87
|
-
|
|
93
|
+
const sid = Session_1.default.create();
|
|
94
|
+
events_1.event$.next({
|
|
95
|
+
type: events_1.SystemEventType.Connect, id, sid, settings, file
|
|
96
|
+
});
|
|
97
|
+
return { id, sid, settings };
|
|
88
98
|
}
|
|
89
99
|
catch (e) {
|
|
90
100
|
Password_1.default.delete(id, false);
|
package/dist/commands/copy.js
CHANGED
|
@@ -43,7 +43,7 @@ function default_1(src, dest, move, filter, root, sid, onComplete = () => { }) {
|
|
|
43
43
|
if (fromId == toId) {
|
|
44
44
|
queueType = move ? types_1.QueueType.Move : types_1.QueueType.Copy;
|
|
45
45
|
connection = fromId;
|
|
46
|
-
queue = new Copy_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict
|
|
46
|
+
queue = new Copy_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict, error => {
|
|
47
47
|
App_1.default.onError({
|
|
48
48
|
type: types_1.FailureType.RemoteError,
|
|
49
49
|
id: fromId,
|
|
@@ -55,8 +55,8 @@ function default_1(src, dest, move, filter, root, sid, onComplete = () => { }) {
|
|
|
55
55
|
queueType = (0, URI_1.isLocal)(dest) ? types_1.QueueType.Download : types_1.QueueType.Upload;
|
|
56
56
|
connection = queueType == types_1.QueueType.Download ? fromId : toId;
|
|
57
57
|
queue = queueType == types_1.QueueType.Download ?
|
|
58
|
-
new Download_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict
|
|
59
|
-
new Upload_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict
|
|
58
|
+
new Download_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict, error => App_1.default.onError(error), onFinish) :
|
|
59
|
+
new Upload_1.default(connection, from, toPath, filter, root, state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), onConflict, error => App_1.default.onError(error), onFinish, App_1.default.remoteWatcher);
|
|
60
60
|
}
|
|
61
61
|
Queue_1.queues.set(id, queue);
|
|
62
62
|
setTimeout(() => {
|
|
@@ -6,7 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.default = default_1;
|
|
7
7
|
const Session_1 = __importDefault(require("../Session"));
|
|
8
8
|
const Connection_1 = __importDefault(require("../Connection"));
|
|
9
|
+
const events_1 = require("../events");
|
|
9
10
|
function default_1(id, sid) {
|
|
11
|
+
events_1.event$.next({ type: events_1.SystemEventType.Disconnect, id, sid });
|
|
10
12
|
Session_1.default.remove(sid);
|
|
11
13
|
Connection_1.default.close(id);
|
|
12
14
|
}
|
package/dist/commands/mkdir.js
CHANGED
|
@@ -20,6 +20,9 @@ async function default_1(name, parent) {
|
|
|
20
20
|
else {
|
|
21
21
|
const parts = (0, path_1.split)(name);
|
|
22
22
|
const conn = Connection_1.default.get(id);
|
|
23
|
+
if (!conn) {
|
|
24
|
+
throw new Error(`Invalid connection id ${id}`);
|
|
25
|
+
}
|
|
23
26
|
for (let i = 0; i < parts.length; i++) {
|
|
24
27
|
path = (0, posix_1.join)(path, parts[i]);
|
|
25
28
|
try {
|
|
@@ -27,14 +30,18 @@ async function default_1(name, parent) {
|
|
|
27
30
|
}
|
|
28
31
|
catch (e) {
|
|
29
32
|
if (i == parts.length - 1) {
|
|
30
|
-
throw e
|
|
33
|
+
// throw e SKIP
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
catch (
|
|
37
|
-
App_1.default.onError({
|
|
39
|
+
catch (e) {
|
|
40
|
+
App_1.default.onError({
|
|
41
|
+
type: types_1.FailureType.RemoteError,
|
|
42
|
+
id,
|
|
43
|
+
message: (e instanceof Error) ? e.message : String(e)
|
|
44
|
+
});
|
|
38
45
|
}
|
|
39
46
|
if (!(0, URI_1.isLocal)(parent)) {
|
|
40
47
|
App_1.default.remoteWatcher.refresh(parent);
|
package/dist/commands/remove.js
CHANGED
|
@@ -38,7 +38,7 @@ async function default_1(files, connPath, immediately = false) {
|
|
|
38
38
|
const queue = new Remove_1.default(connId, files.map((0, ramda_1.pipe)(URI_1.parseURI, (0, ramda_1.prop)('path'))), state => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Update, state }), error => {
|
|
39
39
|
App_1.default.onError({
|
|
40
40
|
type: types_1.FailureType.RemoteError,
|
|
41
|
-
id:
|
|
41
|
+
id: connId,
|
|
42
42
|
message: error.message ?? String(error)
|
|
43
43
|
});
|
|
44
44
|
}, () => {
|
package/dist/commands/rename.js
CHANGED
|
@@ -14,7 +14,7 @@ async function default_1(uri, name) {
|
|
|
14
14
|
if (path == to) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
await Connection_1.default.get(id)
|
|
17
|
+
await Connection_1.default.get(id)?.rename(path, to);
|
|
18
18
|
if (!(0, URI_1.isLocal)(uri)) {
|
|
19
19
|
App_1.default.remoteWatcher.refresh((0, URI_1.createURI)(id, (0, posix_1.dirname)(path)));
|
|
20
20
|
}
|
|
@@ -42,6 +42,9 @@ async function default_1(path, settings) {
|
|
|
42
42
|
config.path = settings.path;
|
|
43
43
|
}
|
|
44
44
|
config.sync = settings.sync;
|
|
45
|
+
config.mirrors = settings.mirrors
|
|
46
|
+
? settings.mirrors
|
|
47
|
+
: [];
|
|
45
48
|
}
|
|
46
49
|
if (!config) {
|
|
47
50
|
throw new Error(`Save invalid config into ${path}`);
|
package/dist/commands/write.js
CHANGED
|
@@ -10,7 +10,7 @@ const posix_1 = require("node:path/posix");
|
|
|
10
10
|
const App_1 = __importDefault(require("../App"));
|
|
11
11
|
async function default_1(file, content) {
|
|
12
12
|
const { id, path } = (0, URI_1.parseURI)(file);
|
|
13
|
-
await Connection_1.default.get(id)
|
|
13
|
+
await Connection_1.default.get(id)?.write(path, content);
|
|
14
14
|
if (!(0, URI_1.isLocal)(file)) {
|
|
15
15
|
App_1.default.remoteWatcher.refresh((0, URI_1.createURI)(id, (0, posix_1.dirname)(path)));
|
|
16
16
|
}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.event$ = exports.SystemEventType = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
var SystemEventType;
|
|
6
|
+
(function (SystemEventType) {
|
|
7
|
+
SystemEventType["Connect"] = "connect";
|
|
8
|
+
SystemEventType["Disconnect"] = "disconnect";
|
|
9
|
+
})(SystemEventType || (exports.SystemEventType = SystemEventType = {}));
|
|
10
|
+
exports.event$ = new rxjs_1.Subject();
|
package/dist/fs/S3.js
CHANGED
|
@@ -51,14 +51,14 @@ class S3 extends FileSystem_1.FileSystem {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
catch (e) {
|
|
54
|
-
this.onError(e);
|
|
54
|
+
this.onError(e instanceof Error ? e : new Error(String(e)));
|
|
55
55
|
}
|
|
56
56
|
return Promise.resolve(true);
|
|
57
57
|
}
|
|
58
58
|
close() {
|
|
59
59
|
this.connection && this.onClose();
|
|
60
60
|
this.connection?.destroy();
|
|
61
|
-
this.connection =
|
|
61
|
+
this.connection = undefined;
|
|
62
62
|
}
|
|
63
63
|
opened() {
|
|
64
64
|
return this.connection != null;
|
|
@@ -79,7 +79,7 @@ class S3 extends FileSystem_1.FileSystem {
|
|
|
79
79
|
Prefix: target
|
|
80
80
|
}));
|
|
81
81
|
return [
|
|
82
|
-
...(output.CommonPrefixes ?? []).map(prefix => ({
|
|
82
|
+
...(output.CommonPrefixes ?? []).filter(p => !!p.Prefix).map(prefix => ({
|
|
83
83
|
path: (0, posix_1.resolve)('/', parts[0], prefix.Prefix),
|
|
84
84
|
name: (0, posix_1.basename)(prefix.Prefix),
|
|
85
85
|
dir: true,
|
|
@@ -87,13 +87,14 @@ class S3 extends FileSystem_1.FileSystem {
|
|
|
87
87
|
modified: new Date()
|
|
88
88
|
})),
|
|
89
89
|
...(output.Contents ?? [])
|
|
90
|
+
.filter(item => 'Key' in item)
|
|
90
91
|
.filter(({ Key }) => Key != target)
|
|
91
92
|
.map(item => ({
|
|
92
93
|
path: (0, posix_1.resolve)('/', parts[0], item.Key),
|
|
93
94
|
name: (0, posix_1.basename)(item.Key),
|
|
94
95
|
dir: false,
|
|
95
|
-
size: item.Size,
|
|
96
|
-
modified: new Date(item.LastModified)
|
|
96
|
+
size: item.Size || 0,
|
|
97
|
+
modified: 'LastModified' in item ? new Date(item.LastModified) : new Date()
|
|
97
98
|
}))
|
|
98
99
|
];
|
|
99
100
|
}
|
|
@@ -170,7 +171,7 @@ class S3 extends FileSystem_1.FileSystem {
|
|
|
170
171
|
}));
|
|
171
172
|
}
|
|
172
173
|
catch (e) {
|
|
173
|
-
this.onError(e);
|
|
174
|
+
this.onError(e instanceof Error ? e : new Error(String(e)));
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
async rename(from, to) {
|
|
@@ -208,7 +209,7 @@ class S3 extends FileSystem_1.FileSystem {
|
|
|
208
209
|
}));
|
|
209
210
|
}
|
|
210
211
|
catch (e) {
|
|
211
|
-
this.onError(e);
|
|
212
|
+
this.onError(e instanceof Error ? e : new Error(String(e)));
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
async listBuckets() {
|
package/dist/fs/SFtp.js
CHANGED
|
@@ -263,9 +263,11 @@ class SFtp extends FileSystem_1.FileSystem {
|
|
|
263
263
|
[STATUS_CODE.CONNECTION_LOST]: 'Connection lost',
|
|
264
264
|
[STATUS_CODE.OP_UNSUPPORTED]: 'Operation unsupported',
|
|
265
265
|
};
|
|
266
|
-
let msg = e.message || (('code' in e)
|
|
267
|
-
(e['code'] in STATUS_CODE_STR
|
|
268
|
-
|
|
266
|
+
let msg = e.message || ((e && 'code' in e && typeof e['code'] === 'number')
|
|
267
|
+
? (e['code'] in STATUS_CODE_STR
|
|
268
|
+
? STATUS_CODE_STR[e['code']]
|
|
269
|
+
: `Code: ${e['code']}`)
|
|
270
|
+
: 'Unknown error');
|
|
269
271
|
return msg;
|
|
270
272
|
}
|
|
271
273
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ 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.inputOptions = void 0;
|
|
7
8
|
const express_1 = __importDefault(require("express"));
|
|
8
9
|
const multer_1 = __importDefault(require("multer"));
|
|
9
10
|
const posix_1 = require("node:path/posix");
|
|
@@ -26,9 +27,9 @@ commander_1.program
|
|
|
26
27
|
.option('-p, --port <number>', 'port number', '3113')
|
|
27
28
|
.option('--log', 'prints the log information');
|
|
28
29
|
commander_1.program.parse();
|
|
29
|
-
|
|
30
|
-
const port = inputOptions.port ?? 3113;
|
|
31
|
-
options_1.default.log = inputOptions.log ?? process.env.NODE_ENV == 'development';
|
|
30
|
+
exports.inputOptions = commander_1.program.opts();
|
|
31
|
+
const port = exports.inputOptions.port ?? 3113;
|
|
32
|
+
options_1.default.log = exports.inputOptions.log ?? process.env.NODE_ENV == 'development';
|
|
32
33
|
const app = (0, express_1.default)();
|
|
33
34
|
app.use(function (req, res, next) {
|
|
34
35
|
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
@@ -52,7 +53,7 @@ const handle = async (name, handler) => {
|
|
|
52
53
|
catch (e) {
|
|
53
54
|
res.status(400);
|
|
54
55
|
process.env.NODE_ENV == 'development' && console.error('API error: ', e);
|
|
55
|
-
res.json({ message: (typeof e == 'object' && 'message' in e) ? e.message : String(e) });
|
|
56
|
+
res.json({ message: (e !== null && typeof e == 'object' && 'message' in e) ? e.message : String(e) });
|
|
56
57
|
}
|
|
57
58
|
});
|
|
58
59
|
};
|
|
@@ -66,7 +67,7 @@ app.post('/api/upload', upload.array('files'), async function (req, res) {
|
|
|
66
67
|
await (0, Local_1.move)(path, fnm);
|
|
67
68
|
src.push(fnm);
|
|
68
69
|
}
|
|
69
|
-
commands_1.commands.copy(src.map(path => (0, URI_1.createURI)(types_1.LocalFileSystemID, path)), req.body['to'], true,
|
|
70
|
+
commands_1.commands.copy(src.map(path => (0, URI_1.createURI)(types_1.LocalFileSystemID, path)), req.body['to'], true, undefined, undefined, undefined, () => src.forEach(path => (0, Local_1.del)(path)));
|
|
70
71
|
}
|
|
71
72
|
res.json(true);
|
|
72
73
|
});
|