Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Jan 10, 2025
2 parents f9b3c74 + f1478d4 commit 3a5439f
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 23 deletions.
105 changes: 95 additions & 10 deletions cmd/pls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package cmd

import (
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"os"
"strconv"

"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/core/auth"
Expand All @@ -15,26 +19,52 @@ import (
)

var (
playlistID string
outputFile string
playlistID string
outputFile string
userID string
outputFormat string
)

type displayPlaylist struct {
Id string `json:"id"`
Name string `json:"name"`
OwnerName string `json:"ownerName"`
OwnerId string `json:"ownerId"`
Public bool `json:"public"`
}

type displayPlaylists []displayPlaylist

func init() {
plsCmd.Flags().StringVarP(&playlistID, "playlist", "p", "", "playlist name or ID")
plsCmd.Flags().StringVarP(&outputFile, "output", "o", "", "output file (default stdout)")
_ = plsCmd.MarkFlagRequired("playlist")
rootCmd.AddCommand(plsCmd)
}

var plsCmd = &cobra.Command{
Use: "pls",
Short: "Export playlists",
Long: "Export Navidrome playlists to M3U files",
Run: func(cmd *cobra.Command, args []string) {
runExporter()
},
listCommand.Flags().StringVarP(&userID, "user", "u", "", "username or ID")
listCommand.Flags().StringVarP(&outputFormat, "format", "f", "csv", "output format [supported values: csv, json]")
plsCmd.AddCommand(listCommand)
}

var (
plsCmd = &cobra.Command{
Use: "pls",
Short: "Export playlists",
Long: "Export Navidrome playlists to M3U files",
Run: func(cmd *cobra.Command, args []string) {
runExporter()
},
}

listCommand = &cobra.Command{
Use: "list",
Short: "List playlists",
Run: func(cmd *cobra.Command, args []string) {
runList()
},
}
)

func runExporter() {
sqlDB := db.Db()
ds := persistence.New(sqlDB)
Expand Down Expand Up @@ -69,3 +99,58 @@ func runExporter() {
log.Fatal("Error writing to the output file", "file", outputFile, err)
}
}

func runList() {
if outputFormat != "csv" && outputFormat != "json" {
log.Fatal("Invalid output format. Must be one of csv, json", "format", outputFormat)
}

sqlDB := db.Db()
ds := persistence.New(sqlDB)
ctx := auth.WithAdminUser(context.Background(), ds)

options := model.QueryOptions{Sort: "owner_name"}

if userID != "" {
user, err := ds.User(ctx).FindByUsername(userID)

if err != nil && !errors.Is(err, model.ErrNotFound) {
log.Fatal("Error retrieving user by name", "name", userID, err)
}

if errors.Is(err, model.ErrNotFound) {
user, err = ds.User(ctx).Get(userID)
if err != nil {
log.Fatal("Error retrieving user by id", "id", userID, err)
}
}

options.Filters = squirrel.Eq{"owner_id": user.ID}
}

playlists, err := ds.Playlist(ctx).GetAll(options)
if err != nil {
log.Fatal(ctx, "Failed to retrieve playlists", err)
}

if outputFormat == "csv" {
w := csv.NewWriter(os.Stdout)
_ = w.Write([]string{"playlist id", "playlist name", "owner id", "owner name", "public"})
for _, playlist := range playlists {
_ = w.Write([]string{playlist.ID, playlist.Name, playlist.OwnerID, playlist.OwnerName, strconv.FormatBool(playlist.Public)})
}
w.Flush()
} else {
display := make(displayPlaylists, len(playlists))
for idx, playlist := range playlists {
display[idx].Id = playlist.ID
display[idx].Name = playlist.Name
display[idx].OwnerId = playlist.OwnerID
display[idx].OwnerName = playlist.OwnerName
display[idx].Public = playlist.Public
}

j, _ := json.Marshal(display)
fmt.Printf("%s\n", j)
}
}
8 changes: 5 additions & 3 deletions db/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ func backupOrRestore(ctx context.Context, isBackup bool, path string) error {
// heavily inspired by https://codingrabbits.dev/posts/go_and_sqlite_backup_and_maybe_restore/
existingConn, err := Db().Conn(ctx)
if err != nil {
return err
return fmt.Errorf("getting existing connection: %w", err)
}
defer existingConn.Close()

backupDb, err := sql.Open(Driver, path)
if err != nil {
return err
return fmt.Errorf("opening backup database in '%s': %w", path, err)
}
defer backupDb.Close()

backupConn, err := backupDb.Conn(ctx)
if err != nil {
return err
return fmt.Errorf("getting backup connection: %w", err)
}
defer backupConn.Close()

Expand Down Expand Up @@ -102,6 +102,7 @@ func backupOrRestore(ctx context.Context, isBackup bool, path string) error {

func Backup(ctx context.Context) (string, error) {
destPath := backupPath(time.Now())
log.Debug(ctx, "Creating backup", "path", destPath)
err := backupOrRestore(ctx, true, destPath)
if err != nil {
return "", err
Expand All @@ -111,6 +112,7 @@ func Backup(ctx context.Context) (string, error) {
}

func Restore(ctx context.Context, path string) error {
log.Debug(ctx, "Restoring backup", "path", path)
return backupOrRestore(ctx, false, path)
}

Expand Down
2 changes: 1 addition & 1 deletion resources/i18n/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@
"sign_in": "Bejelentkezés",
"sign_in_error": "A hitelesítés sikertelen. Kérjük, próbáld újra!",
"logout": "Kijelentkezés",
"insightsCollectionNote": "A Navidrome anonim metrikűkat gyűjt \na projekt fejlesztéséhez. Kattints [ide],\n információkért és az adatgyűjtésből kilépésért."
"insightsCollectionNote": "A Navidrome anonim metrikákat gyűjt \na projekt fejlesztéséhez. Kattints [ide],\n információkért és az adatgyűjtésből kilépésért."
},
"validation": {
"invalidChars": "Kérlek, csak betűket és számokat használj!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
"url": "http://localhost/p/ABC123",
"description": "Check it out!",
"username": "deluan",
"created": "0001-01-01T00:00:00Z",
"expires": "0001-01-01T00:00:00Z",
"lastVisited": "0001-01-01T00:00:00Z",
"created": "2016-03-02T20:30:00Z",
"expires": "2016-03-02T20:30:00Z",
"lastVisited": "2016-03-02T20:30:00Z",
"visitCount": 2
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0" openSubsonic="true">
<shares>
<share id="ABC123" url="http://localhost/p/ABC123" description="Check it out!" username="deluan" created="0001-01-01T00:00:00Z" expires="0001-01-01T00:00:00Z" lastVisited="0001-01-01T00:00:00Z" visitCount="2">
<share id="ABC123" url="http://localhost/p/ABC123" description="Check it out!" username="deluan" created="2016-03-02T20:30:00Z" expires="2016-03-02T20:30:00Z" lastVisited="2016-03-02T20:30:00Z" visitCount="2">
<entry id="1" isDir="false" title="title" album="album" artist="artist" duration="120" isVideo="false" bpm="0" comment="" sortName="" mediaType="" musicBrainzId="" channelCount="0" samplingRate="0">
<replayGain></replayGain>
</entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"status": "ok",
"version": "1.8.0",
"type": "navidrome",
"serverVersion": "v0.0.0",
"openSubsonic": true,
"shares": {
"share": [
{
"id": "ABC123",
"url": "http://localhost/s/ABC123",
"username": "johndoe",
"created": "2016-03-02T20:30:00Z",
"visitCount": 1
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0" type="navidrome" serverVersion="v0.0.0" openSubsonic="true">
<shares>
<share id="ABC123" url="http://localhost/s/ABC123" username="johndoe" created="2016-03-02T20:30:00Z" visitCount="1"></share>
</shares>
</subsonic-response>
2 changes: 1 addition & 1 deletion server/subsonic/responses/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ type Share struct {
Username string `xml:"username,attr" json:"username"`
Created time.Time `xml:"created,attr" json:"created"`
Expires *time.Time `xml:"expires,omitempty,attr" json:"expires,omitempty"`
LastVisited time.Time `xml:"lastVisited,omitempty,attr" json:"lastVisited"`
LastVisited *time.Time `xml:"lastVisited,omitempty,attr" json:"lastVisited,omitempty"`
VisitCount int32 `xml:"visitCount,attr" json:"visitCount"`
}

Expand Down
23 changes: 21 additions & 2 deletions server/subsonic/responses/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,17 +671,36 @@ var _ = Describe("Responses", func() {
})
})

Context("with only required fields", func() {
BeforeEach(func() {
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
response.Shares.Share = []Share{{
ID: "ABC123",
Url: "http://localhost/s/ABC123",
Username: "johndoe",
Created: t,
VisitCount: 1,
}}
})
It("should match .XML", func() {
Expect(xml.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
It("should match .JSON", func() {
Expect(json.MarshalIndent(response, "", " ")).To(MatchSnapshot())
})
})

Context("with data", func() {
BeforeEach(func() {
t := time.Time{}
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
share := Share{
ID: "ABC123",
Url: "http://localhost/p/ABC123",
Description: "Check it out!",
Username: "deluan",
Created: t,
Expires: &t,
LastVisited: t,
LastVisited: &t,
VisitCount: 2,
}
share.Entry = make([]Child, 2)
Expand Down
3 changes: 1 addition & 2 deletions server/subsonic/sharing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server/public"
"github.com/navidrome/navidrome/server/subsonic/responses"
. "github.com/navidrome/navidrome/utils/gg"
"github.com/navidrome/navidrome/utils/req"
"github.com/navidrome/navidrome/utils/slice"
)
Expand Down Expand Up @@ -37,7 +36,7 @@ func (api *Router) buildShare(r *http.Request, share model.Share) responses.Shar
Username: share.Username,
Created: share.CreatedAt,
Expires: share.ExpiresAt,
LastVisited: V(share.LastVisitedAt),
LastVisited: share.LastVisitedAt,
VisitCount: int32(share.VisitCount),
}
if resp.Description == "" {
Expand Down

0 comments on commit 3a5439f

Please sign in to comment.