mirror of
https://github.com/wekan/wekan.git
synced 2025-12-23 10:50:13 +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-filesystem/.travis.yml
Normal file
5
packages/wekan-cfs-filesystem/.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-filesystem/LICENSE.md
Normal file
20
packages/wekan-cfs-filesystem/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 [@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.
|
||||
41
packages/wekan-cfs-filesystem/README.md
Normal file
41
packages/wekan-cfs-filesystem/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
wekan-cfs-filesystem
|
||||
=========================
|
||||
|
||||
NOTE: This package is under active development right now (2014-3-31). It has
|
||||
bugs and the API may continue to change. Please help test it and fix bugs,
|
||||
but don't use in production yet.
|
||||
|
||||
A Meteor package that adds local server filesystem storage for
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). When you
|
||||
use this storage adapter, file data is stored in a directory of your choosing
|
||||
on the same server on which your Meteor app is running.
|
||||
|
||||
## Installation
|
||||
|
||||
Install using Meteorite. When in a Meteor app directory, enter:
|
||||
|
||||
```
|
||||
$ meteor add wekan-cfs-filesystem
|
||||
```
|
||||
|
||||
## Important Note
|
||||
|
||||
Note that using this Storage Adapter on the free Meteor deployment servers on `*.meteor.com` will cause a reset of files at every code deploy. You may want to have a look at the [GridFS Storage Adapter](https://github.com/zcfs/Meteor-CollectionFS/tree/devel/packages/gridfs) for persistent file storage.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var imageStore = new FS.Store.FileSystem("images", {
|
||||
path: "~/app-files/images", //optional, default is "/cfs/files" path within app container
|
||||
transformWrite: myTransformWriteFunction, //optional
|
||||
transformRead: myTransformReadFunction, //optional
|
||||
maxTries: 1 //optional, default 5
|
||||
});
|
||||
|
||||
Images = new FS.Collection("images", {
|
||||
stores: [imageStore]
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [CollectionFS](https://github.com/zcfs/Meteor-CollectionFS)
|
||||
package documentation for more information.
|
||||
10
packages/wekan-cfs-filesystem/filesystem.client.js
Normal file
10
packages/wekan-cfs-filesystem/filesystem.client.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// On the client we have just a shell
|
||||
FS.Store.FileSystem = function(name, options) {
|
||||
var self = this;
|
||||
if (!(self instanceof FS.Store.FileSystem))
|
||||
throw new Error('FS.Store.FileSystem missing keyword "new"');
|
||||
|
||||
return new FS.StorageAdapter(name, options, {
|
||||
typeName: 'storage.filesystem'
|
||||
});
|
||||
};
|
||||
157
packages/wekan-cfs-filesystem/filesystem.server.js
Normal file
157
packages/wekan-cfs-filesystem/filesystem.server.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
var fs = Npm.require('fs');
|
||||
var path = Npm.require('path');
|
||||
var mkdirp = Npm.require('mkdirp');
|
||||
//var chokidar = Npm.require('chokidar');
|
||||
|
||||
FS.Store.FileSystem = function(name, options) {
|
||||
var self = this;
|
||||
if (!(self instanceof FS.Store.FileSystem))
|
||||
throw new Error('FS.Store.FileSystem missing keyword "new"');
|
||||
|
||||
// We allow options to be string/path empty or options.path
|
||||
options = (options !== ''+options) ? options || {} : { path: options };
|
||||
|
||||
// Provide a default FS directory one level up from the build/bundle directory
|
||||
var pathname = options.path;
|
||||
if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
|
||||
pathname = path.join(__meteor_bootstrap__.serverDir, '../../../cfs/files/' + name);
|
||||
}
|
||||
|
||||
if (!pathname)
|
||||
throw new Error('FS.Store.FileSystem unable to determine path');
|
||||
|
||||
// Check if we have '~/foo/bar'
|
||||
if (pathname.split(path.sep)[0] === '~') {
|
||||
var homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
||||
if (homepath) {
|
||||
pathname = pathname.replace('~', homepath);
|
||||
} else {
|
||||
throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
|
||||
}
|
||||
}
|
||||
|
||||
// Set absolute path
|
||||
var absolutePath = path.resolve(pathname);
|
||||
|
||||
// Ensure the path exists
|
||||
mkdirp.sync(absolutePath);
|
||||
FS.debug && console.log(name + ' FileSystem mounted on: ' + absolutePath);
|
||||
|
||||
return new FS.StorageAdapter(name, options, {
|
||||
typeName: 'storage.filesystem',
|
||||
fileKey: function(fileObj) {
|
||||
// Lookup the copy
|
||||
var store = fileObj && fileObj._getInfo(name);
|
||||
// If the store and key is found return the key
|
||||
if (store && store.key) return store.key;
|
||||
|
||||
var filename = fileObj.name();
|
||||
var filenameInStore = fileObj.name({store: name});
|
||||
|
||||
// If no store key found we resolve / generate a key
|
||||
return fileObj.collectionName + '-' + fileObj._id + '-' + (filenameInStore || filename);
|
||||
},
|
||||
createReadStream: function(fileKey, options) {
|
||||
// this is the Storage adapter scope
|
||||
var filepath = path.join(absolutePath, fileKey);
|
||||
|
||||
// return the read stream - Options allow { start, end }
|
||||
return fs.createReadStream(filepath, options);
|
||||
},
|
||||
createWriteStream: function(fileKey, options) {
|
||||
options = options || {};
|
||||
|
||||
// this is the Storage adapter scope
|
||||
var filepath = path.join(absolutePath, fileKey);
|
||||
|
||||
// Return the stream handle
|
||||
var writeStream = fs.createWriteStream(filepath, options);
|
||||
|
||||
// The filesystem does not emit the "end" event only close - so we
|
||||
// manually send the end event
|
||||
writeStream.on('close', function() {
|
||||
if (FS.debug) console.log('SA FileSystem - DONE!! fileKey: "' + fileKey + '"');
|
||||
|
||||
// Get the exact size of the stored file, so that we can pass it to onEnd/onStored.
|
||||
// Since stream transforms might have altered the size, this is the best way to
|
||||
// ensure we update the fileObj.copies with the correct size.
|
||||
try {
|
||||
// Get the stats of the file
|
||||
var stats = fs.statSync(filepath);
|
||||
|
||||
// Emit end and return the fileKey, size, and updated date
|
||||
writeStream.emit('stored', {
|
||||
fileKey: fileKey,
|
||||
size: stats.size,
|
||||
storedAt: stats.mtime
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
// On error we emit the error on
|
||||
writeStream.emit('error', err);
|
||||
}
|
||||
});
|
||||
|
||||
return writeStream;
|
||||
},
|
||||
remove: function(fileKey, callback) {
|
||||
// this is the Storage adapter scope
|
||||
var filepath = path.join(absolutePath, fileKey);
|
||||
|
||||
// Call node unlink file
|
||||
fs.unlink(filepath, function (error, result) {
|
||||
if (error && error.errno === 34) {
|
||||
console.warn("SA FileSystem: Could not delete " + filepath + " because the file was not found.");
|
||||
callback && callback(null);
|
||||
} else {
|
||||
callback && callback(error, result);
|
||||
}
|
||||
});
|
||||
},
|
||||
stats: function(fileKey, callback) {
|
||||
// this is the Storage adapter scope
|
||||
var filepath = path.join(absolutePath, fileKey);
|
||||
if (typeof callback === 'function') {
|
||||
fs.stat(filepath, callback);
|
||||
} else {
|
||||
return fs.statSync(filepath);
|
||||
}
|
||||
}
|
||||
// Add this back and add the chokidar dependency back when we make this work eventually
|
||||
// watch: function(callback) {
|
||||
// function fileKey(filePath) {
|
||||
// return filePath.replace(absolutePath, "");
|
||||
// }
|
||||
|
||||
// FS.debug && console.log('Watching ' + absolutePath);
|
||||
|
||||
// // chokidar seems to be most widely used and production ready watcher
|
||||
// var watcher = chokidar.watch(absolutePath, {ignored: /\/\./, ignoreInitial: true});
|
||||
// watcher.on('add', Meteor.bindEnvironment(function(filePath, stats) {
|
||||
// callback("change", fileKey(filePath), {
|
||||
// name: path.basename(filePath),
|
||||
// type: null,
|
||||
// size: stats.size,
|
||||
// utime: stats.mtime
|
||||
// });
|
||||
// }, function(err) {
|
||||
// throw err;
|
||||
// }));
|
||||
// watcher.on('change', Meteor.bindEnvironment(function(filePath, stats) {
|
||||
// callback("change", fileKey(filePath), {
|
||||
// name: path.basename(filePath),
|
||||
// type: null,
|
||||
// size: stats.size,
|
||||
// utime: stats.mtime
|
||||
// });
|
||||
// }, function(err) {
|
||||
// throw err;
|
||||
// }));
|
||||
// watcher.on('unlink', Meteor.bindEnvironment(function(filePath) {
|
||||
// callback("remove", fileKey(filePath));
|
||||
// }, function(err) {
|
||||
// throw err;
|
||||
// }));
|
||||
// }
|
||||
});
|
||||
};
|
||||
24
packages/wekan-cfs-filesystem/package.js
Normal file
24
packages/wekan-cfs-filesystem/package.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-filesystem.git',
|
||||
name: 'wekan-cfs-filesystem',
|
||||
version: '0.1.2',
|
||||
summary: "Filesystem storage adapter for CollectionFS"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
//chokidar: "0.8.2",
|
||||
mkdirp: "0.3.5"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-storage-adapter@0.2.1']);
|
||||
api.addFiles('filesystem.server.js', 'server');
|
||||
api.addFiles('filesystem.client.js', 'client');
|
||||
});
|
||||
|
||||
Package.onTest(function(api) {
|
||||
api.use(['wekan-cfs-filesystem', 'test-helpers', 'tinytest'], 'server');
|
||||
api.addFiles('tests.js', 'server');
|
||||
});
|
||||
9
packages/wekan-cfs-filesystem/tests.js
Normal file
9
packages/wekan-cfs-filesystem/tests.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//TODO
|
||||
|
||||
/*
|
||||
* FileSystem Tests (Server Only)
|
||||
*
|
||||
* Create FS SA and use for all tests (verify the "~" correctly uses the home directory)
|
||||
* Test get, getBytes, put, remove, stats. Verify that the correct things happen
|
||||
* in the correct places on the local filesystem.
|
||||
*/
|
||||
44
packages/wekan-cfs-filesystem/tests/client-tests.js
Normal file
44
packages/wekan-cfs-filesystem/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-filesystem - 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-filesystem/tests/server-tests.js
Normal file
49
packages/wekan-cfs-filesystem/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-filesystem - 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue