Store LastScan in Library table
This commit is contained in:
parent
34f87bedc3
commit
03957f2050
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue