actor-image.ts 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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 { ActorImageType } from '@shared/models'
  7. import { retryTransactionWrapper } from '../helpers/database-utils'
  8. import { processImage } from '../helpers/image-utils'
  9. import { downloadImage } from '../helpers/requests'
  10. import { CONFIG } from '../initializers/config'
  11. import { ACTOR_IMAGES_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
  12. import { sequelizeTypescript } from '../initializers/database'
  13. import { MAccountDefault, MChannelDefault } from '../types/models'
  14. import { deleteActorImageInstance, updateActorImageInstance } from './activitypub/actor'
  15. import { sendUpdateActor } from './activitypub/send'
  16. async function updateLocalActorImageFile (
  17. accountOrChannel: MAccountDefault | MChannelDefault,
  18. imagePhysicalFile: Express.Multer.File,
  19. type: ActorImageType
  20. ) {
  21. const imageSize = type === ActorImageType.AVATAR
  22. ? ACTOR_IMAGES_SIZE.AVATARS
  23. : ACTOR_IMAGES_SIZE.BANNERS
  24. const extension = extname(imagePhysicalFile.filename)
  25. const imageName = uuidv4() + extension
  26. const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName)
  27. await processImage(imagePhysicalFile.path, destination, imageSize)
  28. return retryTransactionWrapper(() => {
  29. return sequelizeTypescript.transaction(async t => {
  30. const actorImageInfo = {
  31. name: imageName,
  32. fileUrl: null,
  33. height: imageSize.height,
  34. width: imageSize.width,
  35. onDisk: true
  36. }
  37. const updatedActor = await updateActorImageInstance(accountOrChannel.Actor, type, actorImageInfo, t)
  38. await updatedActor.save({ transaction: t })
  39. await sendUpdateActor(accountOrChannel, t)
  40. return type === ActorImageType.AVATAR
  41. ? updatedActor.Avatar
  42. : updatedActor.Banner
  43. })
  44. })
  45. }
  46. async function deleteLocalActorImageFile (accountOrChannel: MAccountDefault | MChannelDefault, type: ActorImageType) {
  47. return retryTransactionWrapper(() => {
  48. return sequelizeTypescript.transaction(async t => {
  49. const updatedActor = await deleteActorImageInstance(accountOrChannel.Actor, type, t)
  50. await updatedActor.save({ transaction: t })
  51. await sendUpdateActor(accountOrChannel, t)
  52. return updatedActor.Avatar
  53. })
  54. })
  55. }
  56. type DownloadImageQueueTask = { fileUrl: string, filename: string, type: ActorImageType }
  57. const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => {
  58. const size = task.type === ActorImageType.AVATAR
  59. ? ACTOR_IMAGES_SIZE.AVATARS
  60. : ACTOR_IMAGES_SIZE.BANNERS
  61. downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, size)
  62. .then(() => cb())
  63. .catch(err => cb(err))
  64. }, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE)
  65. function pushActorImageProcessInQueue (task: DownloadImageQueueTask) {
  66. return new Promise<void>((res, rej) => {
  67. downloadImageQueue.push(task, err => {
  68. if (err) return rej(err)
  69. return res()
  70. })
  71. })
  72. }
  73. // Unsafe so could returns paths that does not exist anymore
  74. const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.ACTOR_IMAGE_STATIC.MAX_SIZE })
  75. export {
  76. actorImagePathUnsafeCache,
  77. updateLocalActorImageFile,
  78. deleteLocalActorImageFile,
  79. pushActorImageProcessInQueue
  80. }