mirror of
https://github.com/YunoHost-Apps/rocketchat_ynh.git
synced 2024-09-03 20:16:25 +02:00
1183 lines
136 KiB
JavaScript
1183 lines
136 KiB
JavaScript
(function () {
|
|
|
|
/* Imports */
|
|
var Meteor = Package.meteor.Meteor;
|
|
var check = Package.check.check;
|
|
var Match = Package.check.Match;
|
|
var CollectionHooks = Package['matb33:collection-hooks'].CollectionHooks;
|
|
var MongoInternals = Package.mongo.MongoInternals;
|
|
var Mongo = Package.mongo.Mongo;
|
|
var _ = Package.underscore._;
|
|
var WebApp = Package.webapp.WebApp;
|
|
var main = Package.webapp.main;
|
|
var WebAppInternals = Package.webapp.WebAppInternals;
|
|
|
|
/* Package-scope variables */
|
|
var UploadFS, domain, fs, Future, http, https, mkdirp, stream, zlib;
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
var stores = {}; // 1
|
|
// 2
|
|
UploadFS = { // 3
|
|
/** // 4
|
|
* Contains all stores // 5
|
|
*/ // 6
|
|
store: {}, // 7
|
|
/** // 8
|
|
* Returns the temporary file path // 9
|
|
* @param fileId // 10
|
|
* @return {string} // 11
|
|
*/ // 12
|
|
getTempFilePath: function (fileId) { // 13
|
|
return UploadFS.config.tmpDir + '/' + fileId; // 14
|
|
}, // 15
|
|
/** // 16
|
|
* Returns the store by its name // 17
|
|
* @param name // 18
|
|
* @return {UploadFS.Store} // 19
|
|
*/ // 20
|
|
getStore: function (name) { // 21
|
|
return stores[name]; // 22
|
|
}, // 23
|
|
/** // 24
|
|
* Returns all stores // 25
|
|
* @return {object} // 26
|
|
*/ // 27
|
|
getStores: function () { // 28
|
|
return stores; // 29
|
|
}, // 30
|
|
/** // 31
|
|
* Imports a file from a URL // 32
|
|
* @param url // 33
|
|
* @param file // 34
|
|
* @param store // 35
|
|
* @param callback // 36
|
|
*/ // 37
|
|
importFromURL: function (url, file, store, callback) { // 38
|
|
Meteor.call('ufsImportURL', url, file, store && store.getName(), callback); // 39
|
|
} // 40
|
|
}; // 41
|
|
// 42
|
|
if (Meteor.isServer) { // 43
|
|
/** // 44
|
|
* Generates a random token using a pattern (xy) // 45
|
|
* @param pattern // 46
|
|
* @return {string} // 47
|
|
*/ // 48
|
|
UploadFS.generateToken = function (pattern) { // 49
|
|
return (pattern || 'xyxyxyxyxy').replace(/[xy]/g, function (c) { // 50
|
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); // 51
|
|
var s = v.toString(16); // 52
|
|
return Math.round(Math.random()) ? s.toUpperCase() : s; // 53
|
|
}); // 54
|
|
}; // 55
|
|
} // 56
|
|
// 57
|
|
if (Meteor.isClient) { // 58
|
|
/** // 59
|
|
* Returns file and data as ArrayBuffer for each files in the event // 60
|
|
* @param event // 61
|
|
* @param callback // 62
|
|
*/ // 63
|
|
UploadFS.readAsArrayBuffer = function (event, callback) { // 64
|
|
if (typeof callback !== 'function') { // 65
|
|
throw new TypeError('callback is not a function'); // 66
|
|
} // 67
|
|
// 68
|
|
var files = event.target.files; // 69
|
|
// 70
|
|
for (var i = 0; i < files.length; i += 1) { // 71
|
|
var file = files[i]; // 72
|
|
// 73
|
|
(function (file) { // 74
|
|
var reader = new FileReader(); // 75
|
|
reader.onload = function (ev) { // 76
|
|
callback.call(UploadFS, ev.target.result, file); // 77
|
|
}; // 78
|
|
reader.readAsArrayBuffer(file); // 79
|
|
})(file); // 80
|
|
} // 81
|
|
}; // 82
|
|
// 83
|
|
/** // 84
|
|
* Opens the browser's file selection dialog // 85
|
|
* @param callback // 86
|
|
*/ // 87
|
|
UploadFS.selectFiles = function (callback) { // 88
|
|
var img = document.createElement('input'); // 89
|
|
img.type = 'file'; // 90
|
|
img.onchange = callback; // 91
|
|
img.click(); // 92
|
|
} // 93
|
|
} // 94
|
|
// 95
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs-config.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/** // 1
|
|
* UploadFS configuration // 2
|
|
* @param options // 3
|
|
* @constructor // 4
|
|
*/ // 5
|
|
UploadFS.Config = function (options) { // 6
|
|
// Set default options // 7
|
|
options = _.extend({ // 8
|
|
https: false, // 9
|
|
simulateReadDelay: 0, // 10
|
|
simulateUploadSpeed: 0, // 11
|
|
simulateWriteDelay: 0, // 12
|
|
storesPath: 'ufs', // 13
|
|
tmpDir: '/tmp/ufs' // 14
|
|
}, options); // 15
|
|
// 16
|
|
// Check options // 17
|
|
if (typeof options.https !== 'boolean') { // 18
|
|
throw new TypeError('https is not a function'); // 19
|
|
} // 20
|
|
if (typeof options.simulateReadDelay !== 'number') { // 21
|
|
throw new Meteor.Error('simulateReadDelay is not a number'); // 22
|
|
} // 23
|
|
if (typeof options.simulateUploadSpeed !== 'number') { // 24
|
|
throw new Meteor.Error('simulateUploadSpeed is not a number'); // 25
|
|
} // 26
|
|
if (typeof options.simulateWriteDelay !== 'number') { // 27
|
|
throw new Meteor.Error('simulateWriteDelay is not a number'); // 28
|
|
} // 29
|
|
if (typeof options.storesPath !== 'string') { // 30
|
|
throw new Meteor.Error('storesPath is not a string'); // 31
|
|
} // 32
|
|
if (typeof options.tmpDir !== 'string') { // 33
|
|
throw new Meteor.Error('tmpDir is not a string'); // 34
|
|
} // 35
|
|
// 36
|
|
// Public attributes // 37
|
|
this.https = options.https; // 38
|
|
this.simulateReadDelay = parseInt(options.simulateReadDelay); // 39
|
|
this.simulateUploadSpeed = parseInt(options.simulateUploadSpeed); // 40
|
|
this.simulateWriteDelay = parseInt(options.simulateWriteDelay); // 41
|
|
this.storesPath = options.storesPath; // 42
|
|
this.tmpDir = options.tmpDir; // 43
|
|
}; // 44
|
|
// 45
|
|
/** // 46
|
|
* Simulation read delay in milliseconds // 47
|
|
* @type {number} // 48
|
|
*/ // 49
|
|
UploadFS.Config.prototype.simulateReadDelay = 0; // 50
|
|
// 51
|
|
/** // 52
|
|
* Simulation upload speed in milliseconds // 53
|
|
* @type {number} // 54
|
|
*/ // 55
|
|
UploadFS.Config.prototype.simulateUploadSpeed = 0; // 56
|
|
// 57
|
|
/** // 58
|
|
* Simulation write delay in milliseconds // 59
|
|
* @type {number} // 60
|
|
*/ // 61
|
|
UploadFS.Config.prototype.simulateWriteDelay = 0; // 62
|
|
// 63
|
|
/** // 64
|
|
* URL path to stores // 65
|
|
* @type {string} // 66
|
|
*/ // 67
|
|
UploadFS.Config.prototype.storesPath = null; // 68
|
|
// 69
|
|
/** // 70
|
|
* Local temporary directory for uploading files // 71
|
|
* @type {string} // 72
|
|
*/ // 73
|
|
UploadFS.Config.prototype.tmpDir = null; // 74
|
|
// 75
|
|
/** // 76
|
|
* Global configuration // 77
|
|
* @type {UploadFS.Config} // 78
|
|
*/ // 79
|
|
UploadFS.config = new UploadFS.Config(); // 80
|
|
// 81
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs-filter.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/** // 1
|
|
* File filter // 2
|
|
* @param options // 3
|
|
* @constructor // 4
|
|
*/ // 5
|
|
UploadFS.Filter = function (options) { // 6
|
|
var self = this; // 7
|
|
// 8
|
|
// Set default options // 9
|
|
options = _.extend({ // 10
|
|
contentTypes: null, // 11
|
|
extensions: null, // 12
|
|
minSize: 1, // 13
|
|
maxSize: 0, // 14
|
|
onCheck: null // 15
|
|
}, options); // 16
|
|
// 17
|
|
// Check options // 18
|
|
if (options.contentTypes && !(options.contentTypes instanceof Array)) { // 19
|
|
throw new TypeError('contentTypes is not an Array'); // 20
|
|
} // 21
|
|
if (options.extensions && !(options.extensions instanceof Array)) { // 22
|
|
throw new TypeError('extensions is not an Array'); // 23
|
|
} // 24
|
|
if (typeof options.minSize !== 'number') { // 25
|
|
throw new TypeError('minSize is not a number'); // 26
|
|
} // 27
|
|
if (typeof options.maxSize !== 'number') { // 28
|
|
throw new TypeError('maxSize is not a number'); // 29
|
|
} // 30
|
|
if (options.onCheck && typeof options.onCheck !== 'function') { // 31
|
|
throw new TypeError('onCheck is not a function'); // 32
|
|
} // 33
|
|
// 34
|
|
// Private attributes // 35
|
|
var contentTypes = options.contentTypes; // 36
|
|
var extensions = options.extensions; // 37
|
|
var onCheck = options.onCheck; // 38
|
|
var maxSize = parseInt(options.maxSize); // 39
|
|
var minSize = parseInt(options.minSize); // 40
|
|
// 41
|
|
/** // 42
|
|
* Checks the file // 43
|
|
* @param file // 44
|
|
*/ // 45
|
|
self.check = function (file) { // 46
|
|
// Check size // 47
|
|
if (file.size <= 0 || file.size < self.getMinSize()) { // 48
|
|
throw new Meteor.Error('file-too-small', 'File is too small (min =' + self.getMinSize() + ')'); // 49
|
|
} // 50
|
|
if (self.getMaxSize() > 0 && file.size > self.getMaxSize()) { // 51
|
|
throw new Meteor.Error('file-too-large', 'File is too large (max = ' + self.getMaxSize() + ')'); // 52
|
|
} // 53
|
|
// Check extension // 54
|
|
if (self.getExtensions() && !_.contains(self.getExtensions(), file.extension)) { // 55
|
|
throw new Meteor.Error('invalid-file-extension', 'File extension is not accepted'); // 56
|
|
} // 57
|
|
// Check content type // 58
|
|
if (self.getContentTypes() && !checkContentType(file.type, self.getContentTypes())) { // 59
|
|
throw new Meteor.Error('invalid-file-type', 'File type is not accepted'); // 60
|
|
} // 61
|
|
// Apply custom check // 62
|
|
if (typeof onCheck === 'function' && !onCheck.call(self, file)) { // 63
|
|
throw new Meteor.Error('invalid-file', 'File does not match filter'); // 64
|
|
} // 65
|
|
}; // 66
|
|
// 67
|
|
/** // 68
|
|
* Returns the allowed content types // 69
|
|
* @return {Array} // 70
|
|
*/ // 71
|
|
self.getContentTypes = function () { // 72
|
|
return contentTypes; // 73
|
|
}; // 74
|
|
// 75
|
|
/** // 76
|
|
* Returns the allowed extensions // 77
|
|
* @return {Array} // 78
|
|
*/ // 79
|
|
self.getExtensions = function () { // 80
|
|
return extensions; // 81
|
|
}; // 82
|
|
// 83
|
|
/** // 84
|
|
* Returns the maximum file size // 85
|
|
* @return {Number} // 86
|
|
*/ // 87
|
|
self.getMaxSize = function () { // 88
|
|
return maxSize; // 89
|
|
}; // 90
|
|
// 91
|
|
/** // 92
|
|
* Returns the minimum file size // 93
|
|
* @return {Number} // 94
|
|
*/ // 95
|
|
self.getMinSize = function () { // 96
|
|
return minSize; // 97
|
|
}; // 98
|
|
// 99
|
|
/** // 100
|
|
* Checks if the file matches filter // 101
|
|
* @param file // 102
|
|
* @return {boolean} // 103
|
|
*/ // 104
|
|
self.isValid = function (file) { // 105
|
|
return !( // 106
|
|
file.size <= 0 || file.size < self.getMinSize() // 107
|
|
|| self.getMaxSize() > 0 && file.size > self.getMaxSize() // 108
|
|
|| self.getExtensions() && !_.contains(self.getExtensions(), file.extension) // 109
|
|
|| self.getContentTypes() && !checkContentType(file.type, self.getContentTypes()) // 110
|
|
|| (typeof onCheck === 'function' && !onCheck.call(self, file)) // 111
|
|
); // 112
|
|
}; // 113
|
|
}; // 114
|
|
// 115
|
|
function checkContentType(type, list) { // 116
|
|
if (_.contains(list, type)) { // 117
|
|
return true; // 118
|
|
} else { // 119
|
|
var wildCardGlob = '/*'; // 120
|
|
var wildcards = _.filter(list, function (item) { // 121
|
|
return item.indexOf(wildCardGlob) > 0; // 122
|
|
}); // 123
|
|
// 124
|
|
if (_.contains(wildcards, type.replace(/(\/.*)$/, wildCardGlob))) { // 125
|
|
return true; // 126
|
|
} // 127
|
|
} // 128
|
|
return false; // 129
|
|
} // 130
|
|
// 131
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs-store.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/** // 1
|
|
* File store // 2
|
|
* @param options // 3
|
|
* @constructor // 4
|
|
*/ // 5
|
|
UploadFS.Store = function (options) { // 6
|
|
var self = this; // 7
|
|
// 8
|
|
// Set default options // 9
|
|
options = _.extend({ // 10
|
|
collection: null, // 11
|
|
filter: null, // 12
|
|
name: null, // 13
|
|
onCopyError: null, // 14
|
|
onFinishUpload: null, // 15
|
|
onRead: null, // 16
|
|
onReadError: null, // 17
|
|
onWriteError: null, // 18
|
|
transformRead: null, // 19
|
|
transformWrite: null // 20
|
|
}, options); // 21
|
|
// 22
|
|
// Check instance // 23
|
|
if (!(self instanceof UploadFS.Store)) { // 24
|
|
throw new Error('UploadFS.Store is not an instance'); // 25
|
|
} // 26
|
|
// 27
|
|
// Check options // 28
|
|
if (!(options.collection instanceof Mongo.Collection)) { // 29
|
|
throw new TypeError('collection is not a Mongo.Collection'); // 30
|
|
} // 31
|
|
if (options.filter && !(options.filter instanceof UploadFS.Filter)) { // 32
|
|
throw new TypeError('filter is not an UploadFS.Filter'); // 33
|
|
} // 34
|
|
if (typeof options.name !== 'string') { // 35
|
|
throw new TypeError('name is not a string'); // 36
|
|
} // 37
|
|
if (UploadFS.getStore(options.name)) { // 38
|
|
throw new TypeError('name already exists'); // 39
|
|
} // 40
|
|
if (options.onCopyError && typeof options.onCopyError !== 'function') { // 41
|
|
throw new TypeError('onCopyError is not a function'); // 42
|
|
} // 43
|
|
if (options.onFinishUpload && typeof options.onFinishUpload !== 'function') { // 44
|
|
throw new TypeError('onFinishUpload is not a function'); // 45
|
|
} // 46
|
|
if (options.onRead && typeof options.onRead !== 'function') { // 47
|
|
throw new TypeError('onRead is not a function'); // 48
|
|
} // 49
|
|
if (options.onReadError && typeof options.onReadError !== 'function') { // 50
|
|
throw new TypeError('onReadError is not a function'); // 51
|
|
} // 52
|
|
if (options.onWriteError && typeof options.onWriteError !== 'function') { // 53
|
|
throw new TypeError('onWriteError is not a function'); // 54
|
|
} // 55
|
|
if (options.transformRead && typeof options.transformRead !== 'function') { // 56
|
|
throw new TypeError('transformRead is not a function'); // 57
|
|
} // 58
|
|
if (options.transformWrite && typeof options.transformWrite !== 'function') { // 59
|
|
throw new TypeError('transformWrite is not a function'); // 60
|
|
} // 61
|
|
// 62
|
|
// Public attributes // 63
|
|
self.onCopyError = options.onCopyError || self.onCopyError; // 64
|
|
self.onFinishUpload = options.onFinishUpload || self.onFinishUpload; // 65
|
|
self.onRead = options.onRead || self.onRead; // 66
|
|
self.onReadError = options.onReadError || self.onReadError; // 67
|
|
self.onWriteError = options.onWriteError || self.onWriteError; // 68
|
|
// 69
|
|
// Private attributes // 70
|
|
var collection = options.collection; // 71
|
|
var copyTo = options.copyTo; // 72
|
|
var filter = options.filter; // 73
|
|
var name = options.name; // 74
|
|
var transformRead = options.transformRead; // 75
|
|
var transformWrite = options.transformWrite; // 76
|
|
// 77
|
|
// Add the store to the list // 78
|
|
UploadFS.getStores()[name] = self; // 79
|
|
// 80
|
|
/** // 81
|
|
* Creates the file in the collection // 82
|
|
* @param file // 83
|
|
* @return {string} // 84
|
|
*/ // 85
|
|
self.create = function (file) { // 86
|
|
check(file, Object); // 87
|
|
file.store = name; // 88
|
|
return self.getCollection().insert(file); // 89
|
|
}; // 90
|
|
// 91
|
|
/** // 92
|
|
* Returns the collection // 93
|
|
* @return {Mongo.Collection} // 94
|
|
*/ // 95
|
|
self.getCollection = function () { // 96
|
|
return collection; // 97
|
|
}; // 98
|
|
// 99
|
|
/** // 100
|
|
* Returns the file filter // 101
|
|
* @return {UploadFS.Filter} // 102
|
|
*/ // 103
|
|
self.getFilter = function () { // 104
|
|
return filter; // 105
|
|
}; // 106
|
|
// 107
|
|
/** // 108
|
|
* Returns the store name // 109
|
|
* @return {string} // 110
|
|
*/ // 111
|
|
self.getName = function () { // 112
|
|
return name; // 113
|
|
}; // 114
|
|
// 115
|
|
// 116
|
|
if (Meteor.isServer) { // 117
|
|
// 118
|
|
/** // 119
|
|
* Copies the file to a store // 120
|
|
* @param fileId // 121
|
|
* @param store // 122
|
|
* @param callback // 123
|
|
*/ // 124
|
|
self.copy = function (fileId, store, callback) { // 125
|
|
check(fileId, String); // 126
|
|
// 127
|
|
if (!(store instanceof UploadFS.Store)) { // 128
|
|
throw new TypeError('store is not an UploadFS.store.Store'); // 129
|
|
} // 130
|
|
// 131
|
|
// Get original file // 132
|
|
var file = self.getCollection().findOne(fileId); // 133
|
|
if (!file) { // 134
|
|
throw new Meteor.Error(404, 'File not found'); // 135
|
|
} // 136
|
|
// 137
|
|
// Prepare copy // 138
|
|
var copy = _.omit(file, '_id', 'url'); // 139
|
|
copy.originalStore = self.getName(); // 140
|
|
copy.originalId = fileId; // 141
|
|
// 142
|
|
// Create the copy // 143
|
|
var copyId = store.create(copy); // 144
|
|
// 145
|
|
// Get original stream // 146
|
|
var rs = self.getReadStream(fileId, file); // 147
|
|
// 148
|
|
// Copy file data // 149
|
|
store.write(rs, copyId, Meteor.bindEnvironment(function (err) { // 150
|
|
if (err) { // 151
|
|
store.getCollection().remove(copyId); // 152
|
|
self.onCopyError.call(self, err, fileId, file); // 153
|
|
} // 154
|
|
if (typeof callback === 'function') { // 155
|
|
callback.call(self, err, copyId, copy, store); // 156
|
|
} // 157
|
|
})); // 158
|
|
}; // 159
|
|
// 160
|
|
/** // 161
|
|
* Transforms the file on reading // 162
|
|
* @param from // 163
|
|
* @param to // 164
|
|
* @param fileId // 165
|
|
* @param file // 166
|
|
* @param request // 167
|
|
* @param headers // 168
|
|
*/ // 169
|
|
self.transformRead = function (from, to, fileId, file, request, headers) { // 170
|
|
if (typeof transformRead === 'function') { // 171
|
|
transformRead.call(self, from, to, fileId, file, request, headers); // 172
|
|
} else { // 173
|
|
from.pipe(to); // 174
|
|
} // 175
|
|
}; // 176
|
|
// 177
|
|
/** // 178
|
|
* Transforms the file on writing // 179
|
|
* @param from // 180
|
|
* @param to // 181
|
|
* @param fileId // 182
|
|
* @param file // 183
|
|
*/ // 184
|
|
self.transformWrite = function (from, to, fileId, file) { // 185
|
|
if (typeof transformWrite === 'function') { // 186
|
|
transformWrite.call(self, from, to, fileId, file); // 187
|
|
} else { // 188
|
|
from.pipe(to); // 189
|
|
} // 190
|
|
}; // 191
|
|
// 192
|
|
/** // 193
|
|
* Writes the file to the store // 194
|
|
* @param rs // 195
|
|
* @param fileId // 196
|
|
* @param callback // 197
|
|
*/ // 198
|
|
self.write = function (rs, fileId, callback) { // 199
|
|
var file = self.getCollection().findOne(fileId); // 200
|
|
var ws = self.getWriteStream(fileId, file); // 201
|
|
// 202
|
|
var errorHandler = Meteor.bindEnvironment(function (err) { // 203
|
|
self.getCollection().remove(fileId); // 204
|
|
self.onWriteError.call(self, err, fileId, file); // 205
|
|
callback.call(self, err); // 206
|
|
}); // 207
|
|
// 208
|
|
ws.on('error', errorHandler); // 209
|
|
ws.on('finish', Meteor.bindEnvironment(function () { // 210
|
|
var size = 0; // 211
|
|
var from = self.getReadStream(fileId, file); // 212
|
|
// 213
|
|
from.on('data', function (data) { // 214
|
|
size += data.length; // 215
|
|
}); // 216
|
|
from.on('end', Meteor.bindEnvironment(function () { // 217
|
|
// Set file attribute // 218
|
|
file.complete = true; // 219
|
|
file.progress = 1; // 220
|
|
file.size = size; // 221
|
|
file.token = UploadFS.generateToken(); // 222
|
|
file.uploading = false; // 223
|
|
file.uploadedAt = new Date(); // 224
|
|
file.url = self.getFileURL(fileId); // 225
|
|
// 226
|
|
// Sets the file URL when file transfer is complete, // 227
|
|
// this way, the image will loads entirely. // 228
|
|
self.getCollection().update(fileId, { // 229
|
|
$set: { // 230
|
|
complete: file.complete, // 231
|
|
progress: file.progress, // 232
|
|
size: file.size, // 233
|
|
token: file.token, // 234
|
|
uploading: file.uploading, // 235
|
|
uploadedAt: file.uploadedAt, // 236
|
|
url: file.url // 237
|
|
} // 238
|
|
}); // 239
|
|
// 240
|
|
// Return file info // 241
|
|
callback.call(self, null, file); // 242
|
|
// 243
|
|
// Execute callback // 244
|
|
if (typeof self.onFinishUpload == 'function') { // 245
|
|
self.onFinishUpload.call(self, file); // 246
|
|
} // 247
|
|
// 248
|
|
// Simulate write speed // 249
|
|
if (UploadFS.config.simulateWriteDelay) { // 250
|
|
Meteor._sleepForMs(UploadFS.config.simulateWriteDelay); // 251
|
|
} // 252
|
|
// 253
|
|
// Copy file to other stores // 254
|
|
if (copyTo instanceof Array) { // 255
|
|
for (var i = 0; i < copyTo.length; i += 1) { // 256
|
|
var store = copyTo[i]; // 257
|
|
// 258
|
|
if (!store.getFilter() || store.getFilter().isValid(file)) { // 259
|
|
self.copy(fileId, store); // 260
|
|
} // 261
|
|
} // 262
|
|
} // 263
|
|
})); // 264
|
|
})); // 265
|
|
// 266
|
|
// Execute transformation // 267
|
|
self.transformWrite(rs, ws, fileId, file); // 268
|
|
}; // 269
|
|
} // 270
|
|
// 271
|
|
// Code executed before inserting file // 272
|
|
collection.before.insert(function (userId, file) { // 273
|
|
if (typeof file.name !== 'string' || !file.name.length) { // 274
|
|
throw new Meteor.Error(400, "file name not defined"); // 275
|
|
} // 276
|
|
if (typeof file.store !== 'string' || !file.store.length) { // 277
|
|
throw new Meteor.Error(400, "file store not defined"); // 278
|
|
} // 279
|
|
if (typeof file.complete !== 'boolean') { // 280
|
|
file.complete = false; // 281
|
|
} // 282
|
|
if (typeof file.uploading !== 'boolean') { // 283
|
|
file.uploading = true; // 284
|
|
} // 285
|
|
file.extension = file.name && file.name.substr((~-file.name.lastIndexOf('.') >>> 0) + 2).toLowerCase(); // 286
|
|
file.progress = parseFloat(file.progress) || 0; // 287
|
|
file.size = parseInt(file.size) || 0; // 288
|
|
file.userId = file.userId || userId; // 289
|
|
}); // 290
|
|
// 291
|
|
// Code executed after removing file // 292
|
|
collection.after.remove(function (userId, file) { // 293
|
|
if (Meteor.isServer) { // 294
|
|
if (copyTo instanceof Array) { // 295
|
|
for (var i = 0; i < copyTo.length; i += 1) { // 296
|
|
// Remove copies in stores // 297
|
|
copyTo[i].getCollection().remove({originalId: file._id}); // 298
|
|
} // 299
|
|
} // 300
|
|
} // 301
|
|
}); // 302
|
|
// 303
|
|
// Code executed before removing file // 304
|
|
collection.before.remove(function (userId, file) { // 305
|
|
if (Meteor.isServer) { // 306
|
|
// Delete the physical file in the store // 307
|
|
self.delete(file._id); // 308
|
|
// 309
|
|
var tmpFile = UploadFS.getTempFilePath(file._id); // 310
|
|
// 311
|
|
// Delete the temp file // 312
|
|
fs.stat(tmpFile, function (err) { // 313
|
|
!err && fs.unlink(tmpFile, function (err) { // 314
|
|
err && console.error('ufs: cannot delete temp file at ' + tmpFile + ' (' + err.message + ')'); // 315
|
|
}); // 316
|
|
}); // 317
|
|
} // 318
|
|
}); // 319
|
|
// 320
|
|
collection.deny({ // 321
|
|
// Test filter on file insertion // 322
|
|
insert: function (userId, file) { // 323
|
|
if (filter instanceof UploadFS.Filter) { // 324
|
|
filter.check(file); // 325
|
|
} // 326
|
|
return typeof options.insert === 'function' // 327
|
|
&& !options.insert.apply(this, arguments); // 328
|
|
} // 329
|
|
}); // 330
|
|
}; // 331
|
|
// 332
|
|
/** // 333
|
|
* Returns the file URL // 334
|
|
* @param fileId // 335
|
|
*/ // 336
|
|
UploadFS.Store.prototype.getFileURL = function (fileId) { // 337
|
|
var file = this.getCollection().findOne(fileId, { // 338
|
|
fields: {name: 1} // 339
|
|
}); // 340
|
|
return file && this.getURL() + '/' + fileId + '/' + encodeURIComponent(file.name); // 341
|
|
}; // 342
|
|
// 343
|
|
/** // 344
|
|
* Returns the store URL // 345
|
|
*/ // 346
|
|
UploadFS.Store.prototype.getURL = function () { // 347
|
|
return Meteor.absoluteUrl(UploadFS.config.storesPath + '/' + this.getName(), { // 348
|
|
secure: UploadFS.config.https // 349
|
|
}); // 350
|
|
}; // 351
|
|
// 352
|
|
if (Meteor.isServer) { // 353
|
|
/** // 354
|
|
* Deletes a file async // 355
|
|
* @param fileId // 356
|
|
* @param callback // 357
|
|
*/ // 358
|
|
UploadFS.Store.prototype.delete = function (fileId, callback) { // 359
|
|
throw new Error('delete is not implemented'); // 360
|
|
}; // 361
|
|
// 362
|
|
/** // 363
|
|
* Returns the file read stream // 364
|
|
* @param fileId // 365
|
|
* @param file // 366
|
|
*/ // 367
|
|
UploadFS.Store.prototype.getReadStream = function (fileId, file) { // 368
|
|
throw new Error('getReadStream is not implemented'); // 369
|
|
}; // 370
|
|
// 371
|
|
/** // 372
|
|
* Returns the file write stream // 373
|
|
* @param fileId // 374
|
|
* @param file // 375
|
|
*/ // 376
|
|
UploadFS.Store.prototype.getWriteStream = function (fileId, file) { // 377
|
|
throw new Error('getWriteStream is not implemented'); // 378
|
|
}; // 379
|
|
// 380
|
|
/** // 381
|
|
* Callback for copy errors // 382
|
|
* @param err // 383
|
|
* @param fileId // 384
|
|
* @param file // 385
|
|
* @return boolean // 386
|
|
*/ // 387
|
|
UploadFS.Store.prototype.onCopyError = function (err, fileId, file) { // 388
|
|
console.error('ufs: cannot copy file "' + fileId + '" (' + err.message + ')'); // 389
|
|
}; // 390
|
|
// 391
|
|
/** // 392
|
|
* Called when a file has been uploaded // 393
|
|
* @param file // 394
|
|
*/ // 395
|
|
UploadFS.Store.prototype.onFinishUpload = function (file) { // 396
|
|
}; // 397
|
|
// 398
|
|
/** // 399
|
|
* Called when a file is read from the store // 400
|
|
* @param fileId // 401
|
|
* @param file // 402
|
|
* @param request // 403
|
|
* @param response // 404
|
|
* @return boolean // 405
|
|
*/ // 406
|
|
UploadFS.Store.prototype.onRead = function (fileId, file, request, response) { // 407
|
|
return true; // 408
|
|
}; // 409
|
|
// 410
|
|
/** // 411
|
|
* Callback for read errors // 412
|
|
* @param err // 413
|
|
* @param fileId // 414
|
|
* @param file // 415
|
|
* @return boolean // 416
|
|
*/ // 417
|
|
UploadFS.Store.prototype.onReadError = function (err, fileId, file) { // 418
|
|
console.error('ufs: cannot read file "' + fileId + '" (' + err.message + ')'); // 419
|
|
}; // 420
|
|
// 421
|
|
/** // 422
|
|
* Callback for write errors // 423
|
|
* @param err // 424
|
|
* @param fileId // 425
|
|
* @param file // 426
|
|
* @return boolean // 427
|
|
*/ // 428
|
|
UploadFS.Store.prototype.onWriteError = function (err, fileId, file) { // 429
|
|
console.error('ufs: cannot write file "' + fileId + '" (' + err.message + ')'); // 430
|
|
}; // 431
|
|
} // 432
|
|
// 433
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs-methods.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
Meteor.methods({ // 1
|
|
// 2
|
|
/** // 3
|
|
* Completes the file transfer // 4
|
|
* @param fileId // 5
|
|
* @param storeName // 6
|
|
*/ // 7
|
|
ufsComplete: function (fileId, storeName) { // 8
|
|
check(fileId, String); // 9
|
|
check(storeName, String); // 10
|
|
// 11
|
|
// Allow other uploads to run concurrently // 12
|
|
this.unblock(); // 13
|
|
// 14
|
|
var store = UploadFS.getStore(storeName); // 15
|
|
if (!store) { // 16
|
|
throw new Meteor.Error(404, 'store "' + storeName + '" does not exist'); // 17
|
|
} // 18
|
|
// Check that file exists and is owned by current user // 19
|
|
if (store.getCollection().find({_id: fileId, userId: this.userId}).count() < 1) { // 20
|
|
throw new Meteor.Error(404, 'file "' + fileId + '" does not exist'); // 21
|
|
} // 22
|
|
// 23
|
|
var fut = new Future(); // 24
|
|
var tmpFile = UploadFS.getTempFilePath(fileId); // 25
|
|
// 26
|
|
// Get the temp file // 27
|
|
var rs = fs.createReadStream(tmpFile, { // 28
|
|
flags: 'r', // 29
|
|
encoding: null, // 30
|
|
autoClose: true // 31
|
|
}); // 32
|
|
// 33
|
|
rs.on('error', Meteor.bindEnvironment(function () { // 34
|
|
store.getCollection().remove(fileId); // 35
|
|
})); // 36
|
|
// 37
|
|
// Save file in the store // 38
|
|
store.write(rs, fileId, Meteor.bindEnvironment(function (err, file) { // 39
|
|
fs.unlink(tmpFile, function (err) { // 40
|
|
err && console.error('ufs: cannot delete temp file ' + tmpFile + ' (' + err.message + ')'); // 41
|
|
}); // 42
|
|
// 43
|
|
if (err) { // 44
|
|
fut.throw(err); // 45
|
|
} else { // 46
|
|
fut.return(file); // 47
|
|
} // 48
|
|
})); // 49
|
|
return fut.wait(); // 50
|
|
}, // 51
|
|
// 52
|
|
/** // 53
|
|
* Imports a file from the URL // 54
|
|
* @param url // 55
|
|
* @param file // 56
|
|
* @param storeName // 57
|
|
* @return {*} // 58
|
|
*/ // 59
|
|
ufsImportURL: function (url, file, storeName) { // 60
|
|
check(url, String); // 61
|
|
check(file, Object); // 62
|
|
check(storeName, String); // 63
|
|
// 64
|
|
this.unblock(); // 65
|
|
// 66
|
|
var store = UploadFS.getStore(storeName); // 67
|
|
if (!store) { // 68
|
|
throw new Meteor.Error(404, 'Store "' + storeName + '" does not exist'); // 69
|
|
} // 70
|
|
// 71
|
|
try { // 72
|
|
// Extract file info // 73
|
|
if (!file.name) { // 74
|
|
file.name = url.replace(/\?.*$/, '').split('/').pop(); // 75
|
|
file.extension = file.name.split('.').pop(); // 76
|
|
file.type = 'image/' + file.extension; // 77
|
|
} // 78
|
|
// Check if file is valid // 79
|
|
if (store.getFilter() instanceof UploadFS.Filter) { // 80
|
|
store.getFilter().check(file); // 81
|
|
} // 82
|
|
// Create the file // 83
|
|
var fileId = store.create(file); // 84
|
|
// 85
|
|
} catch (err) { // 86
|
|
throw new Meteor.Error(500, err.message); // 87
|
|
} // 88
|
|
// 89
|
|
var fut = new Future(); // 90
|
|
var proto; // 91
|
|
// 92
|
|
// Detect protocol to use // 93
|
|
if (/http:\/\//i.test(url)) { // 94
|
|
proto = http; // 95
|
|
} else if (/https:\/\//i.test(url)) { // 96
|
|
proto = https; // 97
|
|
} // 98
|
|
// 99
|
|
// Download file // 100
|
|
proto.get(url, Meteor.bindEnvironment(function (res) { // 101
|
|
// Save the file in the store // 102
|
|
store.write(res, fileId, function (err, file) { // 103
|
|
if (err) { // 104
|
|
fut.throw(err); // 105
|
|
} else { // 106
|
|
fut.return(fileId); // 107
|
|
} // 108
|
|
}); // 109
|
|
})).on('error', function (err) { // 110
|
|
fut.throw(err); // 111
|
|
}); // 112
|
|
return fut.wait(); // 113
|
|
}, // 114
|
|
// 115
|
|
/** // 116
|
|
* Saves a chunk of file // 117
|
|
* @param chunk // 118
|
|
* @param fileId // 119
|
|
* @param storeName // 120
|
|
* @param progress // 121
|
|
* @return {*} // 122
|
|
*/ // 123
|
|
ufsWrite: function (chunk, fileId, storeName, progress) { // 124
|
|
check(fileId, String); // 125
|
|
check(storeName, String); // 126
|
|
check(progress, Number); // 127
|
|
// 128
|
|
this.unblock(); // 129
|
|
// 130
|
|
// Check arguments // 131
|
|
if (!(chunk instanceof Uint8Array)) { // 132
|
|
throw new Meteor.Error(400, 'chunk is not an Uint8Array'); // 133
|
|
} // 134
|
|
if (chunk.length <= 0) { // 135
|
|
throw new Meteor.Error(400, 'chunk is empty'); // 136
|
|
} // 137
|
|
// 138
|
|
var store = UploadFS.getStore(storeName); // 139
|
|
if (!store) { // 140
|
|
throw new Meteor.Error(404, 'store ' + storeName + ' does not exist'); // 141
|
|
} // 142
|
|
// 143
|
|
// Check that file exists, is not complete and is owned by current user // 144
|
|
if (store.getCollection().find({_id: fileId, complete: false, userId: this.userId}).count() < 1) { // 145
|
|
throw new Meteor.Error(404, 'file ' + fileId + ' does not exist'); // 146
|
|
} // 147
|
|
// 148
|
|
var fut = new Future(); // 149
|
|
var tmpFile = UploadFS.getTempFilePath(fileId); // 150
|
|
// 151
|
|
// Save the chunk // 152
|
|
fs.appendFile(tmpFile, new Buffer(chunk), Meteor.bindEnvironment(function (err) { // 153
|
|
if (err) { // 154
|
|
console.error('ufs: cannot write chunk of file "' + fileId + '" (' + err.message + ')'); // 155
|
|
fs.unlink(tmpFile, function (err) { // 156
|
|
err && console.error('ufs: cannot delete temp file ' + tmpFile + ' (' + err.message + ')'); // 157
|
|
}); // 158
|
|
fut.throw(err); // 159
|
|
} else { // 160
|
|
// Update completed state // 161
|
|
store.getCollection().update(fileId, { // 162
|
|
$set: {progress: progress} // 163
|
|
}); // 164
|
|
fut.return(chunk.length); // 165
|
|
} // 166
|
|
})); // 167
|
|
return fut.wait(); // 168
|
|
} // 169
|
|
}); // 170
|
|
// 171
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// packages/jalik_ufs/ufs-server.js //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
domain = Npm.require('domain'); // 1
|
|
fs = Npm.require('fs'); // 2
|
|
Future = Npm.require('fibers/future'); // 3
|
|
http = Npm.require('http'); // 4
|
|
https = Npm.require('https'); // 5
|
|
mkdirp = Npm.require('mkdirp'); // 6
|
|
stream = Npm.require('stream'); // 7
|
|
zlib = Npm.require('zlib'); // 8
|
|
// 9
|
|
Meteor.startup(function () { // 10
|
|
var path = UploadFS.config.tmpDir; // 11
|
|
var mode = '0744'; // 12
|
|
// 13
|
|
fs.stat(path, function (err) { // 14
|
|
if (err) { // 15
|
|
// Create the temp directory // 16
|
|
mkdirp(path, {mode: mode}, function (err) { // 17
|
|
if (err) { // 18
|
|
console.error('ufs: cannot create temp directory at ' + path + ' (' + err.message + ')'); // 19
|
|
} else { // 20
|
|
console.log('ufs: temp directory created at ' + path); // 21
|
|
} // 22
|
|
}); // 23
|
|
} else { // 24
|
|
// Set directory permissions // 25
|
|
fs.chmod(path, mode, function (err) { // 26
|
|
err && console.error('ufs: cannot set temp directory permissions ' + mode + ' (' + err.message + ')');
|
|
}); // 28
|
|
} // 29
|
|
}); // 30
|
|
}); // 31
|
|
// 32
|
|
// Create domain to handle errors // 33
|
|
// and possibly avoid server crashes. // 34
|
|
var d = domain.create(); // 35
|
|
// 36
|
|
d.on('error', function (err) { // 37
|
|
console.error('ufs: ' + err.message); // 38
|
|
}); // 39
|
|
// 40
|
|
// Listen HTTP requests to serve files // 41
|
|
WebApp.connectHandlers.use(function (req, res, next) { // 42
|
|
// Quick check to see if request should be catch // 43
|
|
if (req.url.indexOf(UploadFS.config.storesPath) === -1) { // 44
|
|
next(); // 45
|
|
return; // 46
|
|
} // 47
|
|
// 48
|
|
// Remove store path // 49
|
|
var path = req.url.substr(UploadFS.config.storesPath.length + 1); // 50
|
|
// 51
|
|
// Get store, file Id and file name // 52
|
|
var regExp = new RegExp('^\/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?$'); // 53
|
|
var match = regExp.exec(path); // 54
|
|
// 55
|
|
if (match !== null) { // 56
|
|
// Get store // 57
|
|
var storeName = match[1]; // 58
|
|
var store = UploadFS.getStore(storeName); // 59
|
|
// 60
|
|
if (!store) { // 61
|
|
res.writeHead(404); // 62
|
|
res.end(); // 63
|
|
return; // 64
|
|
} // 65
|
|
// 66
|
|
if (typeof store.onRead !== 'function') { // 67
|
|
console.error('ufs: store "' + storeName + '" onRead is not a function'); // 68
|
|
res.writeHead(500); // 69
|
|
res.end(); // 70
|
|
return; // 71
|
|
} // 72
|
|
// 73
|
|
// Remove file extension from file Id // 74
|
|
var index = match[2].indexOf('.'); // 75
|
|
var fileId = index !== -1 ? match[2].substr(0, index) : match[2]; // 76
|
|
// 77
|
|
// Get file from database // 78
|
|
var file = store.getCollection().findOne(fileId); // 79
|
|
if (!file) { // 80
|
|
res.writeHead(404); // 81
|
|
res.end(); // 82
|
|
return; // 83
|
|
} // 84
|
|
// 85
|
|
// Simulate read speed // 86
|
|
if (UploadFS.config.simulateReadDelay) { // 87
|
|
Meteor._sleepForMs(UploadFS.config.simulateReadDelay); // 88
|
|
} // 89
|
|
// 90
|
|
d.run(function () { // 91
|
|
// Check if the file can be accessed // 92
|
|
if (store.onRead.call(store, fileId, file, req, res)) { // 93
|
|
// Open the file stream // 94
|
|
var rs = store.getReadStream(fileId, file); // 95
|
|
var ws = new stream.PassThrough(); // 96
|
|
// 97
|
|
rs.on('error', function (err) { // 98
|
|
store.onReadError.call(store, err, fileId, file); // 99
|
|
res.end(); // 100
|
|
}); // 101
|
|
ws.on('error', function (err) { // 102
|
|
store.onReadError.call(store, err, fileId, file); // 103
|
|
res.end(); // 104
|
|
}); // 105
|
|
ws.on('close', function () { // 106
|
|
// Close output stream at the end // 107
|
|
ws.emit('end'); // 108
|
|
}); // 109
|
|
// 110
|
|
var accept = req.headers['accept-encoding'] || ''; // 111
|
|
var headers = { // 112
|
|
'Content-Type': file.type, // 113
|
|
'Content-Length': file.size // 114
|
|
}; // 115
|
|
// 116
|
|
// Transform stream // 117
|
|
store.transformRead(rs, ws, fileId, file, req, headers); // 118
|
|
// 119
|
|
// Compress data using gzip // 120
|
|
if (accept.match(/\bgzip\b/)) { // 121
|
|
headers['Content-Encoding'] = 'gzip'; // 122
|
|
delete headers['Content-Length']; // 123
|
|
res.writeHead(200, headers); // 124
|
|
ws.pipe(zlib.createGzip()).pipe(res); // 125
|
|
} // 126
|
|
// Compress data using deflate // 127
|
|
else if (accept.match(/\bdeflate\b/)) { // 128
|
|
headers['Content-Encoding'] = 'deflate'; // 129
|
|
delete headers['Content-Length']; // 130
|
|
res.writeHead(200, headers); // 131
|
|
ws.pipe(zlib.createDeflate()).pipe(res); // 132
|
|
} // 133
|
|
// Send raw data // 134
|
|
else { // 135
|
|
res.writeHead(200, headers); // 136
|
|
ws.pipe(res); // 137
|
|
} // 138
|
|
} else { // 139
|
|
res.end(); // 140
|
|
} // 141
|
|
}); // 142
|
|
// 143
|
|
} else { // 144
|
|
next(); // 145
|
|
} // 146
|
|
}); // 147
|
|
// 148
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
}).call(this);
|
|
|
|
|
|
/* Exports */
|
|
if (typeof Package === 'undefined') Package = {};
|
|
Package['jalik:ufs'] = {
|
|
UploadFS: UploadFS
|
|
};
|
|
|
|
})();
|
|
|
|
//# sourceMappingURL=jalik_ufs.js.map
|