diff --git a/packages/rocketchat-file/file.server.coffee b/packages/rocketchat-file/file.server.coffee deleted file mode 100644 index 75e80a2c0dd..00000000000 --- a/packages/rocketchat-file/file.server.coffee +++ /dev/null @@ -1,256 +0,0 @@ -Grid = Npm.require('gridfs-stream') -stream = Npm.require('stream') -fs = Npm.require('fs') -path = Npm.require('path') -mkdirp = Npm.require('mkdirp') -gm = Npm.require('gm') -exec = Npm.require('child_process').exec - -# Fix problem with usernames being converted to object id -Grid.prototype.tryParseObjectId = -> false - -RocketChatFile = - gm: gm - enabled: undefined - enable: -> - RocketChatFile.enabled = true - RocketChat.settings.updateOptionsById 'Accounts_AvatarResize', {alert: undefined} - disable: -> - RocketChatFile.enabled = false - RocketChat.settings.updateOptionsById 'Accounts_AvatarResize', {alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server'} - - -detectGM = -> - exec 'gm version', Meteor.bindEnvironment (error, stdout, stderr) -> - if not error? and stdout.indexOf('GraphicsMagick') > -1 - RocketChatFile.enable() - - RocketChat.Info.GraphicsMagick = - enabled: true - version: stdout - else - RocketChat.Info.GraphicsMagick = - enabled: false - - exec 'convert -version', Meteor.bindEnvironment (error, stdout, stderr) -> - if not error? and stdout.indexOf('ImageMagick') > -1 - if RocketChatFile.enabled isnt true - # Enable GM to work with ImageMagick if no GraphicsMagick - RocketChatFile.gm = RocketChatFile.gm.subClass({imageMagick: true}) - RocketChatFile.enable() - - RocketChat.Info.ImageMagick = - enabled: true - version: stdout - else - if RocketChatFile.enabled isnt true - RocketChatFile.disable() - - RocketChat.Info.ImageMagick = - enabled: false - -detectGM() - -Meteor.methods - 'detectGM': -> - detectGM() - return - - -RocketChatFile.bufferToStream = (buffer) -> - bufferStream = new stream.PassThrough() - bufferStream.end buffer - return bufferStream - -RocketChatFile.dataURIParse = (dataURI) -> - imageData = dataURI.split ';base64,' - return { - image: imageData[1] - contentType: imageData[0].replace('data:', '') - } - -RocketChatFile.addPassThrough = (st, fn) -> - pass = new stream.PassThrough() - fn pass, st - return pass - - -RocketChatFile.GridFS = class - constructor: (config={}) -> - {name, transformWrite} = config - - name ?= 'file' - - this.name = name - this.transformWrite = transformWrite - - mongo = Package.mongo.MongoInternals.NpmModule - db = Package.mongo.MongoInternals.defaultRemoteCollectionDriver().mongo.db - - this.store = new Grid(db, mongo) - this.findOneSync = Meteor.wrapAsync this.store.collection(this.name).findOne.bind this.store.collection(this.name) - this.removeSync = Meteor.wrapAsync this.store.remove.bind this.store - - this.getFileSync = Meteor.wrapAsync this.getFile.bind this - - findOne: (fileName) -> - return this.findOneSync {_id: fileName} - - remove: (fileName) -> - return this.removeSync - _id: fileName - root: this.name - - createWriteStream: (fileName, contentType) -> - self = this - - ws = this.store.createWriteStream - _id: fileName - filename: fileName - mode: 'w' - root: this.name - content_type: contentType - - if self.transformWrite? - ws = RocketChatFile.addPassThrough ws, (rs, ws) -> - file = - name: self.name - fileName: fileName - contentType: contentType - - self.transformWrite file, rs, ws - - ws.on 'close', -> - ws.emit 'end' - - return ws - - createReadStream: (fileName) -> - return this.store.createReadStream - _id: fileName - root: this.name - return undefined - - getFileWithReadStream: (fileName) -> - file = this.findOne fileName - if not file? - return undefined - - rs = this.createReadStream fileName - - return { - readStream: rs - contentType: file.contentType - length: file.length - uploadDate: file.uploadDate - } - - getFile: (fileName, cb) -> - file = this.getFileWithReadStream(fileName) - - if not file - return cb() - - data = [] - file.readStream.on 'data', Meteor.bindEnvironment (chunk) -> - data.push chunk - - file.readStream.on 'end', Meteor.bindEnvironment -> - cb null, - buffer: Buffer.concat(data) - contentType: file.contentType - length: file.length - uploadDate: file.uploadDate - - deleteFile: (fileName) -> - file = this.findOne fileName - if not file? - return undefined - - return this.remove fileName - - -RocketChatFile.FileSystem = class - constructor: (config={}) -> - {absolutePath, transformWrite} = config - - absolutePath ?= '~/uploads' - - this.transformWrite = transformWrite - - if absolutePath.split(path.sep)[0] is '~' - homepath = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE - if homepath? - absolutePath = absolutePath.replace '~', homepath - else - throw new Error('Unable to resolve "~" in path') - - this.absolutePath = path.resolve absolutePath - mkdirp.sync this.absolutePath - this.statSync = Meteor.wrapAsync fs.stat.bind fs - this.unlinkSync = Meteor.wrapAsync fs.unlink.bind fs - - this.getFileSync = Meteor.wrapAsync this.getFile.bind this - - createWriteStream: (fileName, contentType) -> - self = this - - ws = fs.createWriteStream path.join this.absolutePath, fileName - - if self.transformWrite? - ws = RocketChatFile.addPassThrough ws, (rs, ws) -> - file = - fileName: fileName - contentType: contentType - - self.transformWrite file, rs, ws - - ws.on 'close', -> - ws.emit 'end' - - return ws - - createReadStream: (fileName) -> - return fs.createReadStream path.join this.absolutePath, fileName - - stat: (fileName) -> - return this.statSync path.join this.absolutePath, fileName - - remove: (fileName) -> - return this.unlinkSync path.join this.absolutePath, fileName - - getFileWithReadStream: (fileName) -> - try - stat = this.stat fileName - rs = this.createReadStream fileName - - return { - readStream: rs - # contentType: file.contentType - length: stat.size - } - catch e - return undefined - - getFile: (fileName, cb) -> - file = this.getFileWithReadStream(fileName) - - if not file - return cb() - - data = [] - file.readStream.on 'data', Meteor.bindEnvironment (chunk) -> - data.push chunk - - file.readStream.on 'end', Meteor.bindEnvironment -> - buffer: Buffer.concat(data) - contentType: file.contentType - length: file.length - uploadDate: file.uploadDate - - deleteFile: (fileName) -> - try - stat = this.stat fileName - return this.remove fileName - catch e - return undefined diff --git a/packages/rocketchat-file/file.server.js b/packages/rocketchat-file/file.server.js new file mode 100644 index 00000000000..3a1e323ad0f --- /dev/null +++ b/packages/rocketchat-file/file.server.js @@ -0,0 +1,308 @@ + + +const Grid = Npm.require('gridfs-stream'); + +const stream = Npm.require('stream'); + +const fs = Npm.require('fs'); + +const path = Npm.require('path'); + +const mkdirp = Npm.require('mkdirp'); + +const gm = Npm.require('gm'); + +const exec = Npm.require('child_process').exec; + +Grid.tryParseObjectId = function() { + return false; +}; +//TODO: REMOVE RocketChatFile from globals +RocketChatFile = { + gm, + enabled: undefined, + enable() { + RocketChatFile.enabled = true; + return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', { + alert: undefined + }); + }, + disable() { + RocketChatFile.enabled = false; + return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', { + alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server' + }); + } +}; + +const detectGM = function() { + return exec('gm version', Meteor.bindEnvironment(function(error, stdout) { + if ((error == null) && stdout.indexOf('GraphicsMagick') > -1) { + RocketChatFile.enable(); + RocketChat.Info.GraphicsMagick = { + enabled: true, + version: stdout + }; + } else { + RocketChat.Info.GraphicsMagick = { + enabled: false + }; + } + return exec('convert -version', Meteor.bindEnvironment(function(error, stdout) { + if ((error == null) && stdout.indexOf('ImageMagick') > -1) { + if (RocketChatFile.enabled !== true) { + RocketChatFile.gm = RocketChatFile.gm.subClass({ + imageMagick: true + }); + RocketChatFile.enable(); + } + return RocketChat.Info.ImageMagick = { + enabled: true, + version: stdout + }; + } else { + if (RocketChatFile.enabled !== true) { + RocketChatFile.disable(); + } + return RocketChat.Info.ImageMagick = { + enabled: false + }; + } + })); + })); +}; + +detectGM(); + +Meteor.methods({ + 'detectGM'() { + detectGM(); + } +}); + +RocketChatFile.bufferToStream = function(buffer) { + const bufferStream = new stream.PassThrough(); + bufferStream.end(buffer); + return bufferStream; +}; + +RocketChatFile.dataURIParse = function(dataURI) { + const imageData = dataURI.split(';base64,'); + return { + image: imageData[1], + contentType: imageData[0].replace('data:', '') + }; +}; + +RocketChatFile.addPassThrough = function(st, fn) { + const pass = new stream.PassThrough(); + fn(pass, st); + return pass; +}; + +RocketChatFile.GridFS = class { + constructor(config) { + if (config == null) { + config = {}; + } + let name = config.name; + const transformWrite = config.transformWrite; + if (name == null) { + name = 'file'; + } + this.name = name; + this.transformWrite = transformWrite; + const mongo = Package.mongo.MongoInternals.NpmModule; + const db = Package.mongo.MongoInternals.defaultRemoteCollectionDriver().mongo.db; + this.store = new Grid(db, mongo); + this.findOneSync = Meteor.wrapAsync(this.store.collection(this.name).findOne.bind(this.store.collection(this.name))); + this.removeSync = Meteor.wrapAsync(this.store.remove.bind(this.store)); + this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this)); + } + + findOne(fileName) { + return this.findOneSync({ + _id: fileName + }); + } + + remove(fileName) { + return this.removeSync({ + _id: fileName, + root: this.name + }); + } + + createWriteStream(fileName, contentType) { + const self = this; + let ws = this.store.createWriteStream({ + _id: fileName, + filename: fileName, + mode: 'w', + root: this.name, + content_type: contentType + }); + if (self.transformWrite != null) { + ws = RocketChatFile.addPassThrough(ws, function(rs, ws) { + const file = { + name: self.name, + fileName, + contentType + }; + return self.transformWrite(file, rs, ws); + }); + } + ws.on('close', function() { + return ws.emit('end'); + }); + return ws; + } + + createReadStream(fileName) { + return this.store.createReadStream({ + _id: fileName, + root: this.name + }); + } + + getFileWithReadStream(fileName) { + const file = this.findOne(fileName); + if (file == null) { + return null; + } + const rs = this.createReadStream(fileName); + return { + readStream: rs, + contentType: file.contentType, + length: file.length, + uploadDate: file.uploadDate + }; + } + + getFile(fileName, cb) { + const file = this.getFileWithReadStream(fileName); + if (!file) { + return cb(); + } + const data = []; + file.readStream.on('data', Meteor.bindEnvironment(function(chunk) { + return data.push(chunk); + })); + return file.readStream.on('end', Meteor.bindEnvironment(function() { + return cb(null, { + buffer: Buffer.concat(data), + contentType: file.contentType, + length: file.length, + uploadDate: file.uploadDate + }); + })); + } + + deleteFile(fileName) { + const file = this.findOne(fileName); + if (file == null) { + return null; + } + return this.remove(fileName); + } + + +}; + +RocketChatFile.FileSystem = class { + constructor(config) { + if (config == null) { + config = {}; + } + let absolutePath = config.absolutePath; + const transformWrite = config.transformWrite; + if (absolutePath == null) { + absolutePath = '~/uploads'; + } + this.transformWrite = transformWrite; + if (absolutePath.split(path.sep)[0] === '~') { + const homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + if (homepath != null) { + absolutePath = absolutePath.replace('~', homepath); + } else { + throw new Error('Unable to resolve "~" in path'); + } + } + this.absolutePath = path.resolve(absolutePath); + mkdirp.sync(this.absolutePath); + this.statSync = Meteor.wrapAsync(fs.stat.bind(fs)); + this.unlinkSync = Meteor.wrapAsync(fs.unlink.bind(fs)); + this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this)); + } + + createWriteStream(fileName, contentType) { + const self = this; + let ws = fs.createWriteStream(path.join(this.absolutePath, fileName)); + if (self.transformWrite != null) { + ws = RocketChatFile.addPassThrough(ws, function(rs, ws) { + const file = { + fileName, + contentType + }; + return self.transformWrite(file, rs, ws); + }); + } + ws.on('close', function() { + return ws.emit('end'); + }); + return ws; + } + + createReadStream(fileName) { + return fs.createReadStream(path.join(this.absolutePath, fileName)); + } + + stat(fileName) { + return this.statSync(path.join(this.absolutePath, fileName)); + } + + remove(fileName) { + return this.unlinkSync(path.join(this.absolutePath, fileName)); + } + + getFileWithReadStream(fileName) { + try { + const stat = this.stat(fileName); + const rs = this.createReadStream(fileName); + return { + readStream: rs, + length: stat.size + }; + } catch (error1) { + return null; + } + } + + getFile(fileName, cb) { + const file = this.getFileWithReadStream(fileName); + if (!file) { + return cb(); + } + const data = []; + file.readStream.on('data', Meteor.bindEnvironment(function(chunk) { + return data.push(chunk); + })); + return file.readStream.on('end', Meteor.bindEnvironment(function() { + return { + buffer: Buffer.concat(data)({ + contentType: file.contentType, + length: file.length, + uploadDate: file.uploadDate + }) + }; + })); + } + + deleteFile(fileName) { + try { + return this.remove(fileName); + } catch (error1) { + return null; + } + } +}; diff --git a/packages/rocketchat-file/package.js b/packages/rocketchat-file/package.js index cf616d8f1f5..35a345627cd 100644 --- a/packages/rocketchat-file/package.js +++ b/packages/rocketchat-file/package.js @@ -8,10 +8,9 @@ Package.describe({ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('rocketchat:version'); - api.use('coffeescript'); api.use('ecmascript'); - api.addFiles('file.server.coffee', 'server'); + api.addFiles('file.server.js', 'server'); api.export('RocketChatFile', 'server'); });