SQLITE 3
This commit is contained in:
163
server/services/playlist-sync.service.ts
Normal file
163
server/services/playlist-sync.service.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { Database } from 'sqlite'
|
||||
import { fileURLToPath } from 'url'
|
||||
import chokidar from 'chokidar'
|
||||
import { db } from '../database'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const PLAYLISTS_DIR = path.join(process.cwd(), 'public/ESPLAYLISTS')
|
||||
const AUDIO_EXTENSIONS = ['.mp3', '.wav', '.ogg', '.m4a', '.flac']
|
||||
|
||||
export class PlaylistSyncService {
|
||||
private watcher: chokidar.FSWatcher | null = null
|
||||
|
||||
constructor(private db: Database) {}
|
||||
|
||||
async initialize() {
|
||||
await this.scanPlaylists()
|
||||
this.setupWatcher()
|
||||
}
|
||||
|
||||
private async scanPlaylists() {
|
||||
try {
|
||||
if (!fs.existsSync(PLAYLISTS_DIR)) {
|
||||
console.warn(`Playlists directory not found: ${PLAYLISTS_DIR}`)
|
||||
return
|
||||
}
|
||||
|
||||
const playlists = fs
|
||||
.readdirSync(PLAYLISTS_DIR, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
for (const playlistName of playlists) {
|
||||
await this.syncPlaylist(playlistName)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error scanning playlists:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private async syncPlaylist(playlistName: string) {
|
||||
const playlistPath = path.join(PLAYLISTS_DIR, playlistName)
|
||||
|
||||
try {
|
||||
const stats = fs.statSync(playlistPath)
|
||||
|
||||
// Vérifier ou créer la playlist dans la base de données
|
||||
let playlist = await this.db.get('SELECT * FROM playlists WHERE name = ?', [playlistName])
|
||||
|
||||
if (!playlist) {
|
||||
const result = await this.db.run(
|
||||
'INSERT INTO playlists (name, path, last_modified) VALUES (?, ?, ?)',
|
||||
[playlistName, playlistPath, new Date(stats.mtime).toISOString()]
|
||||
)
|
||||
playlist = await this.db.get('SELECT * FROM playlists WHERE id = ?', [result.lastID])
|
||||
}
|
||||
|
||||
// Récupérer les pistes actuelles
|
||||
const currentTracks = fs
|
||||
.readdirSync(playlistPath)
|
||||
.filter((file) => {
|
||||
const ext = path.extname(file).toLowerCase()
|
||||
return AUDIO_EXTENSIONS.includes(ext)
|
||||
})
|
||||
.map((file, index) => ({
|
||||
path: path.join(playlistName, file),
|
||||
order: index
|
||||
}))
|
||||
|
||||
// Mettre à jour les pistes dans la base de données
|
||||
await this.db.run('BEGIN TRANSACTION')
|
||||
try {
|
||||
// Supprimer les anciennes entrées
|
||||
await this.db.run('DELETE FROM playlist_tracks WHERE playlist_id = ?', [playlist.id])
|
||||
|
||||
// Ajouter les nouvelles pistes
|
||||
for (const track of currentTracks) {
|
||||
await this.db.run(
|
||||
'INSERT INTO playlist_tracks (playlist_id, track_path, track_order) VALUES (?, ?, ?)',
|
||||
[playlist.id, track.path, track.order]
|
||||
)
|
||||
}
|
||||
|
||||
// Mettre à jour la date de modification
|
||||
await this.db.run('UPDATE playlists SET last_modified = ? WHERE id = ?', [
|
||||
new Date().toISOString(),
|
||||
playlist.id
|
||||
])
|
||||
|
||||
await this.db.run('COMMIT')
|
||||
console.log(`Playlist "${playlistName}" synchronized with ${currentTracks.length} tracks`)
|
||||
} catch (error) {
|
||||
await this.db.run('ROLLBACK')
|
||||
console.error(`Error syncing playlist ${playlistName}:`, error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error accessing playlist directory ${playlistPath}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
private setupWatcher() {
|
||||
if (!fs.existsSync(PLAYLISTS_DIR)) {
|
||||
console.warn(`Playlists directory not found, watcher not started: ${PLAYLISTS_DIR}`)
|
||||
return
|
||||
}
|
||||
|
||||
this.watcher = chokidar.watch(PLAYLISTS_DIR, {
|
||||
ignored: /(^|[\/\\])\../, // ignore les fichiers cachés
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
depth: 2 // surveille un seul niveau de sous-dossiers
|
||||
})
|
||||
|
||||
this.watcher
|
||||
.on('add', (filePath) => this.handleFileChange('add', filePath))
|
||||
.on('change', (filePath) => this.handleFileChange('change', filePath))
|
||||
.on('unlink', (filePath) => this.handleFileChange('unlink', filePath))
|
||||
.on('addDir', (dirPath) => this.handleDirChange('addDir', dirPath))
|
||||
.on('unlinkDir', (dirPath) => this.handleDirChange('unlinkDir', dirPath))
|
||||
.on('error', (error) => console.error('Watcher error:', error))
|
||||
}
|
||||
|
||||
private async handleFileChange(event: string, filePath: string) {
|
||||
const relativePath = path.relative(PLAYLISTS_DIR, filePath)
|
||||
const playlistName = path.dirname(relativePath)
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
|
||||
// Ignorer les fichiers qui ne sont pas des fichiers audio
|
||||
if (playlistName === '.' || !AUDIO_EXTENSIONS.includes(ext)) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`File ${event}: ${relativePath}`)
|
||||
await this.syncPlaylist(playlistName)
|
||||
}
|
||||
|
||||
private async handleDirChange(event: string, dirPath: string) {
|
||||
const relativePath = path.relative(PLAYLISTS_DIR, dirPath)
|
||||
|
||||
if (relativePath === '') return // Ignorer le dossier racine
|
||||
|
||||
console.log(`Directory ${event}: ${relativePath}`)
|
||||
if (event === 'addDir') {
|
||||
await this.syncPlaylist(relativePath)
|
||||
} else if (event === 'unlinkDir') {
|
||||
// Supprimer la playlist de la base de données
|
||||
await this.db.run('DELETE FROM playlists WHERE name = ?', [relativePath])
|
||||
console.log(`Removed playlist from database: ${relativePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.watcher) {
|
||||
return this.watcher.close()
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const playlistSyncService = new PlaylistSyncService(db)
|
||||
Reference in New Issue
Block a user