diff options
Diffstat (limited to 'src/api/common')
| -rw-r--r-- | src/api/common/add-file-to-drive.ts | 306 | ||||
| -rw-r--r-- | src/api/common/drive/add-file.ts | 16 | ||||
| -rw-r--r-- | src/api/common/get-friends.ts | 8 | ||||
| -rw-r--r-- | src/api/common/notify.ts | 18 | ||||
| -rw-r--r-- | src/api/common/push-sw.ts | 4 | ||||
| -rw-r--r-- | src/api/common/read-messaging-message.ts | 12 | ||||
| -rw-r--r-- | src/api/common/read-notification.ts | 8 | ||||
| -rw-r--r-- | src/api/common/watch-post.ts | 14 |
8 files changed, 346 insertions, 40 deletions
diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts new file mode 100644 index 0000000000..6bf5fcbc02 --- /dev/null +++ b/src/api/common/add-file-to-drive.ts @@ -0,0 +1,306 @@ +import { Buffer } from 'buffer'; +import * as fs from 'fs'; +import * as tmp from 'tmp'; +import * as stream from 'stream'; + +import * as mongodb from 'mongodb'; +import * as crypto from 'crypto'; +import * as _gm from 'gm'; +import * as debug from 'debug'; +import fileType = require('file-type'); +import prominence = require('prominence'); + +import DriveFile, { getGridFSBucket } from '../models/drive-file'; +import DriveFolder from '../models/drive-folder'; +import { pack } from '../models/drive-file'; +import event, { publishDriveStream } from '../event'; +import config from '../../conf'; + +const gm = _gm.subClass({ + imageMagick: true +}); + +const log = debug('misskey:register-drive-file'); + +const tmpFile = (): Promise<string> => new Promise((resolve, reject) => { + tmp.file((e, path) => { + if (e) return reject(e); + resolve(path); + }); +}); + +const addToGridFS = (name: string, readable: stream.Readable, type: string, metadata: any): Promise<any> => + getGridFSBucket() + .then(bucket => new Promise((resolve, reject) => { + const writeStream = bucket.openUploadStream(name, { contentType: type, metadata }); + writeStream.once('finish', (doc) => { resolve(doc); }); + writeStream.on('error', reject); + readable.pipe(writeStream); + })); + +const addFile = async ( + user: any, + path: string, + name: string = null, + comment: string = null, + folderId: mongodb.ObjectID = null, + force: boolean = false +) => { + log(`registering ${name} (user: ${user.username}, path: ${path})`); + + // Calculate hash, get content type and get file size + const [hash, [mime, ext], size] = await Promise.all([ + // hash + ((): Promise<string> => new Promise((res, rej) => { + const readable = fs.createReadStream(path); + const hash = crypto.createHash('md5'); + const chunks = []; + readable + .on('error', rej) + .pipe(hash) + .on('error', rej) + .on('data', (chunk) => chunks.push(chunk)) + .on('end', () => { + const buffer = Buffer.concat(chunks); + res(buffer.toString('hex')); + }); + }))(), + // mime + ((): Promise<[string, string | null]> => new Promise((res, rej) => { + const readable = fs.createReadStream(path); + readable + .on('error', rej) + .once('data', (buffer: Buffer) => { + readable.destroy(); + const type = fileType(buffer); + if (type) { + return res([type.mime, type.ext]); + } else { + // 種類が同定できなかったら application/octet-stream にする + return res(['application/octet-stream', null]); + } + }); + }))(), + // size + ((): Promise<number> => new Promise((res, rej) => { + fs.stat(path, (err, stats) => { + if (err) return rej(err); + res(stats.size); + }); + }))() + ]); + + log(`hash: ${hash}, mime: ${mime}, ext: ${ext}, size: ${size}`); + + // detect name + const detectedName: string = name || (ext ? `untitled.${ext}` : 'untitled'); + + if (!force) { + // Check if there is a file with the same hash + const much = await DriveFile.findOne({ + md5: hash, + 'metadata.userId': user._id + }); + + if (much !== null) { + log('file with same hash is found'); + return much; + } else { + log('file with same hash is not found'); + } + } + + const [wh, avgColor, folder] = await Promise.all([ + // Width and height (when image) + (async () => { + // 画像かどうか + if (!/^image\/.*$/.test(mime)) { + return null; + } + + const imageType = mime.split('/')[1]; + + // 画像でもPNGかJPEGかGIFでないならスキップ + if (imageType != 'png' && imageType != 'jpeg' && imageType != 'gif') { + return null; + } + + log('calculate image width and height...'); + + // Calculate width and height + const g = gm(fs.createReadStream(path), name); + const size = await prominence(g).size(); + + log(`image width and height is calculated: ${size.width}, ${size.height}`); + + return [size.width, size.height]; + })(), + // average color (when image) + (async () => { + // 画像かどうか + if (!/^image\/.*$/.test(mime)) { + return null; + } + + const imageType = mime.split('/')[1]; + + // 画像でもPNGかJPEGでないならスキップ + if (imageType != 'png' && imageType != 'jpeg') { + return null; + } + + log('calculate average color...'); + + const buffer = await prominence(gm(fs.createReadStream(path), name) + .setFormat('ppm') + .resize(1, 1)) // 1pxのサイズに縮小して平均色を取得するというハック + .toBuffer(); + + const r = buffer.readUInt8(buffer.length - 3); + const g = buffer.readUInt8(buffer.length - 2); + const b = buffer.readUInt8(buffer.length - 1); + + log(`average color is calculated: ${r}, ${g}, ${b}`); + + return [r, g, b]; + })(), + // folder + (async () => { + if (!folderId) { + return null; + } + const driveFolder = await DriveFolder.findOne({ + _id: folderId, + userId: user._id + }); + if (!driveFolder) { + throw 'folder-not-found'; + } + return driveFolder; + })(), + // usage checker + (async () => { + // Calculate drive usage + const usage = await DriveFile + .aggregate([{ + $match: { 'metadata.userId': user._id } + }, { + $project: { + length: true + } + }, { + $group: { + _id: null, + usage: { $sum: '$length' } + } + }]) + .then((aggregates: any[]) => { + if (aggregates.length > 0) { + return aggregates[0].usage; + } + return 0; + }); + + log(`drive usage is ${usage}`); + + // If usage limit exceeded + if (usage + size > user.driveCapacity) { + throw 'no-free-space'; + } + })() + ]); + + const readable = fs.createReadStream(path); + + const properties = {}; + + if (wh) { + properties['width'] = wh[0]; + properties['height'] = wh[1]; + } + + if (avgColor) { + properties['avgColor'] = avgColor; + } + + return addToGridFS(detectedName, readable, mime, { + userId: user._id, + folderId: folder !== null ? folder._id : null, + comment: comment, + properties: properties + }); +}; + +/** + * Add file to drive + * + * @param user User who wish to add file + * @param file File path or readableStream + * @param comment Comment + * @param type File type + * @param folderId Folder ID + * @param force If set to true, forcibly upload the file even if there is a file with the same hash. + * @return Object that represents added file + */ +export default (user: any, file: string | stream.Readable, ...args) => new Promise<any>((resolve, reject) => { + // Get file path + new Promise((res: (v: [string, boolean]) => void, rej) => { + if (typeof file === 'string') { + res([file, false]); + return; + } + if (typeof file === 'object' && typeof file.read === 'function') { + tmpFile() + .then(path => { + const readable: stream.Readable = file; + const writable = fs.createWriteStream(path); + readable + .on('error', rej) + .on('end', () => { + res([path, true]); + }) + .pipe(writable) + .on('error', rej); + }) + .catch(rej); + } + rej(new Error('un-compatible file.')); + }) + .then(([path, shouldCleanup]): Promise<any> => new Promise((res, rej) => { + addFile(user, path, ...args) + .then(file => { + res(file); + if (shouldCleanup) { + fs.unlink(path, (e) => { + if (e) log(e.stack); + }); + } + }) + .catch(rej); + })) + .then(file => { + log(`drive file has been created ${file._id}`); + resolve(file); + + pack(file).then(serializedFile => { + // Publish drive_file_created event + event(user._id, 'drive_file_created', serializedFile); + publishDriveStream(user._id, 'file_created', serializedFile); + + // Register to search database + if (config.elasticsearch.enable) { + const es = require('../../db/elasticsearch'); + es.index({ + index: 'misskey', + type: 'drive_file', + id: file._id.toString(), + body: { + name: file.name, + userId: user._id.toString() + } + }); + } + }); + }) + .catch(reject); +}); diff --git a/src/api/common/drive/add-file.ts b/src/api/common/drive/add-file.ts index c4f2f212ac..b10f9e381b 100644 --- a/src/api/common/drive/add-file.ts +++ b/src/api/common/drive/add-file.ts @@ -100,7 +100,7 @@ const addFile = async ( // Check if there is a file with the same hash const much = await DriveFile.findOne({ md5: hash, - 'metadata.user_id': user._id + 'metadata.userId': user._id }); if (much !== null) { @@ -172,7 +172,7 @@ const addFile = async ( } const driveFolder = await DriveFolder.findOne({ _id: folderId, - user_id: user._id + userId: user._id }); if (!driveFolder) { throw 'folder-not-found'; @@ -184,7 +184,7 @@ const addFile = async ( // Calculate drive usage const usage = await DriveFile .aggregate([{ - $match: { 'metadata.user_id': user._id } + $match: { 'metadata.userId': user._id } }, { $project: { length: true @@ -205,7 +205,7 @@ const addFile = async ( log(`drive usage is ${usage}`); // If usage limit exceeded - if (usage + size > user.drive_capacity) { + if (usage + size > user.driveCapacity) { throw 'no-free-space'; } })() @@ -221,12 +221,12 @@ const addFile = async ( } if (averageColor) { - properties['average_color'] = averageColor; + properties['avgColor'] = averageColor; } return addToGridFS(detectedName, readable, mime, { - user_id: user._id, - folder_id: folder !== null ? folder._id : null, + userId: user._id, + folderId: folder !== null ? folder._id : null, comment: comment, properties: properties }); @@ -297,7 +297,7 @@ export default (user: any, file: string | stream.Readable, ...args) => new Promi id: file._id.toString(), body: { name: file.name, - user_id: user._id.toString() + userId: user._id.toString() } }); } diff --git a/src/api/common/get-friends.ts b/src/api/common/get-friends.ts index db6313816d..7f548b3bbf 100644 --- a/src/api/common/get-friends.ts +++ b/src/api/common/get-friends.ts @@ -6,17 +6,17 @@ export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { // SELECT followee const myfollowing = await Following .find({ - follower_id: me, + followerId: me, // 削除されたドキュメントは除く - deleted_at: { $exists: false } + deletedAt: { $exists: false } }, { fields: { - followee_id: true + followeeId: true } }); // ID list of other users who the I follows - const myfollowingIds = myfollowing.map(follow => follow.followee_id); + const myfollowingIds = myfollowing.map(follow => follow.followeeId); if (includeMe) { myfollowingIds.push(me); diff --git a/src/api/common/notify.ts b/src/api/common/notify.ts index ae5669b84c..c4df17f880 100644 --- a/src/api/common/notify.ts +++ b/src/api/common/notify.ts @@ -16,11 +16,11 @@ export default ( // Create notification const notification = await Notification.insert(Object.assign({ - created_at: new Date(), - notifiee_id: notifiee, - notifier_id: notifier, + createdAt: new Date(), + notifieeId: notifiee, + notifierId: notifier, type: type, - is_read: false + isRead: false }, content)); resolve(notification); @@ -31,14 +31,14 @@ export default ( // 3秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { - const fresh = await Notification.findOne({ _id: notification._id }, { is_read: true }); - if (!fresh.is_read) { + const fresh = await Notification.findOne({ _id: notification._id }, { isRead: true }); + if (!fresh.isRead) { //#region ただしミュートしているユーザーからの通知なら無視 const mute = await Mute.find({ - muter_id: notifiee, - deleted_at: { $exists: false } + muterId: notifiee, + deletedAt: { $exists: false } }); - const mutedUserIds = mute.map(m => m.mutee_id.toString()); + const mutedUserIds = mute.map(m => m.muteeId.toString()); if (mutedUserIds.indexOf(notifier.toString()) != -1) { return; } diff --git a/src/api/common/push-sw.ts b/src/api/common/push-sw.ts index 2993c760ee..f90233233f 100644 --- a/src/api/common/push-sw.ts +++ b/src/api/common/push-sw.ts @@ -20,7 +20,7 @@ export default async function(userId: mongo.ObjectID | string, type, body?) { // Fetch const subscriptions = await Subscription.find({ - user_id: userId + userId: userId }); subscriptions.forEach(subscription => { @@ -41,7 +41,7 @@ export default async function(userId: mongo.ObjectID | string, type, body?) { if (err.statusCode == 410) { Subscription.remove({ - user_id: userId, + userId: userId, endpoint: subscription.endpoint, auth: subscription.auth, publickey: subscription.publickey diff --git a/src/api/common/read-messaging-message.ts b/src/api/common/read-messaging-message.ts index 8e5e5b2b68..9047edec8c 100644 --- a/src/api/common/read-messaging-message.ts +++ b/src/api/common/read-messaging-message.ts @@ -37,12 +37,12 @@ export default ( // Update documents await Message.update({ _id: { $in: ids }, - user_id: otherpartyId, - recipient_id: userId, - is_read: false + userId: otherpartyId, + recipientId: userId, + isRead: false }, { $set: { - is_read: true + isRead: true } }, { multi: true @@ -55,8 +55,8 @@ export default ( // Calc count of my unread messages const count = await Message .count({ - recipient_id: userId, - is_read: false + recipientId: userId, + isRead: false }); if (count == 0) { diff --git a/src/api/common/read-notification.ts b/src/api/common/read-notification.ts index 3009cc5d08..5bbf136323 100644 --- a/src/api/common/read-notification.ts +++ b/src/api/common/read-notification.ts @@ -29,10 +29,10 @@ export default ( // Update documents await Notification.update({ _id: { $in: ids }, - is_read: false + isRead: false }, { $set: { - is_read: true + isRead: true } }, { multi: true @@ -41,8 +41,8 @@ export default ( // Calc count of my unread notifications const count = await Notification .count({ - notifiee_id: userId, - is_read: false + notifieeId: userId, + isRead: false }); if (count == 0) { diff --git a/src/api/common/watch-post.ts b/src/api/common/watch-post.ts index 1a50f0edaa..61ea444430 100644 --- a/src/api/common/watch-post.ts +++ b/src/api/common/watch-post.ts @@ -3,15 +3,15 @@ import Watching from '../models/post-watching'; export default async (me: mongodb.ObjectID, post: object) => { // 自分の投稿はwatchできない - if (me.equals((post as any).user_id)) { + if (me.equals((post as any).userId)) { return; } // if watching now const exist = await Watching.findOne({ - post_id: (post as any)._id, - user_id: me, - deleted_at: { $exists: false } + postId: (post as any)._id, + userId: me, + deletedAt: { $exists: false } }); if (exist !== null) { @@ -19,8 +19,8 @@ export default async (me: mongodb.ObjectID, post: object) => { } await Watching.insert({ - created_at: new Date(), - post_id: (post as any)._id, - user_id: me + createdAt: new Date(), + postId: (post as any)._id, + userId: me }); }; |