diff --git a/cmd/gonic/gonic.go b/cmd/gonic/gonic.go index 18dcb47c..8548d40d 100644 --- a/cmd/gonic/gonic.go +++ b/cmd/gonic/gonic.go @@ -16,6 +16,7 @@ import ( "time" "github.com/google/shlex" + _ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/oklog/run" "github.com/peterbourgon/ff" @@ -47,7 +48,12 @@ func main() { confPlaylistsPath := set.String("playlists-path", "", "path to your list of new or existing m3u playlists that gonic can manage") - confDBPath := set.String("db-path", "gonic.db", "path to database (optional)") + confSqlitePath := set.String("db-path", "gonic.db", "path to database (optional, default: gonic.db)") + confPostgresHost := set.String("postgres-host", "", "name of the PostgreSQL gonicServer (optional)") + confPostgresPort := set.Int("postgres-port", 5432, "port to use for PostgreSQL connection (optional, default: 5432)") + confPostgresName := set.String("postgres-db", "gonic", "name of the PostgreSQL database (optional, default: gonic)") + confPostgresUser := set.String("postgres-user", "gonic", "name of the PostgreSQL user (optional, default: gonic)") + confPostgresSslModel := set.String("postgres-ssl-mode", "verify-full", "the ssl mode used for connecting to the PostreSQL instance (optional, default: verify-full)") confScanIntervalMins := set.Int("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)") confScanAtStart := set.Bool("scan-at-start-enabled", false, "whether to perform an initial scan at startup (optional)") @@ -113,7 +119,12 @@ func main() { log.Fatalf("couldn't create covers cache path: %v\n", err) } - dbc, err := db.New(*confDBPath, db.DefaultOptions()) + var dbc *db.DB + if len(*confPostgresHost) > 0 { + dbc, err = db.NewPostgres(*confPostgresHost, *confPostgresPort, *confPostgresName, *confPostgresUser, os.Getenv("GONIC_POSTGRES_PW"), *confPostgresSslModel) + } else { + dbc, err = db.NewSqlite3(*confSqlitePath, db.DefaultOptions()) + } if err != nil { log.Fatalf("error opening database: %v\n", err) } diff --git a/db/db.go b/db/db.go index 7e4edaf0..41cbaee4 100644 --- a/db/db.go +++ b/db/db.go @@ -34,7 +34,7 @@ type DB struct { *gorm.DB } -func New(path string, options url.Values) (*DB, error) { +func NewSqlite3(path string, options url.Values) (*DB, error) { // https://github.com/mattn/go-sqlite3#connection-string url := url.URL{ Scheme: "file", @@ -45,13 +45,26 @@ func New(path string, options url.Values) (*DB, error) { if err != nil { return nil, fmt.Errorf("with gorm: %w", err) } + return newDB(db) +} + +func NewPostgres(host string, port int, databaseName string, username string, password string, sslmode string) (*DB, error) { + pathAndArgs := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=%s", host, port, username, databaseName, password, sslmode) + db, err := gorm.Open("postgres", pathAndArgs) + if err != nil { + return nil, fmt.Errorf("with gorm: %w", err) + } + return newDB(db) +} + +func newDB(db *gorm.DB) (*DB, error) { db.SetLogger(log.New(os.Stdout, "gorm ", 0)) db.DB().SetMaxOpenConns(1) return &DB{DB: db}, nil } func NewMock() (*DB, error) { - return New(":memory:", mockOptions()) + return NewSqlite3(":memory:", mockOptions()) } func (db *DB) GetSetting(key string) (string, error) { @@ -80,10 +93,11 @@ func (db *DB) InsertBulkLeftMany(table string, head []string, left int, col []in rows = append(rows, "(?, ?)") values = append(values, left, c) } - q := fmt.Sprintf("INSERT OR IGNORE INTO %q (%s) VALUES %s", + q := fmt.Sprintf("INSERT INTO %q (%s) VALUES %s ON CONFLICT (%s) DO NOTHING", table, strings.Join(head, ", "), strings.Join(rows, ", "), + strings.Join(head, ", "), ) return db.Exec(q, values...).Error } diff --git a/db/migrations.go b/db/migrations.go index c560c025..0e7b028c 100644 --- a/db/migrations.go +++ b/db/migrations.go @@ -82,14 +82,15 @@ func construct(ctx MigrationContext, id string, f func(*gorm.DB, MigrationContex func migrateInitSchema(tx *gorm.DB, _ MigrationContext) error { return tx.AutoMigrate( Genre{}, + Artist{}, + Album{}, + Track{}, TrackGenre{}, AlbumGenre{}, - Track{}, - Artist{}, User{}, Setting{}, Play{}, - Album{}, + Playlist{}, PlayQueue{}, ). Error @@ -155,12 +156,18 @@ func migrateAddGenre(tx *gorm.DB, _ MigrationContext) 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 tx.Dialect().GetName() == "sqlite3" { + tx.Select("1"). + Table("sqlite_master"). + Where("type = ?", "index"). + Where("name = ?", "idx_user_id_client"). + Count(&hasIDX) + } else if tx.Dialect().GetName() == "postgres" { + tx.Select("1"). + Table("pg_indexes"). + Where("indexname = ?", "idx_user_id_client"). + Count(&hasIDX) + } if hasIDX == 1 { // index already exists return nil @@ -437,9 +444,15 @@ func migratePlaylistsQueuesToFullID(tx *gorm.DB, _ MigrationContext) error { if err := step.Error; err != nil { return fmt.Errorf("step migrate play_queues to full id: %w", err) } - step = tx.Exec(` + if tx.Dialect().GetName() == "postgres" { + step = tx.Exec(` + UPDATE play_queues SET newcurrent=('tr-' || current)::varchar[200]; + `) + } else { + step = tx.Exec(` UPDATE play_queues SET newcurrent=('tr-' || CAST(current AS varchar(10))); `) + } if err := step.Error; err != nil { return fmt.Errorf("step migrate play_queues to full id: %w", err) } diff --git a/go.sum b/go.sum index 2ead1788..799b35b6 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ +cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -12,6 +14,8 @@ github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJs github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -42,11 +46,15 @@ github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/e github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -84,7 +92,9 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -104,6 +114,7 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -128,6 +139,7 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/peterbourgon/ff v1.7.1 h1:xt1lxTG+Nr2+tFtysY7abFgPoH3Lug8CwYJMOmJRXhk= github.com/peterbourgon/ff v1.7.1/go.mod h1:fYI5YA+3RDqQRExmFbHnBjEeWzh9TrS8rnRpEq7XIg0= @@ -137,10 +149,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 h1:sLILANWN76ja66/K4k/mBqJuCjDZaM67w+Ru6rEB0s0= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981/go.mod h1:Rx8XB1ck+so+41uu9VY1gMKs1CPQ2NTq0pzf+OCCQHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -148,6 +163,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -162,6 +179,7 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -174,6 +192,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -188,6 +207,7 @@ golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -198,8 +218,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/mockfs/mockfs.go b/mockfs/mockfs.go index 06d91364..ebd39f58 100644 --- a/mockfs/mockfs.go +++ b/mockfs/mockfs.go @@ -292,7 +292,7 @@ func (m *MockFS) DumpDB(suffix ...string) { p = append(p, suffix...) destPath := filepath.Join(os.TempDir(), strings.Join(p, "-")) - dest, err := db.New(destPath, url.Values{}) + dest, err := db.NewSqlite3(destPath, url.Values{}) if err != nil { m.t.Fatalf("create dest db: %v", err) } diff --git a/scanner/scanner_test.go b/scanner/scanner_test.go index 7e068e5e..2e18033f 100644 --- a/scanner/scanner_test.go +++ b/scanner/scanner_test.go @@ -411,7 +411,7 @@ func TestMultiFolderWithSharedArtist(t *testing.T) { sq := func(db *gorm.DB) *gorm.DB { return db. - Select("*, count(sub.id) child_count, sum(sub.length) duration"). + Select("albums.*, count(sub.id) child_count, sum(sub.length) duration"). Joins("LEFT JOIN tracks sub ON albums.id=sub.album_id"). Group("albums.id") } diff --git a/server/ctrlsubsonic/handlers_by_folder.go b/server/ctrlsubsonic/handlers_by_folder.go index 558b3112..14469b26 100644 --- a/server/ctrlsubsonic/handlers_by_folder.go +++ b/server/ctrlsubsonic/handlers_by_folder.go @@ -31,13 +31,13 @@ func (c *Controller) ServeGetIndexes(r *http.Request) *spec.Response { } var folders []*db.Album c.DB. - Select("*, count(sub.id) child_count"). + Select("albums.*, count(sub.id) child_count"). Preload("AlbumStar", "user_id=?", user.ID). Preload("AlbumRating", "user_id=?", user.ID). Joins("LEFT JOIN albums sub ON albums.id=sub.parent_id"). Where("albums.parent_id IN ?", rootQ.SubQuery()). Group("albums.id"). - Order("albums.right_path COLLATE NOCASE"). + Order("albums.right_path"). Find(&folders) // [a-z#] -> 27 indexMap := make(map[string]*spec.Index, 27) @@ -80,7 +80,7 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response { Where("parent_id=?", id.Value). Preload("AlbumStar", "user_id=?", user.ID). Preload("AlbumRating", "user_id=?", user.ID). - Order("albums.right_path COLLATE NOCASE"). + Order("albums.right_path"). Find(&childFolders) for _, ch := range childFolders { childrenObj = append(childrenObj, spec.NewTCAlbumByFolder(ch)) diff --git a/server/ctrlsubsonic/handlers_by_tags.go b/server/ctrlsubsonic/handlers_by_tags.go index b16ca506..d594a40b 100644 --- a/server/ctrlsubsonic/handlers_by_tags.go +++ b/server/ctrlsubsonic/handlers_by_tags.go @@ -23,12 +23,12 @@ func (c *Controller) ServeGetArtists(r *http.Request) *spec.Response { user := r.Context().Value(CtxUser).(*db.User) var artists []*db.Artist q := c.DB. - Select("*, count(sub.id) album_count"). + Select("artists.*, count(sub.id) album_count"). Joins("LEFT JOIN albums sub ON artists.id=sub.tag_artist_id"). Preload("ArtistStar", "user_id=?", user.ID). Preload("ArtistRating", "user_id=?", user.ID). Group("artists.id"). - Order("artists.name COLLATE NOCASE") + Order("artists.name") if m := getMusicFolder(c.MusicPaths, params); m != "" { q = q.Where("sub.root_dir=?", m) } @@ -67,7 +67,7 @@ func (c *Controller) ServeGetArtist(r *http.Request) *spec.Response { c.DB. Preload("Albums", func(db *gorm.DB) *gorm.DB { return db. - Select("*, count(sub.id) child_count, sum(sub.length) duration"). + Select("albums.*, count(sub.id) child_count, sum(sub.length) duration"). Joins("LEFT JOIN tracks sub ON albums.id=sub.album_id"). Preload("AlbumStar", "user_id=?", user.ID). Preload("AlbumRating", "user_id=?", user.ID). @@ -98,6 +98,7 @@ func (c *Controller) ServeGetAlbum(r *http.Request) *spec.Response { err = c.DB. Select("albums.*, count(tracks.id) child_count, sum(tracks.length) duration"). Joins("LEFT JOIN tracks ON tracks.album_id=albums.id"). + Group("albums.id"). Preload("TagArtist"). Preload("Genres"). Preload("Tracks", func(db *gorm.DB) *gorm.DB { @@ -162,14 +163,14 @@ func (c *Controller) ServeGetAlbumListTwo(r *http.Request) *spec.Response { case "frequent": user := r.Context().Value(CtxUser).(*db.User) q = q.Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?", user.ID) - q = q.Order("plays.count DESC") + q = q.Order("SUM(plays.count) DESC") case "newest": q = q.Order("created_at DESC") case "random": q = q.Order(gorm.Expr("random()")) case "recent": q = q.Joins("JOIN plays ON albums.id=plays.album_id AND plays.user_id=?", user.ID) - q = q.Order("plays.time DESC") + q = q.Order("MAX(plays.time) DESC") case "starred": q = q.Joins("JOIN album_stars ON albums.id=album_stars.album_id AND album_stars.user_id=?", user.ID) q = q.Order("tag_title") @@ -217,7 +218,7 @@ func (c *Controller) ServeSearchThree(r *http.Request) *spec.Response { // search artists var artists []*db.Artist q := c.DB. - Select("*, count(albums.id) album_count"). + Select("artists.*, count(albums.id) album_count"). Group("artists.id"). Where("name LIKE ? OR name_u_dec LIKE ?", query, query). Joins("JOIN albums ON albums.tag_artist_id=artists.id"). diff --git a/server/ctrlsubsonic/handlers_raw.go b/server/ctrlsubsonic/handlers_raw.go index 50063140..e087fcbd 100644 --- a/server/ctrlsubsonic/handlers_raw.go +++ b/server/ctrlsubsonic/handlers_raw.go @@ -31,7 +31,7 @@ func streamGetTransPref(dbc *db.DB, userID int, client string) (*db.TranscodePre var pref db.TranscodePreference err := dbc. Where("user_id=?", userID). - Where("client COLLATE NOCASE IN (?)", []string{"*", client}). + Where("client IN (?)", []string{"*", client}). Order("client DESC"). // ensure "*" is last if it's there First(&pref). Error diff --git a/server/server.go b/server/server.go index 56d8b39a..7c63e0c9 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,7 @@ package server import ( + "encoding/base64" "fmt" "log" "net/http" @@ -76,17 +77,19 @@ func New(opts Options) (*Server, error) { r.Use(base.WithCORS) r.Use(handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))) - sessKey, err := opts.DB.GetSetting("session_key") + encSessKey, err := opts.DB.GetSetting("session_key") if err != nil { return nil, fmt.Errorf("get session key: %w", err) } - if sessKey == "" { - if err := opts.DB.SetSetting("session_key", string(securecookie.GenerateRandomKey(32))); err != nil { + sessKey, err := base64.StdEncoding.DecodeString(encSessKey) + if err != nil || len(sessKey) == 0 { + sessKey = securecookie.GenerateRandomKey(32) + if err := opts.DB.SetSetting("session_key", base64.StdEncoding.EncodeToString(sessKey)); err != nil { return nil, fmt.Errorf("set session key: %w", err) } } - sessDB := gormstore.New(opts.DB.DB, []byte(sessKey)) + sessDB := gormstore.New(opts.DB.DB, sessKey) sessDB.SessionOpts.HttpOnly = true sessDB.SessionOpts.SameSite = http.SameSiteLaxMode