Skip to content

Commit

Permalink
Store LastScan in Library table
Browse files Browse the repository at this point in the history
  • Loading branch information
deluan committed Dec 16, 2023
1 parent 0f0e88c commit c53d1bf
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 55 deletions.
42 changes: 41 additions & 1 deletion db/migration/20231212210020_add_library_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package migrations
import (
"context"
"database/sql"
"fmt"

"github.com/navidrome/navidrome/conf"
"github.com/pressly/goose/v3"
)

Expand All @@ -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
}
2 changes: 2 additions & 0 deletions model/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 0 additions & 5 deletions model/properties.go
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
29 changes: 22 additions & 7 deletions persistence/library_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -31,19 +31,34 @@ 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
}

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
}
Expand Down
31 changes: 12 additions & 19 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package scanner
import (
"context"
"errors"
"fmt"
"strconv"
"sync"
"time"

Expand Down Expand Up @@ -41,7 +39,10 @@ type FolderScanner interface {
var isScanning sync.Mutex

type scanner struct {
once sync.Once

Check failure on line 42 in scanner/scanner.go

View workflow job for this annotation

GitHub Actions / Lint Go code

field `once` is unused (unused)
folders map[string]FolderScanner
lastScans map[string]time.Time
libIds map[string]int
status map[string]*scanStatus
lock *sync.RWMutex
ds model.DataStore
Expand All @@ -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
}

Expand All @@ -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)
Expand Down Expand Up @@ -213,23 +215,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() {

Check failure on line 226 in scanner/scanner.go

View workflow job for this annotation

GitHub Actions / Lint Go code

func `(*scanner).loadFolders` is unused (unused)
Expand All @@ -238,11 +229,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,
}
}
}
Expand Down
14 changes: 3 additions & 11 deletions server/initial_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,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
Expand Down Expand Up @@ -114,12 +115,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
}
15 changes: 3 additions & 12 deletions server/subsonic/browsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"net/http"
"strconv"
"time"

"github.com/navidrome/navidrome/conf"
Expand All @@ -30,22 +29,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)
Expand All @@ -55,7 +46,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))
Expand Down

0 comments on commit c53d1bf

Please sign in to comment.