diff options
author | kontrollanten <6680299+kontrollanten@users.noreply.github.com> | 2022-02-28 08:34:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-28 08:34:43 +0100 |
commit | d0800f7661f13fabe7bb6f4aa0ea50764f106405 (patch) | |
tree | d43e6b0b6f4a5a32e03487e6464edbcaf288be2a /server/lib | |
parent | 5cad2ca9db9b9d138f8a33058d10b94a9fd50c69 (diff) | |
download | PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.gz PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.zst PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.zip |
Implement avatar miniatures (#4639)
* client: remove unused file
* refactor(client/my-actor-avatar): size from input
Read size from component input instead of scss, to make it possible to
use smaller avatar images when implemented.
* implement avatar miniatures
close #4560
* fix(test): max file size
* fix(search-index): normalize res acc to avatarMini
* refactor avatars to an array
* client/search: resize channel avatar to 120
* refactor(client/videos): remove unused function
* client(actor-avatar): set default size
* fix tests and avatars full result
When findOne is used only an array containting one avatar is returned.
* update migration version and version notations
* server/search: harmonize normalizing
* Cleanup avatar miniature PR
Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/actors/image.ts | 89 | ||||
-rw-r--r-- | server/lib/activitypub/actors/shared/creator.ts | 16 | ||||
-rw-r--r-- | server/lib/activitypub/actors/shared/object-to-model-attributes.ts | 56 | ||||
-rw-r--r-- | server/lib/activitypub/actors/updater.ts | 12 | ||||
-rw-r--r-- | server/lib/actor-image.ts | 14 | ||||
-rw-r--r-- | server/lib/client-html.ts | 10 | ||||
-rw-r--r-- | server/lib/local-actor.ts | 89 | ||||
-rw-r--r-- | server/lib/notifier/shared/comment/comment-mention.ts | 2 | ||||
-rw-r--r-- | server/lib/notifier/shared/comment/new-comment-for-video-owner.ts | 2 |
9 files changed, 165 insertions, 125 deletions
diff --git a/server/lib/activitypub/actors/image.ts b/server/lib/activitypub/actors/image.ts index 443ad0a63..d17c2ef1a 100644 --- a/server/lib/activitypub/actors/image.ts +++ b/server/lib/activitypub/actors/image.ts | |||
@@ -12,53 +12,52 @@ type ImageInfo = { | |||
12 | onDisk?: boolean | 12 | onDisk?: boolean |
13 | } | 13 | } |
14 | 14 | ||
15 | async function updateActorImageInstance (actor: MActorImages, type: ActorImageType, imageInfo: ImageInfo | null, t: Transaction) { | 15 | async function updateActorImages (actor: MActorImages, type: ActorImageType, imagesInfo: ImageInfo[], t: Transaction) { |
16 | const oldImageModel = type === ActorImageType.AVATAR | 16 | const avatarsOrBanners = type === ActorImageType.AVATAR |
17 | ? actor.Avatar | 17 | ? actor.Avatars |
18 | : actor.Banner | 18 | : actor.Banners |
19 | 19 | ||
20 | if (oldImageModel) { | 20 | if (imagesInfo.length === 0) { |
21 | // Don't update the avatar if the file URL did not change | 21 | await deleteActorImages(actor, type, t) |
22 | if (imageInfo?.fileUrl && oldImageModel.fileUrl === imageInfo.fileUrl) return actor | 22 | } |
23 | |||
24 | for (const imageInfo of imagesInfo) { | ||
25 | const oldImageModel = (avatarsOrBanners || []).find(i => i.width === imageInfo.width) | ||
23 | 26 | ||
24 | try { | 27 | if (oldImageModel) { |
25 | await oldImageModel.destroy({ transaction: t }) | 28 | // Don't update the avatar if the file URL did not change |
29 | if (imageInfo?.fileUrl && oldImageModel.fileUrl === imageInfo.fileUrl) { | ||
30 | continue | ||
31 | } | ||
26 | 32 | ||
27 | setActorImage(actor, type, null) | 33 | await safeDeleteActorImage(actor, oldImageModel, type, t) |
28 | } catch (err) { | ||
29 | logger.error('Cannot remove old actor image of actor %s.', actor.url, { err }) | ||
30 | } | 34 | } |
31 | } | ||
32 | 35 | ||
33 | if (imageInfo) { | ||
34 | const imageModel = await ActorImageModel.create({ | 36 | const imageModel = await ActorImageModel.create({ |
35 | filename: imageInfo.name, | 37 | filename: imageInfo.name, |
36 | onDisk: imageInfo.onDisk ?? false, | 38 | onDisk: imageInfo.onDisk ?? false, |
37 | fileUrl: imageInfo.fileUrl, | 39 | fileUrl: imageInfo.fileUrl, |
38 | height: imageInfo.height, | 40 | height: imageInfo.height, |
39 | width: imageInfo.width, | 41 | width: imageInfo.width, |
40 | type | 42 | type, |
43 | actorId: actor.id | ||
41 | }, { transaction: t }) | 44 | }, { transaction: t }) |
42 | 45 | ||
43 | setActorImage(actor, type, imageModel) | 46 | addActorImage(actor, type, imageModel) |
44 | } | 47 | } |
45 | 48 | ||
46 | return actor | 49 | return actor |
47 | } | 50 | } |
48 | 51 | ||
49 | async function deleteActorImageInstance (actor: MActorImages, type: ActorImageType, t: Transaction) { | 52 | async function deleteActorImages (actor: MActorImages, type: ActorImageType, t: Transaction) { |
50 | try { | 53 | try { |
51 | if (type === ActorImageType.AVATAR) { | 54 | const association = buildAssociationName(type) |
52 | await actor.Avatar.destroy({ transaction: t }) | ||
53 | |||
54 | actor.avatarId = null | ||
55 | actor.Avatar = null | ||
56 | } else { | ||
57 | await actor.Banner.destroy({ transaction: t }) | ||
58 | 55 | ||
59 | actor.bannerId = null | 56 | for (const image of actor[association]) { |
60 | actor.Banner = null | 57 | await image.destroy({ transaction: t }) |
61 | } | 58 | } |
59 | |||
60 | actor[association] = [] | ||
62 | } catch (err) { | 61 | } catch (err) { |
63 | logger.error('Cannot remove old image of actor %s.', actor.url, { err }) | 62 | logger.error('Cannot remove old image of actor %s.', actor.url, { err }) |
64 | } | 63 | } |
@@ -66,29 +65,37 @@ async function deleteActorImageInstance (actor: MActorImages, type: ActorImageTy | |||
66 | return actor | 65 | return actor |
67 | } | 66 | } |
68 | 67 | ||
68 | async function safeDeleteActorImage (actor: MActorImages, toDelete: MActorImage, type: ActorImageType, t: Transaction) { | ||
69 | try { | ||
70 | await toDelete.destroy({ transaction: t }) | ||
71 | |||
72 | const association = buildAssociationName(type) | ||
73 | actor[association] = actor[association].filter(image => image.id !== toDelete.id) | ||
74 | } catch (err) { | ||
75 | logger.error('Cannot remove old actor image of actor %s.', actor.url, { err }) | ||
76 | } | ||
77 | } | ||
78 | |||
69 | // --------------------------------------------------------------------------- | 79 | // --------------------------------------------------------------------------- |
70 | 80 | ||
71 | export { | 81 | export { |
72 | ImageInfo, | 82 | ImageInfo, |
73 | 83 | ||
74 | updateActorImageInstance, | 84 | updateActorImages, |
75 | deleteActorImageInstance | 85 | deleteActorImages |
76 | } | 86 | } |
77 | 87 | ||
78 | // --------------------------------------------------------------------------- | 88 | // --------------------------------------------------------------------------- |
79 | 89 | ||
80 | function setActorImage (actorModel: MActorImages, type: ActorImageType, imageModel: MActorImage) { | 90 | function addActorImage (actor: MActorImages, type: ActorImageType, imageModel: MActorImage) { |
81 | const id = imageModel | 91 | const association = buildAssociationName(type) |
82 | ? imageModel.id | 92 | if (!actor[association]) actor[association] = [] |
83 | : null | 93 | |
84 | 94 | actor[association].push(imageModel) | |
85 | if (type === ActorImageType.AVATAR) { | 95 | } |
86 | actorModel.avatarId = id | ||
87 | actorModel.Avatar = imageModel | ||
88 | } else { | ||
89 | actorModel.bannerId = id | ||
90 | actorModel.Banner = imageModel | ||
91 | } | ||
92 | 96 | ||
93 | return actorModel | 97 | function buildAssociationName (type: ActorImageType) { |
98 | return type === ActorImageType.AVATAR | ||
99 | ? 'Avatars' | ||
100 | : 'Banners' | ||
94 | } | 101 | } |
diff --git a/server/lib/activitypub/actors/shared/creator.ts b/server/lib/activitypub/actors/shared/creator.ts index 999aed97d..500bc9912 100644 --- a/server/lib/activitypub/actors/shared/creator.ts +++ b/server/lib/activitypub/actors/shared/creator.ts | |||
@@ -6,8 +6,8 @@ import { ServerModel } from '@server/models/server/server' | |||
6 | import { VideoChannelModel } from '@server/models/video/video-channel' | 6 | import { VideoChannelModel } from '@server/models/video/video-channel' |
7 | import { MAccount, MAccountDefault, MActor, MActorFullActor, MActorId, MActorImages, MChannel, MServer } from '@server/types/models' | 7 | import { MAccount, MAccountDefault, MActor, MActorFullActor, MActorId, MActorImages, MChannel, MServer } from '@server/types/models' |
8 | import { ActivityPubActor, ActorImageType } from '@shared/models' | 8 | import { ActivityPubActor, ActorImageType } from '@shared/models' |
9 | import { updateActorImageInstance } from '../image' | 9 | import { updateActorImages } from '../image' |
10 | import { getActorAttributesFromObject, getActorDisplayNameFromObject, getImageInfoFromObject } from './object-to-model-attributes' | 10 | import { getActorAttributesFromObject, getActorDisplayNameFromObject, getImagesInfoFromObject } from './object-to-model-attributes' |
11 | import { fetchActorFollowsCount } from './url-to-object' | 11 | import { fetchActorFollowsCount } from './url-to-object' |
12 | 12 | ||
13 | export class APActorCreator { | 13 | export class APActorCreator { |
@@ -27,11 +27,11 @@ export class APActorCreator { | |||
27 | return sequelizeTypescript.transaction(async t => { | 27 | return sequelizeTypescript.transaction(async t => { |
28 | const server = await this.setServer(actorInstance, t) | 28 | const server = await this.setServer(actorInstance, t) |
29 | 29 | ||
30 | await this.setImageIfNeeded(actorInstance, ActorImageType.AVATAR, t) | ||
31 | await this.setImageIfNeeded(actorInstance, ActorImageType.BANNER, t) | ||
32 | |||
33 | const { actorCreated, created } = await this.saveActor(actorInstance, t) | 30 | const { actorCreated, created } = await this.saveActor(actorInstance, t) |
34 | 31 | ||
32 | await this.setImageIfNeeded(actorCreated, ActorImageType.AVATAR, t) | ||
33 | await this.setImageIfNeeded(actorCreated, ActorImageType.BANNER, t) | ||
34 | |||
35 | await this.tryToFixActorUrlIfNeeded(actorCreated, actorInstance, created, t) | 35 | await this.tryToFixActorUrlIfNeeded(actorCreated, actorInstance, created, t) |
36 | 36 | ||
37 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { // Account or PeerTube instance | 37 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { // Account or PeerTube instance |
@@ -71,10 +71,10 @@ export class APActorCreator { | |||
71 | } | 71 | } |
72 | 72 | ||
73 | private async setImageIfNeeded (actor: MActor, type: ActorImageType, t: Transaction) { | 73 | private async setImageIfNeeded (actor: MActor, type: ActorImageType, t: Transaction) { |
74 | const imageInfo = getImageInfoFromObject(this.actorObject, type) | 74 | const imagesInfo = getImagesInfoFromObject(this.actorObject, type) |
75 | if (!imageInfo) return | 75 | if (imagesInfo.length === 0) return |
76 | 76 | ||
77 | return updateActorImageInstance(actor as MActorImages, type, imageInfo, t) | 77 | return updateActorImages(actor as MActorImages, type, imagesInfo, t) |
78 | } | 78 | } |
79 | 79 | ||
80 | private async saveActor (actor: MActor, t: Transaction) { | 80 | private async saveActor (actor: MActor, t: Transaction) { |
diff --git a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts index 23bc972e5..f6a78c457 100644 --- a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts +++ b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts | |||
@@ -4,7 +4,7 @@ import { ActorModel } from '@server/models/actor/actor' | |||
4 | import { FilteredModelAttributes } from '@server/types' | 4 | import { FilteredModelAttributes } from '@server/types' |
5 | import { getLowercaseExtension } from '@shared/core-utils' | 5 | import { getLowercaseExtension } from '@shared/core-utils' |
6 | import { buildUUID } from '@shared/extra-utils' | 6 | import { buildUUID } from '@shared/extra-utils' |
7 | import { ActivityPubActor, ActorImageType } from '@shared/models' | 7 | import { ActivityIconObject, ActivityPubActor, ActorImageType } from '@shared/models' |
8 | 8 | ||
9 | function getActorAttributesFromObject ( | 9 | function getActorAttributesFromObject ( |
10 | actorObject: ActivityPubActor, | 10 | actorObject: ActivityPubActor, |
@@ -30,33 +30,36 @@ function getActorAttributesFromObject ( | |||
30 | } | 30 | } |
31 | } | 31 | } |
32 | 32 | ||
33 | function getImageInfoFromObject (actorObject: ActivityPubActor, type: ActorImageType) { | 33 | function getImagesInfoFromObject (actorObject: ActivityPubActor, type: ActorImageType) { |
34 | const mimetypes = MIMETYPES.IMAGE | 34 | const iconsOrImages = type === ActorImageType.AVATAR |
35 | const icon = type === ActorImageType.AVATAR | 35 | ? actorObject.icons || actorObject.icon |
36 | ? actorObject.icon | ||
37 | : actorObject.image | 36 | : actorObject.image |
38 | 37 | ||
39 | if (!icon || icon.type !== 'Image' || !isActivityPubUrlValid(icon.url)) return undefined | 38 | return normalizeIconOrImage(iconsOrImages).map(iconOrImage => { |
39 | const mimetypes = MIMETYPES.IMAGE | ||
40 | 40 | ||
41 | let extension: string | 41 | if (iconOrImage.type !== 'Image' || !isActivityPubUrlValid(iconOrImage.url)) return undefined |
42 | 42 | ||
43 | if (icon.mediaType) { | 43 | let extension: string |
44 | extension = mimetypes.MIMETYPE_EXT[icon.mediaType] | ||
45 | } else { | ||
46 | const tmp = getLowercaseExtension(icon.url) | ||
47 | 44 | ||
48 | if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp | 45 | if (iconOrImage.mediaType) { |
49 | } | 46 | extension = mimetypes.MIMETYPE_EXT[iconOrImage.mediaType] |
47 | } else { | ||
48 | const tmp = getLowercaseExtension(iconOrImage.url) | ||
50 | 49 | ||
51 | if (!extension) return undefined | 50 | if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp |
51 | } | ||
52 | 52 | ||
53 | return { | 53 | if (!extension) return undefined |
54 | name: buildUUID() + extension, | 54 | |
55 | fileUrl: icon.url, | 55 | return { |
56 | height: icon.height, | 56 | name: buildUUID() + extension, |
57 | width: icon.width, | 57 | fileUrl: iconOrImage.url, |
58 | type | 58 | height: iconOrImage.height, |
59 | } | 59 | width: iconOrImage.width, |
60 | type | ||
61 | } | ||
62 | }) | ||
60 | } | 63 | } |
61 | 64 | ||
62 | function getActorDisplayNameFromObject (actorObject: ActivityPubActor) { | 65 | function getActorDisplayNameFromObject (actorObject: ActivityPubActor) { |
@@ -65,6 +68,15 @@ function getActorDisplayNameFromObject (actorObject: ActivityPubActor) { | |||
65 | 68 | ||
66 | export { | 69 | export { |
67 | getActorAttributesFromObject, | 70 | getActorAttributesFromObject, |
68 | getImageInfoFromObject, | 71 | getImagesInfoFromObject, |
69 | getActorDisplayNameFromObject | 72 | getActorDisplayNameFromObject |
70 | } | 73 | } |
74 | |||
75 | // --------------------------------------------------------------------------- | ||
76 | |||
77 | function normalizeIconOrImage (icon: ActivityIconObject | ActivityIconObject[]): ActivityIconObject[] { | ||
78 | if (Array.isArray(icon)) return icon | ||
79 | if (icon) return [ icon ] | ||
80 | |||
81 | return [] | ||
82 | } | ||
diff --git a/server/lib/activitypub/actors/updater.ts b/server/lib/activitypub/actors/updater.ts index 042438d9c..fe94af9f1 100644 --- a/server/lib/activitypub/actors/updater.ts +++ b/server/lib/activitypub/actors/updater.ts | |||
@@ -5,9 +5,9 @@ import { VideoChannelModel } from '@server/models/video/video-channel' | |||
5 | import { MAccount, MActor, MActorFull, MChannel } from '@server/types/models' | 5 | import { MAccount, MActor, MActorFull, MChannel } from '@server/types/models' |
6 | import { ActivityPubActor, ActorImageType } from '@shared/models' | 6 | import { ActivityPubActor, ActorImageType } from '@shared/models' |
7 | import { getOrCreateAPOwner } from './get' | 7 | import { getOrCreateAPOwner } from './get' |
8 | import { updateActorImageInstance } from './image' | 8 | import { updateActorImages } from './image' |
9 | import { fetchActorFollowsCount } from './shared' | 9 | import { fetchActorFollowsCount } from './shared' |
10 | import { getImageInfoFromObject } from './shared/object-to-model-attributes' | 10 | import { getImagesInfoFromObject } from './shared/object-to-model-attributes' |
11 | 11 | ||
12 | export class APActorUpdater { | 12 | export class APActorUpdater { |
13 | 13 | ||
@@ -29,8 +29,8 @@ export class APActorUpdater { | |||
29 | } | 29 | } |
30 | 30 | ||
31 | async update () { | 31 | async update () { |
32 | const avatarInfo = getImageInfoFromObject(this.actorObject, ActorImageType.AVATAR) | 32 | const avatarsInfo = getImagesInfoFromObject(this.actorObject, ActorImageType.AVATAR) |
33 | const bannerInfo = getImageInfoFromObject(this.actorObject, ActorImageType.BANNER) | 33 | const bannersInfo = getImagesInfoFromObject(this.actorObject, ActorImageType.BANNER) |
34 | 34 | ||
35 | try { | 35 | try { |
36 | await this.updateActorInstance(this.actor, this.actorObject) | 36 | await this.updateActorInstance(this.actor, this.actorObject) |
@@ -47,8 +47,8 @@ export class APActorUpdater { | |||
47 | } | 47 | } |
48 | 48 | ||
49 | await runInReadCommittedTransaction(async t => { | 49 | await runInReadCommittedTransaction(async t => { |
50 | await updateActorImageInstance(this.actor, ActorImageType.AVATAR, avatarInfo, t) | 50 | await updateActorImages(this.actor, ActorImageType.BANNER, bannersInfo, t) |
51 | await updateActorImageInstance(this.actor, ActorImageType.BANNER, bannerInfo, t) | 51 | await updateActorImages(this.actor, ActorImageType.AVATAR, avatarsInfo, t) |
52 | }) | 52 | }) |
53 | 53 | ||
54 | await runInReadCommittedTransaction(async t => { | 54 | await runInReadCommittedTransaction(async t => { |
diff --git a/server/lib/actor-image.ts b/server/lib/actor-image.ts new file mode 100644 index 000000000..e9bd148f6 --- /dev/null +++ b/server/lib/actor-image.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import maxBy from 'lodash/maxBy' | ||
2 | |||
3 | function getBiggestActorImage <T extends { width: number }> (images: T[]) { | ||
4 | const image = maxBy(images, 'width') | ||
5 | |||
6 | // If width is null, maxBy won't return a value | ||
7 | if (!image) return images[0] | ||
8 | |||
9 | return image | ||
10 | } | ||
11 | |||
12 | export { | ||
13 | getBiggestActorImage | ||
14 | } | ||
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 19354ab70..c010f3c44 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -3,6 +3,7 @@ import { readFile } from 'fs-extra' | |||
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import validator from 'validator' | 4 | import validator from 'validator' |
5 | import { toCompleteUUID } from '@server/helpers/custom-validators/misc' | 5 | import { toCompleteUUID } from '@server/helpers/custom-validators/misc' |
6 | import { ActorImageModel } from '@server/models/actor/actor-image' | ||
6 | import { root } from '@shared/core-utils' | 7 | import { root } from '@shared/core-utils' |
7 | import { escapeHTML } from '@shared/core-utils/renderer' | 8 | import { escapeHTML } from '@shared/core-utils/renderer' |
8 | import { sha256 } from '@shared/extra-utils' | 9 | import { sha256 } from '@shared/extra-utils' |
@@ -16,7 +17,6 @@ import { mdToOneLinePlainText } from '../helpers/markdown' | |||
16 | import { CONFIG } from '../initializers/config' | 17 | import { CONFIG } from '../initializers/config' |
17 | import { | 18 | import { |
18 | ACCEPT_HEADERS, | 19 | ACCEPT_HEADERS, |
19 | ACTOR_IMAGES_SIZE, | ||
20 | CUSTOM_HTML_TAG_COMMENTS, | 20 | CUSTOM_HTML_TAG_COMMENTS, |
21 | EMBED_SIZE, | 21 | EMBED_SIZE, |
22 | FILES_CONTENT_HASH, | 22 | FILES_CONTENT_HASH, |
@@ -29,6 +29,7 @@ import { VideoModel } from '../models/video/video' | |||
29 | import { VideoChannelModel } from '../models/video/video-channel' | 29 | import { VideoChannelModel } from '../models/video/video-channel' |
30 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 30 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
31 | import { MAccountActor, MChannelActor } from '../types/models' | 31 | import { MAccountActor, MChannelActor } from '../types/models' |
32 | import { getBiggestActorImage } from './actor-image' | ||
32 | import { ServerConfigManager } from './server-config-manager' | 33 | import { ServerConfigManager } from './server-config-manager' |
33 | 34 | ||
34 | type Tags = { | 35 | type Tags = { |
@@ -273,10 +274,11 @@ class ClientHtml { | |||
273 | const siteName = CONFIG.INSTANCE.NAME | 274 | const siteName = CONFIG.INSTANCE.NAME |
274 | const title = entity.getDisplayName() | 275 | const title = entity.getDisplayName() |
275 | 276 | ||
277 | const avatar = getBiggestActorImage(entity.Actor.Avatars) | ||
276 | const image = { | 278 | const image = { |
277 | url: entity.Actor.getAvatarUrl(), | 279 | url: ActorImageModel.getImageUrl(avatar), |
278 | width: ACTOR_IMAGES_SIZE.AVATARS.width, | 280 | width: avatar?.width, |
279 | height: ACTOR_IMAGES_SIZE.AVATARS.height | 281 | height: avatar?.height |
280 | } | 282 | } |
281 | 283 | ||
282 | const ogType = 'website' | 284 | const ogType = 'website' |
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts index c6826759b..01046d017 100644 --- a/server/lib/local-actor.ts +++ b/server/lib/local-actor.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import 'multer' | ||
2 | import { queue } from 'async' | 1 | import { queue } from 'async' |
2 | import { remove } from 'fs-extra' | ||
3 | import LRUCache from 'lru-cache' | 3 | import LRUCache from 'lru-cache' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import { ActorModel } from '@server/models/actor/actor' | 5 | import { ActorModel } from '@server/models/actor/actor' |
@@ -13,7 +13,7 @@ import { CONFIG } from '../initializers/config' | |||
13 | import { ACTOR_IMAGES_SIZE, LRU_CACHE, QUEUE_CONCURRENCY, WEBSERVER } from '../initializers/constants' | 13 | import { ACTOR_IMAGES_SIZE, LRU_CACHE, QUEUE_CONCURRENCY, WEBSERVER } from '../initializers/constants' |
14 | import { sequelizeTypescript } from '../initializers/database' | 14 | import { sequelizeTypescript } from '../initializers/database' |
15 | import { MAccountDefault, MActor, MChannelDefault } from '../types/models' | 15 | import { MAccountDefault, MActor, MChannelDefault } from '../types/models' |
16 | import { deleteActorImageInstance, updateActorImageInstance } from './activitypub/actors' | 16 | import { deleteActorImages, updateActorImages } from './activitypub/actors' |
17 | import { sendUpdateActor } from './activitypub/send' | 17 | import { sendUpdateActor } from './activitypub/send' |
18 | 18 | ||
19 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) { | 19 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) { |
@@ -33,64 +33,69 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU | |||
33 | }) as MActor | 33 | }) as MActor |
34 | } | 34 | } |
35 | 35 | ||
36 | async function updateLocalActorImageFile ( | 36 | async function updateLocalActorImageFiles ( |
37 | accountOrChannel: MAccountDefault | MChannelDefault, | 37 | accountOrChannel: MAccountDefault | MChannelDefault, |
38 | imagePhysicalFile: Express.Multer.File, | 38 | imagePhysicalFile: Express.Multer.File, |
39 | type: ActorImageType | 39 | type: ActorImageType |
40 | ) { | 40 | ) { |
41 | const imageSize = type === ActorImageType.AVATAR | 41 | const processImageSize = async (imageSize: { width: number, height: number }) => { |
42 | ? ACTOR_IMAGES_SIZE.AVATARS | 42 | const extension = getLowercaseExtension(imagePhysicalFile.filename) |
43 | : ACTOR_IMAGES_SIZE.BANNERS | 43 | |
44 | 44 | const imageName = buildUUID() + extension | |
45 | const extension = getLowercaseExtension(imagePhysicalFile.filename) | 45 | const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName) |
46 | 46 | await processImage(imagePhysicalFile.path, destination, imageSize, true) | |
47 | const imageName = buildUUID() + extension | 47 | |
48 | const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName) | 48 | return { |
49 | await processImage(imagePhysicalFile.path, destination, imageSize) | 49 | imageName, |
50 | 50 | imageSize | |
51 | return retryTransactionWrapper(() => { | 51 | } |
52 | return sequelizeTypescript.transaction(async t => { | 52 | } |
53 | const actorImageInfo = { | 53 | |
54 | name: imageName, | 54 | const processedImages = await Promise.all(ACTOR_IMAGES_SIZE[type].map(processImageSize)) |
55 | fileUrl: null, | 55 | await remove(imagePhysicalFile.path) |
56 | height: imageSize.height, | 56 | |
57 | width: imageSize.width, | 57 | return retryTransactionWrapper(() => sequelizeTypescript.transaction(async t => { |
58 | onDisk: true | 58 | const actorImagesInfo = processedImages.map(({ imageName, imageSize }) => ({ |
59 | } | 59 | name: imageName, |
60 | 60 | fileUrl: null, | |
61 | const updatedActor = await updateActorImageInstance(accountOrChannel.Actor, type, actorImageInfo, t) | 61 | height: imageSize.height, |
62 | await updatedActor.save({ transaction: t }) | 62 | width: imageSize.width, |
63 | 63 | onDisk: true | |
64 | await sendUpdateActor(accountOrChannel, t) | 64 | })) |
65 | 65 | ||
66 | return type === ActorImageType.AVATAR | 66 | const updatedActor = await updateActorImages(accountOrChannel.Actor, type, actorImagesInfo, t) |
67 | ? updatedActor.Avatar | 67 | await updatedActor.save({ transaction: t }) |
68 | : updatedActor.Banner | 68 | |
69 | }) | 69 | await sendUpdateActor(accountOrChannel, t) |
70 | }) | 70 | |
71 | return type === ActorImageType.AVATAR | ||
72 | ? updatedActor.Avatars | ||
73 | : updatedActor.Banners | ||
74 | })) | ||
71 | } | 75 | } |
72 | 76 | ||
73 | async function deleteLocalActorImageFile (accountOrChannel: MAccountDefault | MChannelDefault, type: ActorImageType) { | 77 | async function deleteLocalActorImageFile (accountOrChannel: MAccountDefault | MChannelDefault, type: ActorImageType) { |
74 | return retryTransactionWrapper(() => { | 78 | return retryTransactionWrapper(() => { |
75 | return sequelizeTypescript.transaction(async t => { | 79 | return sequelizeTypescript.transaction(async t => { |
76 | const updatedActor = await deleteActorImageInstance(accountOrChannel.Actor, type, t) | 80 | const updatedActor = await deleteActorImages(accountOrChannel.Actor, type, t) |
77 | await updatedActor.save({ transaction: t }) | 81 | await updatedActor.save({ transaction: t }) |
78 | 82 | ||
79 | await sendUpdateActor(accountOrChannel, t) | 83 | await sendUpdateActor(accountOrChannel, t) |
80 | 84 | ||
81 | return updatedActor.Avatar | 85 | return updatedActor.Avatars |
82 | }) | 86 | }) |
83 | }) | 87 | }) |
84 | } | 88 | } |
85 | 89 | ||
86 | type DownloadImageQueueTask = { fileUrl: string, filename: string, type: ActorImageType } | 90 | type DownloadImageQueueTask = { |
91 | fileUrl: string | ||
92 | filename: string | ||
93 | type: ActorImageType | ||
94 | size: typeof ACTOR_IMAGES_SIZE[ActorImageType][0] | ||
95 | } | ||
87 | 96 | ||
88 | const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => { | 97 | const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => { |
89 | const size = task.type === ActorImageType.AVATAR | 98 | downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, task.size) |
90 | ? ACTOR_IMAGES_SIZE.AVATARS | ||
91 | : ACTOR_IMAGES_SIZE.BANNERS | ||
92 | |||
93 | downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, size) | ||
94 | .then(() => cb()) | 99 | .then(() => cb()) |
95 | .catch(err => cb(err)) | 100 | .catch(err => cb(err)) |
96 | }, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE) | 101 | }, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE) |
@@ -110,7 +115,7 @@ const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE. | |||
110 | 115 | ||
111 | export { | 116 | export { |
112 | actorImagePathUnsafeCache, | 117 | actorImagePathUnsafeCache, |
113 | updateLocalActorImageFile, | 118 | updateLocalActorImageFiles, |
114 | deleteLocalActorImageFile, | 119 | deleteLocalActorImageFile, |
115 | pushActorImageProcessInQueue, | 120 | pushActorImageProcessInQueue, |
116 | buildActorInstance | 121 | buildActorInstance |
diff --git a/server/lib/notifier/shared/comment/comment-mention.ts b/server/lib/notifier/shared/comment/comment-mention.ts index 765cbaad9..ecd1687b4 100644 --- a/server/lib/notifier/shared/comment/comment-mention.ts +++ b/server/lib/notifier/shared/comment/comment-mention.ts | |||
@@ -77,7 +77,7 @@ export class CommentMention extends AbstractNotification <MCommentOwnerVideo, MU | |||
77 | userId: user.id, | 77 | userId: user.id, |
78 | commentId: this.payload.id | 78 | commentId: this.payload.id |
79 | }) | 79 | }) |
80 | notification.Comment = this.payload | 80 | notification.VideoComment = this.payload |
81 | 81 | ||
82 | return notification | 82 | return notification |
83 | } | 83 | } |
diff --git a/server/lib/notifier/shared/comment/new-comment-for-video-owner.ts b/server/lib/notifier/shared/comment/new-comment-for-video-owner.ts index b76fc15bf..757502703 100644 --- a/server/lib/notifier/shared/comment/new-comment-for-video-owner.ts +++ b/server/lib/notifier/shared/comment/new-comment-for-video-owner.ts | |||
@@ -44,7 +44,7 @@ export class NewCommentForVideoOwner extends AbstractNotification <MCommentOwner | |||
44 | userId: user.id, | 44 | userId: user.id, |
45 | commentId: this.payload.id | 45 | commentId: this.payload.id |
46 | }) | 46 | }) |
47 | notification.Comment = this.payload | 47 | notification.VideoComment = this.payload |
48 | 48 | ||
49 | return notification | 49 | return notification |
50 | } | 50 | } |