const metadata = require('../libs/metadata.js'); const { extname } = require('path'); const Artist = require('../models/Artist.js'); const Album = require('../models/Album.js'); const Track = require('../models/Track.js'); const AlbumToArtist = require('../models/AlbumToArtist.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 artists = 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.linkAlbumToArtists(album, artists); } 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; } 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]); await artist.save(); artists[index] = artist; } return artists; } 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]); await artist.save(); artists[index] = artist; } return artists; } async addAlbum(tags) { if (tags?.common?.album === undefined) { return; } let album = new Album(tags.common.album); await album.save(); return album; } async addTrack(tags, file) { if (tags?.common?.title === undefined || file === undefined) { return; } let track = new Track(tags, file); await track.save(); return track; } 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 linkAlbumToArtists(album, artists) { if (album === undefined || artists === undefined || artists.length === 0) { return; } for (let index = 0; index < artists.length; index++) { await new AlbumToArtist(album, artists[index]).save(); } } 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;