Basics of a web interface
Some checks failed
Build / build (push) Failing after 1h10m17s

This commit is contained in:
Sebastiaan de Schaetzen 2025-03-12 08:43:07 +01:00
parent 53aaf09c76
commit 6fd1850e20
10 changed files with 165 additions and 108 deletions

View File

@ -17,6 +17,10 @@ func openDatabase() *mysqlite.Db {
databaseSource = "videos.db3" databaseSource = "videos.db3"
} }
return openDatabaseSource(databaseSource)
}
func openDatabaseSource(databaseSource string) *mysqlite.Db {
db, err := mysqlite.OpenDb(databaseSource) db, err := mysqlite.OpenDb(databaseSource)
if err != nil { if err != nil {
log.Fatalf("error opening database: %v", err) log.Fatalf("error opening database: %v", err)

View File

@ -1,12 +1,12 @@
package main package main
import ( import (
"database/sql"
"fmt" "fmt"
"gitea.seeseepuff.be/seeseemelk/mysqlite"
"log" "log"
) )
func CalculateEpisodeNumbers(db *sql.DB) error { func CalculateEpisodeNumbers(db *mysqlite.Db) error {
log.Printf("Calculating episode numbers") log.Printf("Calculating episode numbers")
// Start a transaction // Start a transaction
@ -14,50 +14,50 @@ func CalculateEpisodeNumbers(db *sql.DB) error {
if err != nil { if err != nil {
return fmt.Errorf("error starting transaction: %w", err) return fmt.Errorf("error starting transaction: %w", err)
} }
defer tx.Rollback() defer tx.MustRollback()
// First find all days that still need episodes numbers query := `
rows, err := tx.Query(`
select v.upload_date, select v.upload_date,
ifnull( ifnull(
(select max(episode) from videos where upload_date = v.upload_date), (select max(episode) from videos where upload_date = v.upload_date),
substr(upload_date, 6, 2) || substr(upload_date, 9, 2) || '00') substr(upload_date, 6, 2) || substr(upload_date, 9, 2) || '00')
from videos v where episode is null group by upload_date`) from videos v where episode is null group by upload_date
if err != nil { `
return fmt.Errorf("error retrieving rows: %w", err)
}
// Loop over each year and set the missing episode numbers // Loop over each year and set the missing episode numbers
for rows.Next() { for row := range tx.Query(query).Range(&err) {
var uploadDate string var uploadDate string
var lastEpisode int var lastEpisode int
err = rows.Scan(&uploadDate, &lastEpisode) err = row.Scan(&uploadDate, &lastEpisode)
log.Printf("Last episode for %s is %d", uploadDate, lastEpisode) log.Printf("Last episode for %s is %d", uploadDate, lastEpisode)
if err != nil { if err != nil {
return fmt.Errorf("error getting row: %w", err) return fmt.Errorf("error getting row: %w", err)
} }
// Find all episodes that need updating // Find all episodes that need updating
episodes, err := tx.Query("select id from videos where upload_date = ? and episode is null order by run, id desc", uploadDate) query := tx.Query("select id from videos where upload_date = ? and episode is null order by run, id desc").Bind(uploadDate)
if err != nil {
return fmt.Errorf("error retrieving episodes: %w", err)
}
// Update each episode // Update each episode
for episodes.Next() { for episode := range query.Range(&err) {
var episodeId int var episodeId int
err = episodes.Scan(&episodeId) err = episode.Scan(&episodeId)
if err != nil { if err != nil {
return fmt.Errorf("error retrieving episode id: %w", err) return fmt.Errorf("error retrieving episode id: %w", err)
} }
// Set the episode ID // Set the episode ID
lastEpisode++ lastEpisode++
_, err = tx.Exec("update videos set episode = ? where id = ?", &lastEpisode, &episodeId) err = tx.Query("update videos set episode = ? where id = ?").Bind(&lastEpisode, &episodeId).Exec()
if err != nil { if err != nil {
return fmt.Errorf("error updating episode id: %w", err) return fmt.Errorf("error updating episode id: %w", err)
} }
} }
if err != nil {
return fmt.Errorf("error retrieving episodes: %w", err)
}
}
if err != nil {
return fmt.Errorf("error fetching episodes: %w", err)
} }
// Commit all changes // Commit all changes

View File

@ -6,28 +6,22 @@ func TestCalculateEpisodeNumbers(t *testing.T) {
db := openDatabaseSource(":memory:") db := openDatabaseSource(":memory:")
// Ensure the DB is empty // Ensure the DB is empty
_, err := db.Exec("delete from videos") db.Query("delete from videos").MustExec()
if err != nil {
t.Error(err)
}
_, err = db.Exec(`insert into videos (title, url, upload_date, year, episode, run) values db.Query(`insert into videos (title, url, upload_date, year, episode, run) values
('Video A', 'a', '2025-01-04', 2025, 10401, 1), ('Video A', 'a', '2025-01-04', 2025, 10401, 1),
('Video D', 'd', '2025-01-05', 2025, null, 2), ('Video D', 'd', '2025-01-05', 2025, null, 2),
('Video C', 'c', '2025-01-04', 2025, null, 2), ('Video C', 'c', '2025-01-04', 2025, null, 2),
('Video B', 'b', '2025-01-04', 2025, null, 2), ('Video B', 'b', '2025-01-04', 2025, null, 2),
('Video E', 'e', '2025-10-01', 2025, null, 3), ('Video E', 'e', '2025-10-01', 2025, null, 3),
('Old Video', 'z', '2020-12-31', 2020, 100, 1)`) ('Old Video', 'z', '2020-12-31', 2020, 100, 1)`).MustExec()
err := CalculateEpisodeNumbers(db)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = CalculateEpisodeNumbers(db) rows, err := db.Query("select title, episode from videos where year = 2025").ScanMulti()
if err != nil {
t.Error(err)
}
rows, err := db.Query("select title, episode from videos where year = 2025")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -46,7 +40,7 @@ func TestCalculateEpisodeNumbers(t *testing.T) {
for _, expected := range expectedList { for _, expected := range expectedList {
var title string var title string
var episode int var episode int
rows.Next() rows.MustNext()
err = rows.Scan(&title, &episode) err = rows.Scan(&title, &episode)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -59,4 +53,5 @@ func TestCalculateEpisodeNumbers(t *testing.T) {
t.Errorf("Episode is %d, expected %d for video %s", episode, expected.Episode, title) t.Errorf("Episode is %d, expected %d for video %s", episode, expected.Episode, title)
} }
} }
rows.MustFinish()
} }

3
go.mod
View File

@ -5,12 +5,13 @@ go 1.24
toolchain go1.24.0 toolchain go1.24.0
require ( require (
gitea.seeseepuff.be/seeseemelk/mysqlite v0.3.0 gitea.seeseepuff.be/seeseemelk/mysqlite v0.4.0
github.com/playwright-community/playwright-go v0.4902.0 github.com/playwright-community/playwright-go v0.4902.0
) )
require ( require (
github.com/deckarep/golang-set/v2 v2.7.0 // indirect github.com/deckarep/golang-set/v2 v2.7.0 // indirect
github.com/donseba/go-htmx v1.12.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect

6
go.sum
View File

@ -1,10 +1,12 @@
gitea.seeseepuff.be/seeseemelk/mysqlite v0.3.0 h1:T2TjZj7hvb3KF0zYf/uzidprcDJIBPFi3QcN+gmyutM= gitea.seeseepuff.be/seeseemelk/mysqlite v0.4.0 h1:jBj4qsSJCz7XOBakh2Rl4Pggvv5hPpR8iLcvxwdQ4NQ=
gitea.seeseepuff.be/seeseemelk/mysqlite v0.3.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= gitea.seeseepuff.be/seeseemelk/mysqlite v0.4.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k= github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k=
github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/donseba/go-htmx v1.12.0 h1:7tESER0uxaqsuGMv3yP3pK1drfBUXM6apG4H7/3+IgE=
github.com/donseba/go-htmx v1.12.0/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=

20
html_debug.go Normal file
View File

@ -0,0 +1,20 @@
// / +build !release
package main
import (
"html/template"
"io"
"net/http"
)
func staticFileServer() http.Handler {
return http.FileServer(http.Dir("./static"))
}
func renderTemplate(wr io.Writer) error {
templates, err := template.ParseGlob("web/*.gohtml")
if err != nil {
return err
}
return templates.Execute(wr, nil)
}

139
main.go
View File

@ -1,78 +1,69 @@
package main package main
import (
"encoding/base64"
"flag"
"github.com/playwright-community/playwright-go"
"log"
"os"
"strconv"
"time"
)
func main() { func main() {
var err error //var err error
var onlyInstall = flag.Bool("install", false, "install the required browser and do nothing else") //var onlyInstall = flag.Bool("install", false, "install the required browser and do nothing else")
flag.Parse() //flag.Parse()
//
options := &playwright.RunOptions{ //options := &playwright.RunOptions{
Browsers: []string{"firefox"}, // Browsers: []string{"firefox"},
} //}
err = playwright.Install(options) //err = playwright.Install(options)
if err != nil { //if err != nil {
log.Panicf("error installing playwright: %v", err) // log.Panicf("error installing playwright: %v", err)
} //}
//
if *onlyInstall { //if *onlyInstall {
return // return
} //}
//
db := openDatabase() //db := openDatabase()
defer db.Close() //defer db.Close()
//
sleepTimeStr := os.Getenv("VIVAPLUS_SLEEPTIME") //sleepTimeStr := os.Getenv("VIVAPLUS_SLEEPTIME")
sleepTime := 15 //sleepTime := 15
if sleepTimeStr != "" { //if sleepTimeStr != "" {
sleepTime, err = strconv.Atoi(sleepTimeStr) // sleepTime, err = strconv.Atoi(sleepTimeStr)
if err != nil { // if err != nil {
log.Fatalf("error parsing sleep time: %v", err) // log.Fatalf("error parsing sleep time: %v", err)
} // }
} //}
//
w := NewWebClient(options) //w := NewWebClient(options)
username := os.Getenv("VIVAPLUS_USER") //username := os.Getenv("VIVAPLUS_USER")
password, err := base64.StdEncoding.DecodeString(os.Getenv("VIVAPLUS_PASS")) //password, err := base64.StdEncoding.DecodeString(os.Getenv("VIVAPLUS_PASS"))
if err != nil { //if err != nil {
log.Fatalf("error decoding password: %v", err) // log.Fatalf("error decoding password: %v", err)
} //}
//
err = w.Login(username, string(password)) //err = w.Login(username, string(password))
if err != nil { //if err != nil {
log.Fatalf("error login in: %v", err) // log.Fatalf("error login in: %v", err)
} //}
//
for { //for {
err = w.DiscoverAllVideos(db) // err = w.DiscoverAllVideos(db)
if err != nil { // if err != nil {
panic(err) // panic(err)
} // }
//
err = w.FetchVideoMetadata(db) // err = w.FetchVideoMetadata(db)
if err != nil { // if err != nil {
panic(err) // panic(err)
} // }
//
err = CalculateEpisodeNumbers(db) // err = CalculateEpisodeNumbers(db)
if err != nil { // if err != nil {
panic(err) // panic(err)
} // }
//
err = DownloadAllVideos(db) // err = DownloadAllVideos(db)
if err != nil { // if err != nil {
panic(err) // panic(err)
} // }
//
log.Printf("Sleeping %d minutes until next run", sleepTime) // log.Printf("Sleeping %d minutes until next run", sleepTime)
time.Sleep(time.Duration(sleepTime) * time.Minute) // time.Sleep(time.Duration(sleepTime) * time.Minute)
} //}
serveWebview()
} }

9
static/style.css Normal file
View File

@ -0,0 +1,9 @@
body {
background-color: black;
color: white;
font-family: sans-serif;
}
h1 {
text-transform: uppercase;
}

11
web/index.gohtml Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Viva++</title>
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<h1>Viva++</h1>
</body>
</html>

24
webview.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"log"
"net/http"
)
func serveWebview() {
addr := "localhost:8081"
log.Printf("Listening on %s", addr)
http.Handle("/static/", http.StripPrefix("/static/", staticFileServer()))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := renderTemplate(w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
err := http.ListenAndServe(addr, nil)
if err != nil {
panic(err)
}
}