filefive 1.2.0 → 1.4.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
@@ -8,10 +8,18 @@ FileFive is a free open-source SFTP/FTP client and file manager with intuitive a
8
8
 
9
9
  It is installed as a Node.js package and uses the web browser as GUI.
10
10
 
11
- FileFive has a unique set of features and may an alternative to FileZilla, Cyberduck, Transmit and ForkLift.
11
+ FileFive has a unique set of features and may be an alternative to FileZilla, Cyberduck, Transmit, and ForkLift.
12
12
 
13
13
  <p align="center">
14
- <img src="https://github.com/miroshnikov/filefive/blob/main/screenshot.png" alt="FileFive" />
14
+ <img src="https://github.com/miroshnikov/filefive/blob/main/docs/screenshots/screenshot-1.png" alt="FileFive" />
15
+ </p>
16
+ <p align="center">
17
+ <details>
18
+ <summary>More screenshots</summary>
19
+ <img src="https://github.com/miroshnikov/filefive/blob/main/docs/screenshots/screenshot-2.png" alt="FileFive"/>
20
+ <img src="https://github.com/miroshnikov/filefive/blob/main/docs/screenshots/screenshot-3.png" alt="FileFive"/>
21
+ <img src="https://github.com/miroshnikov/filefive/blob/main/docs/screenshots/screenshot-4.png" alt="FileFive"/>
22
+ </details>
15
23
  </p>
16
24
 
17
25
  ## Installation
@@ -37,11 +45,14 @@ Options:
37
45
  ## Features
38
46
  - Cross-platform, runs on Mac OS, Linux and any *nix with Node.js
39
47
  - Supports SSH File Transfer Protocol (SFTP) and FTP
40
- - Minimalistic and intuitive UI, mimicing the VSCode Explorer view
48
+ - Minimalistic and intuitive UI, mimicing the look and feel of VSCode Explorer view
41
49
  - Search/filter files using wildcards and JavaScript Regular Expressions
42
50
  - Synchronized browsing
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
51
+ - Copy files keeping relative paths, allows synchronization files in nested folders in one click
52
+ - Remote file editing
53
+ - Files' Git statuses (uses your machine's Git installation)
54
+ - Connections/servers are plain files stored on your filesystem, no need to export/import
55
+ - Easy to backup connections and settings in `~/.f5` folder, e.g. by putting them into a Git repo
45
56
  - Drag & drop, copy & paste files support
46
57
  - Use browser tabs to browse more than one server or transfer files simultaneously
47
58
  - Utilize the built-in browser password manager to store passwords
package/dist/App.js CHANGED
@@ -14,9 +14,9 @@ const RemoteWatcher_1 = __importDefault(require("./RemoteWatcher"));
14
14
  const Queue_1 = require("./queues/Queue");
15
15
  const Password_1 = __importDefault(require("./Password"));
16
16
  const commands_1 = require("./commands");
17
- const transform_1 = __importDefault(require("./transform"));
18
17
  const Local_1 = require("./Local");
19
18
  const URI_1 = require("./utils/URI");
19
+ const Local_2 = __importDefault(require("./transformers/Local"));
20
20
  class App {
21
21
  static async bootstrap(handle, emitter, opener) {
22
22
  const dataPath = (0, node_path_1.join)((0, node_os_1.homedir)(), '.f5');
@@ -39,11 +39,11 @@ class App {
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
- copy: ({ src, dest, move, filter, sid }) => commands_1.commands.copy(src, dest, move, filter, sid),
42
+ copy: ({ src, dest, move, filter, root, sid }) => commands_1.commands.copy(src, dest, move, filter, root, sid),
43
43
  duplicate: ({ src, filter }) => commands_1.commands.duplicate(src, filter),
44
44
  remove: ({ files }) => commands_1.commands.remove(files, connPath),
45
45
  clear: ({ file }) => commands_1.commands.clear(file),
46
- open: ({ file }) => opener(file),
46
+ open: ({ file, app }) => commands_1.commands.open(file, app, opener),
47
47
  mkdir: ({ name, parent }) => commands_1.commands.mkdir(name, parent),
48
48
  read: ({ file }) => commands_1.commands.read(file),
49
49
  write: ({ path, content }) => commands_1.commands.write(path, content),
@@ -57,8 +57,11 @@ class App {
57
57
  this.onError = (error) => emitError(error);
58
58
  const emitDir = emitter('dir');
59
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);
60
+ this.localWatcher = new LocalWatcher_1.default(async (path, files) => {
61
+ const transformer = new Local_2.default();
62
+ 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
+ }, path => this.onError({ type: types_1.FailureType.MissingDir, uri: (0, URI_1.createURI)(types_1.LocalFileSystemID, path) }));
64
+ this.remoteWatcher = new RemoteWatcher_1.default(sendDirContent, uri => this.onError({ type: types_1.FailureType.MissingDir, uri }));
62
65
  const emitFile = emitter('file');
63
66
  const sendFileStat = (path, stat) => emitFile({ path, stat });
64
67
  this.fileWatcher = new FileWatcher_1.default(sendFileStat);
@@ -7,10 +7,9 @@ const node_path_1 = require("node:path");
7
7
  const Local_1 = require("./Local");
8
8
  const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap"));
9
9
  class default_1 {
10
- constructor(listener, onMissing, transform = (files) => files) {
10
+ constructor(listener, onMissing) {
11
11
  this.listener = listener;
12
12
  this.onMissing = onMissing;
13
- this.transform = transform;
14
13
  this.watched = new ReferenceCountMap_1.default;
15
14
  }
16
15
  watch(dir) {
@@ -24,14 +23,14 @@ class default_1 {
24
23
  }
25
24
  }
26
25
  try {
27
- this.listener(dir, this.transform((0, Local_1.list)(dir)), event, target);
26
+ this.listener(dir, (0, Local_1.list)(dir), event, target);
28
27
  }
29
28
  catch (e) {
30
29
  this.watched.del(dir);
31
30
  this.onMissing(dir);
32
31
  }
33
32
  }));
34
- const files = this.transform((0, Local_1.list)(dir));
33
+ const files = (0, Local_1.list)(dir);
35
34
  this.listener(dir, files);
36
35
  return files;
37
36
  }
package/dist/Password.js CHANGED
@@ -15,6 +15,7 @@ class Passwords {
15
15
  else {
16
16
  this.pending.get(id)?.[0](password);
17
17
  remember && this.store.set(id, [password, save]);
18
+ save && this.dump();
18
19
  }
19
20
  }
20
21
  static async get(id, skipMissing = false) {
@@ -0,0 +1,61 @@
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.open = open;
7
+ const types_1 = require("./types");
8
+ const URI_1 = require("./utils/URI");
9
+ const commands_1 = require("./commands");
10
+ const node_os_1 = require("node:os");
11
+ const node_path_1 = require("node:path");
12
+ const promises_1 = require("node:fs/promises");
13
+ const FileWatcher_1 = __importDefault(require("./FileWatcher"));
14
+ const Connection_1 = __importDefault(require("./Connection"));
15
+ const App_1 = __importDefault(require("./App"));
16
+ const files = new Map();
17
+ const watcher = new FileWatcher_1.default(path => send(path));
18
+ async function open(file, onLoad) {
19
+ const { path } = (0, URI_1.parseURI)(file);
20
+ try {
21
+ const tmpDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'f5-'));
22
+ const tmpName = (0, node_path_1.join)(tmpDir, (0, node_path_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 });
25
+ watcher.watch(tmpName);
26
+ onLoad(tmpName);
27
+ });
28
+ }
29
+ catch (err) {
30
+ console.error(err);
31
+ }
32
+ }
33
+ async function send(file) {
34
+ const watched = files.get(file);
35
+ if (!watched) {
36
+ return;
37
+ }
38
+ if (watched.sending) {
39
+ watched.changed = true;
40
+ return;
41
+ }
42
+ watched.sending = true;
43
+ watched.deletion = resetDeletion(file, watched.deletion);
44
+ const { id, path } = (0, URI_1.parseURI)(watched.file);
45
+ const [conn, close] = await Connection_1.default.transmit(id);
46
+ await conn.put(file, path);
47
+ close();
48
+ App_1.default.remoteWatcher.refresh((0, URI_1.createURI)(id, (0, node_path_1.dirname)(path)));
49
+ watched.sending = false;
50
+ if (watched.changed) {
51
+ watched.changed = false;
52
+ send(file);
53
+ }
54
+ }
55
+ function resetDeletion(file, current) {
56
+ clearTimeout(current);
57
+ return setTimeout(() => {
58
+ files.delete(file);
59
+ (0, promises_1.rm)((0, node_path_1.dirname)(file), { force: true, recursive: true });
60
+ }, 1000 * 60 * 60);
61
+ }
@@ -8,10 +8,9 @@ const ReferenceCountMap_1 = __importDefault(require("./utils/ReferenceCountMap")
8
8
  const Connection_1 = __importDefault(require("./Connection"));
9
9
  const URI_1 = require("./utils/URI");
10
10
  class RemoteWatcher {
11
- constructor(listener, onMissing, transform = (files) => files) {
11
+ constructor(listener, onMissing) {
12
12
  this.listener = listener;
13
13
  this.onMissing = onMissing;
14
- this.transform = transform;
15
14
  this.watched = new ReferenceCountMap_1.default;
16
15
  }
17
16
  watch(uri) {
@@ -34,8 +33,8 @@ class RemoteWatcher {
34
33
  list(uri) {
35
34
  const { id, path } = (0, URI_1.parseURI)(uri);
36
35
  Connection_1.default.list(id, path)
37
- .then(files => this.listener(uri, this.transform(files)))
38
- .catch(e => {
36
+ .then(files => this.listener(uri, files))
37
+ .catch(() => {
39
38
  this.watched.del(uri);
40
39
  this.onMissing(uri);
41
40
  });
@@ -14,7 +14,7 @@ const Upload_1 = __importDefault(require("../queues/Upload"));
14
14
  const uniqid_1 = __importDefault(require("../utils/uniqid"));
15
15
  const App_1 = __importDefault(require("../App"));
16
16
  const ramda_1 = require("ramda");
17
- function default_1(src, dest, move, filter, sid, onComplete = () => { }) {
17
+ function default_1(src, dest, move, filter, root, sid, onComplete = () => { }) {
18
18
  if (!src.length) {
19
19
  return;
20
20
  }
@@ -43,7 +43,7 @@ function default_1(src, dest, move, filter, 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, 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.bind(queue), error => {
47
47
  App_1.default.onError({
48
48
  type: types_1.FailureType.RemoteError,
49
49
  id: fromId,
@@ -55,11 +55,13 @@ function default_1(src, dest, move, filter, 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, 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, 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.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);
60
60
  }
61
61
  Queue_1.queues.set(id, queue);
62
- queue.create();
63
- setTimeout(() => App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType, connection }), 100);
62
+ setTimeout(() => {
63
+ App_1.default.onQueueUpdate(id, { type: types_1.QueueEventType.Create, queueType, connection });
64
+ queue.create();
65
+ }, 100);
64
66
  return id;
65
67
  }
@@ -13,6 +13,7 @@ const remove_1 = __importDefault(require("./remove"));
13
13
  const clear_1 = __importDefault(require("./clear"));
14
14
  const mkdir_1 = __importDefault(require("./mkdir"));
15
15
  const read_1 = __importDefault(require("./read"));
16
+ const open_1 = __importDefault(require("./open"));
16
17
  const write_1 = __importDefault(require("./write"));
17
18
  const settings_1 = __importDefault(require("./settings"));
18
19
  const rename_1 = __importDefault(require("./rename"));
@@ -34,6 +35,7 @@ exports.commands = {
34
35
  clear: clear_1.default,
35
36
  mkdir: mkdir_1.default,
36
37
  read: read_1.default,
38
+ open: open_1.default,
37
39
  write: write_1.default,
38
40
  rename: rename_1.default,
39
41
  getConnection: getConnection_1.default,
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const URI_1 = require("../utils/URI");
5
+ const RemoteFiles_1 = require("../RemoteFiles");
6
+ async function default_1(file, app, opener) {
7
+ if ((0, URI_1.isLocal)(file)) {
8
+ const { id, path } = (0, URI_1.parseURI)(file);
9
+ if (app == 'code') {
10
+ // open files through protocol links
11
+ // vscode://file/<path>
12
+ // vscode-insiders://file/<path>
13
+ opener(`vscode://file/${path}`);
14
+ }
15
+ else {
16
+ opener(path);
17
+ }
18
+ return '';
19
+ }
20
+ else {
21
+ return (0, RemoteFiles_1.open)(file, path => opener(app == 'code' ? `vscode://file/${path}` : path));
22
+ }
23
+ }
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ app.post('/api/upload', upload.array('files'), async function (req, res) {
60
60
  await (0, promises_1.rename)(path, fnm);
61
61
  src.push(fnm);
62
62
  }
63
- commands_1.commands.copy(src.map(path => (0, URI_1.createURI)(types_1.LocalFileSystemID, path)), req.body['to'], true, null, null, () => src.forEach(path => (0, promises_1.rm)(path, { force: true })));
63
+ 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, promises_1.rm)(path, { force: true })));
64
64
  }
65
65
  res.json(true);
66
66
  });