This commit is contained in:
Simon Vieille 2024-08-27 23:46:37 +02:00
commit c9450de603
18 changed files with 500 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/client
/server

View file

@ -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)
}

View file

@ -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{})
}

64
cmd/client/main.go Normal file
View file

@ -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)
}

47
cmd/server/main.go Normal file
View file

@ -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"))
}

40
config/client/config.go Normal file
View file

@ -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)
}

13
file/reader.go Normal file
View file

@ -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")
}

17
go.mod Normal file
View file

@ -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
)

25
go.sum Normal file
View file

@ -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=

11
model/playlist.go Normal file
View file

@ -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"`
}

8
model/profile.go Normal file
View file

@ -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"`
}

7
model/subscription.go Normal file
View file

@ -0,0 +1,7 @@
package model
type Subscription struct {
Id string `json:"id"`
Name string `json:"name"`
Thumbnail string `json:"thumbnail"`
}

19
model/video.go Normal file
View file

@ -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"`
}

23
store/file/history.go Normal file
View file

@ -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
}

27
store/file/playlist.go Normal file
View file

@ -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
}

View file

@ -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
}

69
web/client/client.go Normal file
View file

@ -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
}

15
web/route/route.go Normal file
View file

@ -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"
)