Skip to content

Commit aadacaa

Browse files
Release v0.2.0
2 parents 80d43c6 + 50693b3 commit aadacaa

18 files changed

+572
-73
lines changed

database/migration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"gorm.io/gorm"
99
)
1010

11-
const targetDatabaseVersion = 32
11+
const targetDatabaseVersion = 35
1212

1313
func migrateIfNecessary(db *gorm.DB) {
1414
logger.Info("Connection acquired. Checking database version")

database/model/listeningSession.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package model
22

33
type SimpleListeningSession struct {
44
BaseModel
5-
Active bool `json:"active"`
6-
OwnerId uint `json:"owner_id"`
7-
JoinId string `json:"join_id"`
8-
QueuePlaylistId string `gorm:"column:queue_playlist" json:"queue_playlist_id"`
9-
Title string `json:"title"`
10-
FallbackPlaylistId *string `gorm:"column:fallback_playlist" json:"fallback_playlist_id"`
5+
Active bool `json:"active"`
6+
OwnerId uint `json:"owner_id"`
7+
JoinId string `json:"join_id"`
8+
QueuePlaylistId string `gorm:"column:queue_playlist" json:"queue_playlist_id"`
9+
Title string `json:"title"`
10+
FallbackPlaylistId *string `gorm:"column:fallback_playlist" json:"fallback_playlist_id"`
11+
FallbackPlaylistShuffle bool `json:"fallback_playlist_shuffle"`
1112
}
1213

1314
func (SimpleListeningSession) TableName() string {

database/model/user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type SimpleUser struct {
99
BaseModel
1010
SpotifyId string `json:"spotify_id"`
1111
SpotifyDisplayName string `json:"spotify_display_name"`
12+
Country string `json:"country"`
1213
SpotifyAccessToken string `json:"-"`
1314
SpotifyRefreshToken string `json:"-"`
1415
SpotifyTokenType string `json:"-"`

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module github.com/47-11/spotifete
33
go 1.16
44

55
require (
6-
github.com/fsnotify/fsnotify v1.4.9 // indirect
6+
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 // indirect
7+
github.com/containerd/containerd v1.5.0-beta.1 // indirect
78
github.com/getsentry/sentry-go v0.10.0
89
github.com/gin-contrib/cors v1.3.1
910
github.com/gin-gonic/gin v1.6.3
@@ -12,17 +13,16 @@ require (
1213
github.com/google/logger v1.1.0
1314
github.com/hashicorp/errwrap v1.1.0 // indirect
1415
github.com/jackc/pgproto3/v2 v2.0.7 // indirect
15-
github.com/json-iterator/go v1.1.10 // indirect
1616
github.com/leodido/go-urn v1.2.1 // indirect
1717
github.com/lib/pq v1.9.0 // indirect
1818
github.com/magiconair/properties v1.8.4 // indirect
1919
github.com/mitchellh/mapstructure v1.4.1 // indirect
20+
github.com/patrickmn/go-cache v2.1.0+incompatible
2021
github.com/pelletier/go-toml v1.8.1 // indirect
2122
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
2223
github.com/spf13/afero v1.5.1 // indirect
2324
github.com/spf13/cast v1.3.1 // indirect
2425
github.com/spf13/jwalterweatherman v1.1.0 // indirect
25-
github.com/spf13/pflag v1.0.5 // indirect
2626
github.com/spf13/viper v1.7.1
2727
github.com/ugorji/go v1.2.4 // indirect
2828
github.com/zmb3/spotify v1.1.1

go.sum

Lines changed: 361 additions & 7 deletions
Large diffs are not rendered by default.

listeningSession/fallbackPlaylist.go

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import (
66
"github.com/47-11/spotifete/database/model"
77
. "github.com/47-11/spotifete/shared"
88
"github.com/47-11/spotifete/users"
9+
"github.com/google/logger"
910
"github.com/zmb3/spotify"
11+
"math/rand"
1012
"net/http"
13+
"time"
1114
)
1215

1316
func ChangeFallbackPlaylist(session model.SimpleListeningSession, user model.SimpleUser, playlistId string) *SpotifeteError {
@@ -38,6 +41,22 @@ func RemoveFallbackPlaylist(session model.SimpleListeningSession, user model.Sim
3841
return nil
3942
}
4043

44+
func SetFallbackPlaylistShuffle(session model.SimpleListeningSession, user model.SimpleUser, shuffle bool) *SpotifeteError {
45+
if user.ID != session.OwnerId {
46+
return NewUserError("Only the session owner can change the shuffle mode.")
47+
}
48+
49+
if shuffle == session.FallbackPlaylistShuffle {
50+
return nil
51+
}
52+
53+
session.FallbackPlaylistShuffle = shuffle
54+
55+
database.GetConnection().Model(&session).Update("fallback_playlist_shuffle", shuffle)
56+
57+
return nil
58+
}
59+
4160
func addFallbackTrackIfNecessary(session model.FullListeningSession, upNextRequest *model.SongRequest) (trackAdded bool, error *SpotifeteError) {
4261
if session.FallbackPlaylistId == nil {
4362
return false, nil
@@ -70,51 +89,29 @@ func addFallbackTrack(session model.FullListeningSession) (error *SpotifeteError
7089
}
7190

7291
func findNextFallbackTrack(session model.FullListeningSession) (nextFallbackTrackId string, spotifeteError *SpotifeteError) {
73-
74-
client := Client(session)
75-
currentUser, err := client.CurrentUser()
76-
if err != nil {
77-
return "", NewError("Could not get user information on session owner from Spotify.", err, http.StatusInternalServerError)
92+
playableTracks, spotifeteError := getPlayablePlaylistTracks(*session.FallbackPlaylistId, session.Owner)
93+
if spotifeteError != nil {
94+
return "", spotifeteError
7895
}
79-
country := currentUser.Country
80-
81-
currentlyPlayingRequest := GetCurrentlyPlayingRequest(session.SimpleListeningSession)
82-
83-
var playableTracks []spotify.FullTrack
84-
offset := 0
85-
allTracksLoaded := false
86-
for !allTracksLoaded {
87-
newPage, err := client.GetPlaylistTracksOpt(spotify.ID(*session.FallbackPlaylistId), &spotify.Options{Country: &country, Offset: &offset}, "")
88-
if err != nil {
89-
return "", NewError("Could not get tracks in fallback playlist from Spotify.", err, http.StatusInternalServerError)
90-
}
9196

92-
newPlayableTracks := filterPlayableTracksFromPlaylistTracks(newPage.Tracks)
93-
fallbackTrack := findPossibleFallbackTrackFromPlayableTracks(newPlayableTracks, session.SimpleListeningSession, currentlyPlayingRequest, 0)
94-
if fallbackTrack != nil {
95-
return *fallbackTrack, nil
96-
}
97-
98-
playableTracks = append(playableTracks, newPlayableTracks...)
99-
offset += newPage.Limit
100-
allTracksLoaded = offset == newPage.Total
97+
if len(*playableTracks) >= 0 {
98+
return doFindNextFallbackTrack(playableTracks, session)
10199
}
102100

103-
return doFindNextFallbackTrack(playableTracks, session, currentlyPlayingRequest)
104-
}
105-
106-
func doFindNextFallbackTrack(playableTracks []spotify.FullTrack, session model.FullListeningSession, currentlyPlayingRequest *model.SongRequest) (nextFallbackTrackId string, spotifeteError *SpotifeteError) {
107-
if len(playableTracks) == 0 {
108-
fallbackPlaylistId := *session.FallbackPlaylistId
101+
session.FallbackPlaylistId = nil
102+
database.GetConnection().Save(session)
109103

110-
session.FallbackPlaylistId = nil
111-
database.GetConnection().Save(session)
104+
logger.Info(fmt.Sprintf("Fallback playlist (%s) for session %d does not contain any playable tracks. Removing fallback playlist.",
105+
*session.FallbackPlaylistId,
106+
session.ID))
107+
return "", NewUserError("Fallback playlist does not contain any playable tracks.")
108+
}
112109

113-
return "", NewInternalError(fmt.Sprintf("Fallback playlist (%s) for session %d does not contain any playable tracks. Removing fallback playlist.", fallbackPlaylistId, session.ID), nil)
114-
}
110+
func doFindNextFallbackTrack(playableTracks *[]spotify.FullTrack, session model.FullListeningSession) (nextFallbackTrackId string, spotifeteError *SpotifeteError) {
111+
currentlyPlayingRequest := GetCurrentlyPlayingRequest(session.SimpleListeningSession)
115112

116113
for i := 0; i < 10_000; i++ {
117-
fallbackTrack := findPossibleFallbackTrackFromPlayableTracks(playableTracks, session.SimpleListeningSession, currentlyPlayingRequest, 0)
114+
fallbackTrack := findPossibleFallbackTrackFromPlayableTracks(*playableTracks, session.SimpleListeningSession, currentlyPlayingRequest, 0)
118115
if fallbackTrack != nil {
119116
return *fallbackTrack, nil
120117
}
@@ -126,21 +123,18 @@ func doFindNextFallbackTrack(playableTracks []spotify.FullTrack, session model.F
126123
return "", NewInternalError(fmt.Sprintf("No track found in fallback playlist for session %d that has been played less than 10,000 times. Aborting and removing fallback playlist.", session.ID), nil)
127124
}
128125

129-
func filterPlayableTracksFromPlaylistTracks(playlistTracks []spotify.PlaylistTrack) (playableTracks []spotify.FullTrack) {
130-
for _, playlistTrack := range playlistTracks {
131-
track := playlistTrack.Track
132-
if track.IsPlayable != nil && *track.IsPlayable {
133-
playableTracks = append(playableTracks, track)
134-
}
126+
func findPossibleFallbackTrackFromPlayableTracks(playableTracks []spotify.FullTrack, session model.SimpleListeningSession, currentlyPlayingRequest *model.SongRequest, maximumPlays int64) (possibleFallbackTrackId *string) {
127+
if session.FallbackPlaylistShuffle {
128+
rand.Seed(time.Now().UnixNano())
129+
rand.Shuffle(
130+
len(playableTracks),
131+
func(i, j int) {
132+
playableTracks[i], playableTracks[j] = playableTracks[j], playableTracks[i]
133+
})
135134
}
136135

137-
return playableTracks
138-
}
139-
140-
func findPossibleFallbackTrackFromPlayableTracks(playableTracks []spotify.FullTrack, session model.SimpleListeningSession, currentlyPlayingRequest *model.SongRequest, maximumPlays int64) (possibleFallbackTrackId *string) {
141-
// TODO: Maybe we could choose a random track? To do that we could just filter all tracks in the current page first and then choose a random one
142-
for _, playableTrack := range playableTracks {
143-
trackId := playableTrack.ID.String()
136+
for _, track := range playableTracks {
137+
trackId := track.ID.String()
144138
if currentlyPlayingRequest == nil || currentlyPlayingRequest.SpotifyTrackId != trackId {
145139
playCount := getTrackPlayCount(session, trackId)
146140

listeningSession/listeningSession.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,13 @@ func NewSession(user model.SimpleUser, title string) (*model.SimpleListeningSess
9494

9595
// Create database entry
9696
listeningSession := model.SimpleListeningSession{
97-
BaseModel: model.BaseModel{},
98-
Active: true,
99-
OwnerId: user.ID,
100-
JoinId: joinId,
101-
QueuePlaylistId: playlist.ID.String(),
102-
Title: title,
97+
BaseModel: model.BaseModel{},
98+
Active: true,
99+
OwnerId: user.ID,
100+
JoinId: joinId,
101+
QueuePlaylistId: playlist.ID.String(),
102+
Title: title,
103+
FallbackPlaylistShuffle: true,
103104
}
104105

105106
database.GetConnection().Create(&listeningSession)

listeningSession/playlist.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ package listeningSession
33
import (
44
"github.com/47-11/spotifete/database"
55
"github.com/47-11/spotifete/database/model"
6+
. "github.com/47-11/spotifete/shared"
7+
"github.com/47-11/spotifete/users"
8+
"github.com/patrickmn/go-cache"
69
"github.com/zmb3/spotify"
10+
"net/http"
11+
"time"
712
)
813

14+
var playlistTrackCache = cache.New(30*time.Minute, time.Hour)
15+
916
func AddOrUpdatePlaylistMetadata(playlist spotify.FullPlaylist) model.PlaylistMetadata {
1017
knownPlaylistMetadata := GetPlaylistMetadataBySpotifyPlaylistId(playlist.ID.String())
1118
if knownPlaylistMetadata != nil {
@@ -33,3 +40,54 @@ func GetPlaylistMetadataBySpotifyPlaylistId(playlistId string) *model.PlaylistMe
3340
return nil
3441
}
3542
}
43+
44+
func getPlayablePlaylistTracks(playlistId string, user model.SimpleUser) (*[]spotify.FullTrack, *SpotifeteError) {
45+
cachedTracks, found := playlistTrackCache.Get(playlistId)
46+
if found {
47+
return cachedTracks.(*[]spotify.FullTrack), nil
48+
}
49+
50+
loadedTracks, spotifeteError := loadPlaylistTracksFromSpotify(playlistId, user)
51+
if spotifeteError != nil {
52+
return nil, spotifeteError
53+
}
54+
55+
var playableTracks []spotify.FullTrack
56+
for _, playlistTrack := range loadedTracks {
57+
track := playlistTrack.Track
58+
59+
if track.IsPlayable != nil && *track.IsPlayable {
60+
playableTracks = append(playableTracks, track)
61+
}
62+
}
63+
64+
playlistTrackCache.SetDefault(playlistId, &playableTracks)
65+
return &playableTracks, nil
66+
}
67+
68+
func loadPlaylistTracksFromSpotify(playlistId string, user model.SimpleUser) ([]spotify.PlaylistTrack, *SpotifeteError) {
69+
client := users.Client(user)
70+
71+
spotifyPlaylistId := spotify.ID(playlistId)
72+
searchOptions := spotify.Options{Country: &user.Country}
73+
var tracks []spotify.PlaylistTrack
74+
75+
var tracksLeftToLoad = true
76+
for tracksLeftToLoad {
77+
page, err := client.GetPlaylistTracksOpt(spotifyPlaylistId, &searchOptions, "")
78+
if err != nil {
79+
return nil, NewError("Could not get playlist tracks from Spotify.", err, http.StatusInternalServerError)
80+
}
81+
82+
tracks = append(tracks, page.Tracks...)
83+
84+
loadedTrackCount := len(tracks)
85+
if loadedTrackCount >= page.Total {
86+
tracksLeftToLoad = false
87+
} else {
88+
searchOptions.Offset = &loadedTrackCount
89+
}
90+
}
91+
92+
return tracks, nil
93+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BEGIN;
2+
3+
ALTER TABLE listening_sessions
4+
DROP COLUMN fallback_playlist_shuffle;
5+
6+
COMMIT;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BEGIN;
2+
3+
ALTER TABLE listening_sessions
4+
ADD COLUMN fallback_playlist_shuffle BOOLEAN DEFAULT FALSE;
5+
6+
COMMIT;

0 commit comments

Comments
 (0)