Custom channel emoticons WIP (#130) + Fix cleanup on channel deletion.

This commit is contained in:
John Livingston 2024-06-06 14:55:40 +02:00
parent 92e9d6d1af
commit 893708d93a
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
3 changed files with 81 additions and 22 deletions

View File

@ -11,6 +11,10 @@ TODO: handle custom emojis url for remote video.
* #377: new setting to listen C2S connection on non-localhost interfaces. * #377: new setting to listen C2S connection on non-localhost interfaces.
* #130: custom channel emoticons. * #130: custom channel emoticons.
### Minor changes and fixes
* Fix cleanup on channel deletion.
## 10.0.2 ## 10.0.2
### Minor changes and fixes ### Minor changes and fixes

View File

@ -8,6 +8,7 @@ import { fillVideoCustomFields } from '../../custom-fields'
import { videoHasWebchat } from '../../../../shared/lib/video' import { videoHasWebchat } from '../../../../shared/lib/video'
import { updateProsodyRoom } from '../../prosody/api/manage-rooms' import { updateProsodyRoom } from '../../prosody/api/manage-rooms'
import { getChannelInfosById } from '../../database/channel' import { getChannelInfosById } from '../../database/channel'
import { Emojis } from '../../emojis'
/** /**
* Register stuffs related to channel configuration * Register stuffs related to channel configuration
@ -40,11 +41,12 @@ async function initChannelConfiguration (options: RegisterServerOptions): Promis
registerHook({ registerHook({
target: 'action:api.video-channel.deleted', 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. // 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... // 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.`) logger.info(`Channel ${channelId} deleted, removing 'channel configuration' related stuff.`)
// Here the associated channel can be either channel.X@mucdomain or video_uuid@mucdomain. // 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. // 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.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. // Note: we don't delete the room. So that admins can check logs afterward, if any doubts.
} }
}) })

View File

@ -32,7 +32,7 @@ export class Emojis {
constructor (options: RegisterServerOptions) { constructor (options: RegisterServerOptions) {
const logger = options.peertubeHelpers.logger const logger = options.peertubeHelpers.logger
this.options = options this.options = options
this.channelBasePath = path.resolve( this.channelBasePath = path.join(
options.peertubeHelpers.plugin.getDataDirectoryPath(), options.peertubeHelpers.plugin.getDataDirectoryPath(),
'emojis', 'emojis',
'channel' 'channel'
@ -69,7 +69,7 @@ export class Emojis {
*/ */
public channelCustomEmojisDefinitionPath (channelId: number): string { public channelCustomEmojisDefinitionPath (channelId: number): string {
if (typeof channelId !== 'number') { throw new Error('Invalid channelId') } 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 * @returns the file path
*/ */
public channelCustomEmojisFilePath (channelId: number, fileName: string): string { 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') } 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, this.channelBasePath,
channelId.toString(), channelId.toString(),
'files', 'files'
fileName
) )
} }
@ -293,27 +304,62 @@ export class Emojis {
bufferInfos: BufferInfos[] bufferInfos: BufferInfos[]
): Promise<void> { ): Promise<void> {
const filepath = this.channelCustomEmojisDefinitionPath(channelId) const filepath = this.channelCustomEmojisDefinitionPath(channelId)
await fs.promises.mkdir( await fs.promises.mkdir(path.dirname(filepath), {
path.resolve(
path.dirname(filepath),
'files'
),
{
recursive: true recursive: true
} })
)
await fs.promises.writeFile(filepath, JSON.stringify(def)) 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) { for (const b of bufferInfos) {
const fp = path.resolve( const fp = path.join(
path.dirname(filepath), fileDirPath,
'files',
b.filename b.filename
) )
await fs.promises.writeFile(fp, b.buf) await fs.promises.writeFile(fp, b.buf)
} }
// TODO: remove deprecated files. // Finally, remove deprecated files
const presentFiles = new Map<string, true>()
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<void> {
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)
}
}
} }
/** /**