filefive 1.0.0

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