From 279c42a9831fade5a3a5fe94b38974481df365e6 Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Thu, 22 Jun 2017 10:27:16 +0300 Subject: [PATCH 1/9] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7676f412 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@veliov.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ From 2f971124a654ffc5b6128abe88fd1655c8ee7586 Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Thu, 22 Jun 2017 10:32:18 +0300 Subject: [PATCH 2/9] Create CONTRIBUTING.md --- CONTRIBUTING.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a5dae9d8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +### I'm having an issue: +1. Search [issues](https://github.com/VeliovGroup/Meteor-Files/issues), maybe your issue is already solved +2. We have useful threads makred as [`In case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=%E2%9C%93&q=label%3A%22In%20case%20of%20the%20fire%20-%20Read%20This%22%20), read them too +3. Before submitting an issue make sure it's only related to `Meteor-Files` package +4. If your issue not solved: + - Give expressive description + - Version of `Meteor-Files` you're experiencing this issue + - Version of `Meteor` you're experiencing this issue + - Is it *Client* or *Server* issue? + - Post *Client* and/or *Server* logs with enabled `debug` option, you can enable "debug" mode in [*Constructor*](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor) + +### I have a suggestion: +1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) + - Always send PRs only to `dev` branch, thank you +2. If you you can not send a PR for some reason: + - Describe your feature / request + - How you going to use it? Give a usage example(s) + +### Documentation is missing something or incorrect (have typos, etc.) +1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) + - Always send PRs only to `dev` branch, thank you +2. If you you can not send a PR for some reason: + - Create a new [issue](https://github.com/VeliovGroup/Meteor-Files/issues) + - Give a short description what you have changed/added and why + - Make sure you're using correct markdown markup + - Make sure all code blocks starts with tripple ``` (*backtick*) and have a syntax tag, for more details read [this docs](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting) From cf2c8888ae4dae07063a1785dab7bac8869f5b4a Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Tue, 18 Jul 2017 06:40:47 +0300 Subject: [PATCH 3/9] Docs update --- docs/react-example.md | 19 ++++++------------- docs/toc.md | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/react-example.md b/docs/react-example.md index 50df0011..09688ef3 100644 --- a/docs/react-example.md +++ b/docs/react-example.md @@ -1,6 +1,4 @@ -# React example - -*This example is for the front-end UI only. The server side methods and publications the same.* +*This example is for the front-end UI only. The server side [methods](https://github.com/VeliovGroup/Meteor-Files/wiki#api) and [publications](https://github.com/VeliovGroup/Meteor-Files/wiki/collection) are the same.* ## Brief: In this example two components is used. First - to handle the uploads, adds a file input box and progress bar. Second - to show the file details (`FileIndividualFile.jsx`). @@ -13,11 +11,11 @@ In this example two components is used. First - to handle the uploads, adds a fi ## FileUpload.jsx: ```jsx -import {ReactMeteorData} from 'meteor/react-meteor-data'; +import { ReactMeteorData } from 'meteor/react-meteor-data'; import React from 'react'; -import {Meteor} from 'meteor/meteor'; +import { Meteor } from 'meteor/meteor'; import IndividualFile from './FileIndividualFile.jsx'; -import {_} from 'meteor/underscore'; +import { _ } from 'meteor/underscore'; const FileUploadComponent = React.createClass({ mixins: [ReactMeteorData], @@ -136,10 +134,8 @@ const FileUploadComponent = React.createClass({ // Run through each file that the user has stored // (make sure the subscription only sends files owned by this user) - - let showit = fileCursors.map((aFile, key) => { + let display = fileCursors.map((aFile, key) => { // console.log('A file: ', aFile.link(), aFile.get('name')); - let link = UserFiles.findOne({_id: aFile._id}).link(); //The "view/download" link // Send out components that show details of each file @@ -172,7 +168,7 @@ const FileUploadComponent = React.createClass({ - {showit} + {display} } @@ -195,7 +191,6 @@ const IndividualFile = React.createClass({ fileId: React.PropTypes.string.isRequired }, - removeFile(){ "use strict"; let conf = confirm('Are you sure you want to delete the file?') || false; @@ -207,7 +202,6 @@ const IndividualFile = React.createClass({ } }, - renameFile(){ "use strict"; @@ -229,7 +223,6 @@ const IndividualFile = React.createClass({ }, render() { - return
diff --git a/docs/toc.md b/docs/toc.md index d81cca2f..c3a7ea96 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -94,7 +94,7 @@ Please see our experimental [webrtc-data-channel](https://github.com/VeliovGroup - [`unlink()`](https://github.com/VeliovGroup/Meteor-Files/wiki/unlink) [*Server*] - Unlink file from FS - [`link()`](https://github.com/VeliovGroup/Meteor-Files/wiki/link) [*Isomorphic*] - Generate downloadable link - [`collection`](https://github.com/VeliovGroup/Meteor-Files/wiki/collection) [*Isomorphic*] - `Meteor.Collection` instance - - [Template helper `fileURL`](https://github.com/VeliovGroup/Meteor-Files/wiki/Template-Helper) [*Client*] - Generate downloadable link in template + - [Template helper `fileURL`](https://github.com/VeliovGroup/Meteor-Files/wiki/Template-Helper) [*Client*] - Generate downloadable link in a template ##### Examples: - [MUP/Docker Persistent Storage](https://github.com/VeliovGroup/Meteor-Files/wiki/MeteorUp-(MUP)-Usage) - Deploy via MeteorUp to Docker container with persistent `storagePath` From e9b01f9a7cf6cc1b07cd2f9a33e1c6a2e9baa4e5 Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Tue, 18 Jul 2017 06:48:45 +0300 Subject: [PATCH 4/9] ISSUE_TEMPLATE Update --- .github/ISSUE_TEMPLATE | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 03c4ab99..74e97f52 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -3,26 +3,26 @@ 2. We have useful threads makred as [`In case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=%E2%9C%93&q=label%3A%22In%20case%20of%20the%20fire%20-%20Read%20This%22%20), read them too 3. Before submitting an issue make sure it's only related to `Meteor-Files` package 4. If your issue not solved: - - Give expressive description - - Version of `Meteor-Files` you're experiencing this issue - - Version of `Meteor` you're experiencing this issue - - Is it *Client* or *Server* issue? - - Post *Client* and/or *Server* logs with enabled `debug` option, you can enable "debug" mode in [*Constructor*](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor) + - Give expressive description + - Version of `Meteor-Files` you're experiencing this issue + - Version of `Meteor` you're experiencing this issue + - Is it *Client* or *Server* issue? + - Post *Client* and/or *Server* logs with enabled `debug` option, you can enable "debug" mode in [*Constructor*](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor) ### I have a suggestion: 1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) - - Always send PRs only to `dev` branch, thank you + - Always send PRs only to `dev` branch, thank you 2. If you you can not send a PR for some reason: - - Describe your feature / request - - How you going to use it? Give a usage example(s) + - Describe your feature / request + - How you going to use it? Give a usage example(s) ### Documentation is missing something or incorrect (have typos, etc.) 1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) - - Always send PRs only to `dev` branch, thank you + - Always send PRs only to `dev` branch, thank you 2. If you you can not send a PR for some reason: - - Give a short description what you have changed/added and why - - Make sure you're using correct markdown markup - - Make sure all code blocks starts with tripple ``` (*backtick*) and have a syntax tag, for more read [this docs](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting) - - Post your addition/changes in issue, we will manage it - + - Give a short description what you have changed/added and why + - Make sure you're using correct markdown markup + - Make sure all code blocks starts with tripple ``` (*backtick*) and have a syntax tag, for more read [this docs](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting) + - Post your addition/changes in issue, we will manage it + ## Thank you, and do not forget to get rid of this default message From 6d6f149621c7d25059506ead90f17e256a1d742e Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Sat, 22 Jul 2017 03:51:44 +0300 Subject: [PATCH 5/9] Rename files --- files-client.coffee => client.coffee | 351 +------------------- files-server.coffee => server.coffee | 462 +-------------------------- 2 files changed, 5 insertions(+), 808 deletions(-) rename files-client.coffee => client.coffee (77%) rename files-server.coffee => server.coffee (79%) diff --git a/files-client.coffee b/client.coffee similarity index 77% rename from files-client.coffee rename to client.coffee index 5ba5b05d..fab79f38 100755 --- a/files-client.coffee +++ b/client.coffee @@ -1,330 +1,9 @@ `import { Cookies } from 'meteor/ostrio:cookies'` +`import { FilesCursor, FileCursor } from './files-cursor.coffee` +`import { fixJSONParse, fixJSONStringify, formatFleURL } from './files-lib.coffee` NOOP = -> return EventEmitter = require('./event-emitter.jsx').EventEmitter -### -@private -@locus Anywhere -@class FileCursor -@param _fileRef {Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) -@param _collection {FilesCollection} - FilesCollection Instance -@summary Internal class, represents each record in `FilesCursor.each()` or document returned from `.findOne()` method -### -class FileCursor - constructor: (@_fileRef, @_collection) -> - self = @ - self = _.extend self, @_fileRef - - ### - @locus Anywhere - @memberOf FileCursor - @name remove - @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed - @summary Remove document - @returns {FileCursor} - ### - remove: (callback) -> - console.info '[FilesCollection] [FileCursor] [remove()]' if @_collection.debug - if @_fileRef - @_collection.remove(@_fileRef._id, callback) - else - callback and callback new Meteor.Error 404, 'No such file' - return @ - - ### - @locus Anywhere - @memberOf FileCursor - @name link - @param version {String} - Name of file's subversion - @summary Returns downloadable URL to File - @returns {String} - ### - link: (version) -> - console.info "[FilesCollection] [FileCursor] [link(#{version})]" if @_collection.debug - return if @_fileRef then @_collection.link(@_fileRef, version) else '' - - ### - @locus Anywhere - @memberOf FileCursor - @name get - @param property {String} - Name of sub-object property - @summary Returns current document as a plain Object, if `property` is specified - returns value of sub-object property - @returns {Object|mix} - ### - get: (property) -> - console.info "[FilesCollection] [FileCursor] [get(#{property})]" if @_collection.debug - if property - return @_fileRef[property] - else - return @_fileRef - - ### - @locus Anywhere - @memberOf FileCursor - @name fetch - @summary Returns document as plain Object in Array - @returns {[Object]} - ### - fetch: -> - console.info '[FilesCollection] [FileCursor] [fetch()]' if @_collection.debug - return [@_fileRef] - - ### - @locus Anywhere - @memberOf FileCursor - @name with - @summary Returns reactive version of current FileCursor, useful to use with `{{#with}}...{{/with}}` block template helper - @returns {[Object]} - ### - with: -> - console.info '[FilesCollection] [FileCursor] [with()]' if @_collection.debug - self = @ - return _.extend self, @_collection.collection.findOne @_fileRef._id - -### -@private -@locus Anywhere -@class FilesCursor -@param _selector {String|Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) -@param options {Object} - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#selectors) -@param _collection {FilesCollection} - FilesCollection Instance -@summary Implementation of Cursor for FilesCollection -### -class FilesCursor - constructor: (@_selector = {}, options, @_collection) -> - @_current = -1 - @cursor = @_collection.collection.find @_selector, options - - ### - @locus Anywhere - @memberOf FilesCursor - @name get - @summary Returns all matching document(s) as an Array. Alias of `.fetch()` - @returns {[Object]} - ### - get: -> - console.info "[FilesCollection] [FilesCursor] [get()]" if @_collection.debug - return @cursor.fetch() - - ### - @locus Anywhere - @memberOf FilesCursor - @name hasNext - @summary Returns `true` if there is next item available on Cursor - @returns {Boolean} - ### - hasNext: -> - console.info '[FilesCollection] [FilesCursor] [hasNext()]' if @_collection.debug - return @_current < @cursor.count() - 1 - - ### - @locus Anywhere - @memberOf FilesCursor - @name next - @summary Returns next item on Cursor, if available - @returns {Object|undefined} - ### - next: -> - console.info '[FilesCollection] [FilesCursor] [next()]' if @_collection.debug - if @hasNext() - return @cursor.fetch()[++@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name hasPrevious - @summary Returns `true` if there is previous item available on Cursor - @returns {Boolean} - ### - hasPrevious: -> - console.info '[FilesCollection] [FilesCursor] [hasPrevious()]' if @_collection.debug - return @_current isnt -1 - - ### - @locus Anywhere - @memberOf FilesCursor - @name previous - @summary Returns previous item on Cursor, if available - @returns {Object|undefined} - ### - previous: -> - console.info '[FilesCollection] [FilesCursor] [previous()]' if @_collection.debug - if @hasPrevious() - return @cursor.fetch()[--@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name fetch - @summary Returns all matching document(s) as an Array. - @returns {[Object]} - ### - fetch: -> - console.info '[FilesCollection] [FilesCursor] [fetch()]' if @_collection.debug - return @cursor.fetch() - - ### - @locus Anywhere - @memberOf FilesCursor - @name first - @summary Returns first item on Cursor, if available - @returns {Object|undefined} - ### - first: -> - console.info '[FilesCollection] [FilesCursor] [first()]' if @_collection.debug - @_current = 0 - return @fetch()?[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name last - @summary Returns last item on Cursor, if available - @returns {Object|undefined} - ### - last: -> - console.info '[FilesCollection] [FilesCursor] [last()]' if @_collection.debug - @_current = @count() - 1 - return @fetch()?[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name count - @summary Returns the number of documents that match a query - @returns {Number} - ### - count: -> - console.info '[FilesCollection] [FilesCursor] [count()]' if @_collection.debug - return @cursor.count() - - ### - @locus Anywhere - @memberOf FilesCursor - @name remove - @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed - @summary Removes all documents that match a query - @returns {FilesCursor} - ### - remove: (callback) -> - console.info '[FilesCollection] [FilesCursor] [remove()]' if @_collection.debug - @_collection.remove @_selector, callback - return @ - - ### - @locus Anywhere - @memberOf FilesCursor - @name forEach - @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself - @param context {Object} - An object which will be the value of `this` inside `callback` - @summary Call `callback` once for each matching document, sequentially and synchronously. - @returns {undefined} - ### - forEach: (callback, context = {}) -> - console.info '[FilesCollection] [FilesCursor] [forEach()]' if @_collection.debug - @cursor.forEach callback, context - return - - ### - @locus Anywhere - @memberOf FilesCursor - @name each - @summary Returns an Array of FileCursor made for each document on current cursor - Useful when using in {{#each FilesCursor#each}}...{{/each}} block template helper - @returns {[FileCursor]} - ### - each: -> - self = @ - return @map (file) -> - return new FileCursor file, self._collection - - ### - @locus Anywhere - @memberOf FilesCursor - @name map - @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself - @param context {Object} - An object which will be the value of `this` inside `callback` - @summary Map `callback` over all matching documents. Returns an Array. - @returns {Array} - ### - map: (callback, context = {}) -> - console.info '[FilesCollection] [FilesCursor] [map()]' if @_collection.debug - return @cursor.map callback, context - - ### - @locus Anywhere - @memberOf FilesCursor - @name current - @summary Returns current item on Cursor, if available - @returns {Object|undefined} - ### - current: -> - console.info '[FilesCollection] [FilesCursor] [current()]' if @_collection.debug - @_current = 0 if @_current < 0 - return @fetch()[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name observe - @param callbacks {Object} - Functions to call to deliver the result set as it changes - @summary Watch a query. Receive callbacks as the result set changes. - @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observe - @returns {Object} - live query handle - ### - observe: (callbacks) -> - console.info '[FilesCollection] [FilesCursor] [observe()]' if @_collection.debug - return @cursor.observe callbacks - - ### - @locus Anywhere - @memberOf FilesCursor - @name observeChanges - @param callbacks {Object} - Functions to call to deliver the result set as it changes - @summary Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks. - @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observeChanges - @returns {Object} - live query handle - ### - observeChanges: (callbacks) -> - console.info '[FilesCollection] [FilesCursor] [observeChanges()]' if @_collection.debug - return @cursor.observeChanges callbacks - -### -@var {Function} fixJSONParse - Fix issue with Date parse -### -fixJSONParse = (obj) -> - for key, value of obj - if _.isString(value) and !!~value.indexOf '=--JSON-DATE--=' - value = value.replace '=--JSON-DATE--=', '' - obj[key] = new Date parseInt value - else if _.isObject value - obj[key] = fixJSONParse value - else if _.isArray value - for v, i in value - if _.isObject(v) - obj[key][i] = fixJSONParse v - else if _.isString(v) and !!~v.indexOf '=--JSON-DATE--=' - v = v.replace '=--JSON-DATE--=', '' - obj[key][i] = new Date parseInt v - return obj - -### -@var {Function} fixJSONStringify - Fix issue with Date stringify -### -fixJSONStringify = (obj) -> - for key, value of obj - if _.isDate value - obj[key] = '=--JSON-DATE--=' + (+value) - else if _.isObject value - obj[key] = fixJSONStringify value - else if _.isArray value - for v, i in value - if _.isObject(v) - obj[key][i] = fixJSONStringify v - else if _.isDate v - obj[key][i] = '=--JSON-DATE--=' + (+v) - return obj - ### @locus Anywhere @class FilesCollection @@ -1272,32 +951,6 @@ class FilesCollection return '' if not fileRef return formatFleURL fileRef, version -### -@locus Anywhere -@private -@name formatFleURL -@param {Object} fileRef - File reference object -@param {String} version - [Optional] Version of file you would like build URL for -@summary Returns formatted URL for file -@returns {String} Downloadable link -### -formatFleURL = (fileRef, version = 'original') -> - check fileRef, Object - check version, String - - root = __meteor_runtime_config__.ROOT_URL.replace(/\/+$/, '') - - if fileRef.extension?.length - ext = '.' + fileRef.extension - else - ext = '' - - if fileRef.public is true - return root + (if version is 'original' then "#{fileRef._downloadRoute}/#{fileRef._id}#{ext}" else "#{fileRef._downloadRoute}/#{version}-#{fileRef._id}#{ext}") - else - return root + "#{fileRef._downloadRoute}/#{fileRef._collectionName}/#{fileRef._id}/#{version}/#{fileRef._id}#{ext}" - - ### @locus Client @TemplateHelper diff --git a/files-server.coffee b/server.coffee similarity index 79% rename from files-server.coffee rename to server.coffee index 298e21af..9ccc2ab5 100644 --- a/files-server.coffee +++ b/server.coffee @@ -1,4 +1,7 @@ `import { Cookies } from 'meteor/ostrio:cookies'` +`import { FilesCursor, FileCursor } from './files-cursor.coffee` +`import { writeStream } from './files-write-stream.coffee'` +`import { fixJSONParse, fixJSONStringify, formatFleURL } from './files-lib.coffee` NOOP = -> return ### @@ -16,440 +19,6 @@ NOOP = -> return ### bound = Meteor.bindEnvironment (callback) -> return callback() -### -@private -@locus Server -@class writeStream -@param path {String} - Path to file on FS -@param maxLength {Number} - Max amount of chunks in stream -@param file {Object} - fileRef Object -@summary writableStream wrapper class, makes sure chunks is written in given order. Implementation of queue stream. -### -class writeStream - constructor: (@path, @maxLength, @file, @permissions) -> - if not @path or not _.isString @path - return - - self = @ - @fd = null - fs.open @path, 'w+', @permissions, (error, fd) -> bound -> - if error - throw new Meteor.Error 500, '[FilesCollection] [writeStream] [Exception:]', error - else - self.fd = fd - return - @ended = false - @aborted = false - @writtenChunks = 0 - - ### - @memberOf writeStream - @name write - @param {Number} num - Chunk position in stream - @param {Buffer} chunk - Chunk binary data - @param {Function} callback - Callback - @summary Write chunk in given order - @returns {Boolean} - True if chunk is sent to stream, false if chunk is set into queue - ### - write: (num, chunk, callback) -> - if not @aborted and not @ended - self = @ - if @fd - _stream = fs.createWriteStream @path, { - flags: 'r+' - mode: @permissions - highWaterMark: 0 - fd: @fd - autoClose: true - start: (num - 1) * @file.chunkSize - } - _stream.on 'error', (error) -> bound -> - console.error "[FilesCollection] [writeStream] [ERROR:]", error - self.abort() - return - _stream.write chunk, -> bound -> - ++self.writtenChunks - callback and callback() - return - else - Meteor.setTimeout -> - self.write num, chunk, callback - return - , 25 - return false - - ### - @memberOf writeStream - @name end - @param {Function} callback - Callback - @summary Finishes writing to writableStream, only after all chunks in queue is written - @returns {Boolean} - True if stream is fulfilled, false if queue is in progress - ### - end: (callback) -> - if not @aborted and not @ended - if @writtenChunks is @maxLength - self = @ - fs.close @fd, -> bound -> - self.ended = true - callback and callback(true) - return - return true - else - self = @ - Meteor.setTimeout -> - self.end callback - return - , 25 - else - callback and callback(false) - return false - - ### - @memberOf writeStream - @name abort - @param {Function} callback - Callback - @summary Aborts writing to writableStream, removes created file - @returns {Boolean} - True - ### - abort: (callback) -> - @aborted = true - fs.unlink @path, (callback or NOOP) - return true - - ### - @memberOf writeStream - @name stop - @summary Stop writing to writableStream - @returns {Boolean} - True - ### - stop: -> - @aborted = true - @ended = true - return true - -### -@private -@locus Anywhere -@class FileCursor -@param _fileRef {Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) -@param _collection {FilesCollection} - FilesCollection Instance -@summary Internal class, represents each record in `FilesCursor.each()` or document returned from `.findOne()` method -### -class FileCursor - constructor: (@_fileRef, @_collection) -> - self = @ - self = _.extend self, @_fileRef - - ### - @locus Anywhere - @memberOf FileCursor - @name remove - @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed - @summary Remove document - @returns {FileCursor} - ### - remove: (callback) -> - console.info '[FilesCollection] [FileCursor] [remove()]' if @_collection.debug - if @_fileRef - @_collection.remove(@_fileRef._id, callback) - else - callback and callback new Meteor.Error 404, 'No such file' - return @ - - ### - @locus Anywhere - @memberOf FileCursor - @name link - @param version {String} - Name of file's subversion - @summary Returns downloadable URL to File - @returns {String} - ### - link: (version) -> - console.info "[FilesCollection] [FileCursor] [link(#{version})]" if @_collection.debug - return if @_fileRef then @_collection.link(@_fileRef, version) else '' - - ### - @locus Anywhere - @memberOf FileCursor - @name get - @param property {String} - Name of sub-object property - @summary Returns current document as a plain Object, if `property` is specified - returns value of sub-object property - @returns {Object|mix} - ### - get: (property) -> - console.info "[FilesCollection] [FileCursor] [get(#{property})]" if @_collection.debug - if property - return @_fileRef[property] - else - return @_fileRef - - ### - @locus Anywhere - @memberOf FileCursor - @name fetch - @summary Returns document as plain Object in Array - @returns {[Object]} - ### - fetch: -> - console.info '[FilesCollection] [FileCursor] [fetch()]' if @_collection.debug - return [@_fileRef] - - ### - @locus Anywhere - @memberOf FileCursor - @name with - @summary Returns reactive version of current FileCursor, useful to use with `{{#with}}...{{/with}}` block template helper - @returns {[Object]} - ### - with: -> - console.info '[FilesCollection] [FileCursor] [with()]' if @_collection.debug - self = @ - return _.extend self, @_collection.collection.findOne @_fileRef._id - -### -@private -@locus Anywhere -@class FilesCursor -@param _selector {String|Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) -@param options {Object} - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#selectors) -@param _collection {FilesCollection} - FilesCollection Instance -@summary Implementation of Cursor for FilesCollection -### -class FilesCursor - constructor: (@_selector = {}, options, @_collection) -> - @_current = -1 - @cursor = @_collection.collection.find @_selector, options - - ### - @locus Anywhere - @memberOf FilesCursor - @name get - @summary Returns all matching document(s) as an Array. Alias of `.fetch()` - @returns {[Object]} - ### - get: -> - console.info "[FilesCollection] [FilesCursor] [get()]" if @_collection.debug - return @cursor.fetch() - - ### - @locus Anywhere - @memberOf FilesCursor - @name hasNext - @summary Returns `true` if there is next item available on Cursor - @returns {Boolean} - ### - hasNext: -> - console.info '[FilesCollection] [FilesCursor] [hasNext()]' if @_collection.debug - return @_current < @cursor.count() - 1 - - ### - @locus Anywhere - @memberOf FilesCursor - @name next - @summary Returns next item on Cursor, if available - @returns {Object|undefined} - ### - next: -> - console.info '[FilesCollection] [FilesCursor] [next()]' if @_collection.debug - if @hasNext() - return @cursor.fetch()[++@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name hasPrevious - @summary Returns `true` if there is previous item available on Cursor - @returns {Boolean} - ### - hasPrevious: -> - console.info '[FilesCollection] [FilesCursor] [hasPrevious()]' if @_collection.debug - return @_current isnt -1 - - ### - @locus Anywhere - @memberOf FilesCursor - @name previous - @summary Returns previous item on Cursor, if available - @returns {Object|undefined} - ### - previous: -> - console.info '[FilesCollection] [FilesCursor] [previous()]' if @_collection.debug - if @hasPrevious() - return @cursor.fetch()[--@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name fetch - @summary Returns all matching document(s) as an Array. - @returns {[Object]} - ### - fetch: -> - console.info '[FilesCollection] [FilesCursor] [fetch()]' if @_collection.debug - return @cursor.fetch() - - ### - @locus Anywhere - @memberOf FilesCursor - @name first - @summary Returns first item on Cursor, if available - @returns {Object|undefined} - ### - first: -> - console.info '[FilesCollection] [FilesCursor] [first()]' if @_collection.debug - @_current = 0 - return @fetch()?[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name last - @summary Returns last item on Cursor, if available - @returns {Object|undefined} - ### - last: -> - console.info '[FilesCollection] [FilesCursor] [last()]' if @_collection.debug - @_current = @count() - 1 - return @fetch()?[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name count - @summary Returns the number of documents that match a query - @returns {Number} - ### - count: -> - console.info '[FilesCollection] [FilesCursor] [count()]' if @_collection.debug - return @cursor.count() - - ### - @locus Anywhere - @memberOf FilesCursor - @name remove - @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed - @summary Removes all documents that match a query - @returns {FilesCursor} - ### - remove: (callback) -> - console.info '[FilesCollection] [FilesCursor] [remove()]' if @_collection.debug - @_collection.remove @_selector, callback - return @ - - ### - @locus Anywhere - @memberOf FilesCursor - @name forEach - @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself - @param context {Object} - An object which will be the value of `this` inside `callback` - @summary Call `callback` once for each matching document, sequentially and synchronously. - @returns {undefined} - ### - forEach: (callback, context = {}) -> - console.info '[FilesCollection] [FilesCursor] [forEach()]' if @_collection.debug - @cursor.forEach callback, context - return - - ### - @locus Anywhere - @memberOf FilesCursor - @name each - @summary Returns an Array of FileCursor made for each document on current cursor - Useful when using in {{#each FilesCursor#each}}...{{/each}} block template helper - @returns {[FileCursor]} - ### - each: -> - self = @ - return @map (file) -> - return new FileCursor file, self._collection - - ### - @locus Anywhere - @memberOf FilesCursor - @name map - @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself - @param context {Object} - An object which will be the value of `this` inside `callback` - @summary Map `callback` over all matching documents. Returns an Array. - @returns {Array} - ### - map: (callback, context = {}) -> - console.info '[FilesCollection] [FilesCursor] [map()]' if @_collection.debug - return @cursor.map callback, context - - ### - @locus Anywhere - @memberOf FilesCursor - @name current - @summary Returns current item on Cursor, if available - @returns {Object|undefined} - ### - current: -> - console.info '[FilesCollection] [FilesCursor] [current()]' if @_collection.debug - @_current = 0 if @_current < 0 - return @fetch()[@_current] - - ### - @locus Anywhere - @memberOf FilesCursor - @name observe - @param callbacks {Object} - Functions to call to deliver the result set as it changes - @summary Watch a query. Receive callbacks as the result set changes. - @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observe - @returns {Object} - live query handle - ### - observe: (callbacks) -> - console.info '[FilesCollection] [FilesCursor] [observe()]' if @_collection.debug - return @cursor.observe callbacks - - ### - @locus Anywhere - @memberOf FilesCursor - @name observeChanges - @param callbacks {Object} - Functions to call to deliver the result set as it changes - @summary Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks. - @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observeChanges - @returns {Object} - live query handle - ### - observeChanges: (callbacks) -> - console.info '[FilesCollection] [FilesCursor] [observeChanges()]' if @_collection.debug - return @cursor.observeChanges callbacks - -### -@var {Function} fixJSONParse - Fix issue with Date parse -### -fixJSONParse = (obj) -> - for key, value of obj - if _.isString(value) and !!~value.indexOf '=--JSON-DATE--=' - value = value.replace '=--JSON-DATE--=', '' - obj[key] = new Date parseInt value - else if _.isObject value - obj[key] = fixJSONParse value - else if _.isArray value - for v, i in value - if _.isObject(v) - obj[key][i] = fixJSONParse v - else if _.isString(v) and !!~v.indexOf '=--JSON-DATE--=' - v = v.replace '=--JSON-DATE--=', '' - obj[key][i] = new Date parseInt v - return obj - -### -@var {Function} fixJSONStringify - Fix issue with Date stringify -### -fixJSONStringify = (obj) -> - for key, value of obj - if _.isDate value - obj[key] = '=--JSON-DATE--=' + (+value) - else if _.isObject value - obj[key] = fixJSONStringify value - else if _.isArray value - for v, i in value - if _.isObject(v) - obj[key][i] = fixJSONStringify v - else if _.isDate v - obj[key][i] = '=--JSON-DATE--=' + (+v) - return obj - ### @locus Anywhere @class FilesCollection @@ -1898,31 +1467,6 @@ class FilesCollection return '' if not fileRef return formatFleURL fileRef, version -### -@locus Anywhere -@private -@name formatFleURL -@param {Object} fileRef - File reference object -@param {String} version - [Optional] Version of file you would like build URL for -@summary Returns formatted URL for file -@returns {String} Downloadable link -### -formatFleURL = (fileRef, version = 'original') -> - check fileRef, Object - check version, String - - root = __meteor_runtime_config__.ROOT_URL.replace(/\/+$/, '') - - if fileRef.extension?.length - ext = '.' + fileRef.extension - else - ext = '' - - if fileRef.public is true - return root + (if version is 'original' then "#{fileRef._downloadRoute}/#{fileRef._id}#{ext}" else "#{fileRef._downloadRoute}/#{version}-#{fileRef._id}#{ext}") - else - return root + "#{fileRef._downloadRoute}/#{fileRef._collectionName}/#{fileRef._id}/#{version}/#{fileRef._id}#{ext}" - ### Export the FilesCollection class ### From e6f5d63894c4f7ca356c986e3ad039f63d04c9d2 Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Sat, 22 Jul 2017 03:52:04 +0300 Subject: [PATCH 6/9] Docs update --- .github/ISSUE_TEMPLATE | 2 +- CONTRIBUTING.md | 6 +++--- README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 74e97f52..d45115d9 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1,6 +1,6 @@ ### I'm having an issue: 1. Search [issues](https://github.com/VeliovGroup/Meteor-Files/issues), maybe your issue is already solved - 2. We have useful threads makred as [`In case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=%E2%9C%93&q=label%3A%22In%20case%20of%20the%20fire%20-%20Read%20This%22%20), read them too + 2. We have useful threads makred as [`In a case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=✓&q=label%3A%22In%20a%20case%20of%20the%20fire%20-%20Read%20This%22), read them too 3. Before submitting an issue make sure it's only related to `Meteor-Files` package 4. If your issue not solved: - Give expressive description diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b93f9de1..d8625fef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ### I'm having an issue: 1. Search [issues](https://github.com/VeliovGroup/Meteor-Files/issues), maybe your issue is already solved - 2. We have useful threads makred as [`In case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=%E2%9C%93&q=label%3A%22In%20case%20of%20the%20fire%20-%20Read%20This%22%20), read them too + 2. We have useful threads makred as [`In a case of the fire - Read This`](https://github.com/VeliovGroup/Meteor-Files/issues?utf8=✓&q=label%3A%22In%20a%20case%20of%20the%20fire%20-%20Read%20This%22), read them too 3. Before submitting an issue make sure it's only related to `Meteor-Files` package 4. If your issue not solved: - Give expressive description @@ -11,14 +11,14 @@ ### I have a suggestion: 1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) - - Always send PRs only to `dev` branch, thank you + - Always send PRs only to [`dev` branch](https://github.com/VeliovGroup/Meteor-Files/compare/dev), thank you 2. If you you can not send a PR for some reason: - Describe your feature / request - How you going to use it? Give a usage example(s) ### Documentation is missing something or incorrect (have typos, etc.) 1. PRs is always welcome - [send a PR](https://github.com/VeliovGroup/Meteor-Files/compare) - - Always send PRs only to `dev` branch, thank you + - Always send PRs only to [`dev` branch](https://github.com/VeliovGroup/Meteor-Files/compare/dev), thank you 2. If you you can not send a PR for some reason: - Give a short description what you have changed/added and why - Make sure you're using correct markdown markup diff --git a/README.md b/README.md index 4260fd9d..21ccb275 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Support: Support Meteor-Files project: ======== - - Try [ostr.io](https://ostr.io) - The toolkit for web-developers. + - Try [ostr.io](https://ostr.io) - The toolkit for web-developers. By using [ostr.io](https://ostr.io) you are not only [protecting domain names](https://ostr.io/info/domain-names-protection), [monitoring websites and servers](https://ostr.io/info/monitoring), using [Prerendering for better SEO](https://ostr.io/info/prerendering) of your JavaScript website, but support our Open Source activity, and great packages like this one are available for free. - [Donate via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FVDSXRFW9VGA2) - Star on [GitHub](https://github.com/VeliovGroup/Meteor-Files) - Star on [Atmosphere](https://atmospherejs.com/ostrio/files) From e26ca7c35a84eeead207e0612eac9c6ebe730c61 Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Sun, 23 Jul 2017 02:30:20 +0300 Subject: [PATCH 7/9] Exact code splitting --- client.coffee | 243 +++++++++---------------------------- core.coffee | 149 +++++++++++++++++++++++ cursor.coffee | 288 ++++++++++++++++++++++++++++++++++++++++++++ lib.coffee | 62 ++++++++++ server.coffee | 238 ++++++++---------------------------- write-stream.coffee | 130 ++++++++++++++++++++ 6 files changed, 733 insertions(+), 377 deletions(-) create mode 100644 core.coffee create mode 100644 cursor.coffee create mode 100644 lib.coffee create mode 100644 write-stream.coffee diff --git a/client.coffee b/client.coffee index fab79f38..a7b85758 100755 --- a/client.coffee +++ b/client.coffee @@ -1,8 +1,10 @@ `import { Cookies } from 'meteor/ostrio:cookies'` -`import { FilesCursor, FileCursor } from './files-cursor.coffee` -`import { fixJSONParse, fixJSONStringify, formatFleURL } from './files-lib.coffee` -NOOP = -> return -EventEmitter = require('./event-emitter.jsx').EventEmitter +`import { EventEmitter} from './event-emitter.jsx'` +`import { FilesCollectionCore } from './core.coffee'` +`import { fixJSONParse, fixJSONStringify, formatFleURL } from './lib.coffee'` + +isSafari = /^((?!chrome|android).)*safari/i.test navigator.userAgent +NOOP = -> return ### @locus Anywhere @@ -28,7 +30,7 @@ return `false` or `String` to abort upload @summary Create new instance of FilesCollection ### class FilesCollection - __proto__: do -> EventEmitter.prototype + __proto__: do -> _.extend EventEmitter.prototype, FilesCollectionCore.prototype constructor: (config) -> EventEmitter.call @ {storagePath, @ddp, @collection, @collectionName, @downloadRoute, @schema, @chunkSize, @namingFunction, @debug, @onbeforeunloadMessage, @permissions, @parentDirPermissions, @allowClientCode, @onBeforeUpload, @onInitiateUpload, @integrityCheck, @protected, @public, @strict, @downloadCallback, @cacheControl, @responseHeaders, @throttle, @onAfterUpload, @onAfterRemove, @interceptDownload, @onBeforeRemove, @continueUploadTTL} = config if config @@ -36,6 +38,8 @@ class FilesCollection self = @ cookie = new Cookies() @debug ?= false + @_debug = -> + (console.info || console.log).apply undefined, arguments if self.debug @public ?= false @protected ?= false @chunkSize ?= 1024*512 @@ -120,7 +124,7 @@ class FilesCollection else @_supportWebWorker = false catch e - console.warn('[FilesCollection] [Check WebWorker Availability] Error:', e) if self.debug + self._debug '[FilesCollection] [Check WebWorker Availability] Error:', e @_supportWebWorker = false if not @schema @@ -189,7 +193,7 @@ class FilesCollection return true else rc = if _.isNumber(result) then result else 401 - console.warn '[FilesCollection._checkAccess] WARN: Access denied!' if self.debug + self._debug '[FilesCollection._checkAccess] WARN: Access denied!' if http text = 'Access denied!' if !http.response.headersSent @@ -224,21 +228,6 @@ class FilesCollection mime = 'application/octet-stream' return mime - ### - @locus Anywhere - @memberOf FilesCollection - @name _getFileName - @param {Object} fileData - File Object - @summary Returns file's name - @returns {String} - ### - _getFileName: (fileData) -> - fileName = fileData.name or fileData.fileName - if _.isString(fileName) and fileName.length > 0 - return (fileData.name or fileData.fileName).replace(/\.\./g, '').replace /\//g, '' - else - return '' - ### @locus Anywhere @memberOf FilesCollection @@ -257,106 +246,6 @@ class FilesCollection return result - ### - @locus Anywhere - @memberOf FilesCollection - @name _getExt - @param {String} FileName - File name - @summary Get extension from FileName - @returns {Object} - ### - _getExt: (fileName) -> - if !!~fileName.indexOf('.') - extension = (fileName.split('.').pop().split('?')[0] or '').toLowerCase() - return { ext: extension, extension, extensionWithDot: '.' + extension } - else - return { ext: '', extension: '', extensionWithDot: '' } - - ### - @locus Anywhere - @memberOf FilesCollection - @name _updateFileTypes - @param {Object} data - File data - @summary Internal method. Classify file based on 'type' field - ### - _updateFileTypes: (data) -> - data.isVideo = /^video\//i.test data.type - data.isAudio = /^audio\//i.test data.type - data.isImage = /^image\//i.test data.type - data.isText = /^text\//i.test data.type - data.isJSON = /^application\/json$/i.test data.type - data.isPDF = /^application\/(x-)?pdf$/i.test data.type - return - - ### - @locus Anywhere - @memberOf FilesCollection - @name _dataToSchema - @param {Object} data - File data - @summary Internal method. Build object in accordance with default schema from File data - @returns {Object} - ### - _dataToSchema: (data) -> - ds = - name: data.name - extension: data.extension - path: data.path - meta: data.meta - type: data.type - size: data.size - userId: data.userId or null - versions: - original: - path: data.path - size: data.size - type: data.type - extension: data.extension - _downloadRoute: data._downloadRoute or @downloadRoute - _collectionName: data._collectionName or @collectionName - - #Optional fileId - if data.fileId - ds._id = data.fileId; - - @_updateFileTypes ds - ds._storagePath = data._storagePath or @storagePath(_.extend(data, ds)) - return ds - - ### - @locus Anywhere - @memberOf FilesCollection - @name findOne - @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) - @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) - @summary Find and return Cursor for matching document Object - @returns {FileCursor} Instance - ### - findOne: (selector, options) -> - console.info "[FilesCollection] [findOne(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" if @debug - check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null - check options, Match.Optional Object - - selector = {} unless arguments.length - doc = @collection.findOne selector, options - return if doc then new FileCursor(doc, @) else doc - - ### - @locus Anywhere - @memberOf FilesCollection - @name find - @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) - @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) - @summary Find and return Cursor for matching documents - @returns {FilesCursor} Instance - ### - find: (selector, options) -> - console.info "[FilesCollection] [find(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" if @debug - check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null - check options, Match.Optional Object - - selector = {} unless arguments.length - return new FilesCursor selector, options, @ - ### @locus Client @memberOf FilesCollection @@ -403,7 +292,7 @@ class FilesCollection __proto__: EventEmitter.prototype constructor: (@config, @collection) -> EventEmitter.call @ - console.info '[FilesCollection] [insert()]' if @collection.debug + @collection._debug '[FilesCollection] [insert()]' self = @ @config.ddp ?= @collection.ddp @config.meta ?= {} @@ -474,21 +363,22 @@ class FilesCollection @worker = new Worker @collection._webWorkerUrl catch wwError @worker = false - console.warn '[FilesCollection] [insert] [create WebWorker]: Can\'t create WebWorker, fallback to MainThread', wwError if @collection.debug + @collection._debug '[FilesCollection] [insert] [create WebWorker]: Can\'t create WebWorker, fallback to MainThread', wwError else @worker = null - @startTime = {} - @config.debug = @collection.debug - @currentChunk = 0 - @transferTime = 0 - @trackerComp = null - @sentChunks = 0 - @fileLength = 1 - @EOFsent = false - @fileId = Random.id() - @FSName = if @collection.namingFunction then @collection.namingFunction(@fileData) else @fileId - @pipes = [] + @startTime = {} + @config.debug = @collection.debug + @config._debug = @collection._debug + @currentChunk = 0 + @transferTime = 0 + @trackerComp = null + @sentChunks = 0 + @fileLength = 1 + @EOFsent = false + @fileId = Random.id() + @FSName = if @collection.namingFunction then @collection.namingFunction(@fileData) else @fileId + @pipes = [] @fileData = _.extend @fileData, @collection._getExt(self.fileData.name), {mime: @collection._getMimeType(@fileData)} @fileData['mime-type'] = @fileData.mime @@ -534,12 +424,13 @@ class FilesCollection throw new Meteor.Error 500, '[FilesCollection] [insert] Have you forget to pass a File itself?' end: (error, data) -> + @collection._debug '[FilesCollection] [UploadInstance] [end]', @fileData.name console.timeEnd('insert ' + @fileData.name) if @collection.debug @emitEvent '_onEnd' @result.emitEvent 'uploaded', [error, data] @config.onUploaded and @config.onUploaded.call @result, error, data if error - console.error '[FilesCollection] [insert] [end] Error:', error if @collection.debug + @collection._debug '[FilesCollection] [insert] [end] Error:', error @result.abort() @result.state.set 'aborted' @result.emitEvent 'error', [error, @fileData] @@ -616,6 +507,7 @@ class FilesCollection return sendEOF: -> + @collection._debug '[FilesCollection] [UploadInstance] [sendEOF]', @EOFsent unless @EOFsent @EOFsent = true self = @ @@ -635,9 +527,9 @@ class FilesCollection 'x-eof': '1' 'x-fileId': opts.fileId 'content-type': 'text/plain' - }, (error, result) -> + }, (error, _result) -> try - result = JSON.parse result?.content or {} + result = JSON.parse _result?.content or {} catch e console.warn 'Something went wrong! [sendEOF] method doesn\'t returned JSON! Looks like you\'re on Cordova app or behind proxy, switching to DDP transport is recommended.' result = {} @@ -702,10 +594,13 @@ class FilesCollection @worker.postMessage({sc: @sentChunks, cc: @currentChunk, cs: @config.chunkSize, f: @config.file, ib: @config.isBase64}) else @emitEvent 'proceedChunk', [@currentChunk] + else + @emitEvent 'sendEOF' @startTime[@currentChunk] = +new Date return createStreams: -> + @collection._debug '[FilesCollection] [UploadInstance] [createStreams]' i = 1 self = @ while i <= @config.streams @@ -728,6 +623,8 @@ class FilesCollection if @config.transport is 'http' @config.chunkSize = Math.round @config.chunkSize / 2 + else if isSafari + @config.chunkSize = Math.ceil @config.chunkSize / 8 if @config.isBase64 @config.chunkSize = Math.floor(@config.chunkSize / 4) * 4 @@ -742,6 +639,8 @@ class FilesCollection if @config.transport is 'http' @config.streams = Math.round @config.streams / 2 + else if isSafari + @config.streams = 1 @fileLength = if _len <= 0 then 1 else _len @config.streams = @fileLength if @config.streams > @fileLength @@ -756,11 +655,11 @@ class FilesCollection handleStart = (error) -> if error - console.error '[FilesCollection] [_Start] Error:', error if self.collection.debug + self.collection._debug '[FilesCollection] [_Start] Error:', error self.emitEvent 'end', [error] else self.result.continueFunc = -> - console.info '[FilesCollection] [insert] [continueFunc]' if self.collection.debug + self.collection._debug '[FilesCollection] [insert] [continueFunc]' self.emitEvent 'createStreams' return self.emitEvent 'createStreams' @@ -800,33 +699,26 @@ class FilesCollection Tracker.autorun (computation) -> self.trackerComp = computation - unless self.result.onPause.get() - if Meteor.status().connected - console.info '[FilesCollection] [insert] [Tracker] [continue]' if self.collection.debug - self.result.continue() - else - console.info '[FilesCollection] [insert] [Tracker] [pause]' if self.collection.debug - self.result.pause() + if not self.result.onPause.curValue and not Meteor.status().connected + self.collection._debug '[FilesCollection] [insert] [Tracker] [pause]' + self.result.pause() return if @worker + @collection._debug '[FilesCollection] [insert] using WebWorkers' @worker.onmessage = (evt) -> if evt.data.error - console.warn '[FilesCollection] [insert] [worker] [onmessage] [ERROR:]', evt.data.error if self.collection.debug + self.collection._debug '[FilesCollection] [insert] [worker] [onmessage] [ERROR:]', evt.data.error self.emitEvent 'proceedChunk', [evt.data.chunkId] else self.emitEvent 'sendChunk', [evt] return @worker.onerror = (e) -> - console.error '[FilesCollection] [insert] [worker] [onerror] [ERROR:]', e if self.collection.debug + self.collection._debug '[FilesCollection] [insert] [worker] [onerror] [ERROR:]', e self.emitEvent 'end', [e.message] return - - if @collection.debug - if @worker - console.info '[FilesCollection] [insert] using WebWorkers' - else - console.info '[FilesCollection] [insert] using MainThread' + else + @collection._debug '[FilesCollection] [insert] using MainThread' self.emitEvent 'prepare' return @result @@ -851,6 +743,7 @@ class FilesCollection _FileUpload: class FileUpload __proto__: EventEmitter.prototype constructor: (@config) -> + @config._debug '[FilesCollection] [FileUpload] [constructor]' EventEmitter.call @ self = @ unless @config.isBase64 @@ -871,14 +764,14 @@ class FilesCollection , 1000 continueFunc: -> return pause: -> - console.info '[FilesCollection] [insert] [.pause()]' if @config.debug + @config._debug '[FilesCollection] [insert] [.pause()]' unless @onPause.get() @onPause.set true @state.set 'paused' @emitEvent 'pause', [@file] return continue: -> - console.info '[FilesCollection] [insert] [.continue()]' if @config.debug + @config._debug '[FilesCollection] [insert] [.continue()]' if @onPause.get() @onPause.set false @state.set 'active' @@ -886,11 +779,11 @@ class FilesCollection @continueFunc() return toggle: -> - console.info '[FilesCollection] [insert] [.toggle()]' if @config.debug + @config._debug '[FilesCollection] [insert] [.toggle()]' if @onPause.get() then @continue() else @pause() return abort: -> - console.info '[FilesCollection] [insert] [.abort()]' if @config.debug + @config._debug '[FilesCollection] [insert] [.abort()]' window.removeEventListener 'beforeunload', @config.beforeunload, false @config.onAbort and @config.onAbort.call @, @file @emitEvent 'abort', [@file] @@ -911,7 +804,7 @@ class FilesCollection @returns {FilesCollection} Instance ### remove: (selector = {}, callback) -> - console.info "[FilesCollection] [remove(#{JSON.stringify(selector)})]" if @debug + @_debug "[FilesCollection] [remove(#{JSON.stringify(selector)})]" check selector, Match.OneOf Object, String check callback, Match.Optional Function @@ -919,38 +812,10 @@ class FilesCollection @ddp.call @_methodNames._Remove, selector, (callback or NOOP) else callback and callback new Meteor.Error 401, '[FilesCollection] [remove] Run code from client is not allowed!' - console.warn '[FilesCollection] [remove] Run code from client is not allowed!' if @debug + @_debug '[FilesCollection] [remove] Run code from client is not allowed!' return @ - ### - @locus Anywhere - @memberOf FilesCollection - @name update - @see http://docs.meteor.com/#/full/update - @summary link Mongo.Collection update method - @returns {Mongo.Collection} Instance - ### - update: -> - @collection.update.apply @collection, arguments - return @collection - - ### - @locus Anywhere - @memberOf FilesCollection - @name link - @param {Object} fileRef - File reference object - @param {String} version - Version of file you would like to request - @summary Returns downloadable URL - @returns {String} Empty string returned in case if file not found in DB - ### - link: (fileRef, version = 'original') -> - console.info "[FilesCollection] [link(#{fileRef?._id}, #{version})]" if @debug - check fileRef, Object - check version, String - return '' if not fileRef - return formatFleURL fileRef, version - ### @locus Client @TemplateHelper diff --git a/core.coffee b/core.coffee new file mode 100644 index 00000000..4b2021e1 --- /dev/null +++ b/core.coffee @@ -0,0 +1,149 @@ +`import { FilesCursor, FileCursor } from './cursor.coffee'` +`import { fixJSONParse, fixJSONStringify, formatFleURL } from './lib.coffee'` + +class FilesCollectionCore + constructor: () -> + ### + @locus Anywhere + @memberOf FilesCollection + @name _getFileName + @param {Object} fileData - File Object + @summary Returns file's name + @returns {String} + ### + _getFileName: (fileData) -> + fileName = fileData.name or fileData.fileName + if _.isString(fileName) and fileName.length > 0 + return (fileData.name or fileData.fileName).replace(/\.\./g, '').replace /\//g, '' + else + return '' + + ### + @locus Anywhere + @memberOf FilesCollection + @name _getExt + @param {String} FileName - File name + @summary Get extension from FileName + @returns {Object} + ### + _getExt: (fileName) -> + if !!~fileName.indexOf('.') + extension = (fileName.split('.').pop().split('?')[0] or '').toLowerCase() + return { ext: extension, extension, extensionWithDot: '.' + extension } + else + return { ext: '', extension: '', extensionWithDot: '' } + + ### + @locus Anywhere + @memberOf FilesCollection + @name _updateFileTypes + @param {Object} data - File data + @summary Internal method. Classify file based on 'type' field + ### + _updateFileTypes: (data) -> + data.isVideo = /^video\//i.test data.type + data.isAudio = /^audio\//i.test data.type + data.isImage = /^image\//i.test data.type + data.isText = /^text\//i.test data.type + data.isJSON = /^application\/json$/i.test data.type + data.isPDF = /^application\/(x-)?pdf$/i.test data.type + return + + ### + @locus Anywhere + @memberOf FilesCollection + @name _dataToSchema + @param {Object} data - File data + @summary Internal method. Build object in accordance with default schema from File data + @returns {Object} + ### + _dataToSchema: (data) -> + ds = + name: data.name + extension: data.extension + path: data.path + meta: data.meta + type: data.type + size: data.size + userId: data.userId or null + versions: + original: + path: data.path + size: data.size + type: data.type + extension: data.extension + _downloadRoute: data._downloadRoute or @downloadRoute + _collectionName: data._collectionName or @collectionName + + #Optional fileId + if data.fileId + ds._id = data.fileId; + + @_updateFileTypes ds + ds._storagePath = data._storagePath or @storagePath(_.extend(data, ds)) + return ds + + ### + @locus Anywhere + @memberOf FilesCollection + @name findOne + @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) + @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) + @summary Find and return Cursor for matching document Object + @returns {FileCursor} Instance + ### + findOne: (selector, options) -> + @_debug "[FilesCollection] [findOne(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" + check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null + check options, Match.Optional Object + + selector = {} unless arguments.length + doc = @collection.findOne selector, options + return if doc then new FileCursor(doc, @) else doc + + ### + @locus Anywhere + @memberOf FilesCollection + @name find + @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) + @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) + @summary Find and return Cursor for matching documents + @returns {FilesCursor} Instance + ### + find: (selector, options) -> + @_debug "[FilesCollection] [find(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" + check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null + check options, Match.Optional Object + + selector = {} unless arguments.length + return new FilesCursor selector, options, @ + + ### + @locus Anywhere + @memberOf FilesCollection + @name update + @see http://docs.meteor.com/#/full/update + @summary link Mongo.Collection update method + @returns {Mongo.Collection} Instance + ### + update: -> + @collection.update.apply @collection, arguments + return @collection + + ### + @locus Anywhere + @memberOf FilesCollection + @name link + @param {Object} fileRef - File reference object + @param {String} version - Version of file you would like to request + @summary Returns downloadable URL + @returns {String} Empty string returned in case if file not found in DB + ### + link: (fileRef, version = 'original') -> + @_debug "[FilesCollection] [link(#{fileRef?._id}, #{version})]" + check fileRef, Object + check version, String + return '' if not fileRef + return formatFleURL fileRef, version + +`export { FilesCollectionCore }` \ No newline at end of file diff --git a/cursor.coffee b/cursor.coffee new file mode 100644 index 00000000..68ce7090 --- /dev/null +++ b/cursor.coffee @@ -0,0 +1,288 @@ +### +@private +@locus Anywhere +@class FileCursor +@param _fileRef {Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) +@param _collection {FilesCollection} - FilesCollection Instance +@summary Internal class, represents each record in `FilesCursor.each()` or document returned from `.findOne()` method +### +class FileCursor + constructor: (@_fileRef, @_collection) -> + self = @ + self = _.extend self, @_fileRef + + ### + @locus Anywhere + @memberOf FileCursor + @name remove + @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed + @summary Remove document + @returns {FileCursor} + ### + remove: (callback) -> + @_collection._debug '[FilesCollection] [FileCursor] [remove()]' + if @_fileRef + @_collection.remove(@_fileRef._id, callback) + else + callback and callback new Meteor.Error 404, 'No such file' + return @ + + ### + @locus Anywhere + @memberOf FileCursor + @name link + @param version {String} - Name of file's subversion + @summary Returns downloadable URL to File + @returns {String} + ### + link: (version) -> + @_collection._debug "[FilesCollection] [FileCursor] [link(#{version})]" + return if @_fileRef then @_collection.link(@_fileRef, version) else '' + + ### + @locus Anywhere + @memberOf FileCursor + @name get + @param property {String} - Name of sub-object property + @summary Returns current document as a plain Object, if `property` is specified - returns value of sub-object property + @returns {Object|mix} + ### + get: (property) -> + @_collection._debug "[FilesCollection] [FileCursor] [get(#{property})]" + if property + return @_fileRef[property] + else + return @_fileRef + + ### + @locus Anywhere + @memberOf FileCursor + @name fetch + @summary Returns document as plain Object in Array + @returns {[Object]} + ### + fetch: -> + @_collection._debug '[FilesCollection] [FileCursor] [fetch()]' + return [@_fileRef] + + ### + @locus Anywhere + @memberOf FileCursor + @name with + @summary Returns reactive version of current FileCursor, useful to use with `{{#with}}...{{/with}}` block template helper + @returns {[Object]} + ### + with: -> + @_collection._debug '[FilesCollection] [FileCursor] [with()]' + self = @ + return _.extend self, @_collection.collection.findOne @_fileRef._id + +### +@private +@locus Anywhere +@class FilesCursor +@param _selector {String|Object} - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) +@param options {Object} - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#selectors) +@param _collection {FilesCollection} - FilesCollection Instance +@summary Implementation of Cursor for FilesCollection +### +class FilesCursor + constructor: (@_selector = {}, options, @_collection) -> + @_current = -1 + @cursor = @_collection.collection.find @_selector, options + + ### + @locus Anywhere + @memberOf FilesCursor + @name get + @summary Returns all matching document(s) as an Array. Alias of `.fetch()` + @returns {[Object]} + ### + get: -> + @_collection._debug "[FilesCollection] [FilesCursor] [get()]" + return @cursor.fetch() + + ### + @locus Anywhere + @memberOf FilesCursor + @name hasNext + @summary Returns `true` if there is next item available on Cursor + @returns {Boolean} + ### + hasNext: -> + @_collection._debug '[FilesCollection] [FilesCursor] [hasNext()]' + return @_current < @cursor.count() - 1 + + ### + @locus Anywhere + @memberOf FilesCursor + @name next + @summary Returns next item on Cursor, if available + @returns {Object|undefined} + ### + next: -> + @_collection._debug '[FilesCollection] [FilesCursor] [next()]' + if @hasNext() + return @cursor.fetch()[++@_current] + + ### + @locus Anywhere + @memberOf FilesCursor + @name hasPrevious + @summary Returns `true` if there is previous item available on Cursor + @returns {Boolean} + ### + hasPrevious: -> + @_collection._debug '[FilesCollection] [FilesCursor] [hasPrevious()]' + return @_current isnt -1 + + ### + @locus Anywhere + @memberOf FilesCursor + @name previous + @summary Returns previous item on Cursor, if available + @returns {Object|undefined} + ### + previous: -> + @_collection._debug '[FilesCollection] [FilesCursor] [previous()]' + if @hasPrevious() + return @cursor.fetch()[--@_current] + + ### + @locus Anywhere + @memberOf FilesCursor + @name fetch + @summary Returns all matching document(s) as an Array. + @returns {[Object]} + ### + fetch: -> + @_collection._debug '[FilesCollection] [FilesCursor] [fetch()]' + return @cursor.fetch() + + ### + @locus Anywhere + @memberOf FilesCursor + @name first + @summary Returns first item on Cursor, if available + @returns {Object|undefined} + ### + first: -> + @_collection._debug '[FilesCollection] [FilesCursor] [first()]' + @_current = 0 + return @fetch()?[@_current] + + ### + @locus Anywhere + @memberOf FilesCursor + @name last + @summary Returns last item on Cursor, if available + @returns {Object|undefined} + ### + last: -> + @_collection._debug '[FilesCollection] [FilesCursor] [last()]' + @_current = @count() - 1 + return @fetch()?[@_current] + + ### + @locus Anywhere + @memberOf FilesCursor + @name count + @summary Returns the number of documents that match a query + @returns {Number} + ### + count: -> + @_collection._debug '[FilesCollection] [FilesCursor] [count()]' + return @cursor.count() + + ### + @locus Anywhere + @memberOf FilesCursor + @name remove + @param callback {Function} - Triggered asynchronously after item is removed or failed to be removed + @summary Removes all documents that match a query + @returns {FilesCursor} + ### + remove: (callback) -> + @_collection._debug '[FilesCollection] [FilesCursor] [remove()]' + @_collection.remove @_selector, callback + return @ + + ### + @locus Anywhere + @memberOf FilesCursor + @name forEach + @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself + @param context {Object} - An object which will be the value of `this` inside `callback` + @summary Call `callback` once for each matching document, sequentially and synchronously. + @returns {undefined} + ### + forEach: (callback, context = {}) -> + @_collection._debug '[FilesCollection] [FilesCursor] [forEach()]' + @cursor.forEach callback, context + return + + ### + @locus Anywhere + @memberOf FilesCursor + @name each + @summary Returns an Array of FileCursor made for each document on current cursor + Useful when using in {{#each FilesCursor#each}}...{{/each}} block template helper + @returns {[FileCursor]} + ### + each: -> + self = @ + return @map (file) -> + return new FileCursor file, self._collection + + ### + @locus Anywhere + @memberOf FilesCursor + @name map + @param callback {Function} - Function to call. It will be called with three arguments: the `file`, a 0-based index, and cursor itself + @param context {Object} - An object which will be the value of `this` inside `callback` + @summary Map `callback` over all matching documents. Returns an Array. + @returns {Array} + ### + map: (callback, context = {}) -> + @_collection._debug '[FilesCollection] [FilesCursor] [map()]' + return @cursor.map callback, context + + ### + @locus Anywhere + @memberOf FilesCursor + @name current + @summary Returns current item on Cursor, if available + @returns {Object|undefined} + ### + current: -> + @_collection._debug '[FilesCollection] [FilesCursor] [current()]' + @_current = 0 if @_current < 0 + return @fetch()[@_current] + + ### + @locus Anywhere + @memberOf FilesCursor + @name observe + @param callbacks {Object} - Functions to call to deliver the result set as it changes + @summary Watch a query. Receive callbacks as the result set changes. + @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observe + @returns {Object} - live query handle + ### + observe: (callbacks) -> + @_collection._debug '[FilesCollection] [FilesCursor] [observe()]' + return @cursor.observe callbacks + + ### + @locus Anywhere + @memberOf FilesCursor + @name observeChanges + @param callbacks {Object} - Functions to call to deliver the result set as it changes + @summary Watch a query. Receive callbacks as the result set changes. Only the differences between the old and new documents are passed to the callbacks. + @url http://docs.meteor.com/api/collections.html#Mongo-Cursor-observeChanges + @returns {Object} - live query handle + ### + observeChanges: (callbacks) -> + @_collection._debug '[FilesCollection] [FilesCursor] [observeChanges()]' + return @cursor.observeChanges callbacks + +`export { FilesCursor, FileCursor }` \ No newline at end of file diff --git a/lib.coffee b/lib.coffee new file mode 100644 index 00000000..d872f6e7 --- /dev/null +++ b/lib.coffee @@ -0,0 +1,62 @@ +### +@var {Function} fixJSONParse - Fix issue with Date parse +### +fixJSONParse = (obj) -> + for key, value of obj + if _.isString(value) and !!~value.indexOf '=--JSON-DATE--=' + value = value.replace '=--JSON-DATE--=', '' + obj[key] = new Date parseInt value + else if _.isObject value + obj[key] = fixJSONParse value + else if _.isArray value + for v, i in value + if _.isObject(v) + obj[key][i] = fixJSONParse v + else if _.isString(v) and !!~v.indexOf '=--JSON-DATE--=' + v = v.replace '=--JSON-DATE--=', '' + obj[key][i] = new Date parseInt v + return obj + +### +@var {Function} fixJSONStringify - Fix issue with Date stringify +### +fixJSONStringify = (obj) -> + for key, value of obj + if _.isDate value + obj[key] = '=--JSON-DATE--=' + (+value) + else if _.isObject value + obj[key] = fixJSONStringify value + else if _.isArray value + for v, i in value + if _.isObject(v) + obj[key][i] = fixJSONStringify v + else if _.isDate v + obj[key][i] = '=--JSON-DATE--=' + (+v) + return obj + +### +@locus Anywhere +@private +@name formatFleURL +@param {Object} fileRef - File reference object +@param {String} version - [Optional] Version of file you would like build URL for +@summary Returns formatted URL for file +@returns {String} Downloadable link +### +formatFleURL = (fileRef, version = 'original') -> + check fileRef, Object + check version, String + + root = __meteor_runtime_config__.ROOT_URL.replace(/\/+$/, '') + + if fileRef.extension?.length + ext = '.' + fileRef.extension + else + ext = '' + + if fileRef.public is true + return root + (if version is 'original' then "#{fileRef._downloadRoute}/#{fileRef._id}#{ext}" else "#{fileRef._downloadRoute}/#{version}-#{fileRef._id}#{ext}") + else + return root + "#{fileRef._downloadRoute}/#{fileRef._collectionName}/#{fileRef._id}/#{version}/#{fileRef._id}#{ext}" + +`export { fixJSONParse, fixJSONStringify, formatFleURL }` \ No newline at end of file diff --git a/server.coffee b/server.coffee index 9ccc2ab5..60ee912a 100644 --- a/server.coffee +++ b/server.coffee @@ -1,8 +1,7 @@ `import { Cookies } from 'meteor/ostrio:cookies'` -`import { FilesCursor, FileCursor } from './files-cursor.coffee` -`import { writeStream } from './files-write-stream.coffee'` -`import { fixJSONParse, fixJSONStringify, formatFleURL } from './files-lib.coffee` -NOOP = -> return +`import { writeStream } from './write-stream.coffee'` +`import { FilesCollectionCore } from './core.coffee'` +`import { fixJSONParse, fixJSONStringify, formatFleURL } from './lib.coffee'` ### @summary Require NPM packages @@ -18,6 +17,7 @@ NOOP = -> return @var {Object} bound - Meteor.bindEnvironment (Fiber wrapper) ### bound = Meteor.bindEnvironment (callback) -> return callback() +NOOP = -> return ### @locus Anywhere @@ -58,7 +58,7 @@ return `false` or `String` to abort upload @summary Create new instance of FilesCollection ### class FilesCollection - __proto__: do -> events.EventEmitter.prototype + __proto__: do -> _.extend events.EventEmitter.prototype, FilesCollectionCore.prototype constructor: (config) -> events.EventEmitter.call @ {storagePath, @ddp, @collection, @collectionName, @downloadRoute, @schema, @chunkSize, @namingFunction, @debug, @onbeforeunloadMessage, @permissions, @parentDirPermissions, @allowClientCode, @onBeforeUpload, @onInitiateUpload, @integrityCheck, @protected, @public, @strict, @downloadCallback, @cacheControl, @responseHeaders, @throttle, @onAfterUpload, @onAfterRemove, @interceptDownload, @onBeforeRemove, @continueUploadTTL} = config if config @@ -66,6 +66,8 @@ class FilesCollection self = @ cookie = new Cookies() @debug ?= false + @_debug = -> + console.info.apply undefined, arguments if self.debug @public ?= false @protected ?= false @chunkSize ?= 1024*512 @@ -130,7 +132,7 @@ class FilesCollection if @public and not storagePath throw new Meteor.Error 500, "[FilesCollection.#{@collectionName}] \"storagePath\" must be set on \"public\" collections! Note: \"storagePath\" must be equal on be inside of your web/proxy-server (absolute) root." - console.info('[FilesCollection.storagePath] Set to:', @storagePath({})) if @debug + @_debug '[FilesCollection.storagePath] Set to:', @storagePath({}) fs.mkdirs @storagePath({}), {mode: @parentDirPermissions}, (error) -> if error @@ -161,19 +163,19 @@ class FilesCollection _preCollectionCursor.observe changed: (doc) -> if doc.isFinished - console.info "[FilesCollection] [_preCollectionCursor.observe] [changed]: #{doc._id}" if self.debug + self._debug "[FilesCollection] [_preCollectionCursor.observe] [changed]: #{doc._id}" self._preCollection.remove {_id: doc._id}, NOOP return removed: (doc) -> # Free memory after upload is done # Or if upload is unfinished - console.info "[FilesCollection] [_preCollectionCursor.observe] [removed]: #{doc._id}" if self.debug + self._debug "[FilesCollection] [_preCollectionCursor.observe] [removed]: #{doc._id}" if self._currentUploads?[doc._id] self._currentUploads[doc._id].stop() self._currentUploads[doc._id].end() unless doc.isFinished - console.info "[FilesCollection] [_preCollectionCursor.observe] [removeUnfinishedUpload]: #{doc._id}" if self.debug + self._debug "[FilesCollection] [_preCollectionCursor.observe] [removeUnfinishedUpload]: #{doc._id}" self._currentUploads[doc._id].abort() delete self._currentUploads[doc._id] @@ -184,6 +186,7 @@ class FilesCollection # This little function allows to continue upload # even after server is restarted (*not on dev-stage*) + _iwcz = 0 @_continueUpload = (_id) -> if self._currentUploads?[_id]?.file if not self._currentUploads[_id].aborted and not self._currentUploads[_id].ended @@ -194,8 +197,9 @@ class FilesCollection else contUpld = self._preCollection.findOne {_id} if contUpld - self._createStream _id, contUpld.file.path, contUpld.file - return contUpld + self._createStream _id, contUpld.file.path, contUpld + return self._currentUploads[_id].file + return false if not @schema @schema = @@ -263,7 +267,7 @@ class FilesCollection return true else rc = if _.isNumber(result) then result else 401 - console.warn '[FilesCollection._checkAccess] WARN: Access denied!' if self.debug + self._debug '[FilesCollection._checkAccess] WARN: Access denied!' if http text = 'Access denied!' if !http.response.headersSent @@ -355,13 +359,14 @@ class FilesCollection opts = file: {} opts.___s = true - console.info "[FilesCollection] [File Start HTTP] #{opts.file.name} - #{opts.fileId}" if self.debug + self._debug "[FilesCollection] [File Start HTTP] #{opts.file.name} - #{opts.fileId}" opts.file.meta = fixJSONParse opts.file.meta if opts?.file?.meta {result} = self._prepareUpload _.clone(opts), user.userId, 'HTTP Start Method' if self.collection.findOne result._id throw new Meteor.Error 400, 'Can\'t start upload, data substitution detected!' opts._id = opts.fileId opts.createdAt = new Date() + opts.maxLength = opts.fileLength self._preCollection.insert _.omit(opts, '___s') self._createStream result._id, result.path, _.omit(opts, '___s') @@ -441,7 +446,7 @@ class FilesCollection # from Client side _methods[self._methodNames._Remove] = (selector) -> check selector, Match.OneOf String, Object - console.info "[FilesCollection] [Unlink Method] [.remove(#{selector})]" if self.debug + self._debug "[FilesCollection] [Unlink Method] [.remove(#{selector})]" if self.allowClientCode if self.onBeforeRemove and _.isFunction self.onBeforeRemove @@ -482,13 +487,14 @@ class FilesCollection check returnMeta, Match.Optional Boolean - console.info "[FilesCollection] [File Start Method] #{opts.file.name} - #{opts.fileId}" if self.debug + self._debug "[FilesCollection] [File Start Method] #{opts.file.name} - #{opts.fileId}" opts.___s = true {result} = self._prepareUpload _.clone(opts), @userId, 'DDP Start Method' if self.collection.findOne result._id throw new Meteor.Error 400, 'Can\'t start upload, data substitution detected!' opts._id = opts.fileId opts.createdAt = new Date() + opts.maxLength = opts.fileLength self._preCollection.insert _.omit(opts, '___s') self._createStream result._id, result.path, _.omit(opts, '___s') @@ -532,7 +538,7 @@ class FilesCollection try return Meteor.wrapAsync(self._handleUpload.bind(self, result, opts))() catch e - console.warn "[FilesCollection] [Write Method] [DDP] Exception:", e if self.debug + self._debug "[FilesCollection] [Write Method] [DDP] Exception:", e throw e else self.emit '_handleUpload', result, opts, NOOP @@ -547,7 +553,7 @@ class FilesCollection check _id, String _continueUpload = self._continueUpload _id - console.info "[FilesCollection] [Abort Method]: #{_id} - #{_continueUpload?.file?.path}" if self.debug + self._debug "[FilesCollection] [Abort Method]: #{_id} - #{_continueUpload?.file?.path}" if self._currentUploads?[_id] self._currentUploads[_id].stop() @@ -573,13 +579,13 @@ class FilesCollection opts.binData ?= 'EOF' opts.chunkId ?= -1 opts.FSName ?= opts.fileId - opts.file.meta ?= {} - console.info "[FilesCollection] [Upload] [#{transport}] Got ##{opts.chunkId}/#{opts.fileLength} chunks, dst: #{opts.file.name or opts.file.fileName}" if @debug + @_debug "[FilesCollection] [Upload] [#{transport}] Got ##{opts.chunkId}/#{opts.fileLength} chunks, dst: #{opts.file.name or opts.file.fileName}" fileName = @_getFileName opts.file {extension, extensionWithDot} = @_getExt fileName + opts.file.meta ?= {} result = opts.file result.name = fileName result.meta = opts.file.meta @@ -628,7 +634,7 @@ class FilesCollection @returns {undefined} ### _finishUpload: (result, opts, cb) -> - console.info "[FilesCollection] [Upload] [finish(ing)Upload] -> #{result.path}" if @debug + @_debug "[FilesCollection] [Upload] [finish(ing)Upload] -> #{result.path}" fs.chmod result.path, @permissions, NOOP self = @ result.type = @_getMimeType opts.file @@ -638,11 +644,11 @@ class FilesCollection @collection.insert _.clone(result), (error, _id) -> if error cb and cb error - console.error '[FilesCollection] [Upload] [_finishUpload] Error:', error if self.debug + self._debug '[FilesCollection] [Upload] [_finishUpload] Error:', error else self._preCollection.update {_id: opts.fileId}, {$set: {isFinished: true}} result._id = _id - console.info "[FilesCollection] [Upload] [finish(ed)Upload] -> #{result.path}" if self.debug + self._debug "[FilesCollection] [Upload] [finish(ed)Upload] -> #{result.path}" self.onAfterUpload and self.onAfterUpload.call self, result self.emit 'afterUpload', result cb and cb null, result @@ -660,13 +666,13 @@ class FilesCollection try if opts.eof self = @ - @_currentUploads[result._id].end -> bound -> + @_currentUploads[result._id].end -> self.emit '_finishUpload', result, opts, cb return else @_currentUploads[result._id].write opts.chunkId, opts.binData, cb catch e - console.warn "[_handleUpload] [EXCEPTION:]", e if @debug + @_debug "[_handleUpload] [EXCEPTION:]", e cb and cb e return @@ -694,21 +700,6 @@ class FilesCollection mime = 'application/octet-stream' return mime - ### - @locus Anywhere - @memberOf FilesCollection - @name _getFileName - @param {Object} fileData - File Object - @summary Returns file's name - @returns {String} - ### - _getFileName: (fileData) -> - fileName = fileData.name or fileData.fileName - if _.isString(fileName) and fileName.length > 0 - return (fileData.name or fileData.fileName).replace(/\.\./g, '').replace /\//g, '' - else - return '' - ### @locus Anywhere @memberOf FilesCollection @@ -738,71 +729,6 @@ class FilesCollection return result - ### - @locus Anywhere - @memberOf FilesCollection - @name _getExt - @param {String} FileName - File name - @summary Get extension from FileName - @returns {Object} - ### - _getExt: (fileName) -> - if !!~fileName.indexOf('.') - extension = (fileName.split('.').pop().split('?')[0] or '').toLowerCase() - return { ext: extension, extension, extensionWithDot: '.' + extension } - else - return { ext: '', extension: '', extensionWithDot: '' } - - ### - @locus Anywhere - @memberOf FilesCollection - @name _updateFileTypes - @param {Object} data - File data - @summary Internal method. Classify file based on 'type' field - ### - _updateFileTypes: (data) -> - data.isVideo = /^video\//i.test data.type - data.isAudio = /^audio\//i.test data.type - data.isImage = /^image\//i.test data.type - data.isText = /^text\//i.test data.type - data.isJSON = /^application\/json$/i.test data.type - data.isPDF = /^application\/(x-)?pdf$/i.test data.type - return - - ### - @locus Anywhere - @memberOf FilesCollection - @name _dataToSchema - @param {Object} data - File data - @summary Internal method. Build object in accordance with default schema from File data - @returns {Object} - ### - _dataToSchema: (data) -> - ds = - name: data.name - extension: data.extension - path: data.path - meta: data.meta - type: data.type - size: data.size - userId: data.userId or null - versions: - original: - path: data.path - size: data.size - type: data.type - extension: data.extension - _downloadRoute: data._downloadRoute or @downloadRoute - _collectionName: data._collectionName or @collectionName - - #Optional fileId - if data.fileId - ds._id = data.fileId; - - @_updateFileTypes ds - ds._storagePath = data._storagePath or @storagePath(_.extend(data, ds)) - return ds - ### @locus Server @memberOf FilesCollection @@ -820,7 +746,7 @@ class FilesCollection @returns {FilesCollection} Instance ### write: (buffer, opts = {}, callback, proceedAfterUpload) -> - console.info '[FilesCollection] [write()]' if @debug + @_debug '[FilesCollection] [write()]' if _.isFunction opts proceedAfterUpload = callback @@ -867,14 +793,14 @@ class FilesCollection self.collection.insert result, (error, _id) -> if error callback and callback error - console.warn "[FilesCollection] [write] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug + self._debug "[FilesCollection] [write] [insert] Error: #{fileName} -> #{self.collectionName}", error else fileRef = self.collection.findOne _id callback and callback null, fileRef if proceedAfterUpload is true self.onAfterUpload and self.onAfterUpload.call self, fileRef self.emit 'afterUpload', fileRef - console.info "[FilesCollection] [write]: #{fileName} -> #{self.collectionName}" if self.debug + self._debug "[FilesCollection] [write]: #{fileName} -> #{self.collectionName}" return return return @ @@ -896,7 +822,7 @@ class FilesCollection @returns {FilesCollection} Instance ### load: (url, opts, callback, proceedAfterUpload) -> - console.info "[FilesCollection] [load(#{url}, #{JSON.stringify(opts)}, callback)]" if @debug + @_debug "[FilesCollection] [load(#{url}, #{JSON.stringify(opts)}, callback)]" if _.isFunction opts proceedAfterUpload = callback @@ -929,23 +855,23 @@ class FilesCollection self.collection.insert result, (error, _id) -> if error callback and callback error - console.error "[FilesCollection] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug + self._debug "[FilesCollection] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error else fileRef = self.collection.findOne _id callback and callback null, fileRef if proceedAfterUpload is true self.onAfterUpload and self.onAfterUpload.call self, fileRef self.emit 'afterUpload', fileRef - console.info "[FilesCollection] [load] [insert] #{fileName} -> #{self.collectionName}" if self.debug + self._debug "[FilesCollection] [load] [insert] #{fileName} -> #{self.collectionName}" return return request.get(url).on('error', (error)-> bound -> callback and callback error - console.error "[FilesCollection] [load] [request.get(#{url})] Error:", error if self.debug + self._debug "[FilesCollection] [load] [request.get(#{url})] Error:", error ).on('response', (response) -> bound -> response.on 'end', -> bound -> - console.info "[FilesCollection] [load] Received: #{url}" if self.debug + self._debug "[FilesCollection] [load] Received: #{url}" result = self._dataToSchema name: fileName path: opts.path @@ -988,7 +914,7 @@ class FilesCollection @returns {FilesCollection} Instance ### addFile: (path, opts, callback, proceedAfterUpload) -> - console.info "[FilesCollection] [addFile(#{path})]" if @debug + @_debug "[FilesCollection] [addFile(#{path})]" if _.isFunction opts proceedAfterUpload = callback @@ -1038,56 +964,20 @@ class FilesCollection self.collection.insert result, (error, _id) -> if error callback and callback error - console.warn "[FilesCollection] [addFile] [insert] Error: #{result.name} -> #{self.collectionName}", error if self.debug + self._debug "[FilesCollection] [addFile] [insert] Error: #{result.name} -> #{self.collectionName}", error else fileRef = self.collection.findOne _id callback and callback null, fileRef if proceedAfterUpload is true self.onAfterUpload and self.onAfterUpload.call self, fileRef self.emit 'afterUpload', fileRef - console.info "[FilesCollection] [addFile]: #{result.name} -> #{self.collectionName}" if self.debug + self._debug "[FilesCollection] [addFile]: #{result.name} -> #{self.collectionName}" return else callback and callback new Meteor.Error 400, "[FilesCollection] [addFile(#{path})]: File does not exist" return - return @ - ### - @locus Anywhere - @memberOf FilesCollection - @name findOne - @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) - @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) - @summary Find and return Cursor for matching document Object - @returns {FileCursor} Instance - ### - findOne: (selector, options) -> - console.info "[FilesCollection] [findOne(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" if @debug - check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null - check options, Match.Optional Object - - selector = {} unless arguments.length - doc = @collection.findOne selector, options - return if doc then new FileCursor(doc, @) else doc - - ### - @locus Anywhere - @memberOf FilesCollection - @name find - @param {String|Object} selector - Mongo-Style selector (http://docs.meteor.com/api/collections.html#selectors) - @param {Object} options - Mongo-Style selector Options (http://docs.meteor.com/api/collections.html#sortspecifiers) - @summary Find and return Cursor for matching documents - @returns {FilesCursor} Instance - ### - find: (selector, options) -> - console.info "[FilesCollection] [find(#{JSON.stringify(selector)}, #{JSON.stringify(options)})]" if @debug - check selector, Match.Optional Match.OneOf Object, String, Boolean, Number, null - check options, Match.Optional Object - - selector = {} unless arguments.length - return new FilesCursor selector, options, @ - ### @locus Anywhere @memberOf FilesCollection @@ -1098,7 +988,7 @@ class FilesCollection @returns {FilesCollection} Instance ### remove: (selector = {}, callback) -> - console.info "[FilesCollection] [remove(#{JSON.stringify(selector)})]" if @debug + @_debug "[FilesCollection] [remove(#{JSON.stringify(selector)})]" check selector, Match.OneOf Object, String check callback, Match.Optional Function @@ -1124,18 +1014,6 @@ class FilesCollection @collection.remove selector, (callback or NOOP) return @ - ### - @locus Anywhere - @memberOf FilesCollection - @name update - @see http://docs.meteor.com/#/full/update - @summary link Mongo.Collection update method - @returns {Mongo.Collection} Instance - ### - update: -> - @collection.update.apply @collection, arguments - return @collection - ### @locus Server @memberOf FilesCollection @@ -1204,7 +1082,7 @@ class FilesCollection @returns {FilesCollection} Instance ### unlink: (fileRef, version, callback) -> - console.info "[FilesCollection] [unlink(#{fileRef._id}, #{version})]" if @debug + @_debug "[FilesCollection] [unlink(#{fileRef._id}, #{version})]" if version if fileRef.versions?[version] and fileRef.versions[version]?.path fs.unlink fileRef.versions[version].path, (callback or NOOP) @@ -1225,7 +1103,7 @@ class FilesCollection @returns {undefined} ### _404: (http) -> - console.warn "[FilesCollection] [download(#{http.request.originalUrl})] [_404] File not found" if @debug + @_debug "[FilesCollection] [download(#{http.request.originalUrl})] [_404] File not found" text = 'File Not Found :(' if !http.response.headersSent @@ -1247,7 +1125,7 @@ class FilesCollection @returns {undefined} ### download: (http, version = 'original', fileRef) -> - console.info "[FilesCollection] [download(#{http.request.originalUrl}, #{version})]" if @debug + @_debug "[FilesCollection] [download(#{http.request.originalUrl}, #{version})]" if fileRef if _.has(fileRef, 'versions') and _.has fileRef.versions, version vRef = fileRef.versions[version] @@ -1342,7 +1220,7 @@ class FilesCollection responseType = '200' streamErrorHandler = (error) -> - console.error "[FilesCollection] [serve(#{vRef.path}, #{version})] [500]", error if self.debug + self._debug "[FilesCollection] [serve(#{vRef.path}, #{version})] [500]", error if !http.response.finished http.response.end error.toString() return @@ -1359,7 +1237,7 @@ class FilesCollection switch responseType when '400' - console.warn "[FilesCollection] [serve(#{vRef.path}, #{version})] [400] Content-Length mismatch!" if self.debug + self._debug "[FilesCollection] [serve(#{vRef.path}, #{version})] [400] Content-Length mismatch!" text = 'Content-Length mismatch!' if !http.response.headersSent @@ -1373,14 +1251,14 @@ class FilesCollection return self._404 http break when '416' - console.warn "[FilesCollection] [serve(#{vRef.path}, #{version})] [416] Content-Range is not specified!" if self.debug + self._debug "[FilesCollection] [serve(#{vRef.path}, #{version})] [416] Content-Range is not specified!" if !http.response.headersSent http.response.writeHead 416 if !http.response.finished http.response.end() break when '200' - console.info "[FilesCollection] [serve(#{vRef.path}, #{version})] [200]" if self.debug + self._debug "[FilesCollection] [serve(#{vRef.path}, #{version})] [200]" stream = readableStream or fs.createReadStream vRef.path if !http.response.headersSent http.response.writeHead 200 if readableStream @@ -1414,7 +1292,7 @@ class FilesCollection stream.pipe http.response break when '206' - console.info "[FilesCollection] [serve(#{vRef.path}, #{version})] [206]" if self.debug + self._debug "[FilesCollection] [serve(#{vRef.path}, #{version})] [206]" if !http.response.headersSent http.response.setHeader 'Content-Range', "bytes #{reqRange.start}-#{reqRange.end}/#{vRef.size}" stream = readableStream or fs.createReadStream vRef.path, {start: reqRange.start, end: reqRange.end} @@ -1451,22 +1329,6 @@ class FilesCollection break return - ### - @locus Anywhere - @memberOf FilesCollection - @name link - @param {Object} fileRef - File reference object - @param {String} version - Version of file you would like to request - @summary Returns downloadable URL - @returns {String} Empty string returned in case if file not found in DB - ### - link: (fileRef, version = 'original') -> - console.info "[FilesCollection] [link(#{fileRef?._id}, #{version})]" if @debug - check fileRef, Object - check version, String - return '' if not fileRef - return formatFleURL fileRef, version - ### Export the FilesCollection class ### diff --git a/write-stream.coffee b/write-stream.coffee new file mode 100644 index 00000000..8fa1a9c0 --- /dev/null +++ b/write-stream.coffee @@ -0,0 +1,130 @@ +`import fs from 'fs-extra'` +NOOP = -> return + +### +@var {Object} bound - Meteor.bindEnvironment (Fiber wrapper) +### +bound = Meteor.bindEnvironment (callback) -> return callback() +fdCache = {} + +### +@private +@locus Server +@class writeStream +@param path {String} - Path to file on FS +@param maxLength {Number} - Max amount of chunks in stream +@param file {Object} - fileRef Object +@summary writableStream wrapper class, makes sure chunks is written in given order. Implementation of queue stream. +### +class writeStream + constructor: (@path, @maxLength, @file, @permissions) -> + if not @path or not _.isString @path + return + + self = @ + @fd = null + @writtenChunks = 0 + @ended = false + @aborted = false + + if fdCache[@path] && !fdCache[@path].ended && !fdCache[@path].aborted + @fd = fdCache[@path].fd + @writtenChunks = fdCache[@path].writtenChunks + else + fs.ensureFile @path, (efError) -> bound -> + if efError + throw new Meteor.Error 500, '[FilesCollection] [writeStream] [ensureFile] [Error:]', efError + else + fs.open self.path, 'r+', self.permissions, (oError, fd) -> bound -> + if oError + throw new Meteor.Error 500, '[FilesCollection] [writeStream] [ensureFile] [open] [Error:]', oError + else + self.fd = fd + fdCache[self.path] = self + return + return + + ### + @memberOf writeStream + @name write + @param {Number} num - Chunk position in a stream + @param {Buffer} chunk - Buffer (chunk binary data) + @param {Function} callback - Callback + @summary Write chunk in given order + @returns {Boolean} - True if chunk is sent to stream, false if chunk is set into queue + ### + write: (num, chunk, callback) -> + if not @aborted and not @ended + self = @ + if @fd + fs.write @fd, chunk, 0, chunk.length, (num - 1) * @file.chunkSize, (error, written, buffer) -> bound -> + callback and callback(error, written, buffer) + if error + console.warn '[FilesCollection] [writeStream] [write] [Error:]', error + self.abort() + else + ++self.writtenChunks + return + else + Meteor.setTimeout -> + self.write num, chunk, callback + return + , 25 + return false + + ### + @memberOf writeStream + @name end + @param {Function} callback - Callback + @summary Finishes writing to writableStream, only after all chunks in queue is written + @returns {Boolean} - True if stream is fulfilled, false if queue is in progress + ### + end: (callback) -> + if not @aborted and not @ended + if @writtenChunks is @maxLength + self = @ + fs.close @fd, -> bound -> + delete fdCache[@path] + self.ended = true + callback and callback(undefined, true) + return + return true + else + self = @ + fs.stat self.path, (error, stat) -> bound -> + if not error and stat + self.writtenChunks = Math.ceil stat.size / self.file.chunkSize + + Meteor.setTimeout -> + self.end callback + return + , 25 + else + callback and callback(undefined, @ended) + return false + + ### + @memberOf writeStream + @name abort + @param {Function} callback - Callback + @summary Aborts writing to writableStream, removes created file + @returns {Boolean} - True + ### + abort: (callback) -> + @aborted = true + delete fdCache[@path] + fs.unlink @path, (callback or NOOP) + return true + + ### + @memberOf writeStream + @name stop + @summary Stop writing to writableStream + @returns {Boolean} - True + ### + stop: -> + @aborted = true + delete fdCache[@path] + return true + +`export { writeStream }` From 839ab213382a08d9c32d74ef88c1315b189e645d Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Sun, 23 Jul 2017 02:30:33 +0300 Subject: [PATCH 8/9] Docs update --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 21ccb275..5fe43a00 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,11 @@ Related Packages: Why `Meteor-Files`? ======== - Support for `HTTP` and `DDP` transports for upload, [read about difference](https://github.com/VeliovGroup/Meteor-Files/wiki/About-Upload-Transports) - - You need store to *[GridFS](https://github.com/VeliovGroup/Meteor-Files/wiki/GridFS-Integration)*, *[AWS S3](https://github.com/VeliovGroup/Meteor-Files/wiki/AWS-S3-Integration)*, *[Google Storage](https://github.com/VeliovGroup/Meteor-Files/wiki/Google-Cloud-Storage-Integration)* or *[DropBox](https://github.com/VeliovGroup/Meteor-Files/wiki/DropBox-Integration)*? (*[Use 3rd-party storage](https://github.com/VeliovGroup/Meteor-Files/wiki/Third-party-storage)*) - *Add it yourself* - - You need to check file mime-type, size or extension? (*[`onBeforeUpload`](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor)*) - *Add it yourself* - - You need to [resize images](https://github.com/VeliovGroup/Meteor-Files-Demos/blob/master/demo/server/image-processing.js) after upload? (*[`onAfterUpload`](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor)*, *[file's subversions](https://github.com/VeliovGroup/Meteor-Files/wiki/Create-and-Manage-Subversions)*) - *Add it yourself* + - Sustainable and resumable uploads, which will survive connection interruption and server reboot (*if a server has persistent storage*) + - Upload files through computing cloud without persistent File System, like Heroku + - You need store to *[GridFS](https://github.com/VeliovGroup/Meteor-Files/wiki/GridFS-Integration)*, *[AWS S3](https://github.com/VeliovGroup/Meteor-Files/wiki/AWS-S3-Integration)*, *[Google Storage](https://github.com/VeliovGroup/Meteor-Files/wiki/Google-Cloud-Storage-Integration)* or *[DropBox](https://github.com/VeliovGroup/Meteor-Files/wiki/DropBox-Integration)*? (*[Use 3rd-party storage](https://github.com/VeliovGroup/Meteor-Files/wiki/Third-party-storage)*) + - You need to check file mime-type, size or extension? Easy! Use (*[`onBeforeUpload`](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor)*) hook + - You need to [resize images](https://github.com/VeliovGroup/Meteor-Files-Demos/blob/master/demo/server/image-processing.js) after upload? Easy too! Use (*[`onAfterUpload`](https://github.com/VeliovGroup/Meteor-Files/wiki/Constructor)* hook, and *[manage file's subversions](https://github.com/VeliovGroup/Meteor-Files/wiki/Create-and-Manage-Subversions)*) in single record Easy-peasy kids, *yeah*? @@ -97,6 +99,7 @@ FAQ: 2. __How to pause/continue upload and get progress/speed/remaining time?__: see *Object* returned from [`insert` method](https://github.com/VeliovGroup/Meteor-Files/wiki/Insert-(Upload)) 3. When using any of `accounts` packages - package `accounts-base` must be explicitly added to `.meteor/packages` above `ostrio:files` 4. __cURL/POST uploads__ - Take a look on [POST-Example](https://github.com/noris666/Meteor-Files-POST-Example) by [@noris666](https://github.com/noris666) + 5. In Safari (Mobile and Desktop) for `DDP` upload streams is hardcoded to `1` and chunk size is reduced by algorithm, due to error hrown if too many connection is open by the browser or frame is too big. Limit simultaneous uploads to `6` is recommended for Safari. This issue should be fixed in Safari 11. Switching to `http` transport (*which has no such issue*) is recommended for Safari. See [#458](https://github.com/VeliovGroup/Meteor-Files/issues/458) API overview (*[full API](https://github.com/VeliovGroup/Meteor-Files/wiki)*) ======== From df7a9710ca7d4b5ce366958d51fab94f6a30954e Mon Sep 17 00:00:00 2001 From: "dr.dimitru" Date: Sun, 23 Jul 2017 02:38:32 +0300 Subject: [PATCH 9/9] v1.8.1 - Exact code splitting, client part of the library reduced for 35% - Atmosphere and NPM packages update - Compatibility with meteor@1.5.1 - Fix for #458 - Safari can't handle simultaneous WebSockets connection, and fails to send large data chunks. It's fixed in this release with detecting if browser is Safari with `ddp` used as transport. Note is added to FAQ, Should be fixed in Safari 11. Thanks to @derwok for bringing it in, and @IDCOLL for original issue - Fix resumable uploads, now in 90% upload should survive server reboot and in 98% connection interruption - Better file stream management with file descriptor caching - should reduce RAM and I/O --- .npm/package/npm-shrinkwrap.json | 46 +++++++++++--------------------- .versions | 32 +++++++++++----------- package.js | 10 +++---- 3 files changed, 37 insertions(+), 51 deletions(-) diff --git a/.npm/package/npm-shrinkwrap.json b/.npm/package/npm-shrinkwrap.json index 20627e02..0935522b 100644 --- a/.npm/package/npm-shrinkwrap.json +++ b/.npm/package/npm-shrinkwrap.json @@ -118,9 +118,9 @@ "from": "form-data@>=2.1.1 <2.2.0" }, "fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "from": "fs-extra@3.0.1" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.0.tgz", + "from": "fs-extra@4.0.0" }, "getpass": { "version": "0.1.7", @@ -167,7 +167,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "from": "inherits@>=2.0.1 <2.1.0" + "from": "inherits@>=2.0.3 <2.1.0" }, "is-typedarray": { "version": "1.0.0", @@ -205,8 +205,8 @@ "from": "json-stringify-safe@>=5.0.1 <5.1.0" }, "jsonfile": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.0.tgz", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "from": "jsonfile@>=3.0.0 <4.0.0" }, "jsonify": { @@ -267,16 +267,9 @@ "from": "qs@>=6.4.0 <6.5.0" }, "readable-stream": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "from": "readable-stream@>=0.3.0", - "dependencies": { - "safe-buffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "from": "safe-buffer@>=5.0.1 <5.1.0" - } - } + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "from": "readable-stream@>=0.3.0" }, "request": { "version": "2.81.0", @@ -284,8 +277,8 @@ "from": "request@2.81.0" }, "safe-buffer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "from": "safe-buffer@>=5.0.1 <6.0.0" }, "sntp": { @@ -311,16 +304,9 @@ "from": "stream-parser@>=0.0.2" }, "string_decoder": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "from": "string_decoder@>=1.0.0 <1.1.0", - "dependencies": { - "safe-buffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "from": "safe-buffer@~5.0.1" - } - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "from": "string_decoder@>=1.0.3 <1.1.0" }, "stringstream": { "version": "0.0.5", @@ -348,8 +334,8 @@ "from": "tweetnacl@>=0.14.0 <0.15.0" }, "universalify": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.0.tgz", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", "from": "universalify@>=0.1.0 <0.2.0" }, "util-deprecate": { diff --git a/.versions b/.versions index 5f83c8ba..a3bb7ee6 100644 --- a/.versions +++ b/.versions @@ -1,24 +1,24 @@ -allow-deny@1.0.5 -babel-compiler@6.19.1 +allow-deny@1.0.6 +babel-compiler@6.19.4 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 blaze@2.3.2 blaze-tools@1.0.10 -boilerplate-generator@1.1.0 +boilerplate-generator@1.1.1 caching-compiler@1.1.9 callback-hook@1.0.10 check@1.2.5 coffeescript@1.11.1_3 -ddp@1.2.5 -ddp-client@1.3.4 -ddp-common@1.2.8 -ddp-server@1.3.14 +ddp@1.3.0 +ddp-client@2.0.0 +ddp-common@1.2.9 +ddp-server@2.0.0 deps@1.0.12 diff-sequence@1.0.7 -ecmascript@0.8.0 +ecmascript@0.8.1 ecmascript-runtime@0.4.1 -ecmascript-runtime-client@0.4.1 +ecmascript-runtime-client@0.4.2 ecmascript-runtime-server@0.4.1 ejson@1.0.13 geojson-utils@1.0.10 @@ -28,17 +28,17 @@ http@1.2.12 id-map@1.0.9 jquery@1.11.10 logging@1.1.17 -meteor@1.6.1 -minimongo@1.2.0 -modules@0.9.0 +meteor@1.7.0 +minimongo@1.2.1 +modules@0.9.2 modules-runtime@0.8.0 -mongo@1.1.18 +mongo@1.1.19 mongo-id@1.0.6 npm-mongo@2.2.24 observe-sequence@1.0.16 ordered-dict@1.0.9 -ostrio:cookies@2.2.1 -ostrio:files@1.8.0 +ostrio:cookies@2.2.2 +ostrio:files@1.8.1 promise@0.8.9 random@1.0.10 reactive-var@1.0.11 @@ -50,5 +50,5 @@ tracker@1.1.3 ui@1.0.13 underscore@1.0.10 url@1.1.0 -webapp@1.3.16 +webapp@1.3.17 webapp-hashing@1.0.9 diff --git a/package.js b/package.js index 0d2c7490..e1ee0488 100755 --- a/package.js +++ b/package.js @@ -1,13 +1,13 @@ Package.describe({ name: 'ostrio:files', - version: '1.8.0', + version: '1.8.1', summary: 'File upload via DDP/HTTP to server FS, AWS, GridFS, DropBox, Google Drive or other 3rd party storage', git: 'https://github.com/VeliovGroup/Meteor-Files', documentation: 'README.md' }); Npm.depends({ - 'fs-extra': '3.0.1', + 'fs-extra': '4.0.0', 'request': '2.81.0', 'throttle': '1.0.3', 'file-type': '5.2.0' @@ -17,9 +17,9 @@ Package.onUse(function(api) { api.versionsFrom('1.4'); api.use('webapp', 'server'); api.use(['reactive-var', 'tracker', 'http'], 'client'); - api.use(['mongo', 'underscore', 'check', 'random', 'coffeescript', 'ecmascript', 'ostrio:cookies@2.2.1'], ['client', 'server']); + api.use(['mongo', 'underscore', 'check', 'random', 'coffeescript', 'ecmascript', 'ostrio:cookies@2.2.2'], ['client', 'server']); api.addAssets('worker.min.js', 'client'); - api.mainModule('files-server.coffee', 'server'); - api.mainModule('files-client.coffee', 'client'); + api.mainModule('server.coffee', 'server'); + api.mainModule('client.coffee', 'client'); api.export('FilesCollection'); });