From 1c7a1949501973014dfa6efa79a9d33d1a292a7e Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Aug 2018 06:27:35 +0900 Subject: [PATCH 1/3] wip --- src/services/drive/add-file.ts | 61 ++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 701d54777..277d628ac 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -17,30 +17,52 @@ import { publishUserStream, publishDriveStream } from '../../stream'; import { isLocalUser, IUser, IRemoteUser } from '../../models/user'; import delFile from './delete-file'; import config from '../../config'; +import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; const log = debug('misskey:drive:add-file'); -async function save(readable: stream.Readable, name: string, type: string, hash: string, size: number, metadata: any): Promise { +async function save(path: string, name: string, type: string, hash: string, size: number, metadata: any): Promise { + let thumbnail: Buffer; + + if (['image/jpeg', 'image/png', 'image/webp'].includes(type)) { + thumbnail = await sharp(path) + .resize(500) + .jpeg({ + quality: 70, + progressive: true + }) + .toBuffer(); + } + if (config.drive && config.drive.storage == 'minio') { const minio = new Minio.Client(config.drive.config); const id = uuid.v4(); const obj = `${config.drive.prefix}/${id}`; + const thumbnailObj = `${obj}-thumbnail`; const baseUrl = config.drive.baseUrl || `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`; - await minio.putObject(config.drive.bucket, obj, readable, size, { + await minio.putObject(config.drive.bucket, obj, fs.createReadStream(path), size, { 'Content-Type': type, 'Cache-Control': 'max-age=31536000, immutable' }); + if (thumbnail) { + await minio.putObject(config.drive.bucket, thumbnailObj, fs.createReadStream(path), size, { + 'Content-Type': 'image/jpeg', + 'Cache-Control': 'max-age=31536000, immutable' + }); + } + Object.assign(metadata, { withoutChunks: true, storage: 'minio', storageProps: { id: id }, - url: `${ baseUrl }/${ obj }` + url: `${ baseUrl }/${ obj }`, + thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailObj }` : null }); const file = await DriveFile.insert({ @@ -57,12 +79,37 @@ async function save(readable: stream.Readable, name: string, type: string, hash: // Get MongoDB GridFS bucket const bucket = await getDriveFileBucket(); - return new Promise((resolve, reject) => { - const writeStream = bucket.openUploadStream(name, { contentType: type, metadata }); + const file = await new Promise((resolve, reject) => { + const writeStream = bucket.openUploadStream(name, { + contentType: type, + metadata + }); + writeStream.once('finish', resolve); writeStream.on('error', reject); - readable.pipe(writeStream); + + fs.createReadStream(path).pipe(writeStream); }); + + if (thumbnail) { + const thumbnailBucket = await getDriveFileThumbnailBucket(); + + await new Promise((resolve, reject) => { + const writeStream = thumbnailBucket.openUploadStream(name, { + contentType: 'image/jpeg', + metadata: { + originalId: file._id + } + }); + + writeStream.once('finish', resolve); + writeStream.on('error', reject); + + fs.createReadStream(path).pipe(writeStream); + }); + } + + return file; } } @@ -321,7 +368,7 @@ export default async function( } } } else { - driveFile = await (save(fs.createReadStream(path), detectedName, mime, hash, size, metadata)); + driveFile = await (save(path, detectedName, mime, hash, size, metadata)); } log(`drive file has been created ${driveFile._id}`); From 61832620375cac0c942fe0c29f4c823eb83b5561 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Aug 2018 07:17:04 +0900 Subject: [PATCH 2/3] wip --- .../desktop/views/components/drive.file.vue | 2 +- .../desktop/views/components/media-image.vue | 2 +- .../mobile/views/components/media-image.vue | 2 +- src/models/drive-file.ts | 2 ++ src/server/file/send-drive-file.ts | 27 +++++++------------ src/services/drive/add-file.ts | 8 +++--- src/services/drive/delete-file.ts | 6 +++++ 7 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/client/app/desktop/views/components/drive.file.vue b/src/client/app/desktop/views/components/drive.file.vue index 55218625c..3b5be19dc 100644 --- a/src/client/app/desktop/views/components/drive.file.vue +++ b/src/client/app/desktop/views/components/drive.file.vue @@ -16,7 +16,7 @@

%i18n:@banner%

- +

{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }} diff --git a/src/client/app/desktop/views/components/media-image.vue b/src/client/app/desktop/views/components/media-image.vue index 74bb03f4e..8b68f260f 100644 --- a/src/client/app/desktop/views/components/media-image.vue +++ b/src/client/app/desktop/views/components/media-image.vue @@ -37,7 +37,7 @@ export default Vue.extend({ style(): any { return { 'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent', - 'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})` + 'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.thumbnailUrl})` }; } }, diff --git a/src/client/app/mobile/views/components/media-image.vue b/src/client/app/mobile/views/components/media-image.vue index d9d68fa7b..e40069bbe 100644 --- a/src/client/app/mobile/views/components/media-image.vue +++ b/src/client/app/mobile/views/components/media-image.vue @@ -27,7 +27,7 @@ export default Vue.extend({ }, computed: { style(): any { - let url = `url(${this.image.url})`; + let url = `url(${this.image.thumbnailUrl})`; if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) { url = null; diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 38e1bf549..358dd8944 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -31,6 +31,7 @@ export type IMetadata = { comment: string; uri?: string; url?: string; + thumbnailUrl?: string; src?: string; deletedAt?: Date; withoutChunks?: boolean; @@ -164,6 +165,7 @@ export const pack = ( _target = Object.assign(_target, _file.metadata); _target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; + _target.thumbnailUrl = _file.metadata.thumbnailUrl ? _file.metadata.thumbnailUrl : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}?thumbnail`; _target.isRemote = _file.metadata.isRemote; if (_target.properties == null) _target.properties = {}; diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index 1a76b0e41..b904bda91 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -1,5 +1,3 @@ -import * as fs from 'fs'; - import * as Koa from 'koa'; import * as send from 'koa-send'; import * as mongodb from 'mongodb'; @@ -51,23 +49,16 @@ export default async function(ctx: Koa.Context) { }; if ('thumbnail' in ctx.query) { - // 画像以外 - if (!file.contentType.startsWith('image/')) { - const readable = fs.createReadStream(`${__dirname}/assets/thumbnail-not-available.png`); - ctx.set('Content-Type', 'image/png'); - ctx.body = readable; - } else if (file.contentType == 'image/gif') { - // GIF - await sendRaw(); + const thumb = await DriveFileThumbnail.findOne({ + 'metadata.originalId': fileId + }); + + if (thumb != null) { + ctx.set('Content-Type', 'image/jpeg'); + const bucket = await getDriveFileThumbnailBucket(); + ctx.body = bucket.openDownloadStream(thumb._id); } else { - const thumb = await DriveFileThumbnail.findOne({ 'metadata.originalId': fileId }); - if (thumb != null) { - ctx.set('Content-Type', 'image/jpeg'); - const bucket = await getDriveFileThumbnailBucket(); - ctx.body = bucket.openDownloadStream(thumb._id); - } else { - await sendRaw(); - } + await sendRaw(); } } else { if ('download' in ctx.query) { diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 277d628ac..b8a2a33da 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -1,6 +1,5 @@ import { Buffer } from 'buffer'; import * as fs from 'fs'; -import * as stream from 'stream'; import * as mongodb from 'mongodb'; import * as crypto from 'crypto'; @@ -26,9 +25,9 @@ async function save(path: string, name: string, type: string, hash: string, size if (['image/jpeg', 'image/png', 'image/webp'].includes(type)) { thumbnail = await sharp(path) - .resize(500) + .resize(300) .jpeg({ - quality: 70, + quality: 50, progressive: true }) .toBuffer(); @@ -104,8 +103,7 @@ async function save(path: string, name: string, type: string, hash: string, size writeStream.once('finish', resolve); writeStream.on('error', reject); - - fs.createReadStream(path).pipe(writeStream); + writeStream.end(thumbnail); }); } diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 5494023f4..a417d260f 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -6,8 +6,14 @@ import config from '../../config'; export default async function(file: IDriveFile, isExpired = false) { if (file.metadata.storage == 'minio') { const minio = new Minio.Client(config.drive.config); + const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`; await minio.removeObject(config.drive.bucket, obj); + + if (file.metadata.thumbnailUrl) { + const thumbnailObj = `${config.drive.prefix}/${file.metadata.storageProps.id}-thumbnail`; + await minio.removeObject(config.drive.bucket, thumbnailObj); + } } // チャンクをすべて削除 From e7d9018944bdf32d179c19a96e645eb298b3732c Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Aug 2018 07:56:53 +0900 Subject: [PATCH 3/3] :v: --- src/client/app/mobile/views/components/drive.file.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/mobile/views/components/drive.file.vue b/src/client/app/mobile/views/components/drive.file.vue index 776e11ecf..c337629cb 100644 --- a/src/client/app/mobile/views/components/drive.file.vue +++ b/src/client/app/mobile/views/components/drive.file.vue @@ -43,7 +43,7 @@ export default Vue.extend({ thumbnail(): any { return { 'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent', - 'background-image': `url(${this.file.url})` + 'background-image': `url(${this.file.thumbnailUrl})` }; } },