alchemy-media 0.5.2 → 0.6.2

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 (48) hide show
  1. package/CHANGELOG.md +11 -4
  2. package/assets/fonts/fontawesome6/fa-brands-400.ttf +0 -0
  3. package/assets/fonts/fontawesome6/fa-brands-400.woff2 +0 -0
  4. package/assets/fonts/fontawesome6/fa-regular-400.ttf +0 -0
  5. package/assets/fonts/fontawesome6/fa-regular-400.woff2 +0 -0
  6. package/assets/fonts/fontawesome6/fa-solid-900.ttf +0 -0
  7. package/assets/fonts/fontawesome6/fa-solid-900.woff2 +0 -0
  8. package/assets/fonts/fontawesome6/fa-v4compatibility.ttf +0 -0
  9. package/assets/fonts/fontawesome6/fa-v4compatibility.woff2 +0 -0
  10. package/assets/stylesheets/alchemy_icons.scss +68 -0
  11. package/assets/stylesheets/alchemy_svg.scss +9 -0
  12. package/assets/stylesheets/chimera/mediafield.less +1 -2
  13. package/assets/stylesheets/element/alchemy_file.scss +95 -0
  14. package/assets/stylesheets/fontawesome6/_animated.scss +153 -0
  15. package/assets/stylesheets/fontawesome6/_bordered-pulled.scss +20 -0
  16. package/assets/stylesheets/fontawesome6/_core.scss +33 -0
  17. package/assets/stylesheets/fontawesome6/_fixed-width.scss +7 -0
  18. package/assets/stylesheets/fontawesome6/_functions.scss +57 -0
  19. package/assets/stylesheets/fontawesome6/_icons.scss +9 -0
  20. package/assets/stylesheets/fontawesome6/_list.scss +18 -0
  21. package/assets/stylesheets/fontawesome6/_mixins.scss +73 -0
  22. package/assets/stylesheets/fontawesome6/_rotated-flipped.scss +31 -0
  23. package/assets/stylesheets/fontawesome6/_screen-reader.scss +14 -0
  24. package/assets/stylesheets/fontawesome6/_shims.scss +2027 -0
  25. package/assets/stylesheets/fontawesome6/_sizing.scss +16 -0
  26. package/assets/stylesheets/fontawesome6/_stacked.scss +32 -0
  27. package/assets/stylesheets/fontawesome6/_variables.scss +4885 -0
  28. package/assets/stylesheets/fontawesome6/brands.scss +30 -0
  29. package/assets/stylesheets/fontawesome6/fontawesome.scss +21 -0
  30. package/assets/stylesheets/fontawesome6/regular.scss +26 -0
  31. package/assets/stylesheets/fontawesome6/solid.scss +26 -0
  32. package/assets/stylesheets/fontawesome6/v4-shims.scss +11 -0
  33. package/bootstrap.js +1 -0
  34. package/controller/media_files_controller.js +20 -0
  35. package/controller/media_gallery_chimera_controller.js +1 -0
  36. package/element/al_file.js +275 -0
  37. package/element/al_ico_element.js +17 -0
  38. package/element/al_svg.js +81 -0
  39. package/helper/media_helper.js +56 -10
  40. package/helper/widgets/image_widget.js +46 -0
  41. package/{lib/field_type → helper_field}/file_field_type.js +5 -3
  42. package/lib/media_types/image_media_type.js +34 -9
  43. package/model/media_file_model.js +9 -3
  44. package/model/media_raw_model.js +529 -529
  45. package/package.json +6 -6
  46. package/view/element/al_file.hwk +18 -0
  47. package/view/form/inputs/edit/file.hwk +8 -0
  48. package/assets/scripts/chimera/mediafield.js +0 -451
@@ -1,530 +1,530 @@
1
- var fs = alchemy.use('fs'),
2
- Url = alchemy.use('url'),
3
- path = alchemy.use('path'),
4
- http = alchemy.use('http'),
5
- crypto = alchemy.use('crypto');
6
-
7
- /**
8
- * Media Raw Model
9
- *
10
- * @constructor
11
- *
12
- * @author Jelle De Loecker <jelle@develry.be>
13
- * @since 0.0.1
14
- * @version 0.3.0
15
- */
16
- var MediaRaw = Function.inherits('Alchemy.Model', function MediaRaw(options) {
17
- MediaRaw.super.call(this, options);
18
- this.MediaType = Classes.Alchemy.MediaType;
19
- });
20
-
21
- /**
22
- * Constitute the class wide schema
23
- *
24
- * @author Jelle De Loecker <jelle@develry.be>
25
- * @since 0.2.0
26
- * @version 0.4.0
27
- */
28
- MediaRaw.constitute(function addFields() {
29
-
30
- this.addField('name', 'String');
31
- this.addField('extension', 'String');
32
-
33
- // Remember the origin of the file (url or path)
34
- this.addField('origin', 'String');
35
-
36
- // Hash & size should be one unique index together
37
- this.addField('hash', 'String');
38
- this.addField('size', 'Number');
39
- this.addField('mimetype', 'String');
40
- this.addField('type', 'String');
41
- this.addField('extra', 'Object');
42
- });
43
-
44
- MediaRaw.setProperty('basePath', alchemy.plugins.media.path);
45
- MediaRaw.setProperty('hash', alchemy.plugins.media.hash);
46
- MediaRaw.setProperty('types', alchemy.getClassGroup('media_type'));
47
-
48
- /**
49
- * Path to this file
50
- *
51
- * @author Jelle De Loecker <jelle@develry.be>
52
- * @since 0.4.1
53
- * @version 0.4.1
54
- *
55
- * @type {String}
56
- */
57
- MediaRaw.Document.setFieldGetter(function path() {
58
- return this.$model.getPathFromId(this._id);
59
- });
60
-
61
- /**
62
- * Export the actual file
63
- *
64
- * @author Jelle De Loecker <jelle@develry.be>
65
- * @since 0.4.1
66
- * @version 0.4.1
67
- *
68
- * @type {Stream} output
69
- *
70
- * @return {Pledge}
71
- */
72
- MediaRaw.Document.setMethod(function extraExportToStream(output) {
73
-
74
- var that = this,
75
- path = this.path,
76
- stats;
77
-
78
- if (!path) {
79
- return Pledge.resolve();
80
- }
81
-
82
- return Function.series(function getStats(next) {
83
- fs.stat(path, function checked(err, result) {
84
-
85
- if (err) {
86
- return next();
87
- }
88
-
89
- stats = result;
90
-
91
- next();
92
- });
93
- }, function streamFile(next) {
94
-
95
- if (!stats) {
96
- return next();
97
- }
98
-
99
- let hbuf = Buffer.allocUnsafe(5);
100
-
101
- // 0xFF indicates an extra export
102
- hbuf.writeUInt8(0xFF, 0);
103
-
104
- // Now say how long it is
105
- hbuf.writeUInt32BE(stats.size, 1);
106
-
107
- // Write the header to the stream
108
- output.write(hbuf);
109
-
110
- // Create a read stream to the file
111
- let read_stream = fs.createReadStream(path);
112
-
113
- // Listen for the data
114
- read_stream.on('data', function onData(data) {
115
- output.write(data);
116
- });
117
-
118
- // Listen for the stream end
119
- read_stream.on('end', next);
120
-
121
- }, function done(err) {
122
-
123
- });
124
- });
125
-
126
- /**
127
- * Import the actual file
128
- *
129
- * @author Jelle De Loecker <jelle@develry.be>
130
- * @since 0.4.1
131
- * @version 0.4.1
132
- *
133
- * @type {Stream} input
134
- *
135
- * @return {Pledge}
136
- */
137
- MediaRaw.Document.setMethod(function extraImportFromStream(input) {
138
-
139
- var that = this,
140
- file_path = this.path,
141
- pledge = new Pledge(),
142
- stream;
143
-
144
- input.pause();
145
-
146
- alchemy.createDir(path.dirname(file_path), function done(err) {
147
-
148
- if (err) {
149
- return pledge.reject(err);
150
- }
151
-
152
- input.resume();
153
-
154
- stream = fs.createWriteStream(file_path);
155
-
156
- input.pipe(stream);
157
-
158
- input.on('finish', function onEnd() {
159
- pledge.resolve();
160
- });
161
- });
162
-
163
- return pledge;
164
- });
165
-
166
- /**
167
- * Add a file
168
- *
169
- * @author Jelle De Loecker <jelle@develry.be>
170
- * @since 0.0.1
171
- * @version 0.4.2
172
- *
173
- * @param {String} file The path to the file, can be a URL
174
- * @param {Object} options
175
- * @param {Function} callback
176
- */
177
- MediaRaw.setMethod(function addFile(file, options, callback) {
178
-
179
- var that = this,
180
- removeOriginal,
181
- MediaFile;
182
-
183
- if (typeof options == 'function') {
184
- callback = options;
185
- options = {};
186
- }
187
-
188
- // If the given file is actually a url, we'll need to download it first
189
- if (file.startsWith('http://') || file.startsWith('https://')) {
190
-
191
- // Set the url as the origin
192
- options.origin = file;
193
-
194
- // Don't keep the original, temporary file
195
- options.move = true;
196
-
197
- alchemy.downloadFile(file, options, function downloadedFile(err, tempfile, filename) {
198
-
199
- if (err) {
200
- return callback(err);
201
- }
202
-
203
- if (!options.filename) {
204
- options.filename = filename;
205
- }
206
-
207
- if (!options.filename) {
208
- options.filename = Url.parse(file).pathname.split('/').last();
209
- }
210
-
211
- if (!options.name) {
212
- options.name = options.filename.beforeLast('.') || options.filename;
213
- }
214
-
215
- that.addFile(tempfile, options, callback);
216
- });
217
-
218
- return;
219
- };
220
-
221
- removeOriginal = false;
222
- MediaFile = this.getModel('MediaFile');
223
-
224
- if (typeof options.move == 'undefined') {
225
- options.move = false;
226
- }
227
-
228
- if (options.move) {
229
- removeOriginal = true;
230
- }
231
-
232
- alchemy.getFileInfo(file, {hash: this.hashType}, function gotFileInfo(err, info) {
233
-
234
- var type;
235
-
236
- if (err) {
237
- return callback(err);
238
- }
239
-
240
- type = that.MediaType.determineType(info.mimetype, options);
241
-
242
- type.normalize(file, info, function afterNormalize(err, rawPath, rawInfo, rawExtra, extra) {
243
-
244
- if (err) {
245
- return callback(err);
246
- }
247
-
248
- options.rawExtra = rawExtra;
249
- options.move = true;
250
- options.name = options.name || info.name;
251
- options.extension = info.extension;
252
-
253
- // Store the raw file in the database & filesystem
254
- that.storeFile(rawPath, options, function afterRawStore(err, id, item, createdNew) {
255
-
256
- if (err) {
257
- return callback(err);
258
- }
259
-
260
- var FileData = {
261
- MediaFile: {
262
- media_raw_id : item._id,
263
- name : options.name || info.name,
264
- filename : options.filename || info.filename,
265
- extra : extra,
266
- type : type.typeName
267
- }
268
- };
269
-
270
- if (createdNew || (!createdNew && !options.reusefile)) {
271
-
272
- MediaFile.save(FileData, {document: false}, function savedNewRecord(err, result) {
273
- if (err) return callback(err);
274
- callback(null, result[0]);
275
- });
276
- } else {
277
-
278
- MediaFile.find('first', {conditions: {media_raw_id: item._id}, recursive: 0, document: false}, function foundRecord(err, items) {
279
-
280
- if (err) {
281
- return callback(err);
282
- }
283
-
284
- items = items[0];
285
-
286
- if (items && items.MediaFile) {
287
- callback(null, items.MediaFile)
288
- } else {
289
- MediaFile.save(FileData, {document: false}, function(err, result) {
290
- if (err) return callback(err);
291
- callback(null, result[0]);
292
- });
293
- }
294
- });
295
- }
296
- });
297
- });
298
- });
299
- });
300
-
301
- /**
302
- * Get a file based on its raw id
303
- *
304
- * @author Jelle De Loecker <jelle@develry.be>
305
- * @since 0.0.1
306
- * @version 0.2.0
307
- *
308
- * @param {String|ObjectID} id
309
- * @param {Function} callback
310
- */
311
- MediaRaw.setMethod(function getFile(id, callback) {
312
-
313
- var that = this,
314
- options = {
315
- conditions: {
316
- '_id': id
317
- },
318
- document: false
319
- };
320
-
321
- this.find('first', options, function gotFileRecord(err, result) {
322
-
323
- var item;
324
-
325
- if (err) {
326
- return callback(err);
327
- }
328
-
329
- if (!result.length) {
330
- return callback(new Error('No image found'));
331
- }
332
-
333
-
334
- item = result.MediaRaw;
335
-
336
- item.path = that.getPathFromId(item._id);
337
-
338
- callback(null, item);
339
- });
340
- });
341
-
342
- /**
343
- * Store the given raw file in our own folder structure and database,
344
- * this makes sure there are no duplicates
345
- *
346
- * @author Jelle De Loecker <jelle@develry.be>
347
- * @since 0.0.1
348
- * @version 0.4.2
349
- *
350
- * @param {String} file The path to the file
351
- * @param {Object} options Optional options
352
- * @param {Function} callback The callback that gets the id & item
353
- */
354
- MediaRaw.setMethod(function storeFile(file, options, callback) {
355
-
356
- var that = this,
357
- transferType;
358
-
359
- if (typeof options == 'function') {
360
- callback = options;
361
- options = {};
362
- }
363
-
364
- if (!file) {
365
- return callback(new Error('Unable to store file: given path string is empty'));
366
- }
367
-
368
- if (typeof options.move == 'undefined') {
369
- options.move = false;
370
- }
371
-
372
- if (options.move) {
373
- transferType = 'moveFile';
374
- } else {
375
- transferType = 'copyFile';
376
- }
377
-
378
- prepareId.call(this, file, options, function gotDatabaseInfo(err, alreadyCopied, id, item) {
379
-
380
- var targetPath;
381
-
382
- if (err) {
383
- return callback(err);
384
- }
385
-
386
- targetPath = that.getPathFromId(id);
387
- item.path = targetPath;
388
-
389
- if (alreadyCopied) {
390
- callback(null, id, item, false);
391
-
392
- // If we wanted to move this file, remove the original
393
- if (options.move) {
394
- fs.unlink(file, function afterUnlink(){});
395
- }
396
-
397
- } else if (targetPath) {
398
- alchemy[transferType](file, targetPath, function afterTransfer(err) {
399
- if (err) {
400
- callback(err);
401
- } else {
402
- item.path = targetPath;
403
- callback(null, id, item, true);
404
- }
405
- });
406
- } else {
407
- return callback(new Error('Could not copy file to undefined target'));
408
- }
409
-
410
- });
411
- });
412
-
413
- /**
414
- * Look up some information on the given file, store it in the
415
- * database if it is not there yet, and return the id and info.
416
- *
417
- * This does NOT copy the file to our own folder structure!
418
- *
419
- * @author Jelle De Loecker <jelle@develry.be>
420
- * @since 0.0.1
421
- * @version 0.4.2
422
- */
423
- var prepareId = function prepareId(file, options, callback) {
424
-
425
- var that = this,
426
- data;
427
-
428
- if (!file) {
429
- return callback(new Error('Unable to prepare file ID: given path is empty'));
430
- }
431
-
432
- alchemy.getFileInfo(file, {hash: this.hashType}, function gotFileInfo(err, info) {
433
-
434
- if (err) {
435
- return callback(err);
436
- }
437
-
438
- // See if this file already exists,
439
- // based on the hash and the file size
440
- var search_options = {
441
- conditions: {
442
- hash: info.hash,
443
- size: info.size
444
- },
445
- document: false
446
- };
447
-
448
- that.find('first', search_options, function gotFindResult(err, result) {
449
-
450
- if (err) {
451
- return callback(err);
452
- }
453
-
454
- result = result[0];
455
-
456
- // Return the existing id if we found a match
457
- if (result) {
458
- callback(null, true, result.MediaRaw._id, result.MediaRaw);
459
- } else {
460
-
461
- // If not: save the data to the database
462
- data = {
463
- MediaRaw: {
464
- name : options.name || info.name,
465
- extension : options.extension || info.extension,
466
- mimetype : info.mimetype,
467
- hash : info.hash,
468
- size : info.size,
469
- extra : options.rawExtra
470
- }
471
- };
472
-
473
- if (options.origin) {
474
- data.MediaRaw.origin = options.origin;
475
- }
476
-
477
- that.save(data, {document: false}, function getSaveResult(err, result) {
478
-
479
- if (err) {
480
- return callback(err);
481
- }
482
-
483
- result = result[0];
484
-
485
- if (result) {
486
- callback(null, false, result._id, result);
487
- }
488
- });
489
- }
490
- });
491
- });
492
- };
493
-
494
- /**
495
- * Construct the filepath of the given ObjectId
496
- *
497
- * @author Jelle De Loecker <jelle@develry.be>
498
- * @since 0.0.1
499
- * @version 0.2.0
500
- *
501
- * @param {ObjectID|String} objectId
502
- *
503
- * @return {String} The (expected) path to the file
504
- */
505
- MediaRaw.setMethod(function getPathFromId(objectId) {
506
-
507
- var filePath,
508
- month,
509
- year,
510
- date;
511
-
512
- // Ensure an objectid
513
- if (!(objectId = alchemy.castObjectId(objectId))) {
514
- return false;
515
- }
516
-
517
- date = objectId.getTimestamp();
518
- year = String(date.getFullYear());
519
- month = (date.getMonth()+1);
520
-
521
- if (month < 10) {
522
- month = '0' + month;
523
- } else {
524
- month = String(month);
525
- }
526
-
527
- filePath = path.resolve(this.basePath, year, month, String(objectId));
528
-
529
- return filePath;
1
+ var fs = alchemy.use('fs'),
2
+ Url = alchemy.use('url'),
3
+ path = alchemy.use('path'),
4
+ http = alchemy.use('http'),
5
+ crypto = alchemy.use('crypto');
6
+
7
+ /**
8
+ * Media Raw Model
9
+ *
10
+ * @constructor
11
+ *
12
+ * @author Jelle De Loecker <jelle@develry.be>
13
+ * @since 0.0.1
14
+ * @version 0.3.0
15
+ */
16
+ var MediaRaw = Function.inherits('Alchemy.Model', function MediaRaw(options) {
17
+ MediaRaw.super.call(this, options);
18
+ this.MediaType = Classes.Alchemy.MediaType;
19
+ });
20
+
21
+ /**
22
+ * Constitute the class wide schema
23
+ *
24
+ * @author Jelle De Loecker <jelle@develry.be>
25
+ * @since 0.2.0
26
+ * @version 0.4.0
27
+ */
28
+ MediaRaw.constitute(function addFields() {
29
+
30
+ this.addField('name', 'String');
31
+ this.addField('extension', 'String');
32
+
33
+ // Remember the origin of the file (url or path)
34
+ this.addField('origin', 'String');
35
+
36
+ // Hash & size should be one unique index together
37
+ this.addField('hash', 'String');
38
+ this.addField('size', 'Number');
39
+ this.addField('mimetype', 'String');
40
+ this.addField('type', 'String');
41
+ this.addField('extra', 'Object');
42
+ });
43
+
44
+ MediaRaw.setProperty('basePath', alchemy.plugins.media.path);
45
+ MediaRaw.setProperty('hash', alchemy.plugins.media.hash);
46
+ MediaRaw.setProperty('types', alchemy.getClassGroup('media_type'));
47
+
48
+ /**
49
+ * Path to this file
50
+ *
51
+ * @author Jelle De Loecker <jelle@develry.be>
52
+ * @since 0.4.1
53
+ * @version 0.4.1
54
+ *
55
+ * @type {String}
56
+ */
57
+ MediaRaw.Document.setFieldGetter(function path() {
58
+ return this.$model.getPathFromId(this._id);
59
+ });
60
+
61
+ /**
62
+ * Export the actual file
63
+ *
64
+ * @author Jelle De Loecker <jelle@develry.be>
65
+ * @since 0.4.1
66
+ * @version 0.4.1
67
+ *
68
+ * @type {Stream} output
69
+ *
70
+ * @return {Pledge}
71
+ */
72
+ MediaRaw.Document.setMethod(function extraExportToStream(output) {
73
+
74
+ var that = this,
75
+ path = this.path,
76
+ stats;
77
+
78
+ if (!path) {
79
+ return Pledge.resolve();
80
+ }
81
+
82
+ return Function.series(function getStats(next) {
83
+ fs.stat(path, function checked(err, result) {
84
+
85
+ if (err) {
86
+ return next();
87
+ }
88
+
89
+ stats = result;
90
+
91
+ next();
92
+ });
93
+ }, function streamFile(next) {
94
+
95
+ if (!stats) {
96
+ return next();
97
+ }
98
+
99
+ let hbuf = Buffer.allocUnsafe(5);
100
+
101
+ // 0xFF indicates an extra export
102
+ hbuf.writeUInt8(0xFF, 0);
103
+
104
+ // Now say how long it is
105
+ hbuf.writeUInt32BE(stats.size, 1);
106
+
107
+ // Write the header to the stream
108
+ output.write(hbuf);
109
+
110
+ // Create a read stream to the file
111
+ let read_stream = fs.createReadStream(path);
112
+
113
+ // Listen for the data
114
+ read_stream.on('data', function onData(data) {
115
+ output.write(data);
116
+ });
117
+
118
+ // Listen for the stream end
119
+ read_stream.on('end', next);
120
+
121
+ }, function done(err) {
122
+
123
+ });
124
+ });
125
+
126
+ /**
127
+ * Import the actual file
128
+ *
129
+ * @author Jelle De Loecker <jelle@develry.be>
130
+ * @since 0.4.1
131
+ * @version 0.4.1
132
+ *
133
+ * @type {Stream} input
134
+ *
135
+ * @return {Pledge}
136
+ */
137
+ MediaRaw.Document.setMethod(function extraImportFromStream(input) {
138
+
139
+ var that = this,
140
+ file_path = this.path,
141
+ pledge = new Pledge(),
142
+ stream;
143
+
144
+ input.pause();
145
+
146
+ alchemy.createDir(path.dirname(file_path), function done(err) {
147
+
148
+ if (err) {
149
+ return pledge.reject(err);
150
+ }
151
+
152
+ input.resume();
153
+
154
+ stream = fs.createWriteStream(file_path);
155
+
156
+ input.pipe(stream);
157
+
158
+ input.on('finish', function onEnd() {
159
+ pledge.resolve();
160
+ });
161
+ });
162
+
163
+ return pledge;
164
+ });
165
+
166
+ /**
167
+ * Add a file
168
+ *
169
+ * @author Jelle De Loecker <jelle@develry.be>
170
+ * @since 0.0.1
171
+ * @version 0.4.2
172
+ *
173
+ * @param {String} file The path to the file, can be a URL
174
+ * @param {Object} options
175
+ * @param {Function} callback
176
+ */
177
+ MediaRaw.setMethod(function addFile(file, options, callback) {
178
+
179
+ var that = this,
180
+ removeOriginal,
181
+ MediaFile;
182
+
183
+ if (typeof options == 'function') {
184
+ callback = options;
185
+ options = {};
186
+ }
187
+
188
+ // If the given file is actually a url, we'll need to download it first
189
+ if (file.startsWith('http://') || file.startsWith('https://')) {
190
+
191
+ // Set the url as the origin
192
+ options.origin = file;
193
+
194
+ // Don't keep the original, temporary file
195
+ options.move = true;
196
+
197
+ alchemy.downloadFile(file, options, function downloadedFile(err, tempfile, filename) {
198
+
199
+ if (err) {
200
+ return callback(err);
201
+ }
202
+
203
+ if (!options.filename) {
204
+ options.filename = filename;
205
+ }
206
+
207
+ if (!options.filename) {
208
+ options.filename = Url.parse(file).pathname.split('/').last();
209
+ }
210
+
211
+ if (!options.name) {
212
+ options.name = options.filename.beforeLast('.') || options.filename;
213
+ }
214
+
215
+ that.addFile(tempfile, options, callback);
216
+ });
217
+
218
+ return;
219
+ };
220
+
221
+ removeOriginal = false;
222
+ MediaFile = this.getModel('MediaFile');
223
+
224
+ if (typeof options.move == 'undefined') {
225
+ options.move = false;
226
+ }
227
+
228
+ if (options.move) {
229
+ removeOriginal = true;
230
+ }
231
+
232
+ alchemy.getFileInfo(file, {hash: this.hashType}, function gotFileInfo(err, info) {
233
+
234
+ var type;
235
+
236
+ if (err) {
237
+ return callback(err);
238
+ }
239
+
240
+ type = that.MediaType.determineType(info.mimetype, options);
241
+
242
+ type.normalize(file, info, function afterNormalize(err, rawPath, rawInfo, rawExtra, extra) {
243
+
244
+ if (err) {
245
+ return callback(err);
246
+ }
247
+
248
+ options.rawExtra = rawExtra;
249
+ options.move = true;
250
+ options.name = options.name || info.name;
251
+ options.extension = info.extension;
252
+
253
+ // Store the raw file in the database & filesystem
254
+ that.storeFile(rawPath, options, function afterRawStore(err, id, item, createdNew) {
255
+
256
+ if (err) {
257
+ return callback(err);
258
+ }
259
+
260
+ var FileData = {
261
+ MediaFile: {
262
+ media_raw_id : item._id,
263
+ name : options.name || info.name,
264
+ filename : options.filename || info.filename,
265
+ extra : extra,
266
+ type : type.typeName
267
+ }
268
+ };
269
+
270
+ if (createdNew || (!createdNew && !options.reusefile)) {
271
+
272
+ MediaFile.save(FileData, {document: false}, function savedNewRecord(err, result) {
273
+ if (err) return callback(err);
274
+ callback(null, result[0]);
275
+ });
276
+ } else {
277
+
278
+ MediaFile.find('first', {conditions: {media_raw_id: item._id}, recursive: 0, document: false}, function foundRecord(err, items) {
279
+
280
+ if (err) {
281
+ return callback(err);
282
+ }
283
+
284
+ items = items[0];
285
+
286
+ if (items && items.MediaFile) {
287
+ callback(null, items.MediaFile)
288
+ } else {
289
+ MediaFile.save(FileData, {document: false}, function(err, result) {
290
+ if (err) return callback(err);
291
+ callback(null, result[0]);
292
+ });
293
+ }
294
+ });
295
+ }
296
+ });
297
+ });
298
+ });
299
+ });
300
+
301
+ /**
302
+ * Get a file based on its raw id
303
+ *
304
+ * @author Jelle De Loecker <jelle@develry.be>
305
+ * @since 0.0.1
306
+ * @version 0.2.0
307
+ *
308
+ * @param {String|ObjectID} id
309
+ * @param {Function} callback
310
+ */
311
+ MediaRaw.setMethod(function getFile(id, callback) {
312
+
313
+ var that = this,
314
+ options = {
315
+ conditions: {
316
+ '_id': id
317
+ },
318
+ document: false
319
+ };
320
+
321
+ this.find('first', options, function gotFileRecord(err, result) {
322
+
323
+ var item;
324
+
325
+ if (err) {
326
+ return callback(err);
327
+ }
328
+
329
+ if (!result.length) {
330
+ return callback(new Error('No image found'));
331
+ }
332
+
333
+
334
+ item = result.MediaRaw;
335
+
336
+ item.path = that.getPathFromId(item._id);
337
+
338
+ callback(null, item);
339
+ });
340
+ });
341
+
342
+ /**
343
+ * Store the given raw file in our own folder structure and database,
344
+ * this makes sure there are no duplicates
345
+ *
346
+ * @author Jelle De Loecker <jelle@develry.be>
347
+ * @since 0.0.1
348
+ * @version 0.4.2
349
+ *
350
+ * @param {String} file The path to the file
351
+ * @param {Object} options Optional options
352
+ * @param {Function} callback The callback that gets the id & item
353
+ */
354
+ MediaRaw.setMethod(function storeFile(file, options, callback) {
355
+
356
+ var that = this,
357
+ transferType;
358
+
359
+ if (typeof options == 'function') {
360
+ callback = options;
361
+ options = {};
362
+ }
363
+
364
+ if (!file) {
365
+ return callback(new Error('Unable to store file: given path string is empty'));
366
+ }
367
+
368
+ if (typeof options.move == 'undefined') {
369
+ options.move = false;
370
+ }
371
+
372
+ if (options.move) {
373
+ transferType = 'moveFile';
374
+ } else {
375
+ transferType = 'copyFile';
376
+ }
377
+
378
+ prepareId.call(this, file, options, function gotDatabaseInfo(err, alreadyCopied, id, item) {
379
+
380
+ var targetPath;
381
+
382
+ if (err) {
383
+ return callback(err);
384
+ }
385
+
386
+ targetPath = that.getPathFromId(id);
387
+ item.path = targetPath;
388
+
389
+ if (alreadyCopied) {
390
+ callback(null, id, item, false);
391
+
392
+ // If we wanted to move this file, remove the original
393
+ if (options.move) {
394
+ fs.unlink(file, function afterUnlink(){});
395
+ }
396
+
397
+ } else if (targetPath) {
398
+ alchemy[transferType](file, targetPath, function afterTransfer(err) {
399
+ if (err) {
400
+ callback(err);
401
+ } else {
402
+ item.path = targetPath;
403
+ callback(null, id, item, true);
404
+ }
405
+ });
406
+ } else {
407
+ return callback(new Error('Could not copy file to undefined target'));
408
+ }
409
+
410
+ });
411
+ });
412
+
413
+ /**
414
+ * Look up some information on the given file, store it in the
415
+ * database if it is not there yet, and return the id and info.
416
+ *
417
+ * This does NOT copy the file to our own folder structure!
418
+ *
419
+ * @author Jelle De Loecker <jelle@develry.be>
420
+ * @since 0.0.1
421
+ * @version 0.4.2
422
+ */
423
+ var prepareId = function prepareId(file, options, callback) {
424
+
425
+ var that = this,
426
+ data;
427
+
428
+ if (!file) {
429
+ return callback(new Error('Unable to prepare file ID: given path is empty'));
430
+ }
431
+
432
+ alchemy.getFileInfo(file, {hash: this.hashType}, function gotFileInfo(err, info) {
433
+
434
+ if (err) {
435
+ return callback(err);
436
+ }
437
+
438
+ // See if this file already exists,
439
+ // based on the hash and the file size
440
+ var search_options = {
441
+ conditions: {
442
+ hash: info.hash,
443
+ size: info.size
444
+ },
445
+ document: false
446
+ };
447
+
448
+ that.find('first', search_options, function gotFindResult(err, result) {
449
+
450
+ if (err) {
451
+ return callback(err);
452
+ }
453
+
454
+ result = result[0];
455
+
456
+ // Return the existing id if we found a match
457
+ if (result) {
458
+ callback(null, true, result.MediaRaw._id, result.MediaRaw);
459
+ } else {
460
+
461
+ // If not: save the data to the database
462
+ data = {
463
+ MediaRaw: {
464
+ name : options.name || info.name,
465
+ extension : options.extension || info.extension,
466
+ mimetype : info.mimetype,
467
+ hash : info.hash,
468
+ size : info.size,
469
+ extra : options.rawExtra
470
+ }
471
+ };
472
+
473
+ if (options.origin) {
474
+ data.MediaRaw.origin = options.origin;
475
+ }
476
+
477
+ that.save(data, {document: false}, function getSaveResult(err, result) {
478
+
479
+ if (err) {
480
+ return callback(err);
481
+ }
482
+
483
+ result = result[0];
484
+
485
+ if (result) {
486
+ callback(null, false, result._id, result);
487
+ }
488
+ });
489
+ }
490
+ });
491
+ });
492
+ };
493
+
494
+ /**
495
+ * Construct the filepath of the given ObjectId
496
+ *
497
+ * @author Jelle De Loecker <jelle@develry.be>
498
+ * @since 0.0.1
499
+ * @version 0.2.0
500
+ *
501
+ * @param {ObjectID|String} objectId
502
+ *
503
+ * @return {String} The (expected) path to the file
504
+ */
505
+ MediaRaw.setMethod(function getPathFromId(objectId) {
506
+
507
+ var filePath,
508
+ month,
509
+ year,
510
+ date;
511
+
512
+ // Ensure an objectid
513
+ if (!(objectId = alchemy.castObjectId(objectId))) {
514
+ return false;
515
+ }
516
+
517
+ date = objectId.getTimestamp();
518
+ year = String(date.getFullYear());
519
+ month = (date.getMonth()+1);
520
+
521
+ if (month < 10) {
522
+ month = '0' + month;
523
+ } else {
524
+ month = String(month);
525
+ }
526
+
527
+ filePath = path.resolve(this.basePath, year, month, String(objectId));
528
+
529
+ return filePath;
530
530
  });