From 1c7a1949501973014dfa6efa79a9d33d1a292a7e Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
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 701d547776..277d628ac9 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<IDriveFile> {
+async function save(path: string, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
+	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<IDriveFile>((resolve, reject) => {
-			const writeStream = bucket.openUploadStream(name, { contentType: type, metadata });
+		const file = await new Promise<IDriveFile>((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<IDriveFile>((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 <syuilotan@yahoo.co.jp>
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 55218625c1..3b5be19dcf 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 @@
 		<p>%i18n:@banner%</p>
 	</div>
 	<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
-		<img :src="file.url" alt="" @load="onThumbnailLoaded"/>
+		<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
 	</div>
 	<p class="name">
 		<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
diff --git a/src/client/app/desktop/views/components/media-image.vue b/src/client/app/desktop/views/components/media-image.vue
index 74bb03f4ed..8b68f260fa 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 d9d68fa7ba..e40069bbe3 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 38e1bf549c..358dd89441 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 1a76b0e41f..b904bda91b 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 277d628ac9..b8a2a33da4 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 5494023f46..a417d260fa 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 <syuilotan@yahoo.co.jp>
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 776e11ecf8..c337629cb6 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})`
 			};
 		}
 	},