commit c9450de603282c68d2614350138b0da1c7a5e818 Author: Simon Vieille Date: Tue Aug 27 23:46:37 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d33a15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/client +/server diff --git a/cmd/action/initcmd/process.go b/cmd/action/initcmd/process.go new file mode 100644 index 0000000..0422526 --- /dev/null +++ b/cmd/action/initcmd/process.go @@ -0,0 +1,42 @@ +package initcmd + +import ( + "log" + "os" + + filestore "gitnet.fr/deblan/freetube-sync/store/file" + "gitnet.fr/deblan/freetube-sync/web/client" + "gitnet.fr/deblan/freetube-sync/web/route" +) + +func Process(name, route string, data any) bool { + log.Print("Init of " + name) + response, err := client.Init(route, data) + res := true + + if err != nil { + log.Print("Error while initializing " + name + ": " + err.Error()) + res = false + } else { + if response.Code == 201 { + log.Print(name + " initialized!") + } else { + log.Print("Error while initializing " + name + ": " + response.Message) + res = false + } + } + + return res +} + +func Run() { + a := Process("history", route.HistoryInit, filestore.LoadHistory()) + b := Process("playlists", route.PlaylistInit, filestore.LoadPlaylists()) + c := Process("profiles", route.ProfileInit, filestore.LoadProfiles()) + + if a && b && c { + os.Exit(0) + } + + os.Exit(1) +} diff --git a/cmd/action/watchcmd/process.go b/cmd/action/watchcmd/process.go new file mode 100644 index 0000000..a71e6a1 --- /dev/null +++ b/cmd/action/watchcmd/process.go @@ -0,0 +1,44 @@ +package watchcmd + +import ( + "fmt" + "log" + + "github.com/fsnotify/fsnotify" + config "gitnet.fr/deblan/freetube-sync/config/client" +) + +func Run() { + watcher, err := fsnotify.NewWatcher() + + if err != nil { + log.Print("Error while creating the watcher: " + err.Error()) + } + + defer watcher.Close() + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Has(fsnotify.Write) { + switch event.Name { + case config.GetConfig().Path + "/history.db": + fmt.Printf("%+v\n", "update history") + case config.GetConfig().Path + "/playlists.db": + fmt.Printf("%+v\n", "update playlists") + case config.GetConfig().Path + "/profiles.db": + fmt.Printf("%+v\n", "update profiles") + } + } + } + } + }() + + watcher.Add(config.GetConfig().Path) + + <-make(chan struct{}) +} diff --git a/cmd/client/main.go b/cmd/client/main.go new file mode 100644 index 0000000..b4e51da --- /dev/null +++ b/cmd/client/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "gitnet.fr/deblan/freetube-sync/cmd/action/initcmd" + "gitnet.fr/deblan/freetube-sync/cmd/action/watchcmd" + config "gitnet.fr/deblan/freetube-sync/config/client" +) + +func main() { + config.InitConfig() + action := flag.Arg(0) + + switch action { + case "init": + initcmd.Run() + case "watch": + watchcmd.Run() + default: + fmt.Print("You must pass a sub-command: init, watch") + os.Exit(1) + } + + // lines := file.GetLines("/home/simon/.config/FreeTube/history.db") + // collection := []model.Video{} + // + // for _, line := range lines { + // var item model.Video + // json.Unmarshal([]byte(line), &item) + // + // collection = append(collection, item) + // } + // + // data, err := json.Marshal(collection) + // + // if err != nil { + // panic(err) + // } + // + // req, err := http.NewRequest("POST", "http://localhost:1323/history/push", bytes.NewBuffer(data)) + // req.Header.Set("X-Machine", "endurance") + // req.Header.Set("Content-Type", "application/json") + // + // if err != nil { + // panic(err) + // } + // + // client := &http.Client{} + // resp, err := client.Do(req) + // if err != nil { + // panic(err) + // } + // defer resp.Body.Close() + // fmt.Println("response Status:", resp.Status) + // fmt.Println("response Headers:", resp.Header) + // body, _ := io.ReadAll(resp.Body) + // fmt.Println("response Body:", string(body)) + // + // fmt.Printf("%+v\n", data) + // fmt.Printf("%+v\n", collection) +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..f29d0a9 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/freetube-sync/model" + "gitnet.fr/deblan/freetube-sync/web/route" +) + +func main() { + e := echo.New() + + e.POST(route.HistoryInit, func(c echo.Context) error { + payload := []model.Video{} + err := c.Bind(&payload) + + if err != nil { + return c.JSON(400, map[string]any{ + "code": 400, + "message": err, + }) + } + + return c.JSON(201, map[string]any{ + "code": 201, + "message": "ok", + }) + }) + + e.POST(route.HistoryPush, func(c echo.Context) error { + payload := []model.Video{} + err := c.Bind(&payload) + + if err != nil { + return c.JSON(400, map[string]any{ + "code": 400, + "message": err, + }) + } + + return c.JSON(201, map[string]any{ + "code": 201, + "message": "ok", + }) + }) + + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/config/client/config.go b/config/client/config.go new file mode 100644 index 0000000..d03139d --- /dev/null +++ b/config/client/config.go @@ -0,0 +1,40 @@ +package client + +import ( + "flag" + "os" + "strings" +) + +type Config struct { + Server string + Path string + Hostname string +} + +var config *Config + +func GetConfig() *Config { + if config == nil { + config = &Config{} + } + + return config +} + +func (c *Config) Define(server, hostname, path string) { + c.Server = strings.TrimRight(server, "/") + c.Hostname = hostname + c.Path = path +} + +func InitConfig() { + defaultHostname, _ := os.Hostname() + + path := flag.String("p", os.Getenv("HOME")+"/.config/FreeTube", "Path to FreeTube config directory") + hostname := flag.String("h", defaultHostname, "Hostname") + server := flag.String("s", "", "Server to sync") + flag.Parse() + + GetConfig().Define(*server, *hostname, *path) +} diff --git a/file/reader.go b/file/reader.go new file mode 100644 index 0000000..49e40b7 --- /dev/null +++ b/file/reader.go @@ -0,0 +1,13 @@ +package file + +import ( + "io/ioutil" + "strings" +) + +func GetLines(file string) []string { + bytesRead, _ := ioutil.ReadFile(file) + fileContent := string(bytesRead) + + return strings.Split(fileContent, "\n") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5e253e2 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module gitnet.fr/deblan/freetube-sync + +go 1.23.0 + +require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/labstack/echo/v4 v4.12.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e81948d --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/model/playlist.go b/model/playlist.go new file mode 100644 index 0000000..e082a4c --- /dev/null +++ b/model/playlist.go @@ -0,0 +1,11 @@ +package model + +type Playlist struct { + PlaylistName string `json:"playlistName"` + Protected bool `json:"protected"` + Description string `json:"description"` + Videos []Video `json:"videos"` + Id string `json:"_id"` + CreatedAt uint64 `json:"createdAt"` + LastUpdatedAt uint64 `json:"lastUpdatedAt"` +} diff --git a/model/profile.go b/model/profile.go new file mode 100644 index 0000000..95654ce --- /dev/null +++ b/model/profile.go @@ -0,0 +1,8 @@ +package model + +type Profile struct { + Name string `json:"name"` + BgColor string `json:"bgColor"` + TextColor string `json:"textColor"` + Subscriptions []Subscription `json:"subscriptions"` +} diff --git a/model/subscription.go b/model/subscription.go new file mode 100644 index 0000000..ae63db2 --- /dev/null +++ b/model/subscription.go @@ -0,0 +1,7 @@ +package model + +type Subscription struct { + Id string `json:"id"` + Name string `json:"name"` + Thumbnail string `json:"thumbnail"` +} diff --git a/model/video.go b/model/video.go new file mode 100644 index 0000000..c8187b8 --- /dev/null +++ b/model/video.go @@ -0,0 +1,19 @@ +package model + +type Video struct { + VideoId string `json:"videoId"` + Title string `json:"title"` + Author string `json:"author"` + AuthorId string `json:"authorId"` + Published uint64 `json:"published"` + Description string `json:"description"` + ViewCount uint64 `json:"viewCount"` + LengthSeconds uint64 `json:"lengthSeconds"` + WatchProgress uint64 `json:"watchProgress"` + TimeWatched uint64 `json:"timeWatched"` + LsLive bool `json:"isLive"` + Type string `json:"type"` + Id string `json:"_id"` + LastViewedPlaylistType string `json:"lastViewedPlaylistType"` + LastViewedPlaylistItemId string `json:"lastViewedPlaylistItemId"` +} diff --git a/store/file/history.go b/store/file/history.go new file mode 100644 index 0000000..c757055 --- /dev/null +++ b/store/file/history.go @@ -0,0 +1,23 @@ +package file + +import ( + "encoding/json" + + config "gitnet.fr/deblan/freetube-sync/config/client" + "gitnet.fr/deblan/freetube-sync/file" + "gitnet.fr/deblan/freetube-sync/model" +) + +func LoadHistory() []model.Video { + lines := file.GetLines(config.GetConfig().Path + "/history.db") + collection := []model.Video{} + + for _, line := range lines { + var item model.Video + json.Unmarshal([]byte(line), &item) + + collection = append(collection, item) + } + + return collection +} diff --git a/store/file/playlist.go b/store/file/playlist.go new file mode 100644 index 0000000..357d49f --- /dev/null +++ b/store/file/playlist.go @@ -0,0 +1,27 @@ +package file + +import ( + "encoding/json" + + config "gitnet.fr/deblan/freetube-sync/config/client" + "gitnet.fr/deblan/freetube-sync/file" + "gitnet.fr/deblan/freetube-sync/model" +) + +func LoadPlaylists() []model.Playlist { + lines := file.GetLines(config.GetConfig().Path + "/playlists.db") + collection := []model.Playlist{} + added := make(map[string]bool) + + for i := len(lines) - 1; i >= 0; i-- { + var item model.Playlist + json.Unmarshal([]byte(lines[i]), &item) + + if !added[item.Id] { + added[item.Id] = true + collection = append(collection, item) + } + } + + return collection +} diff --git a/store/file/subscription.go b/store/file/subscription.go new file mode 100644 index 0000000..80754ca --- /dev/null +++ b/store/file/subscription.go @@ -0,0 +1,27 @@ +package file + +import ( + "encoding/json" + + config "gitnet.fr/deblan/freetube-sync/config/client" + "gitnet.fr/deblan/freetube-sync/file" + "gitnet.fr/deblan/freetube-sync/model" +) + +func LoadProfiles() []model.Profile { + lines := file.GetLines(config.GetConfig().Path + "/profiles.db") + collection := []model.Profile{} + added := make(map[string]bool) + + for i := len(lines) - 1; i >= 0; i-- { + var item model.Profile + json.Unmarshal([]byte(lines[i]), &item) + + if !added[item.Name] { + added[item.Name] = true + collection = append(collection, item) + } + } + + return collection +} diff --git a/web/client/client.go b/web/client/client.go new file mode 100644 index 0000000..1cd4432 --- /dev/null +++ b/web/client/client.go @@ -0,0 +1,69 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + config "gitnet.fr/deblan/freetube-sync/config/client" +) + +type Data any + +type PostResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func Request(method, route string, data Data) ([]byte, error) { + value, err := json.Marshal(data) + + if err != nil { + return nil, err + } + + url := fmt.Sprintf("%s%s", config.GetConfig().Server, route) + request, _ := http.NewRequest(method, url, bytes.NewBuffer(value)) + request.Header.Set("X-Machine", config.GetConfig().Hostname) + request.Header.Set("Content-Type", "application/json") + + if err != nil { + return nil, err + } + + client := &http.Client{} + response, err := client.Do(request) + + if err != nil { + return nil, err + } + + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + + if err != nil { + return nil, err + } + + return body, nil +} + +func Post(route string, data Data) ([]byte, error) { + return Request("POST", route, data) +} + +func Get(route string, data Data) ([]byte, error) { + return Request("POST", route, data) +} + +func Init(route string, data Data) (PostResponse, error) { + var value PostResponse + + body, err := Post(route, data) + json.Unmarshal(body, &value) + + return value, err +} diff --git a/web/route/route.go b/web/route/route.go new file mode 100644 index 0000000..2947a63 --- /dev/null +++ b/web/route/route.go @@ -0,0 +1,15 @@ +package route + +const ( + HistoryInit = "/history/init" + HistoryPush = "/history/push" + HistoryPull = "/history/pull" + + ProfileInit = "/profile/init" + ProfilePush = "/profile/push" + ProfilePull = "/profile/pull" + + PlaylistInit = "/playlist/init" + PlaylistPush = "/playlist/push" + PlaylistPull = "/playlist/pull" +)