1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | const {methodArguments} = require('./utils/vfs');
|
32 | const systemAdapter = require('./adapters/vfs/system');
|
33 | const uuid = require('uuid/v1');
|
34 | const mime = require('mime');
|
35 | const path = require('path');
|
36 | const vfs = require('./vfs');
|
37 | const {closeWatches} = require('./utils/core.js');
|
38 | const consola = require('consola');
|
39 | const logger = consola.withTag('Filesystem');
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | class Filesystem {
|
45 |
|
46 | |
47 |
|
48 |
|
49 |
|
50 |
|
51 | constructor(core, options = {}) {
|
52 | this.core = core;
|
53 | this.mountpoints = [];
|
54 | this.adapters = {};
|
55 | this.watches = [];
|
56 | this.router = null;
|
57 | this.methods = {};
|
58 | this.options = {
|
59 | adapters: {},
|
60 | ...options
|
61 | };
|
62 | }
|
63 |
|
64 | |
65 |
|
66 |
|
67 | async destroy() {
|
68 | const watches = this.watches.filter(({watch}) => {
|
69 | return watch && typeof watch.close === 'function';
|
70 | }).map(({watch}) => watch);
|
71 |
|
72 | await closeWatches(watches);
|
73 |
|
74 | this.watches = [];
|
75 | }
|
76 |
|
77 | |
78 |
|
79 |
|
80 |
|
81 | async init() {
|
82 | const adapters = {
|
83 | system: systemAdapter,
|
84 | ...this.options.adapters
|
85 | };
|
86 |
|
87 | this.adapters = Object.keys(adapters).reduce((result, iter) => {
|
88 | return {
|
89 | [iter]: adapters[iter](this.core),
|
90 | ...result
|
91 | };
|
92 | }, {});
|
93 |
|
94 |
|
95 | const {router, methods} = vfs(this.core);
|
96 | this.router = router;
|
97 | this.methods = methods;
|
98 |
|
99 |
|
100 | const {define} = this.core.config('mime', {define: {}, filenames: {}});
|
101 | mime.define(define, {force: true});
|
102 |
|
103 |
|
104 | await Promise.all(this.core.config('vfs.mountpoints')
|
105 | .map(mount => this.mount(mount)));
|
106 |
|
107 | return true;
|
108 | }
|
109 |
|
110 | |
111 |
|
112 |
|
113 |
|
114 |
|
115 | mime(filename) {
|
116 | const {filenames} = this.core.config('mime', {
|
117 | define: {},
|
118 | filenames: {}
|
119 | });
|
120 |
|
121 | return filenames[path.basename(filename)]
|
122 | ? filenames[path.basename(filename)]
|
123 | : mime.getType(filename) || 'application/octet-stream';
|
124 | }
|
125 |
|
126 | |
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | request(name, req, res = {}) {
|
133 | return this.methods[name](req, res);
|
134 | }
|
135 |
|
136 | |
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | call(options, ...args) {
|
145 | const {method, user} = {
|
146 | user: {},
|
147 | ...options
|
148 | };
|
149 |
|
150 | const req = methodArguments[method]
|
151 | .reduce(({fields, files}, key, index) => {
|
152 | const arg = args[index];
|
153 | if (typeof key === 'function') {
|
154 | files = Object.assign(key(arg), files);
|
155 | } else {
|
156 | fields = {
|
157 | [key]: arg,
|
158 | ...fields
|
159 | };
|
160 | }
|
161 |
|
162 | return {fields, files};
|
163 | }, {fields: {}, files: {}});
|
164 |
|
165 | req.session = {user};
|
166 |
|
167 | return this.request(method, req);
|
168 | }
|
169 |
|
170 | |
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | realpath(filename, user = {}) {
|
177 | return this.methods.realpath({
|
178 | session: {
|
179 | user: {
|
180 | groups: [],
|
181 | ...user
|
182 | }
|
183 | },
|
184 | fields: {
|
185 | path: filename
|
186 | }
|
187 | });
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
194 |
|
195 | async mount(mount) {
|
196 | const mountpoint = {
|
197 | id: uuid(),
|
198 | root: `${mount.name}:/`,
|
199 | attributes: {},
|
200 | ...mount
|
201 | };
|
202 |
|
203 | this.mountpoints.push(mountpoint);
|
204 |
|
205 | logger.success('Mounted', mountpoint.name);
|
206 |
|
207 | await this.watch(mountpoint);
|
208 |
|
209 | return mountpoint;
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 | async unmount(mountpoint) {
|
218 | const found = this.watches.find(w => w.id === mountpoint.id);
|
219 |
|
220 | if (found && found.watch) {
|
221 | await found.watch.close();
|
222 | }
|
223 |
|
224 | const index = this.mountpoints.indexOf(mountpoint);
|
225 |
|
226 | if (index !== -1) {
|
227 | this.mountpoints.splice(index, 1);
|
228 |
|
229 | return true;
|
230 | }
|
231 |
|
232 | return false;
|
233 | }
|
234 |
|
235 | |
236 |
|
237 |
|
238 |
|
239 | async watch(mountpoint) {
|
240 | if (
|
241 | !mountpoint.attributes.watch ||
|
242 | this.core.config('vfs.watch') === false ||
|
243 | !mountpoint.attributes.root
|
244 | ) {
|
245 | return;
|
246 | }
|
247 |
|
248 | const adapter = await (mountpoint.adapter
|
249 | ? this.adapters[mountpoint.adapter]
|
250 | : this.adapters.system);
|
251 |
|
252 | if (typeof adapter.watch === 'function') {
|
253 | await this._watch(mountpoint, adapter);
|
254 | }
|
255 | }
|
256 |
|
257 | |
258 |
|
259 |
|
260 |
|
261 |
|
262 | async _watch(mountpoint, adapter) {
|
263 | const watch = await adapter.watch(mountpoint, (args, dir, type) => {
|
264 | const target = mountpoint.name + ':/' + dir;
|
265 | const keys = Object.keys(args);
|
266 | const filter = keys.length === 0
|
267 | ? () => true
|
268 | : ws => keys.every(k => ws._osjs_client[k] === args[k]);
|
269 |
|
270 | this.core.emit('osjs/vfs:watch:change', {
|
271 | mountpoint,
|
272 | target,
|
273 | type
|
274 | });
|
275 |
|
276 | this.core.broadcast('osjs/vfs:watch:change', [{
|
277 | path: target,
|
278 | type
|
279 | }, args], filter);
|
280 | });
|
281 |
|
282 | watch.on('error', error => logger.warn('Mountpoint watch error', error));
|
283 |
|
284 | this.watches.push({
|
285 | id: mountpoint.id,
|
286 | watch
|
287 | });
|
288 |
|
289 | logger.info('Watching mountpoint', mountpoint.name);
|
290 | }
|
291 | }
|
292 |
|
293 | module.exports = Filesystem;
|