22
33#include < QFutureWatcher>
44#include < QPixmapCache>
5+ #include < QPointer>
56#include < QSqlDatabase>
67#include < QtConcurrentRun>
78
89#include " library/dao/analysisdao.h"
10+ #include " library/dao/trackdao.h"
911#include " moc_overviewcache.cpp"
1012#include " util/db/dbconnectionpooled.h"
1113#include " util/db/dbconnectionpooler.h"
@@ -40,8 +42,12 @@ OverviewCache::OverviewCache(UserSettingsPointer pConfig,
4042 m_pDbConnectionPool(std::move(pDbConnectionPool)) {
4143}
4244
43- void OverviewCache::onTrackAnalysisProgress (TrackId trackId, AnalyzerProgress analyzerProgress) {
44- if (analyzerProgress < 1.0 ) {
45+ void OverviewCache::onTrackAnalysisProgress (TrackId trackId, AnalyzerProgress progress) {
46+ // Always forward the raw progress so listeners that need to draw
47+ // the partial waveform during analysis (e.g. QmlWaveformOverview)
48+ // can repaint on every tick.
49+ emit analyzerProgress (trackId, progress);
50+ if (progress < 1.0 ) {
4551 return ;
4652 }
4753 m_tracksWithoutOverview.remove (trackId);
@@ -238,6 +244,21 @@ void OverviewCache::requestWaveformSummary(TrackId trackId, const QObject* pRequ
238244
239245 m_currentlyLoadingWaveform.insert (trackId);
240246
247+ // The async load carries `pRequester` verbatim and will eventually
248+ // dispatch the result back to it. If `pRequester` is destroyed while
249+ // the load is still in flight, its `slotWaveformSummaryReady` is
250+ // auto-disconnected by Qt, no one adopts the loaded waveform, and
251+ // `m_currentlyLoadingWaveform` would only be cleared when the
252+ // background job finishes — meanwhile other requesters interested in
253+ // the same track are silently blocked. Drop the pending entry as
254+ // soon as the requester goes away so a new load can be started by
255+ // any other listener still alive.
256+ if (pRequester) {
257+ QObject::connect (pRequester, &QObject::destroyed, this , [this , trackId] {
258+ m_currentlyLoadingWaveform.remove (trackId);
259+ });
260+ }
261+
241262 QFutureWatcher<FutureWaveformResult>* watcher =
242263 new QFutureWatcher<FutureWaveformResult>(this );
243264 QFuture<FutureWaveformResult> future = QtConcurrent::run (
@@ -253,6 +274,35 @@ void OverviewCache::requestWaveformSummary(TrackId trackId, const QObject* pRequ
253274 watcher->setFuture (future);
254275}
255276
277+ void OverviewCache::requestWaveformSummary (
278+ const QString& trackLocation, const QObject* pRequester) {
279+ if (trackLocation.isEmpty ()) {
280+ return ;
281+ }
282+ if (!m_pTrackDAO) {
283+ // Without a TrackDAO we cannot translate a file location into
284+ // a TrackId. The TrackId-based overload is the only available
285+ // path in that case.
286+ return ;
287+ }
288+
289+ // Resolve the location into a TrackId on the GUI thread. The
290+ // query is a single indexed lookup on `track_locations.location`,
291+ // which is essentially instantaneous, so doing it on the GUI
292+ // thread is preferable to introducing an extra round-trip through
293+ // the worker pool and lets us reuse the existing TrackId-based
294+ // dedup logic and async machinery. `TrackDAO` is owned by
295+ // `TrackCollection` and lives on the GUI thread, so its
296+ // pre-initialised `QSqlDatabase` connection is valid here.
297+ const TrackId trackId = m_pTrackDAO->getTrackIdByLocation (trackLocation);
298+ if (!trackId.isValid ()) {
299+ return ;
300+ }
301+
302+ // Delegate to the existing TrackId-based load path.
303+ requestWaveformSummary (trackId, pRequester);
304+ }
305+
256306// static
257307OverviewCache::FutureWaveformResult OverviewCache::prepareWaveformSummary (
258308 const UserSettingsPointer pConfig,
@@ -276,9 +326,18 @@ OverviewCache::FutureWaveformResult OverviewCache::prepareWaveformSummary(
276326 analysisDao.getAnalysesForTrackByType (
277327 trackId, AnalysisDao::AnalysisType::TYPE_WAVESUMMARY );
278328
279- if (!analyses.isEmpty ()) {
280- result.pWaveform = ConstWaveformPointer (
281- WaveformFactory::loadWaveformFromAnalysis (analyses.first ()));
329+ // Only load analyses whose version is compatible with the current
330+ // format, mirroring the logic in `AnalyzerWaveform::shouldAnalyze`.
331+ // Loading an incompatible (e.g. `VC_REMOVE`) waveform would yield a
332+ // `Waveform` with invalid data, producing a garbled overview.
333+ for (const AnalysisDao::AnalysisInfo& analysis : std::as_const (analyses)) {
334+ const WaveformFactory::VersionClass vc =
335+ WaveformFactory::waveformSummaryVersionToVersionClass (analysis.version );
336+ if (vc == WaveformFactory::VC_USE ) {
337+ result.pWaveform = ConstWaveformPointer (
338+ WaveformFactory::loadWaveformFromAnalysis (analysis));
339+ break ;
340+ }
282341 }
283342
284343 return result;
@@ -292,5 +351,15 @@ void OverviewCache::waveformSummaryPrepared() {
292351
293352 m_currentlyLoadingWaveform.remove (res.trackId );
294353
354+ // The async job captured `pRequester` as a raw pointer; it may have
355+ // been deleted on the GUI thread while the load was in flight.
356+ // Re-check on the GUI thread before handing the (possibly dangling)
357+ // pointer to listeners, so they don't compare against `this` with
358+ // a dangling address.
359+ QPointer<QObject> requesterGuard (const_cast <QObject*>(res.requester ));
360+ if (requesterGuard.isNull ()) {
361+ return ;
362+ }
363+
295364 emit waveformSummaryReady (res.requester , res.trackId , res.pWaveform );
296365}
0 commit comments