Audit delle Prestazioni — tunecamp / sidecamp / graphofone
Data: 22-07-2026
Ambito: tunecamp/ (backend + webapp), tunecamp-sidecamp/apps/sidecamp, tunecamp-sidecamp/apps/graphofone.
Legenda stato: [ ] aperto · [x] risolto · [~] in corso
Modello comune nei tre moduli
I/O sequenziale dove la concorrenza è banale (await per-file nei cicli for), mancanza di virtualizzazione degli elenchi, mancanza di paginazione/caching sugli endpoint per elenchi di grandi dimensioni.
tunecamp (backend Node + SQLite, webapp React/Vite)
Priorità Alta
- [ ]
src/server/modules/subsonic/subsonic.service.ts:186getAlbumList()— JOIN+GROUP BY non limitato su tutti gli album, ordinamento/paginazione eseguiti in JS a ogni richiesta. Fix:LIMIT/OFFSETin SQL o cache TTL. - [x]
src/server/modules/subsonic/subsonic.service.ts:75-76formatAlbum()— N+1:isStarred()/getItemRating()per ogni riga album, nessun prefetch massivo (i brani lo eseguono già viaformatTracksBulk). Risolto:formatAlbumsBulkeffettua il prefetch delSetdei preferiti +Mapdelle valutazioni. - [x]
src/server/modules/subsonic/subsonic.service.ts:80-90formatArtist()— stesso problema N+1, raddoppiato (preferiti + valutazione), zero prefetch massivo. Risolto: aggiuntoformatArtistsBulkcon mappe pre-recuperate;getIndexes/search/getStarredlo richiamano ora invece di iterare suformatArtist. - [ ]
src/server/modules/subsonic/subsonic.service.ts:198-204search()— chiamatadb.getTracks()per-album solo per calcolare songCount/duration. Fix: query di aggregazione (JOIN+GROUP BY). - [x]
src/server/routes/library/albums.ts:296— le copertine degli album inviate conmaxAge: 0(cache disabilitata) mentre brani/artisti usanomaxAge: 86400000(tracks.ts:397,artists.ts:295,368). Risolto: allineato a86400000.
Priorità Media
- [ ]
src/server/modules/media/media-engine.ts:53-74sendStreamResult()— assenza diCache-Control/ETagsulle risposte degli stream audio. - [ ]
src/server/modules/catalog/catalog.service.ts:217-230batchDeleteTracks()— ognideleteTrack()riattivasyncRelease(albumId); N brani dello stesso album = N ri-sincronizzazioni. Fix: deduplicare glialbum_id, sincronizzare una sola volta dopo il ciclo. - [ ]
src/server/modules/storage/storage-usage.service.ts:33-55,159dirSize()—await fs.statsequenziale per file, non memorizzato in cache, eseguito a ogni richiesta di panoramica admin. Fix:Promise.all+ breve cache TTL. - [ ]
webapp/src/components/layout/MainLayout.tsx:6-11—CheckoutModal(includeethers),AuthModal,PlaylistModal,UnlockModal, modali admin importati staticamente nella shell dell'app per ogni visitatore. Fix:React.lazy(). - [x]
webapp/vite.config.ts:18-19—vite-plugin-node-polyfillsincludedgram/child_process/os/zlib/stream, residuo dello stack ZEN/ZEN rimosso. Risolto: rimossi dall'include, restano solobuffer/crypto/fs/path/process/util/url/events(usati daethers/altre lib). Rimosso anche l'alias morto"zen" → src/zen.js(file inesistente, mai importato).
Priorità Bassa
- [ ]
src/server/repositories/album.repository.ts:99-115getWithStats()— nessuna paginazione/LIMIT. - [ ]
src/server/repositories/album.repository.ts:79-93getLibraryAlbums()— fallback hardcoded silenziosoLIMIT 1000, nessun segnalehasMore. - [ ]
src/server/routes/api/misc.ts:19-25,93getFilteredChangelog()—fs.readFileSync+ parsing a ogni richiesta, nessuna cache. - [ ]
src/server/repositories/artist.repository.ts:121,301— nessun LIMIT negli elenchi/ricerca artisti. - [ ]
src/server/core/database.ts:1322-1360— scansione completa della tabellatracksin memoria a ogni avvio, incondizionata. - [ ]
webapp/src— nessuna virtualizzazione sulle griglie Libreria/Pubblicazioni/Ricerca (non urgente, il backend limita a ~1000 righe).
sidecamp (App Electron)
Priorità Alta
- [x]
organizer.ts:50-83scanDir()—await fs.stat+await parseFile()sequenziale per file, nessuna concorrenza. Risolto: elaborato a blocchi conCONCURRENCY=8(diviso inscanOne()+Promise.alla blocchi), rispecchiatrack-meta.ts. - [x]
electron/peer/daemon.ts:63-96scanFolders()— stesso ciclo sequenziale per file, eseguito a ogni avvio del demone + ognirescanAndSendManifest(). Risolto: stesso patternCONCURRENCY=8conPromise.alla blocchi; avanzamento emesso una volta per blocco. - [x]
electron/main.ts:449-463+peer/daemon.ts— ognitorrent:seed/torrent:removeattiva unrescanAndSendManifest()completo (ri-scansione + ri-parsing completo) anziché un aggiornamento incrementale del singolo file. Risolto: aggiuntorefreshAndSendManifest()— aggiornamagnetUrisulle voci in cache difileIndexe reinvia, senza ri-scansione/ri-parsing su disco. - [ ]
electron/providers/network.ts:12-129getPeers/getPeerTracks/getCatalogTracks— non limitati/senza paginazione, renderizzati in una tabella non virtualizzata.
Priorità Media
- [ ]
electron/peer/daemon.ts:217-232handleRequest()— audio inviato in streaming come base64-in-JSON su WebSocket (~33% di overhead). Fix: frame WS binari. - [ ]
electron/organizer-cache.ts:33-61,electron/track-meta.ts:53-69— l'intero file JSON di cache viene letto/riscritto su disco a ognicacheGet/cachePut, mai potato. - [ ]
src/App.tsx:1905-1966— tabella Libreria non virtualizzata;currentTime(1/sec) forza il re-render completo dell'intera tabella. - [ ]
src/App.tsx:95-2751— singolo componenteAppdi ~2750 righe, ~50 hookuseState, ogni elemento si ri-renderizza a ogni tick di stato. - [ ]
electron/providers/search.ts:132-172searchArchiveOrg()— fino a 10 chiamate di metadati extra per ricerca, zero caching. - [ ] Nessun HTTP keep-alive presente in
electron/(network.ts,uploader/index.tsusano axios base, nessunAgentcondiviso). - [ ]
electron/providers/ytdlp.ts:23-68—fs.existsSync/mkdirSync/copyFileSync/chmodSyncsincroni bloccano il processo principale Electron a ogni download/ricerca. - [ ]
src/services/platform/capacitorAdapter.ts:5-10,154-159— gli array di listener eseguono solo.push(), nessun annullamento dell'iscrizione; il rimontaggio duplica le callback.
graphofone (App Electron)
Priorità Alta
- [x]
electron/library.ts:174-180+src/App.tsx:90—saveLibrary()riscrive l'intero JSON della libreria su disco per ogni brano analizzato nel ciclo = I/O O(n²). Risolto: aggiuntoupdateTrackMetaBatch()(library.ts), collegato IPC (main.ts/preload.ts/env.d.ts),handleAnalyzeaccumula ora gli aggiornamenti ed esegue il flush una sola volta. - [x]
src/App.tsx:89-102handleAnalyze()—setLibrary(prev => prev.map(...))per-brano all'interno del ciclo = O(n²) re-render. Risolto insieme a quanto sopra — singolosetLibrary/setMetadopo il ciclo.