diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f3f0b96..ee878e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ TODO: handle custom emojis url for remote video. * #377: new setting to listen C2S connection on non-localhost interfaces. * #130: custom channel emoticons. +### Minor changes and fixes + +* Fix cleanup on channel deletion. + ## 10.0.2 ### Minor changes and fixes diff --git a/server/lib/configuration/channel/init.ts b/server/lib/configuration/channel/init.ts index ed21feb0..205b8ece 100644 --- a/server/lib/configuration/channel/init.ts +++ b/server/lib/configuration/channel/init.ts @@ -8,6 +8,7 @@ import { fillVideoCustomFields } from '../../custom-fields' import { videoHasWebchat } from '../../../../shared/lib/video' import { updateProsodyRoom } from '../../prosody/api/manage-rooms' import { getChannelInfosById } from '../../database/channel' +import { Emojis } from '../../emojis' /** * Register stuffs related to channel configuration @@ -40,11 +41,12 @@ async function initChannelConfiguration (options: RegisterServerOptions): Promis registerHook({ target: 'action:api.video-channel.deleted', - handler: async (params: { channel: VideoChannel }) => { + handler: async (params: { videoChannel: VideoChannel }) => { // When a video is deleted, we can delete the channel2room and room2channel files. // Note: don't need to check if there is a chat for this video, just deleting existing files... - if (!params.channel.isLocal) { return } - const channelId = params.channel.id + + // Note: this hook is only called for local channels. + const channelId = params.videoChannel.id logger.info(`Channel ${channelId} deleted, removing 'channel configuration' related stuff.`) // Here the associated channel can be either channel.X@mucdomain or video_uuid@mucdomain. // In first case, nothing to do... in the second, we must delete. @@ -55,6 +57,13 @@ async function initChannelConfiguration (options: RegisterServerOptions): Promis logger.error(err) } + logger.info(`Channel ${channelId} deleted, removing 'custom emojis' related stuff.`) + try { + Emojis.singletonSafe()?.deleteChannelDefinition(channelId) + } catch (err) { + logger.error(err) + } + // Note: we don't delete the room. So that admins can check logs afterward, if any doubts. } }) diff --git a/server/lib/emojis/emojis.ts b/server/lib/emojis/emojis.ts index dc97c498..c196ce5c 100644 --- a/server/lib/emojis/emojis.ts +++ b/server/lib/emojis/emojis.ts @@ -32,7 +32,7 @@ export class Emojis { constructor (options: RegisterServerOptions) { const logger = options.peertubeHelpers.logger this.options = options - this.channelBasePath = path.resolve( + this.channelBasePath = path.join( options.peertubeHelpers.plugin.getDataDirectoryPath(), 'emojis', 'channel' @@ -69,7 +69,7 @@ export class Emojis { */ public channelCustomEmojisDefinitionPath (channelId: number): string { if (typeof channelId !== 'number') { throw new Error('Invalid channelId') } - return path.resolve(this.channelBasePath, channelId.toString(), 'definition.json') + return path.join(this.channelBasePath, channelId.toString(), 'definition.json') } /** @@ -199,13 +199,24 @@ export class Emojis { * @returns the file path */ public channelCustomEmojisFilePath (channelId: number, fileName: string): string { - if (typeof channelId !== 'number') { throw new Error('Invalid channelId') } if (!this.validImageFileName(fileName)) { throw new Error('Invalid filename') } - return path.resolve( + return path.join( + this.channelCustomEmojisDirPath(channelId), + fileName + ) + } + + /** + * Returns the dir path where to store emojis files relative to a channel. + * @param channelId channel Id + * @returns the dir path + */ + public channelCustomEmojisDirPath (channelId: number): string { + if (typeof channelId !== 'number') { throw new Error('Invalid channelId') } + return path.join( this.channelBasePath, channelId.toString(), - 'files', - fileName + 'files' ) } @@ -293,27 +304,62 @@ export class Emojis { bufferInfos: BufferInfos[] ): Promise { const filepath = this.channelCustomEmojisDefinitionPath(channelId) - await fs.promises.mkdir( - path.resolve( - path.dirname(filepath), - 'files' - ), - { - recursive: true - } - ) + await fs.promises.mkdir(path.dirname(filepath), { + recursive: true + }) await fs.promises.writeFile(filepath, JSON.stringify(def)) + const fileDirPath = this.channelCustomEmojisDirPath(channelId) + await fs.promises.mkdir(fileDirPath, { + recursive: true + }) for (const b of bufferInfos) { - const fp = path.resolve( - path.dirname(filepath), - 'files', + const fp = path.join( + fileDirPath, b.filename ) await fs.promises.writeFile(fp, b.buf) } - // TODO: remove deprecated files. + // Finally, remove deprecated files + const presentFiles = new Map() + for (const e of def.customEmojis) { + const fn = e.url.split('/').pop() + if (fn === undefined) { continue } + presentFiles.set(fn, true) + } + const dirents = await fs.promises.readdir(fileDirPath, { withFileTypes: true }) + for (const dirent of dirents) { + if (!dirent.isFile()) { continue } + if (presentFiles.has(dirent.name)) { continue } + const fp = path.join(fileDirPath, dirent.name) + this.logger.debug('Deleting obsolete emojis file: ' + fp) + await fs.promises.unlink(fp) + } + } + + /** + * Deletes channel custom emojis definitions and files. + * @param channelId channel id + */ + public async deleteChannelDefinition (channelId: number): Promise { + const filepath = this.channelCustomEmojisDefinitionPath(channelId) + const fileDirPath = this.channelCustomEmojisDirPath(channelId) + this.logger.info('Deleting all channel ' + channelId.toString() + ' emojis...') + try { + await fs.promises.rm(fileDirPath, { + force: true, + recursive: true + }) + await fs.promises.rm(path.dirname(filepath), { + force: true, + recursive: true + }) + } catch (err: any) { + if (!(('code' in err) && err.code === 'ENOENT')) { + this.logger.error(err) + } + } } /**