feat: add multi folder support

closes #50
This commit is contained in:
sentriz
2021-11-03 23:15:09 +00:00
parent fa587fc7de
commit 40cd031b05
32 changed files with 744 additions and 606 deletions

View File

@@ -9,8 +9,6 @@ import (
"strings"
"github.com/jinzhu/gorm"
"gopkg.in/gormigrate.v1"
)
func DefaultOptions() url.Values {
@@ -49,30 +47,6 @@ func New(path string, options url.Values) (*DB, error) {
}
db.SetLogger(log.New(os.Stdout, "gorm ", 0))
db.DB().SetMaxOpenConns(1)
migrOptions := &gormigrate.Options{
TableName: "migrations",
IDColumnName: "id",
IDColumnSize: 255,
UseTransaction: false,
}
migr := gormigrate.New(db, migrOptions, wrapMigrations(
migrateInitSchema(),
migrateCreateInitUser(),
migrateMergePlaylist(),
migrateCreateTranscode(),
migrateAddGenre(),
migrateUpdateTranscodePrefIDX(),
migrateAddAlbumIDX(),
migrateMultiGenre(),
migrateListenBrainz(),
migratePodcast(),
migrateBookmarks(),
migratePodcastAutoDownload(),
migrateAlbumCreatedAt(),
))
if err = migr.Migrate(); err != nil {
return nil, fmt.Errorf("migrating to latest version: %w", err)
}
return &DB{DB: db}, nil
}

View File

@@ -3,33 +3,45 @@ package db
import (
"errors"
"fmt"
"log"
"github.com/jinzhu/gorm"
"gopkg.in/gormigrate.v1"
)
// $ date '+%Y%m%d%H%M'
type MigrationContext struct {
OriginalMusicPath string
}
func migrateInitSchema() gormigrate.Migration {
return gormigrate.Migration{
ID: "202002192100",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Genre{},
TrackGenre{},
AlbumGenre{},
Track{},
Artist{},
User{},
Setting{},
Play{},
Album{},
Playlist{},
PlayQueue{},
).
Error
},
func (db *DB) Migrate(ctx MigrationContext) error {
options := &gormigrate.Options{
TableName: "migrations",
IDColumnName: "id",
IDColumnSize: 255,
UseTransaction: false,
}
// $ date '+%Y%m%d%H%M'
migrations := []*gormigrate.Migration{
construct(ctx, "202002192100", migrateInitSchema),
construct(ctx, "202002192019", migrateCreateInitUser),
construct(ctx, "202002192222", migrateMergePlaylist),
construct(ctx, "202003111222", migrateCreateTranscode),
construct(ctx, "202003121330", migrateAddGenre),
construct(ctx, "202003241509", migrateUpdateTranscodePrefIDX),
construct(ctx, "202004302006", migrateAddAlbumIDX),
construct(ctx, "202012151806", migrateMultiGenre),
construct(ctx, "202101081149", migrateListenBrainz),
construct(ctx, "202101111537", migratePodcast),
construct(ctx, "202102032210", migrateBookmarks),
construct(ctx, "202102191448", migratePodcastAutoDownload),
construct(ctx, "202110041330", migrateAlbumCreatedAt),
construct(ctx, "202111021951", migrateAlbumRootDir),
}
return gormigrate.
New(db.DB, options, migrations).
Migrate()
}
func construct(ctx MigrationContext, id string, f func(*gorm.DB, MigrationContext) error) *gormigrate.Migration {
@@ -41,240 +53,256 @@ func construct(ctx MigrationContext, id string, f func(*gorm.DB, MigrationContex
if err := f(tx, ctx); err != nil {
return fmt.Errorf("%q: %w", id, err)
}
return tx.Create(&User{
Name: initUsername,
Password: initPassword,
IsAdmin: true,
}).
Error
log.Printf("migration '%s' finished", id)
return nil
},
}
}
func migrateMergePlaylist() gormigrate.Migration {
return gormigrate.Migration{
ID: "202002192222",
Migrate: func(tx *gorm.DB) error {
if !tx.HasTable("playlist_items") {
return nil
}
return tx.Exec(`
UPDATE playlists
SET items=( SELECT group_concat(track_id) FROM (
SELECT track_id
FROM playlist_items
WHERE playlist_items.playlist_id=playlists.id
ORDER BY created_at
) );
DROP TABLE playlist_items;`,
).
Error
},
}
}
func migrateCreateTranscode() gormigrate.Migration {
return gormigrate.Migration{
ID: "202003111222",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
TranscodePreference{},
).
Error
},
}
}
func migrateAddGenre() gormigrate.Migration {
return gormigrate.Migration{
ID: "202003121330",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Genre{},
Album{},
Track{},
).
Error
},
}
}
func migrateUpdateTranscodePrefIDX() gormigrate.Migration {
return gormigrate.Migration{
ID: "202003241509",
Migrate: func(tx *gorm.DB) error {
var hasIDX int
tx.
Select("1").
Table("sqlite_master").
Where("type = ?", "index").
Where("name = ?", "idx_user_id_client").
Count(&hasIDX)
if hasIDX == 1 {
// index already exists
return nil
}
step := tx.Exec(`
ALTER TABLE transcode_preferences RENAME TO transcode_preferences_orig;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step rename: %w", err)
}
step = tx.AutoMigrate(
TranscodePreference{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step create: %w", err)
}
step = tx.Exec(`
INSERT INTO transcode_preferences (user_id, client, profile)
SELECT user_id, client, profile
FROM transcode_preferences_orig;
DROP TABLE transcode_preferences_orig;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step copy: %w", err)
}
Rollback: func(*gorm.DB) error {
return nil
},
}
}
func migrateAddAlbumIDX() gormigrate.Migration {
return gormigrate.Migration{
ID: "202004302006",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Album{},
).
Error
},
}
func migrateInitSchema(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Genre{},
TrackGenre{},
AlbumGenre{},
Track{},
Artist{},
User{},
Setting{},
Play{},
Album{},
Playlist{},
PlayQueue{},
).
Error
}
func migrateMultiGenre() gormigrate.Migration {
return gormigrate.Migration{
ID: "202012151806",
Migrate: func(tx *gorm.DB) error {
step := tx.AutoMigrate(
Genre{},
TrackGenre{},
AlbumGenre{},
Track{},
Album{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
var genreCount int
tx.
Model(Genre{}).
Count(&genreCount)
if genreCount == 0 {
return nil
}
step = tx.Exec(`
INSERT INTO track_genres (track_id, genre_id)
SELECT id, tag_genre_id
FROM tracks
WHERE tag_genre_id IS NOT NULL;
UPDATE tracks SET tag_genre_id=NULL;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate track genres: %w", err)
}
step = tx.Exec(`
INSERT INTO album_genres (album_id, genre_id)
SELECT id, tag_genre_id
FROM albums
WHERE tag_genre_id IS NOT NULL;
UPDATE albums SET tag_genre_id=NULL;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate album genres: %w", err)
}
return nil
},
func migrateCreateInitUser(tx *gorm.DB, _ MigrationContext) error {
const (
initUsername = "admin"
initPassword = "admin"
)
err := tx.
Where("name=?", initUsername).
First(&User{}).
Error
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return tx.Create(&User{
Name: initUsername,
Password: initPassword,
IsAdmin: true,
}).
Error
}
func migrateListenBrainz() gormigrate.Migration {
return gormigrate.Migration{
ID: "202101081149",
Migrate: func(tx *gorm.DB) error {
step := tx.AutoMigrate(
User{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
return nil
},
func migrateMergePlaylist(tx *gorm.DB, _ MigrationContext) error {
if !tx.HasTable("playlist_items") {
return nil
}
return tx.Exec(`
UPDATE playlists
SET items=( SELECT group_concat(track_id) FROM (
SELECT track_id
FROM playlist_items
WHERE playlist_items.playlist_id=playlists.id
ORDER BY created_at
) );
DROP TABLE playlist_items;`,
).
Error
}
func migratePodcast() gormigrate.Migration {
return gormigrate.Migration{
ID: "202101111537",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Podcast{},
PodcastEpisode{},
).
Error
},
}
func migrateCreateTranscode(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
TranscodePreference{},
).
Error
}
func migrateBookmarks() gormigrate.Migration {
return gormigrate.Migration{
ID: "202102032210",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Bookmark{},
).
Error
},
}
func migrateAddGenre(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Genre{},
Album{},
Track{},
).
Error
}
func migratePodcastAutoDownload() gormigrate.Migration {
return gormigrate.Migration{
ID: "202102191448",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
Podcast{},
).
Error
},
func migrateUpdateTranscodePrefIDX(tx *gorm.DB, _ MigrationContext) error {
var hasIDX int
tx.
Select("1").
Table("sqlite_master").
Where("type = ?", "index").
Where("name = ?", "idx_user_id_client").
Count(&hasIDX)
if hasIDX == 1 {
// index already exists
return nil
}
step := tx.Exec(`
ALTER TABLE transcode_preferences RENAME TO transcode_preferences_orig;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step rename: %w", err)
}
step = tx.AutoMigrate(
TranscodePreference{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step create: %w", err)
}
step = tx.Exec(`
INSERT INTO transcode_preferences (user_id, client, profile)
SELECT user_id, client, profile
FROM transcode_preferences_orig;
DROP TABLE transcode_preferences_orig;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step copy: %w", err)
}
return nil
}
func migrateAlbumCreatedAt() gormigrate.Migration {
return gormigrate.Migration{
ID: "202110041330",
Migrate: func(tx *gorm.DB) error {
step := tx.AutoMigrate(
Album{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
step = tx.Exec(`
UPDATE albums SET created_at=modified_at;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate album created_at: %w", err)
}
return nil
},
}
func migrateAddAlbumIDX(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Album{},
).
Error
}
func migrateMultiGenre(tx *gorm.DB, _ MigrationContext) error {
step := tx.AutoMigrate(
Genre{},
TrackGenre{},
AlbumGenre{},
Track{},
Album{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
var genreCount int
tx.
Model(Genre{}).
Count(&genreCount)
if genreCount == 0 {
return nil
}
step = tx.Exec(`
INSERT INTO track_genres (track_id, genre_id)
SELECT id, tag_genre_id
FROM tracks
WHERE tag_genre_id IS NOT NULL;
UPDATE tracks SET tag_genre_id=NULL;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate track genres: %w", err)
}
step = tx.Exec(`
INSERT INTO album_genres (album_id, genre_id)
SELECT id, tag_genre_id
FROM albums
WHERE tag_genre_id IS NOT NULL;
UPDATE albums SET tag_genre_id=NULL;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate album genres: %w", err)
}
return nil
}
func migrateListenBrainz(tx *gorm.DB, _ MigrationContext) error {
step := tx.AutoMigrate(
User{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
return nil
}
func migratePodcast(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Podcast{},
PodcastEpisode{},
).
Error
}
func migrateBookmarks(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Bookmark{},
).
Error
}
func migratePodcastAutoDownload(tx *gorm.DB, _ MigrationContext) error {
return tx.AutoMigrate(
Podcast{},
).
Error
}
func migrateAlbumCreatedAt(tx *gorm.DB, _ MigrationContext) error {
step := tx.AutoMigrate(
Album{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
step = tx.Exec(`
UPDATE albums SET created_at=modified_at;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step migrate album created_at: %w", err)
}
return nil
}
func migrateAlbumRootDir(tx *gorm.DB, ctx MigrationContext) error {
var hasIDX int
tx.
Select("1").
Table("sqlite_master").
Where("type = ?", "index").
Where("name = ?", "idx_album_abs_path").
Count(&hasIDX)
if hasIDX == 1 {
// index already exists
return nil
}
step := tx.AutoMigrate(
Album{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
step = tx.Exec(`
DROP INDEX IF EXISTS idx_left_path_right_path;
`)
if err := step.Error; err != nil {
return fmt.Errorf("step drop idx: %w", err)
}
step = tx.Exec(`
UPDATE albums SET root_dir=?
`, ctx.OriginalMusicPath)
if err := step.Error; err != nil {
return fmt.Errorf("step drop idx: %w", err)
}
return nil
}

View File

@@ -132,6 +132,18 @@ func (t *Track) MIME() string {
return v
}
func (t *Track) AbsPath() string {
if t.Album == nil {
return ""
}
return path.Join(
t.Album.RootDir,
t.Album.LeftPath,
t.Album.RightPath,
t.Filename,
)
}
func (t *Track) RelPath() string {
if t.Album == nil {
return ""
@@ -182,11 +194,12 @@ type Album struct {
CreatedAt time.Time
UpdatedAt time.Time
ModifiedAt time.Time
LeftPath string `gorm:"unique_index:idx_left_path_right_path"`
RightPath string `gorm:"not null; unique_index:idx_left_path_right_path" sql:"default: null"`
LeftPath string `gorm:"unique_index:idx_album_abs_path"`
RightPath string `gorm:"not null; unique_index:idx_album_abs_path" sql:"default: null"`
RightPathUDec string `sql:"default: null"`
Parent *Album
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
Genres []*Genre `gorm:"many2many:album_genres"`
Cover string `sql:"default: null"`
TagArtist *Artist