mirror of
https://github.com/wekan/wekan.git
synced 2025-12-29 13:48:49 +01:00
Fixed Non-ASCII attachment filename will crash when downloading.
Thanks to xet7 ! Fixes #2759
This commit is contained in:
parent
843ff8eaaa
commit
c2da477735
277 changed files with 30568 additions and 52 deletions
5
packages/wekan-cfs-storage-adapter/.travis.yml
Normal file
5
packages/wekan-cfs-storage-adapter/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
20
packages/wekan-cfs-storage-adapter/LICENSE.md
Normal file
20
packages/wekan-cfs-storage-adapter/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
7
packages/wekan-cfs-storage-adapter/README.md
Normal file
7
packages/wekan-cfs-storage-adapter/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
wekan-cfs-storage-adapter
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is used by other packages to create various storage adapters.
|
||||
0
packages/wekan-cfs-storage-adapter/api.md
Normal file
0
packages/wekan-cfs-storage-adapter/api.md
Normal file
101
packages/wekan-cfs-storage-adapter/internal.api.md
Normal file
101
packages/wekan-cfs-storage-adapter/internal.api.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
> File: ["storageAdapter.server.js"](storageAdapter.server.js)
|
||||
> Where: {server}
|
||||
|
||||
-
|
||||
#############################################################################
|
||||
|
||||
STORAGE ADAPTER
|
||||
|
||||
#############################################################################
|
||||
|
||||
#### <a name="self.insert"></a>*self*.insert(fsFile, [options], [callback]) <sub><i>Server</i></sub> ####
|
||||
```
|
||||
Attempts to insert a file into the store, first running the beforeSave
|
||||
function for the store if there is one. If there is a temporary failure,
|
||||
returns (or passes to the second argument of the callback) `null`. If there
|
||||
is a permanant failure or the beforeSave function returns `false`, returns
|
||||
`false`. If the file is successfully stored, returns an object with file
|
||||
info that the FS.Collection can save.
|
||||
Also updates the `files` collection for this store to save info about this
|
||||
file.
|
||||
```
|
||||
-
|
||||
*This method __insert__ is defined in `self`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fsFile__ *{[FS.File](#FS.File)}*
|
||||
The FS.File instance to be stored.
|
||||
* __options__ *{Object}* (Optional)
|
||||
Options (currently unused)
|
||||
* __callback__ *{Function}* (Optional)
|
||||
If not provided, will block and return file info.
|
||||
|
||||
-
|
||||
|
||||
|
||||
|
||||
|
||||
> ```self.insert = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:169](storageAdapter.server.js#L169)
|
||||
|
||||
-
|
||||
|
||||
#### <a name="self.update"></a>*self*.update(fsFile, [options], [callback]) <sub><i>Server</i></sub> ####
|
||||
```
|
||||
Attempts to update a file in the store, first running the beforeSave
|
||||
function for the store if there is one. If there is a temporary failure,
|
||||
returns (or passes to the second argument of the callback) `null`. If there
|
||||
is a permanant failure or the beforeSave function returns `false`, returns
|
||||
`false`. If the file is successfully stored, returns an object with file
|
||||
info that the FS.Collection can save.
|
||||
Also updates the `files` collection for this store to save info about this
|
||||
file.
|
||||
```
|
||||
-
|
||||
*This method __update__ is defined in `self`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fsFile__ *{[FS.File](#FS.File)}*
|
||||
The FS.File instance to be stored.
|
||||
* __options__ *{Object}* (Optional)
|
||||
Options (currently unused)
|
||||
* __callback__ *{Function}* (Optional)
|
||||
If not provided, will block and return file info.
|
||||
|
||||
-
|
||||
|
||||
|
||||
|
||||
|
||||
> ```self.update = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:264](storageAdapter.server.js#L264)
|
||||
|
||||
-
|
||||
|
||||
#### <a name="self.remove"></a>*self*.remove(fsFile, [options], [callback]) <sub><i>Server</i></sub> ####
|
||||
```
|
||||
Attempts to remove a file from the store. Returns true if removed, or false.
|
||||
Also removes file info from the `files` collection for this store.
|
||||
```
|
||||
-
|
||||
*This method __remove__ is defined in `self`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fsFile__ *{[FS.File](#FS.File)}*
|
||||
The FS.File instance to be stored.
|
||||
* __options__ *{Object}* (Optional)
|
||||
Options
|
||||
- __ignoreMissing__ *{Boolean}* (Optional)
|
||||
Set true to treat missing files as a successful deletion. Otherwise throws an error.
|
||||
* __callback__ *{Function}* (Optional)
|
||||
If not provided, will block and return true or false
|
||||
|
||||
-
|
||||
|
||||
|
||||
|
||||
|
||||
> ```self.remove = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:321](storageAdapter.server.js#L321)
|
||||
|
||||
-
|
||||
50
packages/wekan-cfs-storage-adapter/package.js
Normal file
50
packages/wekan-cfs-storage-adapter/package.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-storage-adapter.git',
|
||||
name: 'wekan-cfs-storage-adapter',
|
||||
version: '0.2.4',
|
||||
summary: 'CollectionFS, Class for creating Storage adapters'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'length-stream': '0.1.1'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
// CFS
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
// Core
|
||||
'deps',
|
||||
'check',
|
||||
'livedata',
|
||||
'mongo-livedata',
|
||||
'ejson',
|
||||
// Other
|
||||
'raix:eventemitter@0.1.1'
|
||||
]);
|
||||
|
||||
// We want to make sure that its added to scope for now if installed.
|
||||
// We have set a deprecation warning on the transform scope
|
||||
api.use('wekan-cfs-graphicsmagick@0.0.17', 'server', { weak: true });
|
||||
|
||||
api.addFiles([
|
||||
'storageAdapter.client.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'storageAdapter.server.js',
|
||||
'transform.server.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use('wekan-cfs-storage-adapter');
|
||||
api.use('test-helpers', 'server');
|
||||
api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
|
||||
'random', 'deps']);
|
||||
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
});
|
||||
37
packages/wekan-cfs-storage-adapter/storageAdapter.client.js
Normal file
37
packages/wekan-cfs-storage-adapter/storageAdapter.client.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* global FS, _storageAdapters:true, EventEmitter */
|
||||
|
||||
// #############################################################################
|
||||
//
|
||||
// STORAGE ADAPTER
|
||||
//
|
||||
// #############################################################################
|
||||
|
||||
_storageAdapters = {};
|
||||
|
||||
FS.StorageAdapter = function(name, options, api) {
|
||||
var self = this;
|
||||
|
||||
// Check the api
|
||||
if (typeof api === 'undefined') {
|
||||
throw new Error('FS.StorageAdapter please define an api');
|
||||
}
|
||||
|
||||
// store reference for easy lookup by name
|
||||
if (typeof _storageAdapters[name] !== 'undefined') {
|
||||
throw new Error('Storage name already exists: "' + name + '"');
|
||||
} else {
|
||||
_storageAdapters[name] = self;
|
||||
}
|
||||
|
||||
// extend self with options and other info
|
||||
FS.Utility.extend(this, options || {}, {
|
||||
name: name
|
||||
});
|
||||
|
||||
// XXX: TODO, add upload feature here...
|
||||
// we default to ddp upload but really let the SA like S3Cloud overwrite to
|
||||
// implement direct client to s3 upload
|
||||
|
||||
};
|
||||
|
||||
FS.StorageAdapter.prototype = new EventEmitter();
|
||||
269
packages/wekan-cfs-storage-adapter/storageAdapter.server.js
Normal file
269
packages/wekan-cfs-storage-adapter/storageAdapter.server.js
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/* global FS, _storageAdapters:true, EventEmitter */
|
||||
|
||||
// #############################################################################
|
||||
//
|
||||
// STORAGE ADAPTER
|
||||
//
|
||||
// #############################################################################
|
||||
_storageAdapters = {};
|
||||
|
||||
FS.StorageAdapter = function(storeName, options, api) {
|
||||
var self = this, fileKeyMaker;
|
||||
options = options || {};
|
||||
|
||||
// If storeName is the only argument, a string and the SA already found
|
||||
// we will just return that SA
|
||||
if (arguments.length === 1 && storeName === '' + storeName &&
|
||||
typeof _storageAdapters[storeName] !== 'undefined')
|
||||
return _storageAdapters[storeName];
|
||||
|
||||
// Verify that the storage adapter defines all the necessary API methods
|
||||
if (typeof api === 'undefined') {
|
||||
throw new Error('FS.StorageAdapter please define an api');
|
||||
}
|
||||
|
||||
FS.Utility.each('fileKey,remove,typeName,createReadStream,createWriteStream'.split(','), function(name) {
|
||||
if (typeof api[name] === 'undefined') {
|
||||
throw new Error('FS.StorageAdapter please define an api. "' + name + '" ' + (api.typeName || ''));
|
||||
}
|
||||
});
|
||||
|
||||
// Create an internal namespace, starting a name with underscore is only
|
||||
// allowed for stores marked with options.internal === true
|
||||
if (options.internal !== true && storeName[0] === '_') {
|
||||
throw new Error('A storage adapter name may not begin with "_"');
|
||||
}
|
||||
|
||||
if (storeName.indexOf('.') !== -1) {
|
||||
throw new Error('A storage adapter name may not contain a "."');
|
||||
}
|
||||
|
||||
// store reference for easy lookup by storeName
|
||||
if (typeof _storageAdapters[storeName] !== 'undefined') {
|
||||
throw new Error('Storage name already exists: "' + storeName + '"');
|
||||
} else {
|
||||
_storageAdapters[storeName] = self;
|
||||
}
|
||||
|
||||
// User can customize the file key generation function
|
||||
if (typeof options.fileKeyMaker === "function") {
|
||||
fileKeyMaker = options.fileKeyMaker;
|
||||
} else {
|
||||
fileKeyMaker = api.fileKey;
|
||||
}
|
||||
|
||||
// User can provide a function to adjust the fileObj
|
||||
// before it is written to the store.
|
||||
var beforeWrite = options.beforeWrite;
|
||||
|
||||
// extend self with options and other info
|
||||
FS.Utility.extend(this, options, {
|
||||
name: storeName,
|
||||
typeName: api.typeName
|
||||
});
|
||||
|
||||
// Create a nicer abstracted adapter interface
|
||||
self.adapter = {};
|
||||
|
||||
self.adapter.fileKey = function(fileObj) {
|
||||
return fileKeyMaker(fileObj);
|
||||
};
|
||||
|
||||
// Return readable stream for fileKey
|
||||
self.adapter.createReadStreamForFileKey = function(fileKey, options) {
|
||||
if (FS.debug) console.log('createReadStreamForFileKey ' + storeName);
|
||||
return FS.Utility.safeStream( api.createReadStream(fileKey, options) );
|
||||
};
|
||||
|
||||
// Return readable stream for fileObj
|
||||
self.adapter.createReadStream = function(fileObj, options) {
|
||||
if (FS.debug) console.log('createReadStream ' + storeName);
|
||||
if (self.internal) {
|
||||
// Internal stores take a fileKey
|
||||
return self.adapter.createReadStreamForFileKey(fileObj, options);
|
||||
}
|
||||
return FS.Utility.safeStream( self._transform.createReadStream(fileObj, options) );
|
||||
};
|
||||
|
||||
function logEventsForStream(stream) {
|
||||
if (FS.debug) {
|
||||
stream.on('stored', function() {
|
||||
console.log('-----------STORED STREAM', storeName);
|
||||
});
|
||||
|
||||
stream.on('close', function() {
|
||||
console.log('-----------CLOSE STREAM', storeName);
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
console.log('-----------END STREAM', storeName);
|
||||
});
|
||||
|
||||
stream.on('finish', function() {
|
||||
console.log('-----------FINISH STREAM', storeName);
|
||||
});
|
||||
|
||||
stream.on('error', function(error) {
|
||||
console.log('-----------ERROR STREAM', storeName, error && (error.message || error.code));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Return writeable stream for fileKey
|
||||
self.adapter.createWriteStreamForFileKey = function(fileKey, options) {
|
||||
if (FS.debug) console.log('createWriteStreamForFileKey ' + storeName);
|
||||
var writeStream = FS.Utility.safeStream( api.createWriteStream(fileKey, options) );
|
||||
|
||||
logEventsForStream(writeStream);
|
||||
|
||||
return writeStream;
|
||||
};
|
||||
|
||||
// Return writeable stream for fileObj
|
||||
self.adapter.createWriteStream = function(fileObj, options) {
|
||||
if (FS.debug) console.log('createWriteStream ' + storeName + ', internal: ' + !!self.internal);
|
||||
|
||||
if (self.internal) {
|
||||
// Internal stores take a fileKey
|
||||
return self.adapter.createWriteStreamForFileKey(fileObj, options);
|
||||
}
|
||||
|
||||
// If we haven't set name, type, or size for this version yet,
|
||||
// set it to same values as original version. We don't save
|
||||
// these to the DB right away because they might be changed
|
||||
// in a transformWrite function.
|
||||
if (!fileObj.name({store: storeName})) {
|
||||
fileObj.name(fileObj.name(), {store: storeName, save: false});
|
||||
}
|
||||
if (!fileObj.type({store: storeName})) {
|
||||
fileObj.type(fileObj.type(), {store: storeName, save: false});
|
||||
}
|
||||
if (!fileObj.size({store: storeName})) {
|
||||
fileObj.size(fileObj.size(), {store: storeName, save: false});
|
||||
}
|
||||
|
||||
// Call user function to adjust file metadata for this store.
|
||||
// We support updating name, extension, and/or type based on
|
||||
// info returned in an object. Or `fileObj` could be
|
||||
// altered directly within the beforeWrite function.
|
||||
if (beforeWrite) {
|
||||
var fileChanges = beforeWrite(fileObj);
|
||||
if (typeof fileChanges === "object") {
|
||||
if (fileChanges.extension) {
|
||||
fileObj.extension(fileChanges.extension, {store: storeName, save: false});
|
||||
} else if (fileChanges.name) {
|
||||
fileObj.name(fileChanges.name, {store: storeName, save: false});
|
||||
}
|
||||
if (fileChanges.type) {
|
||||
fileObj.type(fileChanges.type, {store: storeName, save: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var writeStream = FS.Utility.safeStream( self._transform.createWriteStream(fileObj, options) );
|
||||
|
||||
logEventsForStream(writeStream);
|
||||
|
||||
// Its really only the storage adapter who knows if the file is uploaded
|
||||
//
|
||||
// We have to use our own event making sure the storage process is completed
|
||||
// this is mainly
|
||||
writeStream.safeOn('stored', function(result) {
|
||||
if (typeof result.fileKey === 'undefined') {
|
||||
throw new Error('SA ' + storeName + ' type ' + api.typeName + ' did not return a fileKey');
|
||||
}
|
||||
if (FS.debug) console.log('SA', storeName, 'stored', result.fileKey);
|
||||
// Set the fileKey
|
||||
fileObj.copies[storeName].key = result.fileKey;
|
||||
|
||||
// Update the size, as provided by the SA, in case it was changed by stream transformation
|
||||
if (typeof result.size === "number") {
|
||||
fileObj.copies[storeName].size = result.size;
|
||||
}
|
||||
|
||||
// Set last updated time, either provided by SA or now
|
||||
fileObj.copies[storeName].updatedAt = result.storedAt || new Date();
|
||||
|
||||
// If the file object copy havent got a createdAt then set this
|
||||
if (typeof fileObj.copies[storeName].createdAt === 'undefined') {
|
||||
fileObj.copies[storeName].createdAt = fileObj.copies[storeName].updatedAt;
|
||||
}
|
||||
|
||||
fileObj._saveChanges(storeName);
|
||||
|
||||
// There is code in transform that may have set the original file size, too.
|
||||
fileObj._saveChanges('_original');
|
||||
});
|
||||
|
||||
// Emit events from SA
|
||||
writeStream.once('stored', function(/*result*/) {
|
||||
// XXX Because of the way stores inherit from SA, this will emit on every store.
|
||||
// Maybe need to rewrite the way we inherit from SA?
|
||||
var emitted = self.emit('stored', storeName, fileObj);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(fileObj.name() + ' was successfully stored in the ' + storeName + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on this store.');
|
||||
}
|
||||
});
|
||||
|
||||
writeStream.on('error', function(error) {
|
||||
// XXX We could wrap and clarify error
|
||||
// XXX Because of the way stores inherit from SA, this will emit on every store.
|
||||
// Maybe need to rewrite the way we inherit from SA?
|
||||
var emitted = self.emit('error', storeName, error, fileObj);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
return writeStream;
|
||||
};
|
||||
|
||||
//internal
|
||||
self._removeAsync = function(fileKey, callback) {
|
||||
// Remove the file from the store
|
||||
api.remove.call(self, fileKey, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.StorageAdapter.prototype.remove
|
||||
* @public
|
||||
* @param {FS.File} fsFile The FS.File instance to be stored.
|
||||
* @param {Function} [callback] If not provided, will block and return true or false
|
||||
*
|
||||
* Attempts to remove a file from the store. Returns true if removed or not
|
||||
* found, or false if the file couldn't be removed.
|
||||
*/
|
||||
self.adapter.remove = function(fileObj, callback) {
|
||||
if (FS.debug) console.log("---SA REMOVE");
|
||||
|
||||
// Get the fileKey
|
||||
var fileKey = (fileObj instanceof FS.File) ? self.adapter.fileKey(fileObj) : fileObj;
|
||||
|
||||
if (callback) {
|
||||
return self._removeAsync(fileKey, FS.Utility.safeCallback(callback));
|
||||
} else {
|
||||
return Meteor.wrapAsync(self._removeAsync)(fileKey);
|
||||
}
|
||||
};
|
||||
|
||||
self.remove = function(fileObj, callback) {
|
||||
// Add deprecation note
|
||||
console.warn('Storage.remove is deprecating, use "Storage.adapter.remove"');
|
||||
return self.adapter.remove(fileObj, callback);
|
||||
};
|
||||
|
||||
if (typeof api.init === 'function') {
|
||||
Meteor.wrapAsync(api.init.bind(self))();
|
||||
}
|
||||
|
||||
// This supports optional transformWrite and transformRead
|
||||
self._transform = new FS.Transform({
|
||||
adapter: self.adapter,
|
||||
// Optional transformation functions:
|
||||
transformWrite: options.transformWrite,
|
||||
transformRead: options.transformRead
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
Npm.require('util').inherits(FS.StorageAdapter, EventEmitter);
|
||||
44
packages/wekan-cfs-storage-adapter/tests/client-tests.js
Normal file
44
packages/wekan-cfs-storage-adapter/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-storage-adapter - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType');
|
||||
});
|
||||
|
||||
/*
|
||||
* FS.File Client Tests
|
||||
*
|
||||
* construct FS.File with no arguments
|
||||
* construct FS.File passing in File
|
||||
* construct FS.File passing in Blob
|
||||
* load blob into FS.File and then call FS.File.toDataUrl
|
||||
* call FS.File.setDataFromBinary, then FS.File.getBlob(); make sure correct data is returned
|
||||
* load blob into FS.File and then call FS.File.getBinary() with and without start/end; make sure correct data is returned
|
||||
* construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url
|
||||
* set FS.File.name to a filename and test that FS.File.getExtension() returns the extension
|
||||
* load blob into FS.File and make sure FS.File.saveLocal initiates a download (possibly can't do automatically)
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
49
packages/wekan-cfs-storage-adapter/tests/server-tests.js
Normal file
49
packages/wekan-cfs-storage-adapter/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-storage-adapter - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType');
|
||||
});
|
||||
|
||||
/*
|
||||
* FS.File Server Tests
|
||||
*
|
||||
* construct FS.File with no arguments
|
||||
* load data with FS.File.setDataFromBuffer
|
||||
* load data with FS.File.setDataFromBinary
|
||||
* load data and then call FS.File.toDataUrl with and without callback
|
||||
* load buffer into FS.File and then call FS.File.getBinary with and without start/end; make sure correct data is returned
|
||||
* construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url
|
||||
* (call these with and without callback to test sync vs. async)
|
||||
* set FS.File.name to a filename and test that FS.File.getExtension() returns the extension
|
||||
*
|
||||
*
|
||||
* FS.Collection Server Tests
|
||||
*
|
||||
* Make sure options.filter is respected
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
119
packages/wekan-cfs-storage-adapter/transform.server.js
Normal file
119
packages/wekan-cfs-storage-adapter/transform.server.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* global FS */
|
||||
|
||||
var PassThrough = Npm.require('stream').PassThrough;
|
||||
var lengthStream = Npm.require('length-stream');
|
||||
|
||||
FS.Transform = function(options) {
|
||||
var self = this;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (!(self instanceof FS.Transform))
|
||||
throw new Error('FS.Transform must be called with the "new" keyword');
|
||||
|
||||
if (!options.adapter)
|
||||
throw new Error('Transform expects option.adapter to be a storage adapter');
|
||||
|
||||
self.storage = options.adapter;
|
||||
|
||||
// Fetch the transformation functions if any
|
||||
self.transformWrite = options.transformWrite;
|
||||
self.transformRead = options.transformRead;
|
||||
};
|
||||
|
||||
// Allow packages to add scope
|
||||
FS.Transform.scope = {};
|
||||
|
||||
// The transformation stream triggers an "stored" event when data is stored into
|
||||
// the storage adapter
|
||||
FS.Transform.prototype.createWriteStream = function(fileObj) {
|
||||
var self = this;
|
||||
|
||||
// Get the file key
|
||||
var fileKey = self.storage.fileKey(fileObj);
|
||||
|
||||
// Rig write stream
|
||||
var destinationStream = self.storage.createWriteStreamForFileKey(fileKey, {
|
||||
// Not all SA's can set these options and cfs dont depend on setting these
|
||||
// but its nice if other systems are accessing the SA that some of the data
|
||||
// is also available to those
|
||||
aliases: [fileObj.name()],
|
||||
contentType: fileObj.type(),
|
||||
metadata: fileObj.metadata
|
||||
});
|
||||
|
||||
// Pass through transformWrite function if provided
|
||||
if (typeof self.transformWrite === 'function') {
|
||||
|
||||
destinationStream = addPassThrough(destinationStream, function (ptStream, originalStream) {
|
||||
// Rig transform
|
||||
try {
|
||||
self.transformWrite.call(FS.Transform.scope, fileObj, ptStream, originalStream);
|
||||
// XXX: If the transform function returns a buffer should we stream that?
|
||||
} catch(err) {
|
||||
// We emit an error - should we throw an error?
|
||||
console.warn('FS.Transform.createWriteStream transform function failed, Error: ');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// If original doesn't have size, add another PassThrough to get and set the size.
|
||||
// This will run on size=0, too, which is OK.
|
||||
// NOTE: This must come AFTER the transformWrite code block above. This might seem
|
||||
// confusing, but by coming after it, this will actually be executed BEFORE the user's
|
||||
// transform, which is what we need in order to be sure we get the original file
|
||||
// size and not the transformed file size.
|
||||
if (!fileObj.size()) {
|
||||
destinationStream = addPassThrough(destinationStream, function (ptStream, originalStream) {
|
||||
var lstream = lengthStream(function (fileSize) {
|
||||
fileObj.size(fileSize, {save: false});
|
||||
});
|
||||
|
||||
ptStream.pipe(lstream).pipe(originalStream);
|
||||
});
|
||||
}
|
||||
|
||||
return destinationStream;
|
||||
};
|
||||
|
||||
FS.Transform.prototype.createReadStream = function(fileObj, options) {
|
||||
var self = this;
|
||||
|
||||
// Get the file key
|
||||
var fileKey = self.storage.fileKey(fileObj);
|
||||
|
||||
// Rig read stream
|
||||
var sourceStream = self.storage.createReadStreamForFileKey(fileKey, options);
|
||||
|
||||
// Pass through transformRead function if provided
|
||||
if (typeof self.transformRead === 'function') {
|
||||
|
||||
sourceStream = addPassThrough(sourceStream, function (ptStream, originalStream) {
|
||||
// Rig transform
|
||||
try {
|
||||
self.transformRead.call(FS.Transform.scope, fileObj, originalStream, ptStream);
|
||||
} catch(err) {
|
||||
//throw new Error(err);
|
||||
// We emit an error - should we throw an error?
|
||||
sourceStream.emit('error', 'FS.Transform.createReadStream transform function failed');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// We dont transform just normal SA interface
|
||||
return sourceStream;
|
||||
};
|
||||
|
||||
// Utility function to simplify adding layers of passthrough
|
||||
function addPassThrough(stream, func) {
|
||||
var pts = new PassThrough();
|
||||
// We pass on the special "stored" event for those listening
|
||||
stream.on('stored', function(result) {
|
||||
pts.emit('stored', result);
|
||||
});
|
||||
func(pts, stream);
|
||||
return pts;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue