diff options
Diffstat (limited to 'server/lib/local-actor.ts')
-rw-r--r-- | server/lib/local-actor.ts | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts new file mode 100644 index 000000000..55e77dd04 --- /dev/null +++ b/server/lib/local-actor.ts | |||
@@ -0,0 +1,116 @@ | |||
1 | import 'multer' | ||
2 | import { queue } from 'async' | ||
3 | import * as LRUCache from 'lru-cache' | ||
4 | import { extname, join } from 'path' | ||
5 | import { v4 as uuidv4 } from 'uuid' | ||
6 | import { ActorModel } from '@server/models/actor/actor' | ||
7 | import { ActivityPubActorType, ActorImageType } from '@shared/models' | ||
8 | import { retryTransactionWrapper } from '../helpers/database-utils' | ||
9 | import { processImage } from '../helpers/image-utils' | ||
10 | import { downloadImage } from '../helpers/requests' | ||
11 | import { CONFIG } from '../initializers/config' | ||
12 | import { ACTOR_IMAGES_SIZE, LRU_CACHE, QUEUE_CONCURRENCY, WEBSERVER } from '../initializers/constants' | ||
13 | import { sequelizeTypescript } from '../initializers/database' | ||
14 | import { MAccountDefault, MActor, MChannelDefault } from '../types/models' | ||
15 | import { deleteActorImageInstance, updateActorImageInstance } from './activitypub/actors' | ||
16 | import { sendUpdateActor } from './activitypub/send' | ||
17 | |||
18 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) { | ||
19 | return new ActorModel({ | ||
20 | type, | ||
21 | url, | ||
22 | preferredUsername, | ||
23 | publicKey: null, | ||
24 | privateKey: null, | ||
25 | followersCount: 0, | ||
26 | followingCount: 0, | ||
27 | inboxUrl: url + '/inbox', | ||
28 | outboxUrl: url + '/outbox', | ||
29 | sharedInboxUrl: WEBSERVER.URL + '/inbox', | ||
30 | followersUrl: url + '/followers', | ||
31 | followingUrl: url + '/following' | ||
32 | }) as MActor | ||
33 | } | ||
34 | |||
35 | async function updateLocalActorImageFile ( | ||
36 | accountOrChannel: MAccountDefault | MChannelDefault, | ||
37 | imagePhysicalFile: Express.Multer.File, | ||
38 | type: ActorImageType | ||
39 | ) { | ||
40 | const imageSize = type === ActorImageType.AVATAR | ||
41 | ? ACTOR_IMAGES_SIZE.AVATARS | ||
42 | : ACTOR_IMAGES_SIZE.BANNERS | ||
43 | |||
44 | const extension = extname(imagePhysicalFile.filename) | ||
45 | |||
46 | const imageName = uuidv4() + extension | ||
47 | const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName) | ||
48 | await processImage(imagePhysicalFile.path, destination, imageSize) | ||
49 | |||
50 | return retryTransactionWrapper(() => { | ||
51 | return sequelizeTypescript.transaction(async t => { | ||
52 | const actorImageInfo = { | ||
53 | name: imageName, | ||
54 | fileUrl: null, | ||
55 | height: imageSize.height, | ||
56 | width: imageSize.width, | ||
57 | onDisk: true | ||
58 | } | ||
59 | |||
60 | const updatedActor = await updateActorImageInstance(accountOrChannel.Actor, type, actorImageInfo, t) | ||
61 | await updatedActor.save({ transaction: t }) | ||
62 | |||
63 | await sendUpdateActor(accountOrChannel, t) | ||
64 | |||
65 | return type === ActorImageType.AVATAR | ||
66 | ? updatedActor.Avatar | ||
67 | : updatedActor.Banner | ||
68 | }) | ||
69 | }) | ||
70 | } | ||
71 | |||
72 | async function deleteLocalActorImageFile (accountOrChannel: MAccountDefault | MChannelDefault, type: ActorImageType) { | ||
73 | return retryTransactionWrapper(() => { | ||
74 | return sequelizeTypescript.transaction(async t => { | ||
75 | const updatedActor = await deleteActorImageInstance(accountOrChannel.Actor, type, t) | ||
76 | await updatedActor.save({ transaction: t }) | ||
77 | |||
78 | await sendUpdateActor(accountOrChannel, t) | ||
79 | |||
80 | return updatedActor.Avatar | ||
81 | }) | ||
82 | }) | ||
83 | } | ||
84 | |||
85 | type DownloadImageQueueTask = { fileUrl: string, filename: string, type: ActorImageType } | ||
86 | |||
87 | const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => { | ||
88 | const size = task.type === ActorImageType.AVATAR | ||
89 | ? ACTOR_IMAGES_SIZE.AVATARS | ||
90 | : ACTOR_IMAGES_SIZE.BANNERS | ||
91 | |||
92 | downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, size) | ||
93 | .then(() => cb()) | ||
94 | .catch(err => cb(err)) | ||
95 | }, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE) | ||
96 | |||
97 | function pushActorImageProcessInQueue (task: DownloadImageQueueTask) { | ||
98 | return new Promise<void>((res, rej) => { | ||
99 | downloadImageQueue.push(task, err => { | ||
100 | if (err) return rej(err) | ||
101 | |||
102 | return res() | ||
103 | }) | ||
104 | }) | ||
105 | } | ||
106 | |||
107 | // Unsafe so could returns paths that does not exist anymore | ||
108 | const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.ACTOR_IMAGE_STATIC.MAX_SIZE }) | ||
109 | |||
110 | export { | ||
111 | actorImagePathUnsafeCache, | ||
112 | updateLocalActorImageFile, | ||
113 | deleteLocalActorImageFile, | ||
114 | pushActorImageProcessInQueue, | ||
115 | buildActorInstance | ||
116 | } | ||