リモートサーバーのファイルをデータベースに保存せず、クライアントで直接表示させられるように
This commit is contained in:
		
							parent
							
								
									e804757d83
								
							
						
					
					
						commit
						558b897700
					
				
					 10 changed files with 110 additions and 60 deletions
				
			
		| 
						 | 
				
			
			@ -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