filefive 1.0.4 → 1.2.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 CHANGED
@@ -4,9 +4,11 @@
4
4
 
5
5
 
6
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.
7
+ FileFive is a free open-source SFTP/FTP client and file manager with intuitive and modern dual-panel interface, available on Mac, and Linux.
8
8
 
9
- It is installed a Node.js package and works in the browser.
9
+ It is installed as a Node.js package and uses the web browser as GUI.
10
+
11
+ FileFive has a unique set of features and may an alternative to FileZilla, Cyberduck, Transmit and ForkLift.
10
12
 
11
13
  <p align="center">
12
14
  <img src="https://github.com/miroshnikov/filefive/blob/main/screenshot.png" alt="FileFive" />
@@ -29,20 +31,23 @@ Options:
29
31
  -V, --version output the version number
30
32
  -p, --port <number> port number (default: "3113")
31
33
  --log prints the log information
32
- -h, --help display help for command
34
+ -h, --help display help
33
35
  ```
34
36
 
35
37
  ## Features
38
+ - Cross-platform, runs on Mac OS, Linux and any *nix with Node.js
36
39
  - 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
+ - Minimalistic and intuitive UI, mimicing the VSCode Explorer view
41
+ - Search/filter files using wildcards and JavaScript Regular Expressions
40
42
  - 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
43
+ - Connections/servers are plain files stored on your filesystem in the `~/.f5/connections` folder
44
+ - Easy to backup connections and settings in `~/.f5` folder, e.g. using Git or other VCS
45
+ - Drag & drop, copy & paste files support
46
+ - Use browser tabs to browse more than one server or transfer files simultaneously
47
+ - Utilize the built-in browser password manager to store passwords
48
+ - Open files and folders in default app or Visual Studio Code
49
+ - Theming: System preference, Light, Dark; a different color theme per connection
50
+ - Search On Type
46
51
 
47
52
  ## Feedbacks
48
53
  To support its development, [star FileFive on GitHub](https://github.com/miroshnikov/filefive/stargazers)!
package/dist/App.js CHANGED
@@ -34,15 +34,15 @@ class App {
34
34
  getapp: () => commands_1.commands.getSettings(settingsPath),
35
35
  saveapp: ({ settings }) => commands_1.commands.saveSettings(settingsPath, settings),
36
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),
37
+ login: ({ id, password, remember }) => Password_1.default.set(id, password, remember, false),
38
38
  disconnect: ({ id, sid }) => commands_1.commands.disconnect(id, sid),
39
39
  watch: ({ dir }) => commands_1.commands.watch(dir, this.localWatcher, this.remoteWatcher, this.fileWatcher),
40
40
  unwatch: ({ dir }) => commands_1.commands.unwatch(dir, this.localWatcher, this.remoteWatcher, this.fileWatcher),
41
41
  refresh: ({ dir }) => this.remoteWatcher.refresh(dir),
42
42
  copy: ({ src, dest, move, filter, sid }) => commands_1.commands.copy(src, dest, move, filter, sid),
43
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),
44
+ remove: ({ files }) => commands_1.commands.remove(files, connPath),
45
+ clear: ({ file }) => commands_1.commands.clear(file),
46
46
  open: ({ file }) => opener(file),
47
47
  mkdir: ({ name, parent }) => commands_1.commands.mkdir(name, parent),
48
48
  read: ({ file }) => commands_1.commands.read(file),
@@ -64,6 +64,20 @@ class App {
64
64
  this.fileWatcher = new FileWatcher_1.default(sendFileStat);
65
65
  const emitQueue = emitter('queue');
66
66
  this.onQueueUpdate = (id, event) => emitQueue({ id, event });
67
+ const notifyNewVer = async () => {
68
+ const versions = await commands_1.commands.checkVer();
69
+ if (versions) {
70
+ this.onError({
71
+ type: types_1.FailureType.Warning,
72
+ message: `
73
+ <p>A new version of the FileFive is available!</p>
74
+ <p>Current version: <em>${versions[0]}</em> → New version: <em>${versions[1]}</em></p>
75
+ `
76
+ });
77
+ }
78
+ };
79
+ setTimeout(notifyNewVer, 20000);
80
+ setInterval(notifyNewVer, 86400000);
67
81
  }
68
82
  }
69
83
  exports.default = App;
@@ -39,7 +39,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap"));
40
40
  const types_1 = require("./types");
41
41
  const URI_1 = require("./utils/URI");
42
- const Password_1 = __importDefault(require("./Password"));
43
42
  const uniqid_1 = __importDefault(require("./utils/uniqid"));
44
43
  const log_1 = __importStar(require("./log"));
45
44
  const Local_1 = __importDefault(require("./fs/Local"));
@@ -50,12 +49,13 @@ class default_1 {
50
49
  static initialize() {
51
50
  this.shared.set(types_1.LocalFileSystemID, new Local_1.default);
52
51
  }
53
- static async open(scheme, user, host, port) {
52
+ static async open(scheme, user, host, port, password) {
54
53
  const id = (0, URI_1.connectionID)(scheme, user, host, port);
55
54
  const attrs = (scheme == 'sftp') ? SFtp_1.ATTRIBUTES : Ftp_1.ATTRIBUTES;
56
55
  if (this.shared.inc(id)) {
57
56
  return attrs;
58
57
  }
58
+ this.credentials.set(id, password);
59
59
  const conn = await this.create(id, scheme, user, host, port);
60
60
  await conn.open();
61
61
  this.shared.set(id, conn);
@@ -161,9 +161,9 @@ class default_1 {
161
161
  }
162
162
  static async create(id, scheme, user, host, port, onClose = () => { }) {
163
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));
164
+ return new log_1.LogFS(id, await this.createFS(scheme, user, host, port, this.credentials.get(id), onClose));
165
165
  }
166
- return this.createFS(scheme, user, host, port, await Password_1.default.get(id), onClose);
166
+ return this.createFS(scheme, user, host, port, this.credentials.get(id), onClose);
167
167
  }
168
168
  static async createFS(scheme, user, host, port, password, onClose) {
169
169
  switch (scheme) {
@@ -185,9 +185,9 @@ class default_1 {
185
185
  default_1.numOfStartups = 0;
186
186
  default_1.maxStartups = 7; // for SFTP see MaxStartups in /etc/ssh/sshd_config
187
187
  default_1.shared = new ReferenceCountMap_1.default;
188
- // private static pools: Record<string, Record<string, {fs: FileSystem, idle: false|ReturnType<typeof setTimeout>}>> = {}
189
188
  default_1.pools = new Map();
190
189
  default_1.queue = [];
191
190
  default_1.pending = {};
192
191
  default_1.limits = new Map();
192
+ default_1.credentials = new Map();
193
193
  exports.default = default_1;
package/dist/Password.js CHANGED
@@ -8,19 +8,15 @@ class Passwords {
8
8
  this.resolve = onMiss;
9
9
  this.store = new Map(JSON.parse((await (0, promises_1.readFile)(this.saveFile)).toString()).map(([id, password]) => [id, [password, true]]));
10
10
  }
11
- static set(id, password, remember = false) {
11
+ static set(id, password, remember, save) {
12
12
  if (password === false) {
13
13
  this.pending.get(id)?.[1]();
14
14
  }
15
15
  else {
16
16
  this.pending.get(id)?.[0](password);
17
- remember && this.store.set(id, [password, false]);
17
+ remember && this.store.set(id, [password, save]);
18
18
  }
19
19
  }
20
- static save(id, password) {
21
- this.store.set(id, [password, true]);
22
- this.dump();
23
- }
24
20
  static async get(id, skipMissing = false) {
25
21
  if (this.store.has(id)) {
26
22
  return this.store.get(id)[0];
@@ -32,11 +28,11 @@ class Passwords {
32
28
  this.resolve(id);
33
29
  return p;
34
30
  }
35
- static delete(id, saved) {
31
+ static delete(id, save) {
36
32
  const found = this.store.get(id);
37
- if (found && found[1] == saved) {
33
+ if (found && (save || found[1] === save)) {
38
34
  this.store.delete(id);
39
- found[1] == true && this.dump();
35
+ found[1] === true && this.dump();
40
36
  }
41
37
  this.pending.delete(id);
42
38
  }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = default_1;
7
+ const package_json_1 = __importDefault(require("../../package.json"));
8
+ async function default_1() {
9
+ // https://github.com/npm/registry
10
+ const server = await fetch('https://registry.npmjs.com/filefive/latest').then(resp => resp.json());
11
+ if (typeof server === 'object' && 'version' in server) {
12
+ return server.version == package_json_1.default.version ? null : [package_json_1.default.version, server.version];
13
+ }
14
+ return null;
15
+ }
@@ -9,11 +9,7 @@ const types_1 = require("../types");
9
9
  const URI_1 = require("../utils/URI");
10
10
  const Connection_1 = __importDefault(require("../Connection"));
11
11
  const App_1 = __importDefault(require("../App"));
12
- async function default_1(file, force) {
13
- if (!force) {
14
- App_1.default.onError({ type: types_1.FailureType.ConfirmClear, file });
15
- return;
16
- }
12
+ async function default_1(file) {
17
13
  const { id, path } = (0, URI_1.parseURI)(file);
18
14
  await Connection_1.default.get(id).write(path, '');
19
15
  if (id != types_1.LocalFileSystemID) {
@@ -55,14 +55,18 @@ async function default_1(file, onError) {
55
55
  throw new Error(`Invalid connection file ${file}`);
56
56
  }
57
57
  const id = (0, URI_1.connectionID)(config.scheme, config.user, config.host, config.port);
58
+ let password = '';
58
59
  try {
59
- await Password_1.default.get(id);
60
+ password = await Password_1.default.get(id);
60
61
  }
61
62
  catch (e) {
62
63
  return false;
63
64
  }
65
+ if (!password) {
66
+ return false;
67
+ }
64
68
  try {
65
- const attributes = await Connection_1.default.open(config.scheme, config.user, config.host, config.port);
69
+ const attributes = await Connection_1.default.open(config.scheme, config.user, config.host, config.port, password);
66
70
  const pwd = await Connection_1.default.get(id).pwd();
67
71
  const settings = {
68
72
  name: (0, path_1.parse)(file).name,
@@ -59,7 +59,7 @@ function default_1(src, dest, move, filter, sid, onComplete = () => { }) {
59
59
  new Upload_1.default(connection, from, toPath, filter, 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);
60
60
  }
61
61
  Queue_1.queues.set(id, queue);
62
- App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType, connection });
63
62
  queue.create();
63
+ setTimeout(() => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType, connection }), 100);
64
64
  return id;
65
65
  }
@@ -21,6 +21,7 @@ const saveConnection_1 = __importDefault(require("./saveConnection"));
21
21
  const saveSettings_1 = __importDefault(require("./saveSettings"));
22
22
  const duplicate_1 = __importDefault(require("./duplicate"));
23
23
  const resolve_1 = __importDefault(require("./resolve"));
24
+ const checkVer_1 = __importDefault(require("./checkVer"));
24
25
  exports.commands = {
25
26
  connect: connect_1.default,
26
27
  disconnect: disconnect_1.default,
@@ -38,5 +39,6 @@ exports.commands = {
38
39
  getConnection: getConnection_1.default,
39
40
  saveConnection: saveConnection_1.default,
40
41
  saveSettings: saveSettings_1.default,
41
- resolve: resolve_1.default
42
+ resolve: resolve_1.default,
43
+ checkVer: checkVer_1.default
42
44
  };
@@ -14,14 +14,10 @@ const Remove_1 = __importDefault(require("../queues/Remove"));
14
14
  const trash = import("trash");
15
15
  const App_1 = __importDefault(require("../App"));
16
16
  const ramda_1 = require("ramda");
17
- async function default_1(files, force, connPath, immediately = false) {
17
+ async function default_1(files, connPath, immediately = false) {
18
18
  if (!files.length) {
19
19
  return;
20
20
  }
21
- if (!force) {
22
- App_1.default.onError({ type: types_1.FailureType.ConfirmDeletion, files });
23
- return;
24
- }
25
21
  if ((0, URI_1.isLocal)(files[0])) {
26
22
  const paths = files.map(p => (0, URI_1.parseURI)(p)['path']);
27
23
  if (immediately) {
@@ -35,6 +31,7 @@ async function default_1(files, force, connPath, immediately = false) {
35
31
  // TODO delete from credentials
36
32
  }
37
33
  });
34
+ return null;
38
35
  }
39
36
  else {
40
37
  const connId = (0, URI_1.parseURI)(files[0])['id'];
@@ -50,7 +47,8 @@ async function default_1(files, force, connPath, immediately = false) {
50
47
  App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Complete });
51
48
  }, App_1.default.remoteWatcher);
52
49
  Queue_1.queues.set(id, queue);
53
- App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType: types_1.QueueType.Remove, connection: connId });
54
50
  queue.create();
51
+ setTimeout(() => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType: types_1.QueueType.Remove, connection: connId }), 100);
52
+ return id;
55
53
  }
56
54
  }
@@ -26,9 +26,11 @@ async function default_1(path, settings) {
26
26
  }
27
27
  let config = content ? JSON.parse(content) : {};
28
28
  if ('scheme' in settings) {
29
- config = { ...config, ...(0, ramda_1.omit)(['password'], settings) };
29
+ const id = (0, URI_1.connectionID)(settings.scheme, settings.user, settings.host, settings.port);
30
+ config = { ...config, ...(0, ramda_1.omit)(['password', 'savePassword'], settings) };
31
+ Password_1.default.delete(id, true);
30
32
  if (settings.password.length) {
31
- Password_1.default.save((0, URI_1.connectionID)(settings.scheme, settings.user, settings.host, settings.port), settings.password);
33
+ Password_1.default.set(id, settings.password, true, settings.savePassword);
32
34
  }
33
35
  }
34
36
  else {
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ const handle = async (name, handler) => {
46
46
  }
47
47
  catch (e) {
48
48
  res.status(400);
49
- process.env.NODE_ENV == 'development' && console.error(e);
49
+ process.env.NODE_ENV == 'development' && console.error('API error: ', e);
50
50
  res.json({ message: (typeof e == 'object' && 'message' in e) ? e.message : String(e) });
51
51
  }
52
52
  });
@@ -19,6 +19,10 @@
19
19
  "key": "meta+a",
20
20
  "command": "select-all"
21
21
  },
22
+ {
23
+ "key": "meta+shift+a",
24
+ "command": "select-all-files"
25
+ },
22
26
  {
23
27
  "key": "ctrl+r",
24
28
  "command": "refresh"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0",
2
+ "version": "0.0.0",
3
3
  "api_version": 1,
4
4
  "layout": {
5
5
  "logo": "yandex-browser-50x50.png",