Skip to content
Snippets Groups Projects
Commit 536fba74 authored by Rodrigo Nascimento's avatar Rodrigo Nascimento
Browse files

Avatar: Refactor some code, allow set avatar from server for FS and GridFS

parent 1c72057f
No related merge requests found
Showing
with 250 additions and 154 deletions
......@@ -11,7 +11,7 @@ if (UploadFS) {
Meteor.fileStore = new UploadFS.store.GridFS({
collection: RocketChat.models.Uploads.model,
name: 'rocketchat_uploads',
name: 'GridFS:Uploads',
collectionName: 'rocketchat_uploads',
filter: new UploadFS.Filter({
onCheck: FileUpload.validateFileUpload
......@@ -75,7 +75,7 @@ if (UploadFS) {
Meteor.fileStoreAvatar = new UploadFS.store.GridFS({
collection: RocketChat.models.Avatars.model,
name: 'rocketchat_uploads_avatar',
name: 'GridFS:Avatars',
collectionName: 'rocketchat_avatars',
// filter: new UploadFS.Filter({
// onCheck: FileUpload.validateFileUpload
......
......@@ -2,7 +2,7 @@
FileSystemStore = new UploadFS.store.Local({
collection: RocketChat.models.Uploads.model,
name: 'fileSystem',
name: 'FileSystem:Uploads',
filter: new UploadFS.Filter({
onCheck: FileUpload.validateFileUpload
})
......@@ -10,7 +10,7 @@ FileSystemStore = new UploadFS.store.Local({
FileSystemStoreAvatar = new UploadFS.store.Local({
collection: RocketChat.models.Avatars.model,
name: 'fileSystemAvatar',
name: 'FileSystem:Avatars',
filter: new UploadFS.Filter({
onCheck: FileUpload.validateFileUpload
})
......
/* globals Slingshot, FileUpload, AWS */
const crypto = Npm.require('crypto');
import crypto from 'crypto';
import { FileUploadClass } from '../lib/FileUpload';
let S3accessKey;
let S3secretKey;
......@@ -16,7 +17,9 @@ const generateURL = function(file) {
return `${ file.url }?AWSAccessKeyId=${ encodeURIComponent(S3accessKey) }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`;
};
FileUpload.addHandler('s3', {
new FileUploadClass({
name: 'S3:Uploads',
get(file, req, res) {
const fileUrl = generateURL(file);
......@@ -26,6 +29,7 @@ FileUpload.addHandler('s3', {
}
res.end();
},
delete(file) {
const s3 = new AWS.S3();
const request = s3.deleteObject({
......
/* globals FileSystemStore:true, FileUpload, UploadFS, RocketChatFile, FileSystemStoreAvatar */
/* globals FileUpload, UploadFS, RocketChatFile */
const transformWrite = function(readStream, writeStream, fileId, file) {
if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) {
return readStream.pipe(writeStream);
}
import fs from 'fs';
import { FileUploadClass } from '../lib/FileUpload';
let stream = undefined;
const identify = function(err, data) {
if (err != null) {
return stream.pipe(writeStream);
}
file.identify = {
format: data.format,
size: data.size
};
const insert = function(file, stream, cb) {
const fileId = this.store.create(file);
if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) {
return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream);
} else {
return stream.pipe(writeStream);
}
};
stream = RocketChatFile.gm(readStream).identify(identify).stream();
return;
this.store.write(stream, fileId, cb);
};
FileSystemStore = null;
FileSystemStoreAvatar = null;
const FileSystemUploads = new FileUploadClass({
name: 'FileSystem:Uploads',
// store setted bellow
const createFileSystemStore = _.debounce(function() {
const stores = UploadFS.getStores();
delete stores.fileSystem;
delete stores.fileSystemAvatar;
FileSystemStore = new UploadFS.store.Local({
path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos',
collection: RocketChat.models.Uploads.model,
filter: new UploadFS.Filter({
onCheck: FileUpload.validateFileUpload
}),
name: 'fileSystem',
transformWrite
});
FileSystemStoreAvatar = new UploadFS.store.Local({
path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos',
collection: RocketChat.models.Avatars.model,
name: 'fileSystemAvatar',
transformWrite: FileUpload.avatarTransformWrite,
onFinishUpload(file) {
// update file record to match user's username
const user = RocketChat.models.Users.findOneById(file.userId);
const oldAvatar = RocketChat.models.Avatars.findOneByName(user.username);
if (oldAvatar) {
try {
FileSystemStoreAvatar.delete(oldAvatar._id);
RocketChat.models.Avatars.deleteFile(oldAvatar._id);
} catch (e) {
console.error(e);
}
}
RocketChat.models.Avatars.updateFileNameById(file._id, user.username);
// console.log('upload finished ->', file);
}
});
}, 500);
RocketChat.settings.get('FileUpload_FileSystemPath', createFileSystemStore);
const fs = Npm.require('fs');
FileUpload.addHandler('fileSystem', {
get(file, req, res) {
const filePath = FileSystemStore.getFilePath(file._id, file);
const filePath = this.store.getFilePath(file._id, file);
try {
const stat = Meteor.wrapAsync(fs.stat)(filePath);
......@@ -87,7 +26,7 @@ FileUpload.addHandler('fileSystem', {
res.setHeader('Content-Type', file.type);
res.setHeader('Content-Length', file.size);
FileSystemStore.getReadStream(file._id, file).pipe(res);
this.store.getReadStream(file._id, file).pipe(res);
}
} catch (e) {
res.writeHead(404);
......@@ -96,12 +35,13 @@ FileUpload.addHandler('fileSystem', {
}
},
delete(file) {
return FileSystemStore.delete(file._id);
}
insert
});
FileUpload.addHandler('fileSystemAvatar', {
const FileSystemAvatars = new FileUploadClass({
name: 'FileSystem:Avatars',
// store setted bellow
get(file, req, res) {
const reqModifiedHeader = req.headers['if-modified-since'];
if (reqModifiedHeader) {
......@@ -113,7 +53,7 @@ FileUpload.addHandler('fileSystemAvatar', {
}
}
const filePath = FileSystemStoreAvatar.getFilePath(file._id, file);
const filePath = this.store.getFilePath(file._id, file);
try {
const stat = Meteor.wrapAsync(fs.stat)(filePath);
......@@ -125,7 +65,7 @@ FileUpload.addHandler('fileSystemAvatar', {
res.setHeader('Content-Type', file.type);
res.setHeader('Content-Length', file.size);
FileSystemStoreAvatar.getReadStream(file._id, file).pipe(res);
this.store.getReadStream(file._id, file).pipe(res);
}
} catch (e) {
res.writeHead(404);
......@@ -134,7 +74,73 @@ FileUpload.addHandler('fileSystemAvatar', {
}
},
delete(file) {
return FileSystemStoreAvatar.delete(file._id);
}
insert
});
const transformWrite = function(readStream, writeStream, fileId, file) {
if (RocketChatFile.enabled === false || !/^image\/((x-windows-)?bmp|p?jpeg|png)$/.test(file.type)) {
return readStream.pipe(writeStream);
}
let stream = undefined;
const identify = function(err, data) {
if (err != null) {
return stream.pipe(writeStream);
}
file.identify = {
format: data.format,
size: data.size
};
if ([null, undefined, '', 'Unknown', 'Undefined'].indexOf(data.Orientation) === -1) {
return RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream);
} else {
return stream.pipe(writeStream);
}
};
stream = RocketChatFile.gm(readStream).identify(identify).stream();
return;
};
const createFileSystemStore = _.debounce(function() {
const stores = UploadFS.getStores();
delete stores['FileSystem:Uploads'];
delete stores['FileSystem:Avatars'];
FileSystemUploads.store = new UploadFS.store.Local({
path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos',
collection: FileSystemUploads.model.model,
filter: new UploadFS.Filter({
onCheck: FileUpload.validateFileUpload
}),
name: FileSystemUploads.name,
transformWrite
});
FileSystemAvatars.store = new UploadFS.store.Local({
path: RocketChat.settings.get('FileUpload_FileSystemPath'), //'/tmp/uploads/photos',
collection: FileSystemAvatars.model.model,
name: FileSystemAvatars.name,
transformWrite: FileUpload.avatarTransformWrite,
onFinishUpload(file) {
// update file record to match user's username
const user = RocketChat.models.Users.findOneById(file.userId);
const oldAvatar = FileSystemAvatars.model.findOneByName(user.username);
if (oldAvatar) {
try {
FileSystemAvatars.deleteById(oldAvatar._id);
} catch (e) {
console.error(e);
}
}
FileSystemAvatars.model.updateFileNameById(file._id, user.username);
// console.log('upload finished ->', file);
}
});
}, 500);
RocketChat.settings.get('FileUpload_FileSystemPath', createFileSystemStore);
/* globals FileUpload, Slingshot, SystemLogger */
/* globals FileUpload, Slingshot */
const crypto = Npm.require('crypto');
import crypto from 'crypto';
import { FileUploadClass } from '../lib/FileUpload';
function generateUrlParts({ file }) {
const accessId = RocketChat.settings.get('FileUpload_GoogleStorage_AccessId');
......@@ -62,7 +63,9 @@ function createDirective(directiveName, { key, bucket, accessId, secret }) {
}
}
FileUpload.addHandler('googleCloudStorage', {
new FileUploadClass({
name: 'googleCloudStorage',
get(file, req, res) {
const fileUrl = generateGetURL({ file });
......@@ -72,12 +75,15 @@ FileUpload.addHandler('googleCloudStorage', {
}
res.end();
},
delete(file) {
if (!file || !file.googleCloudStorage) {
console.warn('Failed to delete a file which is uploaded to Google Cloud Storage, the file and googleCloudStorage properties are not defined.');
return;
}
// RocketChat.models.Uploads.deleteFile(file._id);
const url = generateDeleteUrl({ file });
if (_.isEmpty(url)) {
......
/* globals FileUpload, UploadFS */
const stream = Npm.require('stream');
const zlib = Npm.require('zlib');
const util = Npm.require('util');
import stream from 'stream';
import zlib from 'zlib';
import util from 'util';
import { FileUploadClass } from '../lib/FileUpload';
const logger = new Logger('FileUpload');
function ExtractRange(options) {
......@@ -124,7 +127,18 @@ const readFromGridFS = function(storeName, fileId, file, headers, req, res) {
}
};
FileUpload.addHandler('rocketchat_uploads', {
const insert = function(file, stream, cb) {
const fileId = this.store.create(file);
this.store.write(stream, fileId, cb);
};
new FileUploadClass({
name: 'GridFS:Uploads',
getStore() {
return Meteor.fileStore;
},
get(file, req, res) {
file = FileUpload.addExtensionTo(file);
const headers = {
......@@ -135,12 +149,16 @@ FileUpload.addHandler('rocketchat_uploads', {
};
return readFromGridFS(file.store, file._id, file, headers, req, res);
},
delete(file) {
return Meteor.fileStore.delete(file._id);
}
insert
});
FileUpload.addHandler('rocketchat_uploads_avatar', {
new FileUploadClass({
name: 'GridFS:Avatars',
getStore() {
return Meteor.fileStoreAvatar
},
get(file, req, res) {
const reqModifiedHeader = req.headers['if-modified-since'];
if (reqModifiedHeader) {
......@@ -163,7 +181,6 @@ FileUpload.addHandler('rocketchat_uploads_avatar', {
};
return readFromGridFS(file.store, file._id, file, headers, req, res);
},
delete(file) {
return Meteor.fileStore.delete(file._id);
}
insert
});
/* globals FileUpload:true */
import mime from 'mime-type/with-db';
FileUpload.handlers = {};
Object.assign(FileUpload, {
handlers: {},
FileUpload.addHandler = function(store, handler) {
this.handlers[store] = handler;
};
avatarTransformWrite(readStream, writeStream/*, fileId, file*/) {
if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) {
return readStream.pipe(writeStream);
}
const height = RocketChat.settings.get('Accounts_AvatarSize');
const width = height;
return RocketChatFile.gm(readStream).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream);
},
FileUpload.delete = function(fileId) {
const file = RocketChat.models.Uploads.findOneById(fileId);
addExtensionTo(file) {
if (mime.lookup(file.name) === file.type) {
return file;
}
if (!file) {
return;
const ext = mime.extension(file.type);
if (ext && false === new RegExp(`\.${ ext }$`, 'i').test(file.name)) {
file.name = `${ file.name }.${ ext }`;
}
return file;
},
getStore(modelName) {
const storageType = RocketChat.settings.get('FileUpload_Storage_Type');
const handlerName = `${ storageType }:${ modelName }`;
if (this.handlers[handlerName] == null) {
console.error(`Upload handler "${ handlerName }" does not exists`);
}
return this.handlers[handlerName];
},
get(file, req, res, next) {
if (file.store && this.handlers && this.handlers[file.store] && this.handlers[file.store].get) {
this.handlers[file.store].get(file, req, res, next);
} else {
res.writeHead(404);
res.end();
return;
}
}
});
this.handlers[file.store].delete(file);
export class FileUploadClass {
constructor({ name, model, store, get, insert, getStore }) {
this.name = name;
this.model = model || this.getModelFromName();
this._store = store;
this.get = get;
this.insert = insert;
return RocketChat.models.Uploads.deleteFile(file._id);
};
if (getStore) {
this.getStore = getStore;
}
FileUpload.get = function(file, req, res, next) {
if (file.store && this.handlers && this.handlers[file.store] && this.handlers[file.store].get) {
this.handlers[file.store].get.call(this, file, req, res, next);
} else {
res.writeHead(404);
res.end();
return;
FileUpload.handlers[name] = this;
}
};
FileUpload.addExtensionTo = function(file) {
if (mime.lookup(file.name) === file.type) {
return file;
getStore() {
return this._store;
}
const ext = mime.extension(file.type);
if (ext && false === new RegExp(`\.${ ext }$`, 'i').test(file.name)) {
file.name = `${ file.name }.${ ext }`;
get store() {
return this.getStore();
}
return file;
};
set store(store) {
this._store = store;
}
FileUpload.avatarTransformWrite = function(readStream, writeStream, fileId, file) {
if (RocketChatFile.enabled === false || RocketChat.settings.get('Accounts_AvatarResize') !== true) {
return readStream.pipe(writeStream);
getModelFromName() {
return RocketChat.models[this.name.split(':')[1]];
}
const height = RocketChat.settings.get('Accounts_AvatarSize');
const width = height;
return RocketChatFile.gm(readStream).background('#ffffff').resize(width, `${ height }^`).gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream);
};
delete(fileId) {
if (this.store && this.store.delete) {
this.store.delete(fileId);
}
return this.model.deleteFile(fileId);
}
deleteById(fileId) {
const file = this.model.findOneById(fileId);
if (!file) {
return;
}
return this.delete(file._id);
}
deleteByName(fileName) {
const file = this.model.findOneByName(fileName);
if (!file) {
return;
}
return this.delete(file._id);
}
}
......@@ -21,7 +21,7 @@ RocketChat.deleteMessage = function(message, user) {
}
if (message.file && message.file._id) {
FileUpload.delete(message.file._id);
FileUpload.getStore('Uploads').deleteById(message.file._id);
}
Meteor.defer(function() {
......
/* globals RocketChat */
RocketChat.deleteUser = function(userId) {
const user = RocketChat.models.Users.findOneById(userId);
......@@ -22,7 +21,7 @@ RocketChat.deleteUser = function(userId) {
// removes user's avatar
if (user.avatarOrigin === 'upload' || user.avatarOrigin === 'url') {
RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ user.username }.jpg`));
FileUpload.getStore('Avatars').deleteByName(user.username);
}
RocketChat.models.Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted.
......
......@@ -40,13 +40,18 @@ RocketChat.setUserAvatar = function(user, dataURI, contentType, service) {
}
const rs = RocketChatFile.bufferToStream(new Buffer(image, encoding));
RocketChatFileAvatarInstance.deleteFile(encodeURIComponent(`${ user.username }.jpg`));
const ws = RocketChatFileAvatarInstance.createWriteStream(encodeURIComponent(`${ user.username }.jpg`), contentType);
ws.on('end', Meteor.bindEnvironment(function() {
const fileStore = FileUpload.getStore('Avatars');
fileStore.deleteByName(user.username);
const file = {
userId: user._id,
type: contentType
};
fileStore.insert(file, rs, () => {
Meteor.setTimeout(function() {
RocketChat.models.Users.setAvatarOrigin(user._id, service);
RocketChat.Notifications.notifyLogged('updateAvatar', {username: user.username});
}, 500);
}));
rs.pipe(ws);
});
};
......@@ -9,6 +9,6 @@ Meteor.methods({
return Meteor.call('deleteMessage', msg);
}
return FileUpload.delete(fileID);
return FileUpload.getStore('Uploads').deleteById(fileID);
}
});
......@@ -13,7 +13,7 @@ Meteor.methods({
}
const user = Meteor.user();
RocketChatFileAvatarInstance.deleteFile(`${ user.username }.jpg`);
FileUpload.getStore('Avatars').deleteByName(user.username);
RocketChat.models.Users.unsetAvatarOrigin(user._id);
RocketChat.Notifications.notifyLogged('updateAvatar', {
username: user.username
......
......@@ -21,16 +21,19 @@ Meteor.startup(function() {
RocketChat.authz.addUserRoles('rocket.cat', 'bot');
const rs = RocketChatFile.bufferToStream(new Buffer(Assets.getBinary('avatars/rocketcat.png'), 'utf8'));
const fileStore = FileUpload.getStore('Avatars');
fileStore.deleteByName('rocket.cat');
RocketChatFileAvatarInstance.deleteFile('rocket.cat.jpg');
const ws = RocketChatFileAvatarInstance.createWriteStream('rocket.cat.jpg', 'image/png');
ws.on('end', Meteor.bindEnvironment(function() {
return RocketChat.models.Users.setAvatarOrigin('rocket.cat', 'local');
}));
const file = {
userId: 'rocket.cat',
type: 'image/png'
};
rs.pipe(ws);
Meteor.runAsUser('rocket.cat', () => {
fileStore.insert(file, rs, () => {
return RocketChat.models.Users.setAvatarOrigin('rocket.cat', 'local');
});
});
}
if (process.env.ADMIN_PASS) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment