UNPKG

15.2 kBJavaScriptView Raw
1/**
2 * @license
3 * MOST Web Framework 2.0 Codename Blueshift
4 * Copyright (c) 2017, THEMOST LP All rights reserved
5 *
6 * Use of this source code is governed by an BSD-3-Clause license that can be
7 * found in the LICENSE file at https://themost.io/license
8 */
9///
10var path = require('path');
11var fs = require('fs');
12var _ = require('lodash');
13var FileNotFoundError = require('@themost/common/errors').FileNotFoundError;
14var Base26Number = require('@themost/common/utils').Base26Number;
15var RandomUtils = require('@themost/common/utils').RandomUtils;
16var TraceUtils = require('@themost/common/utils').TraceUtils;
17var LangUtils = require('@themost/common/utils').LangUtils;
18/**
19 * @abstract
20 * @classdesc An abstract class that describes a file storage.
21 * @class
22 * @constructor
23 * @property {string} root - Gets or sets a string that represents the physical root path of this file storage
24 * @property {string} virtualPath - Gets or sets a string that represents the virtual path of this file storage
25 */
26function FileStorage() {
27 //
28}
29
30/**
31 * @param {HttpContext} context
32 * @param {string} src
33 * @param {*} attrs
34 * @param {Function} callback
35 */
36FileStorage.prototype.copyFrom = function(context, src, attrs, callback) {
37 callback = callback || function() {};
38 callback();
39};
40
41
42/**
43 * @param {HttpContext} context
44 * @param {*} item
45 * @param {string} dest
46 * @param {Function} callback
47 */
48FileStorage.prototype.copyTo = function(context, item, dest, callback) {
49 callback = callback || function() {};
50 callback();
51};
52
53/**
54 * @param {HttpContext} context
55 * @param {*} item
56 * @param {Function} callback
57 */
58FileStorage.prototype.resolvePhysicalPath = function(context, item, callback) {
59 callback = callback || function() {};
60 callback();
61};
62/**
63 * @param {HttpContext} context
64 * @param {*} item
65 * @param {Function} callback
66 */
67FileStorage.prototype.resolveUrl = function(context, item, callback) {
68 callback = callback || function() {};
69 callback();
70};
71
72/**
73 * @param {HttpContext} context
74 * @param {*} item
75 * @param {Function} callback
76 */
77FileStorage.prototype.createReadStream = function(context, item, callback) {
78 callback = callback || function() {};
79 callback();
80};
81
82
83/**
84 * @param {Function} callback
85 */
86FileStorage.prototype.init = function(callback) {
87 callback = callback || function() {};
88 callback();
89};
90
91/**
92 * @param {HttpContext} context
93 * @param {*} query
94 * @param {Function} callback
95 */
96FileStorage.prototype.find = function(context, query, callback) {
97 callback = callback || function() {};
98 callback();
99};
100
101/**
102 * @param {HttpContext} context
103 * @param {*} query
104 * @param {Function} callback
105 */
106FileStorage.prototype.findOne = function(context, query, callback) {
107 callback = callback || function() {};
108 callback();
109};
110
111/**
112 * @param {HttpContext} context
113 * @param {*} item
114 * @param {function(Error=,*=)} callback
115 */
116FileStorage.prototype.remove = function(context, item, callback) {
117 callback = callback || function() {};
118 callback();
119};
120
121/**
122 * @classdesc AttachmentFileSystemStorage class describes a file storage for attachments' management on local file system.
123 * @class
124 * @constructor
125 * @augments FileStorage
126 * @param {string} physicalPath The root directory of this storage
127 */
128function AttachmentFileSystemStorage(physicalPath) {
129 this.root = physicalPath;
130 this.virtualPath = null;
131 this.ensure = function(callback) {
132 var self = this;
133 callback = callback || function() {};
134 if (self._initialized) {
135 callback();
136 return;
137 }
138 if (_.isNil(self.root)) {
139 callback(new Error('The file system storage root directory cannot be empty at this context.'));
140 }
141 else {
142 //check directory existence
143 fs.exists(self.root, function(exists) {
144 if (exists) {
145 self._initialized = true;
146 callback();
147 }
148 else {
149 fs.mkdir(self.root,function(err) {
150 if (err) {
151 TraceUtils.error(err);
152 callback(new Error('An error occured while trying to initialize file system storage.'));
153 }
154 else {
155 self._initialized = true;
156 callback();
157 }
158 });
159 }
160 });
161 }
162 };
163}
164
165LangUtils.inherits(AttachmentFileSystemStorage, FileStorage);
166/**
167 * @param {HttpContext} context
168 * @param {*} item
169 * @param {function} callback
170 */
171AttachmentFileSystemStorage.prototype.save = function(context, item, callback) {
172 var self = this;
173 self.ensure(function(err) {
174 if (err) {
175 callback(err);
176 }
177 else {
178 if (_.isNil(item)) {
179 callback();
180 return;
181 }
182 var attachments = context.model('Attachment');
183 if (_.isNil(attachments)) {
184 callback(new Error('Attachment model cannot be found.'));
185 }
186 //file default version
187 item.version = item.version || 1;
188 //file status (false) not published
189 item.published = item.published || false;
190 if (typeof item.alternateName === 'undefined' || item.alternateName === null) {
191 item.alternateName = RandomUtils.randomChars(12);
192 }
193 //set url
194 item.url = path.join(self.virtualPath, item.alternateName);
195 //save attachment
196 attachments.save(item, function(err) {
197 callback(err);
198 });
199 }
200 });
201
202};
203/**
204 * @param {HttpContext} context
205 * @param {*} query
206 * @param {Function} callback
207 */
208AttachmentFileSystemStorage.prototype.findOne = function(context, query, callback) {
209 var self = this;
210 self.ensure(function(err) {
211 if (err) {
212 callback(err);
213 }
214 else {
215 if (_.isNil(query)) {
216 callback();
217 return;
218 }
219 var attachments = context.model('Attachment');
220 if (_.isNil(attachments)) {
221 callback(new Error('Attachment model cannot be found.'));
222 }
223 attachments.find(query).first(callback);
224 }
225 });
226
227};
228/**
229 *
230 * @param {HttpContext} context
231 * @param {*} item
232 * @param {Function} callback
233 */
234AttachmentFileSystemStorage.prototype.resolvePhysicalPath = function(context, item, callback) {
235 var id = item.id, self = this, file_id;
236 if (id) {
237 file_id = Base26Number.toBase26(id);
238 callback(null, path.join(self.root, file_id.substr(0,1), file_id));
239 }
240 else {
241 self.findOne(context, item, function(err, result) {
242 if (err) {
243 callback(err);
244 }
245 else {
246 if (_.isNil(result)) {
247 callback(new Error('Item cannot be found'));
248 }
249 else {
250 file_id = Base26Number.toBase26(result.id);
251 callback(null, path.join(self.root, file_id.substr(0,1), file_id));
252 }
253 }
254 });
255 }
256};
257/**
258 *
259 * @param {HttpContext} context
260 * @param {*} item
261 * @param {function(Error=,string=)} callback
262 */
263AttachmentFileSystemStorage.prototype.resolveUrl = function(context, item, callback) {
264 var alternateName = item.alternateName, self = this;
265 if (alternateName) {
266 callback(null, path.join(self.virtualPath, alternateName));
267 }
268 else {
269 self.findOne(context, item, function(err, result) {
270 if (err) {
271 callback(err);
272 }
273 else {
274 if (_.isNil(result)) {
275 callback(new Error('Item cannot be found'));
276 }
277 else {
278 callback(null, path.join(self.virtualPath, item.alternateName));
279 }
280 }
281 });
282 }
283};
284
285/**
286 * @param {HttpContext} context
287 * @param {*} item
288 * @param {Function} callback
289 */
290AttachmentFileSystemStorage.prototype.createReadStream = function(context, item, callback) {
291 var self = this, filePath;
292 self.findOne(context, item, function(err, result) {
293 if (err) {
294 callback(err);
295 }
296 else {
297 if (_.isNil(result)) {
298 callback(new Error('Item cannot be found'));
299 }
300 else {
301 //get file id
302 var file_id = Base26Number.toBase26(result.id);
303 //create file path
304 filePath = path.join(self.root, file_id.substr(0,1), file_id);
305 //check file
306 fs.exists(filePath, function(exists) {
307 if (!exists) {
308 callback(new FileNotFoundError());
309 }
310 else {
311 callback(null, fs.createReadStream(filePath));
312 }
313 });
314 }
315 }
316 });
317
318};
319
320/**
321 * @param {HttpContext} context
322 * @param {*} query
323 * @param {Function} callback
324 */
325AttachmentFileSystemStorage.prototype.exists = function(context, query, callback) {
326 callback = callback || function() {};
327 this.findOne(context, query, function(err, result) {
328 if (err) {
329 TraceUtils.error(err);
330 callback(false);
331 }
332 else {
333 callback(!_.isNil(result));
334 }
335 });
336};
337/**
338 * @param {HttpContext} context
339 * @param {*} query
340 * @param {function(Error=,*=)} callback
341 */
342AttachmentFileSystemStorage.prototype.find = function(context, query, callback) {
343 var self = this;
344 self.ensure(function(err) {
345 if (err) {
346 callback(err);
347 }
348 else {
349 if (_.isNil(query)) {
350 callback();
351 }
352 else {
353 var attachments = context.model('Attachment');
354 if (_.isNil(attachments)) {
355 callback(new Error('Attachment model cannot be found.'));
356 }
357 attachments.find(query).all(callback)
358 }
359 }
360 });
361};
362
363
364/**
365 * @param {function(Error=)} callback
366 */
367AttachmentFileSystemStorage.prototype.init = function(callback) {
368 this.ensure(callback);
369};
370
371/**
372 * @param {HttpContext} context
373 * @param {string} src
374 * @param {*} attrs
375 * @param {Function} callback
376 */
377AttachmentFileSystemStorage.prototype.copyFrom = function(context, src, attrs, callback) {
378 var self = this;
379 callback = callback || function() {};
380 self.ensure(function(err) {
381 if (err) {
382 callback(err);
383 }
384 else {
385 var filename = path.basename(src);
386 attrs = attrs || {};
387 //set file composition name
388 attrs.name = attrs.name || filename;
389 //check source file
390 fs.exists(src, function(exists) {
391 if (!exists) {
392 callback(new Error('The source file cannot be found'));
393 }
394 else {
395 //save attributes
396 //insert item attributes
397 self.save(context, attrs, function(err) {
398 if (err) {
399 callback(err);
400 }
401 else {
402 //file operation (save to folder)
403 var file = Base26Number.toBase26(attrs.id);
404 fs.exists(path.join(self.root, file.substr(0,1)), function(exists) {
405 if (exists) {
406 copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
407 callback(err);
408 });
409 }
410 else {
411 fs.mkdir(path.join(self.root, file.substr(0,1)), function(err) {
412 if (err) {
413 callback(err);
414 }
415 else {
416 copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
417 callback(err);
418 });
419 }
420 });
421 }
422 });
423 }
424 });
425 }
426 });
427
428 }
429 });
430};
431
432
433/**
434 * @param {HttpContext} context
435 * @param {string|*} item
436 * @param {string} dest
437 * @param {Function} callback
438 */
439AttachmentFileSystemStorage.prototype.copyTo = function(context, item, dest, callback) {
440 var self = this;
441 callback = callback || function() {};
442 self.ensure(function(err) {
443 if (err) {
444 callback(err);
445 }
446 else {
447 if (_.isNil(item)) {
448 callback(new Error('The source item cannot be empty at this context'));
449 self.findOne(context, item, function(err, result) {
450 if (err) {
451 callback(err);
452 }
453 else {
454 if (_.isNil(result)) {
455 callback(new Error('The source item cannot be found.'));
456 }
457 else {
458 var file = Base26Number.toBase26(result.id), src = path.join(self.root, file.substr(0,1), file);
459 fs.exists(src, function(exists) {
460 if (!exists) {
461 callback(new Error('The source file cannot be found.'));
462 }
463 else {
464 var destFile = path.join(dest, result.name);
465 copyFile(src, destFile, function(err) {
466 callback(err, destFile);
467 });
468 }
469 });
470 }
471 }
472 });
473 }
474 }
475 });
476
477};
478
479/**
480 * @param {string} src
481 * @param {string} dest
482 * @param {Function} callback
483 * @private
484 */
485function copyFile(src, dest, callback) {
486 //create read stream
487 var source = fs.createReadStream(src);
488 //create write stream
489 var finalDest = fs.createWriteStream(dest);
490 //copy file
491 source.pipe(finalDest);
492 source.on('end', function() {
493 callback();
494 });
495 source.on('error', function(err) {
496 callback(err);
497 });
498}
499
500if (typeof exports !== 'undefined') {
501 module.exports.FileStorage = FileStorage;
502 module.exports.AttachmentFileSystemStorage = AttachmentFileSystemStorage;
503}