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.
Files changed (103) hide show
  1. package/README.md +8 -0
  2. package/dist/App.js +9 -11
  3. package/dist/Connection.js +13 -12
  4. package/dist/FileWatcher.js +1 -1
  5. package/dist/Local.js +11 -5
  6. package/dist/LocalWatcher.js +5 -2
  7. package/dist/Password.js +2 -2
  8. package/dist/Queue.js +12 -7
  9. package/dist/RemoteFiles.js +7 -2
  10. package/dist/commands/clear.js +5 -1
  11. package/dist/commands/connect.js +16 -6
  12. package/dist/commands/copy.js +3 -3
  13. package/dist/commands/disconnect.js +2 -0
  14. package/dist/commands/duplicate.js +1 -1
  15. package/dist/commands/mkdir.js +10 -3
  16. package/dist/commands/remove.js +1 -1
  17. package/dist/commands/rename.js +1 -1
  18. package/dist/commands/saveConnection.js +3 -0
  19. package/dist/commands/write.js +1 -1
  20. package/dist/events.js +10 -0
  21. package/dist/fs/S3.js +8 -7
  22. package/dist/fs/SFtp.js +5 -3
  23. package/dist/index.js +6 -5
  24. package/dist/log.js +5 -2
  25. package/dist/plugins/mirror/commands.js +185 -0
  26. package/dist/plugins/mirror/sessions.js +26 -0
  27. package/dist/plugins/mirror/watchers.js +4 -0
  28. package/dist/public/assets/android-chrome-144x144.png +0 -0
  29. package/dist/public/assets/android-chrome-192x192.png +0 -0
  30. package/dist/public/assets/android-chrome-256x256.png +0 -0
  31. package/dist/public/assets/android-chrome-36x36.png +0 -0
  32. package/dist/public/assets/android-chrome-384x384.png +0 -0
  33. package/dist/public/assets/android-chrome-48x48.png +0 -0
  34. package/dist/public/assets/android-chrome-512x512.png +0 -0
  35. package/dist/public/assets/android-chrome-72x72.png +0 -0
  36. package/dist/public/assets/android-chrome-96x96.png +0 -0
  37. package/dist/public/assets/apple-touch-icon-1024x1024.png +0 -0
  38. package/dist/public/assets/apple-touch-icon-114x114.png +0 -0
  39. package/dist/public/assets/apple-touch-icon-120x120.png +0 -0
  40. package/dist/public/assets/apple-touch-icon-144x144.png +0 -0
  41. package/dist/public/assets/apple-touch-icon-152x152.png +0 -0
  42. package/dist/public/assets/apple-touch-icon-167x167.png +0 -0
  43. package/dist/public/assets/apple-touch-icon-180x180.png +0 -0
  44. package/dist/public/assets/apple-touch-icon-57x57.png +0 -0
  45. package/dist/public/assets/apple-touch-icon-60x60.png +0 -0
  46. package/dist/public/assets/apple-touch-icon-72x72.png +0 -0
  47. package/dist/public/assets/apple-touch-icon-76x76.png +0 -0
  48. package/dist/public/assets/apple-touch-icon-precomposed.png +0 -0
  49. package/dist/public/assets/apple-touch-icon.png +0 -0
  50. package/dist/public/assets/apple-touch-startup-image-1125x2436.png +0 -0
  51. package/dist/public/assets/apple-touch-startup-image-1136x640.png +0 -0
  52. package/dist/public/assets/apple-touch-startup-image-1170x2532.png +0 -0
  53. package/dist/public/assets/apple-touch-startup-image-1179x2556.png +0 -0
  54. package/dist/public/assets/apple-touch-startup-image-1242x2208.png +0 -0
  55. package/dist/public/assets/apple-touch-startup-image-1242x2688.png +0 -0
  56. package/dist/public/assets/apple-touch-startup-image-1284x2778.png +0 -0
  57. package/dist/public/assets/apple-touch-startup-image-1290x2796.png +0 -0
  58. package/dist/public/assets/apple-touch-startup-image-1334x750.png +0 -0
  59. package/dist/public/assets/apple-touch-startup-image-1488x2266.png +0 -0
  60. package/dist/public/assets/apple-touch-startup-image-1536x2048.png +0 -0
  61. package/dist/public/assets/apple-touch-startup-image-1620x2160.png +0 -0
  62. package/dist/public/assets/apple-touch-startup-image-1640x2160.png +0 -0
  63. package/dist/public/assets/apple-touch-startup-image-1668x2224.png +0 -0
  64. package/dist/public/assets/apple-touch-startup-image-1668x2388.png +0 -0
  65. package/dist/public/assets/apple-touch-startup-image-1792x828.png +0 -0
  66. package/dist/public/assets/apple-touch-startup-image-2048x1536.png +0 -0
  67. package/dist/public/assets/apple-touch-startup-image-2048x2732.png +0 -0
  68. package/dist/public/assets/apple-touch-startup-image-2160x1620.png +0 -0
  69. package/dist/public/assets/apple-touch-startup-image-2160x1640.png +0 -0
  70. package/dist/public/assets/apple-touch-startup-image-2208x1242.png +0 -0
  71. package/dist/public/assets/apple-touch-startup-image-2224x1668.png +0 -0
  72. package/dist/public/assets/apple-touch-startup-image-2266x1488.png +0 -0
  73. package/dist/public/assets/apple-touch-startup-image-2388x1668.png +0 -0
  74. package/dist/public/assets/apple-touch-startup-image-2436x1125.png +0 -0
  75. package/dist/public/assets/apple-touch-startup-image-2532x1170.png +0 -0
  76. package/dist/public/assets/apple-touch-startup-image-2556x1179.png +0 -0
  77. package/dist/public/assets/apple-touch-startup-image-2688x1242.png +0 -0
  78. package/dist/public/assets/apple-touch-startup-image-2732x2048.png +0 -0
  79. package/dist/public/assets/apple-touch-startup-image-2778x1284.png +0 -0
  80. package/dist/public/assets/apple-touch-startup-image-2796x1290.png +0 -0
  81. package/dist/public/assets/apple-touch-startup-image-640x1136.png +0 -0
  82. package/dist/public/assets/apple-touch-startup-image-750x1334.png +0 -0
  83. package/dist/public/assets/apple-touch-startup-image-828x1792.png +0 -0
  84. package/dist/public/assets/favicon-16x16.png +0 -0
  85. package/dist/public/assets/favicon-32x32.png +0 -0
  86. package/dist/public/assets/favicon-48x48.png +0 -0
  87. package/dist/public/assets/mstile-144x144.png +0 -0
  88. package/dist/public/assets/mstile-150x150.png +0 -0
  89. package/dist/public/assets/mstile-310x150.png +0 -0
  90. package/dist/public/assets/mstile-310x310.png +0 -0
  91. package/dist/public/assets/mstile-70x70.png +0 -0
  92. package/dist/public/assets/yandex-browser-50x50.png +0 -0
  93. package/dist/public/index.js +2 -1
  94. package/dist/public/index.js.map +1 -0
  95. package/dist/queues/Duplicate.js +1 -1
  96. package/dist/queues/Queue.js +12 -14
  97. package/dist/queues/Remove.js +3 -1
  98. package/dist/transformers/git.js +54 -28
  99. package/dist/types.js +9 -1
  100. package/dist/utils/ReferenceCountMap.js +3 -3
  101. package/dist/utils/URI.js +5 -3
  102. package/dist/utils/filter.js +31 -0
  103. 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.Warning,
73
- message: `
74
- <p>A new version of the FileFive is available!</p>
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, 20000);
83
- setInterval(notifyNewVer, 86400000);
81
+ setTimeout(notifyNewVer, 3000);
84
82
  }
85
83
  }
86
84
  exports.default = App;
@@ -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 { fs } = this.pools.get(id).get(poolId);
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
- if (options_1.default.log) {
180
- return new log_1.LogFS(id, await this.createFS(id, onClose));
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
- return;
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}`);
@@ -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 watch(path, listener, onError) {
120
- const watcher = (0, node_fs_1.watch)(osify(path), listener);
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
  }
@@ -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.write)(this.saveFile, JSON.stringify(Array.from(this.store.entries())
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.splice(0, forAll ? this.pending.length : 1).map(f => ({ from: f.src, to: f.dest.path, action })));
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)).then(close) :
93
- transmit(fs, from, to).then(close);
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)).then(close) :
148
- transmit(fs, from, to).then(close);
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 });
@@ -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, null, null, null, () => {
24
- files.set(tmpName, { file, deletion: resetDeletion(tmpName, null), sending: false, changed: false });
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
  });
@@ -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
- await Connection_1.default.get(id).write(path, '');
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
  }
@@ -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 ?? null
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 pwd = await Connection_1.default.get(id).pwd();
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
- return { id, sid: Session_1.default.create(), settings };
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);
@@ -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.bind(queue), error => {
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.bind(queue), 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.bind(queue), error => App_1.default.onError(error), onFinish, App_1.default.remoteWatcher);
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
  }
@@ -23,7 +23,7 @@ function default_1(src, filter) {
23
23
  if (!targets.has(parent)) {
24
24
  targets.set(parent, []);
25
25
  }
26
- targets.get(parent).push(path);
26
+ targets.get(parent)?.push(path);
27
27
  });
28
28
  for (const [to, from] of targets) {
29
29
  const id = (0, uniqid_1.default)();
@@ -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 (error) {
37
- App_1.default.onError({ type: types_1.FailureType.RemoteError, id, message: 'message' in error ? error.message : String(error) });
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);
@@ -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: this.connId,
41
+ id: connId,
42
42
  message: error.message ?? String(error)
43
43
  });
44
44
  }, () => {
@@ -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).rename(path, to);
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}`);
@@ -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).write(path, content);
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 = null;
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 ? STATUS_CODE_STR[e['code']] : `Code: ${e['code']}`) :
268
- 'Unknown error');
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
- const inputOptions = commander_1.program.opts();
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, null, null, null, () => src.forEach(path => (0, Local_1.del)(path)));
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
  });