diff --git a/.versions b/.versions index 4d2be6cc..15c59c16 100644 --- a/.versions +++ b/.versions @@ -32,7 +32,7 @@ minimongo@1.0.8 mongo@1.1.0 observe-sequence@1.0.6 ordered-dict@1.0.3 -ostrio:files@1.0.3 +ostrio:files@1.0.4 ostrio:jsextensions@0.0.4 random@1.0.3 reactive-dict@1.1.0 diff --git a/README.md b/README.md index da87a52c..74e38d3e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ This package allows to: - Write file in file system * Automatically writes uploaded files on FS and special Collection * You able to specify `path`, collection name, schema, chunk size and naming function + - File streaming from server via HTTP + * Correct `mime-type` and `Content-Range` headers + * Correct `206` and `416` responses - Download uploaded files from server via HTTP * You able to specify `route` * Download huge files via stream `pipe`, tested on 100GB @@ -78,9 +81,29 @@ myFiles.findOne(fileRef._id).link() # Get download link myFiles.findOne(fileRef._id).remove() # Remove file ``` +##### File streaming: +To stream file add `?play=true` query to download link. +```coffeescript +audio = new Meteor.Files() + +if Meteor.isClient + Template.my.helpers + audiofiles: -> + audio.find({'meta.post': postId}).cursor +``` + +In template: +```jade +template(name="my") + ul + each audiofiles + li + audio(preload="auto" controls="true") + source(src="{{fileURL this}}?play=true" type="{{type}}") +``` + ##### File download: -To download file simply return result of `link()` method to template. Data will be transfered via pipe, add `?download=true` query to link, to send file directly. -Use `?download=true` query for smaller files, for big files, video and audio files (including streaming) - just use plain link without query. +To download file use `fileURL` template helper. Data will be transfered via pipe, - just add `?download=true` query to link, so server will send file directly. Use `?download=true` query for smaller files, for big files - just use plain link without query. ```coffeescript uploads = new Meteor.Files() @@ -88,9 +111,6 @@ if Meteor.isClient Template.my.helpers files: -> uploads.find({'meta.post': postId}).cursor - - link: -> - uploads.findOne(@_id).link() ``` In template: @@ -99,7 +119,7 @@ template(name="my") ul each files li - a(href="{{link}}?download=true") name + a(href="{{fileURL this}}?download=true") name ``` diff --git a/files.coffee b/files.coffee index e26c934d..7971a01d 100644 --- a/files.coffee +++ b/files.coffee @@ -60,7 +60,7 @@ cp = (to, from) -> @property {Object} schema - Collection Schema @property {Number} chunkSize - Upload chunk size @property {Function} namingFunction - Function which returns `String` -@property {Boolean} debug - Turn on/of debugging and extra logging +@property {Boolean} debug - Turn on/of debugging and extra logging @description Create new instance of Meteor.Files ### class Meteor.Files @@ -126,7 +126,7 @@ class Meteor.Files remove: -> true - Router.route "#{@downloadRoute}/:_id/#{@collectionName}", -> + Router.route "#{@downloadRoute}/#{@collectionName}/:_id/:name", -> self.findOne(this.params._id).download.call @, self , {where: 'server'} @@ -270,7 +270,6 @@ class Meteor.Files ### insert: (file, meta, onUploaded, onProgress, onBeforeUpload) -> console.info "Meteor.Files Debugger: [insert()]" if @debug - check file, Match.OneOf File, Object check meta, Match.Optional Object check onUploaded, Match.Optional Function check onProgress, Match.Optional Function @@ -292,7 +291,6 @@ class Meteor.Files file = _.extend file, fileData - startTime = performance.now() if @debug console.time('insert') if @debug randFileName = @namingFunction.call null, true @@ -302,9 +300,7 @@ class Meteor.Files self = @ end = (error, data) -> - endTime = performance.now() if self.debug console.timeEnd('insert') if self.debug - console.info "Meteor.Files Debugger: [EXECUTION TIME]: " + (endTime - startTime) if self.debug window.onbeforeunload = null onUploaded and onUploaded.call self, error, data @@ -391,6 +387,39 @@ class Meteor.Files 'Content-Length': self.currentFile.size resp.write file resp.end() + else if @params.query.play and @params.query.play == 'true' + if @request.headers.range + array = @request.headers.range.split /bytes=([0-9]*)-([0-9]*)/ + start = parseInt array[1] + end = parseInt array[2] + result = + Start: if isNaN(start) then 0 else start + End: if isNaN(end) then (self.currentFile.size - 1) else end + + if not isNaN(start) and isNaN(end) + result.Start = start + result.End = self.currentFile.size - 1 + + if isNaN(start) and not isNaN(end) + result.Start = self.currentFile.size - end + result.End = self.currentFile.size - 1 + + if not @request.headers.range or (result.Start >= self.currentFile.size or result.End >= self.currentFile.size) + resp.writeHead 416, + 'Content-Range': "bytes */#{self.currentFile.size}" + resp.end() + return null + + stream = fs.createReadStream self.currentFile.path, {start: result.Start, end: result.End} + resp.writeHead 206, + 'Content-Range': "bytes #{result.Start}-#{result.End}/#{self.currentFile.size}" + 'Cache-Control': 'no-cache' + 'Content-Type': self.currentFile.type + 'Content-Encoding': 'binary' + 'Content-Disposition': 'attachment; filename=' + encodeURI self.currentFile.name + ';' + 'Content-Length': if result.Start == result.End then 0 else (result.End - result.Start + 1); + 'Accept-Ranges': 'bytes' + stream.pipe resp else stream = fs.createReadStream self.currentFile.path resp.writeHead 200, @@ -428,6 +457,6 @@ if Meteor.isClient ### Template.registerHelper 'fileURL', (fileRef) -> if fileRef._id - return "#{fileRef._downloadRoute}/#{fileRef._id}/#{fileRef._collectionName}" + return "#{fileRef._downloadRoute}/#{fileRef._collectionName}/#{fileRef._id}/#{fileRef._id}.#{fileRef.extension}" else null \ No newline at end of file diff --git a/package.js b/package.js index d1747986..0514b245 100644 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'ostrio:files', - version: '1.0.3', + version: '1.0.4', summary: 'Upload, Store and Download small and huge files to/from file system (FS) via DDP and HTTP', git: 'https://github.com/VeliovGroup/Meteor-Files', documentation: 'README.md'