リモートサーバーのファイルをデータベースに保存せず、クライアントで直接表示させられるように
This commit is contained in:
		
							parent
							
								
									e804757d83
								
							
						
					
					
						commit
						558b897700
					
				
					 10 changed files with 110 additions and 60 deletions
				
			
		|  | @ -8,7 +8,8 @@ const { default: User } = require('../built/models/user'); | |||
| const q = { | ||||
| 	'metadata._user.host': { | ||||
| 		$ne: null | ||||
| 	} | ||||
| 	}, | ||||
| 	'metadata.isMetaOnly': false | ||||
| }; | ||||
| 
 | ||||
| async function main() { | ||||
|  | @ -56,8 +57,7 @@ async function main() { | |||
| 
 | ||||
| 					DriveFile.update({ _id: file._id }, { | ||||
| 						$set: { | ||||
| 							'metadata.deletedAt': new Date(), | ||||
| 							'metadata.isExpired': true | ||||
| 							'metadata.isMetaOnly': true | ||||
| 						} | ||||
| 					}) | ||||
| 				]).then(async () => { | ||||
|  |  | |||
|  | @ -855,6 +855,8 @@ mobile/views/pages/settings.vue: | |||
|   behavior: "動作" | ||||
|   fetch-on-scroll: "スクロールで自動読み込み" | ||||
|   disable-via-mobile: "「モバイルからの投稿」フラグを付けない" | ||||
|   load-raw-images: "添付された画像を高画質で表示する" | ||||
|   load-remote-media: "リモートサーバーのメディアを表示する" | ||||
|   twitter: "Twitter連携" | ||||
|   twitter-connect: "Twitterアカウントに接続する" | ||||
|   twitter-reconnect: "再接続する" | ||||
|  |  | |||
|  | @ -16,13 +16,18 @@ export default Vue.extend({ | |||
| 		} | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		lightmode(): boolean { | ||||
| 			return this.$store.state.device.lightmode; | ||||
| 		}, | ||||
| 		style(): any { | ||||
| 			let url = `url(${this.image.url}?thumbnail)`; | ||||
| 
 | ||||
| 			if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) { | ||||
| 				url = null; | ||||
| 			} else if (this.raw || this.$store.state.device.loadRawImages) { | ||||
| 				url = `url(${this.image.url})`; | ||||
| 			} | ||||
| 
 | ||||
| 			return { | ||||
| 				'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent', | ||||
| 				'background-image': this.lightmode ? null : this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)` | ||||
| 				'background-image': url | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -59,6 +59,14 @@ | |||
| 						<md-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div> | ||||
| 						<md-switch v-model="loadRawImages">%i18n:@load-raw-images%</md-switch> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div> | ||||
| 						<md-switch v-model="clientSettings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</md-switch> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div> | ||||
| 						<md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch> | ||||
| 					</div> | ||||
|  | @ -166,6 +174,11 @@ export default Vue.extend({ | |||
| 			set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		loadRawImages: { | ||||
| 			get() { return this.$store.state.device.loadRawImages; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'loadRawImages', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		lang: { | ||||
| 			get() { return this.$store.state.device.lang; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'lang', value }); } | ||||
|  | @ -195,6 +208,13 @@ export default Vue.extend({ | |||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		onChangeLoadRemoteMedia(v) { | ||||
| 			this.$store.dispatch('settings/set', { | ||||
| 				key: 'loadRemoteMedia', | ||||
| 				value: v | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		onChangeCircleIcons(v) { | ||||
| 			this.$store.dispatch('settings/set', { | ||||
| 				key: 'circleIcons', | ||||
|  |  | |||
|  | @ -13,7 +13,9 @@ const defaultSettings = { | |||
| 	gradientWindowHeader: false, | ||||
| 	showReplyTarget: true, | ||||
| 	showMyRenotes: true, | ||||
| 	showRenotedMyNotes: true | ||||
| 	showRenotedMyNotes: true, | ||||
| 	loadRemoteMedia: true, | ||||
| 	disableViaMobile: false | ||||
| }; | ||||
| 
 | ||||
| const defaultDeviceSettings = { | ||||
|  | @ -26,6 +28,7 @@ const defaultDeviceSettings = { | |||
| 	preventUpdate: false, | ||||
| 	debug: false, | ||||
| 	lightmode: false, | ||||
| 	loadRawImages: false, | ||||
| 	postStyle: 'standard' | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -41,6 +41,8 @@ export type Source = { | |||
| 		secret_key: string; | ||||
| 	}; | ||||
| 
 | ||||
| 	preventCacheRemoteFiles: boolean; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * ゴーストアカウントのID | ||||
| 	 */ | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ export type IMetadata = { | |||
| 	uri?: string; | ||||
| 	url?: string; | ||||
| 	deletedAt?: Date; | ||||
| 	isExpired?: boolean; | ||||
| 	isMetaOnly?: boolean; | ||||
| }; | ||||
| 
 | ||||
| export type IDriveFile = { | ||||
|  | @ -155,7 +155,8 @@ export const pack = ( | |||
| 	_target = Object.assign(_target, _file.metadata); | ||||
| 
 | ||||
| 	_target.src = _file.metadata.url; | ||||
| 	_target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; | ||||
| 	_target.url = _file.metadata.isMetaOnly ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; | ||||
| 	_target.isRemote = _file.metadata.isMetaOnly; | ||||
| 
 | ||||
| 	if (_target.properties == null) _target.properties = {}; | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,11 +33,12 @@ export default async function(ctx: Koa.Context) { | |||
| 
 | ||||
| 	if (file.metadata.deletedAt) { | ||||
| 		ctx.status = 410; | ||||
| 		if (file.metadata.isExpired) { | ||||
| 			await send(ctx, '/cache-expired.png', { root: assets }); | ||||
| 		} else { | ||||
| 			await send(ctx, '/tombstone.png', { root: assets }); | ||||
| 		} | ||||
| 		await send(ctx, '/tombstone.png', { root: assets }); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (file.metadata.isMetaOnly) { | ||||
| 		ctx.status = 204; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -104,6 +104,7 @@ export default async function( | |||
| 	comment: string = null, | ||||
| 	folderId: mongodb.ObjectID = null, | ||||
| 	force: boolean = false, | ||||
| 	metaOnly: boolean = false, | ||||
| 	url: string = null, | ||||
| 	uri: string = null | ||||
| ): Promise<IDriveFile> { | ||||
|  | @ -170,38 +171,40 @@ export default async function( | |||
| 	} | ||||
| 
 | ||||
| 	//#region Check drive usage
 | ||||
| 	const usage = await DriveFile | ||||
| 		.aggregate([{ | ||||
| 			$match: { | ||||
| 				'metadata.userId': user._id, | ||||
| 				'metadata.deletedAt': { $exists: false } | ||||
| 			} | ||||
| 		}, { | ||||
| 			$project: { | ||||
| 				length: true | ||||
| 			} | ||||
| 		}, { | ||||
| 			$group: { | ||||
| 				_id: null, | ||||
| 				usage: { $sum: '$length' } | ||||
| 			} | ||||
| 		}]) | ||||
| 		.then((aggregates: any[]) => { | ||||
| 			if (aggregates.length > 0) { | ||||
| 				return aggregates[0].usage; | ||||
| 			} | ||||
| 			return 0; | ||||
| 		}); | ||||
| 	if (!metaOnly) { | ||||
| 		const usage = await DriveFile | ||||
| 			.aggregate([{ | ||||
| 				$match: { | ||||
| 					'metadata.userId': user._id, | ||||
| 					'metadata.deletedAt': { $exists: false } | ||||
| 				} | ||||
| 			}, { | ||||
| 				$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}`); | ||||
| 		log(`drive usage is ${usage}`); | ||||
| 
 | ||||
| 	// If usage limit exceeded
 | ||||
| 	if (usage + size > user.driveCapacity) { | ||||
| 		if (isLocalUser(user)) { | ||||
| 			throw 'no-free-space'; | ||||
| 		} else { | ||||
| 			// (アバターまたはバナーを含まず)最も古いファイルを削除する
 | ||||
| 			deleteOldFile(user); | ||||
| 		// If usage limit exceeded
 | ||||
| 		if (usage + size > user.driveCapacity) { | ||||
| 			if (isLocalUser(user)) { | ||||
| 				throw 'no-free-space'; | ||||
| 			} else { | ||||
| 				// (アバターまたはバナーを含まず)最も古いファイルを削除する
 | ||||
| 				deleteOldFile(user); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	//#endregion
 | ||||
|  | @ -270,8 +273,6 @@ export default async function( | |||
| 
 | ||||
| 	const [folder] = await Promise.all([fetchFolder(), propPromises]); | ||||
| 
 | ||||
| 	const readable = fs.createReadStream(path); | ||||
| 
 | ||||
| 	const metadata = { | ||||
| 		userId: user._id, | ||||
| 		_user: { | ||||
|  | @ -279,7 +280,8 @@ export default async function( | |||
| 		}, | ||||
| 		folderId: folder !== null ? folder._id : null, | ||||
| 		comment: comment, | ||||
| 		properties: properties | ||||
| 		properties: properties, | ||||
| 		isMetaOnly: metaOnly | ||||
| 	} as IMetadata; | ||||
| 
 | ||||
| 	if (url !== null) { | ||||
|  | @ -290,7 +292,16 @@ export default async function( | |||
| 		metadata.uri = uri; | ||||
| 	} | ||||
| 
 | ||||
| 	const driveFile = await (writeChunks(detectedName, readable, mime, metadata) as Promise<IDriveFile>); | ||||
| 	const driveFile = metaOnly | ||||
| 		? await DriveFile.insert({ | ||||
| 			length: 0, | ||||
| 			uploadDate: new Date(), | ||||
| 			md5: hash, | ||||
| 			filename: detectedName, | ||||
| 			metadata: metadata, | ||||
| 			contentType: mime | ||||
| 		}) | ||||
| 		: await (writeChunks(detectedName, fs.createReadStream(path), mime, metadata) as Promise<IDriveFile>); | ||||
| 
 | ||||
| 	log(`drive file has been created ${driveFile._id}`); | ||||
| 
 | ||||
|  | @ -300,13 +311,15 @@ export default async function( | |||
| 		publishDriveStream(user._id, 'file_created', packedFile); | ||||
| 	}); | ||||
| 
 | ||||
| 	try { | ||||
| 		const thumb = await genThumbnail(driveFile); | ||||
| 		if (thumb) { | ||||
| 			await writeThumbnailChunks(detectedName, thumb, driveFile._id); | ||||
| 	if (!metaOnly) { | ||||
| 		try { | ||||
| 			const thumb = await genThumbnail(driveFile); | ||||
| 			if (thumb) { | ||||
| 				await writeThumbnailChunks(detectedName, thumb, driveFile._id); | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			// noop
 | ||||
| 		} | ||||
| 	} catch (e) { | ||||
| 		// noop
 | ||||
| 	} | ||||
| 
 | ||||
| 	return driveFile; | ||||
|  |  | |||
|  | @ -1,14 +1,17 @@ | |||
| import * as fs from 'fs'; | ||||
| import * as URL from 'url'; | ||||
| import { IDriveFile, validateFileName } from '../../models/drive-file'; | ||||
| import create from './add-file'; | ||||
| 
 | ||||
| import * as debug from 'debug'; | ||||
| import * as tmp from 'tmp'; | ||||
| import * as fs from 'fs'; | ||||
| import * as request from 'request'; | ||||
| 
 | ||||
| import { IDriveFile, validateFileName } from '../../models/drive-file'; | ||||
| import create from './add-file'; | ||||
| import config from '../../config'; | ||||
| 
 | ||||
| const log = debug('misskey:drive:upload-from-url'); | ||||
| 
 | ||||
| export default async (url, user, folderId = null, uri = null): Promise<IDriveFile> => { | ||||
| export default async (url: string, user, folderId = null, uri: string = null): Promise<IDriveFile> => { | ||||
| 	log(`REQUESTED: ${url}`); | ||||
| 
 | ||||
| 	let name = URL.parse(url).pathname.split('/').pop(); | ||||
|  | @ -43,7 +46,7 @@ export default async (url, user, folderId = null, uri = null): Promise<IDriveFil | |||
| 	let error; | ||||
| 
 | ||||
| 	try { | ||||
| 		driveFile = await create(user, path, name, null, folderId, false, url, uri); | ||||
| 		driveFile = await create(user, path, name, null, folderId, false, config.preventCacheRemoteFiles, url, uri); | ||||
| 		log(`created: ${driveFile._id}`); | ||||
| 	} catch (e) { | ||||
| 		error = e; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue