リモートサーバーのファイルをデータベースに保存せず、クライアントで直接表示させられるように

This commit is contained in:
syuilo 2018-05-25 20:19:14 +09:00
parent e804757d83
commit 558b897700
10 changed files with 110 additions and 60 deletions

View file

@ -8,7 +8,8 @@ const { default: User } = require('../built/models/user');
const q = { const q = {
'metadata._user.host': { 'metadata._user.host': {
$ne: null $ne: null
} },
'metadata.isMetaOnly': false
}; };
async function main() { async function main() {
@ -56,8 +57,7 @@ async function main() {
DriveFile.update({ _id: file._id }, { DriveFile.update({ _id: file._id }, {
$set: { $set: {
'metadata.deletedAt': new Date(), 'metadata.isMetaOnly': true
'metadata.isExpired': true
} }
}) })
]).then(async () => { ]).then(async () => {

View file

@ -855,6 +855,8 @@ mobile/views/pages/settings.vue:
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
load-raw-images: "添付された画像を高画質で表示する"
load-remote-media: "リモートサーバーのメディアを表示する"
twitter: "Twitter連携" twitter: "Twitter連携"
twitter-connect: "Twitterアカウントに接続する" twitter-connect: "Twitterアカウントに接続する"
twitter-reconnect: "再接続する" twitter-reconnect: "再接続する"

View file

@ -16,13 +16,18 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
lightmode(): boolean {
return this.$store.state.device.lightmode;
},
style(): any { 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 { return {
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent', '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
}; };
} }
} }

View file

@ -59,6 +59,14 @@
<md-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch> <md-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch>
</div> </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> <div>
<md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch> <md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch>
</div> </div>
@ -166,6 +174,11 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); } 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: { lang: {
get() { return this.$store.state.device.lang; }, get() { return this.$store.state.device.lang; },
set(value) { this.$store.commit('device/set', { key: 'lang', value }); } 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) { onChangeCircleIcons(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'circleIcons', key: 'circleIcons',

View file

@ -13,7 +13,9 @@ const defaultSettings = {
gradientWindowHeader: false, gradientWindowHeader: false,
showReplyTarget: true, showReplyTarget: true,
showMyRenotes: true, showMyRenotes: true,
showRenotedMyNotes: true showRenotedMyNotes: true,
loadRemoteMedia: true,
disableViaMobile: false
}; };
const defaultDeviceSettings = { const defaultDeviceSettings = {
@ -26,6 +28,7 @@ const defaultDeviceSettings = {
preventUpdate: false, preventUpdate: false,
debug: false, debug: false,
lightmode: false, lightmode: false,
loadRawImages: false,
postStyle: 'standard' postStyle: 'standard'
}; };

View file

@ -41,6 +41,8 @@ export type Source = {
secret_key: string; secret_key: string;
}; };
preventCacheRemoteFiles: boolean;
/** /**
* ID * ID
*/ */

View file

@ -32,7 +32,7 @@ export type IMetadata = {
uri?: string; uri?: string;
url?: string; url?: string;
deletedAt?: Date; deletedAt?: Date;
isExpired?: boolean; isMetaOnly?: boolean;
}; };
export type IDriveFile = { export type IDriveFile = {
@ -155,7 +155,8 @@ export const pack = (
_target = Object.assign(_target, _file.metadata); _target = Object.assign(_target, _file.metadata);
_target.src = _file.metadata.url; _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 = {}; if (_target.properties == null) _target.properties = {};

View file

@ -33,11 +33,12 @@ export default async function(ctx: Koa.Context) {
if (file.metadata.deletedAt) { if (file.metadata.deletedAt) {
ctx.status = 410; ctx.status = 410;
if (file.metadata.isExpired) { await send(ctx, '/tombstone.png', { root: assets });
await send(ctx, '/cache-expired.png', { root: assets }); return;
} else { }
await send(ctx, '/tombstone.png', { root: assets });
} if (file.metadata.isMetaOnly) {
ctx.status = 204;
return; return;
} }

View file

@ -104,6 +104,7 @@ export default async function(
comment: string = null, comment: string = null,
folderId: mongodb.ObjectID = null, folderId: mongodb.ObjectID = null,
force: boolean = false, force: boolean = false,
metaOnly: boolean = false,
url: string = null, url: string = null,
uri: string = null uri: string = null
): Promise<IDriveFile> { ): Promise<IDriveFile> {
@ -170,38 +171,40 @@ export default async function(
} }
//#region Check drive usage //#region Check drive usage
const usage = await DriveFile if (!metaOnly) {
.aggregate([{ const usage = await DriveFile
$match: { .aggregate([{
'metadata.userId': user._id, $match: {
'metadata.deletedAt': { $exists: false } 'metadata.userId': user._id,
} 'metadata.deletedAt': { $exists: false }
}, { }
$project: { }, {
length: true $project: {
} length: true
}, { }
$group: { }, {
_id: null, $group: {
usage: { $sum: '$length' } _id: null,
} usage: { $sum: '$length' }
}]) }
.then((aggregates: any[]) => { }])
if (aggregates.length > 0) { .then((aggregates: any[]) => {
return aggregates[0].usage; if (aggregates.length > 0) {
} return aggregates[0].usage;
return 0; }
}); return 0;
});
log(`drive usage is ${usage}`); log(`drive usage is ${usage}`);
// If usage limit exceeded // If usage limit exceeded
if (usage + size > user.driveCapacity) { if (usage + size > user.driveCapacity) {
if (isLocalUser(user)) { if (isLocalUser(user)) {
throw 'no-free-space'; throw 'no-free-space';
} else { } else {
// (アバターまたはバナーを含まず)最も古いファイルを削除する // (アバターまたはバナーを含まず)最も古いファイルを削除する
deleteOldFile(user); deleteOldFile(user);
}
} }
} }
//#endregion //#endregion
@ -270,8 +273,6 @@ export default async function(
const [folder] = await Promise.all([fetchFolder(), propPromises]); const [folder] = await Promise.all([fetchFolder(), propPromises]);
const readable = fs.createReadStream(path);
const metadata = { const metadata = {
userId: user._id, userId: user._id,
_user: { _user: {
@ -279,7 +280,8 @@ export default async function(
}, },
folderId: folder !== null ? folder._id : null, folderId: folder !== null ? folder._id : null,
comment: comment, comment: comment,
properties: properties properties: properties,
isMetaOnly: metaOnly
} as IMetadata; } as IMetadata;
if (url !== null) { if (url !== null) {
@ -290,7 +292,16 @@ export default async function(
metadata.uri = uri; 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}`); log(`drive file has been created ${driveFile._id}`);
@ -300,13 +311,15 @@ export default async function(
publishDriveStream(user._id, 'file_created', packedFile); publishDriveStream(user._id, 'file_created', packedFile);
}); });
try { if (!metaOnly) {
const thumb = await genThumbnail(driveFile); try {
if (thumb) { const thumb = await genThumbnail(driveFile);
await writeThumbnailChunks(detectedName, thumb, driveFile._id); if (thumb) {
await writeThumbnailChunks(detectedName, thumb, driveFile._id);
}
} catch (e) {
// noop
} }
} catch (e) {
// noop
} }
return driveFile; return driveFile;

View file

@ -1,14 +1,17 @@
import * as fs from 'fs';
import * as URL from 'url'; import * as URL from 'url';
import { IDriveFile, validateFileName } from '../../models/drive-file';
import create from './add-file';
import * as debug from 'debug'; import * as debug from 'debug';
import * as tmp from 'tmp'; import * as tmp from 'tmp';
import * as fs from 'fs';
import * as request from 'request'; 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'); 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}`); log(`REQUESTED: ${url}`);
let name = URL.parse(url).pathname.split('/').pop(); 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; let error;
try { 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}`); log(`created: ${driveFile._id}`);
} catch (e) { } catch (e) {
error = e; error = e;