From fe5eb2db344e86f91a07932f61b8821ab2a448bd Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Thu, 29 Aug 2024 00:47:17 +0200 Subject: [PATCH] add playlists and profiles init/pull/push --- cmd/action/initcmd/process.go | 4 +- cmd/action/pullcmd/process.go | 53 +++++++- cmd/action/watchcmd/process.go | 4 +- model/playlist.go | 19 +-- model/profile.go | 6 +- model/pull.go | 2 +- model/subscription.go | 7 +- model/video.go | 10 +- store/file/{playlist.go => playlists.go} | 8 +- store/file/{subscription.go => profiles.go} | 8 +- web/client/client.go | 18 +++ web/controller/history/controller.go | 14 +-- web/controller/playlist/controller.go | 131 +++++++++++++++----- web/controller/profile/controller.go | 127 ++++++++++++++----- web/route/route.go | 12 +- 15 files changed, 307 insertions(+), 116 deletions(-) rename store/file/{playlist.go => playlists.go} (75%) rename store/file/{subscription.go => profiles.go} (76%) diff --git a/cmd/action/initcmd/process.go b/cmd/action/initcmd/process.go index 4cf13da..14557e8 100644 --- a/cmd/action/initcmd/process.go +++ b/cmd/action/initcmd/process.go @@ -31,8 +31,8 @@ func Process(name, route string, data any) bool { func Run() { a := Process("history", route.HistoryInit, filestore.LoadHistory()) - b := Process("playlists", route.PlaylistInit, filestore.LoadPlaylists()) - c := Process("profiles", route.ProfileInit, filestore.LoadProfiles()) + b := Process("playlists", route.PlaylistsInit, filestore.LoadPlaylists()) + c := Process("profiles", route.ProfilesInit, filestore.LoadProfiles()) if a && b && c { os.Exit(0) diff --git a/cmd/action/pullcmd/process.go b/cmd/action/pullcmd/process.go index fff3745..c05957a 100644 --- a/cmd/action/pullcmd/process.go +++ b/cmd/action/pullcmd/process.go @@ -31,13 +31,58 @@ func ProcessHistory() bool { return res } +func ProcessPlaylists() bool { + log.Print("Pull of playlists") + items, err := client.PullPlaylists() + res := true + _ = items + + if err != nil { + log.Print("Error while pulling playlists: " + err.Error()) + res = false + } else { + lines := []string{} + + for _, item := range items { + line, _ := json.Marshal(item) + lines = append(lines, string(line)) + } + + file.UpdatePlaylists(lines) + } + + return res +} + +func ProcessProfiles() bool { + log.Print("Pull of profiles") + items, err := client.PullProfiles() + res := true + _ = items + + if err != nil { + log.Print("Error while pulling profiles: " + err.Error()) + res = false + } else { + lines := []string{} + + for _, item := range items { + line, _ := json.Marshal(item) + lines = append(lines, string(line)) + } + + file.UpdateProfiles(lines) + } + + return res +} + func Run() { a := ProcessHistory() - // b := Process("playlists", route.PlaylistPull) - // c := Process("profiles", route.ProfilePull) + b := ProcessPlaylists() + c := ProcessProfiles() - // if a && b && c { - if a { + if a && b && c { os.Exit(0) } diff --git a/cmd/action/watchcmd/process.go b/cmd/action/watchcmd/process.go index 3cb078e..54e3267 100644 --- a/cmd/action/watchcmd/process.go +++ b/cmd/action/watchcmd/process.go @@ -52,9 +52,9 @@ func Run() { case c.DbPath("history"): Process("history", route.HistoryPush, filestore.LoadHistory()) case c.DbPath("playlists"): - Process("playlists", route.PlaylistPush, filestore.LoadPlaylists()) + Process("playlists", route.PlaylistsPush, filestore.LoadPlaylists()) case c.DbPath("profiles"): - Process("profiles", route.ProfilePush, filestore.LoadProfiles()) + Process("profiles", route.ProfilesPush, filestore.LoadProfiles()) } } } diff --git a/model/playlist.go b/model/playlist.go index c3926b6..beaf7e3 100644 --- a/model/playlist.go +++ b/model/playlist.go @@ -3,14 +3,15 @@ package model import "time" type Playlist struct { - ID uint `json:"-";gorm:"primary_key"` - DeletedAt *time.Time `json:"-";sql:"index"` + ID uint `json:"-" gorm:"primary_key"` + CreatedAt time.Time `json:"-"` - PlaylistName string `json:"playlistName"` - Protected bool `json:"protected"` - Description string `json:"description"` - Videos []PlaylistVideo `json:"videos"` - Id string `json:"_id"` - CreatedAt uint64 `json:"createdAt"` - LastUpdatedAt uint64 `json:"lastUpdatedAt"` + Hostname string `json:"-"` + PlaylistName string `json:"playlistName"` + Protected bool `json:"protected"` + Description string `json:"description"` + Videos []PlaylistVideo `json:"videos"` + RemoteId string `json:"_id"` + RemoteCreatedAt uint64 `json:"createdAt"` + LastUpdatedAt uint64 `json:"lastUpdatedAt"` } diff --git a/model/profile.go b/model/profile.go index 601e7ac..e3c10fb 100644 --- a/model/profile.go +++ b/model/profile.go @@ -1,13 +1,11 @@ package model -import "time" - type Profile struct { - ID uint `json:"-";gorm:"primary_key"` - DeletedAt *time.Time `json:"-";sql:"index"` + ID uint `json:"-" gorm:"primary_key"` Name string `json:"name"` BgColor string `json:"bgColor"` TextColor string `json:"textColor"` Subscriptions []Subscription `json:"subscriptions" gorm:"many2many:profile_subscription"` + RemoteId string `json:"_id"` } diff --git a/model/pull.go b/model/pull.go index 8991053..ddb4c63 100644 --- a/model/pull.go +++ b/model/pull.go @@ -5,7 +5,7 @@ import ( ) type Pull struct { - ID uint `json:"-";gorm:"primary_key"` + ID uint `json:"-" gorm:"primary_key"` Hostname string Database string diff --git a/model/subscription.go b/model/subscription.go index c2bc12d..26516fb 100644 --- a/model/subscription.go +++ b/model/subscription.go @@ -1,13 +1,10 @@ package model -import "time" - type Subscription struct { - ID uint `json:"-";gorm:"primary_key"` - DeletedAt *time.Time `json:"-";sql:"index"` + ID uint `json:"-" gorm:"primary_key"` Profiles []Profile `gorm:"many2many:profile_subscription"` - Id string `json:"id"` + RemoteId string `json:"id"` Name string `json:"name"` Thumbnail string `json:"thumbnail"` } diff --git a/model/video.go b/model/video.go index 9fe3646..e931cad 100644 --- a/model/video.go +++ b/model/video.go @@ -2,11 +2,13 @@ package model import ( "time" + + "gorm.io/gorm" ) type WatchedVideo struct { - ID uint `json:"-";gorm:"primary_key"` - DeletedAt *time.Time `json:"-";sql:"index"` + ID uint `json:"-" gorm:"primary_key"` + DeletedAt gorm.DeletedAt `json:"-" sql:"index"` VideoId string `json:"videoId"` Title string `json:"title"` @@ -20,14 +22,14 @@ type WatchedVideo struct { TimeWatched uint64 `json:"timeWatched"` IsLive bool `json:"isLive"` Type string `json:"type"` - Id string `json:"_id"` + RemoteId string `json:"_id"` LastViewedPlaylistType string `json:"lastViewedPlaylistType"` LastViewedPlaylistItemId *string `json:"lastViewedPlaylistItemId"` } type PlaylistVideo struct { ID uint `gorm:"primary_key"` - DeletedAt *time.Time `json:"-";sql:"index"` + DeletedAt *time.Time `json:"-" sql:"index"` PlaylistID uint VideoId string `json:"videoId"` diff --git a/store/file/playlist.go b/store/file/playlists.go similarity index 75% rename from store/file/playlist.go rename to store/file/playlists.go index 3639d74..dd853e0 100644 --- a/store/file/playlist.go +++ b/store/file/playlists.go @@ -17,11 +17,15 @@ func LoadPlaylists() []model.Playlist { var item model.Playlist json.Unmarshal([]byte(lines[i]), &item) - if !added[item.Id] { - added[item.Id] = true + if !added[item.RemoteId] { + added[item.RemoteId] = true collection = append(collection, item) } } return collection } + +func UpdatePlaylists(data []string) { + file.WriteDatabase(config.GetConfig().DbPath("playlists"), data) +} diff --git a/store/file/subscription.go b/store/file/profiles.go similarity index 76% rename from store/file/subscription.go rename to store/file/profiles.go index fb17c26..18e9c3f 100644 --- a/store/file/subscription.go +++ b/store/file/profiles.go @@ -17,11 +17,15 @@ func LoadProfiles() []model.Profile { var item model.Profile json.Unmarshal([]byte(lines[i]), &item) - if !added[item.Name] { - added[item.Name] = true + if !added[item.RemoteId] { + added[item.RemoteId] = true collection = append(collection, item) } } return collection } + +func UpdateProfiles(data []string) { + file.WriteDatabase(config.GetConfig().DbPath("profiles"), data) +} diff --git a/web/client/client.go b/web/client/client.go index c4db9fd..a94262c 100644 --- a/web/client/client.go +++ b/web/client/client.go @@ -78,3 +78,21 @@ func PullHistory() ([]model.WatchedVideo, error) { return items, err } + +func PullPlaylists() ([]model.Playlist, error) { + var items []model.Playlist + + body, err := Get(route.PlaylistsPull) + json.Unmarshal(body, &items) + + return items, err +} + +func PullProfiles() ([]model.Profile, error) { + var items []model.Profile + + body, err := Get(route.ProfilesPull) + json.Unmarshal(body, &items) + + return items, err +} diff --git a/web/controller/history/controller.go b/web/controller/history/controller.go index 6536d87..b25a452 100644 --- a/web/controller/history/controller.go +++ b/web/controller/history/controller.go @@ -11,26 +11,26 @@ import ( ) func InitPush(c echo.Context) error { - payload := []model.WatchedVideo{} - err := c.Bind(&payload) + watchedVideos := []model.WatchedVideo{} + err := c.Bind(&watchedVideos) manager := database.GetManager() if err != nil { return helper.Ko(c, err) } - for _, item := range payload { - manager.Db.Where(item).FirstOrCreate(&item) + for _, watchedVideo := range watchedVideos { + manager.Db.Where(watchedVideo).FirstOrCreate(&watchedVideo) } return helper.Ok(c) } func Pull(c echo.Context) error { - entities := []model.WatchedVideo{} + watchedVideos := []model.WatchedVideo{} manager := database.GetManager() - manager.Db.Find(&entities) + manager.Db.Find(&watchedVideos) pull := model.Pull{ Hostname: c.Request().Header.Get("X-Machine"), @@ -41,7 +41,7 @@ func Pull(c echo.Context) error { pull.PullAt = time.Now() manager.Db.Save(&pull) - return c.JSON(200, entities) + return c.JSON(200, watchedVideos) } func Register(e *echo.Echo) { diff --git a/web/controller/playlist/controller.go b/web/controller/playlist/controller.go index b6c5e99..847cb10 100644 --- a/web/controller/playlist/controller.go +++ b/web/controller/playlist/controller.go @@ -1,58 +1,121 @@ package playlist import ( + "time" + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/freetube-sync/model" + "gitnet.fr/deblan/freetube-sync/store/database" + "gitnet.fr/deblan/freetube-sync/web/helper" "gitnet.fr/deblan/freetube-sync/web/route" + "gorm.io/gorm/clause" ) -func Ko(c echo.Context, err error) error { - return c.JSON(400, map[string]any{ - "code": 400, - "message": err, - }) -} +func Init(c echo.Context) error { + playlists := []model.Playlist{} + err := c.Bind(&playlists) + manager := database.GetManager() -func Ok(c echo.Context) error { - return c.JSON(201, map[string]any{ - "code": 201, - "message": "ok", - }) -} - -func OkKo(c echo.Context, err error) error { if err != nil { - return Ko(c, err) + return helper.Ko(c, err) } - return Ok(c) -} + for _, playlist := range playlists { + manager.Db.Create(&playlist) + } -func Init(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error - - return OkKo(c, err) + return helper.Ok(c) } func Push(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error + playlists := []model.Playlist{} + err := c.Bind(&playlists) + manager := database.GetManager() - return OkKo(c, err) + if err != nil { + return helper.Ko(c, err) + } + + hostname := c.Request().Header.Get("X-Machine") + + pull := model.Pull{ + Hostname: hostname, + Database: "playlists", + } + + manager.Db.Where(pull).First(&pull) + + ids := []string{} + + for _, playlist := range playlists { + if playlist.PlaylistName == "" { + continue + } + + var existingPlaylist model.Playlist + manager.Db.Preload("Videos").Where(model.Playlist{ + RemoteId: playlist.RemoteId, + }).First(&existingPlaylist) + + if existingPlaylist.ID == 0 { + playlist.Hostname = hostname + manager.Db.Create(&playlist) + ids = append(ids, playlist.RemoteId) + } else { + existingPlaylist.Description = playlist.Description + existingPlaylist.LastUpdatedAt = playlist.LastUpdatedAt + existingPlaylist.PlaylistName = playlist.PlaylistName + existingPlaylist.Protected = playlist.Protected + + for _, v := range existingPlaylist.Videos { + manager.Db.Delete(v) + } + + existingPlaylist.Videos = playlist.Videos + manager.Db.Save(&existingPlaylist) + ids = append(ids, existingPlaylist.RemoteId) + } + } + + if len(ids) > 0 { + var playlistsToDelete []model.Playlist + + manager.Db.Find( + &playlistsToDelete, + "remote_id not in (?) and (created_at < ? or hostname = ?)", + ids, + pull.PullAt, + hostname, + ) + + for _, entity := range playlistsToDelete { + manager.Db.Select(clause.Associations).Delete(&entity) + } + } + + return helper.Ok(c) } func Pull(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error + playlists := []model.Playlist{} + manager := database.GetManager() - return OkKo(c, err) + manager.Db.Preload("Videos").Find(&playlists) + + pull := model.Pull{ + Hostname: c.Request().Header.Get("X-Machine"), + Database: "playlist", + } + + manager.Db.Where(pull).FirstOrCreate(&pull) + pull.PullAt = time.Now() + manager.Db.Save(&pull) + + return c.JSON(200, playlists) } func Register(e *echo.Echo) { - e.POST(route.PlaylistInit, Init) - e.POST(route.PlaylistPush, Push) - e.GET(route.PlaylistPull, Pull) + e.POST(route.PlaylistsInit, Init) + e.POST(route.PlaylistsPush, Push) + e.GET(route.PlaylistsPull, Pull) } diff --git a/web/controller/profile/controller.go b/web/controller/profile/controller.go index 416d764..e1ca649 100644 --- a/web/controller/profile/controller.go +++ b/web/controller/profile/controller.go @@ -1,58 +1,117 @@ package profile import ( + "time" + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/freetube-sync/model" + "gitnet.fr/deblan/freetube-sync/store/database" + "gitnet.fr/deblan/freetube-sync/web/helper" "gitnet.fr/deblan/freetube-sync/web/route" + "gorm.io/gorm/clause" ) -func Ko(c echo.Context, err error) error { - return c.JSON(400, map[string]any{ - "code": 400, - "message": err, - }) -} +func Init(c echo.Context) error { + profiles := []model.Profile{} + err := c.Bind(&profiles) + manager := database.GetManager() -func Ok(c echo.Context) error { - return c.JSON(201, map[string]any{ - "code": 201, - "message": "ok", - }) -} - -func OkKo(c echo.Context, err error) error { if err != nil { - return Ko(c, err) + return helper.Ko(c, err) } - return Ok(c) -} + for _, profile := range profiles { + manager.Db.Create(&profile) + } -func Init(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error - - return OkKo(c, err) + return helper.Ok(c) } func Push(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error + profiles := []model.Profile{} + err := c.Bind(&profiles) + manager := database.GetManager() - return OkKo(c, err) + if err != nil { + return helper.Ko(c, err) + } + + hostname := c.Request().Header.Get("X-Machine") + + pull := model.Pull{ + Hostname: hostname, + Database: "playlist", + } + + manager.Db.Where(pull).First(&pull) + + ids := []string{} + + for _, profile := range profiles { + if profile.Name == "" { + continue + } + + var existingProfile model.Profile + manager.Db.Preload("Subscriptions").Where(model.Profile{ + RemoteId: profile.RemoteId, + }).First(&existingProfile) + + if existingProfile.ID == 0 { + manager.Db.Create(&profile) + ids = append(ids, profile.Name) + } else { + existingProfile.Name = profile.Name + existingProfile.BgColor = profile.BgColor + existingProfile.TextColor = profile.TextColor + + for _, v := range existingProfile.Subscriptions { + manager.Db.Select(clause.Associations).Delete(v) + } + + existingProfile.Subscriptions = profile.Subscriptions + manager.Db.Save(&existingProfile) + ids = append(ids, existingProfile.Name) + } + } + + if len(ids) > 0 { + var profilesToDelete []model.Profile + + manager.Db.Find( + &profilesToDelete, + "name not in (?)", + ids, + ) + + for _, entity := range profilesToDelete { + manager.Db.Select(clause.Associations).Delete(&entity) + } + } + + return helper.Ok(c) } func Pull(c echo.Context) error { - // payload := []model.Video{} - // err := c.Bind(&payload) - var err error + profiles := []model.Profile{} + manager := database.GetManager() - return OkKo(c, err) + manager.Db.Preload("Subscriptions").Find(&profiles) + + pull := model.Pull{ + Hostname: c.Request().Header.Get("X-Machine"), + Database: "profiles", + } + + manager.Db.Where(pull).FirstOrCreate(&pull) + pull.PullAt = time.Now() + manager.Db.Save(&pull) + + return c.JSON(200, profiles) } func Register(e *echo.Echo) { - e.POST(route.ProfileInit, Init) - e.POST(route.ProfilePush, Push) - e.GET(route.ProfilePull, Pull) + e.POST(route.ProfilesInit, Init) + e.POST(route.ProfilesPush, Push) + e.GET(route.ProfilesPull, Pull) } diff --git a/web/route/route.go b/web/route/route.go index 2947a63..b0d5095 100644 --- a/web/route/route.go +++ b/web/route/route.go @@ -5,11 +5,11 @@ const ( HistoryPush = "/history/push" HistoryPull = "/history/pull" - ProfileInit = "/profile/init" - ProfilePush = "/profile/push" - ProfilePull = "/profile/pull" + ProfilesInit = "/profiles/init" + ProfilesPush = "/profiles/push" + ProfilesPull = "/profiles/pull" - PlaylistInit = "/playlist/init" - PlaylistPush = "/playlist/push" - PlaylistPull = "/playlist/pull" + PlaylistsInit = "/playlists/init" + PlaylistsPush = "/playlists/push" + PlaylistsPull = "/playlists/pull" )