mirror of
https://github.com/navidrome/navidrome.git
synced 2025-06-02 16:41:21 +03:00
Store MusicFolder as a library in DB
This commit is contained in:
parent
081ef85db6
commit
477bcaee58
71
db/migrations/20240511220020_add_library_table.go
Normal file
71
db/migrations/20240511220020_add_library_table.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
|
"github.com/pressly/goose/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
goose.AddMigrationContext(upAddLibraryTable, downAddLibraryTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upAddLibraryTable(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
_, err := tx.ExecContext(ctx, `
|
||||||
|
create table library (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
name text not null unique,
|
||||||
|
path text not null unique,
|
||||||
|
remote_path text null default '',
|
||||||
|
last_scan_at datetime not null default '0000-00-00 00:00:00',
|
||||||
|
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, `
|
||||||
|
alter table media_file drop column library_id;
|
||||||
|
alter table album drop column library_id;
|
||||||
|
drop table library_artist;
|
||||||
|
drop table library;
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}
|
@ -3,12 +3,17 @@ package model
|
|||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Library struct {
|
type Library struct {
|
||||||
ID int32
|
ID int
|
||||||
Name string
|
Name string
|
||||||
Path string
|
Path string
|
||||||
|
RemotePath string
|
||||||
|
LastScanAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Library) FS() fs.FS {
|
func (f Library) FS() fs.FS {
|
||||||
@ -18,6 +23,7 @@ func (f Library) FS() fs.FS {
|
|||||||
type Libraries []Library
|
type Libraries []Library
|
||||||
|
|
||||||
type LibraryRepository interface {
|
type LibraryRepository interface {
|
||||||
Get(id int32) (*Library, error)
|
Get(id int) (*Library, error)
|
||||||
GetAll() (Libraries, error)
|
Put(*Library) error
|
||||||
|
GetAll(...QueryOptions) (Libraries, error)
|
||||||
}
|
}
|
||||||
|
@ -2,33 +2,57 @@ package persistence
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
. "github.com/Masterminds/squirrel"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type libraryRepository struct {
|
type libraryRepository struct {
|
||||||
ctx context.Context
|
sqlRepository
|
||||||
|
sqlRestful
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLibraryRepository(ctx context.Context, _ dbx.Builder) model.LibraryRepository {
|
func NewLibraryRepository(ctx context.Context, db dbx.Builder) model.LibraryRepository {
|
||||||
return &libraryRepository{ctx}
|
r := &libraryRepository{}
|
||||||
|
r.ctx = ctx
|
||||||
|
r.db = db
|
||||||
|
r.tableName = "library"
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *libraryRepository) Get(int32) (*model.Library, error) {
|
func (r *libraryRepository) Get(id int) (*model.Library, error) {
|
||||||
library := hardCoded()
|
sq := r.newSelect().Columns("*").Where(Eq{"id": id})
|
||||||
return &library, nil
|
var res model.Library
|
||||||
|
err := r.queryOne(sq, &res)
|
||||||
|
return &res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*libraryRepository) GetAll() (model.Libraries, error) {
|
func (r *libraryRepository) Put(l *model.Library) error {
|
||||||
return model.Libraries{hardCoded()}, nil
|
cols := map[string]any{
|
||||||
|
"name": l.Name,
|
||||||
|
"path": l.Path,
|
||||||
|
"remote_path": l.RemotePath,
|
||||||
|
"last_scan_at": l.LastScanAt,
|
||||||
|
"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`)
|
||||||
|
_, err := r.executeSQL(sq)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func hardCoded() model.Library {
|
func (r *libraryRepository) GetAll(ops ...model.QueryOptions) (model.Libraries, error) {
|
||||||
library := model.Library{ID: 0, Path: conf.Server.MusicFolder}
|
sq := r.newSelect(ops...).Columns("*")
|
||||||
library.Name = "Music Library"
|
res := model.Libraries{}
|
||||||
return library
|
err := r.queryAll(sq, &res)
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ model.LibraryRepository = (*libraryRepository)(nil)
|
var _ model.LibraryRepository = (*libraryRepository)(nil)
|
||||||
|
@ -16,6 +16,10 @@ import (
|
|||||||
|
|
||||||
func initialSetup(ds model.DataStore) {
|
func initialSetup(ds model.DataStore) {
|
||||||
_ = ds.WithTx(func(tx model.DataStore) error {
|
_ = ds.WithTx(func(tx model.DataStore) error {
|
||||||
|
if err := createOrUpdateMusicFolder(ds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
properties := ds.Property(context.TODO())
|
properties := ds.Property(context.TODO())
|
||||||
_, err := properties.Get(consts.InitialSetupFlagKey)
|
_, err := properties.Get(consts.InitialSetupFlagKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -112,3 +116,12 @@ 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
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@ func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error)
|
|||||||
libraries, _ := api.ds.Library(r.Context()).GetAll()
|
libraries, _ := api.ds.Library(r.Context()).GetAll()
|
||||||
folders := make([]responses.MusicFolder, len(libraries))
|
folders := make([]responses.MusicFolder, len(libraries))
|
||||||
for i, f := range libraries {
|
for i, f := range libraries {
|
||||||
folders[i].Id = f.ID
|
folders[i].Id = int32(f.ID)
|
||||||
folders[i].Name = f.Name
|
folders[i].Name = f.Name
|
||||||
}
|
}
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
@ -30,7 +30,7 @@ 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) {
|
func (api *Router) getArtistIndex(r *http.Request, libId int, ifModifiedSince time.Time) (*responses.Indexes, error) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
folder, err := api.ds.Library(ctx).Get(int32(libId))
|
folder, err := api.ds.Library(ctx).Get(libId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error retrieving Library", "id", libId, err)
|
log.Error(ctx, "Error retrieving Library", "id", libId, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -68,7 +68,7 @@ func (api *Router) getArtistIndex(r *http.Request, libId int, ifModifiedSince ti
|
|||||||
|
|
||||||
func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) {
|
func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) {
|
||||||
p := req.Params(r)
|
p := req.Params(r)
|
||||||
musicFolderId := p.IntOr("musicFolderId", 0)
|
musicFolderId := p.IntOr("musicFolderId", 1)
|
||||||
ifModifiedSince := p.TimeOr("ifModifiedSince", time.Time{})
|
ifModifiedSince := p.TimeOr("ifModifiedSince", time.Time{})
|
||||||
|
|
||||||
res, err := api.getArtistIndex(r, musicFolderId, ifModifiedSince)
|
res, err := api.getArtistIndex(r, musicFolderId, ifModifiedSince)
|
||||||
@ -83,7 +83,7 @@ func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) {
|
|||||||
|
|
||||||
func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) {
|
func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) {
|
||||||
p := req.Params(r)
|
p := req.Params(r)
|
||||||
musicFolderId := p.IntOr("musicFolderId", 0)
|
musicFolderId := p.IntOr("musicFolderId", 1)
|
||||||
res, err := api.getArtistIndex(r, musicFolderId, time.Time{})
|
res, err := api.getArtistIndex(r, musicFolderId, time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
x
Reference in New Issue
Block a user