diff --git a/.config/example.yml b/.config/example.yml index 6c939a4f3..28a850182 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -53,6 +53,22 @@ remoteDriveCapacityMb: 8 # Users cannot see remote images when they turn off "Show media from a remote server" setting. preventCache: false +drive: + storage: 'db' + + # OR + + # storage: 'object-storage' + # service: 'minio' + # bucket: + # prefix: + # config: + # endPoint: + # port: + # secure: + # accessKey: + # secretKey: + # # Below settings are optional # diff --git a/cli/clean-cached-remote-files.js b/cli/clean-cached-remote-files.js index a9c38a4cd..5b388c73b 100644 --- a/cli/clean-cached-remote-files.js +++ b/cli/clean-cached-remote-files.js @@ -9,7 +9,7 @@ const q = { 'metadata._user.host': { $ne: null }, - 'metadata.isMetaOnly': false + 'metadata.withoutChunks': false }; async function main() { @@ -57,7 +57,7 @@ async function main() { DriveFile.update({ _id: file._id }, { $set: { - 'metadata.isMetaOnly': true + 'metadata.withoutChunks': true } }) ]).then(async () => { diff --git a/migration/2.0.0.js b/cli/migration/2.0.0.js similarity index 88% rename from migration/2.0.0.js rename to cli/migration/2.0.0.js index eb8f5730c..f7298972e 100644 --- a/migration/2.0.0.js +++ b/cli/migration/2.0.0.js @@ -3,8 +3,8 @@ const chalk = require('chalk'); const sequential = require('promise-sequential'); -const { default: User } = require('../built/models/user'); -const { default: DriveFile } = require('../built/models/drive-file'); +const { default: User } = require('../../built/models/user'); +const { default: DriveFile } = require('../../built/models/drive-file'); async function main() { const promiseGens = []; diff --git a/migration/2.4.0.js b/cli/migration/2.4.0.js similarity index 91% rename from migration/2.4.0.js rename to cli/migration/2.4.0.js index e9584a1df..aa37849aa 100644 --- a/migration/2.4.0.js +++ b/cli/migration/2.4.0.js @@ -3,8 +3,8 @@ const chalk = require('chalk'); const sequential = require('promise-sequential'); -const { default: User } = require('../built/models/user'); -const { default: DriveFile } = require('../built/models/drive-file'); +const { default: User } = require('../../built/models/user'); +const { default: DriveFile } = require('../../built/models/drive-file'); async function main() { const promiseGens = []; diff --git a/cli/migration/5.0.0.js b/cli/migration/5.0.0.js new file mode 100644 index 000000000..8c6f49ed8 --- /dev/null +++ b/cli/migration/5.0.0.js @@ -0,0 +1,10 @@ +const { default: DriveFile } = require('../../built/models/drive-file'); + +DriveFile.update({}, { + $rename: { + 'metadata.url': 'metadata.src', + 'metadata.isMetaOnly': 'metadata.withoutChunks', + } +}, { + multi: true +}); diff --git a/migration/README.md b/migration/README.md deleted file mode 100644 index d52e84b35..000000000 --- a/migration/README.md +++ /dev/null @@ -1,11 +0,0 @@ -Misskeyの破壊的変更に対応するいくつかのスニペットがあります。 -MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。 -ファイル名が `shell.` から始まるものは前者、 `node.` から始まるものは後者です。 - -MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。 - -nodeで実行するいくつかのスニペットは、並列処理させる数を引数で設定できるものがあります。 -処理中にエラーで落ちる場合は、メモリが足りていない可能性があるので、少ない数に設定してみてください。 -※デフォルトは`5`です。 - -ファイルを作成する際は `../init-migration-file.sh -t _type_ -n _name_` を実行すると _type_._unixtime_._name_.js が生成されます diff --git a/migration/init-migration-file.sh b/migration/init-migration-file.sh deleted file mode 100644 index c6a2b862e..000000000 --- a/migration/init-migration-file.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -usage() { - echo "$0 [-t type] [-n name]" - echo " type: [node | shell]" - echo " name: if no present, set untitled" - exit 0 -} - -while getopts :t:n:h OPT -do - case $OPT in - t) type=$OPTARG - ;; - n) name=$OPTARG - ;; - h) usage - ;; - \?) usage - ;; - :) usage - ;; - esac -done - -if [ "$type" = "" ] -then - echo "no type present!!!" - usage -fi - -if [ "$name" = "" ] -then - name="untitled" -fi - -touch "$(realpath $(dirname $BASH_SOURCE))/migration/$type.$(date +%s).$name.js" diff --git a/package.json b/package.json index d3ed47a6f..bdf5c6364 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo <i@syuilo.com>", - "version": "4.27.0", + "version": "5.0.0", "clientVersion": "1.0.7487", "codename": "nighthike", "main": "./built/index.js", @@ -57,6 +57,7 @@ "@types/koa-views": "2.0.3", "@types/koa__cors": "2.2.2", "@types/kue": "0.11.9", + "@types/minio": "6.0.2", "@types/mkdirp": "0.5.2", "@types/mocha": "5.2.3", "@types/mongodb": "3.1.2", @@ -147,6 +148,7 @@ "kue": "0.11.6", "loader-utils": "1.1.0", "mecab-async": "0.1.2", + "minio": "6.0.0", "mkdirp": "0.5.1", "mocha": "5.2.0", "moji": "0.5.1", diff --git a/src/client/app/auth/views/form.vue b/src/client/app/auth/views/form.vue index 152b90042..80086e386 100644 --- a/src/client/app/auth/views/form.vue +++ b/src/client/app/auth/views/form.vue @@ -2,7 +2,7 @@ <div class="form"> <header> <h1><i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?</h1> - <img :src="`${app.iconUrl}?thumbnail&size=64`"/> + <img :src="app.iconUrl"/> </header> <div class="app"> <section> diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts index 47499e549..c93609bc5 100644 --- a/src/client/app/common/scripts/compose-notification.ts +++ b/src/client/app/common/scripts/compose-notification.ts @@ -17,21 +17,21 @@ export default function(type, data): Notification { return { title: 'ファイルがアップロードされました', body: data.name, - icon: data.url + '?thumbnail&size=64' + icon: data.url }; case 'unread_messaging_message': return { title: `${getUserName(data.user)}さんからメッセージ:`, body: data.text, // TODO: getMessagingMessageSummary(data), - icon: data.user.avatarUrl + '?thumbnail&size=64' + icon: data.user.avatarUrl }; case 'reversi_invited': return { title: '対局への招待があります', body: `${getUserName(data.parent)}さんから`, - icon: data.parent.avatarUrl + '?thumbnail&size=64' + icon: data.parent.avatarUrl }; case 'notification': @@ -40,28 +40,28 @@ export default function(type, data): Notification { return { title: `${getUserName(data.user)}さんから:`, body: getNoteSummary(data), - icon: data.user.avatarUrl + '?thumbnail&size=64' + icon: data.user.avatarUrl }; case 'reply': return { title: `${getUserName(data.user)}さんから返信:`, body: getNoteSummary(data), - icon: data.user.avatarUrl + '?thumbnail&size=64' + icon: data.user.avatarUrl }; case 'quote': return { title: `${getUserName(data.user)}さんが引用:`, body: getNoteSummary(data), - icon: data.user.avatarUrl + '?thumbnail&size=64' + icon: data.user.avatarUrl }; case 'reaction': return { title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, body: getNoteSummary(data.note), - icon: data.user.avatarUrl + '?thumbnail&size=64' + icon: data.user.avatarUrl }; default: diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue index 4a130a04e..cd6066877 100644 --- a/src/client/app/common/views/components/autocomplete.vue +++ b/src/client/app/common/views/components/autocomplete.vue @@ -2,7 +2,7 @@ <div class="mk-autocomplete" @contextmenu.prevent="() => {}"> <ol class="users" ref="suggests" v-if="users.length > 0"> <li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> + <img class="avatar" :src="user.avatarUrl" alt=""/> <span class="name">{{ user | userName }}</span> <span class="username">@{{ user | acct }}</span> </li> diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue index a65b62882..a924b62e6 100644 --- a/src/client/app/common/views/components/avatar.vue +++ b/src/client/app/common/views/components/avatar.vue @@ -31,7 +31,7 @@ export default Vue.extend({ : this.user.avatarColor && this.user.avatarColor.length == 3 ? `rgb(${ this.user.avatarColor.join(',') })` : null, - backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl }?thumbnail)`, + backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`, borderRadius: this.$store.state.settings.circleIcons ? '100%' : null }; } diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue index ed9e71849..303070ffd 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.game.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue @@ -26,8 +26,8 @@ :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }" @click="set(i)" :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"> - <img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt=""> - <img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt=""> + <img v-if="stone === true" :src="blackUser.avatarUrl" alt=""> + <img v-if="stone === false" :src="whiteUser.avatarUrl" alt=""> </div> </div> <div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels"> diff --git a/src/client/app/common/views/widgets/photo-stream.vue b/src/client/app/common/views/widgets/photo-stream.vue index ae5924bb1..3e24c58e8 100644 --- a/src/client/app/common/views/widgets/photo-stream.vue +++ b/src/client/app/common/views/widgets/photo-stream.vue @@ -5,7 +5,7 @@ <p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <div :class="$style.stream" v-if="!fetching && images.length > 0"> - <div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div> + <div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url})`"></div> </div> <p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p> </mk-widget-container> diff --git a/src/client/app/common/views/widgets/slideshow.vue b/src/client/app/common/views/widgets/slideshow.vue index e1c28f511..2eede0378 100644 --- a/src/client/app/common/views/widgets/slideshow.vue +++ b/src/client/app/common/views/widgets/slideshow.vue @@ -72,7 +72,7 @@ export default define({ if (this.images.length == 0) return; const index = Math.floor(Math.random() * this.images.length); - const img = `url(${ this.images[index].url }?thumbnail&size=1024)`; + const img = `url(${ this.images[index].url })`; (this.$refs.slideB as any).style.backgroundImage = img; diff --git a/src/client/app/desktop/views/components/drive.file.vue b/src/client/app/desktop/views/components/drive.file.vue index 11700d496..e1db36f5c 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}?thumbnail&size=128`" alt="" @load="onThumbnailLoaded"/> + <img :src="file.url" 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/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue index 7ed31315f..fdab7bc1c 100644 --- a/src/client/app/desktop/views/components/followers-window.vue +++ b/src/client/app/desktop/views/components/followers-window.vue @@ -1,7 +1,7 @@ <template> <mk-window width="400px" height="550px" @closed="$destroy"> <span slot="header" :class="$style.header"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }} + <img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }} </span> <mk-followers :user="user"/> </mk-window> diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue index b97f21e2a..7cca833a8 100644 --- a/src/client/app/desktop/views/components/following-window.vue +++ b/src/client/app/desktop/views/components/following-window.vue @@ -1,7 +1,7 @@ <template> <mk-window width="400px" height="550px" @closed="$destroy"> <span slot="header" :class="$style.header"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }} + <img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }} </span> <mk-following :user="user"/> </mk-window> diff --git a/src/client/app/desktop/views/components/media-image.vue b/src/client/app/desktop/views/components/media-image.vue index 42a31c4c2..74bb03f4e 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}?thumbnail&size=512)` + 'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})` }; } }, diff --git a/src/client/app/desktop/views/components/media-video.vue b/src/client/app/desktop/views/components/media-video.vue index 9f508cf1b..6c60f2da9 100644 --- a/src/client/app/desktop/views/components/media-video.vue +++ b/src/client/app/desktop/views/components/media-video.vue @@ -45,7 +45,7 @@ export default Vue.extend({ computed: { imageStyle(): any { return { - 'background-image': `url(${this.video.url}?thumbnail&size=512)` + 'background-image': `url(${this.video.url})` }; } }, diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 1ce40e60f..bc49fe276 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -23,7 +23,7 @@ <div class="medias" :class="{ with: poll }" v-show="files.length != 0"> <x-draggable :list="files" :options="{ animation: 150 }"> <div v-for="file in files" :key="file.id"> - <div class="img" :style="{ backgroundImage: `url(${file.url}?thumbnail&size=64)` }" :title="file.name"></div> + <div class="img" :style="{ backgroundImage: `url(${file.url})` }" :title="file.name"></div> <img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" title="%i18n:@attach-cancel%" alt=""/> </div> </x-draggable> diff --git a/src/client/app/desktop/views/components/settings.profile.vue b/src/client/app/desktop/views/components/settings.profile.vue index 0b3a25f38..ca29a120c 100644 --- a/src/client/app/desktop/views/components/settings.profile.vue +++ b/src/client/app/desktop/views/components/settings.profile.vue @@ -2,7 +2,7 @@ <div class="profile"> <label class="avatar ui from group"> <p>%i18n:@avatar%</p> - <img class="avatar" :src="`${$store.state.i.avatarUrl}?thumbnail&size=64`" alt="avatar"/> + <img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/> <button class="ui" @click="updateAvatar">%i18n:@choice-avatar%</button> </label> <label class="ui from group"> diff --git a/src/client/app/desktop/views/components/user-preview.vue b/src/client/app/desktop/views/components/user-preview.vue index 933e701e5..2a6815087 100644 --- a/src/client/app/desktop/views/components/user-preview.vue +++ b/src/client/app/desktop/views/components/user-preview.vue @@ -1,7 +1,7 @@ <template> <div class="mk-user-preview"> <template v-if="u != null"> - <div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div> + <div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl})` : ''"></div> <mk-avatar class="avatar" :user="u" :disable-preview="true"/> <div class="title"> <router-link class="name" :to="u | userPage">{{ u | userName }}</router-link> diff --git a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue index 4c1b91e7a..e4a771910 100644 --- a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue +++ b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue @@ -4,7 +4,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p> <div v-if="!fetching && users.length > 0"> <router-link v-for="user in users" :to="user | userPage" :key="user.id"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName" v-user-preview="user.id"/> + <img :src="user.avatarUrl" :alt="user | userName" v-user-preview="user.id"/> </router-link> </div> <p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p> diff --git a/src/client/app/desktop/views/pages/user/user.photos.vue b/src/client/app/desktop/views/pages/user/user.photos.vue index 01c4c7b31..ce7791a96 100644 --- a/src/client/app/desktop/views/pages/user/user.photos.vue +++ b/src/client/app/desktop/views/pages/user/user.photos.vue @@ -4,7 +4,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p> <div class="stream" v-if="!fetching && images.length > 0"> <div v-for="image in images" class="img" - :style="`background-image: url(${image.url}?thumbnail&size=256)`" + :style="`background-image: url(${image.url})`" ></div> </div> <p class="empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p> diff --git a/src/client/app/desktop/views/widgets/profile.vue b/src/client/app/desktop/views/widgets/profile.vue index 7b0fea372..9702aaa90 100644 --- a/src/client/app/desktop/views/widgets/profile.vue +++ b/src/client/app/desktop/views/widgets/profile.vue @@ -4,7 +4,7 @@ :data-melt="props.design == 2" > <div class="banner" - :style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl}?thumbnail&size=256)` : ''" + :style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''" title="%i18n:@update-banner%" @click="os.apis.updateBanner" ></div> diff --git a/src/client/app/mobile/views/components/drive.file.vue b/src/client/app/mobile/views/components/drive.file.vue index 94c8ae353..776e11ecf 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}?thumbnail&size=128)` + 'background-image': `url(${this.file.url})` }; } }, diff --git a/src/client/app/mobile/views/components/media-image.vue b/src/client/app/mobile/views/components/media-image.vue index 1042404c9..d9d68fa7b 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}?thumbnail)`; + let url = `url(${this.image.url})`; if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) { url = null; diff --git a/src/client/app/mobile/views/components/media-video.vue b/src/client/app/mobile/views/components/media-video.vue index 26c9dcc2a..aea7f4146 100644 --- a/src/client/app/mobile/views/components/media-video.vue +++ b/src/client/app/mobile/views/components/media-video.vue @@ -30,7 +30,7 @@ export default Vue.extend({ computed: { imageStyle(): any { return { - 'background-image': `url(${this.video.url}?thumbnail&size=512)` + 'background-image': `url(${this.video.url})` }; } },}) diff --git a/src/client/app/mobile/views/components/note-card.vue b/src/client/app/mobile/views/components/note-card.vue index 6d94c852b..e8427798c 100644 --- a/src/client/app/mobile/views/components/note-card.vue +++ b/src/client/app/mobile/views/components/note-card.vue @@ -2,7 +2,7 @@ <div class="mk-note-card"> <a :href="note | notePage"> <header> - <img :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/><h3>{{ note.user | userName }}</h3> + <img :src="note.user.avatarUrl" alt="avatar"/><h3>{{ note.user | userName }}</h3> </header> <div> {{ text }} diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 285b2e966..4333fefb3 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -21,7 +21,7 @@ <div class="attaches" v-show="files.length != 0"> <x-draggable class="files" :list="files" :options="{ animation: 150 }"> <div class="file" v-for="file in files" :key="file.id"> - <div class="img" :style="`background-image: url(${file.url}?thumbnail&size=128)`" @click="detachMedia(file)"></div> + <div class="img" :style="`background-image: url(${file.url})`" @click="detachMedia(file)"></div> </div> </x-draggable> </div> diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue index bb7a2f558..5257dafd0 100644 --- a/src/client/app/mobile/views/components/ui.nav.vue +++ b/src/client/app/mobile/views/components/ui.nav.vue @@ -10,7 +10,7 @@ <transition name="nav"> <div class="body" v-if="isOpen"> <router-link class="me" v-if="$store.getters.isSignedIn" :to="`/@${$store.state.i.username}`"> - <img class="avatar" :src="`${$store.state.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/> + <img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/> <p class="name">{{ $store.state.i | userName }}</p> </router-link> <div class="links"> diff --git a/src/client/app/mobile/views/components/user-card.vue b/src/client/app/mobile/views/components/user-card.vue index 808ee7240..7b8f2251b 100644 --- a/src/client/app/mobile/views/components/user-card.vue +++ b/src/client/app/mobile/views/components/user-card.vue @@ -1,6 +1,6 @@ <template> <div class="mk-user-card"> - <header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"> + <header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"> <mk-avatar class="avatar" :user="user"/> </header> <a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a> diff --git a/src/client/app/mobile/views/pages/followers.vue b/src/client/app/mobile/views/pages/followers.vue index f53fadb58..7dc72a7c3 100644 --- a/src/client/app/mobile/views/pages/followers.vue +++ b/src/client/app/mobile/views/pages/followers.vue @@ -1,7 +1,7 @@ <template> <mk-ui> <template slot="header" v-if="!fetching"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""> + <img :src="user.avatarUrl" alt=""> {{ '%i18n:@followers-of%'.replace('{}', name) }} </template> <mk-users-list diff --git a/src/client/app/mobile/views/pages/following.vue b/src/client/app/mobile/views/pages/following.vue index b28d7dd7b..6895a76d5 100644 --- a/src/client/app/mobile/views/pages/following.vue +++ b/src/client/app/mobile/views/pages/following.vue @@ -1,7 +1,7 @@ <template> <mk-ui> <template slot="header" v-if="!fetching"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""> + <img :src="user.avatarUrl" alt=""> {{ '%i18n:@following-of%'.replace('{}', name) }} </template> <mk-users-list diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue index 0b51c8b4c..2bc89b81b 100644 --- a/src/client/app/mobile/views/pages/user.vue +++ b/src/client/app/mobile/views/pages/user.vue @@ -1,6 +1,6 @@ <template> <mk-ui> - <template slot="header" v-if="!fetching"><img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">{{ user | userName }}</template> + <template slot="header" v-if="!fetching"><img :src="user.avatarUrl" alt="">{{ user | userName }}</template> <main v-if="!fetching" :data-darkmode="$store.state.device.darkmode"> <div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div> <div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div> diff --git a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue index 6f809d889..d5e3bef96 100644 --- a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue +++ b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue @@ -3,7 +3,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p> <div v-if="!fetching && users.length > 0"> <a v-for="user in users" :key="user.id" :href="user | userPage"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName"/> + <img :src="user.avatarUrl" :alt="user | userName"/> </a> </div> <p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p> diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue index bfd2aa833..73ff1d517 100644 --- a/src/client/app/mobile/views/pages/user/home.photos.vue +++ b/src/client/app/mobile/views/pages/user/home.photos.vue @@ -4,7 +4,7 @@ <div class="stream" v-if="!fetching && images.length > 0"> <a v-for="image in images" class="img" - :style="`background-image: url(${image.media.url}?thumbnail&size=256)`" + :style="`background-image: url(${image.media.url})`" :href="image.note | notePage" ></a> </div> diff --git a/src/client/app/mobile/views/widgets/profile.vue b/src/client/app/mobile/views/widgets/profile.vue index a94f7e94b..6ce3468c4 100644 --- a/src/client/app/mobile/views/widgets/profile.vue +++ b/src/client/app/mobile/views/widgets/profile.vue @@ -2,10 +2,10 @@ <div class="mkw-profile"> <mk-widget-container> <div :class="$style.banner" - :style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl}?thumbnail&size=256)` : ''" + :style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''" ></div> <img :class="$style.avatar" - :src="`${$store.state.i.avatarUrl}?thumbnail&size=96`" + :src="$store.state.i.avatarUrl" alt="avatar" /> <router-link :class="$style.name" :to="$store.state.i | userPage">{{ $store.state.i | userName }}</router-link> diff --git a/src/config/types.ts b/src/config/types.ts index c26ff9db9..b2e28f730 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -49,6 +49,14 @@ export type Source = { remoteDriveCapacityMb: number; preventCacheRemoteFiles: boolean; + drive?: { + storage: string; + bucket: string; + prefix: string; + service?: string; + config?: any; + }; + /** * ゴーストアカウントのID */ diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 3a0390f79..f197f86d4 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -31,8 +31,11 @@ export type IMetadata = { comment: string; uri?: string; url?: string; + src?: string; deletedAt?: Date; - isMetaOnly?: boolean; + withoutChunks?: boolean; + storage?: string; + storageProps?: any; isSensitive?: boolean; }; @@ -155,9 +158,9 @@ 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.src = _file.metadata.url; - _target.url = _file.metadata.isMetaOnly ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; - _target.isRemote = _file.metadata.isMetaOnly; + _target.isRemote = _file.metadata.withoutChunks; if (_target.properties == null) _target.properties = {}; diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index eee4aa1bf..4ff8d23be 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -152,8 +152,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs const avatarId = avatar ? avatar._id : null; const bannerId = banner ? banner._id : null; - const avatarUrl = avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null; - const bannerUrl = banner && banner.metadata.isMetaOnly ? banner.metadata.url : null; + const avatarUrl = avatar && avatar.metadata.url ? avatar.metadata.url : null; + const bannerUrl = banner && banner.metadata.url ? banner.metadata.url : null; await User.update({ _id: user._id }, { $set: { @@ -243,8 +243,8 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver) sharedInbox: person.sharedInbox, avatarId: avatar ? avatar._id : null, bannerId: banner ? banner._id : null, - avatarUrl: avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null, - bannerUrl: banner && banner.metadata.isMetaOnly ? banner.metadata.url : null, + avatarUrl: avatar && avatar.metadata.url ? avatar.metadata.url : null, + bannerUrl: banner && banner.metadata.url ? banner.metadata.url : null, description: htmlToMFM(person.summary), followersCount, followingCount, diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index e04400317..1a76b0e41 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -37,7 +37,7 @@ export default async function(ctx: Koa.Context) { return; } - if (file.metadata.isMetaOnly) { + if (file.metadata.withoutChunks) { ctx.status = 204; return; } diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index f7c8922b5..ab9353c9f 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -8,14 +8,14 @@ import * as _gm from 'gm'; import * as debug from 'debug'; import fileType = require('file-type'); const prominence = require('prominence'); +import * as Minio from 'minio'; +import * as uuid from 'uuid'; import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../models/drive-file'; import DriveFolder from '../../models/drive-folder'; import { pack } from '../../models/drive-file'; import event, { publishDriveStream } from '../../stream'; import { isLocalUser, IUser, IRemoteUser } from '../../models/user'; -import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; -import genThumbnail from '../../drive/gen-thumbnail'; import delFile from './delete-file'; import config from '../../config'; @@ -25,28 +25,47 @@ const gm = _gm.subClass({ const log = debug('misskey:drive:add-file'); -const writeChunks = (name: string, readable: stream.Readable, type: string, metadata: any) => - getDriveFileBucket() - .then(bucket => new Promise((resolve, reject) => { +async function save(readable: stream.Readable, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> { + if (config.drive && config.drive.storage == 'object-storage') { + if (config.drive.service == 'minio') { + + const minio = new Minio.Client(config.drive.config); + const id = uuid.v4(); + const obj = `${config.drive.prefix}/${id}`; + await minio.putObject(config.drive.bucket, obj, readable); + + Object.assign(metadata, { + withoutChunks: true, + storage: 'object-storage', + storageProps: { + id: id + }, + url: `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }/${ obj }` + }); + + const file = await DriveFile.insert({ + length: size, + uploadDate: new Date(), + md5: hash, + filename: name, + metadata: metadata, + contentType: type + }); + + return file; + } + } else { + // Get MongoDB GridFS bucket + const bucket = await getDriveFileBucket(); + + return new Promise<IDriveFile>((resolve, reject) => { const writeStream = bucket.openUploadStream(name, { contentType: type, metadata }); writeStream.once('finish', resolve); writeStream.on('error', reject); readable.pipe(writeStream); - })); - -const writeThumbnailChunks = (name: string, readable: stream.Readable, originalId: mongodb.ObjectID) => - getDriveFileThumbnailBucket() - .then(bucket => new Promise((resolve, reject) => { - const writeStream = bucket.openUploadStream(name, { - contentType: 'image/jpeg', - metadata: { - originalId - } - }); - writeStream.once('finish', resolve); - writeStream.on('error', reject); - readable.pipe(writeStream); - })); + }); + } +} async function deleteOldFile(user: IRemoteUser) { const oldFile = await DriveFile.findOne({ @@ -82,7 +101,7 @@ export default async function( comment: string = null, folderId: mongodb.ObjectID = null, force: boolean = false, - metaOnly: boolean = false, + isLink: boolean = false, url: string = null, uri: string = null, sensitive = false @@ -150,7 +169,7 @@ export default async function( } //#region Check drive usage - if (!metaOnly) { + if (!isLink) { const usage = await DriveFile .aggregate([{ $match: { @@ -262,19 +281,23 @@ export default async function( folderId: folder !== null ? folder._id : null, comment: comment, properties: properties, - isMetaOnly: metaOnly, + withoutChunks: isLink, isSensitive: sensitive } as IMetadata; if (url !== null) { - metadata.url = url; + metadata.src = url; + + if (isLink) { + metadata.url = url; + } } if (uri !== null) { metadata.uri = uri; } - const driveFile = metaOnly + const driveFile = isLink ? await DriveFile.insert({ length: 0, uploadDate: new Date(), @@ -283,7 +306,7 @@ export default async function( metadata: metadata, contentType: mime }) - : await (writeChunks(detectedName, fs.createReadStream(path), mime, metadata) as Promise<IDriveFile>); + : await (save(fs.createReadStream(path), detectedName, mime, hash, size, metadata)); log(`drive file has been created ${driveFile._id}`); @@ -293,16 +316,7 @@ export default async function( publishDriveStream(user._id, 'file_created', packedFile); }); - if (!metaOnly) { - try { - const thumb = await genThumbnail(driveFile); - if (thumb) { - await writeThumbnailChunks(detectedName, thumb, driveFile._id); - } - } catch (e) { - // noop - } - } + // TODO: サムネイル生成 return driveFile; } diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index 4ac60439b..bf9c80f48 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -1,30 +1,40 @@ +import * as Minio from 'minio'; import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file'; import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail'; +import config from '../../config'; export default async function(file: IDriveFile, isExpired = false) { - // チャンクをすべて削除 - await DriveFileChunk.remove({ - files_id: file._id - }); - - await DriveFile.update({ _id: file._id }, { - $set: { - 'metadata.deletedAt': new Date(), - 'metadata.isExpired': isExpired + if (file.metadata.withoutChunks) { + if (file.metadata.storage == 'object-storage') { + const minio = new Minio.Client(config.drive.config); + const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`; + await minio.removeObject(config.drive.bucket, obj); } - }); - - //#region サムネイルもあれば削除 - const thumbnail = await DriveFileThumbnail.findOne({ - 'metadata.originalId': file._id - }); - - if (thumbnail) { - await DriveFileThumbnailChunk.remove({ - files_id: thumbnail._id + } else { + // チャンクをすべて削除 + await DriveFileChunk.remove({ + files_id: file._id }); - await DriveFileThumbnail.remove({ _id: thumbnail._id }); + await DriveFile.update({ _id: file._id }, { + $set: { + 'metadata.deletedAt': new Date(), + 'metadata.isExpired': isExpired + } + }); + + //#region サムネイルもあれば削除 + const thumbnail = await DriveFileThumbnail.findOne({ + 'metadata.originalId': file._id + }); + + if (thumbnail) { + await DriveFileThumbnailChunk.remove({ + files_id: thumbnail._id + }); + + await DriveFileThumbnail.remove({ _id: thumbnail._id }); + } + //#endregion } - //#endregion }