Meteor-Files
This package allows to:
- Upload file(s) via DDP
- Small files
- Huge files, tested on 100GB (Note Browser will eat 7%-10% RAM of the file size)
- Pause / Resume upload
- Auto-pause when connection to server is interrupted
- Multi-stream async upload (faster than ever)
- 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
andContent-Range
headers - Correct
206
and416
responses
- Correct
- Download uploaded files from server via HTTP
- You able to specify
route
- Download huge files via stream
pipe
, tested on 100GB - Fast download for small files
- You able to specify
- Store wherever you like
- You may use
Meteor-Files
as temporary storage - After file is uploaded and stored on FS you able to
mv
orcp
it's content
- You may use
- Support of non-latin (non-Roman) file names
- Subscribe on files you need
Meteor-Files
?
Why cfs
is a good package, but buggy cause it's huge monster which combine everything. In Meteor-Files
is nothing to broke, it's simply upload/store/retrieve files to/from server.
- You need store to GridFS? - Add it yourself
- You need to check file mime-type or extensions? - Add it yourself
- You need to resize images after upload? - Add it yourself
Easy-peasy kids, yeah?
Install:
meteor add ostrio:files
API
new Meteor.Files([config])
[{Isomorphic}]
config
is optional object with next properties:
storagePath
{String} - Storage path on file system- Default value:
/assets/app/uploads
- Default value:
collectionName
{String} - Collection name- Default value:
MeteorUploadFiles
- Default value:
downloadRoute
{String} - Server Route used to retrieve files- Default value:
/cdn/storage
- Default value:
schema
{Object} - Collection Schema (Not editable for current release)chunkSize
{Number} - Upload chunk size- Default value:
272144
- Default value:
namingFunction
{Function} - Function which returnsString
- Default value:
String.rand
- Default value:
permissions
{Number} - Permissions or access rights in octal, like0755
or0777
onbeforeunloadMessage
{String or Function} - Message shown to user when closing browser's window or tab, while upload in the progressallowClientCode
{Boolean} - Allow to runremove()
from clientdebug
{Boolean} - Turn on/of debugging and extra logging- Default value:
false
- Default value:
myFiles.cacheControl = 'public, max-age=31536000' # Set 'Cache-Control' header for downloads
myFiles = new Meteor.Files
storagePath: 'assets/app/uploads/myFiles'
collectionName: 'myFiles'
chunkSize: 256*128
permissions: 0o777
allowClientCode: true
onbeforeunloadMessage: ->
i18n.get '_app.abortUpload' # See 'ostrio:i18n' package
fileObject = myFiles.findOne 'recordId'
if Meteor.isClient
myFiles.collection.subscribe "MeteorFileSubs", postId.get()
if Meteor.isServer
Meteor.publish "MeteorFileSubs", (postId) ->
myFiles.collection.find {'meta.postId': postId}
myFiles.insert(file) # Upload file
myFiles.find({'meta.userId': Meteor.userId()}).cursor # Current collection cursor
myFiles.find({'meta.userId': Meteor.userId()}).get() # Array of fetched rows
myFiles.find({'meta.userId': Meteor.userId()}).remove() # Remove all files on the cursor
myFiles.remove({'meta.userId': Meteor.userId()}) # Remove all files returned by passed search
myFiles.remove(fileRef._id) # Remove file by ID
myFiles.findOne(fileRef._id).get() # Get fileRef
myFiles.findOne(fileRef._id).link() # Get download link
myFiles.findOne(fileRef._id).link('version') # Get download link of specific version
myFiles.link(fileObject) # Get download link
myFiles.link(fileObject, 'version') # Get download link of specific version
myFiles.findOne(fileRef._id).remove() # Remove file
File streaming:
To stream file add ?play=true
query to download link.
audio = new Meteor.Files()
if Meteor.isClient
Template.my.helpers
audiofiles: ->
audio.find({'meta.post': postId}).cursor
In template:
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 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.
uploads = new Meteor.Files()
if Meteor.isClient
Template.my.helpers
files: ->
uploads.find({'meta.post': postId}).cursor
In template:
template(name="my")
ul
each files
li
a(href="{{fileURL this}}?download=true" target="_parent") name
Current schema:
name:
type: String
type:
type: String
extension:
type: String
path:
type: String
meta:
type: Object
blackbox: true
optional: true
versions:
type: Object
blackbox: true
###
Example
original:
path: String
size: Number
type: String
extension: String
...
other:
path: String
size: Number
type: String
extension: String
###
userId:
type: String
optional: true
isVideo:
type: Boolean
isAudio:
type: Boolean
isImage:
type: Boolean
size:
type: Number
Template Helper
fileRef
object, so there is no need for subscription:
To get download URL for file, you only need a(href="{{fileURL fileRef}}?download=true" target="_parent" download) {{fileRef.name}}
version
:
To get specific version of the file use second argument Note: If requested version of file is not available - the original file will be returned
a(href="{{fileURL fileRef 'small'}}?download=true" target="_parent" download) {{fileRef.name}}
To display thumbnail:
Note: If thumbnail (basically version of the file) is not available the original file will be returned
img(src="{{fileURL fileRef 'thumb'}}" alt="{{fileRef.name}}")
To stream video:
video(width="80%" height="auto" controls="controls" poster="{{fileURL fileRef 'videoPoster'}}")
source(src="{{fileURL fileRef 'ogg'}}?play=true" type="video/ogg")
source(src="{{fileURL fileRef 'mp4'}}?play=true" type="video/mp4")
source(src="{{fileURL fileRef 'webm'}}?play=true" type="video/webm")
Note!: There is no build-in way for image or video resizing, encoding and re-sampling, below example how you can multiple file versions:
FilesCollection = new Meteor.Files()
if Meteor.isClient
'change #upload': (e, template) ->
_.each e.currentTarget.files, (file) ->
Collections.FilesCollection.insert
file: file
onUploaded: (error, fileObj) ->
if error
alert error.message
throw Meteor.log.warn "File Upload Error", error
template.$(e.target).val('')
template.$(e.currentTarget).val('')
Meteor.call 'convertVideo', fileObj, () ->
alert "File \"#{fileObj.name}\" successfully uploaded"
onProgress: _.throttle (progress) ->
template.$('input#progress').val progress
,
500
onBeforeUpload: () ->
if ['ogg', 'mp4', 'avi', 'webm'].inArray(@ext) and @size < 512 * 1048 * 1048
true
else
"Please upload file in next formats: 'ogg', 'mp4', 'avi', 'webm' with size less than 512 Mb. You have tried to upload file with \"#{@ext}\" extension and with \"#{Math.round((@size/(1024*1024)) * 100) / 100}\" Mb"
streams: 8
if Meteor.isServer
###
@var {object} bound - Meteor.bindEnvironment aka Fiber wrapper
###
bound = Meteor.bindEnvironment (callback) ->
return callback()
###
@description Require "fs-extra" npm package
###
fs = Npm.require "fs-extra"
Meteor.methods
convertVideo: (fileRef) ->
check fileRef, Object
sourceFile = ffmpeg(fileRef.path).noProfile()
formats =
ogg: true
mp4: true
webm: true
_.each formats, (convert, format) ->
file = _.clone sourceFile
bound ->
version = file.someHowConvertVideoAndReturnFileData(format)
upd =
$set: {}
upd['$set']['versions.' + name] =
path: version.path
size: version.size
type: version.type
extension: version.extension
FilesCollection.collection.update fileRef._id, upd
return true
Methods
insert(settings)
[Client]
settings
is required object with next properties:
file
{File} or {Object} - [REQUIRED] HTML5files
item, like in change event:event.currentTarget.files[0]
meta
{Object} - Additional data as object, use later for searchonUploaded
{Function} - Callback triggered when upload is finished, with two arguments:error
fileRef
- see Current schema section above
onProgress
{Function} - Callback triggered when chunk is sent, with only argument: {*progress
Number} - Current progress from0
to100
onBeforeUpload
{Function} - Callback triggered right before upload is started, with no arguments:- Context of the function is
File
- so you are able to check for extension, mime-type, size and etc. - return
true
to continue - return
false
to abort upload
- Context of the function is
streams
{Number} - Quantity of parallel upload streams
Returns {Object}, with properties:
onPause
{ReactiveVar} - Is upload process on the pause?progress
{ReactiveVar} - Upload progress in percentspause
{Function} - Pause upload processcontinue
{Function} - Continue paused upload processtoggleUpload
{Function} - Togglecontinue
/pause
if upload in the progress
# For example we upload file for blog post
post = Posts.findOne(someId) # Get blog post reference
uploads = new Meteor.Files() # Create Meteor.Files instance
if Meteor is client
# Let's create progress-bar
currentUploadProgress = new ReactiveVar false
Template.my.helpers
progress: ->
currentUploadProgress.get()
Template.my.events
'change #file': (e) ->
_.each e.currentTarget.files, (file) ->
uploads.insert
file: file
meta:
post: post._id # Add meta object with reference to blog post
onUploaded: (error, fileObj) ->
if not error
doSomething fileRef.path, post._id, fileRef
currentUploadProgress.set false
$(e.target).val('')
onProgress: _.throttle (progress) ->
currentUploadProgress.set progress
,
500
onBeforeUpload: () ->
# Set Allowed Extensions and max file size
allowedExt = ['mp3', 'm4a']
allowedMaxSize = 26214400
if allowedExt.inArray(@ext) and @size < allowedMaxSize # See `ostrio:jsextensions` package
true
else
"Max upload size is #{Math.round((allowedMaxSize/(1024*1024)) * 100) / 100} Mb. Allowed extensions is #{allowedExt.join(', ')}"
streams: 8
Progress bar in template (TWBS):
template(name="my")
input.btn.btn-success#file(type="file")
if progress
.progress
.progress-bar.progress-bar-striped.active(style="width:{{progress}}%")
span.sr-only {{progress}}%
collection
[Isomorphic]
Mongo Collection Instance - Use to fetch data. Do not remove
or update
this collection
uploads = new Meteor.Files()
if Meteor.isClient
# postId.get() is some ReactiveVar or session
Meteor.subscribe "MeteorFileSubs", postId.get()
if Meteor.isServer
Meteor.publish "MeteorFileSubs", (postId) ->
uploads.collection.find {'meta.postId': postId}
uploads.collection.find({'meta.post': post._id})
uploads.collection.findOne('hdjJDSHW6784kJS')
findOne(search)
[Isomorphic]
search
{String or Object} -_id
of the file orObject
uploads = new Meteor.Files()
uploads.findOne('hdjJDSHW6784kJS').get() # Get fileRef
uploads.findOne('hdjJDSHW6784kJS').remove() # Remove file
uploads.findOne('hdjJDSHW6784kJS').link('version') # Get download link
uploads.findOne({'meta.post': post._id}).get() # Get fileRef
uploads.findOne({'meta.post': post._id}).remove() # Remove file
uploads.findOne({'meta.post': post._id}).link() # Get download link
find(search)
[Isomorphic]
search
{String or Object} -_id
of the file orObject
uploads = new Meteor.Files()
uploads.find({'meta.post': post._id}).cursor # Current cursor
uploads.find({'meta.post': post._id}).fetch() # Get cursor as Array (Array of objects)
uploads.find('hdjJDSHW6784kJS').get() # Get array of fileRef(s)
uploads.find({'meta.post': post._id}).get() # Get array of fileRef(s)
uploads.find({'meta.post': post._id}).remove() # Remove all files on cursor
write(buffer, [options], [callback])
[Server]
buffer
{Buffer} - Binary dataoptions
{Object} - Object with next properties:type
- File mime-typesize
- File sizemeta
- Additional data as object, use later for searchname
orfileName
- File name
callback(error, fileObj)
Returns:
fileObj
{Object}
uploads = new Meteor.Files()
buffer = fs.readFileSync 'path/to/file.jpg'
uploads.write buffer
,
type: 'image/jpeg'
name: 'MyImage.jpg'
meta:
post: post._id
,
(err, fileObj) ->
# Download File
window.open uploads.link(fileObj, 'original'), '_parent'