mirror of
https://github.com/wekan/wekan.git
synced 2025-04-24 22:17:16 -04: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
|
@ -17,7 +17,7 @@ es5-shim@4.8.0
|
|||
|
||||
# Collections
|
||||
aldeed:collection2
|
||||
cfs:standard-packages
|
||||
wekan-cfs-standard-packages
|
||||
cottz:publish-relations
|
||||
dburles:collection-helpers
|
||||
idmontie:migrations
|
||||
|
@ -75,7 +75,7 @@ horka:swipebox
|
|||
dynamic-import@0.6.0
|
||||
|
||||
accounts-password@1.7.0
|
||||
cfs:gridfs
|
||||
wekan-cfs-gridfs
|
||||
rzymek:fullcalendar
|
||||
momentjs:moment@2.22.2
|
||||
browser-policy-framing@1.1.0
|
||||
|
@ -91,7 +91,7 @@ meteorhacks:aggregate@1.3.0
|
|||
wekan-markdown
|
||||
konecty:mongo-counter
|
||||
percolate:synced-cron
|
||||
cfs:filesystem
|
||||
wekan-cfs-filesystem
|
||||
tmeasday:check-npm-versions
|
||||
steffo:meteor-accounts-saml
|
||||
rajit:bootstrap3-datepicker-fi
|
||||
|
|
|
@ -23,24 +23,7 @@ browser-policy-framing@1.1.0
|
|||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.2.0
|
||||
callback-hook@1.3.0
|
||||
cfs:access-point@0.1.49
|
||||
cfs:base-package@0.0.30
|
||||
cfs:collection@0.5.5
|
||||
cfs:collection-filters@0.2.4
|
||||
cfs:data-man@0.0.6
|
||||
cfs:file@0.1.17
|
||||
cfs:filesystem@0.1.2
|
||||
cfs:gridfs@0.0.34
|
||||
cfs:http-methods@0.0.32
|
||||
cfs:http-publish@0.0.13
|
||||
cfs:power-queue@0.9.11
|
||||
cfs:reactive-list@0.0.9
|
||||
cfs:reactive-property@0.0.4
|
||||
cfs:standard-packages@0.5.10
|
||||
cfs:storage-adapter@0.2.4
|
||||
cfs:tempstore@0.1.6
|
||||
cfs:upload-http@0.0.20
|
||||
cfs:worker@0.1.5
|
||||
check@1.3.1
|
||||
chuangbo:cookie@1.1.0
|
||||
coagmano:stylus@1.1.0
|
||||
|
@ -238,6 +221,24 @@ webapp@1.10.1
|
|||
webapp-hashing@1.1.0
|
||||
wekan-accounts-cas@0.1.0
|
||||
wekan-accounts-oidc@1.0.10
|
||||
wekan-cfs-access-point@0.1.50
|
||||
wekan-cfs-base-package@0.0.30
|
||||
wekan-cfs-collection@0.5.5
|
||||
wekan-cfs-collection-filters@0.2.4
|
||||
wekan-cfs-data-man@0.0.6
|
||||
wekan-cfs-file@0.1.17
|
||||
wekan-cfs-filesystem@0.1.2
|
||||
wekan-cfs-gridfs@0.0.34
|
||||
wekan-cfs-http-methods@0.0.32
|
||||
wekan-cfs-http-publish@0.0.13
|
||||
wekan-cfs-power-queue@0.9.11
|
||||
wekan-cfs-reactive-list@0.0.9
|
||||
wekan-cfs-reactive-property@0.0.4
|
||||
wekan-cfs-standard-packages@0.5.10
|
||||
wekan-cfs-storage-adapter@0.2.4
|
||||
wekan-cfs-tempstore@0.1.6
|
||||
wekan-cfs-upload-http@0.0.21
|
||||
wekan-cfs-worker@0.1.5
|
||||
wekan-ldap@0.0.2
|
||||
wekan-markdown@1.0.9
|
||||
wekan-oidc@1.0.12
|
||||
|
|
|
@ -241,7 +241,7 @@ Template.editor.onRendered(() => {
|
|||
//the function is called before the text is really pasted.
|
||||
updatePastedText(thisNote);
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
},
|
||||
dialogsInBody: true,
|
||||
spellCheck: true,
|
||||
|
|
|
@ -23,21 +23,30 @@ class DateFilter {
|
|||
|
||||
// past builds a filter for all dates before now
|
||||
past() {
|
||||
if (this._filterState == 'past') { this.reset(); return; }
|
||||
if (this._filterState == 'past') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this._filter = { $lte: moment().toDate() };
|
||||
this._updateState('past');
|
||||
}
|
||||
|
||||
// today is a convenience method for calling relativeDay with 0
|
||||
today() {
|
||||
if (this._filterState == 'today') { this.reset(); return; }
|
||||
if (this._filterState == 'today') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this.relativeDay(0);
|
||||
this._updateState('today');
|
||||
}
|
||||
|
||||
// tomorrow is a convenience method for calling relativeDay with 1
|
||||
tomorrow() {
|
||||
if (this._filterState == 'tomorrow') { this.reset(); return; }
|
||||
if (this._filterState == 'tomorrow') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this.relativeDay(1);
|
||||
this._updateState('tomorrow');
|
||||
}
|
||||
|
@ -50,10 +59,18 @@ class DateFilter {
|
|||
// relativeDay builds a filter starting from now and including all
|
||||
// days up to today +/- offset.
|
||||
relativeDay(offset) {
|
||||
if (this._filterState == 'day') { this.reset(); return; }
|
||||
if (this._filterState == 'day') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
var startDay = moment().startOf('day').toDate(),
|
||||
endDay = moment().endOf('day').add(offset, 'day').toDate();
|
||||
var startDay = moment()
|
||||
.startOf('day')
|
||||
.toDate(),
|
||||
endDay = moment()
|
||||
.endOf('day')
|
||||
.add(offset, 'day')
|
||||
.toDate();
|
||||
|
||||
if (offset >= 0) {
|
||||
this._filter = { $gte: startDay, $lte: endDay };
|
||||
|
@ -68,7 +85,10 @@ class DateFilter {
|
|||
// weeks up to today +/- offset. This considers the user's preferred
|
||||
// start of week day (as defined by Meteor).
|
||||
relativeWeek(offset) {
|
||||
if (this._filterState == 'week') { this.reset(); return; }
|
||||
if (this._filterState == 'week') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// getStartDayOfWeek returns the offset from Sunday of the user's
|
||||
// preferred starting day of the week. This date should be added
|
||||
|
@ -78,8 +98,14 @@ class DateFilter {
|
|||
const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1;
|
||||
|
||||
// Moments are mutable so they must be cloned before modification
|
||||
var thisWeekStart = moment().startOf('day').startOf('week').add(weekStartDay, 'days');
|
||||
var thisWeekEnd = thisWeekStart.clone().add(offset, 'week').endOf('day');
|
||||
var thisWeekStart = moment()
|
||||
.startOf('day')
|
||||
.startOf('week')
|
||||
.add(weekStartDay, 'days');
|
||||
var thisWeekEnd = thisWeekStart
|
||||
.clone()
|
||||
.add(offset, 'week')
|
||||
.endOf('day');
|
||||
var startDate = thisWeekStart.toDate();
|
||||
var endDate = thisWeekEnd.toDate();
|
||||
|
||||
|
@ -94,7 +120,10 @@ class DateFilter {
|
|||
|
||||
// noDate builds a filter for items where date is not set
|
||||
noDate() {
|
||||
if (this._filterState == 'noDate') { this.reset(); return; }
|
||||
if (this._filterState == 'noDate') {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this._filter = null;
|
||||
this._updateState('noDate');
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
var Meteor = Package.meteor.Meteor;
|
||||
var global = Package.meteor.global;
|
||||
var meteorEnv = Package.meteor.meteorEnv;
|
||||
var FS = Package['cfs:base-package'].FS;
|
||||
var FS = Package['wekan-cfs-base-package'].FS;
|
||||
var check = Package.check.check;
|
||||
var Match = Package.check.Match;
|
||||
var EJSON = Package.ejson.EJSON;
|
||||
var HTTP = Package['cfs:http-methods'].HTTP;
|
||||
var HTTP = Package['wekan-cfs-http-methods'].HTTP;
|
||||
|
||||
/* Package-scope variables */
|
||||
var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls;
|
||||
|
@ -25,7 +25,7 @@ var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMou
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-common.js //
|
||||
// packages/wekan-cfs-access-point/access-point-common.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@ -217,7 +217,7 @@ FS.File.prototype.url = function(options) {
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-handlers.js //
|
||||
// packages/wekan-cfs-access-point/access-point-handlers.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@ -537,7 +537,7 @@ FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-server.js //
|
||||
// packages/wekan-cfs-access-point/access-point-server.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@ -909,6 +909,6 @@ Meteor.startup(function () {
|
|||
|
||||
/* Exports */
|
||||
if (typeof Package === 'undefined') Package = {};
|
||||
Package['cfs:access-point'] = {};
|
||||
Package['wekan-cfs-access-point'] = {};
|
||||
|
||||
})();
|
||||
|
|
|
@ -650,7 +650,7 @@ Cards.helpers({
|
|||
|
||||
allSubtasks() {
|
||||
return Cards.find({
|
||||
parentId: this._id
|
||||
parentId: this._id,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -385,7 +385,8 @@ export class ExporterExcel {
|
|||
//get kanban swimlanes info
|
||||
const jswimlane = {};
|
||||
for (const kswimlane in result.swimlanes) {
|
||||
jswimlane[result.swimlanes[kswimlane]._id] = result.swimlanes[kswimlane].title;
|
||||
jswimlane[result.swimlanes[kswimlane]._id] =
|
||||
result.swimlanes[kswimlane].title;
|
||||
}
|
||||
//get kanban label info
|
||||
const jlabel = {};
|
||||
|
@ -394,8 +395,7 @@ export class ExporterExcel {
|
|||
console.log(klabel);
|
||||
if (isFirst == 0) {
|
||||
jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
isFirst = 0;
|
||||
jlabel[result.labels[klabel]._id] = result.labels[klabel].name;
|
||||
}
|
||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -720,11 +720,6 @@
|
|||
"lodash.uniq": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@root/request": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.6.1.tgz",
|
||||
"integrity": "sha512-8wrWyeBLRp7T8J36GkT3RODJ6zYmL0/maWlAUD5LOXT28D3TDquUepyYDKYANNA3Gc8R5ZCgf+AXvSTYpJEWwQ=="
|
||||
},
|
||||
"@samverschueren/stream-to-observable": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
"dependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/runtime": "^7.9.6",
|
||||
"@root/request": "^1.6.1",
|
||||
"ajv": "^6.12.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
|
@ -67,7 +66,7 @@
|
|||
"fibers": "^5.0.0",
|
||||
"flatted": "^3.0.4",
|
||||
"gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master",
|
||||
"jszip": "^3.4.0",
|
||||
"jszip": "^3.6.0",
|
||||
"ldapjs": "^2.1.1",
|
||||
"markdown-it": "^12.0.2",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
|
|
5
packages/wekan-cfs-access-point/.travis.yml
Normal file
5
packages/wekan-cfs-access-point/.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"
|
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
|
@ -0,0 +1,288 @@
|
|||
# Changelog
|
||||
|
||||
## [v0.1.50] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 21/1/19 by Harry Adel
|
||||
|
||||
- Bump to version 0.1.50
|
||||
|
||||
- *Merged pull-request:* "filename conversion for FS.HTTP.Handlers.Get" [#9](https://github.com/zcfs/Meteor-CollectionFS/pull/994) ([yatusiter](https://github.com/yatusiter))
|
||||
|
||||
|
||||
|
||||
## [v0.1.46] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 30/3/15 by Eric Dobbertin
|
||||
|
||||
- Bump to version 0.1.46
|
||||
|
||||
- *Merged pull-request:* [#611](https://github.com/zcfs/Meteor-CollectionFS/issues/611)
|
||||
|
||||
- Exposed request handlers on `FS.HTTP.Handlers` object so that app can override
|
||||
|
||||
## [v0.1.43] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.43)
|
||||
#### 20/12/14 by Morten Henriksen
|
||||
- add changelog
|
||||
|
||||
- Bump to version 0.1.43
|
||||
|
||||
- *Fixed bug:* "Doesn't work in IE 8" [#10](https://github.com/zcfs/Meteor-cfs-access-point/issues/10)
|
||||
|
||||
- *Merged pull-request:* "rootUrlPathPrefix fix for cordova" [#9](https://github.com/zcfs/Meteor-cfs-access-point/issues/9) ([dmitriyles](https://github.com/dmitriyles))
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
Patches by GitHub users [@dmitriyles](https://github.com/dmitriyles), [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.1.42] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.42)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
## [v0.1.41] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.41)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Cordova rootUrlPathPrefix fix
|
||||
|
||||
- Bump to version 0.1.41
|
||||
|
||||
## [v0.1.40] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.40)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr fixed warnings
|
||||
|
||||
- fixes to GET handler
|
||||
|
||||
- add back tests
|
||||
|
||||
- support apps in server subdirectories; closes #8
|
||||
|
||||
- 0.9.1 support
|
||||
|
||||
## [v0.0.39] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.39)
|
||||
#### 28/08/14 by Morten Henriksen
|
||||
- Meteor Package System Update
|
||||
|
||||
## [v0.0.38] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.38)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
## [v0.0.37] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.37)
|
||||
#### 26/08/14 by Eric Dobbertin
|
||||
- change package name to lowercase
|
||||
|
||||
## [v0.0.36] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.36)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- pass correct arg
|
||||
|
||||
## [v0.0.35] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.35)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- move to correct place
|
||||
|
||||
## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.34)
|
||||
#### 05/08/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Added contentLength for ranges and inline content" [#5](https://github.com/zcfs/Meteor-cfs-access-point/issues/5) ([maomorales](https://github.com/maomorales))
|
||||
|
||||
- Content-Length and Last-Modified headers
|
||||
|
||||
Patches by GitHub user [@maomorales](https://github.com/maomorales).
|
||||
|
||||
## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.33)
|
||||
#### 31/07/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Force browser to download with filename passed in url" [#3](https://github.com/zcfs/Meteor-cfs-access-point/issues/3) ([elbowz](https://github.com/elbowz))
|
||||
|
||||
- Force browser to download with filename passed in url
|
||||
|
||||
Patches by GitHub user [@elbowz](https://github.com/elbowz).
|
||||
|
||||
## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.32)
|
||||
#### 28/07/14 by Eric Dobbertin
|
||||
- support collection-specific GET headers
|
||||
|
||||
- update API docs
|
||||
|
||||
## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.31)
|
||||
#### 06/07/14 by Eric Dobbertin
|
||||
- allow override filename
|
||||
|
||||
## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.30)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- ignore auth on server so that url method can be called on the server
|
||||
|
||||
## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.29)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- rework the new authtoken stuff to make it easier to debug and cleaner
|
||||
|
||||
## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.28)
|
||||
#### 29/04/14 by Eric Dobbertin
|
||||
- generate api docs
|
||||
|
||||
- adjustments to use new FS.File API functions, plus have `url` function omit query string whenever possible
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
- Switched to HTTP.call() to get the server time
|
||||
|
||||
- Better check for options.auth being a number. Check to see if we have Buffer() available on the server side. New check to make sure we have the token. Switched Metheor.method to HTTP.methods for the getServerTime() function.
|
||||
|
||||
- Expiration is now optional. If auth is set to a number, that is the number of seconds the token is valid for.
|
||||
|
||||
- Added time sync with the server for token generation.
|
||||
|
||||
- Added code to pass a token with a set expiration date from the client. Added token check on the server side.
|
||||
|
||||
Patches by GitHub user [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.27)
|
||||
#### 08/04/14 by Eric Dobbertin
|
||||
- clean up/fix whole-file upload handler
|
||||
|
||||
## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.26)
|
||||
#### 07/04/14 by Eric Dobbertin
|
||||
- add URL options to get temporary images while uploading and storing
|
||||
|
||||
## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.25)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- * allow `setBaseUrl` to be called either outside of Meteor.startup or inside * move encodeParams helper to FS.Utility
|
||||
|
||||
## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.24)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- properly remount URLs
|
||||
|
||||
- when uploading chunks, check the insert allow/deny since it's part of inserting
|
||||
|
||||
## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.23)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.22)
|
||||
#### 29/03/14 by Morten Henriksen
|
||||
- remove underscore deps
|
||||
|
||||
## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.21)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- add comments about shareId
|
||||
|
||||
## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.20)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- Rollback to specific git dependency
|
||||
|
||||
- Try modified test script
|
||||
|
||||
- deps are already in collectionFS
|
||||
|
||||
## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.19)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- try to fix travis test by using general package references
|
||||
|
||||
## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.18)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- If the read stream fails we send an error to the client
|
||||
|
||||
## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.17)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- remove smart lock
|
||||
|
||||
- commit smart.lock, trying to get tests to pass on travis
|
||||
|
||||
- some minor pkg adjustments; trying to get tests to pass on travis
|
||||
|
||||
## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.16)
|
||||
#### 18/03/14 by Morten Henriksen
|
||||
- Rollback to using the direct storage adapter - makes more sense when serving files
|
||||
|
||||
- shift to new http.methods streaming api
|
||||
|
||||
- move server side DDP access points to cfs-download-ddp pkg; update API docs
|
||||
|
||||
- fix typo...
|
||||
|
||||
- return something useful
|
||||
|
||||
- convert to streaming
|
||||
|
||||
- Add streaming WIP
|
||||
|
||||
- fix/adjust some tests; minor improvements to some handlers
|
||||
|
||||
- Add unmount and allow mount to use default selector function
|
||||
|
||||
- Refactor access point - wip
|
||||
|
||||
## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.15)
|
||||
#### 05/03/14 by Morten Henriksen
|
||||
- Refactor note, encode stuff should be prefixed into FS.Utility
|
||||
|
||||
- FS.File.url add user deps when auth is used
|
||||
|
||||
- fix url method
|
||||
|
||||
- query string fix
|
||||
|
||||
- move PUT access points for HTTP upload into this package; mount DELETE on /record/ as well as /files/; some fixes and improvements to handlers
|
||||
|
||||
## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.14)
|
||||
#### 03/03/14 by Eric Dobbertin
|
||||
- better error; return Buffer instead of converting to Uint8Array
|
||||
|
||||
## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.13)
|
||||
#### 02/03/14 by Eric Dobbertin
|
||||
- more tests, make everything work, add unpublish method
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.12)
|
||||
#### 01/03/14 by Eric Dobbertin
|
||||
- add travis-ci image
|
||||
|
||||
- rework URLs a bit, use http-publish package to publish FS.Collection listing, and add a test for this (!)
|
||||
|
||||
- add http-publish dependency
|
||||
|
||||
- del should be delete
|
||||
|
||||
## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.11)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move some code to other packages; redo the HTTP GET/DEL methods
|
||||
|
||||
## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.10)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move DDP upload methods to new cfs-upload-ddp package
|
||||
|
||||
## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.9)
|
||||
#### 21/02/14 by Eric Dobbertin
|
||||
- new URL syntax; use the store's file key instead of ID; also fix allow/deny checks with insecure
|
||||
|
||||
## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.8)
|
||||
#### 20/02/14 by Eric Dobbertin
|
||||
- support HTTP PUT of new file and fix PUT of existing file
|
||||
|
||||
## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.7)
|
||||
#### 17/02/14 by Morten Henriksen
|
||||
- add http-methods dependency
|
||||
|
||||
## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.6)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.5)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
- a few fixes and improvements
|
||||
|
||||
- need to actually mount it
|
||||
|
||||
- attempt at switching to generic HTTP access point; also add support for chunked http downloads (range header)
|
||||
|
||||
## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.4)
|
||||
#### 15/02/14 by Morten Henriksen
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
- corrected typo
|
||||
|
||||
- added debugging
|
||||
|
||||
- call HTTP.methods on server only
|
||||
|
||||
- run client side, too, for side effects
|
||||
|
||||
- rework for additional abstraction; also DDP methods don't need to be per-collection so they no longer are
|
||||
|
||||
## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.3)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.2)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.1)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
- init commit
|
||||
|
20
packages/wekan-cfs-access-point/LICENSE.md
Normal file
20
packages/wekan-cfs-access-point/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.
|
32
packages/wekan-cfs-access-point/README.md
Normal file
32
packages/wekan-cfs-access-point/README.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
wekan-cfs-access-point [](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point)
|
||||
=========================
|
||||
|
||||
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 added when you
|
||||
add the `wekan-cfs-standard-packages` package. You could potentially use your own access point
|
||||
package instead.
|
||||
|
||||
## Define a URL for Collection Listing
|
||||
|
||||
To define a URL that accepts GET requests and returns a list of published
|
||||
files in a FS.Collection:
|
||||
|
||||
```js
|
||||
Images = new FS.Collection("images", {
|
||||
stores: [myStore]
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
// `this` provides a context similar to Meteor.publish
|
||||
return Images.find();
|
||||
});
|
||||
```
|
||||
|
||||
The URL will be '/cfs/record/images', where the `cfs` piece is configurable
|
||||
using the `FS.HTTP.setBaseUrl` method.
|
||||
|
||||
## API Documentation
|
||||
|
||||
[Here](api.md)
|
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
FS.HTTP.setHeadersForGet = function setHeadersForGet() {
|
||||
// Client Stub
|
||||
};
|
||||
|
||||
FS.HTTP.now = function() {
|
||||
return new Date(new Date() + FS.HTTP._serverTimeDiff);
|
||||
};
|
||||
|
||||
// Returns the localstorage if its found and working
|
||||
// TODO: check if this works in IE
|
||||
// could use Meteor._localStorage - just needs a rewrite
|
||||
FS.HTTP._storage = function() {
|
||||
var storage,
|
||||
fail,
|
||||
uid;
|
||||
try {
|
||||
uid = "test";
|
||||
(storage = window.localStorage).setItem(uid, uid);
|
||||
fail = (storage.getItem(uid) !== uid);
|
||||
storage.removeItem(uid);
|
||||
if (fail) {
|
||||
storage = false;
|
||||
}
|
||||
} catch(e) {
|
||||
console.log("Error initializing storage for FS.HTTP");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return storage;
|
||||
};
|
||||
|
||||
// get our storage if found
|
||||
FS.HTTP.storage = FS.HTTP._storage();
|
||||
|
||||
FS.HTTP._prefix = 'fsHTTP.';
|
||||
|
||||
FS.HTTP._serverTimeDiff = 0; // Time difference in ms
|
||||
|
||||
if (FS.HTTP.storage) {
|
||||
// Initialize the FS.HTTP._serverTimeDiff
|
||||
FS.HTTP._serverTimeDiff = (1*FS.HTTP.storage.getItem(FS.HTTP._prefix+'timeDiff')) || 0;
|
||||
// At client startup we figure out the time difference between server and
|
||||
// client time - this includes lag and timezone
|
||||
Meteor.startup(function() {
|
||||
// Call the server method an get server time
|
||||
HTTP.get(rootUrlPathPrefix + '/cfs/servertime', function(error, result) {
|
||||
if (!error) {
|
||||
// Update our server time diff
|
||||
var dateNew = new Date(+result.content);
|
||||
FS.HTTP._serverTimeDiff = dateNew - new Date();// - lag or/and timezone
|
||||
// Update the localstorage
|
||||
FS.HTTP.storage.setItem(FS.HTTP._prefix + 'timeDiff', FS.HTTP._serverTimeDiff);
|
||||
} else {
|
||||
console.log(error.message);
|
||||
}
|
||||
}); // EO Server call
|
||||
});
|
||||
}
|
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";
|
||||
// Adjust the rootUrlPathPrefix if necessary
|
||||
if (rootUrlPathPrefix.length > 0) {
|
||||
if (rootUrlPathPrefix.slice(0, 1) !== '/') {
|
||||
rootUrlPathPrefix = '/' + rootUrlPathPrefix;
|
||||
}
|
||||
if (rootUrlPathPrefix.slice(-1) === '/') {
|
||||
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// prepend ROOT_URL when isCordova
|
||||
if (Meteor.isCordova) {
|
||||
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
baseUrl = '/cfs';
|
||||
FS.HTTP = FS.HTTP || {};
|
||||
|
||||
// Note the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setBaseUrl
|
||||
* @public
|
||||
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {
|
||||
|
||||
// Adjust the baseUrl if necessary
|
||||
if (newBaseUrl.slice(0, 1) !== '/') {
|
||||
newBaseUrl = '/' + newBaseUrl;
|
||||
}
|
||||
if (newBaseUrl.slice(-1) === '/') {
|
||||
newBaseUrl = newBaseUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
// Update the base URL
|
||||
baseUrl = newBaseUrl;
|
||||
|
||||
// Change the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
// Remount URLs with the new baseUrl, unmounting the old, on the server only.
|
||||
// If existingMountPoints is empty, then we haven't run the server startup
|
||||
// code yet, so this new URL will be used at that point for the initial mount.
|
||||
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {
|
||||
mountUrls();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* FS.File extensions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.urlRelative Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.returnWhenStored=false] Flag relevant only on server, Return the URL only when file has been saved to the requested store.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the relative HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.urlRelative = function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
options = FS.Utility.extend({
|
||||
store: null,
|
||||
auth: null,
|
||||
download: false,
|
||||
metadata: false,
|
||||
brokenIsFine: false,
|
||||
returnWhenStored: false,
|
||||
uploading: null, // return this URL while uploading
|
||||
storing: null, // return this URL while storing
|
||||
filename: null // override the filename that is shown to the user
|
||||
}, options.hash || options); // check for "hash" prop if called as helper
|
||||
|
||||
// Primarily useful for displaying a temporary image while uploading an image
|
||||
if (options.uploading && !self.isUploaded()) {
|
||||
return options.uploading;
|
||||
}
|
||||
|
||||
if (self.isMounted()) {
|
||||
// See if we've stored in the requested store yet
|
||||
var storeName = options.store || self.collection.primaryStore.name;
|
||||
if (!self.hasStored(storeName)) {
|
||||
if (options.storing) {
|
||||
return options.storing;
|
||||
} else if (!options.brokenIsFine) {
|
||||
// In case we want to get back the url only when he is stored
|
||||
if (Meteor.isServer && options.returnWhenStored) {
|
||||
// Wait till file is stored to storeName
|
||||
self.onStored(storeName);
|
||||
} else {
|
||||
// We want to return null if we know the URL will be a broken
|
||||
// link because then we can avoid rendering broken links, broken
|
||||
// images, etc.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add filename to end of URL if we can determine one
|
||||
var filename = options.filename || self.name({store: storeName});
|
||||
if (typeof filename === "string" && filename.length) {
|
||||
filename = '/' + filename;
|
||||
} else {
|
||||
filename = '';
|
||||
}
|
||||
|
||||
// TODO: Could we somehow figure out if the collection requires login?
|
||||
var authToken = '';
|
||||
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") {
|
||||
if (options.auth !== false) {
|
||||
// Add reactive deps on the user
|
||||
Meteor.userId();
|
||||
|
||||
var authObject = {
|
||||
authToken: Accounts._storedLoginToken() || ''
|
||||
};
|
||||
|
||||
// If it's a number, we use that as the expiration time (in seconds)
|
||||
if (options.auth === +options.auth) {
|
||||
authObject.expiration = FS.HTTP.now() + options.auth * 1000;
|
||||
}
|
||||
|
||||
// Set the authToken
|
||||
var authString = JSON.stringify(authObject);
|
||||
authToken = FS.Utility.btoa(authString);
|
||||
}
|
||||
} else if (typeof options.auth === "string") {
|
||||
// If the user supplies auth token the user will be responsible for
|
||||
// updating
|
||||
authToken = options.auth;
|
||||
}
|
||||
|
||||
// Construct query string
|
||||
var params = {};
|
||||
if (authToken !== '') {
|
||||
params.token = authToken;
|
||||
}
|
||||
if (options.download) {
|
||||
params.download = true;
|
||||
}
|
||||
if (options.store) {
|
||||
// We use options.store here instead of storeName because we want to omit the queryString
|
||||
// whenever possible, allowing users to have "clean" URLs if they want. The server will
|
||||
// assume the first store defined on the server, which means that we are assuming that
|
||||
// the first on the client is also the first on the server. If that's not the case, the
|
||||
// store option should be supplied.
|
||||
params.store = options.store;
|
||||
}
|
||||
var queryString = FS.Utility.encodeParams(params);
|
||||
if (queryString.length) {
|
||||
queryString = '?' + queryString;
|
||||
}
|
||||
|
||||
// Determine which URL to use
|
||||
var area;
|
||||
if (options.metadata) {
|
||||
area = '/record';
|
||||
} else {
|
||||
area = '/files';
|
||||
}
|
||||
|
||||
// Construct and return the http method url
|
||||
return baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.url Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.url = function(options) {
|
||||
self = this;
|
||||
return rootUrlPathPrefix + self.urlRelative(options);
|
||||
};
|
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
|
@ -0,0 +1,307 @@
|
|||
getHeaders = [];
|
||||
getHeadersByCollection = {};
|
||||
|
||||
var contentDisposition = Npm.require('content-disposition');
|
||||
|
||||
FS.HTTP.Handlers = {};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Del
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP DEL request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Del = function httpDelHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return
|
||||
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId);
|
||||
|
||||
/*
|
||||
* From the DELETE spec:
|
||||
* A successful response SHOULD be 200 (OK) if the response includes an
|
||||
* entity describing the status, 202 (Accepted) if the action has not
|
||||
* yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
* but the response does not include an entity.
|
||||
*/
|
||||
self.setStatusCode(200);
|
||||
|
||||
return {
|
||||
deleted: !!ref.file.remove()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.GetList
|
||||
* @public
|
||||
* @returns {Object} response
|
||||
*
|
||||
* HTTP GET file list request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.GetList = function httpGetListHandler() {
|
||||
// Not Yet Implemented
|
||||
// Need to check publications and return file list based on
|
||||
// what user is allowed to see
|
||||
};
|
||||
|
||||
/*
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
*/
|
||||
var requestRange = function(req, fileSize) {
|
||||
if (req) {
|
||||
if (req.headers) {
|
||||
var rangeString = req.headers.range;
|
||||
|
||||
// Make sure range is a string
|
||||
if (rangeString === ''+rangeString) {
|
||||
|
||||
// range will be in the format "bytes=0-32767"
|
||||
var parts = rangeString.split('=');
|
||||
var unit = parts[0];
|
||||
|
||||
// Make sure parts consists of two strings and range is of type "byte"
|
||||
if (parts.length == 2 && unit == 'bytes') {
|
||||
// Parse the range
|
||||
var range = parts[1].split('-');
|
||||
var start = Number(range[0]);
|
||||
var end = Number(range[1]);
|
||||
|
||||
// Fix invalid ranges?
|
||||
if (range[0] != start) start = 0;
|
||||
if (range[1] != end || !end) end = fileSize - 1;
|
||||
|
||||
// Make sure range consists of a start and end point of numbers and start is less than end
|
||||
if (start < end) {
|
||||
|
||||
var partSize = 0 - start + end + 1;
|
||||
|
||||
// Return the parsed range
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
length: partSize,
|
||||
size: fileSize,
|
||||
unit: unit,
|
||||
partial: (partSize < fileSize)
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new Meteor.Error(416, "Requested Range Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// The first part should be bytes
|
||||
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// No range found
|
||||
}
|
||||
|
||||
} else {
|
||||
// throw new Error('No request headers set for _parseRange function');
|
||||
}
|
||||
} else {
|
||||
throw new Error('No request object passed to _parseRange function');
|
||||
}
|
||||
|
||||
return {
|
||||
start: 0,
|
||||
end: fileSize - 1,
|
||||
length: fileSize,
|
||||
size: fileSize,
|
||||
unit: 'bytes',
|
||||
partial: false
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Get
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP GET request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Get = function httpGetHandler(ref) {
|
||||
var self = this;
|
||||
// Once we have the file, we can test allow/deny validators
|
||||
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?
|
||||
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/);
|
||||
|
||||
var storeName = ref.storeName;
|
||||
|
||||
// If no storeName was specified, use the first defined storeName
|
||||
if (typeof storeName !== "string") {
|
||||
// No store handed, we default to primary store
|
||||
storeName = ref.collection.primaryStore.name;
|
||||
}
|
||||
|
||||
// Get the storage reference
|
||||
var storage = ref.collection.storesLookup[storeName];
|
||||
|
||||
if (!storage) {
|
||||
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"');
|
||||
}
|
||||
|
||||
// Get the file
|
||||
var copyInfo = ref.file.copies[storeName];
|
||||
|
||||
if (!copyInfo) {
|
||||
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store');
|
||||
}
|
||||
|
||||
// Set the content type for file
|
||||
if (typeof copyInfo.type === "string") {
|
||||
self.setContentType(copyInfo.type);
|
||||
} else {
|
||||
self.setContentType('application/octet-stream');
|
||||
}
|
||||
|
||||
// Add 'Content-Disposition' header if requested a download/attachment URL
|
||||
if (typeof ref.download !== "undefined") {
|
||||
var filename = ref.filename || copyInfo.name;
|
||||
self.addHeader('Content-Disposition', contentDisposition(filename));
|
||||
} else {
|
||||
self.addHeader('Content-Disposition', 'inline');
|
||||
}
|
||||
|
||||
// Get the contents range from request
|
||||
var range = requestRange(self.request, copyInfo.size);
|
||||
|
||||
// Some browsers cope better if the content-range header is
|
||||
// still included even for the full file being returned.
|
||||
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
// If a chunk/range was requested instead of the whole file, serve that'
|
||||
if (range.partial) {
|
||||
self.setStatusCode(206, 'Partial Content');
|
||||
} else {
|
||||
self.setStatusCode(200, 'OK');
|
||||
}
|
||||
|
||||
// Add any other global custom headers and collection-specific custom headers
|
||||
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) {
|
||||
self.addHeader(header[0], header[1]);
|
||||
});
|
||||
|
||||
// Inform clients about length (or chunk length in case of ranges)
|
||||
self.addHeader('Content-Length', range.length);
|
||||
|
||||
// Last modified header (updatedAt from file info)
|
||||
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());
|
||||
|
||||
// Inform clients that we accept ranges for resumable chunked downloads
|
||||
self.addHeader('Accept-Ranges', range.unit);
|
||||
|
||||
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end});
|
||||
|
||||
readStream.on('error', function(err) {
|
||||
// Send proper error message on get error
|
||||
if (err.message && err.statusCode) {
|
||||
self.Error(new Meteor.Error(err.statusCode, err.message));
|
||||
} else {
|
||||
self.Error(new Meteor.Error(503, 'Service unavailable'));
|
||||
}
|
||||
});
|
||||
|
||||
readStream.pipe(self.createWriteStream());
|
||||
};
|
||||
|
||||
// File with unicode or other encodings filename can upload to server susscessfully,
|
||||
// but when download, the HTTP header "Content-Disposition" cannot accept
|
||||
// characters other than ASCII, the filename should be converted to binary or URI encoded.
|
||||
// https://github.com/wekan/wekan/issues/784
|
||||
const originalHandler = FS.HTTP.Handlers.Get;
|
||||
FS.HTTP.Handlers.Get = function (ref) {
|
||||
try {
|
||||
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
|
||||
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
|
||||
ref.filename = encodeURIComponent(ref.filename);
|
||||
} else if(userAgent.indexOf('firefox') >= 0) {
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
} else {
|
||||
/* safari*/
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
}
|
||||
} catch (ex){
|
||||
ref.filename = ref.filename;
|
||||
}
|
||||
return originalHandler.call(this, ref);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutInsert
|
||||
* @public
|
||||
* @returns {Object} response object with _id property
|
||||
*
|
||||
* HTTP PUT file insert request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
FS.debug && console.log("HTTP PUT (insert) handler");
|
||||
|
||||
// Create the nice FS.File
|
||||
var fileObj = new FS.File();
|
||||
|
||||
// Set its name
|
||||
fileObj.name(opts.filename || null);
|
||||
|
||||
// Attach the readstream as the file's data
|
||||
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
|
||||
|
||||
// Validate with insert allow/deny
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId);
|
||||
|
||||
// Insert file into collection, triggering readStream storage
|
||||
ref.collection.insert(fileObj);
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
// Return the new file id
|
||||
return {_id: fileObj._id};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutUpdate
|
||||
* @public
|
||||
* @returns {Object} response object with _id and chunk properties
|
||||
*
|
||||
* HTTP PUT file update chunk request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
var chunk = parseInt(opts.chunk, 10);
|
||||
if (isNaN(chunk)) chunk = 0;
|
||||
|
||||
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);
|
||||
|
||||
// Validate with insert allow/deny; also mounts and retrieves the file
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId);
|
||||
|
||||
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
return { _id: ref.file._id, chunk: chunk };
|
||||
};
|
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
|
@ -0,0 +1,362 @@
|
|||
var path = Npm.require("path");
|
||||
|
||||
HTTP.publishFormats({
|
||||
fileRecordFormat: function (input) {
|
||||
// Set the method scope content type to json
|
||||
this.setContentType('application/json');
|
||||
if (FS.Utility.isArray(input)) {
|
||||
return EJSON.stringify(FS.Utility.map(input, function (obj) {
|
||||
return FS.Utility.cloneFileRecord(obj);
|
||||
}));
|
||||
} else {
|
||||
return EJSON.stringify(FS.Utility.cloneFileRecord(input));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setHeadersForGet
|
||||
* @public
|
||||
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
|
||||
if (typeof collections === "string") {
|
||||
collections = [collections];
|
||||
}
|
||||
if (collections) {
|
||||
FS.Utility.each(collections, function(collectionName) {
|
||||
getHeadersByCollection[collectionName] = headers || [];
|
||||
});
|
||||
} else {
|
||||
getHeaders = headers || [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.publish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @param {Function} func - Publish function that returns a cursor.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Publishes all documents returned by the cursor at a GET URL
|
||||
* with the format baseUrl/record/collectionName. The publish
|
||||
* function `this` is similar to normal `Meteor.publish`.
|
||||
*/
|
||||
FS.HTTP.publish = function fsHttpPublish(collection, func) {
|
||||
var name = baseUrl + '/record/' + collection.name;
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.publish({
|
||||
name: name,
|
||||
defaultFormat: 'fileRecordFormat',
|
||||
collection: collection,
|
||||
collectionGet: true,
|
||||
collectionPost: false,
|
||||
documentGet: true,
|
||||
documentPut: false,
|
||||
documentDelete: false
|
||||
}, func);
|
||||
|
||||
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unpublish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
*/
|
||||
FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.unpublish(baseUrl + '/record/' + collection.name);
|
||||
};
|
||||
|
||||
_existingMountPoints = {};
|
||||
|
||||
/**
|
||||
* @method defaultSelectorFunction
|
||||
* @private
|
||||
* @returns { collection, file }
|
||||
*
|
||||
* This is the default selector function
|
||||
*/
|
||||
var defaultSelectorFunction = function() {
|
||||
var self = this;
|
||||
// Selector function
|
||||
//
|
||||
// This function will have to return the collection and the
|
||||
// file. If file not found undefined is returned - if null is returned the
|
||||
// search was not possible
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// Get the collection name from the url
|
||||
var collectionName = opts.collectionName;
|
||||
|
||||
// Get the id from the url
|
||||
var id = opts.id;
|
||||
|
||||
// Get the collection
|
||||
var collection = FS._collections[collectionName];
|
||||
|
||||
//if Mongo ObjectIds are used, then we need to use that in find statement
|
||||
if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') {
|
||||
// Get the file if possible else return null
|
||||
var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null;
|
||||
} else {
|
||||
var file = (id && collection)? collection.findOne({ _id: id }): null;
|
||||
}
|
||||
|
||||
|
||||
// Return the collection and the file
|
||||
return {
|
||||
collection: collection,
|
||||
file: file,
|
||||
storeName: opts.store,
|
||||
download: opts.download,
|
||||
filename: opts.filename
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* @method FS.HTTP.mount
|
||||
* @public
|
||||
* @param {array of string} mountPoints mount points to map rest functinality on
|
||||
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with
|
||||
*
|
||||
*/
|
||||
FS.HTTP.mount = function(mountPoints, selector_f) {
|
||||
// We take mount points as an array and we get a selector function
|
||||
var selectorFunction = selector_f || defaultSelectorFunction;
|
||||
|
||||
var accessPoint = {
|
||||
'stream': true,
|
||||
'auth': expirationAuth,
|
||||
'post': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// We dont support post - this would be normal insert eg. of filerecord?
|
||||
throw new Meteor.Error(501, "Not implemented", "Post is not supported");
|
||||
},
|
||||
'put': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will create a new FS.File instance and
|
||||
// insert the supplied data.
|
||||
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'get': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will return the published list of files ala
|
||||
// http.publish in json format
|
||||
return FS.HTTP.Handlers.GetList.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Get.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'delete': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Del.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var accessPoints = {};
|
||||
|
||||
// Add debug message
|
||||
FS.debug && console.log('Registered HTTP method URLs:');
|
||||
|
||||
FS.Utility.each(mountPoints, function(mountPoint) {
|
||||
// Couple mountpoint and accesspoint
|
||||
accessPoints[mountPoint] = accessPoint;
|
||||
// Remember our mountpoints
|
||||
_existingMountPoints[mountPoint] = mountPoint;
|
||||
// Add debug message
|
||||
FS.debug && console.log(mountPoint);
|
||||
});
|
||||
|
||||
// XXX: HTTP:methods should unmount existing mounts in case of overwriting?
|
||||
HTTP.methods(accessPoints);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unmount
|
||||
* @public
|
||||
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted
|
||||
*
|
||||
*/
|
||||
FS.HTTP.unmount = function(mountPoints) {
|
||||
// The mountPoints is optional, can be string or array if undefined then
|
||||
// _existingMountPoints will be used
|
||||
var unmountList;
|
||||
// Container for the mount points to unmount
|
||||
var unmountPoints = {};
|
||||
|
||||
if (typeof mountPoints === 'undefined') {
|
||||
// Use existing mount points - unmount all
|
||||
unmountList = _existingMountPoints;
|
||||
} else if (mountPoints === ''+mountPoints) {
|
||||
// Got a string
|
||||
unmountList = [mountPoints];
|
||||
} else if (mountPoints.length) {
|
||||
// Got an array
|
||||
unmountList = mountPoints;
|
||||
}
|
||||
|
||||
// If we have a list to unmount
|
||||
if (unmountList) {
|
||||
// Iterate over each item
|
||||
FS.Utility.each(unmountList, function(mountPoint) {
|
||||
// Check _existingMountPoints to make sure the mount point exists in our
|
||||
// context / was created by the FS.HTTP.mount
|
||||
if (_existingMountPoints[mountPoint]) {
|
||||
// Mark as unmount
|
||||
unmountPoints[mountPoint] = false;
|
||||
// Release
|
||||
delete _existingMountPoints[mountPoint];
|
||||
}
|
||||
});
|
||||
FS.debug && console.log('FS.HTTP.unmount:');
|
||||
FS.debug && console.log(unmountPoints);
|
||||
// Complete unmount
|
||||
HTTP.methods(unmountPoints);
|
||||
}
|
||||
};
|
||||
|
||||
// ### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
// *
|
||||
// baseUrl + '/files/:collectionName/:id/:filename',
|
||||
// baseUrl + '/files/:collectionName/:id',
|
||||
// baseUrl + '/files/:collectionName'
|
||||
//
|
||||
// Change/ replace the existing mount point by:
|
||||
// ```js
|
||||
// // unmount all existing
|
||||
// FS.HTTP.unmount();
|
||||
// // Create new mount point
|
||||
// FS.HTTP.mount([
|
||||
// '/cfs/files/:collectionName/:id/:filename',
|
||||
// '/cfs/files/:collectionName/:id',
|
||||
// '/cfs/files/:collectionName'
|
||||
// ]);
|
||||
// ```
|
||||
//
|
||||
mountUrls = function mountUrls() {
|
||||
// We unmount first in case we are calling this a second time
|
||||
FS.HTTP.unmount();
|
||||
|
||||
FS.HTTP.mount([
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
]);
|
||||
};
|
||||
|
||||
// Returns the userId from URL token
|
||||
var expirationAuth = function expirationAuth() {
|
||||
var self = this;
|
||||
|
||||
// Read the token from '/hello?token=base64'
|
||||
var encodedToken = self.query.token;
|
||||
|
||||
FS.debug && console.log("token: "+encodedToken);
|
||||
|
||||
if (!encodedToken || !Meteor.users) return false;
|
||||
|
||||
// Check the userToken before adding it to the db query
|
||||
// Set the this.userId
|
||||
var tokenString = FS.Utility.atob(encodedToken);
|
||||
|
||||
var tokenObject;
|
||||
try {
|
||||
tokenObject = JSON.parse(tokenString);
|
||||
} catch(err) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// XXX: Do some check here of the object
|
||||
var userToken = tokenObject.authToken;
|
||||
if (userToken !== ''+userToken) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// If we have an expiration token we should check that it's still valid
|
||||
if (tokenObject.expiration != null) {
|
||||
// check if its too old
|
||||
var now = Date.now();
|
||||
if (tokenObject.expiration < now) {
|
||||
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);
|
||||
throw new Meteor.Error(500, 'Expired token');
|
||||
}
|
||||
}
|
||||
|
||||
// We are not on a secure line - so we have to look up the user...
|
||||
var user = Meteor.users.findOne({
|
||||
$or: [
|
||||
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
|
||||
{'services.resume.loginTokens.token': userToken}
|
||||
]
|
||||
});
|
||||
|
||||
// Set the userId in the scope
|
||||
return user && user._id;
|
||||
};
|
||||
|
||||
HTTP.methods(
|
||||
{'/cfs/servertime': {
|
||||
get: function(data) {
|
||||
return Date.now().toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Unify client / server api
|
||||
FS.HTTP.now = function() {
|
||||
return Date.now();
|
||||
};
|
||||
|
||||
// Start up the basic mount points
|
||||
Meteor.startup(function () {
|
||||
mountUrls();
|
||||
});
|
271
packages/wekan-cfs-access-point/api.md
Normal file
271
packages/wekan-cfs-access-point/api.md
Normal file
|
@ -0,0 +1,271 @@
|
|||
## wekan-cfs-access-point Public API ##
|
||||
|
||||
CollectionFS, add ddp and http accesspoint capability
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
|
@ -0,0 +1,332 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-common.js"](access-point-common.js) Where: {server|client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-handlers.js"](access-point-handlers.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="self.setStatusCode"></a>*self*.setStatusCode {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
From the DELETE spec:
|
||||
A successful response SHOULD be 200 (OK) if the response includes an
|
||||
entity describing the status, 202 (Accepted) if the action has not
|
||||
yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
but the response does not include an entity.
|
||||
```
|
||||
*This property __setStatusCode__ is defined in `self`*
|
||||
|
||||
> ```self.setStatusCode(200);``` [access-point-handlers.js:27](access-point-handlers.js#L27)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="requestRange"></a>requestRange {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
```
|
||||
*This property is private*
|
||||
|
||||
> ```var requestRange = function(req, fileSize) { ...``` [access-point-handlers.js:60](access-point-handlers.js#L60)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-server.js"](access-point-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="defaultSelectorFunction"></a>defaultSelectorFunction() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Returns__ *{ collection, file }*
|
||||
|
||||
|
||||
This is the default selector function
|
||||
|
||||
> ```var defaultSelectorFunction = function() { ...``` [access-point-server.js:87](access-point-server.js#L87)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
65
packages/wekan-cfs-access-point/package.js
Normal file
65
packages/wekan-cfs-access-point/package.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-access-point',
|
||||
version: '0.1.50',
|
||||
summary: 'CollectionFS, add ddp and http accesspoint capability',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-access-point.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"content-disposition": "0.5.0"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
// This imply is needed for tests, and is technically probably correct anyway.
|
||||
api.imply([
|
||||
'wekan-cfs-base-package'
|
||||
]);
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-file@0.1.16',
|
||||
//Core packages
|
||||
'check',
|
||||
'ejson',
|
||||
//Other packages
|
||||
'wekan-cfs-http-methods@0.0.29',
|
||||
'wekan-cfs-http-publish@0.0.13'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-handlers.js',
|
||||
'access-point-server.js'
|
||||
], 'server');
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-client.js'
|
||||
], 'client');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-access-point',
|
||||
'wekan-cfs-standard-packages@0.0.2',
|
||||
'wekan-cfs-gridfs@0.0.0',
|
||||
//Core packages
|
||||
'test-helpers',
|
||||
'http',
|
||||
'tinytest',
|
||||
'underscore',
|
||||
'ejson',
|
||||
'ordered-dict',
|
||||
'random',
|
||||
'deps'
|
||||
]);
|
||||
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
});
|
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-access-point - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Meteor.subscribe("img");
|
||||
|
||||
var id;
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - addTestImage', function(test, onComplete) {
|
||||
Meteor.call('addTestImage', function(err, result) {
|
||||
id = result;
|
||||
test.equal(typeof id, "string", "Test image was not inserted properly");
|
||||
//Don't continue until the data has been stored
|
||||
Deps.autorun(function (c) {
|
||||
var img = Images.findOne(id);
|
||||
if (img && img.hasCopy('gridList')) {
|
||||
onComplete();
|
||||
c.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET list of files in collection', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images'), function(err, result) {
|
||||
// Test the length of array result
|
||||
var len = result.data && result.data.length;
|
||||
test.isTrue(!!len, 'Result was empty');
|
||||
// Get the object
|
||||
var obj = result.data && result.data[0] || {};
|
||||
test.equal(obj._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET filerecord', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
// Get the object
|
||||
var obj = result.data;
|
||||
test.equal(typeof obj, "object", "Expected object data");
|
||||
test.equal(obj._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET file itself', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.isTrue(!!result.content, "Expected content in response");
|
||||
console.log(result);
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT new file data (update)', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT insert a new file', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images'), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - DELETE filerecord and data', function(test, onComplete) {
|
||||
|
||||
HTTP.del(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
|
||||
// Make sure it's gone
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
test.isTrue(!!err, 'Expected 404 error');
|
||||
test.equal(result.statusCode, 404, "Expected 404 response");
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//TODO test FS.File.prototype.url method with various options
|
||||
|
||||
//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)
|
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
FS.debug = true;
|
||||
|
||||
Tinytest.add('cfs-access-point - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Images.allow({
|
||||
insert: function() {
|
||||
return true;
|
||||
},
|
||||
update: function() {
|
||||
return true;
|
||||
},
|
||||
remove: function() {
|
||||
return true;
|
||||
},
|
||||
download: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.publish("img", function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
addTestImage: function() {
|
||||
Images.remove({});
|
||||
var url = "http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg";
|
||||
var fsFile = Images.insert(url);
|
||||
return fsFile._id;
|
||||
}
|
||||
});
|
||||
|
||||
//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)
|
5
packages/wekan-cfs-base-package/.travis.yml
Normal file
5
packages/wekan-cfs-base-package/.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-base-package/LICENSE.md
Normal file
20
packages/wekan-cfs-base-package/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.
|
11
packages/wekan-cfs-base-package/README.md
Normal file
11
packages/wekan-cfs-base-package/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
wekan-cfs-base-package
|
||||
=========================
|
||||
|
||||
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 added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
||||
|
||||
This package provides the `FS` namespace and helper methods used by many
|
||||
CollectionFS packages.
|
213
packages/wekan-cfs-base-package/api.md
Normal file
213
packages/wekan-cfs-base-package/api.md
Normal file
|
@ -0,0 +1,213 @@
|
|||
## cfs-base-package Public API ##
|
||||
|
||||
CollectionFS, Base package
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
#############################################################################
|
||||
|
||||
HELPERS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __cloneFileRecord__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __full__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Set `true` to prevent certain properties from being omitted from the clone.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
Cloned filerecord
|
||||
|
||||
|
||||
Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
it's an FS.File instance, but which we never want to be part of the stored
|
||||
filerecord.
|
||||
|
||||
This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
to specify whatever additional properties they wish.
|
||||
|
||||
In general, we expect the following whitelist properties used by the internal and
|
||||
external APIs:
|
||||
|
||||
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
|
||||
Those properties, and any additional properties added by the user, should be present
|
||||
in the returned object, which is suitable for inserting into the backing collection or
|
||||
extending an FS.File instance.
|
||||
|
||||
|
||||
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __f__ *{Function}* (Optional)
|
||||
|
||||
A callback function, if you have one. Can be undefined or null.
|
||||
|
||||
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
|
||||
|
||||
Error or error message (string)
|
||||
|
||||
|
||||
__Returns__ *{Any}*
|
||||
the callback result if any
|
||||
|
||||
|
||||
Handle Error, creates an Error instance with the given text. If callback is
|
||||
a function, passes the error to that function. Otherwise throws it. Useful
|
||||
for dealing with errors in methods that optionally accept a callback.
|
||||
|
||||
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.noop"></a>*fsUtility*.noop() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __noop__ is defined in `FS.Utility`*
|
||||
|
||||
Use this to hand a no operation / empty function
|
||||
|
||||
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL that may or may not have an extension.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The extension or an empty string if no extension found.
|
||||
|
||||
|
||||
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename that may or may not already have an extension.
|
||||
|
||||
* __ext__ *{String}*
|
||||
|
||||
An extension without leading period, which you want to be the new extension on `name`.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename with changed extension.
|
||||
|
||||
|
||||
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __binaryToBuffer__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Uint8Array}*
|
||||
|
||||
__Returns__ *{Buffer}*
|
||||
|
||||
|
||||
Converts a Uint8Array instance to a Node Buffer instance
|
||||
|
||||
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __bufferToBinary__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer}*
|
||||
|
||||
__Returns__ *{Uint8Array}*
|
||||
|
||||
|
||||
Converts a Node Buffer instance to a Uint8Array instance
|
||||
|
||||
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __eachFile__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __e__ *{[Event](#Event)}*
|
||||
|
||||
Browser event
|
||||
|
||||
* __f__ *{Function}*
|
||||
|
||||
Function to run for each file found in the event.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Utility for iteration over files in event
|
||||
|
||||
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)
|
||||
|
||||
|
51
packages/wekan-cfs-base-package/base-client.js
Normal file
51
packages/wekan-cfs-base-package/base-client.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
//XXX not sure this is still working properly?
|
||||
FS.Utility.connectionLogin = function(connection) {
|
||||
// We check if the accounts package is installed, since we depend on
|
||||
// `Meteor.userId()`
|
||||
if (typeof Accounts !== 'undefined') {
|
||||
// Monitor logout from main connection
|
||||
Meteor.startup(function() {
|
||||
Tracker.autorun(function() {
|
||||
var userId = Meteor.userId();
|
||||
if (userId) {
|
||||
connection.onReconnect = function() {
|
||||
var token = Accounts._storedLoginToken();
|
||||
connection.apply('login', [{resume: token}], function(err, result) {
|
||||
if (!err && result) {
|
||||
connection.setUserId(result.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
} else {
|
||||
connection.onReconnect = null;
|
||||
connection.setUserId(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.eachFile
|
||||
* @public
|
||||
* @param {Event} e - Browser event
|
||||
* @param {Function} f - Function to run for each file found in the event.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Utility for iteration over files in event
|
||||
*/
|
||||
FS.Utility.eachFile = function(e, f) {
|
||||
var evt = (e.originalEvent || e);
|
||||
|
||||
var files = evt.target.files;
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
files = evt.dataTransfer ? evt.dataTransfer.files : [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
f(files[i], i);
|
||||
}
|
||||
};
|
317
packages/wekan-cfs-base-package/base-common.js
Normal file
317
packages/wekan-cfs-base-package/base-common.js
Normal file
|
@ -0,0 +1,317 @@
|
|||
// Exported namespace
|
||||
FS = {};
|
||||
|
||||
// namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead?
|
||||
FS.Store = {
|
||||
GridFS: function () {
|
||||
throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.');
|
||||
},
|
||||
FileSystem: function () {
|
||||
throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.');
|
||||
},
|
||||
S3: function () {
|
||||
throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.');
|
||||
},
|
||||
WABS: function () {
|
||||
throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.');
|
||||
},
|
||||
Dropbox: function () {
|
||||
throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.');
|
||||
}
|
||||
};
|
||||
|
||||
// namespace for access points
|
||||
FS.AccessPoint = {};
|
||||
|
||||
// namespace for utillities
|
||||
FS.Utility = {};
|
||||
|
||||
// A general place for any package to store global config settings
|
||||
FS.config = {};
|
||||
|
||||
// An internal collection reference
|
||||
FS._collections = {};
|
||||
|
||||
// Test scope
|
||||
_Utility = {};
|
||||
|
||||
// #############################################################################
|
||||
//
|
||||
// HELPERS
|
||||
//
|
||||
// #############################################################################
|
||||
|
||||
/** @method _Utility.defaultZero
|
||||
* @private
|
||||
* @param {Any} val Returns number or 0 if value is a falsy
|
||||
*/
|
||||
_Utility.defaultZero = function(val) {
|
||||
return +(val || 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.cloneFileRecord
|
||||
* @public
|
||||
* @param {FS.File|FS.Collection filerecord} rec
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone.
|
||||
* @returns {Object} Cloned filerecord
|
||||
*
|
||||
* Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
* it's an FS.File instance, but which we never want to be part of the stored
|
||||
* filerecord.
|
||||
*
|
||||
* This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
* to specify whatever additional properties they wish.
|
||||
*
|
||||
* In general, we expect the following whitelist properties used by the internal and
|
||||
* external APIs:
|
||||
*
|
||||
* _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
*
|
||||
* Those properties, and any additional properties added by the user, should be present
|
||||
* in the returned object, which is suitable for inserting into the backing collection or
|
||||
* extending an FS.File instance.
|
||||
*
|
||||
*/
|
||||
FS.Utility.cloneFileRecord = function(rec, options) {
|
||||
options = options || {};
|
||||
var result = {};
|
||||
// We use this method for two purposes. If using it to clone one FS.File into another, then
|
||||
// we want a full clone. But if using it to get a filerecord object for inserting into the
|
||||
// internal collection, then there are certain properties we want to omit so that they aren't
|
||||
// stored in the collection.
|
||||
var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform'];
|
||||
for (var prop in rec) {
|
||||
if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) {
|
||||
result[prop] = rec[prop];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.defaultCallback
|
||||
* @public
|
||||
* @param {Error} [err]
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Can be used as a default callback for client methods that need a callback.
|
||||
* Simply throws the provided error if there is one.
|
||||
*/
|
||||
FS.Utility.defaultCallback = function defaultCallback(err) {
|
||||
if (err) {
|
||||
// Show gentle error if Meteor error
|
||||
if (err instanceof Meteor.Error) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
// Normal error, just throw error
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.defaultCallback
|
||||
* @public
|
||||
* @param {Function} [f] A callback function, if you have one. Can be undefined or null.
|
||||
* @param {Meteor.Error | Error | String} [err] Error or error message (string)
|
||||
* @returns {Any} the callback result if any
|
||||
*
|
||||
* Handle Error, creates an Error instance with the given text. If callback is
|
||||
* a function, passes the error to that function. Otherwise throws it. Useful
|
||||
* for dealing with errors in methods that optionally accept a callback.
|
||||
*/
|
||||
FS.Utility.handleError = function(f, err, result) {
|
||||
// Set callback
|
||||
var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback;
|
||||
// Set the err
|
||||
var error = (err === ''+err)? new Error(err) : err;
|
||||
// callback
|
||||
return callback(error, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method FS.Utility.noop
|
||||
* @public
|
||||
* Use this to hand a no operation / empty function
|
||||
*/
|
||||
FS.Utility.noop = function() {};
|
||||
|
||||
/**
|
||||
* @method validateAction
|
||||
* @private
|
||||
* @param {Object} validators - The validators object to use, with `deny` and `allow` properties.
|
||||
* @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators.
|
||||
* @param {String} userId - The ID of the user who is attempting the action.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Throws a "400-Bad Request" Meteor error if the file is not mounted or
|
||||
* a "400-Access denied" Meteor error if the action is not allowed.
|
||||
*/
|
||||
FS.Utility.validateAction = function validateAction(validators, fileObj, userId) {
|
||||
var denyValidators = validators.deny;
|
||||
var allowValidators = validators.allow;
|
||||
|
||||
// If insecure package is used and there are no validators defined,
|
||||
// allow the action.
|
||||
if (typeof Package === 'object'
|
||||
&& Package.insecure
|
||||
&& denyValidators.length + allowValidators.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If already mounted, validators should receive a fileObj
|
||||
// that is fully populated
|
||||
if (fileObj.isMounted()) {
|
||||
fileObj.getFileRecord();
|
||||
}
|
||||
|
||||
// Any deny returns true means denied.
|
||||
if (_.any(denyValidators, function(validator) {
|
||||
return validator(userId, fileObj);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (_.all(allowValidators, function(validator) {
|
||||
return !validator(userId, fileObj);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.getFileName
|
||||
* @private
|
||||
* @param {String} name - A filename, filepath, or URL
|
||||
* @returns {String} The filename without the URL, filepath, or query string
|
||||
*/
|
||||
FS.Utility.getFileName = function utilGetFileName(name) {
|
||||
// in case it's a URL, strip off potential query string
|
||||
// should have no effect on filepath
|
||||
name = name.split('?')[0];
|
||||
// strip off beginning path or url
|
||||
var lastSlash = name.lastIndexOf('/');
|
||||
if (lastSlash !== -1) {
|
||||
name = name.slice(lastSlash + 1);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.getFileExtension
|
||||
* @public
|
||||
* @param {String} name - A filename, filepath, or URL that may or may not have an extension.
|
||||
* @returns {String} The extension or an empty string if no extension found.
|
||||
*/
|
||||
FS.Utility.getFileExtension = function utilGetFileExtension(name) {
|
||||
name = FS.Utility.getFileName(name);
|
||||
// Seekout the last '.' if found
|
||||
var found = name.lastIndexOf('.');
|
||||
// Return the extension if found else ''
|
||||
// If found is -1, we return '' because there is no extension
|
||||
// If found is 0, we return '' because it's a hidden file
|
||||
return (found > 0 ? name.slice(found + 1).toLowerCase() : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.setFileExtension
|
||||
* @public
|
||||
* @param {String} name - A filename that may or may not already have an extension.
|
||||
* @param {String} ext - An extension without leading period, which you want to be the new extension on `name`.
|
||||
* @returns {String} The filename with changed extension.
|
||||
*/
|
||||
FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) {
|
||||
if (!name || !name.length) {
|
||||
return name;
|
||||
}
|
||||
var currentExt = FS.Utility.getFileExtension(name);
|
||||
if (currentExt.length) {
|
||||
name = name.slice(0, currentExt.length * -1) + ext;
|
||||
} else {
|
||||
name = name + '.' + ext;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/*
|
||||
* Borrowed these from http package
|
||||
*/
|
||||
FS.Utility.encodeParams = function encodeParams(params) {
|
||||
var buf = [];
|
||||
_.each(params, function(value, key) {
|
||||
if (buf.length)
|
||||
buf.push('&');
|
||||
buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value));
|
||||
});
|
||||
return buf.join('').replace(/%20/g, '+');
|
||||
};
|
||||
|
||||
FS.Utility.encodeString = function encodeString(str) {
|
||||
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
};
|
||||
|
||||
/*
|
||||
* btoa and atob shims for client and server
|
||||
*/
|
||||
|
||||
FS.Utility._btoa = function _fsUtility_btoa(str) {
|
||||
var buffer;
|
||||
|
||||
if (str instanceof Buffer) {
|
||||
buffer = str;
|
||||
} else {
|
||||
buffer = new Buffer(str.toString(), 'binary');
|
||||
}
|
||||
|
||||
return buffer.toString('base64');
|
||||
};
|
||||
|
||||
FS.Utility.btoa = function fsUtility_btoa(str) {
|
||||
if (typeof btoa === 'function') {
|
||||
// Client
|
||||
return btoa(str);
|
||||
} else if (typeof Buffer !== 'undefined') {
|
||||
// Server
|
||||
return FS.Utility._btoa(str);
|
||||
} else {
|
||||
throw new Error('FS.Utility.btoa: Cannot base64 encode on your system');
|
||||
}
|
||||
};
|
||||
|
||||
FS.Utility._atob = function _fsUtility_atob(str) {
|
||||
return new Buffer(str, 'base64').toString('binary');
|
||||
};
|
||||
|
||||
FS.Utility.atob = function fsUtility_atob(str) {
|
||||
if (typeof atob === 'function') {
|
||||
// Client
|
||||
return atob(str);
|
||||
} else if (typeof Buffer !== 'undefined') {
|
||||
// Server
|
||||
return FS.Utility._atob(str);
|
||||
} else {
|
||||
throw new Error('FS.Utility.atob: Cannot base64 encode on your system');
|
||||
}
|
||||
};
|
||||
|
||||
// Api wrap for 3party libs like underscore
|
||||
FS.Utility.extend = _.extend;
|
||||
|
||||
FS.Utility.each = _.each;
|
||||
|
||||
FS.Utility.isEmpty = _.isEmpty;
|
||||
|
||||
FS.Utility.indexOf = _.indexOf;
|
||||
|
||||
FS.Utility.isArray = _.isArray;
|
||||
|
||||
FS.Utility.map = _.map;
|
||||
|
||||
FS.Utility.once = _.once;
|
||||
|
||||
FS.Utility.include = _.include;
|
||||
|
||||
FS.Utility.size = _.size;
|
95
packages/wekan-cfs-base-package/base-server.js
Normal file
95
packages/wekan-cfs-base-package/base-server.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* @method FS.Utility.binaryToBuffer
|
||||
* @public
|
||||
* @param {Uint8Array} data
|
||||
* @returns {Buffer}
|
||||
*
|
||||
* Converts a Uint8Array instance to a Node Buffer instance
|
||||
*/
|
||||
FS.Utility.binaryToBuffer = function(data) {
|
||||
var len = data.length;
|
||||
var buffer = new Buffer(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
buffer[i] = data[i];
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.bufferToBinary
|
||||
* @public
|
||||
* @param {Buffer} data
|
||||
* @returns {Uint8Array}
|
||||
*
|
||||
* Converts a Node Buffer instance to a Uint8Array instance
|
||||
*/
|
||||
FS.Utility.bufferToBinary = function(data) {
|
||||
var len = data.length;
|
||||
var binary = EJSON.newBinary(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary[i] = data[i];
|
||||
}
|
||||
return binary;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.safeCallback
|
||||
* @public
|
||||
* @param {Function} callback
|
||||
* @returns {Function}
|
||||
*
|
||||
* Makes a callback safe for Meteor code
|
||||
*/
|
||||
FS.Utility.safeCallback = function (callback) {
|
||||
return Meteor.bindEnvironment(callback, function(err) { throw err; });
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.safeStream
|
||||
* @public
|
||||
* @param {Stream} nodestream
|
||||
* @returns {Stream}
|
||||
*
|
||||
* Adds `safeOn` and `safeOnce` methods to a NodeJS Stream
|
||||
* object. These are the same as `on` and `once`, except
|
||||
* that the callback is wrapped for use in Meteor.
|
||||
*/
|
||||
FS.Utility.safeStream = function(nodestream) {
|
||||
if (!nodestream || typeof nodestream.on !== 'function')
|
||||
throw new Error('FS.Utility.safeStream requires a NodeJS Stream');
|
||||
|
||||
// Create Meteor safe events
|
||||
nodestream.safeOn = function(name, callback) {
|
||||
return nodestream.on(name, FS.Utility.safeCallback(callback));
|
||||
};
|
||||
|
||||
// Create Meteor safe events
|
||||
nodestream.safeOnce = function(name, callback) {
|
||||
return nodestream.once(name, FS.Utility.safeCallback(callback));
|
||||
};
|
||||
|
||||
// Return the modified stream - modified anyway
|
||||
return nodestream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.eachFileFromPath
|
||||
* @public
|
||||
* @param {String} p - Server path
|
||||
* @param {Function} f - Function to run for each file found in the path.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Utility for iteration over files from path on server
|
||||
*/
|
||||
FS.Utility.eachFileFromPath = function(p, f) {
|
||||
var fs = Npm.require('fs');
|
||||
var path = Npm.require('path');
|
||||
var files = fs.readdirSync(p);
|
||||
files.map(function (file) {
|
||||
return path.join(p, file);
|
||||
}).filter(function (filePath) {
|
||||
return fs.statSync(filePath).isFile() && path.basename(filePath)[0] !== '.';
|
||||
}).forEach(function (filePath) {
|
||||
f(filePath);
|
||||
});
|
||||
};
|
293
packages/wekan-cfs-base-package/internal.api.md
Normal file
293
packages/wekan-cfs-base-package/internal.api.md
Normal file
|
@ -0,0 +1,293 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-common.js"](base-common.js) Where: {server|client}__
|
||||
|
||||
***
|
||||
|
||||
#############################################################################
|
||||
|
||||
HELPERS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="_Utility.defaultZero"></a>*_utility*.defaultZero(val) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __defaultZero__ is defined in `_Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __val__ *{Any}*
|
||||
|
||||
Returns number or 0 if value is a falsy
|
||||
|
||||
|
||||
> ```_Utility.defaultZero = function(val) { ...``` [base-common.js:42](base-common.js#L42)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __cloneFileRecord__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __full__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Set `true` to prevent certain properties from being omitted from the clone.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
Cloned filerecord
|
||||
|
||||
|
||||
Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
it's an FS.File instance, but which we never want to be part of the stored
|
||||
filerecord.
|
||||
|
||||
This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
to specify whatever additional properties they wish.
|
||||
|
||||
In general, we expect the following whitelist properties used by the internal and
|
||||
external APIs:
|
||||
|
||||
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
|
||||
Those properties, and any additional properties added by the user, should be present
|
||||
in the returned object, which is suitable for inserting into the backing collection or
|
||||
extending an FS.File instance.
|
||||
|
||||
|
||||
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __f__ *{Function}* (Optional)
|
||||
|
||||
A callback function, if you have one. Can be undefined or null.
|
||||
|
||||
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
|
||||
|
||||
Error or error message (string)
|
||||
|
||||
|
||||
__Returns__ *{Any}*
|
||||
the callback result if any
|
||||
|
||||
|
||||
Handle Error, creates an Error instance with the given text. If callback is
|
||||
a function, passes the error to that function. Otherwise throws it. Useful
|
||||
for dealing with errors in methods that optionally accept a callback.
|
||||
|
||||
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.noop"></a>*fsUtility*.noop() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __noop__ is defined in `FS.Utility`*
|
||||
|
||||
Use this to hand a no operation / empty function
|
||||
|
||||
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="validateAction"></a>validateAction(validators, fileObj, userId) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __validators__ *{Object}*
|
||||
|
||||
The validators object to use, with `deny` and `allow` properties.
|
||||
|
||||
* __fileObj__ *{[FS.File](#FS.File)}*
|
||||
|
||||
Mounted or mountable file object to be passed to validators.
|
||||
|
||||
* __userId__ *{String}*
|
||||
|
||||
The ID of the user who is attempting the action.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Throws a "400-Bad Request" Meteor error if the file is not mounted or
|
||||
a "400-Access denied" Meteor error if the action is not allowed.
|
||||
|
||||
> ```FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { ...``` [base-common.js:147](base-common.js#L147)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileName"></a>*fsUtility*.getFileName(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getFileName__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename without the URL, filepath, or query string
|
||||
|
||||
|
||||
> ```FS.Utility.getFileName = function utilGetFileName(name) { ...``` [base-common.js:187](base-common.js#L187)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL that may or may not have an extension.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The extension or an empty string if no extension found.
|
||||
|
||||
|
||||
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename that may or may not already have an extension.
|
||||
|
||||
* __ext__ *{String}*
|
||||
|
||||
An extension without leading period, which you want to be the new extension on `name`.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename with changed extension.
|
||||
|
||||
|
||||
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-server.js"](base-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __binaryToBuffer__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Uint8Array}*
|
||||
|
||||
__Returns__ *{Buffer}*
|
||||
|
||||
|
||||
Converts a Uint8Array instance to a Node Buffer instance
|
||||
|
||||
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __bufferToBinary__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer}*
|
||||
|
||||
__Returns__ *{Uint8Array}*
|
||||
|
||||
|
||||
Converts a Node Buffer instance to a Uint8Array instance
|
||||
|
||||
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-client.js"](base-client.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __eachFile__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __e__ *{[Event](#Event)}*
|
||||
|
||||
Browser event
|
||||
|
||||
* __f__ *{Function}*
|
||||
|
||||
Function to run for each file found in the event.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Utility for iteration over files in event
|
||||
|
||||
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)
|
||||
|
||||
|
37
packages/wekan-cfs-base-package/package.js
Normal file
37
packages/wekan-cfs-base-package/package.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
Package.describe({
|
||||
version: '0.0.30',
|
||||
name: 'wekan-cfs-base-package',
|
||||
summary: 'CollectionFS, Base package',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-base-package.git'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['deps', 'underscore', 'ejson']);
|
||||
|
||||
if (api.export) {
|
||||
api.export('FS');
|
||||
api.export('_Utility', { testOnly: true });
|
||||
}
|
||||
|
||||
api.addFiles([
|
||||
'base-common.js',
|
||||
'base-server.js'
|
||||
], 'server');
|
||||
|
||||
api.addFiles([
|
||||
'polyfill.base64.js',
|
||||
'base-common.js',
|
||||
'base-client.js'
|
||||
], 'client');
|
||||
});
|
||||
|
||||
// Package.on_test(function (api) {
|
||||
// api.use(['wekan-cfs-base-package', 'cfs-file']);
|
||||
// api.use('test-helpers', 'server');
|
||||
// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
|
||||
// 'random', 'deps']);
|
||||
|
||||
// api.add_files('tests/common-tests.js', ['client', 'server']);
|
||||
// });
|
179
packages/wekan-cfs-base-package/polyfill.base64.js
Normal file
179
packages/wekan-cfs-base-package/polyfill.base64.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Nick Galbreath
|
||||
* http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* base64 encode/decode compatible with window.btoa/atob
|
||||
*
|
||||
* window.atob/btoa is a Firefox extension to convert binary data (the "b")
|
||||
* to base64 (ascii, the "a").
|
||||
*
|
||||
* It is also found in Safari and Chrome. It is not available in IE.
|
||||
*
|
||||
* if (!window.btoa) window.btoa = base64.encode
|
||||
* if (!window.atob) window.atob = base64.decode
|
||||
*
|
||||
* The original spec's for atob/btoa are a bit lacking
|
||||
* https://developer.mozilla.org/en/DOM/window.atob
|
||||
* https://developer.mozilla.org/en/DOM/window.btoa
|
||||
*
|
||||
* window.btoa and base64.encode takes a string where charCodeAt is [0,255]
|
||||
* If any character is not [0,255], then an DOMException(5) is thrown.
|
||||
*
|
||||
* window.atob and base64.decode take a base64-encoded string
|
||||
* If the input length is not a multiple of 4, or contains invalid characters
|
||||
* then an DOMException(5) is thrown.
|
||||
*/
|
||||
var base64 = {};
|
||||
base64.PADCHAR = '=';
|
||||
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
base64.makeDOMException = function() {
|
||||
// sadly in FF,Safari,Chrome you can't make a DOMException
|
||||
var e, tmp;
|
||||
|
||||
try {
|
||||
return new DOMException(DOMException.INVALID_CHARACTER_ERR);
|
||||
} catch (tmp) {
|
||||
// not available, just passback a duck-typed equiv
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
|
||||
var ex = new Error("DOM Exception 5");
|
||||
|
||||
// ex.number and ex.description is IE-specific.
|
||||
ex.code = ex.number = 5;
|
||||
ex.name = ex.description = "INVALID_CHARACTER_ERR";
|
||||
|
||||
// Safari/Chrome output format
|
||||
ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
base64.getbyte64 = function(s,i) {
|
||||
// This is oddly fast, except on Chrome/V8.
|
||||
// Minimal or no improvement in performance by using a
|
||||
// object with properties mapping chars to value (eg. 'A': 0)
|
||||
var idx = base64.ALPHA.indexOf(s.charAt(i));
|
||||
if (idx === -1) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
base64.decode = function(s) {
|
||||
// convert to string
|
||||
s = '' + s;
|
||||
var getbyte64 = base64.getbyte64;
|
||||
var pads, i, b10;
|
||||
var imax = s.length
|
||||
if (imax === 0) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (imax % 4 !== 0) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
|
||||
pads = 0
|
||||
if (s.charAt(imax - 1) === base64.PADCHAR) {
|
||||
pads = 1;
|
||||
if (s.charAt(imax - 2) === base64.PADCHAR) {
|
||||
pads = 2;
|
||||
}
|
||||
// either way, we want to ignore this last block
|
||||
imax -= 4;
|
||||
}
|
||||
|
||||
var x = [];
|
||||
for (i = 0; i < imax; i += 4) {
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
|
||||
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
|
||||
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
|
||||
}
|
||||
|
||||
switch (pads) {
|
||||
case 1:
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
|
||||
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
|
||||
break;
|
||||
case 2:
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
|
||||
x.push(String.fromCharCode(b10 >> 16));
|
||||
break;
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
base64.getbyte = function(s,i) {
|
||||
var x = s.charCodeAt(i);
|
||||
if (x > 255) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
base64.encode = function(s) {
|
||||
if (arguments.length !== 1) {
|
||||
throw new SyntaxError("Not enough arguments");
|
||||
}
|
||||
var padchar = base64.PADCHAR;
|
||||
var alpha = base64.ALPHA;
|
||||
var getbyte = base64.getbyte;
|
||||
|
||||
var i, b10;
|
||||
var x = [];
|
||||
|
||||
// convert to string
|
||||
s = '' + s;
|
||||
|
||||
var imax = s.length - s.length % 3;
|
||||
|
||||
if (s.length === 0) {
|
||||
return s;
|
||||
}
|
||||
for (i = 0; i < imax; i += 3) {
|
||||
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
|
||||
x.push(alpha.charAt(b10 >> 18));
|
||||
x.push(alpha.charAt((b10 >> 12) & 0x3F));
|
||||
x.push(alpha.charAt((b10 >> 6) & 0x3f));
|
||||
x.push(alpha.charAt(b10 & 0x3f));
|
||||
}
|
||||
switch (s.length - imax) {
|
||||
case 1:
|
||||
b10 = getbyte(s,i) << 16;
|
||||
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
||||
padchar + padchar);
|
||||
break;
|
||||
case 2:
|
||||
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
|
||||
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
||||
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
|
||||
break;
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
if (!window.btoa) window.btoa = base64.encode
|
||||
if (!window.atob) window.atob = base64.decode
|
161
packages/wekan-cfs-base-package/tests/common-tests.js
Normal file
161
packages/wekan-cfs-base-package/tests/common-tests.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
function equals(a, b) {
|
||||
return EJSON.stringify(a) === EJSON.stringify(b);
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-base-package - test environment', function(test) {
|
||||
test.isTrue(typeof FS !== 'undefined',
|
||||
'FS scope not declared');
|
||||
|
||||
test.isTrue(typeof FS.Store !== 'undefined',
|
||||
'FS scope "FS.Store" not declared');
|
||||
|
||||
test.isTrue(typeof FS.AccessPoint !== 'undefined',
|
||||
'FS scope "FS.AccessPoint" not declared');
|
||||
|
||||
test.isTrue(typeof FS.Utility !== 'undefined',
|
||||
'FS scope "FS.Utility" not declared');
|
||||
|
||||
test.isTrue(typeof FS._collections !== 'undefined',
|
||||
'FS scope "FS._collections" not declared');
|
||||
|
||||
test.isTrue(typeof _Utility !== 'undefined',
|
||||
'_Utility test scope not declared');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - _Utility.defaultZero', function(test) {
|
||||
test.equal(_Utility.defaultZero(), 0, 'Failes to return 0 when (undefined)');
|
||||
test.equal(_Utility.defaultZero(undefined), 0, 'Failes to return 0 when undefined');
|
||||
test.equal(_Utility.defaultZero(null), 0, 'Failes to return 0 when null');
|
||||
test.equal(_Utility.defaultZero(false), 0, 'Failes to return 0 when false');
|
||||
test.equal(_Utility.defaultZero(0), 0, 'Failes to return 0 when 0');
|
||||
test.equal(_Utility.defaultZero(-1), -1, 'Failes to return -1');
|
||||
test.equal(_Utility.defaultZero(1), 1, 'Failes to return 1');
|
||||
test.equal(_Utility.defaultZero(-0.1), -0.1, 'Failes to return -0.1');
|
||||
test.equal(_Utility.defaultZero(0.1), 0.1, 'Failes to return 0.1');
|
||||
test.equal(_Utility.defaultZero(''), 0, 'Failes to return ""');
|
||||
test.equal(_Utility.defaultZero({}), NaN, 'Failes to return NaN when object');
|
||||
test.equal(_Utility.defaultZero("dfdsfs"), NaN, 'Failes to return NaN when string');
|
||||
test.equal(_Utility.defaultZero("1"), 1, 'Failes to return 1 when string "1"');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.cloneFileRecord', function(test) {
|
||||
// Given an object with any props, should filter out 'collectionName',
|
||||
// 'collection', 'data', and 'createdByTransform'
|
||||
var result = FS.Utility.cloneFileRecord({a: 1, b: {c: 1}, d: [1, 2], collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
|
||||
test.equal(result, {a: 1, b: {c: 1}, d: [1, 2]});
|
||||
|
||||
// Given an FS.File instance, should filter out 'collectionName',
|
||||
// 'collection', 'data', and 'createdByTransform' and return a plain Object
|
||||
var fileObj = new FS.File({a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100, collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
|
||||
test.isTrue(fileObj instanceof FS.File);
|
||||
var result = FS.Utility.cloneFileRecord(fileObj);
|
||||
test.isFalse(result instanceof FS.File);
|
||||
test.isTrue(equals(result, {a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100}));
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.defaultCallback', function(test) {
|
||||
// should throw an error passed in, but not a Meteor.Error
|
||||
test.throws(function () {
|
||||
var cb = FS.Utility.defaultCallback;
|
||||
cb(new Error('test'));
|
||||
});
|
||||
|
||||
var cb2 = FS.Utility.defaultCallback;
|
||||
test.isUndefined(cb2(new Meteor.Error('test')));
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.handleError', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.binaryToBuffer', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.bufferToBinary', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.connectionLogin', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.getFileName', function(test) {
|
||||
|
||||
function t(input, expected) {
|
||||
var ext = FS.Utility.getFileName(input);
|
||||
test.equal(ext, expected, 'Got incorrect filename');
|
||||
}
|
||||
|
||||
t('bar.png', 'bar.png');
|
||||
t('foo/bar.png', 'bar.png');
|
||||
t('/foo/foo/bar.png', 'bar.png');
|
||||
t('http://foobar.com/file.png', 'file.png');
|
||||
t('http://foobar.com/file', 'file');
|
||||
t('http://foobar.com/file.png?a=b', 'file.png');
|
||||
t('http://foobar.com/.file?a=b', '.file');
|
||||
t('file', 'file');
|
||||
t('.file', '.file');
|
||||
t('foo/.file', '.file');
|
||||
t('/foo/foo/.file', '.file');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.getFileExtension', function(test) {
|
||||
|
||||
function t(input, expected) {
|
||||
var ext = FS.Utility.getFileExtension(input);
|
||||
test.equal(ext, expected, 'Got incorrect extension');
|
||||
}
|
||||
|
||||
t('bar.png', 'png');
|
||||
t('foo/bar.png', 'png');
|
||||
t('/foo/foo/bar.png', 'png');
|
||||
t('http://foobar.com/file.png', 'png');
|
||||
t('http://foobar.com/file', '');
|
||||
t('http://foobar.com/file.png?a=b', 'png');
|
||||
t('http://foobar.com/file?a=b', '');
|
||||
t('file', '');
|
||||
t('.file', '');
|
||||
t('foo/.file', '');
|
||||
t('/foo/foo/.file', '');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.setFileExtension', function(test) {
|
||||
|
||||
function t(name, ext, expected) {
|
||||
var newName = FS.Utility.setFileExtension(name, ext);
|
||||
test.equal(newName, expected, 'Extension was not set correctly');
|
||||
}
|
||||
|
||||
t('bar.png', 'jpeg', 'bar.jpeg');
|
||||
t('bar', 'jpeg', 'bar.jpeg');
|
||||
t('.bar', 'jpeg', '.bar.jpeg');
|
||||
t('', 'jpeg', '');
|
||||
t(null, 'jpeg', null);
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//Tinytest.add('', function(test) {});
|
||||
//Tinytest.addAsync('', function(test, onComplete) {});
|
||||
//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)
|
5
packages/wekan-cfs-collection-filters/.travis.yml
Normal file
5
packages/wekan-cfs-collection-filters/.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-collection-filters/LICENSE.md
Normal file
20
packages/wekan-cfs-collection-filters/LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 [@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.
|
8
packages/wekan-cfs-collection-filters/README.md
Normal file
8
packages/wekan-cfs-collection-filters/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-collection-filters
|
||||
=========================
|
||||
|
||||
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 added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
44
packages/wekan-cfs-collection-filters/api.md
Normal file
44
packages/wekan-cfs-collection-filters/api.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
## cfs-collection-filters Public API ##
|
||||
|
||||
CollectionFS, adds FS.Collection filters
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.filters"></a>*fsCollection*.filters(filters) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __filters__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filters__ *{Object}*
|
||||
|
||||
File filters for this collection.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allowsFile"></a>*fsCollection*.allowsFile() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allowsFile__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the collection allows this file.
|
||||
|
||||
|
||||
Checks based on any filters defined on the collection. If the
|
||||
file is not valid according to the filters, this method returns false
|
||||
and also calls the filter `onInvalid` method defined for the
|
||||
collection, passing it an English error string that explains why it
|
||||
failed.
|
||||
|
||||
> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108)
|
||||
|
||||
|
191
packages/wekan-cfs-collection-filters/filters.js
Normal file
191
packages/wekan-cfs-collection-filters/filters.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* @method FS.Collection.prototype.filters
|
||||
* @public
|
||||
* @param {Object} filters - File filters for this collection.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.Collection.prototype.filters = function fsColFilters(filters) {
|
||||
var self = this;
|
||||
|
||||
// Check filter option values and normalize them for quicker checking later
|
||||
if (filters) {
|
||||
// check/adjust allow/deny
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type]) {
|
||||
filters[type] = {};
|
||||
} else if (typeof filters[type] !== "object") {
|
||||
throw new Error(type + ' filter must be an object');
|
||||
}
|
||||
});
|
||||
|
||||
// check/adjust maxSize
|
||||
if (typeof filters.maxSize === "undefined") {
|
||||
filters.maxSize = null;
|
||||
} else if (filters.maxSize && typeof filters.maxSize !== "number") {
|
||||
throw new Error('maxSize filter must be an number');
|
||||
}
|
||||
|
||||
// check/adjust extensions
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type].extensions) {
|
||||
filters[type].extensions = [];
|
||||
} else if (!FS.Utility.isArray(filters[type].extensions)) {
|
||||
throw new Error(type + '.extensions filter must be an array of extensions');
|
||||
} else {
|
||||
//convert all to lowercase
|
||||
for (var i = 0, ln = filters[type].extensions.length; i < ln; i++) {
|
||||
filters[type].extensions[i] = filters[type].extensions[i].toLowerCase();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// check/adjust content types
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type].contentTypes) {
|
||||
filters[type].contentTypes = [];
|
||||
} else if (!FS.Utility.isArray(filters[type].contentTypes)) {
|
||||
throw new Error(type + '.contentTypes filter must be an array of content types');
|
||||
}
|
||||
});
|
||||
|
||||
self.options.filter = filters;
|
||||
}
|
||||
|
||||
// Define deny functions to enforce file filters on the server
|
||||
// for inserts and updates that initiate from untrusted code.
|
||||
self.files.deny({
|
||||
insert: function(userId, fsFile) {
|
||||
return !self.allowsFile(fsFile);
|
||||
},
|
||||
update: function(userId, fsFile, fields, modifier) {
|
||||
// TODO will need some kind of additional security here:
|
||||
// Don't allow them to change the type, size, name, and
|
||||
// anything else that would be security or data integrity issue.
|
||||
// Such security should probably be added by cfs-collection package, not here.
|
||||
return !self.allowsFile(fsFile);
|
||||
},
|
||||
fetch: []
|
||||
});
|
||||
|
||||
// If insecure package is in use, we need to add allow rules that return
|
||||
// true. Otherwise, it would seemingly turn off insecure mode.
|
||||
if (Package && Package.insecure) {
|
||||
self.allow({
|
||||
insert: function() {
|
||||
return true;
|
||||
},
|
||||
update: function() {
|
||||
return true;
|
||||
},
|
||||
remove: function() {
|
||||
return true;
|
||||
},
|
||||
download: function() {
|
||||
return true;
|
||||
},
|
||||
fetch: [],
|
||||
transform: null
|
||||
});
|
||||
}
|
||||
// If insecure package is NOT in use, then adding the deny function
|
||||
// does not have any effect on the main app's security paradigm. The
|
||||
// user will still be required to add at least one allow function of her
|
||||
// own for each operation for this collection. And the user may still add
|
||||
// additional deny functions, but does not have to.
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Collection.prototype.allowsFile Does the collection allow the specified file?
|
||||
* @public
|
||||
* @returns {boolean} True if the collection allows this file.
|
||||
*
|
||||
* Checks based on any filters defined on the collection. If the
|
||||
* file is not valid according to the filters, this method returns false
|
||||
* and also calls the filter `onInvalid` method defined for the
|
||||
* collection, passing it an English error string that explains why it
|
||||
* failed.
|
||||
*/
|
||||
FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
|
||||
var self = this;
|
||||
|
||||
// Get filters
|
||||
var filter = self.options.filter;
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
var saveAllFileExtensions = (filter.allow.extensions.length === 0);
|
||||
var saveAllContentTypes = (filter.allow.contentTypes.length === 0);
|
||||
|
||||
// Get info about the file
|
||||
var filename = fileObj.name();
|
||||
var contentType = fileObj.type();
|
||||
if (!saveAllContentTypes && !contentType) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " has an unknown content type");
|
||||
return false;
|
||||
}
|
||||
var fileSize = fileObj.size();
|
||||
if (!fileSize || isNaN(fileSize)) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " has an unknown file size");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do extension checks only if we have a filename
|
||||
if (filename) {
|
||||
var ext = fileObj.getExtension();
|
||||
if (!((saveAllFileExtensions ||
|
||||
FS.Utility.indexOf(filter.allow.extensions, ext) !== -1) &&
|
||||
FS.Utility.indexOf(filter.deny.extensions, ext) === -1)) {
|
||||
filter.onInvalid && filter.onInvalid(filename + ' has the extension "' + ext + '", which is not allowed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Do content type checks
|
||||
if (!((saveAllContentTypes ||
|
||||
contentTypeInList(filter.allow.contentTypes, contentType)) &&
|
||||
!contentTypeInList(filter.deny.contentTypes, contentType))) {
|
||||
filter.onInvalid && filter.onInvalid(filename + ' is of the type "' + contentType + '", which is not allowed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do max size check
|
||||
if (typeof filter.maxSize === "number" && fileSize > filter.maxSize) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " is too big");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method contentTypeInList Is the content type string in the list?
|
||||
* @private
|
||||
* @param {String[]} list - Array of content types
|
||||
* @param {String} contentType - The content type
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* Returns true if the content type is in the list, or if it matches
|
||||
* one of the special types in the list, e.g., "image/*".
|
||||
*/
|
||||
function contentTypeInList(list, contentType) {
|
||||
var listType, found = false;
|
||||
for (var i = 0, ln = list.length; i < ln; i++) {
|
||||
listType = list[i];
|
||||
if (listType === contentType) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "image/*" && contentType.indexOf("image/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "audio/*" && contentType.indexOf("audio/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "video/*" && contentType.indexOf("video/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
72
packages/wekan-cfs-collection-filters/internal.api.md
Normal file
72
packages/wekan-cfs-collection-filters/internal.api.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["filters.js"](filters.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Collection.prototype.filters"></a>*fsCollection*.filters(filters) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __filters__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filters__ *{Object}*
|
||||
|
||||
File filters for this collection.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allowsFile"></a>*fsCollection*.allowsFile() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allowsFile__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the collection allows this file.
|
||||
|
||||
|
||||
Checks based on any filters defined on the collection. If the
|
||||
file is not valid according to the filters, this method returns false
|
||||
and also calls the filter `onInvalid` method defined for the
|
||||
collection, passing it an English error string that explains why it
|
||||
failed.
|
||||
|
||||
> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="contentTypeInList"></a>contentTypeInList(list, contentType) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __list__ *{[String[]](#String[])}*
|
||||
|
||||
Array of content types
|
||||
|
||||
* __contentType__ *{String}*
|
||||
|
||||
The content type
|
||||
|
||||
|
||||
__Returns__ *{Boolean}*
|
||||
|
||||
|
||||
Returns true if the content type is in the list, or if it matches
|
||||
one of the special types in the list, e.g., "image/*".
|
||||
|
||||
> ```function contentTypeInList(list, contentType) { ...``` [filters.js:169](filters.js#L169)
|
||||
|
||||
|
29
packages/wekan-cfs-collection-filters/package.js
Normal file
29
packages/wekan-cfs-collection-filters/package.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-collection-filters.git',
|
||||
name: 'wekan-cfs-collection-filters',
|
||||
version: '0.2.4',
|
||||
summary: 'CollectionFS, adds FS.Collection filters'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-collection@0.5.4']);
|
||||
|
||||
api.addFiles([
|
||||
'filters.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'filters.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
// Package.on_test(function (api) {
|
||||
// api.use('collectionfs');
|
||||
// api.use('test-helpers', 'server');
|
||||
// api.use(['tinytest']);
|
||||
|
||||
// api.addFiles('tests/server-tests.js', 'server');
|
||||
// api.addFiles('tests/client-tests.js', 'client');
|
||||
// });
|
27
packages/wekan-cfs-collection-filters/tests/client-tests.js
Normal file
27
packages/wekan-cfs-collection-filters/tests/client-tests.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection-filters - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
//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)
|
27
packages/wekan-cfs-collection-filters/tests/server-tests.js
Normal file
27
packages/wekan-cfs-collection-filters/tests/server-tests.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection-filters - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
//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)
|
5
packages/wekan-cfs-collection/.travis.yml
Normal file
5
packages/wekan-cfs-collection/.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"
|
727
packages/wekan-cfs-collection/CHANGELOG.md
Normal file
727
packages/wekan-cfs-collection/CHANGELOG.md
Normal file
|
@ -0,0 +1,727 @@
|
|||
# Changelog
|
||||
|
||||
## vCurrent
|
||||
## [v0.5.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.3)
|
||||
#### 20/12/14 by Morten Henriksen
|
||||
- add changelog
|
||||
|
||||
- Bump to version 0.5.3
|
||||
|
||||
## [v0.5.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.2)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- Bump to version 0.5.2
|
||||
|
||||
## [v0.5.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.1)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Bump to version 0.5.1
|
||||
|
||||
## [v0.5.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.0)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- mbr update versions and fix warnings
|
||||
|
||||
- update pkg, dependencies, etc.
|
||||
|
||||
- Fix #483
|
||||
|
||||
- *Merged pull-request:* "mrt graphicsmagick instructions to meteor 0.9+" [#442](https://github.com/zcfs/Meteor-CollectionFS/issues/442) ([yogiben](https://github.com/yogiben))
|
||||
|
||||
- mrt graphicsmagick instructions to meteor 0.9+
|
||||
|
||||
- mention underlying collection
|
||||
|
||||
- *Merged pull-request:* "Added server-side Buffer example to README.md" [#405](https://github.com/zcfs/Meteor-CollectionFS/issues/405) ([abuddenb](https://github.com/abuddenb))
|
||||
|
||||
- change prop names
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- add "Display an Uploaded Image" example
|
||||
|
||||
- addition to steps
|
||||
|
||||
- correct 0.9.0 steps
|
||||
|
||||
- Merge branch 'devel' of github.com:abuddenb/Meteor-CollectionFS into devel
|
||||
|
||||
- Added server-side Buffer example to README.md
|
||||
|
||||
Patches by GitHub users [@yogiben](https://github.com/yogiben), [@abuddenb](https://github.com/abuddenb).
|
||||
|
||||
## [v0.4.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.9)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
- add 0.9.0 instructions
|
||||
|
||||
- change package name to lowercase
|
||||
|
||||
- Added server-side Buffer example to README.md
|
||||
|
||||
- *Merged pull-request:* "Updated README.md to reflect that FS.File - Objects can't be stored in t..." [#395](https://github.com/zcfs/Meteor-CollectionFS/issues/395) ([DanielDornhardt](https://github.com/DanielDornhardt))
|
||||
|
||||
- Updated README.md to reflect that FS.File - Objects can't be stored in the Server MongoDB at the moment.
|
||||
|
||||
- document key
|
||||
|
||||
- update the descriptions of collections
|
||||
|
||||
- document beforeWrite
|
||||
|
||||
- add download button example
|
||||
|
||||
- *Merged pull-request:* "Update README.md" [#354](https://github.com/zcfs/Meteor-CollectionFS/issues/354) ([karabijavad](https://github.com/karabijavad))
|
||||
|
||||
- should say to use cfs-graphicsmagick pkg
|
||||
|
||||
- fix typo
|
||||
|
||||
- *Merged pull-request:* "Added "Storing FS.File references in your objects" chapter to README" [#311](https://github.com/zcfs/Meteor-CollectionFS/issues/311) ([Sanjo](https://github.com/Sanjo))
|
||||
|
||||
- Added "Storing FS.File references in your objects" chapter to README
|
||||
|
||||
- a few more API updates
|
||||
|
||||
- updates for changed FS.File API
|
||||
|
||||
- additions and clarifications
|
||||
|
||||
- *Merged pull-request:* "Custom Metadata misleading example" [#282](https://github.com/zcfs/Meteor-CollectionFS/issues/282) ([czeslaaw](https://github.com/czeslaaw))
|
||||
|
||||
- Custom Metadata misleading example
|
||||
|
||||
Patches by GitHub users [@DanielDornhardt](https://github.com/DanielDornhardt), [@karabijavad](https://github.com/karabijavad), [@Sanjo](https://github.com/Sanjo), [@czeslaaw](https://github.com/czeslaaw).
|
||||
|
||||
## [v0.4.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.8)
|
||||
#### 09/04/14 by Eric Dobbertin
|
||||
- Add cfs-collection-filters pkg by default
|
||||
|
||||
- Use FS.Utility.setFileExtension in example
|
||||
|
||||
## [v0.4.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.7)
|
||||
#### 05/04/14 by Morten Henriksen
|
||||
- corrections, and move image examples here so we can deprecate cfs-imagemagick
|
||||
|
||||
## [v0.4.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.6)
|
||||
#### 02/04/14 by Morten Henriksen
|
||||
- *Fixed bug:* "Documentation Issues" [#241](https://github.com/zcfs/Meteor-CollectionFS/issues/241)
|
||||
|
||||
- point to cfs-graphicsmagick readme for transform examples
|
||||
|
||||
## [v0.4.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.5)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
- Add yet a note about mrt and collectionFS in the readme
|
||||
|
||||
## [v0.4.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.4)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- attempt to get everything up to date
|
||||
|
||||
- Add notice about mrt having troubles figuring out deps
|
||||
|
||||
- *Merged pull-request:* "Removed redundant installation instructions." [#212](https://github.com/zcfs/Meteor-CollectionFS/issues/212) ([lleonard188](https://github.com/lleonard188))
|
||||
|
||||
- up to date
|
||||
|
||||
- read me update
|
||||
|
||||
- add test note
|
||||
|
||||
Patches by GitHub user [@lleonard188](https://github.com/lleonard188).
|
||||
|
||||
## [v0.4.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.3)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- use collectionFS travis version force update
|
||||
|
||||
## [v0.4.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.2)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- change deps
|
||||
|
||||
- Add read me about transformWrite
|
||||
|
||||
## [v0.4.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.1)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- update reference to devel branch
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- don't need to install transfer
|
||||
|
||||
- mark deprecated api docs
|
||||
|
||||
- test strikeout md
|
||||
|
||||
- no need to imply cfs-transfer since it doesn't do anything at the moment
|
||||
|
||||
- Remove ejson-file imply
|
||||
|
||||
## [v0.4.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.0)
|
||||
#### 06/03/14 by Eric Dobbertin
|
||||
- add section for documenting example code for common tasks, and add some of the examples; @raix feel free to add more
|
||||
|
||||
- add http upload package to smart.json
|
||||
|
||||
- Merge origin/devel into devel
|
||||
|
||||
- need to imply the http upload package because it's the default now
|
||||
|
||||
- update HTTP access point override docs
|
||||
|
||||
- update docs to use `devel` branch
|
||||
|
||||
- list component packages in smart.json
|
||||
|
||||
- Merge origin/devel into devel-merge
|
||||
|
||||
- add the ejson-file and correct doc
|
||||
|
||||
- refactor everything into packages
|
||||
|
||||
## [devel-merge-old] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-merge-old)
|
||||
#### 12/02/14 by Eric Dobbertin
|
||||
- update to latest FileSaver.js
|
||||
|
||||
- one of many refactores
|
||||
|
||||
- add optimization section
|
||||
|
||||
- add section explaining client vs server insert
|
||||
|
||||
- prevent autopublish for the SA collections
|
||||
|
||||
- remove arg that isn't used or passed
|
||||
|
||||
- changes to support client SA, plus clean/reorg SA code to have less duplication
|
||||
|
||||
- merge the concepts of "store" and "copy", update docs, switch to FS.Store namespace
|
||||
|
||||
- use wait:true to avoid Meteor issue
|
||||
|
||||
- extend allow when insecure package is installed
|
||||
|
||||
- document and improve code flow a bit
|
||||
|
||||
- faster to use onResultReceived callback
|
||||
|
||||
- use onload instead of onloadend because we want only successful loads
|
||||
|
||||
- remove some console logging
|
||||
|
||||
- fix issue where stuff wasn't uploading because we weren't calling getFileRecord() in the access point methods
|
||||
|
||||
- make allowed file extension checks not be case sensitive
|
||||
|
||||
- remove DDP "/del" access point since it's not used or necessary
|
||||
|
||||
- Remove collection-hooks dependency; use deny instead. Also some cleanup and code docs
|
||||
|
||||
- replace `mmmagic` dependency with a `mime` dependency; hopefully fixes issues we've seen with meteor deploy
|
||||
|
||||
- refactor to expose saveCopy to API; then change fileworker observes to be per-copy, calling saveCopy and allowing better control of which copies to create at which times
|
||||
|
||||
- use observes for all store saving and temp store deleting; add/adjust some api doc comments (didn't regenerate yet)
|
||||
|
||||
- improve fileIsAllowed check order and messages
|
||||
|
||||
- Add check for the Accounts package
|
||||
|
||||
- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120)
|
||||
|
||||
- always append access token to url when a user is logged in; makes usage simpler to do opt-out rather than opt-in
|
||||
|
||||
- Don't queue ddp method calls
|
||||
|
||||
- Merge changes
|
||||
|
||||
- Optimize buffer handling
|
||||
|
||||
- Switch to MicroQueue
|
||||
|
||||
- improve memory management, attempt client side resume implementation, other minor fixes
|
||||
|
||||
- fix several issues, make downloading work, improve a few bits of code
|
||||
|
||||
- do the isImage test correctly; add some API docs
|
||||
|
||||
- isEmpty will be true for null or undefined
|
||||
|
||||
- rewrite getExtension so it works for unmounted files, too
|
||||
|
||||
- add strong reactive-list dependency for powerqueue
|
||||
|
||||
- minor fixes and code improvements; track uploading files by both collection and ID since one queue handles all collections
|
||||
|
||||
- Base the upload queue on PowerQueue sub queues
|
||||
|
||||
- Add sa note
|
||||
|
||||
- limit use of db
|
||||
|
||||
- Refactor and documentation
|
||||
|
||||
- use mmmagic 0.3.5
|
||||
|
||||
- *Fixed bug:* "Option to set Cache-Control and Max-Age" [#117](https://github.com/zcfs/Meteor-CollectionFS/issues/117)
|
||||
|
||||
- fix stuff that's broken
|
||||
|
||||
## [v0.3.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.7)
|
||||
#### 08/01/14 by Morten Henriksen
|
||||
- *Fixed bug:* "How to store files on the server side?" [#29](https://github.com/zcfs/Meteor-CollectionFS/issues/29)
|
||||
|
||||
- rework ejson and remove fsFile.reload
|
||||
|
||||
- explain complete upload process in ADVANCED docs
|
||||
|
||||
- Adds join to smart.json (its a weak dependency #119)
|
||||
|
||||
- link to api docs for acceptDropsOn
|
||||
|
||||
- typo/formatting fixes
|
||||
|
||||
- docs + insert callback returns `FS.File` instead of `id`
|
||||
|
||||
- Add docs and `FS.File.fetch`
|
||||
|
||||
- Add support for `Join`
|
||||
|
||||
- end with line
|
||||
|
||||
- smaller headlines in docs
|
||||
|
||||
- init api docs
|
||||
|
||||
- Refactor and documentation
|
||||
|
||||
- FS.Collection.insert should return the `FS.File` object
|
||||
|
||||
- remove some comments and such
|
||||
|
||||
- document custom connections
|
||||
|
||||
- add livedata ref to access DDP obj
|
||||
|
||||
- use separate ddp connection with option to pass in custom
|
||||
|
||||
- public folder and gm/im
|
||||
|
||||
- fix null options
|
||||
|
||||
- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120)
|
||||
|
||||
- make useHTTP true by default
|
||||
|
||||
- skip auth checks if Package.insecure
|
||||
|
||||
- remove `callback` arg from fsFile.get since it's not used or necessary; fix partial gets such that they actually use getBytes, greatly speeding up downloads of large files
|
||||
|
||||
- reorg code and speed up downloads
|
||||
|
||||
- split TransferQueue into DownloadTransferQueue and UploadTransferQueue
|
||||
|
||||
- improvements to make use of new PowerQueue features
|
||||
|
||||
- fix issue with previous commit
|
||||
|
||||
- add accessPoints option
|
||||
|
||||
- Pull out temporary chunk code into a separate tempStore.js file, within a TempStore object. This makes it easier to maintain. Also updated the file worker code to correctly find temporary chunks that can be removed and delete those files.
|
||||
|
||||
- add security section
|
||||
|
||||
- Internally, change all "master" stuff to be the same as "copies". External API is still the same, but master options are copied to a special copy named "_master" so that all the other code can be cleaner. This may be a step toward being able to blur or eliminate the master/copy distinction, although there are still some benefits to having a master.
|
||||
|
||||
- Add instructions for installing for testing
|
||||
|
||||
- refactor code to be a bit cleaner
|
||||
|
||||
- add functions for getting an FS.File or setting FS.File data from a URL on the server
|
||||
|
||||
- clean up and improve some transfer code
|
||||
|
||||
- update console log message to be more correct
|
||||
|
||||
- ensure that fsFile.bytesUploaded is always set correctly
|
||||
|
||||
- fix some issues with recent commits
|
||||
|
||||
- *Merged pull-request:* "Updates the filter example area so that it works" [#109](https://github.com/zcfs/Meteor-CollectionFS/issues/109) ([cramhead](https://github.com/cramhead))
|
||||
|
||||
- add .npm to gitignore
|
||||
|
||||
- Remove .npm folder
|
||||
|
||||
- Clean clone just a bit
|
||||
|
||||
- Refactor fsCollection and argParser
|
||||
|
||||
- Comment on filter options
|
||||
|
||||
- Add download url
|
||||
|
||||
- refactor access point
|
||||
|
||||
- add put/get/del security based on allow/deny functions
|
||||
|
||||
- Updates the filter example area so that it works
|
||||
|
||||
- update for API change
|
||||
|
||||
- more client-side speed improvements and allow passing File/Blob to FS.File constructor again
|
||||
|
||||
- fix data mixup
|
||||
|
||||
- fix upload slowness and blocking
|
||||
|
||||
- change API to adjust issue with data loading callbacks
|
||||
|
||||
- revise FS.File API where data handling is concerned; fix some issues with callback handling in client-side methods; upshot should be faster, smoother uploads and downloads
|
||||
|
||||
- change names and put everything in exported FS namespace
|
||||
|
||||
- fix get/download of copies
|
||||
|
||||
- *Fixed bug:* "Files after certain size aren't saved properly. " [#104](https://github.com/zcfs/Meteor-CollectionFS/issues/104)
|
||||
|
||||
- add fileobject metadata and acceptDropsOn
|
||||
|
||||
- add some methods to load FO data from URL
|
||||
|
||||
- Correct allow/deny examples
|
||||
|
||||
- call put callback correctly
|
||||
|
||||
- add correct temporary installation instructions
|
||||
|
||||
- use correct filename when saving download
|
||||
|
||||
- filtering fixes
|
||||
|
||||
- removed some unused stuff
|
||||
|
||||
- adjust some comments
|
||||
|
||||
- switch api.remove to api.del for consistency with the other methods
|
||||
|
||||
- add hasCopy method
|
||||
|
||||
- new api; tons of changes
|
||||
|
||||
- use generic queue for server file handling
|
||||
|
||||
- update progress in the correct place
|
||||
|
||||
- minor changes to comments
|
||||
|
||||
- fix gridfs get method
|
||||
|
||||
- incorporate #82
|
||||
|
||||
- make filtering work (added collection-hooks dependency for core package)
|
||||
|
||||
- change UploadsCollection to CollectionFS; change former CollectionFS to GridFS and don't export it (used only by the gridfs storage adaptor); clean up some other areas and update readmes
|
||||
|
||||
- *Fixed bug:* "retrieveBlob failure" [#93](https://github.com/zcfs/Meteor-CollectionFS/issues/93)
|
||||
|
||||
- implement http methods URLs
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS.git into devel
|
||||
|
||||
- significant revisions to move downloading support to the UploadsCollection and make collectionFS/gridFS a pure storage adaptor
|
||||
|
||||
- Merge branch 'pr/94' into devel
|
||||
|
||||
- split and revise readmes
|
||||
|
||||
- rename packages and organize package.js
|
||||
|
||||
- error handling improvements
|
||||
|
||||
- better failure handling for removeCopy
|
||||
|
||||
- handlebars helper to display blob image in CFS package
|
||||
|
||||
- remove all encoding info
|
||||
|
||||
- refactor to fix multiple-file simultaneous uploads
|
||||
|
||||
- don't use _id in filesystem destination since it's not set anyway
|
||||
|
||||
- remove FileObject.file and instead save file as Blob (.blob) when FileObject.fromFile is called
|
||||
|
||||
- revise API a bit
|
||||
|
||||
- stop using strings and encoding and pass everything as Uint8Array (fixes downloading corruption)
|
||||
|
||||
- only attempt to delete file if it exists
|
||||
|
||||
- remove unused file
|
||||
|
||||
- complete refactoring; temporary for testing/tweaking and then will split into multiple packages
|
||||
|
||||
- *Merged pull-request:* "Implemented max parallel transfers" [#62](https://github.com/zcfs/Meteor-CollectionFS/issues/62) ([floo51](https://github.com/floo51))
|
||||
|
||||
Patches by GitHub users [@cramhead](https://github.com/cramhead), [@floo51](https://github.com/floo51).
|
||||
|
||||
## [v0.3.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.6)
|
||||
#### 10/10/13 by Morten Henriksen
|
||||
- Edit ideas about storage adapters and filehandlers
|
||||
|
||||
- Add MIT License
|
||||
|
||||
- Add paypal and weak deps
|
||||
|
||||
- Added the org. filemanager demo/example
|
||||
|
||||
## [v0.3.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.5)
|
||||
#### 20/09/13 by Morten Henriksen
|
||||
## [v0.3.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.4)
|
||||
#### 20/09/13 by Morten Henriksen
|
||||
- Extract the examples from collectionFS
|
||||
|
||||
- Added examples from @mxab
|
||||
|
||||
## [v0.3.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.3)
|
||||
#### 18/09/13 by Morten Henriksen
|
||||
- added travis badge
|
||||
|
||||
- Added travis badge
|
||||
|
||||
- Added basic environment
|
||||
|
||||
- Added metadata getter to docs
|
||||
|
||||
## [v0.3.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.2)
|
||||
#### 17/09/13 by Morten Henriksen
|
||||
## [v0.3.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.1)
|
||||
#### 16/09/13 by Morten Henriksen
|
||||
- Added some details about http.publishing of collections
|
||||
|
||||
- added check in filehandlers
|
||||
|
||||
- Updated som docs
|
||||
|
||||
- js hint added scope
|
||||
|
||||
- jshint clean up
|
||||
|
||||
- *Fixed bug:* "Exception from setTimeout callback: { stack: [Getter] }" [#64](https://github.com/zcfs/Meteor-CollectionFS/issues/64)
|
||||
|
||||
- Merge branch 'devel'
|
||||
|
||||
- Bump version to preview of 0.3.0
|
||||
|
||||
- Implemented max parallel transfers
|
||||
|
||||
- Revert "began refractoring"
|
||||
|
||||
- Revert "Run jshint through files"
|
||||
|
||||
- Revert "Preparing file object methods for better api"
|
||||
|
||||
- Revert "added fileobject files to package"
|
||||
|
||||
- added fileobject files to package
|
||||
|
||||
- Preparing file object methods for better api
|
||||
|
||||
- Run jshint through files
|
||||
|
||||
- *Merged pull-request:* "Improvements and Fixes to built-in helpers, storeFiles, and acceptDropsOn" [#51](https://github.com/zcfs/Meteor-CollectionFS/issues/51) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Add generic events system, switch to "enums" for invalid event types, and change "fileFilter" to "filter" throughout. Update README to reflect these changes.
|
||||
|
||||
- Update readme to reflect storeFiles and acceptDropsOn changes, plus add documentation for all of the new built-in handlebars helpers
|
||||
|
||||
- commit some files that should be committed
|
||||
|
||||
- -Improve and fix built-in handlebars helpers -Prefix all built-in helpers with "cfs" -Update new example app to reflect helper changes, and improve and fix it a bit, too
|
||||
|
||||
- Merge remote-tracking branch 'upstream/master'
|
||||
|
||||
- update documentation of storeFiles and acceptDropsOn
|
||||
|
||||
- Added credit to @eprochasson
|
||||
|
||||
- *Merged pull-request:* "Should fix issue #45" [#50](https://github.com/zcfs/Meteor-CollectionFS/issues/50) ([eprochasson](https://github.com/eprochasson))
|
||||
|
||||
- *Fixed bug:* "file handlers not showing up in local demo" [#45](https://github.com/zcfs/Meteor-CollectionFS/issues/45)
|
||||
|
||||
- *Merged pull-request:* "Update README.md" [#49](https://github.com/zcfs/Meteor-CollectionFS/issues/49) ([eprochasson](https://github.com/eprochasson))
|
||||
|
||||
- Added meteor style guide for jshint
|
||||
|
||||
- fix package file
|
||||
|
||||
- fixes and improvements to storeFiles() and acceptDropsOn()
|
||||
|
||||
- Use a different saveAs shim
|
||||
|
||||
- Merge branch 'master' of https://github.com/aldeed/Meteor-CollectionFS
|
||||
|
||||
- Fixes and changes to support cfs changes
|
||||
|
||||
- -"cfs" prefix, new helpers, improvements and fixes -include saveAs shim in the package so that download button helper can reliably call it
|
||||
|
||||
- Add dependencies and files
|
||||
|
||||
- Document storeFiles() and acceptDropsOn()
|
||||
|
||||
- *Merged pull-request:* "Built-ins, fixes, etc." [#48](https://github.com/zcfs/Meteor-CollectionFS/issues/48) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Add the new files to the package manifest
|
||||
|
||||
- Add underscore as dependency. It seems that Meteor may soon remove underscore from the core.
|
||||
|
||||
- Minor changes to support fileHanders() and fileFilter() function changes
|
||||
|
||||
- Copy in numeral.js for use by the built-in handlebar helper that displays file size in human readable format. This means the helper supports any of the format strings supported by numeral.js for file sizes.
|
||||
|
||||
- -Add fileFilter() function to specify allowed and disallowed files per collectionFS, based on extensions and/or content types -Add fileIsAllowed() function to easily check whether a particular file is allowed based on the rules set up by fileFilter() -Change all of the passthrough functions (find, findOne, update, remove, allow, deny) to pass through all function arguments more simply and more safely. This allows, for example, using find() instead of find({}). -Change fileHandlers() to extend the object whenever called, which means you can safely call it more than once -Add several utility functions for use in either client or server code
|
||||
|
||||
- -Add storeFiles API -Check that files are allowed by fileFilter before saving -Add acceptDropsOn API -Use .depend() instead of Deps throughout -Pull out _getProgress calc to use in two places -Add isUploading API -Improve isDownloading code
|
||||
|
||||
- New file to hold built-in handlebars helpers, including several initial helpers
|
||||
|
||||
- New file manager example app showing how to use new features, built-in handlebars helpers, etc.
|
||||
|
||||
- *Merged pull-request:* "Minor doc correction" [#47](https://github.com/zcfs/Meteor-CollectionFS/issues/47) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- *Merged pull-request:* "Documentation Improvement" [#46](https://github.com/zcfs/Meteor-CollectionFS/issues/46) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Extensively revised README
|
||||
|
||||
Patches by GitHub users [@aldeed](https://github.com/aldeed), [@eprochasson](https://github.com/eprochasson).
|
||||
|
||||
## [v0.2.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.3)
|
||||
#### 04/05/13 by Morten Henriksen
|
||||
- *Fixed bug:* "Binary File Transfers Corrupted? Truncated?" [#41](https://github.com/zcfs/Meteor-CollectionFS/issues/41)
|
||||
|
||||
## [v0.2.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.2)
|
||||
#### 03/05/13 by Morten Henriksen
|
||||
- updated scope for serverConsole
|
||||
|
||||
- *Fixed bug:* "Error when using fresh meteor install" [#40](https://github.com/zcfs/Meteor-CollectionFS/issues/40)
|
||||
|
||||
- more text edits
|
||||
|
||||
- Minor text edits
|
||||
|
||||
- Add credit to README
|
||||
|
||||
- *Merged pull-request:* "Option for file encoding" [#36](https://github.com/zcfs/Meteor-CollectionFS/issues/36) ([nhibner](https://github.com/nhibner))
|
||||
|
||||
- Typo fix.
|
||||
|
||||
- Updated documentation (added encoding to the fileRecord structure).
|
||||
|
||||
- File encoding is stored in the fileRecord.
|
||||
|
||||
- Allow the user to specify an encoding for the buffer when storing on the server.
|
||||
|
||||
- *Merged pull-request:* "Fix backwards incompatibility" [#33](https://github.com/zcfs/Meteor-CollectionFS/issues/33) ([mitar](https://github.com/mitar))
|
||||
|
||||
- Fix backwards incompatibility.
|
||||
|
||||
Patches by GitHub users [@nhibner](https://github.com/nhibner), [@mitar](https://github.com/mitar).
|
||||
|
||||
## [v0.2.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.1)
|
||||
#### 08/04/13 by Morten Henriksen
|
||||
- Minor fixes, added dragndrop, minor refractoring
|
||||
|
||||
- Only work on completed files
|
||||
|
||||
## [v0.2.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.0)
|
||||
#### 06/04/13 by Morten Henriksen
|
||||
- Bump to 0.2.0 - Nice
|
||||
|
||||
- Big speed and refractoring
|
||||
|
||||
## [v0.1.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.9)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added some more doc, deprecated autosubscribe to autopublish instead
|
||||
|
||||
## [v0.1.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.8)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added maxFilehandlers to the doc
|
||||
|
||||
## [devel-#27-fixed] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-#27-fixed)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added documentation by @petrocket
|
||||
|
||||
- Got filehandlers up and running in bundles
|
||||
|
||||
- Make a seperate thread + connection foreach collectionFS
|
||||
|
||||
## [v0.1.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.7)
|
||||
#### 14/03/13 by Morten Henriksen
|
||||
## [v0.1.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.6)
|
||||
#### 13/01/13 by Morten Henriksen
|
||||
- Converted .length to string to cope with Meteor use of underscore
|
||||
|
||||
## [devel-server-cache] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-server-cache)
|
||||
#### 11/01/13 by Morten Henriksen
|
||||
## [v0.1.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.5)
|
||||
#### 11/01/13 by Morten Henriksen
|
||||
- On going tests - db updates gone, requires refresh to commit changes to db - guess some que not working
|
||||
|
||||
- removed setTimeout when spawn == 1
|
||||
|
||||
## [v0.1.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.4)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.3)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.2)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.1)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
- Big one, added fileHandler to create cashed versions of the file
|
||||
|
||||
- added made with Meteor in fileHandler example
|
||||
|
||||
- corrected param bug in find and findOne
|
||||
|
||||
- minor fix and update
|
||||
|
||||
- Corrected install guide to use Meteorite
|
||||
|
||||
## [v0.1.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.0)
|
||||
#### 07/01/13 by Morten Henriksen
|
||||
## [v0.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1)
|
||||
#### 07/01/13 by Morten Henriksen
|
||||
- updated file strukture and example
|
||||
|
||||
- Restructure to package system
|
||||
|
||||
- Refractoring filenames and edit package.js
|
||||
|
||||
- Package.js added
|
||||
|
||||
- Added smart.json for Atmosphere packages - Not tested!
|
||||
|
||||
- Readme styling corrected
|
||||
|
||||
- Added comments to the upload, storeFile
|
||||
|
||||
- StoreFile should return fileId or null
|
||||
|
||||
- Added notes about security
|
||||
|
||||
- Added only owner can resume, makes sense for now
|
||||
|
||||
- Added how to make a download...
|
||||
|
||||
- Changed project title git
|
||||
|
||||
- And some more corrections
|
||||
|
||||
- More readme text
|
||||
|
||||
- Added some short reference
|
||||
|
||||
- some more md
|
||||
|
||||
- Initial commit
|
||||
|
20
packages/wekan-cfs-collection/LICENSE.md
Normal file
20
packages/wekan-cfs-collection/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.
|
8
packages/wekan-cfs-collection/README.md
Normal file
8
packages/wekan-cfs-collection/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-collection
|
||||
=========================
|
||||
|
||||
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 added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
260
packages/wekan-cfs-collection/api.common.js
Normal file
260
packages/wekan-cfs-collection/api.common.js
Normal file
|
@ -0,0 +1,260 @@
|
|||
/** @method FS.Collection.prototype.insert Insert `File` or `FS.File` or remote URL into collection
|
||||
* @public
|
||||
* @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} fileRef File, FS.File, or other data to insert
|
||||
* @param {function} [callback] Callback `function(error, fileObj)`
|
||||
* @returns {FS.File|undefined} The `file object`
|
||||
* [Meteor docs](http://docs.meteor.com/#insert)
|
||||
*/
|
||||
FS.Collection.prototype.insert = function(fileRef, callback) {
|
||||
var self = this;
|
||||
|
||||
if (Meteor.isClient && !callback) {
|
||||
callback = FS.Utility.defaultCallback;
|
||||
}
|
||||
|
||||
// XXX:
|
||||
// We should out factor beginStorage to FS.File.beginStorage
|
||||
// the client side storage adapters should be the one providing
|
||||
// the upload either via http/ddp or direct upload
|
||||
// Could be cool to have a streaming api on the client side
|
||||
// having a createReadStream etc. on the client too...
|
||||
function beginStorage(fileObj) {
|
||||
|
||||
// If on client, begin uploading the data
|
||||
if (Meteor.isClient) {
|
||||
self.options.uploader && self.options.uploader(fileObj);
|
||||
}
|
||||
|
||||
// If on the server, save the binary to a single chunk temp file,
|
||||
// so that it is available when FileWorker calls saveCopies.
|
||||
// This will also trigger file handling from collection observes.
|
||||
else if (Meteor.isServer) {
|
||||
fileObj.createReadStream().pipe(FS.TempStore.createWriteStream(fileObj));
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: would be great if this function could be simplyfied - if even possible?
|
||||
function checkAndInsert(fileObj) {
|
||||
// Check filters. This is called in deny functions, too, but we call here to catch
|
||||
// server inserts and to catch client inserts early, allowing us to call `onInvalid` on
|
||||
// the client and save a trip to the server.
|
||||
if (!self.allowsFile(fileObj)) {
|
||||
return FS.Utility.handleError(callback, 'FS.Collection insert: file does not pass collection filters');
|
||||
}
|
||||
|
||||
// Set collection name
|
||||
fileObj.collectionName = self.name;
|
||||
|
||||
// Insert the file into db
|
||||
// We call cloneFileRecord as an easy way of extracting the properties
|
||||
// that need saving.
|
||||
if (callback) {
|
||||
fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj), function(err, id) {
|
||||
if (err) {
|
||||
if (fileObj._id) {
|
||||
delete fileObj._id;
|
||||
}
|
||||
} else {
|
||||
// Set _id, just to be safe, since this could be before or after the insert method returns
|
||||
fileObj._id = id;
|
||||
// Pass to uploader or stream data to the temp store
|
||||
beginStorage(fileObj);
|
||||
}
|
||||
callback(err, err ? void 0 : fileObj);
|
||||
});
|
||||
} else {
|
||||
fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj));
|
||||
// Pass to uploader or stream data to the temp store
|
||||
beginStorage(fileObj);
|
||||
}
|
||||
return fileObj;
|
||||
}
|
||||
|
||||
// Parse, adjust fileRef
|
||||
if (fileRef instanceof FS.File) {
|
||||
return checkAndInsert(fileRef);
|
||||
} else {
|
||||
// For convenience, allow File, Blob, Buffer, data URI, filepath, URL, etc. to be passed as first arg,
|
||||
// and we will attach that to a new fileobj for them
|
||||
var fileObj = new FS.File(fileRef);
|
||||
if (callback) {
|
||||
fileObj.attachData(fileRef, function attachDataCallback(error) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
checkAndInsert(fileObj);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// We ensure there's a callback on the client, so if there isn't one at this point,
|
||||
// we must be on the server expecting synchronous behavior.
|
||||
fileObj.attachData(fileRef);
|
||||
checkAndInsert(fileObj);
|
||||
}
|
||||
return fileObj;
|
||||
}
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.update Update the file record
|
||||
* @public
|
||||
* @param {FS.File|object} selector
|
||||
* @param {object} modifier
|
||||
* @param {object} [options]
|
||||
* @param {function} [callback]
|
||||
* [Meteor docs](http://docs.meteor.com/#update)
|
||||
*/
|
||||
FS.Collection.prototype.update = function(selector, modifier, options, callback) {
|
||||
var self = this;
|
||||
if (selector instanceof FS.File) {
|
||||
// Make sure the file belongs to this FS.Collection
|
||||
if (selector.collectionName === self.files._name) {
|
||||
return selector.update(modifier, options, callback);
|
||||
} else {
|
||||
// Tried to save a file in the wrong FS.Collection
|
||||
throw new Error('FS.Collection cannot update file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return self.files.update(selector, modifier, options, callback);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.remove Remove the file from the collection
|
||||
* @public
|
||||
* @param {FS.File|object} selector
|
||||
* @param {Function} [callback]
|
||||
* [Meteor docs](http://docs.meteor.com/#remove)
|
||||
*/
|
||||
FS.Collection.prototype.remove = function(selector, callback) {
|
||||
var self = this;
|
||||
if (selector instanceof FS.File) {
|
||||
|
||||
// Make sure the file belongs to this FS.Collection
|
||||
if (selector.collectionName === self.files._name) {
|
||||
return selector.remove(callback);
|
||||
} else {
|
||||
// Tried to remove a file from the wrong FS.Collection
|
||||
throw new Error('FS.Collection cannot remove file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"');
|
||||
}
|
||||
}
|
||||
|
||||
//doesn't work correctly on the client without a callback
|
||||
callback = callback || FS.Utility.defaultCallback;
|
||||
return self.files.remove(selector, callback);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.findOne
|
||||
* @public
|
||||
* @param {[selector](http://docs.meteor.com/#selectors)} selector
|
||||
* [Meteor docs](http://docs.meteor.com/#findone)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
*/
|
||||
// Call findOne on files collection
|
||||
FS.Collection.prototype.findOne = function(selector) {
|
||||
var self = this;
|
||||
return self.files.findOne.apply(self.files, arguments);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.find
|
||||
* @public
|
||||
* @param {[selector](http://docs.meteor.com/#selectors)} selector
|
||||
* [Meteor docs](http://docs.meteor.com/#find)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.find = function(selector) {
|
||||
var self = this;
|
||||
return self.files.find.apply(self.files, arguments);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.allow
|
||||
* @public
|
||||
* @param {object} options
|
||||
* @param {function} options.download Function that checks if the file contents may be downloaded
|
||||
* @param {function} options.insert
|
||||
* @param {function} options.update
|
||||
* @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
* @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
* [Meteor docs](http://docs.meteor.com/#allow)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.allow = function(options) {
|
||||
var self = this;
|
||||
|
||||
// Pull out the custom "download" functions
|
||||
if (options.download) {
|
||||
if (!(options.download instanceof Function)) {
|
||||
throw new Error("allow: Value for `download` must be a function");
|
||||
}
|
||||
self._validators.download.allow.push(options.download);
|
||||
delete options.download;
|
||||
}
|
||||
|
||||
return self.files.allow.call(self.files, options);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.deny
|
||||
* @public
|
||||
* @param {object} options
|
||||
* @param {function} options.download Function that checks if the file contents may be downloaded
|
||||
* @param {function} options.insert
|
||||
* @param {function} options.update
|
||||
* @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
* @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
* [Meteor docs](http://docs.meteor.com/#deny)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.deny = function(options) {
|
||||
var self = this;
|
||||
|
||||
// Pull out the custom "download" functions
|
||||
if (options.download) {
|
||||
if (!(options.download instanceof Function)) {
|
||||
throw new Error("deny: Value for `download` must be a function");
|
||||
}
|
||||
self._validators.download.deny.push(options.download);
|
||||
delete options.download;
|
||||
}
|
||||
|
||||
return self.files.deny.call(self.files, options);
|
||||
};
|
||||
|
||||
// TODO: Upsert?
|
||||
|
||||
/**
|
||||
* We provide a default implementation that doesn't do anything.
|
||||
* Can be changed by user or packages, such as the default cfs-collection-filters pkg.
|
||||
* @param {FS.File} fileObj File object
|
||||
* @return {Boolean} Should we allow insertion of this file?
|
||||
*/
|
||||
FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
|
||||
return true;
|
||||
};
|
180
packages/wekan-cfs-collection/api.md
Normal file
180
packages/wekan-cfs-collection/api.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
## cfs-collection Public API ##
|
||||
|
||||
CollectionFS, FS.Collection object
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.insert"></a>*fsCollection*.insert(fileRef, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __insert__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}*
|
||||
|
||||
File data reference
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
Callback `function(error, fileObj)`
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The `file object`
|
||||
[Meteor docs](http://docs.meteor.com/#insert)
|
||||
|
||||
> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.update"></a>*fsCollection*.update(selector, modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __modifier__ *{object}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#update)
|
||||
|
||||
> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.remove"></a>*fsCollection*.remove(selector, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __callback__ *{Function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#remove)
|
||||
|
||||
> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.findOne"></a>*fsCollection*.findOne(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __findOne__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#findone)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.find"></a>*fsCollection*.find(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __find__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#find)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allow"></a>*fsCollection*.allow(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allow__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#allow)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.deny"></a>*fsCollection*.deny(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __deny__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#deny)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200)
|
||||
|
||||
|
171
packages/wekan-cfs-collection/common.js
Normal file
171
packages/wekan-cfs-collection/common.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name A name for the collection
|
||||
* @param {Object} options
|
||||
* @param {FS.StorageAdapter[]} options.stores An array of stores in which files should be saved. At least one is required.
|
||||
* @param {Object} [options.filter] Filter definitions
|
||||
* @param {Number} [options.chunkSize=2MB] Override the chunk size in bytes for uploads
|
||||
* @param {Function} [options.uploader] A function to pass FS.File instances after inserting, which will begin uploading them. By default, `FS.HTTP.uploadQueue.uploadFile` is used if the `cfs-upload-http` package is present, or `FS.DDP.uploadQueue.uploadFile` is used if the `cfs-upload-ddp` package is present. You can override with your own, or set to `null` to prevent automatic uploading.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.Collection = function(name, options) {
|
||||
var self = this;
|
||||
|
||||
self.storesLookup = {};
|
||||
|
||||
self.primaryStore = {};
|
||||
|
||||
self.options = {
|
||||
filter: null, //optional
|
||||
stores: [], //required
|
||||
chunkSize: null
|
||||
};
|
||||
|
||||
// Define a default uploader based on which upload packages are present,
|
||||
// preferring HTTP. You may override with your own function or
|
||||
// set to null to skip automatic uploading of data after file insert/update.
|
||||
if (FS.HTTP && FS.HTTP.uploadQueue) {
|
||||
self.options.uploader = FS.HTTP.uploadQueue.uploadFile;
|
||||
} else if (FS.DDP && FS.DDP.uploadQueue) {
|
||||
self.options.uploader = FS.DDP.uploadQueue.uploadFile;
|
||||
}
|
||||
|
||||
// Extend and overwrite options
|
||||
FS.Utility.extend(self.options, options || {});
|
||||
|
||||
// Set the FS.Collection name
|
||||
self.name = name;
|
||||
|
||||
// Make sure at least one store has been supplied.
|
||||
// Usually the stores aren't used on the client, but we need them defined
|
||||
// so that we can access their names and use the first one as the default.
|
||||
if (FS.Utility.isEmpty(self.options.stores)) {
|
||||
throw new Error("You must specify at least one store. Please consult the documentation.");
|
||||
}
|
||||
|
||||
FS.Utility.each(self.options.stores, function(store, i) {
|
||||
// Set the primary store
|
||||
if (i === 0) {
|
||||
self.primaryStore = store;
|
||||
}
|
||||
|
||||
// Check for duplicate naming
|
||||
if (typeof self.storesLookup[store.name] !== 'undefined') {
|
||||
throw new Error('FS.Collection store names must be uniq, duplicate found: ' + store.name);
|
||||
}
|
||||
|
||||
// Set the lookup
|
||||
self.storesLookup[store.name] = store;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
|
||||
// Emit events based on store events
|
||||
store.on('stored', function (storeName, fileObj) {
|
||||
// This is weird, but currently there is a bug where each store will emit the
|
||||
// events for all other stores, too, so we need to make sure that this event
|
||||
// is truly for this store.
|
||||
if (storeName !== store.name)
|
||||
return;
|
||||
// When a file is successfully stored into the store, we emit a "stored" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
var emitted = self.emit('stored', fileObj, store.name);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(fileObj.name({store: store.name}) + ' was successfully saved to the ' + store.name + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on the ' + name + ' collection.');
|
||||
}
|
||||
}
|
||||
fileObj.emit('stored', store.name);
|
||||
});
|
||||
|
||||
store.on('error', function (storeName, error, fileObj) {
|
||||
// This is weird, but currently there is a bug where each store will emit the
|
||||
// events for all other stores, too, so we need to make sure that this event
|
||||
// is truly for this store.
|
||||
if (storeName !== store.name)
|
||||
return;
|
||||
// When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
error = new Error('Error storing file to the ' + store.name + ' store: ' + error.message);
|
||||
var emitted = self.emit('error', error, fileObj, store.name);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
fileObj.emit('error', store.name);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var _filesOptions = {
|
||||
transform: function(doc) {
|
||||
// This should keep the filerecord in the file object updated in reactive
|
||||
// context
|
||||
var result = new FS.File(doc, true);
|
||||
result.collectionName = name;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
if(self.options.idGeneration) _filesOptions.idGeneration = self.options.idGeneration;
|
||||
|
||||
// Enable specifying an alternate driver, to change where the filerecord is stored
|
||||
// Drivers can be created with MongoInternals.RemoteCollectionDriver()
|
||||
if(self.options._driver){
|
||||
_filesOptions._driver = self.options._driver;
|
||||
}
|
||||
|
||||
// Create the 'cfs.' ++ ".filerecord" and use fsFile
|
||||
var collectionName = 'cfs.' + name + '.filerecord';
|
||||
self.files = new Mongo.Collection(collectionName, _filesOptions);
|
||||
|
||||
// For storing custom allow/deny functions
|
||||
self._validators = {
|
||||
download: {allow: [], deny: []}
|
||||
};
|
||||
|
||||
// Set up filters
|
||||
// XXX Should we deprecate the filter option now that this is done with a separate pkg, or just keep it?
|
||||
if (self.filters) {
|
||||
self.filters(self.options.filter);
|
||||
}
|
||||
|
||||
// Save the collection reference (we want it without the 'cfs.' prefix and '.filerecord' suffix)
|
||||
FS._collections[name] = this;
|
||||
|
||||
// Set up observers
|
||||
Meteor.isServer && FS.FileWorker && FS.FileWorker.observe(this);
|
||||
|
||||
// Emit "removed" event on collection
|
||||
self.files.find().observe({
|
||||
removed: function(fileObj) {
|
||||
self.emit('removed', fileObj);
|
||||
}
|
||||
});
|
||||
|
||||
// Emit events based on TempStore events
|
||||
if (FS.TempStore) {
|
||||
FS.TempStore.on('stored', function (fileObj, result) {
|
||||
// When a file is successfully stored into the temp store, we emit an "uploaded" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
var emitted = self.emit('uploaded', fileObj);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(fileObj.name() + ' was successfully uploaded. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "uploaded" event on the ' + name + ' collection.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FS.TempStore.on('error', function (error, fileObj) {
|
||||
// When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
self.emit('error', new Error('Error storing uploaded file to TempStore: ' + error.message), fileObj);
|
||||
}
|
||||
});
|
||||
} else if (Meteor.isServer) {
|
||||
throw new Error("FS.Collection constructor: FS.TempStore must be defined before constructing any FS.Collections.")
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// An FS.Collection can emit events
|
||||
FS.Collection.prototype = new EventEmitter();
|
390
packages/wekan-cfs-collection/internal.api.md
Normal file
390
packages/wekan-cfs-collection/internal.api.md
Normal file
|
@ -0,0 +1,390 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["common.js"](common.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
#############################################################################
|
||||
|
||||
COLLECTION FS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="FS.Collection"></a>new *fs*.Collection(name, options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __Collection__ is defined in `FS`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{string}*
|
||||
|
||||
A name for the collection
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __stores__ *{[FS.StorageAdapter[]](#FS.StorageAdapter[])}*
|
||||
|
||||
An array of stores in which files should be saved. At least one is required.
|
||||
|
||||
* __filter__ *{Object}* (Optional)
|
||||
|
||||
Filter definitions
|
||||
|
||||
* __chunkSize__ *{Number}* (Optional, Default = 131072)
|
||||
|
||||
Override the chunk size in bytes for uploads and downloads
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
|
||||
> ```FS.Collection = function(name, options) { ...``` [common.js:17](common.js#L17)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["api.common.js"](api.common.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Collection.prototype.insert"></a>*fsCollection*.insert(fileRef, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __insert__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}*
|
||||
|
||||
File data reference
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
Callback `function(error, fileObj)`
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The `file object`
|
||||
[Meteor docs](http://docs.meteor.com/#insert)
|
||||
|
||||
> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.update"></a>*fsCollection*.update(selector, modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __modifier__ *{object}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#update)
|
||||
|
||||
> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.remove"></a>*fsCollection*.remove(selector, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __callback__ *{Function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#remove)
|
||||
|
||||
> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.findOne"></a>*fsCollection*.findOne(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __findOne__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#findone)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.find"></a>*fsCollection*.find(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __find__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#find)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allow"></a>*fsCollection*.allow(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allow__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#allow)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.deny"></a>*fsCollection*.deny(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __deny__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#deny)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["api.client.js"](api.client.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="_eventCallback"></a>_eventCallback(templateName, selector, dataContext, evt, temp, fsFile) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __dataContext__ *{object}*
|
||||
|
||||
The event datacontext
|
||||
|
||||
* __evt__ *{object}*
|
||||
|
||||
The event object { error, file }
|
||||
|
||||
* __temp__ *{object}*
|
||||
|
||||
The template instance
|
||||
|
||||
* __fsFile__ *{[FS.File](#FS.File)}*
|
||||
|
||||
File that triggered the event
|
||||
|
||||
|
||||
> ```var _eventCallback = function fsEventCallback(templateName, selector, dataContext, evt, temp, fsFile) { ...``` [api.client.js:10](api.client.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="_eachFile"></a>_eachFile(files, metadata, callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __files__ *{array}*
|
||||
|
||||
List of files to iterate over
|
||||
|
||||
* __metadata__ *{object}*
|
||||
|
||||
Data to attach to the files
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
Function to pass the prepared `FS.File` object
|
||||
|
||||
|
||||
> ```var _eachFile = function(files, metadata, callback) { ...``` [api.client.js:36](api.client.js#L36)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.acceptDropsOn"></a>*fsCollection*.acceptDropsOn(templateName, selector, [metadata]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __acceptDropsOn__ is defined in `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __metadata__ *{object|function}* (Optional)
|
||||
|
||||
Data/getter to attach to the file objects
|
||||
|
||||
|
||||
Using this method adds an `uploaded` and `uploadFailed` event to the
|
||||
template events. The event object contains `{ error, file }`
|
||||
|
||||
Example:
|
||||
```css
|
||||
.dropzone {
|
||||
border: 2px dashed silver;
|
||||
height: 5em;
|
||||
padding-top: 3em;
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
-ms-border-radius: 8px;
|
||||
-o-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
```
|
||||
```html
|
||||
<template name="hello">
|
||||
Choose file to upload:<br/>
|
||||
<div id="dropzone" class="dropzone">
|
||||
<div style="text-align: center; color: gray;">Drop file to upload</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
```js
|
||||
Template.hello.events({
|
||||
'uploaded #dropzone': function(event, temp) {
|
||||
console.log('Event Uploaded: ' + event.file._id);
|
||||
}
|
||||
});
|
||||
images.acceptDropsOn('hello', '#dropzone');
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.acceptDropsOn = function(templateName, selector, metadata) { ...``` [api.client.js:99](api.client.js#L99)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.acceptUploadFrom"></a>*fsCollection*.acceptUploadFrom(templateName, selector, [metadata]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __acceptUploadFrom__ is defined in `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __metadata__ *{object|function}* (Optional)
|
||||
|
||||
Data/getter to attach to the file objects
|
||||
|
||||
|
||||
Using this method adds an `uploaded` and `uploadFailed` event to the
|
||||
template events. The event object contains `{ error, file }`
|
||||
|
||||
Example:
|
||||
```html
|
||||
<template name="hello">
|
||||
Choose file to upload:<br/>
|
||||
<input type="file" id="files" multiple/>
|
||||
</template>
|
||||
```
|
||||
```js
|
||||
Template.hello.events({
|
||||
'uploaded #files': function(event, temp) {
|
||||
console.log('Event Uploaded: ' + event.file._id);
|
||||
}
|
||||
});
|
||||
images.acceptUploadFrom('hello', '#files');
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.acceptUploadFrom = function(templateName, selector, metadata) { ...``` [api.client.js:156](api.client.js#L156)
|
||||
|
||||
|
43
packages/wekan-cfs-collection/package.js
Normal file
43
packages/wekan-cfs-collection/package.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-collection',
|
||||
version: '0.5.5',
|
||||
summary: 'CollectionFS, FS.Collection object',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-collection.git'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
// CFS
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-tempstore@0.1.4',
|
||||
// Core
|
||||
'deps',
|
||||
'check',
|
||||
'livedata',
|
||||
'mongo-livedata',
|
||||
// Other
|
||||
'raix:eventemitter@0.1.1'
|
||||
]);
|
||||
|
||||
// Weak dependencies for uploaders
|
||||
api.use(['wekan-cfs-upload-http@0.0.20', 'wekan-cfs-upload-ddp@0.0.17'], { weak: true });
|
||||
|
||||
api.addFiles([
|
||||
'common.js',
|
||||
'api.common.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'common.js',
|
||||
'api.common.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(['wekan-cfs-standard-packages', 'wekan-cfs-gridfs', 'tinytest', 'underscore', 'test-helpers']);
|
||||
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
});
|
33
packages/wekan-cfs-collection/tests/client-tests.js
Normal file
33
packages/wekan-cfs-collection/tests/client-tests.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
/*
|
||||
* TODO FS.Collection Client Tests
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//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)
|
96
packages/wekan-cfs-collection/tests/server-tests.js
Normal file
96
packages/wekan-cfs-collection/tests/server-tests.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
var fileCollection = new FS.Collection('files', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('files')
|
||||
]
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - insert URL - sync - one', function (test) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
// One
|
||||
try {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg");
|
||||
test.isTrue(true);
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - insert URL - sync - loop', function (test) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
try {
|
||||
for (var i = 0, len = 10; i < len; i++) {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg");
|
||||
}
|
||||
test.isTrue(true);
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
}
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-collection - insert URL - async - one', function (test, next) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
// One
|
||||
try {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) {
|
||||
test.isNull(error);
|
||||
test.instanceOf(result, FS.File);
|
||||
next();
|
||||
});
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-collection - insert URL - async - loop', function (test, next) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
try {
|
||||
var done = 0;
|
||||
for (var i = 0, len = 10; i < len; i++) {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) {
|
||||
test.isNull(error);
|
||||
test.instanceOf(result, FS.File);
|
||||
done++;
|
||||
if (done === 10) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
//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)
|
5
packages/wekan-cfs-data-man/.travis.yml
Normal file
5
packages/wekan-cfs-data-man/.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-data-man/LICENSE.md
Normal file
20
packages/wekan-cfs-data-man/LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com, and [@aldeed](https://github.com/aldeed), aka Eric Dobbertin
|
||||
|
||||
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.
|
8
packages/wekan-cfs-data-man/README.md
Normal file
8
packages/wekan-cfs-data-man/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-data-man [](https://travis-ci.org/CollectionFS/Meteor-data-man)
|
||||
=========================
|
||||
|
||||
Who can handle your arbitrary data? The DataMan can.
|
||||
|
||||
This is a package used by CollectionFS to attach data to file objects. Could be used for other things, though.
|
||||
|
||||
[Public API](api.md)
|
309
packages/wekan-cfs-data-man/api.md
Normal file
309
packages/wekan-cfs-data-man/api.md
Normal file
|
@ -0,0 +1,309 @@
|
|||
## data-man Public API ##
|
||||
|
||||
A data manager, allowing you to attach various types of data and get it back in various other types
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Client</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBlob"></a>*dataman*.getBlob(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBlob__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, blob)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Blob representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBinary"></a>*dataman*.getBinary([start], [end], callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBinary__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __start__ *{Number}* (Optional)
|
||||
|
||||
First byte position to read.
|
||||
|
||||
* __end__ *{Number}* (Optional)
|
||||
|
||||
Last byte position to read.
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, binaryData)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Uint8Array representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveAs"></a>*dataman*.saveAs([filename]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __saveAs__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filename__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
Tells the browser to save the data like a normal downloaded file,
|
||||
using the provided filename.
|
||||
|
||||
|
||||
> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBuffer"></a>*dataman*.getBuffer([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveToFile"></a>*dataman*.saveToFile() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __saveToFile__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Saves this data to a filepath on the local filesystem.
|
||||
|
||||
> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the data URI.
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.createReadStream"></a>*dataman*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.size"></a>*dataman*.size([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the size in bytes of the data.
|
||||
|
||||
> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer"></a>new *dataman*.Buffer(buffer, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Buffer__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __buffer__ *{Buffer}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.DataURI"></a>new *dataman*.DataURI(dataUri) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __DataURI__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataUri__ *{String}*
|
||||
|
||||
|
||||
> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath"></a>new *dataman*.FilePath(filepath, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __FilePath__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filepath__ *{String}*
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type. Will lookup from file if not passed.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL"></a>new *dataman*.URL(url, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __URL__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __url__ *{String}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10)
|
||||
|
||||
|
166
packages/wekan-cfs-data-man/client/Blob.js
Normal file
166
packages/wekan-cfs-data-man/client/Blob.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
/* Blob.js
|
||||
* A Blob implementation.
|
||||
* 2013-12-27
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* By Devin Samarin, https://github.com/eboyjr
|
||||
* License: X11/MIT
|
||||
* See LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self, unescape */
|
||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||
|
||||
if (!(typeof Blob === "function" || typeof Blob === "object") || typeof URL === "undefined")
|
||||
if ((typeof Blob === "function" || typeof Blob === "object") && typeof webkitURL !== "undefined") self.URL = webkitURL;
|
||||
else var Blob = (function (view) {
|
||||
"use strict";
|
||||
|
||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
|
||||
var
|
||||
get_class = function(object) {
|
||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||
}
|
||||
, FakeBlobBuilder = function BlobBuilder() {
|
||||
this.data = [];
|
||||
}
|
||||
, FakeBlob = function Blob(data, type, encoding) {
|
||||
this.data = data;
|
||||
this.size = data.length;
|
||||
this.type = type;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
, FBB_proto = FakeBlobBuilder.prototype
|
||||
, FB_proto = FakeBlob.prototype
|
||||
, FileReaderSync = view.FileReaderSync
|
||||
, FileException = function(type) {
|
||||
this.code = this[this.name = type];
|
||||
}
|
||||
, file_ex_codes = (
|
||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||
).split(" ")
|
||||
, file_ex_code = file_ex_codes.length
|
||||
, real_URL = view.URL || view.webkitURL || view
|
||||
, real_create_object_URL = real_URL.createObjectURL
|
||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||
, URL = real_URL
|
||||
, btoa = view.btoa
|
||||
, atob = view.atob
|
||||
|
||||
, ArrayBuffer = view.ArrayBuffer
|
||||
, Uint8Array = view.Uint8Array
|
||||
;
|
||||
FakeBlob.fake = FB_proto.fake = true;
|
||||
while (file_ex_code--) {
|
||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||
}
|
||||
if (!real_URL.createObjectURL) {
|
||||
URL = view.URL = {};
|
||||
}
|
||||
URL.createObjectURL = function(blob) {
|
||||
var
|
||||
type = blob.type
|
||||
, data_URI_header
|
||||
;
|
||||
if (type === null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
if (blob instanceof FakeBlob) {
|
||||
data_URI_header = "data:" + type;
|
||||
if (blob.encoding === "base64") {
|
||||
return data_URI_header + ";base64," + blob.data;
|
||||
} else if (blob.encoding === "URI") {
|
||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||
} if (btoa) {
|
||||
return data_URI_header + ";base64," + btoa(blob.data);
|
||||
} else {
|
||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||
}
|
||||
} else if (real_create_object_URL) {
|
||||
return real_create_object_URL.call(real_URL, blob);
|
||||
}
|
||||
};
|
||||
URL.revokeObjectURL = function(object_URL) {
|
||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||
real_revoke_object_URL.call(real_URL, object_URL);
|
||||
}
|
||||
};
|
||||
FBB_proto.append = function(data/*, endings*/) {
|
||||
var bb = this.data;
|
||||
// decode data to a binary string
|
||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||
var
|
||||
str = ""
|
||||
, buf = new Uint8Array(data)
|
||||
, i = 0
|
||||
, buf_len = buf.length
|
||||
;
|
||||
for (; i < buf_len; i++) {
|
||||
str += String.fromCharCode(buf[i]);
|
||||
}
|
||||
bb.push(str);
|
||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||
if (FileReaderSync) {
|
||||
var fr = new FileReaderSync;
|
||||
bb.push(fr.readAsBinaryString(data));
|
||||
} else {
|
||||
// async FileReader won't work as BlobBuilder is sync
|
||||
throw new FileException("NOT_READABLE_ERR");
|
||||
}
|
||||
} else if (data instanceof FakeBlob) {
|
||||
if (data.encoding === "base64" && atob) {
|
||||
bb.push(atob(data.data));
|
||||
} else if (data.encoding === "URI") {
|
||||
bb.push(decodeURIComponent(data.data));
|
||||
} else if (data.encoding === "raw") {
|
||||
bb.push(data.data);
|
||||
}
|
||||
} else {
|
||||
if (typeof data !== "string") {
|
||||
data += ""; // convert unsupported types to strings
|
||||
}
|
||||
// decode UTF-16 to binary string
|
||||
bb.push(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
};
|
||||
FBB_proto.getBlob = function(type) {
|
||||
if (!arguments.length) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(this.data.join(""), type, "raw");
|
||||
};
|
||||
FBB_proto.toString = function() {
|
||||
return "[object BlobBuilder]";
|
||||
};
|
||||
FB_proto.slice = function(start, end, type) {
|
||||
var args = arguments.length;
|
||||
if (args < 3) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(
|
||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||
, type
|
||||
, this.encoding
|
||||
);
|
||||
};
|
||||
FB_proto.toString = function() {
|
||||
return "[object Blob]";
|
||||
};
|
||||
return FakeBlobBuilder;
|
||||
}(view));
|
||||
|
||||
return function Blob(blobParts, options) {
|
||||
var type = options ? (options.type || "") : "";
|
||||
var builder = new BlobBuilder();
|
||||
if (blobParts) {
|
||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||
builder.append(blobParts[i]);
|
||||
}
|
||||
}
|
||||
return builder.getBlob(type);
|
||||
};
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
302
packages/wekan-cfs-data-man/client/data-man-api.js
Normal file
302
packages/wekan-cfs-data-man/client/data-man-api.js
Normal file
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* @method DataMan
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {File|Blob|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
|
||||
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
*/
|
||||
DataMan = function DataMan(data, type) {
|
||||
var self = this;
|
||||
|
||||
if (!data) {
|
||||
throw new Error("DataMan constructor requires a data argument");
|
||||
}
|
||||
|
||||
// The end result of all this is that we will have one of the following set:
|
||||
// - self.blob
|
||||
// - self.url
|
||||
// Unless we already have in-memory data, we don't load anything into memory
|
||||
// and instead rely on obtaining a read stream when the time comes.
|
||||
if (typeof File !== "undefined" && data instanceof File) {
|
||||
self.blob = data; // File inherits from Blob so this is OK
|
||||
self._type = data.type;
|
||||
} else if (typeof Blob !== "undefined" && data instanceof Blob) {
|
||||
self.blob = data;
|
||||
self._type = data.type;
|
||||
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer || EJSON.isBinary(data)) {
|
||||
if (typeof Blob === "undefined") {
|
||||
throw new Error("Browser must support Blobs to handle an ArrayBuffer or Uint8Array");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer or Uint8Array");
|
||||
}
|
||||
self.blob = new Blob([data], {type: type});
|
||||
self._type = type;
|
||||
} else if (typeof data === "string") {
|
||||
if (data.slice(0, 5) === "data:") {
|
||||
self._type = data.slice(5, data.indexOf(';'));
|
||||
self.blob = dataURItoBlob(data, self._type);
|
||||
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a URL");
|
||||
}
|
||||
self.url = data;
|
||||
self._type = type;
|
||||
} else {
|
||||
throw new Error("DataMan constructor received unrecognized data string");
|
||||
}
|
||||
} else {
|
||||
throw new Error("DataMan constructor received data that it doesn't support");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBlob
|
||||
* @public
|
||||
* @param {Function} [callback] - callback(error, blob)
|
||||
* @returns {undefined|Blob}
|
||||
*
|
||||
* Passes a Blob representing this data to a callback or returns
|
||||
* the Blob if no callback is provided. A callback is required
|
||||
* if getting a Blob for a URL.
|
||||
*/
|
||||
DataMan.prototype.getBlob = function dataManGetBlob(callback) {
|
||||
var self = this;
|
||||
|
||||
if (callback) {
|
||||
if (self.blob) {
|
||||
callback(null, self.blob);
|
||||
} else if (self.url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', self.url, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.onload = function(data) {
|
||||
self.blob = xhr.response;
|
||||
callback(null, self.blob);
|
||||
};
|
||||
xhr.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
} else {
|
||||
if (self.url)
|
||||
throw new Error('DataMan.getBlob requires a callback when managing a URL');
|
||||
return self.blob;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBinary
|
||||
* @public
|
||||
* @param {Number} [start] - First byte position to read.
|
||||
* @param {Number} [end] - Last byte position to read.
|
||||
* @param {Function} callback - callback(error, binaryData)
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Passes a Uint8Array representing this data to a callback.
|
||||
*/
|
||||
DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof start === "function") {
|
||||
callback = start;
|
||||
}
|
||||
callback = callback || defaultCallback;
|
||||
|
||||
function read(blob) {
|
||||
if (typeof FileReader === "undefined") {
|
||||
callback(new Error("Browser does not support FileReader"));
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
callback(null, new Uint8Array(evt.target.result));
|
||||
};
|
||||
reader.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (typeof start === "number" && typeof end === "number") {
|
||||
var size = blob.size;
|
||||
// Return the requested chunk of binary data
|
||||
if (start >= size) {
|
||||
callback(new Error("DataMan.getBinary: start position beyond end of data (" + size + ")"));
|
||||
return;
|
||||
}
|
||||
end = Math.min(size, end);
|
||||
|
||||
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
|
||||
if (typeof slice === 'undefined') {
|
||||
callback(new Error('Browser does not support File.slice'));
|
||||
return;
|
||||
}
|
||||
|
||||
read(slice.call(blob, start, end, self._type));
|
||||
} else {
|
||||
// Return the entire binary data
|
||||
read(blob);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/** @method DataMan.prototype.saveAs
|
||||
* @public
|
||||
* @param {String} [filename]
|
||||
* @return {undefined}
|
||||
*
|
||||
* Tells the browser to save the data like a normal downloaded file,
|
||||
* using the provided filename.
|
||||
*
|
||||
*/
|
||||
DataMan.prototype.saveAs = function dataManSaveAs(filename) {
|
||||
var self = this;
|
||||
|
||||
if (typeof window === "undefined")
|
||||
throw new Error("window must be defined to use saveLocal");
|
||||
|
||||
if (!window.saveAs) {
|
||||
console.warn('DataMan.saveAs: window.saveAs not supported by this browser - add cfs-filesaver package');
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
window.saveAs(blob, filename);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getDataUri
|
||||
* @public
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*/
|
||||
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
|
||||
// XXX: We could consider using: URL.createObjectURL(blob);
|
||||
// This will create a reference to the blob data instead of a clone
|
||||
// This is part of the File API - as the rest - Not sure how to generally
|
||||
// support from IE10, FF26, Chrome 31, safari 7, opera 19, ios 6, android 4
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof callback !== 'function')
|
||||
throw new Error("getDataUri requires callback function");
|
||||
|
||||
if (typeof FileReader === "undefined") {
|
||||
callback(new Error("Browser does not support FileReader"));
|
||||
return;
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var dataUri = event.target.result;
|
||||
callback(null, dataUri);
|
||||
};
|
||||
fileReader.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
fileReader.readAsDataURL(blob);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.size
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, size)
|
||||
*
|
||||
* Passes the size of the data to the callback, if provided,
|
||||
* or returns it. A callback is required to get the size of a URL on the client.
|
||||
*/
|
||||
DataMan.prototype.size = function dataManSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (callback) {
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
} else {
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
self._size = blob.size;
|
||||
callback(null, self._size);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (self.url) {
|
||||
throw new Error("On the client, DataMan.size requires a callback when getting size for a URL on the client");
|
||||
} else if (typeof self._size === "number") {
|
||||
return self._size;
|
||||
} else {
|
||||
var blob = self.getBlob();
|
||||
self._size = blob.size;
|
||||
return self._size;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.type
|
||||
* @public
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.prototype.type = function dataManType() {
|
||||
return this._type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method dataURItoBlob
|
||||
* @private
|
||||
* @param {String} dataURI The data URI
|
||||
* @param {String} dataTYPE The content type
|
||||
* @returns {Blob} A new Blob instance
|
||||
*
|
||||
* Converts a data URI to a Blob.
|
||||
*/
|
||||
function dataURItoBlob(dataURI, dataTYPE) {
|
||||
var str = atob(dataURI.split(',')[1]), array = [];
|
||||
for(var i = 0; i < str.length; i++) array.push(str.charCodeAt(i));
|
||||
return new Blob([new Uint8Array(array)], {type: dataTYPE});
|
||||
}
|
||||
|
||||
/**
|
||||
* @method defaultCallback
|
||||
* @private
|
||||
* @param {Error} [err]
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Can be used as a default callback for client methods that need a callback.
|
||||
* Simply throws the provided error if there is one.
|
||||
*/
|
||||
function defaultCallback(err) {
|
||||
if (err) {
|
||||
// Show gentle error if Meteor error
|
||||
if (err instanceof Meteor.Error) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
// Normal error, just throw error
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
674
packages/wekan-cfs-data-man/internal.api.md
Normal file
674
packages/wekan-cfs-data-man/internal.api.md
Normal file
|
@ -0,0 +1,674 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["client/Blob.js"](client/Blob.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="if "></a>if {any} <sub><i>Client</i></sub> ###
|
||||
|
||||
```
|
||||
Blob.js
|
||||
A Blob implementation.
|
||||
2013-06-20
|
||||
By Eli Grey, http:
|
||||
By Devin Samarin, https:
|
||||
License: X11/MIT
|
||||
See LICENSE.md
|
||||
```
|
||||
|
||||
|
||||
> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="if "></a>if {any} <sub><i>Client</i></sub> ###
|
||||
|
||||
```
|
||||
global unescape jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true
|
||||
```
|
||||
|
||||
> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["client/data-man-api.js"](client/data-man-api.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Client</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBlob"></a>*dataman*.getBlob(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBlob__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, blob)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Blob representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBinary"></a>*dataman*.getBinary([start], [end], callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBinary__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __start__ *{Number}* (Optional)
|
||||
|
||||
First byte position to read.
|
||||
|
||||
* __end__ *{Number}* (Optional)
|
||||
|
||||
Last byte position to read.
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, binaryData)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Uint8Array representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveAs"></a>*dataman*.saveAs([filename]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __saveAs__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filename__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
Tells the browser to save the data like a normal downloaded file,
|
||||
using the provided filename.
|
||||
|
||||
|
||||
> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="dataURItoBlob"></a>dataURItoBlob(dataURI, dataTYPE) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataURI__ *{String}*
|
||||
|
||||
The data URI
|
||||
|
||||
* __dataTYPE__ *{String}*
|
||||
|
||||
The content type
|
||||
|
||||
|
||||
__Returns__ *{Blob}*
|
||||
A new Blob instance
|
||||
|
||||
|
||||
Converts a data URI to a Blob.
|
||||
|
||||
> ```function dataURItoBlob(dataURI, dataTYPE) { ...``` [client/data-man-api.js:240](client/data-man-api.js#L240)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="defaultCallback"></a>defaultCallback([err]) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```function defaultCallback(err) { ...``` [client/data-man-api.js:255](client/data-man-api.js#L255)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-api.js"](server/data-man-api.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBuffer"></a>*dataman*.getBuffer([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveToFile"></a>*dataman*.saveToFile() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __saveToFile__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Saves this data to a filepath on the local filesystem.
|
||||
|
||||
> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the data URI.
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.createReadStream"></a>*dataman*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.size"></a>*dataman*.size([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the size in bytes of the data.
|
||||
|
||||
> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-buffer.js"](server/data-man-buffer.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.Buffer"></a>new *dataman*.Buffer(buffer, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Buffer__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __buffer__ *{Buffer}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.getBuffer"></a>*datamanBuffer*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) { ...``` [server/data-man-buffer.js:24](server/data-man-buffer.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.getDataUri"></a>*datamanBuffer*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data in the buffer to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) { ...``` [server/data-man-buffer.js:35](server/data-man-buffer.js#L35)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.createReadStream"></a>*datamanBuffer*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() { ...``` [server/data-man-buffer.js:51](server/data-man-buffer.js#L51)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.size"></a>*datamanBuffer*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Passes the size in bytes of the data in the buffer to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.size = function dataManBufferSize(callback) { ...``` [server/data-man-buffer.js:62](server/data-man-buffer.js#L62)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.type"></a>*datamanBuffer*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.Buffer.prototype.type = function dataManBufferType() { ...``` [server/data-man-buffer.js:80](server/data-man-buffer.js#L80)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-datauri.js"](server/data-man-datauri.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.DataURI"></a>new *dataman*.DataURI(dataUri) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __DataURI__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataUri__ *{String}*
|
||||
|
||||
|
||||
> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-filepath.js"](server/data-man-filepath.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.FilePath"></a>new *dataman*.FilePath(filepath, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __FilePath__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filepath__ *{String}*
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type. Will lookup from file if not passed.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.getBuffer"></a>*datamanFilepath*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) { ...``` [server/data-man-filepath.js:25](server/data-man-filepath.js#L25)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.getDataUri"></a>*datamanFilepath*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) { ...``` [server/data-man-filepath.js:43](server/data-man-filepath.js#L43)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.createReadStream"></a>*datamanFilepath*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() { ...``` [server/data-man-filepath.js:67](server/data-man-filepath.js#L67)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.size"></a>*datamanFilepath*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Passes the size in bytes of the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) { ...``` [server/data-man-filepath.js:79](server/data-man-filepath.js#L79)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.type"></a>*datamanFilepath*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.FilePath.prototype.type = function dataManFilePathType() { ...``` [server/data-man-filepath.js:104](server/data-man-filepath.js#L104)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-url.js"](server/data-man-url.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.URL"></a>new *dataman*.URL(url, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __URL__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __url__ *{String}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.getBuffer"></a>*datamanUrl*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data at the URL to a callback.
|
||||
|
||||
> ```DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) { ...``` [server/data-man-url.js:24](server/data-man-url.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.getDataUri"></a>*datamanUrl*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data at the URL to a callback.
|
||||
|
||||
> ```DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) { ...``` [server/data-man-url.js:57](server/data-man-url.js#L57)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.createReadStream"></a>*datamanUrl*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() { ...``` [server/data-man-url.js:85](server/data-man-url.js#L85)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.size"></a>*datamanUrl*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Returns the size in bytes of the data at the URL.
|
||||
|
||||
> ```DataMan.URL.prototype.size = function dataManUrlSize(callback) { ...``` [server/data-man-url.js:97](server/data-man-url.js#L97)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.type"></a>*datamanUrl*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.URL.prototype.type = function dataManUrlType() { ...``` [server/data-man-url.js:121](server/data-man-url.js#L121)
|
||||
|
||||
|
48
packages/wekan-cfs-data-man/package.js
Normal file
48
packages/wekan-cfs-data-man/package.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-data-man',
|
||||
version: '0.0.6',
|
||||
summary: 'A data manager, allowing you to attach various types of data and get it back in various other types',
|
||||
git: 'https://github.com/zcfs/Meteor-data-man.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
mime: "1.2.11",
|
||||
'buffer-stream-reader': "0.1.1",
|
||||
//request: "2.44.0",
|
||||
// We use a specific commit from a fork of "request" package for now; we need fix for
|
||||
// https://github.com/mikeal/request/issues/887 (https://github.com/zcfs/Meteor-CollectionFS/issues/347)
|
||||
request: "https://github.com/wekan/request",
|
||||
temp: "0.7.0" // for tests only
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['ejson']);
|
||||
|
||||
api.use(['wekan-cfs-filesaver@0.0.6'], {weak: true});
|
||||
|
||||
api.export('DataMan');
|
||||
|
||||
api.addFiles([
|
||||
'client/Blob.js', //polyfill for browsers without Blob constructor; currently necessary for phantomjs support, too
|
||||
'client/data-man-api.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'server/data-man-api.js',
|
||||
'server/data-man-buffer.js',
|
||||
'server/data-man-datauri.js',
|
||||
'server/data-man-filepath.js',
|
||||
'server/data-man-url.js',
|
||||
'server/data-man-readstream.js'
|
||||
], 'server');
|
||||
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(['wekan-cfs-data-man', 'http', 'tinytest', 'test-helpers', 'wekan-cfs-http-methods@0.0.29']);
|
||||
|
||||
api.addFiles(['tests/common.js', 'tests/client-tests.js'], 'client');
|
||||
api.addFiles(['tests/common.js', 'tests/server-tests.js'], 'server');
|
||||
});
|
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
/* global DataMan:true, Buffer */
|
||||
|
||||
var fs = Npm.require("fs");
|
||||
var Readable = Npm.require('stream').Readable;
|
||||
|
||||
/**
|
||||
* @method DataMan
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
|
||||
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
* @param {Object} [options] Currently used only to pass options for the GET request when `data` is a URL.
|
||||
*/
|
||||
DataMan = function DataMan(data, type, options) {
|
||||
var self = this, buffer;
|
||||
|
||||
if (!data) {
|
||||
throw new Error("DataMan constructor requires a data argument");
|
||||
}
|
||||
|
||||
// The end result of all this is that we will have this.source set to a correct
|
||||
// data type handler. We are simply detecting what the data arg is.
|
||||
//
|
||||
// Unless we already have in-memory data, we don't load anything into memory
|
||||
// and instead rely on obtaining a read stream when the time comes.
|
||||
if (typeof Buffer !== "undefined" && data instanceof Buffer) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Buffer");
|
||||
}
|
||||
self.source = new DataMan.Buffer(data, type);
|
||||
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer");
|
||||
}
|
||||
buffer = new Buffer(new Uint8Array(data));
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (EJSON.isBinary(data)) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Uint8Array");
|
||||
}
|
||||
buffer = new Buffer(data);
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (typeof Readable !== "undefined" && data instanceof Readable) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a stream.Readable");
|
||||
}
|
||||
self.source = new DataMan.ReadStream(data, type);
|
||||
} else if (typeof data === "string") {
|
||||
if (data.slice(0, 5) === "data:") {
|
||||
self.source = new DataMan.DataURI(data);
|
||||
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a URL");
|
||||
}
|
||||
self.source = new DataMan.URL(data, type, options);
|
||||
} else {
|
||||
// assume it's a filepath
|
||||
self.source = new DataMan.FilePath(data, type);
|
||||
}
|
||||
} else {
|
||||
throw new Error("DataMan constructor received data that it doesn't support");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBuffer
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
*/
|
||||
DataMan.prototype.getBuffer = function dataManGetBuffer(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getBuffer(callback) : Meteor.wrapAsync(bind(self.source.getBuffer, self.source))();
|
||||
};
|
||||
|
||||
function _saveToFile(readStream, filePath, callback) {
|
||||
var writeStream = fs.createWriteStream(filePath);
|
||||
writeStream.on('close', Meteor.bindEnvironment(function () {
|
||||
callback();
|
||||
}, function (error) { callback(error); }));
|
||||
writeStream.on('error', Meteor.bindEnvironment(function (error) {
|
||||
callback(error);
|
||||
}, function (error) { callback(error); }));
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.saveToFile
|
||||
* @public
|
||||
* @param {String} filePath
|
||||
* @param {Function} callback
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Saves this data to a filepath on the local filesystem.
|
||||
*/
|
||||
DataMan.prototype.saveToFile = function dataManSaveToFile(filePath, callback) {
|
||||
var readStream = this.createReadStream();
|
||||
return callback ? _saveToFile(readStream, filePath, callback) : Meteor.wrapAsync(_saveToFile)(readStream, filePath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getDataUri
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, dataUri)
|
||||
*
|
||||
* If no callback, returns the data URI.
|
||||
*/
|
||||
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getDataUri(callback) : Meteor.wrapAsync(bind(self.source.getDataUri, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.createReadStream
|
||||
* @public
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.prototype.createReadStream = function dataManCreateReadStream() {
|
||||
return this.source.createReadStream();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.size
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, size)
|
||||
*
|
||||
* If no callback, returns the size in bytes of the data.
|
||||
*/
|
||||
DataMan.prototype.size = function dataManSize(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.size(callback) : Meteor.wrapAsync(bind(self.source.size, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.type
|
||||
* @public
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.prototype.type = function dataManType() {
|
||||
return this.source.type();
|
||||
};
|
||||
|
||||
/*
|
||||
* "bind" shim; from underscorejs, but we avoid a dependency
|
||||
*/
|
||||
var slice = Array.prototype.slice;
|
||||
var nativeBind = Function.prototype.bind;
|
||||
var ctor = function(){};
|
||||
function isFunction(obj) {
|
||||
return Object.prototype.toString.call(obj) == '[object Function]';
|
||||
}
|
||||
function bind(func, context) {
|
||||
var args, bound;
|
||||
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
if (!isFunction(func)) throw new TypeError;
|
||||
args = slice.call(arguments, 2);
|
||||
return bound = function() {
|
||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
||||
ctor.prototype = func.prototype;
|
||||
var self = new ctor;
|
||||
ctor.prototype = null;
|
||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
||||
if (Object(result) === result) return result;
|
||||
return self;
|
||||
};
|
||||
}
|
82
packages/wekan-cfs-data-man/server/data-man-buffer.js
Normal file
82
packages/wekan-cfs-data-man/server/data-man-buffer.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
var bufferStreamReader = Npm.require('buffer-stream-reader');
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {Buffer} buffer
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.Buffer = function DataManBuffer(buffer, type) {
|
||||
var self = this;
|
||||
self.buffer = buffer;
|
||||
self._type = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) {
|
||||
callback(null, this.buffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data in the buffer to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) {
|
||||
var self = this;
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + self.buffer.toString("base64");
|
||||
callback(null, dataUri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() {
|
||||
return new bufferStreamReader(this.buffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data in the buffer to a callback.
|
||||
*/
|
||||
DataMan.Buffer.prototype.size = function dataManBufferSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
self._size = self.buffer.length;
|
||||
callback(null, self._size);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.Buffer.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.Buffer.prototype.type = function dataManBufferType() {
|
||||
return this._type;
|
||||
};
|
14
packages/wekan-cfs-data-man/server/data-man-datauri.js
Normal file
14
packages/wekan-cfs-data-man/server/data-man-datauri.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* @method DataMan.DataURI
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} dataUri
|
||||
*/
|
||||
DataMan.DataURI = function DataManDataURI(dataUri) {
|
||||
var self = this;
|
||||
var pieces = dataUri.match(/^data:(.*);base64,(.*)$/);
|
||||
var buffer = new Buffer(pieces[2], 'base64');
|
||||
return new DataMan.Buffer(buffer, pieces[1]);
|
||||
};
|
||||
|
||||
DataMan.DataURI.prototype = DataMan.Buffer.prototype;
|
108
packages/wekan-cfs-data-man/server/data-man-filepath.js
Normal file
108
packages/wekan-cfs-data-man/server/data-man-filepath.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
var mime = Npm.require('mime');
|
||||
var fs = Npm.require("fs");
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} filepath
|
||||
* @param {String} [type] The data content (MIME) type. Will lookup from file if not passed.
|
||||
*/
|
||||
DataMan.FilePath = function DataManFilePath(filepath, type) {
|
||||
var self = this;
|
||||
self.filepath = filepath;
|
||||
self._type = type || mime.lookup(filepath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) {
|
||||
var self = this;
|
||||
|
||||
// Call node readFile
|
||||
fs.readFile(self.filepath, Meteor.bindEnvironment(function(err, buffer) {
|
||||
callback(err, buffer);
|
||||
}, function(err) {
|
||||
callback(err);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) {
|
||||
var self = this;
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64");
|
||||
buffer = null;
|
||||
callback(null, dataUri);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() {
|
||||
// Stream from filesystem
|
||||
return fs.createReadStream(this.filepath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data to a callback.
|
||||
*/
|
||||
DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
// We can get the size without buffering
|
||||
fs.stat(self.filepath, Meteor.bindEnvironment(function (error, stats) {
|
||||
if (stats && typeof stats.size === "number") {
|
||||
self._size = stats.size;
|
||||
callback(null, self._size);
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}, function (error) {
|
||||
callback(error);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.FilePath.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.FilePath.prototype.type = function dataManFilePathType() {
|
||||
return this._type;
|
||||
};
|
80
packages/wekan-cfs-data-man/server/data-man-readstream.js
Normal file
80
packages/wekan-cfs-data-man/server/data-man-readstream.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
/* global DataMan */
|
||||
|
||||
var PassThrough = Npm.require('stream').PassThrough;
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {ReadStream} stream
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.ReadStream = function DataManBuffer(stream, type) {
|
||||
var self = this;
|
||||
|
||||
// Create a bufferable / paused new stream...
|
||||
var pt = new PassThrough();
|
||||
|
||||
// Pipe provided read stream into pass-through stream
|
||||
stream.pipe(pt);
|
||||
|
||||
// Set pass-through stream reference
|
||||
self.stream = pt;
|
||||
|
||||
// Set type as provided
|
||||
self._type = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.getBuffer = function dataManReadStreamGetBuffer(/*callback*/) {
|
||||
// TODO implement as passthrough stream?
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data in the stream to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.getDataUri = function dataManReadStreamGetDataUri(/*callback*/) {
|
||||
// TODO implement as passthrough stream?
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.createReadStream = function dataManReadStreamCreateReadStream() {
|
||||
return this.stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Passes the size in bytes of the data in the stream to a callback.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.size = function dataManReadStreamSize(callback) {
|
||||
callback(0); // will determine from stream later
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.ReadStream.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.ReadStream.prototype.type = function dataManReadStreamType() {
|
||||
return this._type;
|
||||
};
|
133
packages/wekan-cfs-data-man/server/data-man-url.js
Normal file
133
packages/wekan-cfs-data-man/server/data-man-url.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
var request = Npm.require("request");
|
||||
|
||||
/**
|
||||
* @method DataMan.URL
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} url
|
||||
* @param {String} type The data content (MIME) type.
|
||||
*/
|
||||
DataMan.URL = function DataManURL(url, type, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
self.url = url;
|
||||
self._type = type;
|
||||
|
||||
// This is some code borrowed from the http package. Hopefully
|
||||
// we can eventually use HTTP pkg directly instead of 'request'
|
||||
// once it supports streams and buffers and such. (`request` takes
|
||||
// and `auth` option, too, but not of the same form as `HTTP`.)
|
||||
if (options.auth) {
|
||||
if (options.auth.indexOf(':') < 0)
|
||||
throw new Error('auth option should be of the form "username:password"');
|
||||
options.headers = options.headers || {};
|
||||
options.headers['Authorization'] = "Basic "+
|
||||
(new Buffer(options.auth, "ascii")).toString("base64");
|
||||
delete options.auth;
|
||||
}
|
||||
|
||||
self.urlOpts = options;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.getBuffer
|
||||
* @private
|
||||
* @param {function} callback callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Passes a Buffer representing the data at the URL to a callback.
|
||||
*/
|
||||
DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) {
|
||||
var self = this;
|
||||
|
||||
request(_.extend({
|
||||
url: self.url,
|
||||
method: "GET",
|
||||
encoding: null,
|
||||
jar: false
|
||||
}, self.urlOpts), Meteor.bindEnvironment(function(err, res, body) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._type = res.headers['content-type'];
|
||||
callback(null, body);
|
||||
}
|
||||
}, function(err) {
|
||||
callback(err);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.getDataUri
|
||||
* @private
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*
|
||||
* Passes a data URI representing the data at the URL to a callback.
|
||||
*/
|
||||
DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) {
|
||||
var self = this;
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (!self._type) {
|
||||
callback(new Error("DataMan.getDataUri couldn't get a contentType"));
|
||||
} else {
|
||||
var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64");
|
||||
callback(null, dataUri);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.createReadStream
|
||||
* @private
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() {
|
||||
var self = this;
|
||||
// Stream from URL
|
||||
return request(_.extend({
|
||||
url: self.url,
|
||||
method: "GET"
|
||||
}, self.urlOpts));
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.size
|
||||
* @param {function} callback callback(err, size)
|
||||
* @private
|
||||
*
|
||||
* Returns the size in bytes of the data at the URL.
|
||||
*/
|
||||
DataMan.URL.prototype.size = function dataManUrlSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBuffer(function (error, buffer) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
self._size = buffer.length;
|
||||
callback(null, self._size);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.URL.prototype.type
|
||||
* @private
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.URL.prototype.type = function dataManUrlType() {
|
||||
return this._type;
|
||||
};
|
296
packages/wekan-cfs-data-man/tests/client-tests.js
Normal file
296
packages/wekan-cfs-data-man/tests/client-tests.js
Normal file
|
@ -0,0 +1,296 @@
|
|||
var blobData;
|
||||
var arrayBufferData;
|
||||
var binaryData;
|
||||
var dataUriData;
|
||||
var urlData;
|
||||
|
||||
// Init with Blob
|
||||
Tinytest.addAsync('cfs-data - client - Init with Blob', function(test, onComplete) {
|
||||
var blob = new Blob(['Hello World'], {type : 'text/plain'});
|
||||
blobData = new DataMan(blob);
|
||||
test.instanceOf(blobData.blob, Blob);
|
||||
test.equal(blobData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with ArrayBuffer
|
||||
Tinytest.addAsync('cfs-data - client - Init with ArrayBuffer', function(test, onComplete) {
|
||||
arrayBufferData = new DataMan(str2ab('Hello World'), "text/plain");
|
||||
// Should be converted upon init to a Blob
|
||||
test.instanceOf(arrayBufferData.blob, Blob);
|
||||
test.equal(arrayBufferData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with Binary
|
||||
Tinytest.addAsync('cfs-data - client - Init with Binary', function(test, onComplete) {
|
||||
binaryData = new DataMan(new Uint8Array(str2ab('Hello World')), "text/plain");
|
||||
// Should be converted upon init to a Blob
|
||||
test.instanceOf(arrayBufferData.blob, Blob);
|
||||
test.equal(binaryData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with data URI string
|
||||
Tinytest.addAsync('cfs-data - client - Init with data URI string', function(test, onComplete) {
|
||||
var dataUri = 'data:text/plain;base64,SGVsbG8gV29ybGQ='; //'Hello World'
|
||||
dataUriData = new DataMan(dataUri);
|
||||
// Should be converted upon init to a Blob
|
||||
test.instanceOf(dataUriData.blob, Blob);
|
||||
test.equal(dataUriData.type(), "text/plain"); //should be extracted from data URI
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with URL string
|
||||
Tinytest.addAsync('cfs-data - client - Init with URL string', function(test, onComplete) {
|
||||
urlData = new DataMan(Meteor.absoluteUrl('test'), "text/plain"); //'Hello World'
|
||||
// URLs are not converted to Blobs upon init
|
||||
test.equal(urlData.url, Meteor.absoluteUrl('test'));
|
||||
test.equal(urlData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// getBlob
|
||||
Tinytest.addAsync('cfs-data - client - getBlob', function(test, onComplete) {
|
||||
var total = 10, done = 0;
|
||||
function continueIfDone() {
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
function testBlob(error, blob, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.instanceOf(blob, Blob, testType + ' got no blob');
|
||||
|
||||
if (blob instanceof Blob) {
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener("load", function(event) {
|
||||
test.equal(reader.result, 'Hello World', testType + ' got back blob with incorrect data');
|
||||
continueIfDone();
|
||||
}, false);
|
||||
reader.addEventListener("error", function(err) {
|
||||
test.equal(reader.error, null, testType + ' error reading blob as text');
|
||||
continueIfDone();
|
||||
}, false);
|
||||
reader.readAsText(blob, 'utf-8');
|
||||
} else {
|
||||
continueIfDone();
|
||||
}
|
||||
}
|
||||
|
||||
// from Blob
|
||||
blobData.getBlob(function (error, blob) {
|
||||
testBlob(error, blob, 'getBlob from Blob');
|
||||
});
|
||||
|
||||
// from Blob (no callback)
|
||||
testBlob(false, blobData.getBlob(), 'getBlob from Blob');
|
||||
|
||||
// from ArrayBuffer
|
||||
arrayBufferData.getBlob(function (error, blob) {
|
||||
testBlob(error, blob, 'getBlob from ArrayBuffer');
|
||||
});
|
||||
|
||||
// from ArrayBuffer (no callback)
|
||||
testBlob(false, arrayBufferData.getBlob(), 'getBlob from ArrayBuffer');
|
||||
|
||||
// from binary
|
||||
binaryData.getBlob(function (error, blob) {
|
||||
testBlob(error, blob, 'getBlob from binary');
|
||||
});
|
||||
|
||||
// from binary (no callback)
|
||||
testBlob(false, binaryData.getBlob(), 'getBlob from binary');
|
||||
|
||||
// from data URI
|
||||
dataUriData.getBlob(function (error, blob) {
|
||||
testBlob(error, blob, 'getBlob from data URI');
|
||||
});
|
||||
|
||||
// from data URI (no callback)
|
||||
testBlob(false, dataUriData.getBlob(), 'getBlob from data URI');
|
||||
|
||||
// from URL
|
||||
urlData.getBlob(function (error, blob) {
|
||||
testBlob(error, blob, 'getBlob from URL');
|
||||
});
|
||||
|
||||
// from URL (no callback)
|
||||
test.throws(function () {
|
||||
// callback is required for URLs on the client
|
||||
urlData.getBlob();
|
||||
});
|
||||
continueIfDone();
|
||||
|
||||
});
|
||||
|
||||
// getBinary
|
||||
Tinytest.addAsync('cfs-data - client - getBinary', function(test, onComplete) {
|
||||
var total = 5, done = 0;
|
||||
function continueIfDone() {
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
function testBinary(error, binary, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.isTrue(EJSON.isBinary(binary), testType + ' got no binary');
|
||||
|
||||
if (EJSON.isBinary(binary)) {
|
||||
test.equal(bin2str(binary), 'Hello World', testType + ' got back binary with incorrect data');
|
||||
continueIfDone();
|
||||
} else {
|
||||
continueIfDone();
|
||||
}
|
||||
}
|
||||
|
||||
// from Blob
|
||||
blobData.getBinary(function (error, binary) {
|
||||
testBinary(error, binary, 'getBinary from Blob');
|
||||
});
|
||||
|
||||
// from ArrayBuffer
|
||||
arrayBufferData.getBinary(function (error, binary) {
|
||||
testBinary(error, binary, 'getBinary from ArrayBuffer');
|
||||
});
|
||||
|
||||
// from binary
|
||||
binaryData.getBinary(function (error, binary) {
|
||||
testBinary(error, binary, 'getBinary from binary');
|
||||
});
|
||||
|
||||
// from data URI
|
||||
dataUriData.getBinary(function (error, binary) {
|
||||
testBinary(error, binary, 'getBinary from data URI');
|
||||
});
|
||||
|
||||
// from URL
|
||||
urlData.getBinary(function (error, binary) {
|
||||
testBinary(error, binary, 'getBinary from URL');
|
||||
});
|
||||
});
|
||||
|
||||
// getDataUri
|
||||
Tinytest.addAsync('cfs-data - client - getDataUri', function(test, onComplete) {
|
||||
var total = 5, done = 0;
|
||||
function testURI(error, uri, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.equal(typeof uri, "string", testType + ' got no URI string');
|
||||
test.equal(uri, 'data:text/plain;base64,SGVsbG8gV29ybGQ=', testType + ' got invalid URI');
|
||||
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// from Blob
|
||||
blobData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from Blob');
|
||||
});
|
||||
|
||||
// from ArrayBuffer
|
||||
arrayBufferData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from ArrayBuffer');
|
||||
});
|
||||
|
||||
// from binary
|
||||
binaryData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from binary');
|
||||
});
|
||||
|
||||
// from data URI
|
||||
dataUriData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from data URI');
|
||||
});
|
||||
|
||||
// from URL
|
||||
urlData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from URL');
|
||||
});
|
||||
});
|
||||
|
||||
// size
|
||||
Tinytest.addAsync('cfs-data - client - size', function(test, onComplete) {
|
||||
var total = 10, done = 0;
|
||||
function continueIfDone() {
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
function testSize(error, size, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.equal(size, 11, testType + ' got wrong size');
|
||||
continueIfDone();
|
||||
}
|
||||
|
||||
// from Blob
|
||||
blobData.size(function (error, size) {
|
||||
testSize(error, size, 'size from Blob');
|
||||
});
|
||||
|
||||
// from Blob (no callback)
|
||||
testSize(false, blobData.size(), 'size from Blob');
|
||||
|
||||
// from ArrayBuffer
|
||||
arrayBufferData.size(function (error, size) {
|
||||
testSize(error, size, 'size from ArrayBuffer');
|
||||
});
|
||||
|
||||
// from ArrayBuffer (no callback)
|
||||
testSize(false, arrayBufferData.size(), 'size from ArrayBuffer');
|
||||
|
||||
// from binary
|
||||
binaryData.size(function (error, size) {
|
||||
testSize(error, size, 'size from binary');
|
||||
});
|
||||
|
||||
// from binary (no callback)
|
||||
testSize(false, binaryData.size(), 'size from binary');
|
||||
|
||||
// from data URI
|
||||
dataUriData.size(function (error, size) {
|
||||
testSize(error, size, 'size from data URI');
|
||||
});
|
||||
|
||||
// from data URI (no callback)
|
||||
testSize(false, dataUriData.size(), 'size from data URI');
|
||||
|
||||
// from URL
|
||||
urlData.size(function (error, size) {
|
||||
testSize(error, size, 'size from URL');
|
||||
});
|
||||
|
||||
// from URL (no callback)
|
||||
test.throws(function () {
|
||||
// callback is required for URLs on the client
|
||||
urlData.size();
|
||||
});
|
||||
continueIfDone();
|
||||
});
|
||||
|
||||
//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)
|
38
packages/wekan-cfs-data-man/tests/common.js
Normal file
38
packages/wekan-cfs-data-man/tests/common.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
// ab2str = function ab2str(buf) {
|
||||
// return String.fromCharCode(new Uint8Array(buf));
|
||||
// }
|
||||
|
||||
bin2str = function bin2str(bufView) {
|
||||
var length = bufView.length;
|
||||
var result = '';
|
||||
for (var i = 0; i<length; i+=65535) {
|
||||
var addition = 65535;
|
||||
if(i + 65535 > length) {
|
||||
addition = length - i;
|
||||
}
|
||||
try {
|
||||
// this fails on phantomjs due to old webkit bug; hence the try/catch
|
||||
result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
|
||||
} catch (e) {
|
||||
var dataArray = [];
|
||||
for (var j = i; j < i+addition; j++) {
|
||||
dataArray.push(bufView[j]);
|
||||
}
|
||||
result += String.fromCharCode.apply(null, dataArray);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
ab2str = function ab2str(buffer) {
|
||||
return bin2str(new Uint8Array(buffer));
|
||||
};
|
||||
|
||||
str2ab = function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
};
|
366
packages/wekan-cfs-data-man/tests/server-tests.js
Normal file
366
packages/wekan-cfs-data-man/tests/server-tests.js
Normal file
|
@ -0,0 +1,366 @@
|
|||
var fs = Npm.require('fs');
|
||||
var temp = Npm.require('temp');
|
||||
|
||||
// Automatically track and cleanup files at exit
|
||||
temp.track();
|
||||
|
||||
// Set up HTTP method URL used by client tests
|
||||
HTTP.methods({
|
||||
'test': {
|
||||
get: function () {
|
||||
var buf = new Buffer('Hello World');
|
||||
this.setContentType('text/plain');
|
||||
return buf;
|
||||
},
|
||||
head: function () {
|
||||
var buf = new Buffer('Hello World');
|
||||
this.setContentType('text/plain');
|
||||
this.addHeader('Content-Length', buf.length);
|
||||
buf = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Save temp file for testing with
|
||||
function openTempFile(name, callback) {
|
||||
return temp.open(name, callback);
|
||||
}
|
||||
var openTempFileSync = Meteor.wrapAsync(openTempFile);
|
||||
|
||||
var info = openTempFileSync(null);
|
||||
var tempFilePath = info.path;
|
||||
fs.writeSync(info.fd, 'Hello World');
|
||||
fs.closeSync(info.fd);
|
||||
|
||||
var bufferData;
|
||||
var arrayBufferData;
|
||||
var binaryData;
|
||||
var dataUriData;
|
||||
var urlData;
|
||||
var filePathData;
|
||||
var streamData;
|
||||
|
||||
// Init with Buffer
|
||||
Tinytest.addAsync('cfs-data - server - Init with Buffer', function(test, onComplete) {
|
||||
bufferData = new DataMan(new Buffer('Hello World'), "text/plain");
|
||||
test.instanceOf(bufferData.source, DataMan.Buffer);
|
||||
test.equal(bufferData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with ArrayBuffer
|
||||
Tinytest.addAsync('cfs-data - server - Init with ArrayBuffer', function(test, onComplete) {
|
||||
arrayBufferData = new DataMan(str2ab('Hello World'), "text/plain");
|
||||
// Should be converted upon init to a Buffer
|
||||
test.instanceOf(arrayBufferData.source, DataMan.Buffer);
|
||||
test.equal(arrayBufferData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with Binary
|
||||
Tinytest.addAsync('cfs-data - server - Init with Binary', function(test, onComplete) {
|
||||
binaryData = new DataMan(new Uint8Array(str2ab('Hello World')), "text/plain");
|
||||
// Should be converted upon init to a Buffer
|
||||
test.instanceOf(arrayBufferData.source, DataMan.Buffer);
|
||||
test.equal(binaryData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with data URI string
|
||||
Tinytest.addAsync('cfs-data - server - Init with data URI string', function(test, onComplete) {
|
||||
var dataUri = 'data:text/plain;base64,SGVsbG8gV29ybGQ='; //'Hello World'
|
||||
dataUriData = new DataMan(dataUri);
|
||||
// Data URIs are not converted to Buffers upon init
|
||||
test.instanceOf(dataUriData.source, DataMan.DataURI);
|
||||
test.equal(dataUriData.type(), "text/plain"); //should be extracted from data URI
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with URL string
|
||||
Tinytest.addAsync('cfs-data - server - Init with URL string', function(test, onComplete) {
|
||||
var url = Meteor.absoluteUrl('test');
|
||||
urlData = new DataMan(url, "text/plain"); //'Hello World'
|
||||
// URLs are not converted to Buffers upon init
|
||||
test.instanceOf(urlData.source, DataMan.URL);
|
||||
test.equal(urlData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with filepath string
|
||||
Tinytest.addAsync('cfs-data - server - Init with filepath string', function(test, onComplete) {
|
||||
filePathData = new DataMan(tempFilePath, "text/plain");
|
||||
// filepaths are not converted to Buffers upon init
|
||||
test.instanceOf(filePathData.source, DataMan.FilePath);
|
||||
test.equal(filePathData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// Init with readable stream
|
||||
Tinytest.addAsync('cfs-data - server - Init with readable stream', function(test, onComplete) {
|
||||
streamData = new DataMan(fs.createReadStream(tempFilePath), "text/plain");
|
||||
// filepaths are not converted to Buffers upon init
|
||||
test.instanceOf(streamData.source, DataMan.ReadStream);
|
||||
test.equal(streamData.type(), "text/plain");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
// getBuffer
|
||||
Tinytest.addAsync('cfs-data - server - getBuffer', function(test, onComplete) {
|
||||
var total = 12, done = 0;
|
||||
|
||||
function testBuffer(error, buffer, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.instanceOf(buffer, Buffer);
|
||||
|
||||
if (buffer instanceof Buffer) {
|
||||
test.equal(buffer.toString(), 'Hello World', testType + ' got back buffer with incorrect data');
|
||||
}
|
||||
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// from Buffer (async)
|
||||
bufferData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer from Buffer async');
|
||||
});
|
||||
|
||||
// from Buffer (sync)
|
||||
testBuffer(null, bufferData.getBuffer(), 'getBuffer from Buffer sync');
|
||||
|
||||
// from ArrayBuffer (async)
|
||||
arrayBufferData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer from ArrayBuffer async');
|
||||
});
|
||||
|
||||
// from ArrayBuffer (sync)
|
||||
testBuffer(null, arrayBufferData.getBuffer(), 'getBuffer from ArrayBuffer sync');
|
||||
|
||||
// from binary (async)
|
||||
binaryData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer from binary async');
|
||||
});
|
||||
|
||||
// from binary (sync)
|
||||
testBuffer(null, binaryData.getBuffer(), 'getBuffer from binary sync');
|
||||
|
||||
// from data URI (async)
|
||||
dataUriData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer from data URI async');
|
||||
});
|
||||
|
||||
// from data URI (sync)
|
||||
testBuffer(null, dataUriData.getBuffer(), 'getBuffer from data URI sync');
|
||||
|
||||
// from URL (async)
|
||||
urlData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer from URL async');
|
||||
});
|
||||
|
||||
// from URL (sync)
|
||||
testBuffer(null, urlData.getBuffer(), 'getBuffer from URL sync');
|
||||
|
||||
// from filepath (async)
|
||||
filePathData.getBuffer(function (error, buffer) {
|
||||
testBuffer(error, buffer, 'getBuffer filepath async');
|
||||
});
|
||||
|
||||
// from filepath (sync)
|
||||
testBuffer(null, filePathData.getBuffer(), 'getBuffer filepath sync');
|
||||
});
|
||||
|
||||
// getDataUri
|
||||
Tinytest.addAsync('cfs-data - server - getDataUri', function(test, onComplete) {
|
||||
var total = 12, done = 0;
|
||||
function testURI(error, uri, testType) {
|
||||
test.isFalse(!!error, testType + ' got error: ' + (error && error.message));
|
||||
test.equal(typeof uri, "string", testType + ' got no URI string');
|
||||
test.equal(uri, 'data:text/plain;base64,SGVsbG8gV29ybGQ=', testType + ' got invalid URI');
|
||||
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// from Buffer (async)
|
||||
bufferData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from Buffer async');
|
||||
});
|
||||
|
||||
// from Buffer (sync)
|
||||
testURI(null, bufferData.getDataUri(), 'getDataUri from Buffer sync');
|
||||
|
||||
// from ArrayBuffer (async)
|
||||
arrayBufferData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from ArrayBuffer async');
|
||||
});
|
||||
|
||||
// from ArrayBuffer (sync)
|
||||
testURI(null, arrayBufferData.getDataUri(), 'getDataUri from ArrayBuffer sync');
|
||||
|
||||
// from binary (async)
|
||||
binaryData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from binary async');
|
||||
});
|
||||
|
||||
// from binary (sync)
|
||||
testURI(null, binaryData.getDataUri(), 'getDataUri from binary sync');
|
||||
|
||||
// from data URI (async)
|
||||
dataUriData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from data URI async');
|
||||
});
|
||||
|
||||
// from data URI (sync)
|
||||
testURI(null, dataUriData.getDataUri(), 'getDataUri from data URI sync');
|
||||
|
||||
// from URL (async)
|
||||
urlData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri from URL async');
|
||||
});
|
||||
|
||||
// from URL (sync)
|
||||
testURI(null, urlData.getDataUri(), 'getDataUri from URL sync');
|
||||
|
||||
// from filepath (async)
|
||||
filePathData.getDataUri(function (error, uri) {
|
||||
testURI(error, uri, 'getDataUri filepath async');
|
||||
});
|
||||
|
||||
// from filepath (sync)
|
||||
testURI(null, filePathData.getDataUri(), 'getDataUri filepath sync');
|
||||
});
|
||||
|
||||
// size
|
||||
Tinytest.addAsync('cfs-data - server - size', function(test, onComplete) {
|
||||
var total = 6, done = 0;
|
||||
function testSize(size, testType) {
|
||||
test.equal(size, 11, testType + ' got wrong size');
|
||||
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// from Buffer
|
||||
testSize(bufferData.size(), 'size from Buffer');
|
||||
|
||||
// from ArrayBuffer
|
||||
testSize(arrayBufferData.size(), 'size from ArrayBuffer');
|
||||
|
||||
// from binary
|
||||
testSize(binaryData.size(), 'size from binary');
|
||||
|
||||
// from data URI
|
||||
testSize(dataUriData.size(), 'size from data URI');
|
||||
|
||||
// from URL
|
||||
testSize(urlData.size(), 'size from URL');
|
||||
|
||||
// from filepath
|
||||
testSize(filePathData.size(), 'size from filepath');
|
||||
});
|
||||
|
||||
// saveToFile
|
||||
// Since saveToFile uses createReadStream, this tests that function also
|
||||
Tinytest.addAsync('cfs-data - server - saveToFile', function(test, onComplete) {
|
||||
var total = 12, done = 0;
|
||||
function testSave(dataInstance) {
|
||||
var tempName = temp.path({suffix: '.txt'});
|
||||
dataInstance.saveToFile(tempName, function (error) {
|
||||
test.isFalse(!!error);
|
||||
test.equal(fs.readFileSync(tempName, {encoding: 'utf8'}), 'Hello World', 'file was not saved with correct data');
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testSaveSync(dataInstance) {
|
||||
var tempName = temp.path({suffix: '.txt'});
|
||||
dataInstance.saveToFile(tempName);
|
||||
test.equal(fs.readFileSync(tempName, {encoding: 'utf8'}), 'Hello World', 'file was not saved with correct data');
|
||||
done++;
|
||||
if (total === done) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// from Buffer
|
||||
testSave(bufferData);
|
||||
testSaveSync(bufferData);
|
||||
|
||||
// from ArrayBuffer
|
||||
testSave(arrayBufferData);
|
||||
testSaveSync(arrayBufferData);
|
||||
|
||||
// from binary
|
||||
testSave(binaryData);
|
||||
testSaveSync(binaryData);
|
||||
|
||||
// from data URI
|
||||
testSave(dataUriData);
|
||||
testSaveSync(dataUriData);
|
||||
|
||||
// from URL
|
||||
testSave(urlData);
|
||||
testSaveSync(urlData);
|
||||
|
||||
// from filepath
|
||||
testSave(filePathData);
|
||||
testSaveSync(filePathData);
|
||||
});
|
||||
|
||||
// Ensure that URL createReadStream can be piped after delay
|
||||
// https://github.com/mikeal/request/issues/887
|
||||
Tinytest.addAsync('cfs-data - server - createReadStream delay', function(test, onComplete) {
|
||||
var readStream = urlData.createReadStream();
|
||||
|
||||
// wait for 5 seconds, then pipe
|
||||
Meteor.setTimeout(function() {
|
||||
var tempName = temp.path({suffix: '.txt'});
|
||||
|
||||
try {
|
||||
var writeStream = readStream.pipe(fs.createWriteStream(tempName));
|
||||
|
||||
writeStream.on('finish', Meteor.bindEnvironment(function() {
|
||||
test.equal(fs.readFileSync(tempName, {encoding: 'utf8'}), 'Hello World', 'file was not saved with correct data');
|
||||
onComplete();
|
||||
}));
|
||||
|
||||
writeStream.on('error', Meteor.bindEnvironment(function(err) {
|
||||
test.isFalse(!!err);
|
||||
}));
|
||||
} catch (err) {
|
||||
test.isFalse(!!err);
|
||||
onComplete();
|
||||
}
|
||||
|
||||
}, 5000);
|
||||
|
||||
});
|
||||
|
||||
//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)
|
5
packages/wekan-cfs-file/.travis.yml
Normal file
5
packages/wekan-cfs-file/.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"
|
283
packages/wekan-cfs-file/CHANGELOG.md
Normal file
283
packages/wekan-cfs-file/CHANGELOG.md
Normal file
|
@ -0,0 +1,283 @@
|
|||
# Changelog
|
||||
|
||||
## vCurrent
|
||||
## [v0.1.14] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.14)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-file
|
||||
|
||||
- update pkg, dependencies, delinting, etc.
|
||||
|
||||
## [v0.1.13] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.13)
|
||||
#### 05/09/14 by Eric Dobbertin
|
||||
- 0.9.1 support
|
||||
|
||||
## [v0.1.12] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.12)
|
||||
#### 28/08/14 by Morten Henriksen
|
||||
- Meteor Package System Update
|
||||
|
||||
## [v0.1.11] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.11)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
- change package name to lowercase
|
||||
|
||||
## [v0.1.10] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.10)
|
||||
#### 31/07/14 by Eric Dobbertin
|
||||
- allow FS.File to emit events
|
||||
|
||||
## [v0.1.9] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.9)
|
||||
#### 30/07/14 by Eric Dobbertin
|
||||
- add fileObj.copy()
|
||||
|
||||
## [v0.1.8] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.8)
|
||||
#### 14/07/14 by Eric Dobbertin
|
||||
- make setters save to DB automatically with ability to disable
|
||||
|
||||
## [v0.1.7] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.7)
|
||||
#### 30/06/14 by Eric Dobbertin
|
||||
- *Fixed bug:* "fsFile-common.js:66:8: Unexpected identifier" [#3](https://github.com/zcfs/Meteor-cfs-file/issues/3)
|
||||
|
||||
## [v0.1.6] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.6)
|
||||
#### 30/06/14 by Eric Dobbertin
|
||||
- a few more changes for supporting URL options
|
||||
|
||||
- support request options like `auth` and `headers` when attaching a URL
|
||||
|
||||
## [v0.1.5] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.5)
|
||||
#### 20/05/14 by Eric Dobbertin
|
||||
- add almost all necessary tests!
|
||||
|
||||
- fix updatedAt setting
|
||||
|
||||
- correct logic
|
||||
|
||||
- store size as number
|
||||
|
||||
- API doc fixes
|
||||
|
||||
## [v0.1.4] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.4)
|
||||
#### 11/05/14 by Eric Dobbertin
|
||||
- document API for createReadStream and createWriteStream
|
||||
|
||||
## [v0.1.3] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.3)
|
||||
#### 06/05/14 by Eric Dobbertin
|
||||
- missed this during api change
|
||||
|
||||
## [v0.1.2] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.2)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- updating from DB before getting should be opt-in instead of opt-out; it's more efficient this way, and it prevents issues with local props unintentionally being overwritten
|
||||
|
||||
## [v0.1.1] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.1)
|
||||
#### 29/04/14 by Eric Dobbertin
|
||||
- add `extension` getter/setter method and make the other new methods work as UI helpers
|
||||
|
||||
## [v0.1.0] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.1.0)
|
||||
#### 29/04/14 by Eric Dobbertin
|
||||
- generate api docs
|
||||
|
||||
- add updatedAt method
|
||||
|
||||
- provide option to skip updating from the file record before getting info
|
||||
|
||||
- more changes for new name, size, and type API
|
||||
|
||||
- add getter/setter methods for name, size, and type; move original file info under `original` property object backwards compatibility break!
|
||||
|
||||
- change name of `hasCopy` function to `hasStored`
|
||||
|
||||
## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.34)
|
||||
#### 18/04/14 by Eric Dobbertin
|
||||
- add formattedSize method and weak dependency on numeral pkg
|
||||
|
||||
## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.33)
|
||||
#### 15/04/14 by Eric Dobbertin
|
||||
- call attachData only if we have some data
|
||||
|
||||
## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.32)
|
||||
#### 08/04/14 by Eric Dobbertin
|
||||
- throw a more helpful error if null/undefined data is passed to attachData
|
||||
|
||||
- remove `get` method; the code in it is old and would fail so I'm assuming nothing calls it anymore
|
||||
|
||||
## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.31)
|
||||
#### 07/04/14 by Eric Dobbertin
|
||||
- allow passing any data (except URL on client) to FS.File constructor to attach it
|
||||
|
||||
## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.30)
|
||||
#### 07/04/14 by Eric Dobbertin
|
||||
- make it safe to call attachData without a callback on the client, unless we are attaching a URL; in that case, an error is thrown if no callback
|
||||
|
||||
## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.29)
|
||||
#### 06/04/14 by Eric Dobbertin
|
||||
- use full clone to fix issue with saving FS.File into a collection using EJSON
|
||||
|
||||
## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.28)
|
||||
#### 06/04/14 by Eric Dobbertin
|
||||
- use uploadedAt so that we can remove chunk info when it's no longer needed
|
||||
|
||||
## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.27)
|
||||
#### 06/04/14 by Eric Dobbertin
|
||||
- improve setName a bit and move some logic to FS.Utility
|
||||
|
||||
## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.26)
|
||||
#### 05/04/14 by Eric Dobbertin
|
||||
- set name when inserting filepath without callback
|
||||
|
||||
## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.25)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- Make sure passing in a storeName works
|
||||
|
||||
## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.24)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- move all data handling to data-man package; fix issue getting size properly
|
||||
|
||||
## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.23)
|
||||
#### 02/04/14 by Morten Henriksen
|
||||
- We should allow size to be updated if org size is 0
|
||||
|
||||
## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.22)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.21)
|
||||
#### 29/03/14 by Morten Henriksen
|
||||
- remove underscore deps
|
||||
|
||||
## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.20)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- refactor utime into updatedAt
|
||||
|
||||
## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.19)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- Rollback to specific git dependency
|
||||
|
||||
- use collectionFS travis version force update
|
||||
|
||||
## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.18)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- try to fix travis test by using general package references
|
||||
|
||||
## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.17)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- remove smart.lock
|
||||
|
||||
- adjustments, trying to make everything work in phantomjs
|
||||
|
||||
- add server tests for FS.Data and fix found issues; add Blob.js polyfill so that we have phantomjs support (and travis tests can pass)
|
||||
|
||||
- ignore potential query string when seeing if URL ends in filename
|
||||
|
||||
## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.16)
|
||||
#### 20/03/14 by Eric Dobbertin
|
||||
- set size when attaching filepath
|
||||
|
||||
- add travis-ci image
|
||||
|
||||
- package changes to fix auto test issues
|
||||
|
||||
- should not have been committed
|
||||
|
||||
- we should ignore packages folder entirely
|
||||
|
||||
- Return self from attachData, and adjust code in other areas to keep the flow of all methods similar
|
||||
|
||||
## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.15)
|
||||
#### 18/03/14 by Eric Dobbertin
|
||||
- See if we can extract a file name from URL or filepath
|
||||
|
||||
- use utility function to get file extension
|
||||
|
||||
## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.14)
|
||||
#### 18/03/14 by Eric Dobbertin
|
||||
- remove code that deleted files from temp store; the worker does this in a remove observe
|
||||
|
||||
## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.13)
|
||||
#### 18/03/14 by Morten Henriksen
|
||||
- Add note about dataUrl references
|
||||
|
||||
- fixed issue #204
|
||||
|
||||
- client-side tests for FS.Data (!) and fix failed tests
|
||||
|
||||
- allow attachData to run synchronous on server even when attaching URL
|
||||
|
||||
- fix some data attachment issues and move file allowed check to FS.Collection
|
||||
|
||||
- silently handle non-object args
|
||||
|
||||
- refactor FS.File.createWriteStream to support TempStore and FileWorker
|
||||
|
||||
- tidy the FS.File.createReadStream
|
||||
|
||||
- update API docs
|
||||
|
||||
- use correct api
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-file
|
||||
|
||||
- Complete rewrite; better split between client/server, move data handling to FS.Data class, and support streams
|
||||
|
||||
- use chunks in the filerecord
|
||||
|
||||
- make sure we have a downloadqueue
|
||||
|
||||
## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.12)
|
||||
#### 07/03/14 by Eric Dobbertin
|
||||
- should not require a filename; check extension only if we have one
|
||||
|
||||
- allow `FS.File.fromUrl` to run synchronously without a callback on the server
|
||||
|
||||
## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.11)
|
||||
#### 03/03/14 by Eric Dobbertin
|
||||
- add `format` option for FS.File.prototype.get
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-file
|
||||
|
||||
## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.10)
|
||||
#### 01/03/14 by Eric Dobbertin
|
||||
- add pkg necessary for tests
|
||||
|
||||
## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.9)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- changes for http uploads
|
||||
|
||||
## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.8)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- fix issues with callbacks
|
||||
|
||||
## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.7)
|
||||
#### 21/02/14 by Eric Dobbertin
|
||||
- new URL syntax; use the store's file key instead of ID
|
||||
|
||||
## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.6)
|
||||
#### 17/02/14 by Morten Henriksen
|
||||
- add getCopyInfo method
|
||||
|
||||
## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.5)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
- added public
|
||||
|
||||
## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.4)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
- fix url
|
||||
|
||||
- use generic http url
|
||||
|
||||
## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.3)
|
||||
#### 15/02/14 by Morten Henriksen
|
||||
- reference cfs-filesaver pkg instead
|
||||
|
||||
- rework http/ddp method init; also DDP methods don't need to be per-collection so they no longer are
|
||||
|
||||
- minor adjustments; add missing FileSaver.js
|
||||
|
||||
## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.2)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
- Fixed getter of storage adapters
|
||||
|
||||
## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-file/tree/v0.0.1)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
- Corrections to new scope
|
||||
|
||||
- init commit
|
||||
|
20
packages/wekan-cfs-file/LICENSE.md
Normal file
20
packages/wekan-cfs-file/LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com, and [@aldeed](https://github.com/aldeed)
|
||||
|
||||
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.
|
8
packages/wekan-cfs-file/README.md
Normal file
8
packages/wekan-cfs-file/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-file [](https://travis-ci.org/CollectionFS/Meteor-cfs-file)
|
||||
=========================
|
||||
|
||||
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 added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
588
packages/wekan-cfs-file/api.md
Normal file
588
packages/wekan-cfs-file/api.md
Normal file
|
@ -0,0 +1,588 @@
|
|||
## wekan-cfs-file Public API ##
|
||||
|
||||
CollectionFS, FS.File object
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File"></a>new *fs*.File([ref]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __File__ is defined in `FS`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __ref__ *{object|[FS.File](#FS.File)|[data to attach](#data to attach)}* (Optional)
|
||||
|
||||
Another FS.File instance, a filerecord, or some data to pass to attachData
|
||||
|
||||
|
||||
|
||||
> ```FS.File = function(ref, createdByTransform) { ...``` [fsFile-common.js:8](fsFile-common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.attachData"></a>*fsFile*.attachData(data, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __attachData__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to attach to the file.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
|
||||
Options
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known.
|
||||
|
||||
* __headers__ *{String}* (Optional)
|
||||
|
||||
When attaching a URL, headers to be used for the GET request (currently server only)
|
||||
|
||||
* __auth__ *{String}* (Optional)
|
||||
|
||||
When attaching a URL, "username:password" to be used for the GET request (currently server only)
|
||||
|
||||
* __callback__ *{Function}* (Optional)
|
||||
|
||||
Callback function, callback(error). On the client, a callback is required if data is a URL.
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
This FS.File instance.
|
||||
|
||||
|
||||
|
||||
> ```FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) { ...``` [fsFile-common.js:36](fsFile-common.js#L36)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.uploadProgress"></a>*fsFile*.uploadProgress() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __uploadProgress__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{number}*
|
||||
The server confirmed upload progress
|
||||
|
||||
|
||||
> ```FS.File.prototype.uploadProgress = function() { ...``` [fsFile-common.js:154](fsFile-common.js#L154)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.controlledByDeps"></a>*fsFile*.controlledByDeps() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __controlledByDeps__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns true if this FS.File is reactive
|
||||
|
||||
|
||||
> Note: Returns true if this FS.File object was created by a FS.Collection
|
||||
> and we are in a reactive computations. What does this mean? Well it should
|
||||
> mean that our fileRecord is fully updated by Meteor and we are mounted on
|
||||
> a collection
|
||||
|
||||
> ```FS.File.prototype.controlledByDeps = function() { ...``` [fsFile-common.js:179](fsFile-common.js#L179)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getCollection"></a>*fsFile*.getCollection() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getCollection__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns attached collection or undefined if not mounted
|
||||
|
||||
|
||||
> ```FS.File.prototype.getCollection = function() { ...``` [fsFile-common.js:189](fsFile-common.js#L189)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isMounted"></a>*fsFile*.isMounted() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isMounted__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns attached collection or undefined if not mounted
|
||||
|
||||
|
||||
> ```FS.File.prototype.isMounted = FS.File.prototype.getCollection;``` [fsFile-common.js:217](fsFile-common.js#L217)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getFileRecord"></a>*fsFile*.getFileRecord() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileRecord__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{object}*
|
||||
The filerecord
|
||||
|
||||
|
||||
> ```FS.File.prototype.getFileRecord = function() { ...``` [fsFile-common.js:224](fsFile-common.js#L224)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.update"></a>*fsFile*.update(modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __modifier__ *{[modifier](#modifier)}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
|
||||
Updates the fileRecord.
|
||||
|
||||
> ```FS.File.prototype.update = function(modifier, options, callback) { ...``` [fsFile-common.js:255](fsFile-common.js#L255)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.remove"></a>*fsFile*.remove([callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}* (Optional)
|
||||
|
||||
__Returns__ *{number}*
|
||||
Count
|
||||
|
||||
|
||||
Remove the current file from its FS.Collection
|
||||
|
||||
> ```FS.File.prototype.remove = function(callback) { ...``` [fsFile-common.js:323](fsFile-common.js#L323)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getExtension"></a>*fsFile*.getExtension([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
> __Warning!__
|
||||
> This method "FS.File.prototype.getExtension" has deprecated from the API
|
||||
> Use the `extension` getter/setter method instead.
|
||||
|
||||
*This method __getExtension__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Store name. Default is the original extension.
|
||||
|
||||
|
||||
__Returns__ *{string}*
|
||||
The extension eg.: `jpg` or if not found then an empty string ''
|
||||
|
||||
|
||||
> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isImage"></a>*fsFile*.isImage([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isImage__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has an image
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isImage = function(options) { ...``` [fsFile-common.js:393](fsFile-common.js#L393)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isVideo"></a>*fsFile*.isVideo([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isVideo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has a video
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isVideo = function(options) { ...``` [fsFile-common.js:408](fsFile-common.js#L408)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isAudio"></a>*fsFile*.isAudio([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isAudio__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has an audio
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isAudio = function(options) { ...``` [fsFile-common.js:423](fsFile-common.js#L423)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.formattedSize"></a>*fsFile*.formattedSize({Object}, {String}, {String}) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __formattedSize__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __{Object}__ *{any}*
|
||||
|
||||
options
|
||||
|
||||
* __{String}__ *{any}*
|
||||
|
||||
[options.store=none,display original file size] Which file do you want to get the size of?
|
||||
|
||||
* __{String}__ *{any}*
|
||||
|
||||
[options.formatString='0.00 b'] The `numeral` format string to use.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The file size formatted as a human readable string and reactively updated.
|
||||
|
||||
|
||||
You must add the `numeral` package to your app before you can use this method.
|
||||
If info is not found or a size can't be determined, it will show 0.
|
||||
|
||||
> ```FS.File.prototype.formattedSize = function fsFileFormattedSize(options) { ...``` [fsFile-common.js:438](fsFile-common.js#L438)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isUploaded"></a>*fsFile*.isUploaded() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isUploaded__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the number of uploaded bytes is equal to the file size.
|
||||
|
||||
|
||||
> ```FS.File.prototype.isUploaded = function() { ...``` [fsFile-common.js:456](fsFile-common.js#L456)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.hasStored"></a>*fsFile*.hasStored(storeName, [optimistic]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __hasStored__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{string}*
|
||||
|
||||
Name of the store
|
||||
|
||||
* __optimistic__ *{boolean}* (Optional, Default = false)
|
||||
|
||||
In case that the file record is not found, read below
|
||||
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
Is a version of this file stored in the given store?
|
||||
|
||||
|
||||
> Note: If the file is not published to the client or simply not found:
|
||||
this method cannot know for sure if it exists or not. The `optimistic`
|
||||
param is the boolean value to return. Are we `optimistic` that the copy
|
||||
could exist. This is the case in `FS.File.url` we are optimistic that the
|
||||
copy supplied by the user exists.
|
||||
|
||||
> ```FS.File.prototype.hasStored = function(storeName, optimistic) { ...``` [fsFile-common.js:478](fsFile-common.js#L478)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getCopyInfo"></a>*fsFile*.getCopyInfo(storeName) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
> __Warning!__
|
||||
> This method "FS.File.prototype.getCopyInfo" has deprecated from the API
|
||||
> Use individual methods with `store` option instead.
|
||||
|
||||
*This method __getCopyInfo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{string}*
|
||||
|
||||
Name of the store for which to get copy info.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
The file details, e.g., name, size, key, etc., specific to the copy saved in this store.
|
||||
|
||||
|
||||
> ```FS.File.prototype.getCopyInfo = function(storeName) { ...``` [fsFile-common.js:504](fsFile-common.js#L504)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.name"></a>*fsFile*.name([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __name__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String|null}* (Optional)
|
||||
|
||||
If setting the name, specify the new name as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the name of the version of the file that was saved in this store. Default is the original file name.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file name.
|
||||
|
||||
|
||||
> ```FS.File.prototype.name = function(value, options) { ...``` [fsFile-common.js:568](fsFile-common.js#L568)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.extension"></a>*fsFile*.extension([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __extension__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String|null}* (Optional)
|
||||
|
||||
If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the extension of the version of the file that was saved in this store. Default is the original file extension.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one.
|
||||
|
||||
|
||||
> ```FS.File.prototype.extension = function(value, options) { ...``` [fsFile-common.js:593](fsFile-common.js#L593)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.size"></a>*fsFile*.size([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{Number}* (Optional)
|
||||
|
||||
If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the size of the version of the file that was saved in this store. Default is the original file size.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{Number|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file size.
|
||||
|
||||
|
||||
> ```FS.File.prototype.size = function(value, options) { ...``` [fsFile-common.js:618](fsFile-common.js#L618)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.type"></a>*fsFile*.type([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String}* (Optional)
|
||||
|
||||
If setting the type, specify the new type as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the type of the version of the file that was saved in this store. Default is the original file type.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file type.
|
||||
|
||||
|
||||
> ```FS.File.prototype.type = function(value, options) { ...``` [fsFile-common.js:643](fsFile-common.js#L643)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.updatedAt"></a>*fsFile*.updatedAt([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __updatedAt__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String}* (Optional)
|
||||
|
||||
If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file's last updated date.
|
||||
|
||||
|
||||
> ```FS.File.prototype.updatedAt = function(value, options) { ...``` [fsFile-common.js:668](fsFile-common.js#L668)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.createReadStream"></a>*fsFile*.createReadStream([storeName]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{stream.Readable}*
|
||||
Readable NodeJS stream
|
||||
|
||||
|
||||
Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name.
|
||||
|
||||
If you pass a `storeName`, a readable stream for the file data saved in that store is returned.
|
||||
If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned.
|
||||
If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned.
|
||||
|
||||
|
||||
> ```FS.File.prototype.createReadStream = function(storeName) { ...``` [fsFile-server.js:62](fsFile-server.js#L62)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.createWriteStream"></a>*fsFile*.createWriteStream([storeName]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createWriteStream__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{stream.Writeable}*
|
||||
Writeable NodeJS stream
|
||||
|
||||
|
||||
Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name.
|
||||
|
||||
If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned.
|
||||
If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned.
|
||||
|
||||
|
||||
> ```FS.File.prototype.createWriteStream = function(storeName) { ...``` [fsFile-server.js:100](fsFile-server.js#L100)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.copy"></a>*fsFile*.copy() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __copy__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The new FS.File instance
|
||||
|
||||
|
||||
> ```FS.File.prototype.copy = function() { ...``` [fsFile-server.js:126](fsFile-server.js#L126)
|
||||
|
||||
|
765
packages/wekan-cfs-file/fsFile-common.js
Normal file
765
packages/wekan-cfs-file/fsFile-common.js
Normal file
|
@ -0,0 +1,765 @@
|
|||
/**
|
||||
* @method FS.File
|
||||
* @namespace FS.File
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {object|FS.File|data to attach} [ref] Another FS.File instance, a filerecord, or some data to pass to attachData
|
||||
*/
|
||||
FS.File = function(ref, createdByTransform) {
|
||||
var self = this;
|
||||
|
||||
self.createdByTransform = !!createdByTransform;
|
||||
|
||||
if (ref instanceof FS.File || isBasicObject(ref)) {
|
||||
// Extend self with filerecord related data
|
||||
FS.Utility.extend(self, FS.Utility.cloneFileRecord(ref, {full: true}));
|
||||
} else if (ref) {
|
||||
self.attachData(ref);
|
||||
}
|
||||
};
|
||||
|
||||
// An FS.File can emit events
|
||||
FS.File.prototype = new EventEmitter();
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.attachData
|
||||
* @public
|
||||
* @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to attach to the file.
|
||||
* @param {Object} [options] Options
|
||||
* @param {String} [options.type] The data content (MIME) type, if known.
|
||||
* @param {String} [options.headers] When attaching a URL, headers to be used for the GET request (currently server only)
|
||||
* @param {String} [options.auth] When attaching a URL, "username:password" to be used for the GET request (currently server only)
|
||||
* @param {Function} [callback] Callback function, callback(error). On the client, a callback is required if data is a URL.
|
||||
* @returns {FS.File} This FS.File instance.
|
||||
*
|
||||
*/
|
||||
FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
options = options || {};
|
||||
|
||||
if (!data) {
|
||||
throw new Error('FS.File.attachData requires a data argument with some data');
|
||||
}
|
||||
|
||||
var urlOpts;
|
||||
|
||||
// Set any other properties we can determine from the source data
|
||||
// File
|
||||
if (typeof File !== "undefined" && data instanceof File) {
|
||||
self.name(data.name);
|
||||
self.updatedAt(data.lastModifiedDate);
|
||||
self.size(data.size);
|
||||
setData(data.type);
|
||||
}
|
||||
// Blob
|
||||
else if (typeof Blob !== "undefined" && data instanceof Blob) {
|
||||
self.name(data.name);
|
||||
self.updatedAt(new Date());
|
||||
self.size(data.size);
|
||||
setData(data.type);
|
||||
}
|
||||
// URL: we need to do a HEAD request to get the type because type
|
||||
// is required for filtering to work.
|
||||
else if (typeof data === "string" && (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:")) {
|
||||
urlOpts = FS.Utility.extend({}, options);
|
||||
if (urlOpts.type) {
|
||||
delete urlOpts.type;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
if (Meteor.isClient) {
|
||||
throw new Error('FS.File.attachData requires a callback when attaching a URL on the client');
|
||||
}
|
||||
var result = Meteor.call('_cfs_getUrlInfo', data, urlOpts);
|
||||
FS.Utility.extend(self, {original: result});
|
||||
setData(result.type);
|
||||
} else {
|
||||
Meteor.call('_cfs_getUrlInfo', data, urlOpts, function (error, result) {
|
||||
FS.debug && console.log("URL HEAD RESULT:", result);
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
var type = result.type || options.type;
|
||||
if (! type) {
|
||||
throw new Error('FS.File.attachData got a URL for which it could not determine the MIME type and none was provided using options.type');
|
||||
}
|
||||
FS.Utility.extend(self, {original: result});
|
||||
setData(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Everything else
|
||||
else {
|
||||
setData(options.type);
|
||||
}
|
||||
|
||||
// Set the data
|
||||
function setData(type) {
|
||||
self.data = new DataMan(data, type, urlOpts);
|
||||
|
||||
// Update the type to match what the data is
|
||||
self.type(self.data.type());
|
||||
|
||||
// Update the size to match what the data is.
|
||||
// It's always safe to call self.data.size() without supplying a callback
|
||||
// because it requires a callback only for URLs on the client, and we
|
||||
// already added size for URLs when we got the result from '_cfs_getUrlInfo' method.
|
||||
if (!self.size()) {
|
||||
if (callback) {
|
||||
self.data.size(function (error, size) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
} else {
|
||||
self.size(size);
|
||||
setName();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.size(self.data.size());
|
||||
setName();
|
||||
}
|
||||
} else {
|
||||
setName();
|
||||
}
|
||||
}
|
||||
|
||||
function setName() {
|
||||
// See if we can extract a file name from URL or filepath
|
||||
if (!self.name() && typeof data === "string") {
|
||||
// name from URL
|
||||
if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (FS.Utility.getFileExtension(data).length) {
|
||||
// for a URL we assume the end is a filename only if it has an extension
|
||||
self.name(FS.Utility.getFileName(data));
|
||||
}
|
||||
}
|
||||
// name from filepath
|
||||
else if (data.slice(0, 5) !== "data:") {
|
||||
self.name(FS.Utility.getFileName(data));
|
||||
}
|
||||
}
|
||||
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
return self; //allow chaining
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.uploadProgress
|
||||
* @public
|
||||
* @returns {number} The server confirmed upload progress
|
||||
*/
|
||||
FS.File.prototype.uploadProgress = function() {
|
||||
var self = this;
|
||||
// Make sure our file record is updated
|
||||
self.getFileRecord();
|
||||
|
||||
// If fully uploaded, return 100
|
||||
if (self.uploadedAt) {
|
||||
return 100;
|
||||
}
|
||||
// Otherwise return the confirmed progress or 0
|
||||
else {
|
||||
return Math.round((self.chunkCount || 0) / (self.chunkSum || 1) * 100);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.controlledByDeps
|
||||
* @public
|
||||
* @returns {FS.Collection} Returns true if this FS.File is reactive
|
||||
*
|
||||
* > Note: Returns true if this FS.File object was created by a FS.Collection
|
||||
* > and we are in a reactive computations. What does this mean? Well it should
|
||||
* > mean that our fileRecord is fully updated by Meteor and we are mounted on
|
||||
* > a collection
|
||||
*/
|
||||
FS.File.prototype.controlledByDeps = function() {
|
||||
var self = this;
|
||||
return self.createdByTransform && Deps.active;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.getCollection
|
||||
* @public
|
||||
* @returns {FS.Collection} Returns attached collection or undefined if not mounted
|
||||
*/
|
||||
FS.File.prototype.getCollection = function() {
|
||||
// Get the collection reference
|
||||
var self = this;
|
||||
|
||||
// If we already made the link then do no more
|
||||
if (self.collection) {
|
||||
return self.collection;
|
||||
}
|
||||
|
||||
// If we don't have a collectionName then there's not much to do, the file is
|
||||
// not mounted yet
|
||||
if (!self.collectionName) {
|
||||
// Should not throw an error here - could be common that the file is not
|
||||
// yet mounted into a collection
|
||||
return;
|
||||
}
|
||||
|
||||
// Link the collection to the file
|
||||
self.collection = FS._collections[self.collectionName];
|
||||
|
||||
return self.collection; //possibly undefined, but that's desired behavior
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.isMounted
|
||||
* @public
|
||||
* @returns {FS.Collection} Returns attached collection or undefined if not mounted
|
||||
*/
|
||||
FS.File.prototype.isMounted = FS.File.prototype.getCollection;
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.getFileRecord Returns the fileRecord
|
||||
* @public
|
||||
* @returns {object} The filerecord
|
||||
*/
|
||||
FS.File.prototype.getFileRecord = function() {
|
||||
var self = this;
|
||||
// Check if this file object fileRecord is kept updated by Meteor, if so
|
||||
// return self
|
||||
if (self.controlledByDeps()) {
|
||||
return self;
|
||||
}
|
||||
// Go for manually updating the file record
|
||||
if (self.isMounted()) {
|
||||
FS.debug && console.log('GET FILERECORD: ' + self._id);
|
||||
|
||||
// Return the fileRecord or an empty object
|
||||
var fileRecord = self.collection.files.findOne({_id: self._id}) || {};
|
||||
FS.Utility.extend(self, fileRecord);
|
||||
return fileRecord;
|
||||
} else {
|
||||
// We return an empty object, this way users can still do `getRecord().size`
|
||||
// Without getting an error
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.update
|
||||
* @public
|
||||
* @param {modifier} modifier
|
||||
* @param {object} [options]
|
||||
* @param {function} [callback]
|
||||
*
|
||||
* Updates the fileRecord.
|
||||
*/
|
||||
FS.File.prototype.update = function(modifier, options, callback) {
|
||||
var self = this;
|
||||
|
||||
FS.debug && console.log('UPDATE: ' + JSON.stringify(modifier));
|
||||
|
||||
// Make sure we have options and callback
|
||||
if (!callback && typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
callback = callback || FS.Utility.defaultCallback;
|
||||
|
||||
if (!self.isMounted()) {
|
||||
callback(new Error("Cannot update a file that is not associated with a collection"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Call collection update - File record
|
||||
return self.collection.files.update({_id: self._id}, modifier, options, function(err, count) {
|
||||
// Update the fileRecord if it was changed and on the client
|
||||
// The server-side methods will pull the fileRecord if needed
|
||||
if (count > 0 && Meteor.isClient)
|
||||
self.getFileRecord();
|
||||
// Call callback
|
||||
callback(err, count);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype._saveChanges
|
||||
* @private
|
||||
* @param {String} [what] "_original" to save original info, or a store name to save info for that store, or saves everything
|
||||
*
|
||||
* Updates the fileRecord from values currently set on the FS.File instance.
|
||||
*/
|
||||
FS.File.prototype._saveChanges = function(what) {
|
||||
var self = this;
|
||||
|
||||
if (!self.isMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FS.debug && console.log("FS.File._saveChanges:", what || "all");
|
||||
|
||||
var mod = {$set: {}};
|
||||
if (what === "_original") {
|
||||
mod.$set.original = self.original;
|
||||
} else if (typeof what === "string") {
|
||||
var info = self.copies[what];
|
||||
if (info) {
|
||||
mod.$set["copies." + what] = info;
|
||||
}
|
||||
} else {
|
||||
mod.$set.original = self.original;
|
||||
mod.$set.copies = self.copies;
|
||||
}
|
||||
|
||||
self.update(mod);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.remove
|
||||
* @public
|
||||
* @param {Function} [callback]
|
||||
* @returns {number} Count
|
||||
*
|
||||
* Remove the current file from its FS.Collection
|
||||
*/
|
||||
FS.File.prototype.remove = function(callback) {
|
||||
var self = this;
|
||||
|
||||
FS.debug && console.log('REMOVE: ' + self._id);
|
||||
|
||||
callback = callback || FS.Utility.defaultCallback;
|
||||
|
||||
if (!self.isMounted()) {
|
||||
callback(new Error("Cannot remove a file that is not associated with a collection"));
|
||||
return;
|
||||
}
|
||||
|
||||
return self.collection.files.remove({_id: self._id}, function(err, res) {
|
||||
if (!err) {
|
||||
delete self._id;
|
||||
delete self.collection;
|
||||
delete self.collectionName;
|
||||
}
|
||||
callback(err, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.moveTo
|
||||
* @param {FS.Collection} targetCollection
|
||||
* @private // Marked private until implemented
|
||||
* @todo Needs to be implemented
|
||||
*
|
||||
* Move the file from current collection to another collection
|
||||
*
|
||||
* > Note: Not yet implemented
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.getExtension Returns the lowercase file extension
|
||||
* @public
|
||||
* @deprecated Use the `extension` getter/setter method instead.
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] - Store name. Default is the original extension.
|
||||
* @returns {string} The extension eg.: `jpg` or if not found then an empty string ''
|
||||
*/
|
||||
FS.File.prototype.getExtension = function(options) {
|
||||
var self = this;
|
||||
return self.extension(options);
|
||||
};
|
||||
|
||||
function checkContentType(fsFile, storeName, startOfType) {
|
||||
var type;
|
||||
if (storeName && fsFile.hasStored(storeName)) {
|
||||
type = fsFile.type({store: storeName});
|
||||
} else {
|
||||
type = fsFile.type();
|
||||
}
|
||||
if (typeof type === "string") {
|
||||
return type.indexOf(startOfType) === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.isImage Is it an image file?
|
||||
* @public
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.store] The store we're interested in
|
||||
*
|
||||
* Returns true if the copy of this file in the specified store has an image
|
||||
* content type. If the file object is unmounted or doesn't have a copy for
|
||||
* the specified store, or if you don't specify a store, this method checks
|
||||
* the content type of the original file.
|
||||
*/
|
||||
FS.File.prototype.isImage = function(options) {
|
||||
return checkContentType(this, (options || {}).store, 'image/');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.isVideo Is it a video file?
|
||||
* @public
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.store] The store we're interested in
|
||||
*
|
||||
* Returns true if the copy of this file in the specified store has a video
|
||||
* content type. If the file object is unmounted or doesn't have a copy for
|
||||
* the specified store, or if you don't specify a store, this method checks
|
||||
* the content type of the original file.
|
||||
*/
|
||||
FS.File.prototype.isVideo = function(options) {
|
||||
return checkContentType(this, (options || {}).store, 'video/');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.isAudio Is it an audio file?
|
||||
* @public
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.store] The store we're interested in
|
||||
*
|
||||
* Returns true if the copy of this file in the specified store has an audio
|
||||
* content type. If the file object is unmounted or doesn't have a copy for
|
||||
* the specified store, or if you don't specify a store, this method checks
|
||||
* the content type of the original file.
|
||||
*/
|
||||
FS.File.prototype.isAudio = function(options) {
|
||||
return checkContentType(this, (options || {}).store, 'audio/');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.formattedSize
|
||||
* @public
|
||||
* @param {Object} options
|
||||
* @param {String} [options.store=none,display original file size] Which file do you want to get the size of?
|
||||
* @param {String} [options.formatString='0.00 b'] The `numeral` format string to use.
|
||||
* @return {String} The file size formatted as a human readable string and reactively updated.
|
||||
*
|
||||
* * You must add the `numeral` package to your app before you can use this method.
|
||||
* * If info is not found or a size can't be determined, it will show 0.
|
||||
*/
|
||||
FS.File.prototype.formattedSize = function fsFileFormattedSize(options) {
|
||||
var self = this;
|
||||
|
||||
if (typeof numeral !== "function")
|
||||
throw new Error("You must add the numeral package if you call FS.File.formattedSize");
|
||||
|
||||
options = options || {};
|
||||
options = options.hash || options;
|
||||
|
||||
var size = self.size(options) || 0;
|
||||
return numeral(size).format(options.formatString || '0.00 b');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.isUploaded Is this file completely uploaded?
|
||||
* @public
|
||||
* @returns {boolean} True if the number of uploaded bytes is equal to the file size.
|
||||
*/
|
||||
FS.File.prototype.isUploaded = function() {
|
||||
var self = this;
|
||||
|
||||
// Make sure we use the updated file record
|
||||
self.getFileRecord();
|
||||
|
||||
return !!self.uploadedAt;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.hasStored
|
||||
* @public
|
||||
* @param {string} storeName Name of the store
|
||||
* @param {boolean} [optimistic=false] In case that the file record is not found, read below
|
||||
* @returns {boolean} Is a version of this file stored in the given store?
|
||||
*
|
||||
* > Note: If the file is not published to the client or simply not found:
|
||||
* this method cannot know for sure if it exists or not. The `optimistic`
|
||||
* param is the boolean value to return. Are we `optimistic` that the copy
|
||||
* could exist. This is the case in `FS.File.url` we are optimistic that the
|
||||
* copy supplied by the user exists.
|
||||
*/
|
||||
FS.File.prototype.hasStored = function(storeName, optimistic) {
|
||||
var self = this;
|
||||
// Make sure we use the updated file record
|
||||
self.getFileRecord();
|
||||
// If we havent the published data then
|
||||
if (FS.Utility.isEmpty(self.copies)) {
|
||||
return !!optimistic;
|
||||
}
|
||||
if (typeof storeName === "string") {
|
||||
// Return true only if the `key` property is present, which is not set until
|
||||
// storage is complete.
|
||||
return !!(self.copies && self.copies[storeName] && self.copies[storeName].key);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Backwards compatibility
|
||||
FS.File.prototype.hasCopy = FS.File.prototype.hasStored;
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.getCopyInfo
|
||||
* @public
|
||||
* @deprecated Use individual methods with `store` option instead.
|
||||
* @param {string} storeName Name of the store for which to get copy info.
|
||||
* @returns {Object} The file details, e.g., name, size, key, etc., specific to the copy saved in this store.
|
||||
*/
|
||||
FS.File.prototype.getCopyInfo = function(storeName) {
|
||||
var self = this;
|
||||
// Make sure we use the updated file record
|
||||
self.getFileRecord();
|
||||
return (self.copies && self.copies[storeName]) || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype._getInfo
|
||||
* @private
|
||||
* @param {String} [storeName] Name of the store for which to get file info. Omit for original file details.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first?
|
||||
* @returns {Object} The file details, e.g., name, size, key, etc. If not found, returns an empty object.
|
||||
*/
|
||||
FS.File.prototype._getInfo = function(storeName, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
if (options.updateFileRecordFirst) {
|
||||
// Make sure we use the updated file record
|
||||
self.getFileRecord();
|
||||
}
|
||||
|
||||
if (storeName) {
|
||||
return (self.copies && self.copies[storeName]) || {};
|
||||
} else {
|
||||
return self.original || {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype._setInfo
|
||||
* @private
|
||||
* @param {String} storeName - Name of the store for which to set file info. Non-string will set original file details.
|
||||
* @param {String} property - Property to set
|
||||
* @param {String} value - New value for property
|
||||
* @param {Boolean} save - Should the new value be saved to the DB, too, or just set in the FS.File properties?
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.File.prototype._setInfo = function(storeName, property, value, save) {
|
||||
var self = this;
|
||||
if (typeof storeName === "string") {
|
||||
self.copies = self.copies || {};
|
||||
self.copies[storeName] = self.copies[storeName] || {};
|
||||
self.copies[storeName][property] = value;
|
||||
save && self._saveChanges(storeName);
|
||||
} else {
|
||||
self.original = self.original || {};
|
||||
self.original[property] = value;
|
||||
save && self._saveChanges("_original");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.name
|
||||
* @public
|
||||
* @param {String|null} [value] - If setting the name, specify the new name as the first argument. Otherwise the options argument should be first.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.store=none,original] - Get or set the name of the version of the file that was saved in this store. Default is the original file name.
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
|
||||
* @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
|
||||
* @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file name.
|
||||
*/
|
||||
FS.File.prototype.name = function(value, options) {
|
||||
var self = this;
|
||||
|
||||
if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
|
||||
// GET
|
||||
options = value || {};
|
||||
options = options.hash || options; // allow use as UI helper
|
||||
return self._getInfo(options.store, options).name;
|
||||
} else {
|
||||
// SET
|
||||
options = options || {};
|
||||
return self._setInfo(options.store, 'name', value, typeof options.save === "boolean" ? options.save : true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.extension
|
||||
* @public
|
||||
* @param {String|null} [value] - If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.store=none,original] - Get or set the extension of the version of the file that was saved in this store. Default is the original file extension.
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
|
||||
* @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
|
||||
* @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one.
|
||||
*/
|
||||
FS.File.prototype.extension = function(value, options) {
|
||||
var self = this;
|
||||
|
||||
if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
|
||||
// GET
|
||||
options = value || {};
|
||||
return FS.Utility.getFileExtension(self.name(options) || '');
|
||||
} else {
|
||||
// SET
|
||||
options = options || {};
|
||||
var newName = FS.Utility.setFileExtension(self.name(options) || '', value);
|
||||
return self._setInfo(options.store, 'name', newName, typeof options.save === "boolean" ? options.save : true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.size
|
||||
* @public
|
||||
* @param {Number} [value] - If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.store=none,original] - Get or set the size of the version of the file that was saved in this store. Default is the original file size.
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
|
||||
* @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
|
||||
* @returns {Number|undefined} If setting, returns `undefined`. If getting, returns the file size.
|
||||
*/
|
||||
FS.File.prototype.size = function(value, options) {
|
||||
var self = this;
|
||||
|
||||
if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
|
||||
// GET
|
||||
options = value || {};
|
||||
options = options.hash || options; // allow use as UI helper
|
||||
return self._getInfo(options.store, options).size;
|
||||
} else {
|
||||
// SET
|
||||
options = options || {};
|
||||
return self._setInfo(options.store, 'size', value, typeof options.save === "boolean" ? options.save : true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.type
|
||||
* @public
|
||||
* @param {String} [value] - If setting the type, specify the new type as the first argument. Otherwise the options argument should be first.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.store=none,original] - Get or set the type of the version of the file that was saved in this store. Default is the original file type.
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
|
||||
* @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
|
||||
* @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file type.
|
||||
*/
|
||||
FS.File.prototype.type = function(value, options) {
|
||||
var self = this;
|
||||
|
||||
if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
|
||||
// GET
|
||||
options = value || {};
|
||||
options = options.hash || options; // allow use as UI helper
|
||||
return self._getInfo(options.store, options).type;
|
||||
} else {
|
||||
// SET
|
||||
options = options || {};
|
||||
return self._setInfo(options.store, 'type', value, typeof options.save === "boolean" ? options.save : true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.updatedAt
|
||||
* @public
|
||||
* @param {String} [value] - If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.store=none,original] - Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date.
|
||||
* @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
|
||||
* @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
|
||||
* @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file's last updated date.
|
||||
*/
|
||||
FS.File.prototype.updatedAt = function(value, options) {
|
||||
var self = this;
|
||||
|
||||
if (!options && ((typeof value === "object" && value !== null && !(value instanceof Date)) || typeof value === "undefined")) {
|
||||
// GET
|
||||
options = value || {};
|
||||
options = options.hash || options; // allow use as UI helper
|
||||
return self._getInfo(options.store, options).updatedAt;
|
||||
} else {
|
||||
// SET
|
||||
options = options || {};
|
||||
return self._setInfo(options.store, 'updatedAt', value, typeof options.save === "boolean" ? options.save : true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.onStoredCallback
|
||||
* @summary Calls callback when the file is fully stored to the specify storeName
|
||||
* @public
|
||||
* @param {String} [storeName] - The name of the file store we want to get called when stored.
|
||||
* @param {function} [callback]
|
||||
*/
|
||||
FS.File.prototype.onStoredCallback = function (storeName, callback) {
|
||||
// Check file is not already stored
|
||||
if (this.hasStored(storeName)) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (Meteor.isServer) {
|
||||
// Listen to file stored events
|
||||
// TODO Require thinking whether it is better to use observer for case of using multiple application instances, Ask for same image url while upload is being done.
|
||||
this.on('stored', function (newStoreName) {
|
||||
// If stored is completed to the specified store call callback
|
||||
if (storeName === newStoreName) {
|
||||
// Remove the specified file stored listener
|
||||
this.removeListener('stored', arguments.callee);
|
||||
callback();
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
} else {
|
||||
var fileId = this._id,
|
||||
collectionName = this.collectionName;
|
||||
// Wait for file to be fully uploaded
|
||||
Tracker.autorun(function (c) {
|
||||
Meteor.call('_cfs_returnWhenStored', collectionName, fileId, storeName, function (error, result) {
|
||||
if (result && result === true) {
|
||||
c.stop();
|
||||
callback();
|
||||
} else {
|
||||
Meteor.setTimeout(function () {
|
||||
c.invalidate();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.onStored
|
||||
* @summary Function that returns when the file is fully stored to the specify storeName
|
||||
* @public
|
||||
* @param {String} storeName - The name of the file store we want to get called when stored.
|
||||
*
|
||||
* Function that returns when the file is fully stored to the specify storeName.
|
||||
*
|
||||
* For example needed if wanted to save the direct link to a file on s3 when fully uploaded.
|
||||
*/
|
||||
FS.File.prototype.onStored = function (arguments) {
|
||||
var onStoredSync = Meteor.wrapAsync(this.onStoredCallback);
|
||||
return onStoredSync.call(this, arguments);
|
||||
};
|
||||
|
||||
function isBasicObject(obj) {
|
||||
return (obj === Object(obj) && Object.getPrototypeOf(obj) === Object.prototype);
|
||||
}
|
||||
|
||||
// getPrototypeOf polyfill
|
||||
if (typeof Object.getPrototypeOf !== "function") {
|
||||
if (typeof "".__proto__ === "object") {
|
||||
Object.getPrototypeOf = function(object) {
|
||||
return object.__proto__;
|
||||
};
|
||||
} else {
|
||||
Object.getPrototypeOf = function(object) {
|
||||
// May break if the constructor has been tampered with
|
||||
return object.constructor.prototype;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
361
packages/wekan-cfs-file/fsFile-server.js
Normal file
361
packages/wekan-cfs-file/fsFile-server.js
Normal file
|
@ -0,0 +1,361 @@
|
|||
/**
|
||||
* Notes a details about a storage adapter failure within the file record
|
||||
* @param {string} storeName
|
||||
* @param {number} maxTries
|
||||
* @return {undefined}
|
||||
* @todo deprecate this
|
||||
*/
|
||||
FS.File.prototype.logCopyFailure = function(storeName, maxTries) {
|
||||
var self = this;
|
||||
|
||||
// hasStored will update from the fileRecord
|
||||
if (self.hasStored(storeName)) {
|
||||
throw new Error("logCopyFailure: invalid storeName");
|
||||
}
|
||||
|
||||
// Make sure we have a temporary file saved since we will be
|
||||
// trying the save again.
|
||||
FS.TempStore.ensureForFile(self);
|
||||
|
||||
var now = new Date();
|
||||
var currentCount = (self.failures && self.failures.copies && self.failures.copies[storeName] && typeof self.failures.copies[storeName].count === "number") ? self.failures.copies[storeName].count : 0;
|
||||
maxTries = maxTries || 5;
|
||||
|
||||
var modifier = {};
|
||||
modifier.$set = {};
|
||||
modifier.$set['failures.copies.' + storeName + '.lastAttempt'] = now;
|
||||
if (currentCount === 0) {
|
||||
modifier.$set['failures.copies.' + storeName + '.firstAttempt'] = now;
|
||||
}
|
||||
modifier.$set['failures.copies.' + storeName + '.count'] = currentCount + 1;
|
||||
modifier.$set['failures.copies.' + storeName + '.doneTrying'] = (currentCount + 1 >= maxTries);
|
||||
self.update(modifier);
|
||||
};
|
||||
|
||||
/**
|
||||
* Has this store permanently failed?
|
||||
* @param {String} storeName The name of the store
|
||||
* @return {boolean} Has this store failed permanently?
|
||||
* @todo deprecate this
|
||||
*/
|
||||
FS.File.prototype.failedPermanently = function(storeName) {
|
||||
var self = this;
|
||||
return !!(self.failures &&
|
||||
self.failures.copies &&
|
||||
self.failures.copies[storeName] &&
|
||||
self.failures.copies[storeName].doneTrying);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.createReadStream
|
||||
* @public
|
||||
* @param {String} [storeName]
|
||||
* @returns {stream.Readable} Readable NodeJS stream
|
||||
*
|
||||
* Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name.
|
||||
*
|
||||
* * If you pass a `storeName`, a readable stream for the file data saved in that store is returned.
|
||||
* * If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned.
|
||||
* * If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned.
|
||||
*
|
||||
*/
|
||||
FS.File.prototype.createReadStream = function(storeName) {
|
||||
var self = this;
|
||||
|
||||
// If we dont have a store name but got Buffer data?
|
||||
if (!storeName && self.data) {
|
||||
FS.debug && console.log("fileObj.createReadStream creating read stream for attached data");
|
||||
// Stream from attached data if present
|
||||
return self.data.createReadStream();
|
||||
} else if (!storeName && FS.TempStore && FS.TempStore.exists(self)) {
|
||||
FS.debug && console.log("fileObj.createReadStream creating read stream for temp store");
|
||||
// Stream from temp store - its a bit slower than regular streams?
|
||||
return FS.TempStore.createReadStream(self);
|
||||
} else {
|
||||
// Stream from the store using storage adapter
|
||||
if (self.isMounted()) {
|
||||
var storage = self.collection.storesLookup[storeName] || self.collection.primaryStore;
|
||||
FS.debug && console.log("fileObj.createReadStream creating read stream for store", storage.name);
|
||||
// return stream
|
||||
return storage.adapter.createReadStream(self);
|
||||
} else {
|
||||
throw new Meteor.Error('File not mounted');
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.createWriteStream
|
||||
* @public
|
||||
* @param {String} [storeName]
|
||||
* @returns {stream.Writeable} Writeable NodeJS stream
|
||||
*
|
||||
* Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name.
|
||||
*
|
||||
* * If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned.
|
||||
* * If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned.
|
||||
*
|
||||
*/
|
||||
FS.File.prototype.createWriteStream = function(storeName) {
|
||||
var self = this;
|
||||
|
||||
// We have to have a mounted file in order for this to work
|
||||
if (self.isMounted()) {
|
||||
if (!storeName && FS.TempStore && FS.FileWorker) {
|
||||
// If we have worker installed - we pass the file to FS.TempStore
|
||||
// We dont need the storeName since all stores will be generated from
|
||||
// TempStore.
|
||||
// This should trigger FS.FileWorker at some point?
|
||||
FS.TempStore.createWriteStream(self);
|
||||
} else {
|
||||
// Stream directly to the store using storage adapter
|
||||
var storage = self.collection.storesLookup[storeName] || self.collection.primaryStore;
|
||||
return storage.adapter.createWriteStream(self);
|
||||
}
|
||||
} else {
|
||||
throw new Meteor.Error('File not mounted');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.copy Makes a copy of the file and underlying data in all stores.
|
||||
* @public
|
||||
* @returns {FS.File} The new FS.File instance
|
||||
*/
|
||||
FS.File.prototype.copy = function() {
|
||||
var self = this;
|
||||
|
||||
if (!self.isMounted()) {
|
||||
throw new Error("Cannot copy a file that is not associated with a collection");
|
||||
}
|
||||
|
||||
// Get the file record
|
||||
var fileRecord = self.collection.files.findOne({_id: self._id}, {transform: null}) || {};
|
||||
|
||||
// Remove _id and copy keys from the file record
|
||||
delete fileRecord._id;
|
||||
|
||||
// Insert directly; we don't have access to "original" in this case
|
||||
var newId = self.collection.files.insert(fileRecord);
|
||||
|
||||
var newFile = self.collection.findOne(newId);
|
||||
|
||||
// Copy underlying files in the stores
|
||||
var mod, oldKey;
|
||||
for (var name in newFile.copies) {
|
||||
if (newFile.copies.hasOwnProperty(name)) {
|
||||
oldKey = newFile.copies[name].key;
|
||||
if (oldKey) {
|
||||
// We need to ask the adapter for the true oldKey because
|
||||
// right now gridfs does some extra stuff.
|
||||
// TODO GridFS should probably set the full key object
|
||||
// (with _id and filename) into `copies.key`
|
||||
// so that copies.key can be passed directly to
|
||||
// createReadStreamForFileKey
|
||||
var sourceFileStorage = self.collection.storesLookup[name];
|
||||
if (!sourceFileStorage) {
|
||||
throw new Error(name + " is not a valid store name");
|
||||
}
|
||||
oldKey = sourceFileStorage.adapter.fileKey(self);
|
||||
// delete so that new fileKey will be generated in copyStoreData
|
||||
delete newFile.copies[name].key;
|
||||
mod = mod || {};
|
||||
mod["copies." + name + ".key"] = copyStoreData(newFile, name, oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update keys in the filerecord
|
||||
if (mod) {
|
||||
newFile.update({$set: mod});
|
||||
}
|
||||
|
||||
return newFile;
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
// Does a HEAD request to URL to get the type, updatedAt,
|
||||
// and size prior to actually downloading the data.
|
||||
// That way we can do filter checks without actually downloading.
|
||||
'_cfs_getUrlInfo': function (url, options) {
|
||||
check(url, String);
|
||||
check(options, Object);
|
||||
|
||||
this.unblock();
|
||||
|
||||
var response = HTTP.call("HEAD", url, options);
|
||||
var headers = response.headers;
|
||||
var result = {};
|
||||
|
||||
if (headers['content-type']) {
|
||||
result.type = headers['content-type'];
|
||||
}
|
||||
|
||||
if (headers['content-length']) {
|
||||
result.size = +headers['content-length'];
|
||||
}
|
||||
|
||||
if (headers['last-modified']) {
|
||||
result.updatedAt = new Date(headers['last-modified']);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
// Helper function that checks whether given fileId from collectionName
|
||||
// Is fully uploaded to specify storeName.
|
||||
'_cfs_returnWhenStored' : function (collectionName, fileId, storeName) {
|
||||
check(collectionName, String);
|
||||
check(fileId, String);
|
||||
check(storeName, String);
|
||||
|
||||
var collection = FS._collections[collectionName];
|
||||
if (!collection) {
|
||||
return Meteor.Error('_cfs_returnWhenStored: FSCollection name not exists');
|
||||
}
|
||||
|
||||
var file = collection.findOne({_id: fileId});
|
||||
if (!file) {
|
||||
return Meteor.Error('_cfs_returnWhenStored: FSFile not exists');
|
||||
}
|
||||
return file.hasStored(storeName);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO maybe this should be in cfs-storage-adapter
|
||||
function _copyStoreData(fileObj, storeName, sourceKey, callback) {
|
||||
if (!fileObj.isMounted()) {
|
||||
throw new Error("Cannot copy store data for a file that is not associated with a collection");
|
||||
}
|
||||
|
||||
var storage = fileObj.collection.storesLookup[storeName];
|
||||
if (!storage) {
|
||||
throw new Error(storeName + " is not a valid store name");
|
||||
}
|
||||
|
||||
// We want to prevent beforeWrite and transformWrite from running, so
|
||||
// we interact directly with the store.
|
||||
var destinationKey = storage.adapter.fileKey(fileObj);
|
||||
var readStream = storage.adapter.createReadStreamForFileKey(sourceKey);
|
||||
var writeStream = storage.adapter.createWriteStreamForFileKey(destinationKey);
|
||||
|
||||
writeStream.once('stored', function(result) {
|
||||
callback(null, result.fileKey);
|
||||
});
|
||||
|
||||
writeStream.once('error', function(error) {
|
||||
callback(error);
|
||||
});
|
||||
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
var copyStoreData = Meteor.wrapAsync(_copyStoreData);
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.copyData Copies the content of a store directly into another store.
|
||||
* @public
|
||||
* @param {string} sourceStoreName
|
||||
* @param {string} targetStoreName
|
||||
* @param {boolean=} move
|
||||
*/
|
||||
FS.File.prototype.copyData = function(sourceStoreName, targetStoreName, move){
|
||||
|
||||
move = !!move;
|
||||
/**
|
||||
* @type {Object.<string,*>}
|
||||
*/
|
||||
var sourceStoreValues = this.copies[sourceStoreName];
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
var copyKey = cloneDataToStore(this, sourceStoreName, targetStoreName, move);
|
||||
/**
|
||||
* @type {Object.<string,*>}
|
||||
*/
|
||||
var targetStoreValues = {};
|
||||
for (var v in sourceStoreValues) {
|
||||
if (sourceStoreValues.hasOwnProperty(v)) {
|
||||
targetStoreValues[v] = sourceStoreValues[v]
|
||||
}
|
||||
}
|
||||
targetStoreValues.key = copyKey;
|
||||
targetStoreValues.createdAt = new Date();
|
||||
targetStoreValues.updatedAt = new Date();
|
||||
/**
|
||||
*
|
||||
* @type {modifier}
|
||||
*/
|
||||
var modifier = {};
|
||||
modifier.$set = {};
|
||||
modifier.$set["copies."+targetStoreName] = targetStoreValues;
|
||||
if(move){
|
||||
modifier.$unset = {};
|
||||
modifier.$unset["copies."+sourceStoreName] = "";
|
||||
}
|
||||
this.update(modifier);
|
||||
};
|
||||
/**
|
||||
* @method FS.File.prototype.moveData Moves the content of a store directly into another store.
|
||||
* @public
|
||||
* @param {string} sourceStoreName
|
||||
* @param {string} targetStoreName
|
||||
*/
|
||||
FS.File.prototype.moveData = function(sourceStoreName, targetStoreName){
|
||||
this.copyData(sourceStoreName, targetStoreName, true);
|
||||
};
|
||||
// TODO maybe this should be in cfs-storage-adapter
|
||||
/**
|
||||
*
|
||||
* @param {FS.File} fileObj
|
||||
* @param {string} sourceStoreName
|
||||
* @param {string} targetStoreName
|
||||
* @param {boolean} move
|
||||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
function _copyDataFromStoreToStore(fileObj, sourceStoreName, targetStoreName, move, callback) {
|
||||
if (!fileObj.isMounted()) {
|
||||
throw new Error("Cannot copy store data for a file that is not associated with a collection");
|
||||
}
|
||||
/**
|
||||
* @type {FS.StorageAdapter}
|
||||
*/
|
||||
var sourceStorage = fileObj.collection.storesLookup[sourceStoreName];
|
||||
/**
|
||||
* @type {FS.StorageAdapter}
|
||||
*/
|
||||
var targetStorage = fileObj.collection.storesLookup[targetStoreName];
|
||||
|
||||
if (!sourceStorage) {
|
||||
throw new Error(sourceStoreName + " is not a valid store name");
|
||||
}
|
||||
if (!targetStorage) {
|
||||
throw new Error(targetStorage + " is not a valid store name");
|
||||
}
|
||||
|
||||
// We want to prevent beforeWrite and transformWrite from running, so
|
||||
// we interact directly with the store.
|
||||
var sourceKey = sourceStorage.adapter.fileKey(fileObj);
|
||||
var targetKey = targetStorage.adapter.fileKey(fileObj);
|
||||
var readStream = sourceStorage.adapter.createReadStreamForFileKey(sourceKey);
|
||||
var writeStream = targetStorage.adapter.createWriteStreamForFileKey(targetKey);
|
||||
|
||||
|
||||
writeStream.safeOnce('stored', function(result) {
|
||||
if(move && sourceStorage.adapter.remove(fileObj)===false){
|
||||
callback("Copied to store:" + targetStoreName
|
||||
+ " with fileKey: "
|
||||
+ result.fileKey
|
||||
+ ", but could not delete from source store: "
|
||||
+ sourceStoreName);
|
||||
}else{
|
||||
callback(null, result.fileKey);
|
||||
}
|
||||
});
|
||||
|
||||
writeStream.once('error', function(error) {
|
||||
callback(error);
|
||||
});
|
||||
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
var cloneDataToStore = Meteor.wrapAsync(_copyDataFromStoreToStore);
|
749
packages/wekan-cfs-file/internal.api.md
Normal file
749
packages/wekan-cfs-file/internal.api.md
Normal file
|
@ -0,0 +1,749 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["fsFile-common.js"](fsFile-common.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.File"></a>new *fs*.File([ref]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __File__ is defined in `FS`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __ref__ *{object|[FS.File](#FS.File)|[data to attach](#data to attach)}* (Optional)
|
||||
|
||||
Another FS.File instance, a filerecord, or some data to pass to attachData
|
||||
|
||||
|
||||
|
||||
> ```FS.File = function(ref, createdByTransform) { ...``` [fsFile-common.js:8](fsFile-common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.attachData"></a>*fsFile*.attachData(data, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __attachData__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to attach to the file.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
|
||||
Options
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known.
|
||||
|
||||
* __headers__ *{String}* (Optional)
|
||||
|
||||
When attaching a URL, headers to be used for the GET request (currently server only)
|
||||
|
||||
* __auth__ *{String}* (Optional)
|
||||
|
||||
When attaching a URL, "username:password" to be used for the GET request (currently server only)
|
||||
|
||||
* __callback__ *{Function}* (Optional)
|
||||
|
||||
Callback function, callback(error). On the client, a callback is required if data is a URL.
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
This FS.File instance.
|
||||
|
||||
|
||||
|
||||
> ```FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) { ...``` [fsFile-common.js:36](fsFile-common.js#L36)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.uploadProgress"></a>*fsFile*.uploadProgress() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __uploadProgress__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{number}*
|
||||
The server confirmed upload progress
|
||||
|
||||
|
||||
> ```FS.File.prototype.uploadProgress = function() { ...``` [fsFile-common.js:154](fsFile-common.js#L154)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.controlledByDeps"></a>*fsFile*.controlledByDeps() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __controlledByDeps__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns true if this FS.File is reactive
|
||||
|
||||
|
||||
> Note: Returns true if this FS.File object was created by a FS.Collection
|
||||
> and we are in a reactive computations. What does this mean? Well it should
|
||||
> mean that our fileRecord is fully updated by Meteor and we are mounted on
|
||||
> a collection
|
||||
|
||||
> ```FS.File.prototype.controlledByDeps = function() { ...``` [fsFile-common.js:179](fsFile-common.js#L179)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getCollection"></a>*fsFile*.getCollection() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getCollection__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns attached collection or undefined if not mounted
|
||||
|
||||
|
||||
> ```FS.File.prototype.getCollection = function() { ...``` [fsFile-common.js:189](fsFile-common.js#L189)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isMounted"></a>*fsFile*.isMounted() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isMounted__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.Collection}*
|
||||
Returns attached collection or undefined if not mounted
|
||||
|
||||
|
||||
> ```FS.File.prototype.isMounted = FS.File.prototype.getCollection;``` [fsFile-common.js:217](fsFile-common.js#L217)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getFileRecord"></a>*fsFile*.getFileRecord() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileRecord__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{object}*
|
||||
The filerecord
|
||||
|
||||
|
||||
> ```FS.File.prototype.getFileRecord = function() { ...``` [fsFile-common.js:224](fsFile-common.js#L224)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.update"></a>*fsFile*.update(modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __modifier__ *{[modifier](#modifier)}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
|
||||
Updates the fileRecord.
|
||||
|
||||
> ```FS.File.prototype.update = function(modifier, options, callback) { ...``` [fsFile-common.js:255](fsFile-common.js#L255)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype._saveChanges"></a>*fsFile*._saveChanges([what]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method ___saveChanges__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __what__ *{String}* (Optional)
|
||||
|
||||
"_original" to save original info, or a store name to save info for that store, or saves everything
|
||||
|
||||
|
||||
|
||||
Updates the fileRecord from values currently set on the FS.File instance.
|
||||
|
||||
> ```FS.File.prototype._saveChanges = function(what) { ...``` [fsFile-common.js:290](fsFile-common.js#L290)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.remove"></a>*fsFile*.remove([callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}* (Optional)
|
||||
|
||||
__Returns__ *{number}*
|
||||
Count
|
||||
|
||||
|
||||
Remove the current file from its FS.Collection
|
||||
|
||||
> ```FS.File.prototype.remove = function(callback) { ...``` [fsFile-common.js:323](fsFile-common.js#L323)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.moveTo"></a>*fsFile*.moveTo(targetCollection) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __moveTo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __targetCollection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__TODO__
|
||||
```
|
||||
* Needs to be implemented
|
||||
```
|
||||
|
||||
|
||||
Move the file from current collection to another collection
|
||||
|
||||
> Note: Not yet implemented
|
||||
|
||||
> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getExtension"></a>*fsFile*.getExtension([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
> __Warning!__
|
||||
> This method "FS.File.prototype.getExtension" has deprecated from the API
|
||||
> Use the `extension` getter/setter method instead.
|
||||
|
||||
*This method __getExtension__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Store name. Default is the original extension.
|
||||
|
||||
|
||||
__Returns__ *{string}*
|
||||
The extension eg.: `jpg` or if not found then an empty string ''
|
||||
|
||||
|
||||
> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isImage"></a>*fsFile*.isImage([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isImage__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has an image
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isImage = function(options) { ...``` [fsFile-common.js:393](fsFile-common.js#L393)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isVideo"></a>*fsFile*.isVideo([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isVideo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has a video
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isVideo = function(options) { ...``` [fsFile-common.js:408](fsFile-common.js#L408)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isAudio"></a>*fsFile*.isAudio([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isAudio__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}* (Optional)
|
||||
* __store__ *{string}* (Optional)
|
||||
|
||||
The store we're interested in
|
||||
|
||||
|
||||
|
||||
Returns true if the copy of this file in the specified store has an audio
|
||||
content type. If the file object is unmounted or doesn't have a copy for
|
||||
the specified store, or if you don't specify a store, this method checks
|
||||
the content type of the original file.
|
||||
|
||||
> ```FS.File.prototype.isAudio = function(options) { ...``` [fsFile-common.js:423](fsFile-common.js#L423)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.formattedSize"></a>*fsFile*.formattedSize({Object}, {String}, {String}) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __formattedSize__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __{Object}__ *{any}*
|
||||
|
||||
options
|
||||
|
||||
* __{String}__ *{any}*
|
||||
|
||||
[options.store=none,display original file size] Which file do you want to get the size of?
|
||||
|
||||
* __{String}__ *{any}*
|
||||
|
||||
[options.formatString='0.00 b'] The `numeral` format string to use.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The file size formatted as a human readable string and reactively updated.
|
||||
|
||||
|
||||
You must add the `numeral` package to your app before you can use this method.
|
||||
If info is not found or a size can't be determined, it will show 0.
|
||||
|
||||
> ```FS.File.prototype.formattedSize = function fsFileFormattedSize(options) { ...``` [fsFile-common.js:438](fsFile-common.js#L438)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.isUploaded"></a>*fsFile*.isUploaded() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __isUploaded__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the number of uploaded bytes is equal to the file size.
|
||||
|
||||
|
||||
> ```FS.File.prototype.isUploaded = function() { ...``` [fsFile-common.js:456](fsFile-common.js#L456)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.hasStored"></a>*fsFile*.hasStored(storeName, [optimistic]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __hasStored__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{string}*
|
||||
|
||||
Name of the store
|
||||
|
||||
* __optimistic__ *{boolean}* (Optional, Default = false)
|
||||
|
||||
In case that the file record is not found, read below
|
||||
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
Is a version of this file stored in the given store?
|
||||
|
||||
|
||||
> Note: If the file is not published to the client or simply not found:
|
||||
this method cannot know for sure if it exists or not. The `optimistic`
|
||||
param is the boolean value to return. Are we `optimistic` that the copy
|
||||
could exist. This is the case in `FS.File.url` we are optimistic that the
|
||||
copy supplied by the user exists.
|
||||
|
||||
> ```FS.File.prototype.hasStored = function(storeName, optimistic) { ...``` [fsFile-common.js:478](fsFile-common.js#L478)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.getCopyInfo"></a>*fsFile*.getCopyInfo(storeName) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
> __Warning!__
|
||||
> This method "FS.File.prototype.getCopyInfo" has deprecated from the API
|
||||
> Use individual methods with `store` option instead.
|
||||
|
||||
*This method __getCopyInfo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{string}*
|
||||
|
||||
Name of the store for which to get copy info.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
The file details, e.g., name, size, key, etc., specific to the copy saved in this store.
|
||||
|
||||
|
||||
> ```FS.File.prototype.getCopyInfo = function(storeName) { ...``` [fsFile-common.js:504](fsFile-common.js#L504)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype._getInfo"></a>*fsFile*._getInfo([storeName], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method ___getInfo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}* (Optional)
|
||||
|
||||
Name of the store for which to get file info. Omit for original file details.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first?
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
The file details, e.g., name, size, key, etc. If not found, returns an empty object.
|
||||
|
||||
|
||||
> ```FS.File.prototype._getInfo = function(storeName, options) { ...``` [fsFile-common.js:519](fsFile-common.js#L519)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype._setInfo"></a>*fsFile*._setInfo(storeName, property, value, save) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method ___setInfo__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}*
|
||||
|
||||
Name of the store for which to set file info. Non-string will set original file details.
|
||||
|
||||
* __property__ *{String}*
|
||||
|
||||
Property to set
|
||||
|
||||
* __value__ *{String}*
|
||||
|
||||
New value for property
|
||||
|
||||
* __save__ *{Boolean}*
|
||||
|
||||
Should the new value be saved to the DB, too, or just set in the FS.File properties?
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.File.prototype._setInfo = function(storeName, property, value, save) { ...``` [fsFile-common.js:544](fsFile-common.js#L544)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.name"></a>*fsFile*.name([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __name__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String|null}* (Optional)
|
||||
|
||||
If setting the name, specify the new name as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the name of the version of the file that was saved in this store. Default is the original file name.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file name.
|
||||
|
||||
|
||||
> ```FS.File.prototype.name = function(value, options) { ...``` [fsFile-common.js:568](fsFile-common.js#L568)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.extension"></a>*fsFile*.extension([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __extension__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String|null}* (Optional)
|
||||
|
||||
If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the extension of the version of the file that was saved in this store. Default is the original file extension.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one.
|
||||
|
||||
|
||||
> ```FS.File.prototype.extension = function(value, options) { ...``` [fsFile-common.js:593](fsFile-common.js#L593)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.size"></a>*fsFile*.size([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{Number}* (Optional)
|
||||
|
||||
If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the size of the version of the file that was saved in this store. Default is the original file size.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{Number|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file size.
|
||||
|
||||
|
||||
> ```FS.File.prototype.size = function(value, options) { ...``` [fsFile-common.js:618](fsFile-common.js#L618)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.type"></a>*fsFile*.type([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String}* (Optional)
|
||||
|
||||
If setting the type, specify the new type as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the type of the version of the file that was saved in this store. Default is the original file type.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file type.
|
||||
|
||||
|
||||
> ```FS.File.prototype.type = function(value, options) { ...``` [fsFile-common.js:643](fsFile-common.js#L643)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.updatedAt"></a>*fsFile*.updatedAt([value], [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __updatedAt__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __value__ *{String}* (Optional)
|
||||
|
||||
If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first.
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{Object}* (Optional, Default = none,original)
|
||||
|
||||
Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date.
|
||||
|
||||
* __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Update this instance with data from the DB first? Applies to getter usage only.
|
||||
|
||||
* __save__ *{Boolean}* (Optional, Default = true)
|
||||
|
||||
Save change to database? Applies to setter usage only.
|
||||
|
||||
|
||||
__Returns__ *{String|undefined}*
|
||||
If setting, returns `undefined`. If getting, returns the file's last updated date.
|
||||
|
||||
|
||||
> ```FS.File.prototype.updatedAt = function(value, options) { ...``` [fsFile-common.js:668](fsFile-common.js#L668)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["fsFile-server.js"](fsFile-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.File.prototype.logCopyFailure"></a>*fsFile*.logCopyFailure(storeName, maxTries) <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
Notes a details about a storage adapter failure within the file record
|
||||
```
|
||||
*This method __logCopyFailure__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{string}*
|
||||
* __maxTries__ *{number}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
__TODO__
|
||||
```
|
||||
* deprecate this
|
||||
```
|
||||
|
||||
|
||||
> ```FS.File.prototype.logCopyFailure = function(storeName, maxTries) { ...``` [fsFile-server.js:8](fsFile-server.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.failedPermanently"></a>*fsFile*.failedPermanently(storeName) <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
Has this store permanently failed?
|
||||
```
|
||||
*This method __failedPermanently__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}*
|
||||
|
||||
The name of the store
|
||||
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
Has this store failed permanently?
|
||||
|
||||
__TODO__
|
||||
```
|
||||
* deprecate this
|
||||
```
|
||||
|
||||
|
||||
> ```FS.File.prototype.failedPermanently = function(storeName) { ...``` [fsFile-server.js:41](fsFile-server.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.createReadStream"></a>*fsFile*.createReadStream([storeName]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{stream.Readable}*
|
||||
Readable NodeJS stream
|
||||
|
||||
|
||||
Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name.
|
||||
|
||||
If you pass a `storeName`, a readable stream for the file data saved in that store is returned.
|
||||
If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned.
|
||||
If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned.
|
||||
|
||||
|
||||
> ```FS.File.prototype.createReadStream = function(storeName) { ...``` [fsFile-server.js:62](fsFile-server.js#L62)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.createWriteStream"></a>*fsFile*.createWriteStream([storeName]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createWriteStream__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __storeName__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{stream.Writeable}*
|
||||
Writeable NodeJS stream
|
||||
|
||||
|
||||
Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name.
|
||||
|
||||
If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned.
|
||||
If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned.
|
||||
|
||||
|
||||
> ```FS.File.prototype.createWriteStream = function(storeName) { ...``` [fsFile-server.js:100](fsFile-server.js#L100)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.copy"></a>*fsFile*.copy() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __copy__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The new FS.File instance
|
||||
|
||||
|
||||
> ```FS.File.prototype.copy = function() { ...``` [fsFile-server.js:126](fsFile-server.js#L126)
|
||||
|
||||
|
55
packages/wekan-cfs-file/package.js
Normal file
55
packages/wekan-cfs-file/package.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-file.git',
|
||||
name: 'wekan-cfs-file',
|
||||
version: '0.1.17',
|
||||
summary: 'CollectionFS, FS.File object'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
temp: "0.7.0" // for tests only
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
// This imply is needed for tests, and is technically probably correct anyway.
|
||||
api.imply([
|
||||
'wekan-cfs-base-package@0.0.30'
|
||||
]);
|
||||
|
||||
api.use([
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-storage-adapter@0.2.1',
|
||||
'tracker',
|
||||
'check',
|
||||
'ddp',
|
||||
'mongo',
|
||||
'http',
|
||||
'wekan-cfs-data-man@0.0.6',
|
||||
'raix:eventemitter@0.1.1'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'fsFile-common.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'fsFile-common.js',
|
||||
'fsFile-server.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use([
|
||||
'wekan-cfs-standard-packages@0.0.0',
|
||||
'wekan-cfs-gridfs@0.0.0',
|
||||
'tinytest@1.0.0',
|
||||
'http@1.0.0',
|
||||
'test-helpers@1.0.0',
|
||||
'wekan-cfs-http-methods@0.0.29'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'tests/file-tests.js'
|
||||
]);
|
||||
});
|
436
packages/wekan-cfs-file/tests/file-tests.js
Normal file
436
packages/wekan-cfs-file/tests/file-tests.js
Normal file
|
@ -0,0 +1,436 @@
|
|||
function bin2str(bufView) {
|
||||
var length = bufView.length;
|
||||
var result = '';
|
||||
for (var i = 0; i<length; i+=65535) {
|
||||
var addition = 65535;
|
||||
if(i + 65535 > length) {
|
||||
addition = length - i;
|
||||
}
|
||||
try {
|
||||
// this fails on phantomjs due to old webkit bug; hence the try/catch
|
||||
result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
|
||||
} catch (e) {
|
||||
var dataArray = [];
|
||||
for (var j = i; j < i+addition; j++) {
|
||||
dataArray.push(bufView[j]);
|
||||
}
|
||||
result += String.fromCharCode.apply(null, dataArray);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//function ab2str(buffer) {
|
||||
// return bin2str(new Uint8Array(buffer));
|
||||
//}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
var fileCollection = new FS.Collection('files', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('files')
|
||||
],
|
||||
uploader: null
|
||||
});
|
||||
|
||||
// Set up server stuff
|
||||
if (Meteor.isServer) {
|
||||
var fs = Npm.require('fs');
|
||||
var temp = Npm.require('temp');
|
||||
var path = Npm.require('path');
|
||||
|
||||
// Automatically track and cleanup files at exit
|
||||
temp.track();
|
||||
|
||||
// Set up HTTP method URL used by client tests
|
||||
var conf = {
|
||||
get: function () {
|
||||
var buf = new Buffer('Hello World');
|
||||
this.setContentType('text/plain');
|
||||
return buf;
|
||||
},
|
||||
head: function () {
|
||||
var buf = new Buffer('Hello World');
|
||||
this.setContentType('text/plain');
|
||||
this.addHeader('Content-Length', buf.length);
|
||||
buf = null;
|
||||
}
|
||||
};
|
||||
HTTP.methods({
|
||||
'test': conf,
|
||||
'test.txt': conf
|
||||
});
|
||||
|
||||
// Save temp file for testing with
|
||||
function openTempFile(name, callback) {
|
||||
return temp.open(name, callback);
|
||||
}
|
||||
var openTempFileSync = Meteor.wrapAsync(openTempFile);
|
||||
|
||||
var info = openTempFileSync({suffix: '.txt'});
|
||||
var tempFilePath = info.path;
|
||||
var tempFileName = path.basename(tempFilePath);
|
||||
fs.writeSync(info.fd, 'Hello World');
|
||||
fs.closeSync(info.fd);
|
||||
|
||||
fileCollection.allow({
|
||||
insert: function () {
|
||||
return true;
|
||||
},
|
||||
update: function () {
|
||||
return true;
|
||||
},
|
||||
remove: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.publish("files", function () {
|
||||
return fileCollection.find();
|
||||
});
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.subscribe("files");
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-file - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.File !== 'undefined', 'test environment not initialized FS.File');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-file - construction', function(test) {
|
||||
// Normal object provided will extend the fileObj
|
||||
var f = new FS.File({foo: "bar"});
|
||||
test.equal(f.foo, "bar");
|
||||
|
||||
// If passed another FS.File instance, we clone it
|
||||
var f2 = new FS.File(f);
|
||||
test.equal(f2.foo, "bar");
|
||||
});
|
||||
|
||||
// Types of data and how we support attaching:
|
||||
//
|
||||
// Type C/S Constructor attachData w/ callback attachData w/o callback
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Buffer Server No Yes Yes
|
||||
// ArrayBuffer Both No Yes Yes
|
||||
// Binary Both No Yes Yes
|
||||
// Data URI Both Yes Yes Yes
|
||||
// URL Both Yes on Server Yes Yes on Server
|
||||
// Filepath Server Yes Yes Yes
|
||||
// File Client Yes Yes Yes
|
||||
// Blob Client Yes Yes Yes
|
||||
|
||||
function doAttachDataConstructorTest(data, name, test) {
|
||||
var f = new FS.File(data);
|
||||
if (!name) {
|
||||
test.isUndefined(f.name());
|
||||
} else {
|
||||
test.equal(f.name(), name);
|
||||
}
|
||||
test.equal(f.type(), "text/plain");
|
||||
test.equal(f.size(), 11);
|
||||
}
|
||||
|
||||
function doAttachDataSyncTest(data, opts, name, test) {
|
||||
var f = new FS.File();
|
||||
f.attachData(data, opts);
|
||||
if (!name) {
|
||||
test.isUndefined(f.name());
|
||||
} else {
|
||||
test.equal(f.name(), name);
|
||||
}
|
||||
test.equal(f.type(), "text/plain");
|
||||
test.equal(f.size(), 11);
|
||||
}
|
||||
|
||||
function doAttachDataAsyncTest(data, opts, name, test, next) {
|
||||
var f = new FS.File();
|
||||
f.attachData(data, opts, function (error) {
|
||||
test.isFalse(!!error);
|
||||
if (!name) {
|
||||
test.isUndefined(f.name());
|
||||
} else {
|
||||
test.equal(f.name(), name);
|
||||
}
|
||||
test.equal(f.type(), "text/plain");
|
||||
test.equal(f.size(), 11);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* BUFFER
|
||||
*/
|
||||
if (Meteor.isServer) {
|
||||
Tinytest.add('cfs-file - attachData sync - Buffer', function(test) {
|
||||
doAttachDataSyncTest(new Buffer('Hello World'), {type: "text/plain"}, null, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - Buffer', function(test, next) {
|
||||
doAttachDataAsyncTest(new Buffer('Hello World'), {type: "text/plain"}, null, test, next);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* ARRAYBUFFER
|
||||
*/
|
||||
Tinytest.add('cfs-file - attachData sync - ArrayBuffer', function(test) {
|
||||
doAttachDataSyncTest(str2ab('Hello World'), {type: "text/plain"}, null, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - ArrayBuffer', function(test, next) {
|
||||
doAttachDataAsyncTest(str2ab('Hello World'), {type: "text/plain"}, null, test, next);
|
||||
});
|
||||
|
||||
/*
|
||||
* Binary
|
||||
*/
|
||||
Tinytest.add('cfs-file - attachData sync - Binary', function(test) {
|
||||
doAttachDataSyncTest(new Uint8Array(str2ab('Hello World')), {type: "text/plain"}, null, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - Binary', function(test, next) {
|
||||
doAttachDataAsyncTest(new Uint8Array(str2ab('Hello World')), {type: "text/plain"}, null, test, next);
|
||||
});
|
||||
|
||||
/*
|
||||
* Data URI
|
||||
*/
|
||||
var dataUri = 'data:text/plain;base64,SGVsbG8gV29ybGQ='; //'Hello World'
|
||||
Tinytest.add('cfs-file - attachData sync - Data URI', function(test) {
|
||||
doAttachDataSyncTest(dataUri, null, null, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - Data URI', function(test, next) {
|
||||
doAttachDataAsyncTest(dataUri, null, null, test, next);
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-file - attachData from constructor - Data URI', function(test) {
|
||||
doAttachDataConstructorTest(dataUri, null, test);
|
||||
});
|
||||
|
||||
/*
|
||||
* URL
|
||||
*/
|
||||
var url = Meteor.absoluteUrl('test');
|
||||
var url2 = Meteor.absoluteUrl('test.txt');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Tinytest.add('cfs-file - attachData sync - URL', function(test) {
|
||||
doAttachDataSyncTest(url, null, null, test);
|
||||
doAttachDataSyncTest(url2, null, 'test.txt', test);
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-file - attachData from constructor - URL', function(test) {
|
||||
doAttachDataConstructorTest(url, null, test);
|
||||
doAttachDataConstructorTest(url2, 'test.txt', test);
|
||||
});
|
||||
}
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - URL', function(test, next) {
|
||||
doAttachDataAsyncTest(url, null, null, test, function () {
|
||||
doAttachDataAsyncTest(url2, null, 'test.txt', test, next);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Filepath
|
||||
*/
|
||||
if (Meteor.isServer) {
|
||||
Tinytest.add('cfs-file - attachData sync - Filepath', function(test) {
|
||||
doAttachDataSyncTest(tempFilePath, null, tempFileName, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - Filepath', function(test, next) {
|
||||
doAttachDataAsyncTest(tempFilePath, null, tempFileName, test, next);
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-file - attachData from constructor - Filepath', function(test) {
|
||||
doAttachDataConstructorTest(tempFilePath, tempFileName, test);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Blob
|
||||
*/
|
||||
if (Meteor.isClient) {
|
||||
var blob = new Blob(['Hello World'], {type : 'text/plain'});
|
||||
Tinytest.add('cfs-file - attachData sync - Blob', function(test) {
|
||||
doAttachDataSyncTest(blob, null, null, test);
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-file - attachData async - Blob', function(test, next) {
|
||||
doAttachDataAsyncTest(blob, null, null, test, next);
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-file - attachData from constructor - Blob', function(test) {
|
||||
doAttachDataConstructorTest(blob, null, test);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Collection Mounting
|
||||
*/
|
||||
Tinytest.add('cfs-file - isMounted', function(test) {
|
||||
var f = new FS.File();
|
||||
test.isFalse(!!f.isMounted());
|
||||
f.collectionName = "files";
|
||||
test.isTrue(!!f.isMounted());
|
||||
});
|
||||
|
||||
/*
|
||||
* name/extension
|
||||
*/
|
||||
Tinytest.add('cfs-file - name/extension', function(test) {
|
||||
var f = new FS.File();
|
||||
// Set names
|
||||
f.name("foo.pdf");
|
||||
f.name("bar.txt", {store: "files"});
|
||||
// Get names
|
||||
test.equal(f.name(), "foo.pdf");
|
||||
test.equal(f.name({store: "files"}), "bar.txt");
|
||||
// Get extensions
|
||||
test.equal(f.extension(), "pdf");
|
||||
test.equal(f.extension({store: "files"}), "txt");
|
||||
// Now change extensions
|
||||
f.extension("txt");
|
||||
f.extension("pdf", {store: "files"});
|
||||
// Get changed extensions
|
||||
test.equal(f.extension(), "txt");
|
||||
test.equal(f.extension({store: "files"}), "pdf");
|
||||
});
|
||||
|
||||
/*
|
||||
* size
|
||||
*/
|
||||
Tinytest.add('cfs-file - size', function(test) {
|
||||
var f = new FS.File();
|
||||
// Set size
|
||||
f.size(1);
|
||||
f.size(2, {store: "files"});
|
||||
// Get size
|
||||
test.equal(f.size(), 1);
|
||||
test.equal(f.size({store: "files"}), 2);
|
||||
});
|
||||
|
||||
/*
|
||||
* type
|
||||
*/
|
||||
Tinytest.add('cfs-file - type', function(test) {
|
||||
var f = new FS.File();
|
||||
// Set type
|
||||
f.type("image/png");
|
||||
f.type("image/jpg", {store: "files"});
|
||||
// Get type
|
||||
test.equal(f.type(), "image/png");
|
||||
test.equal(f.type({store: "files"}), "image/jpg");
|
||||
});
|
||||
|
||||
/*
|
||||
* updatedAt
|
||||
*/
|
||||
Tinytest.add('cfs-file - updatedAt', function(test) {
|
||||
var f = new FS.File();
|
||||
var d1 = new Date("2014-01-01");
|
||||
var d2 = new Date("2014-02-01");
|
||||
// Set updatedAt
|
||||
f.updatedAt(d1);
|
||||
f.updatedAt(d2, {store: "files"});
|
||||
// Get updatedAt
|
||||
test.equal(f.updatedAt().getTime(), d1.getTime());
|
||||
test.equal(f.updatedAt({store: "files"}).getTime(), d2.getTime());
|
||||
});
|
||||
|
||||
/*
|
||||
* update, uploadProgress, and isUploaded
|
||||
*/
|
||||
Tinytest.addAsync('cfs-file - update, uploadProgress, and isUploaded', function(test, next) {
|
||||
// Progress is based on chunkCount/chunkSum
|
||||
var f = new FS.File('data:text/plain;base64,SGVsbG8gV29ybGQ=');
|
||||
fileCollection.insert(f, function () {
|
||||
f.update({$set: {chunkSum: 2, chunkCount: 1}}, function (error, result) {
|
||||
test.isFalse(!!error);
|
||||
test.equal(f.uploadProgress(), 50);
|
||||
test.isFalse(f.isUploaded());
|
||||
// But if uploadedAt is set, we should always get 100
|
||||
f.update({$set: {uploadedAt: new Date}}, function (error, result) {
|
||||
test.isFalse(!!error);
|
||||
test.equal(f.uploadProgress(), 100);
|
||||
test.isTrue(f.isUploaded());
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* remove
|
||||
*/
|
||||
Tinytest.addAsync('cfs-file - remove', function(test, next) {
|
||||
var f = new FS.File('data:text/plain;base64,SGVsbG8gV29ybGQ=');
|
||||
var newId;
|
||||
fileCollection.insert(f, function (error, fileObj) {
|
||||
test.isFalse(!!error);
|
||||
test.instanceOf(fileObj, FS.File);
|
||||
newId = fileObj._id;
|
||||
test.isTrue(!!fileCollection.findOne(newId));
|
||||
// Wait 5 seconds to remove; otherwise we could
|
||||
// cause errors with the tempstore or SA trying
|
||||
// to save.
|
||||
Meteor.setTimeout(function () {
|
||||
fileObj.remove(function (error, result) {
|
||||
test.isFalse(!!error);
|
||||
test.equal(result, 1);
|
||||
test.isFalse(!!fileCollection.findOne(newId));
|
||||
next();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
if (Meteor.isServer) {
|
||||
/*
|
||||
* createWriteStream
|
||||
*/
|
||||
Tinytest.add('cfs-file - createWriteStream', function(test) {
|
||||
//TODO
|
||||
test.isTrue(true);
|
||||
});
|
||||
|
||||
/*
|
||||
* createReadStream
|
||||
*/
|
||||
Tinytest.add('cfs-file - createReadStream', function(test) {
|
||||
//TODO
|
||||
test.isTrue(true);
|
||||
});
|
||||
}
|
||||
|
||||
//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)
|
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)
|
5
packages/wekan-cfs-gridfs/.travis.yml
Normal file
5
packages/wekan-cfs-gridfs/.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-gridfs/LICENSE.md
Normal file
20
packages/wekan-cfs-gridfs/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.
|
48
packages/wekan-cfs-gridfs/README.md
Normal file
48
packages/wekan-cfs-gridfs/README.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
wekan-cfs-gridfs
|
||||
=========================
|
||||
|
||||
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 [GridFS](http://docs.mongodb.org/manual/core/gridfs/) file storage for
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). When you
|
||||
use this storage adapter, file data is stored in chunks in your MongoDB database.
|
||||
|
||||
## Installation
|
||||
|
||||
Install using Meteorite. When in a Meteor app directory, enter:
|
||||
|
||||
```
|
||||
$ meteor add wekan-cfs-gridfs
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var imageStore = new FS.Store.GridFS("images", {
|
||||
mongoUrl: 'mongodb://127.0.0.1:27017/test/', // optional, defaults to Meteor's local MongoDB
|
||||
mongoOptions: {...}, // optional, see note below
|
||||
transformWrite: myTransformWriteFunction, //optional
|
||||
transformRead: myTransformReadFunction, //optional
|
||||
maxTries: 1, // optional, default 5
|
||||
chunkSize: 1024*1024 // optional, default GridFS chunk size in bytes (can be overridden per file).
|
||||
// Default: 2MB. Reasonable range: 512KB - 4MB
|
||||
});
|
||||
|
||||
Images = new FS.Collection("images", {
|
||||
stores: [imageStore]
|
||||
});
|
||||
```
|
||||
|
||||
More control over the MongoDB connection is available by specifying [MongoClient.connect options](http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html#mongoclient-connect-options) as a `mongoOptions` attribute in the options object on the constructor.
|
||||
|
||||
Refer to the [CollectionFS](https://github.com/zcfs/Meteor-CollectionFS)
|
||||
package documentation for more information.
|
||||
|
||||
## API
|
||||
|
||||
[For Users](https://github.com/zcfs/Meteor-CollectionFS/blob/master/packages/gridfs/api.md)
|
||||
|
||||
[For Contributors](https://github.com/zcfs/Meteor-CollectionFS/blob/master/packages/gridfs/internal.api.md)
|
||||
|
69
packages/wekan-cfs-gridfs/api.md
Normal file
69
packages/wekan-cfs-gridfs/api.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
## cfs-gridfs Public API ##
|
||||
|
||||
GridFS storage adapter for CollectionFS
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GridFS__ is defined in `FS.Store`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
The store name
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __beforeSave__ *{Function}* (Optional)
|
||||
|
||||
Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
|
||||
* __maxTries__ *{Number}* (Optional, Default = 5)
|
||||
|
||||
Max times to attempt saving a file
|
||||
|
||||
|
||||
__Returns__ *{FS.StorageAdapter}*
|
||||
An instance of FS.StorageAdapter.
|
||||
|
||||
|
||||
Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
|
||||
type.
|
||||
|
||||
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __GridFS__ is defined in `FS.Store`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
The store name
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __beforeSave__ *{Function}* (Optional)
|
||||
|
||||
Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
|
||||
* __maxTries__ *{Number}* (Optional, Default = 5)
|
||||
|
||||
Max times to attempt saving a file
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Creates a GridFS store instance on the client, which is just a shell object
|
||||
storing some info.
|
||||
|
||||
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13)
|
||||
|
||||
|
21
packages/wekan-cfs-gridfs/gridfs.client.js
Normal file
21
packages/wekan-cfs-gridfs/gridfs.client.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} name - The store name
|
||||
* @param {Object} options
|
||||
* @param {Function} [options.beforeSave] - Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
* @param {Number} [options.maxTries=5] - Max times to attempt saving a file
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Creates a GridFS store instance on the client, which is just a shell object
|
||||
* storing some info.
|
||||
*/
|
||||
FS.Store.GridFS = function(name, options) {
|
||||
var self = this;
|
||||
if (!(self instanceof FS.Store.GridFS))
|
||||
throw new Error('FS.Store.GridFS missing keyword "new"');
|
||||
|
||||
return new FS.StorageAdapter(name, options, {
|
||||
typeName: 'storage.gridfs'
|
||||
});
|
||||
};
|
176
packages/wekan-cfs-gridfs/gridfs.server.js
Normal file
176
packages/wekan-cfs-gridfs/gridfs.server.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
var path = Npm.require('path');
|
||||
var mongodb = Npm.require('mongodb');
|
||||
var ObjectID = Npm.require('mongodb').ObjectID;
|
||||
var Grid = Npm.require('gridfs-stream');
|
||||
//var Grid = Npm.require('gridfs-locking-stream');
|
||||
|
||||
var chunkSize = 1024*1024*2; // 256k is default GridFS chunk size, but performs terribly for largish files
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {String} name - The store name
|
||||
* @param {Object} options
|
||||
* @param {Function} [options.beforeSave] - Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
* @param {Number} [options.maxTries=5] - Max times to attempt saving a file
|
||||
* @returns {FS.StorageAdapter} An instance of FS.StorageAdapter.
|
||||
*
|
||||
* Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
|
||||
* type.
|
||||
*/
|
||||
|
||||
FS.Store.GridFS = function(name, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
var gridfsName = name;
|
||||
var mongoOptions = options.mongoOptions || {};
|
||||
|
||||
if (!(self instanceof FS.Store.GridFS))
|
||||
throw new Error('FS.Store.GridFS missing keyword "new"');
|
||||
|
||||
if (!options.mongoUrl) {
|
||||
options.mongoUrl = process.env.MONGO_URL;
|
||||
// When using a Meteor MongoDB instance, preface name with "cfs_gridfs."
|
||||
gridfsName = "cfs_gridfs." + name;
|
||||
}
|
||||
|
||||
if (!options.mongoOptions) {
|
||||
options.mongoOptions = { db: { native_parser: true }, server: { auto_reconnect: true }};
|
||||
}
|
||||
|
||||
if (options.chunkSize) {
|
||||
chunkSize = options.chunkSize;
|
||||
}
|
||||
|
||||
return new FS.StorageAdapter(name, options, {
|
||||
|
||||
typeName: 'storage.gridfs',
|
||||
fileKey: function(fileObj) {
|
||||
// We should not have to mount the file here - We assume its taken
|
||||
// care of - Otherwise we create new files instead of overwriting
|
||||
var key = {
|
||||
_id: null,
|
||||
filename: null
|
||||
};
|
||||
|
||||
// If we're passed a fileObj, we retrieve the _id and filename from it.
|
||||
if (fileObj) {
|
||||
var info = fileObj._getInfo(name, {updateFileRecordFirst: false});
|
||||
key._id = info.key || null;
|
||||
key.filename = info.name || fileObj.name({updateFileRecordFirst: false}) || (fileObj.collectionName + '-' + fileObj._id);
|
||||
}
|
||||
|
||||
// If key._id is null at this point, createWriteStream will let GridFS generate a new ID
|
||||
return key;
|
||||
},
|
||||
createReadStream: function(fileKey, options) {
|
||||
options = options || {};
|
||||
|
||||
// Init GridFS
|
||||
var gfs = new Grid(self.db, mongodb);
|
||||
|
||||
// Set the default streamning settings
|
||||
var settings = {
|
||||
_id: new ObjectID(fileKey._id),
|
||||
root: gridfsName
|
||||
};
|
||||
|
||||
// Check if this should be a partial read
|
||||
if (typeof options.start !== 'undefined' && typeof options.end !== 'undefined' ) {
|
||||
// Add partial info
|
||||
settings.range = {
|
||||
startPos: options.start,
|
||||
endPos: options.end
|
||||
};
|
||||
}
|
||||
|
||||
FS.debug && console.log('GRIDFS', settings);
|
||||
|
||||
return gfs.createReadStream(settings);
|
||||
|
||||
},
|
||||
createWriteStream: function(fileKey, options) {
|
||||
options = options || {};
|
||||
|
||||
// Init GridFS
|
||||
var gfs = new Grid(self.db, mongodb);
|
||||
|
||||
var opts = {
|
||||
filename: fileKey.filename,
|
||||
mode: 'w',
|
||||
root: gridfsName,
|
||||
chunk_size: options.chunk_size || chunkSize,
|
||||
// We allow aliases, metadata and contentType to be passed in via
|
||||
// options
|
||||
aliases: options.aliases || [],
|
||||
metadata: options.metadata || null,
|
||||
content_type: options.contentType || 'application/octet-stream'
|
||||
};
|
||||
|
||||
if (fileKey._id) {
|
||||
opts._id = new ObjectID(fileKey._id);
|
||||
}
|
||||
|
||||
var writeStream = gfs.createWriteStream(opts);
|
||||
|
||||
writeStream.on('close', function(file) {
|
||||
if (!file) {
|
||||
// gridfs-stream will emit "close" without passing a file
|
||||
// if there is an error. We can simply exit here because
|
||||
// the "error" listener will also be called in this case.
|
||||
return;
|
||||
}
|
||||
|
||||
if (FS.debug) console.log('SA GridFS - DONE!');
|
||||
|
||||
// Emit end and return the fileKey, size, and updated date
|
||||
writeStream.emit('stored', {
|
||||
// Set the generated _id so that we know it for future reads and writes.
|
||||
// We store the _id as a string and only convert to ObjectID right before
|
||||
// reading, writing, or deleting. If we store the ObjectID itself,
|
||||
// Meteor (EJSON?) seems to convert it to a LocalCollection.ObjectID,
|
||||
// which GFS doesn't understand.
|
||||
fileKey: file._id.toString(),
|
||||
size: file.length,
|
||||
storedAt: file.uploadDate || new Date()
|
||||
});
|
||||
});
|
||||
|
||||
writeStream.on('error', function(error) {
|
||||
console.log('SA GridFS - ERROR!', error);
|
||||
});
|
||||
|
||||
return writeStream;
|
||||
|
||||
},
|
||||
remove: function(fileKey, callback) {
|
||||
// Init GridFS
|
||||
var gfs = new Grid(self.db, mongodb);
|
||||
|
||||
try {
|
||||
gfs.remove({ _id: new ObjectID(fileKey._id), root: gridfsName }, callback);
|
||||
} catch(err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// Not implemented
|
||||
watch: function() {
|
||||
throw new Error("GridFS storage adapter does not support the sync option");
|
||||
},
|
||||
|
||||
init: function(callback) {
|
||||
mongodb.MongoClient.connect(options.mongoUrl, mongoOptions, function (err, db) {
|
||||
if (err) { return callback(err); }
|
||||
self.db = db;
|
||||
|
||||
// ensure that indexes are added as otherwise CollectionFS fails for Mongo >= 3.0
|
||||
var collection = new Mongo.Collection(gridfsName);
|
||||
collection.rawCollection().ensureIndex({ "files_id": 1, "n": 1});
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
75
packages/wekan-cfs-gridfs/internal.api.md
Normal file
75
packages/wekan-cfs-gridfs/internal.api.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["gridfs.server.js"](gridfs.server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GridFS__ is defined in `FS.Store`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
The store name
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __beforeSave__ *{Function}* (Optional)
|
||||
|
||||
Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
|
||||
* __maxTries__ *{Number}* (Optional, Default = 5)
|
||||
|
||||
Max times to attempt saving a file
|
||||
|
||||
|
||||
__Returns__ *{FS.StorageAdapter}*
|
||||
An instance of FS.StorageAdapter.
|
||||
|
||||
|
||||
Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
|
||||
type.
|
||||
|
||||
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["gridfs.client.js"](gridfs.client.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Store.GridFS"></a>new *fsStore*.GridFS(name, options) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __GridFS__ is defined in `FS.Store`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
The store name
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __beforeSave__ *{Function}* (Optional)
|
||||
|
||||
Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
|
||||
|
||||
* __maxTries__ *{Number}* (Optional, Default = 5)
|
||||
|
||||
Max times to attempt saving a file
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Creates a GridFS store instance on the client, which is just a shell object
|
||||
storing some info.
|
||||
|
||||
> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13)
|
||||
|
||||
|
24
packages/wekan-cfs-gridfs/package.js
Executable file
24
packages/wekan-cfs-gridfs/package.js
Executable file
|
@ -0,0 +1,24 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-gridfs',
|
||||
version: '0.0.34',
|
||||
summary: 'GridFS storage adapter for CollectionFS',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-gridfs.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
mongodb: '2.2.9',
|
||||
'gridfs-stream': '1.1.1'
|
||||
//'gridfs-locking-stream': '0.0.3'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-storage-adapter@0.2.3', 'ecmascript@0.1.0']);
|
||||
api.addFiles('gridfs.server.js', 'server');
|
||||
api.addFiles('gridfs.client.js', 'client');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(['wekan-cfs-gridfs', 'test-helpers', 'tinytest'], 'server');
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
});
|
44
packages/wekan-cfs-gridfs/tests/client-tests.js
Normal file
44
packages/wekan-cfs-gridfs/tests/client-tests.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-gridfs - 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-gridfs/tests/server-tests.js
Normal file
49
packages/wekan-cfs-gridfs/tests/server-tests.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-gridfs - 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)
|
18
packages/wekan-cfs-http-methods/.editorconfig
Normal file
18
packages/wekan-cfs-http-methods/.editorconfig
Normal file
|
@ -0,0 +1,18 @@
|
|||
# .editorconfig
|
||||
# Meteor adapted EditorConfig, http://EditorConfig.org
|
||||
# By RaiX 2013
|
||||
|
||||
root = true
|
||||
|
||||
[*.js]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
max_line_length = 80
|
||||
indent_brace_style = 1TBS
|
||||
spaces_around_operators = true
|
||||
quote_type = auto
|
||||
# curly_bracket_next_line = true
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue