Store LastScan in Library table

This commit is contained in:
Deluan 2023-12-14 22:22:33 -05:00
parent 34f87bedc3
commit 03957f2050
7 changed files with 86 additions and 55 deletions

View File

@ -3,7 +3,9 @@ package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/navidrome/navidrome/conf"
"github.com/pressly/goose/v3"
)
@ -22,10 +24,48 @@ func upAddLibraryTable(ctx context.Context, tx *sql.Tx) error {
updated_at datetime not null default current_timestamp,
created_at datetime not null default current_timestamp
);`)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
insert into library(id, name, path, last_scan_at) values(1, 'Music Library', '%s', current_timestamp);
delete from property where id like 'LastScan-%%';
`, conf.Server.MusicFolder))
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, `
alter table media_file add column library_id integer not null default 1
references library(id) on delete cascade;
alter table album add column library_id integer not null default 1
references library(id) on delete cascade;
create table if not exists library_artist
(
library_id integer not null default 1
references library(id)
on delete cascade,
artist_id varchar not null default null
references artist(id)
on delete cascade,
constraint library_artist_ux
unique (library_id, artist_id)
);
insert into library_artist(library_id, artist_id) select 1, id from artist;
`)
return err
}
func downAddLibraryTable(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `drop table library;`)
_, err := tx.ExecContext(ctx, `
alter table media_file drop column library_id;
alter table album drop column library_id;
drop table library_artist;
drop table library;
`)
return err
}

View File

@ -25,5 +25,7 @@ type Libraries []Library
type LibraryRepository interface {
Get(id int) (*Library, error)
Put(*Library) error
StoreMusicFolder() error
UpdateLastScan(id int, t time.Time) error
GetAll(...QueryOptions) (Libraries, error)
}

View File

@ -1,10 +1,5 @@
package model
const (
// TODO Move other prop keys to here
PropLastScan = "LastScan"
)
type PropertyRepository interface {
Put(id string, value string) error
Get(id string) (string, error)

View File

@ -5,13 +5,13 @@ import (
"time"
. "github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/model"
"github.com/pocketbase/dbx"
)
type libraryRepository struct {
sqlRepository
sqlRestful
}
func NewLibraryRepository(ctx context.Context, db dbx.Builder) model.LibraryRepository {
@ -31,11 +31,10 @@ func (r *libraryRepository) Get(id int) (*model.Library, error) {
func (r *libraryRepository) Put(l *model.Library) error {
cols := map[string]any{
"name": l.Name,
"path": l.Path,
"remote_path": l.RemotePath,
"last_scan_at": l.LastScanAt,
"updated_at": time.Now(),
"name": l.Name,
"path": l.Path,
"remote_path": l.RemotePath,
"updated_at": time.Now(),
}
if l.ID != 0 {
cols["id"] = l.ID
@ -43,7 +42,23 @@ func (r *libraryRepository) Put(l *model.Library) error {
sq := Insert(r.tableName).SetMap(cols).
Suffix(`ON CONFLICT(id) DO UPDATE set name = excluded.name, path = excluded.path,
remote_path = excluded.remote_path, last_scan_at = excluded.last_scan_at`)
remote_path = excluded.remote_path, updated_at = excluded.updated_at`)
_, err := r.executeSQL(sq)
return err
}
// TODO Remove this and the StoreMusicFolder method when we have a proper UI to add libraries
const hardCodedMusicFolderID = 1
func (r *libraryRepository) StoreMusicFolder() error {
sq := Update(r.tableName).Set("path", conf.Server.MusicFolder).Set("updated_at", time.Now()).
Where(Eq{"id": hardCodedMusicFolderID})
_, err := r.executeSQL(sq)
return err
}
func (r *libraryRepository) UpdateLastScan(id int, t time.Time) error {
sq := Update(r.tableName).Set("last_scan_at", t).Where(Eq{"id": id})
_, err := r.executeSQL(sq)
return err
}

View File

@ -3,8 +3,6 @@ package scanner
import (
"context"
"errors"
"fmt"
"strconv"
"sync"
"time"
@ -41,7 +39,10 @@ type FolderScanner interface {
var isScanning sync.Mutex
type scanner struct {
once sync.Once
folders map[string]FolderScanner
lastScans map[string]time.Time
libIds map[string]int
status map[string]*scanStatus
lock *sync.RWMutex
ds model.DataStore
@ -63,11 +64,12 @@ func New(ds model.DataStore, playlists core.Playlists, cacheWarmer artwork.Cache
pls: playlists,
broker: broker,
folders: map[string]FolderScanner{},
lastScans: map[string]time.Time{},
libIds: map[string]int{},
status: map[string]*scanStatus{},
lock: &sync.RWMutex{},
cacheWarmer: cacheWarmer,
}
s.loadFolders()
return s
}
@ -80,7 +82,7 @@ func (s *scanner) rescan(ctx context.Context, library string, fullRescan bool) e
lastModifiedSince := time.Time{}
if !fullRescan {
lastModifiedSince = s.getLastModifiedSince(ctx, library)
lastModifiedSince = s.lastScans[library]
log.Debug("Scanning folder", "folder", library, "lastModifiedSince", lastModifiedSince)
} else {
log.Debug("Scanning folder (full scan)", "folder", library)
@ -179,6 +181,8 @@ func (s *scanner) setStatusEnd(folder string, lastUpdate time.Time) {
func (s *scanner) RescanAll(ctx context.Context, fullRescan bool) error {
ctx = context.WithoutCancel(ctx)
s.once.Do(s.loadFolders)
if !isScanning.TryLock() {
log.Debug(ctx, "Scanner already running, ignoring request for rescan.")
return ErrAlreadyScanning
@ -200,6 +204,7 @@ func (s *scanner) RescanAll(ctx context.Context, fullRescan bool) error {
}
func (s *scanner) Status(library string) (*StatusInfo, error) {
s.once.Do(s.loadFolders)
status, ok := s.getStatus(library)
if !ok {
return nil, errors.New("library not found")
@ -213,23 +218,12 @@ func (s *scanner) Status(library string) (*StatusInfo, error) {
}, nil
}
func (s *scanner) getLastModifiedSince(ctx context.Context, folder string) time.Time {
ms, err := s.ds.Property(ctx).Get(model.PropLastScan + "-" + folder)
if err != nil {
return time.Time{}
}
if ms == "" {
return time.Time{}
}
i, _ := strconv.ParseInt(ms, 10, 64)
return time.Unix(0, i*int64(time.Millisecond))
}
func (s *scanner) updateLastModifiedSince(folder string, t time.Time) {
millis := t.UnixNano() / int64(time.Millisecond)
if err := s.ds.Property(context.TODO()).Put(model.PropLastScan+"-"+folder, fmt.Sprint(millis)); err != nil {
id := s.libIds[folder]
if err := s.ds.Library(context.Background()).UpdateLastScan(id, t); err != nil {
log.Error("Error updating DB after scan", err)
}
s.lastScans[folder] = t
}
func (s *scanner) loadFolders() {
@ -238,11 +232,13 @@ func (s *scanner) loadFolders() {
for _, f := range fs {
log.Info("Configuring Media Folder", "name", f.Name, "path", f.Path)
s.folders[f.Path] = s.newScanner(f)
s.lastScans[f.Path] = f.LastScanAt
s.libIds[f.Path] = f.ID
s.status[f.Path] = &scanStatus{
active: false,
fileCount: 0,
folderCount: 0,
lastUpdate: s.getLastModifiedSince(ctx, f.Path),
lastUpdate: f.LastScanAt,
}
}
}

View File

@ -15,12 +15,13 @@ import (
)
func initialSetup(ds model.DataStore) {
ctx := context.TODO()
_ = ds.WithTx(func(tx model.DataStore) error {
if err := createOrUpdateMusicFolder(ds); err != nil {
if err := ds.Library(ctx).StoreMusicFolder(); err != nil {
return err
}
properties := ds.Property(context.TODO())
properties := ds.Property(ctx)
_, err := properties.Get(consts.InitialSetupFlagKey)
if err == nil {
return nil
@ -116,12 +117,3 @@ func checkExternalCredentials() {
}
}
}
func createOrUpdateMusicFolder(ds model.DataStore) error {
lib := model.Library{ID: 1, Name: "Music Library", Path: conf.Server.MusicFolder}
err := ds.Library(context.TODO()).Put(&lib)
if err != nil {
log.Error("Could not access Library table", err)
}
return err
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"net/http"
"strconv"
"time"
"github.com/navidrome/navidrome/conf"
@ -31,22 +30,14 @@ func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error)
func (api *Router) getArtistIndex(r *http.Request, libId int, ifModifiedSince time.Time) (*responses.Indexes, error) {
ctx := r.Context()
folder, err := api.ds.Library(ctx).Get(libId)
lib, err := api.ds.Library(ctx).Get(libId)
if err != nil {
log.Error(ctx, "Error retrieving Library", "id", libId, err)
return nil, err
}
l, err := api.ds.Property(ctx).DefaultGet(model.PropLastScan+"-"+folder.Path, "-1")
if err != nil {
log.Error(ctx, "Error retrieving LastScan property", err)
return nil, err
}
var indexes model.ArtistIndexes
ms, _ := strconv.ParseInt(l, 10, 64)
lastModified := utils.ToTime(ms)
if lastModified.After(ifModifiedSince) {
if lib.LastScanAt.After(ifModifiedSince) {
indexes, err = api.ds.Artist(ctx).GetIndex()
if err != nil {
log.Error(ctx, "Error retrieving Indexes", err)
@ -56,7 +47,7 @@ func (api *Router) getArtistIndex(r *http.Request, libId int, ifModifiedSince ti
res := &responses.Indexes{
IgnoredArticles: conf.Server.IgnoredArticles,
LastModified: utils.ToMillis(lastModified),
LastModified: utils.ToMillis(lib.LastScanAt),
}
res.Index = make([]responses.Index, len(indexes))