const metadata = require('../libs/metadata.js'); const { extname } = require('path'); const Artist = require('../models/Artist.js'); class Queue { constructor() { this.queue = []; this.filter = this.getFilter(); this.handleQueue(); } add(event, file, stats) { if (file === undefined || !this.filter.includes(extname(file))) { return; } const element = { file: file }; switch (event) { case constants.FS_EVENT_ADD: element.event = constants.FS_EVENT_ADD; break; case constants.FS_EVENT_UNLINK: element.event = constants.FS_EVENT_UNLINK; break; case constants.FS_EVENT_CHANGE: element.event = constants.FS_EVENT_CHANGE; break; default: return; } this.queue.push(element); } async handleQueue() { if (this.queue.length === 0) { if (this.timeout === undefined) { this.timeout = 10; } else { if (this.timeout > 60000) { this.timeout = 60000; } else { this.timeout += 10; } } logger.debug('queue is currently empty - sleeping for ' + this.timeout + 'ms...'); setTimeout(() => { this.handleQueue() }, this.timeout); return; } if (this.timeout !== undefined) { this.timeout = undefined; } const element = this.queue[0]; this.queue.shift(); const timestamp = new Date().getTime(); logger.debug('handling event \'' + element.event + '\' for queued file \'' + element.file + '\'...'); switch (element.event) { case constants.FS_EVENT_ADD: await this.eventAdd(element.file); break; case constants.FS_EVENT_UNLINK: await this.eventUnlink(element.file); break; case constants.FS_EVENT_CHANGE: await this.eventChange(element.file); break; } logger.debug('event \'' + element.event + '\' for file \'' + element.file + '\' handled after ' + (new Date().getTime() - timestamp) + 'ms'); this.handleQueue(); } async eventAdd(file) { if (file === undefined) { return; } const tags = await metadata.parseFile(file); const artist = await this.addArtist(tags); const album = await this.addAlbum(tags); const track = await this.addTrack(tags, file); this.linkTrackToArtist(track, artist); this.linkTrackToAlbum(track, album); this.linkAlbumToArtist(album, artist); } async eventUnlink(file) { if (file === undefined) { return; } try { await database.models.Track.destroy({ where: { file: file } }); } catch (err) { logger.error(err); } } async eventChange(file) { if (file === undefined) { return; } } async addArtist(tags) { if (tags?.common?.artist === undefined && tags?.common?.artists === undefined) { return; } let artist = tags.common.artist; const artists = tags.common.artists || []; if (artist !== undefined && !artists.includes(artist)) { artists.push(artist); } for (let index = 0; index < artists.length; index++) { artist = new Artist(artists[index]); artist = await artist.save(); return artist; } } async addAlbum(tags) { if (tags?.common?.album === undefined) { return; } try { const [element, created] = await database.models.Album.findOrCreate({ where: { name: tags.common.album } }); if (created) { logger.debug('created album: ' + JSON.stringify(element)); } return element; } catch (err) { logger.error('error finding or creating album \'' + JSON.stringify(tags) + '\' > ' + err); } } async addTrack(tags, file) { if (tags?.common?.title === undefined || file === undefined) { return; } const where = { file: file }; if (tags?.common?.title !== undefined) { where.title = tags.common.title; } if (tags?.common?.year !== undefined) { where.year = tags.common.year; } if (tags?.common?.duration !== undefined) { where.duration = tags.common.duration; } if (tags?.common?.comment !== undefined) { let comment = ''; if (Array.isArray(tags.common.comment)) { for (let index = 0; index < tags.common.comment.length; index++) { if (comment.length > 0) { comment += '\n'; } comment += tags.common.comment[index]; } } else { comment = tags.common.comment; } where.comment = comment; } if (tags?.common?.disk?.no !== undefined) { where.diskno = tags.common.disk.no; } if (tags?.common?.disk?.of !== undefined) { where.diskof = tags.common.disk.of; } if (tags?.common?.track?.no !== undefined) { where.trackno = tags.common.track.no; } if (tags?.common?.track?.of !== undefined) { where.trackof = tags.common.track.of; } try { const [element, created] = await database.models.Track.findOrCreate({ where: where }); if (created) { logger.debug('created track: ' + JSON.stringify(element)); } return element; } catch (err) { logger.error('error finding or creating track \'' + JSON.stringify(tags) + '\' > ' + err); } } async linkTrackToArtist(track, artist) { if (track === undefined || artist === undefined) { return; } try { const [element, created] = await database.models.TrackToArtist.findOrCreate({ where: { track: track.id, artist: artist.id } }); if (created) { logger.debug('linked track \'' + track.id + '\' to artist \'' + artist.id + '\': ' + JSON.stringify(element)); } return element; } catch (err) { logger.error('error finding or creating tracktoartist entry for track \'' + track.id + '\' and album \'' + artist.id + '\' > ' + err); } } async linkTrackToAlbum(track, album) { if (track === undefined || album === undefined) { return; } try { const [element, created] = await database.models.TrackToAlbum.findOrCreate({ where: { track: track.id, album: album.id } }); if (created) { logger.debug('linked track \'' + track.id + '\' to album \'' + album.id + '\': ' + JSON.stringify(element)); } return element; } catch (err) { logger.error('error finding or creating tracktoalbum entry for track \'' + track.id + '\' and album \'' + album.id + '\' > ' + err); } } async linkAlbumToArtist(album, artist) { if (album === undefined || artist === undefined) { return; } try { const [element, created] = await database.models.AlbumToArtist.findOrCreate({ where: { album: album.id, artist: artist.id } }); if (created) { logger.debug('linked album \'' + album.id + '\' to artist \'' + artist.id + '\': ' + JSON.stringify(element)); } return element; } catch (err) { logger.error('error finding or creating albumtoartist entry for album \'' + album.id + '\' and artist \'' + artist.id + '\' > ' + err); } } getFilter() { let filter = config?.library?.formats || ['.mp3']; for (let index = 0; index < filter.length; index++) { if (filter[index].startsWith(".")) { continue; } filter[index] = '.' + filter[index]; } return filter; } } module.exports = Queue;