feat: Android download page, analysis retry and a collapsible search
ci / api / api (push) Successful in 37s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 46s
ci / db / db (push) Failing after 14s
ci / recommender / recommender (push) Successful in 51s
ci / ts / ts (packages + web) (push) Successful in 57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m2s
ci / api / api (push) Successful in 37s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 46s
ci / db / db (push) Failing after 14s
ci / recommender / recommender (push) Successful in 51s
ci / ts / ts (packages + web) (push) Successful in 57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m2s
- /android is a public page with the latest APK, the server address to copy and three install steps; linked from the sign-in screen and the profile menu - failed audio analysis can be retried from the library page, which also shows the most common error; the analyzer no longer burns attempts on files that are missing because the disk is not mounted - the top bar search folds into a round button and slides open on click, "/" or Ctrl+K Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d49cb3f19
commit
fda6ed48ab
27 files changed
+757
-277
No files matched your search
@@ -292,6 +292,15 @@ func (h *Handler) ScanSource(ctx context.Context, req gen.ScanSourceRequestObjec
|
||||
return gen.ScanSource202JSONResponse(progressDTO(p)), nil
|
||||
}
|
||||
|
||||
// RetryAnalysis реализует gen.StrictServerInterface.
|
||||
func (h *Handler) RetryAnalysis(ctx context.Context, _ gen.RetryAnalysisRequestObject) (gen.RetryAnalysisResponseObject, error) {
|
||||
n, err := h.svc.RetryAnalysis(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gen.RetryAnalysis200JSONResponse{Requeued: n}, nil
|
||||
}
|
||||
|
||||
// GetLibraryStatus реализует gen.StrictServerInterface.
|
||||
func (h *Handler) GetLibraryStatus(ctx context.Context, _ gen.GetLibraryStatusRequestObject) (gen.GetLibraryStatusResponseObject, error) {
|
||||
st, err := h.svc.Status(ctx)
|
||||
@@ -304,7 +313,7 @@ func (h *Handler) GetLibraryStatus(ctx context.Context, _ gen.GetLibraryStatusRe
|
||||
MissingFiles: int(c.Missing), FileErrors: int(c.Errors), Sources: []gen.SourceStatusItem{},
|
||||
Analysis: gen.AnalysisStatus{
|
||||
Queued: st.Analysis.Queued, Running: st.Analysis.Running, Done: st.Analysis.Done,
|
||||
Failed: st.Analysis.Failed, AnalyzerVer: st.Analysis.AnalyzerVer,
|
||||
Failed: st.Analysis.Failed, AnalyzerVer: st.Analysis.AnalyzerVer, TopError: st.Analysis.TopError,
|
||||
},
|
||||
Wave: gen.WaveMetrics{
|
||||
Served: st.Wave.Served, Sessions: st.Wave.Sessions, ListenThroughRate: st.Wave.ListenThrough,
|
||||
|
||||
@@ -587,6 +587,15 @@ func (s *Service) previewOne(ctx context.Context, root, rel string, patterns []m
|
||||
}
|
||||
|
||||
// StartScan запускает скан источника в фоне.
|
||||
// RetryAnalysis возвращает в очередь треки, которые анализатор не смог обработать (например, пока диск
|
||||
// не был смонтирован).
|
||||
func (s *Service) RetryAnalysis(ctx context.Context) (int, error) {
|
||||
if err := requirePerm(ctx, auth.PermManageLibrary); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.store.RetryFailedAnalysis(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) StartScan(ctx context.Context, id uuid.UUID, full bool) (Progress, error) {
|
||||
if err := requirePerm(ctx, auth.PermManageLibrary); err != nil {
|
||||
return Progress{}, err
|
||||
|
||||
@@ -605,7 +605,7 @@ func (st *Store) AlbumCoverPath(ctx context.Context, albumID uuid.UUID) (*string
|
||||
// AnalysisCounts — состояние очереди аудиоанализа.
|
||||
type AnalysisCounts struct {
|
||||
Queued, Running, Done, Failed int
|
||||
AnalyzerVer *string
|
||||
AnalyzerVer, TopError *string
|
||||
}
|
||||
|
||||
// AnalysisCounts считает задачи анализа по статусам и текущую версию анализатора.
|
||||
@@ -614,14 +614,27 @@ func (st *Store) AnalysisCounts(ctx context.Context) (AnalysisCounts, error) {
|
||||
err := st.pool.QueryRow(ctx, `
|
||||
SELECT count(*) FILTER (WHERE status = 'queued'), count(*) FILTER (WHERE status = 'running'),
|
||||
count(*) FILTER (WHERE status = 'done'), count(*) FILTER (WHERE status = 'failed'),
|
||||
(SELECT analyzer_ver FROM track_features ORDER BY analyzed_at DESC LIMIT 1)
|
||||
FROM analysis_jobs`).Scan(&c.Queued, &c.Running, &c.Done, &c.Failed, &c.AnalyzerVer)
|
||||
(SELECT analyzer_ver FROM track_features ORDER BY analyzed_at DESC LIMIT 1),
|
||||
(SELECT error FROM analysis_jobs WHERE status = 'failed' AND error IS NOT NULL
|
||||
GROUP BY error ORDER BY count(*) DESC LIMIT 1)
|
||||
FROM analysis_jobs`).Scan(&c.Queued, &c.Running, &c.Done, &c.Failed, &c.AnalyzerVer, &c.TopError)
|
||||
if err != nil {
|
||||
return AnalysisCounts{}, fmt.Errorf("library: analysis counts: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// RetryFailedAnalysis возвращает неудавшиеся задачи анализа в очередь с нуля попыток.
|
||||
func (st *Store) RetryFailedAnalysis(ctx context.Context) (int, error) {
|
||||
tag, err := st.pool.Exec(ctx, `
|
||||
UPDATE analysis_jobs SET status = 'queued', attempts = 0, error = NULL, updated_at = now()
|
||||
WHERE status = 'failed'`)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("library: retry analysis: %w", err)
|
||||
}
|
||||
return int(tag.RowsAffected()), nil
|
||||
}
|
||||
|
||||
// WaveMetrics — метрики волны за 7 дней (docs/recommendations.md#1-цели-и-как-измеряем-успех).
|
||||
type WaveMetrics struct {
|
||||
Served, Sessions int
|
||||
|
||||
Reference in new issue
Block a user