2023-10-25 04:14:32 +00:00
|
|
|
// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package project
|
|
|
|
|
|
|
|
import (
|
2023-10-29 14:41:00 +00:00
|
|
|
"crypto/sha256"
|
2023-10-25 04:14:32 +00:00
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2023-11-29 00:19:14 +00:00
|
|
|
"github.com/unascribed/FlexVer/go/flexver"
|
|
|
|
|
2023-10-25 04:14:32 +00:00
|
|
|
"git.sr.ht/~amolith/willow/db"
|
|
|
|
"git.sr.ht/~amolith/willow/git"
|
|
|
|
"git.sr.ht/~amolith/willow/rss"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Project struct {
|
|
|
|
URL string
|
|
|
|
Name string
|
|
|
|
Forge string
|
|
|
|
Running string
|
|
|
|
Releases []Release
|
|
|
|
}
|
|
|
|
|
|
|
|
type Release struct {
|
2023-12-22 22:59:19 +00:00
|
|
|
ID string
|
2023-10-25 04:14:32 +00:00
|
|
|
URL string
|
|
|
|
Tag string
|
|
|
|
Content string
|
|
|
|
Date time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetReleases returns a list of all releases for a project from the database
|
|
|
|
func GetReleases(dbConn *sql.DB, proj Project) (Project, error) {
|
|
|
|
ret, err := db.GetReleases(dbConn, proj.URL)
|
|
|
|
if err != nil {
|
|
|
|
return proj, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ret) == 0 {
|
2023-10-29 14:41:00 +00:00
|
|
|
return fetchReleases(dbConn, proj)
|
2023-10-25 04:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range ret {
|
|
|
|
proj.Releases = append(proj.Releases, Release{
|
|
|
|
Tag: row["tag"],
|
|
|
|
Content: row["content"],
|
|
|
|
URL: row["release_url"],
|
|
|
|
Date: time.Time{},
|
|
|
|
})
|
|
|
|
}
|
2023-11-29 00:19:14 +00:00
|
|
|
proj.Releases = SortReleases(proj.Releases)
|
2023-10-25 04:14:32 +00:00
|
|
|
return proj, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchReleases fetches releases from a project's forge given its URI
|
2023-10-29 14:41:00 +00:00
|
|
|
func fetchReleases(dbConn *sql.DB, p Project) (Project, error) {
|
2023-10-25 04:14:32 +00:00
|
|
|
var err error
|
|
|
|
switch p.Forge {
|
|
|
|
case "github", "gitea", "forgejo":
|
|
|
|
rssReleases, err := rss.GetReleases(p.URL)
|
|
|
|
if err != nil {
|
2023-10-26 00:16:36 +00:00
|
|
|
fmt.Println("Error getting RSS releases:", err)
|
2023-10-25 04:14:32 +00:00
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
for _, release := range rssReleases {
|
|
|
|
p.Releases = append(p.Releases, Release{
|
2023-12-22 22:59:19 +00:00
|
|
|
ID: genReleaseID(p.URL, release.URL, release.Tag),
|
2023-10-25 04:14:32 +00:00
|
|
|
Tag: release.Tag,
|
|
|
|
Content: release.Content,
|
|
|
|
URL: release.URL,
|
|
|
|
Date: release.Date,
|
|
|
|
})
|
2023-10-29 14:41:00 +00:00
|
|
|
err = upsert(dbConn, p.URL, p.Releases)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error upserting release: %v", err)
|
|
|
|
return p, err
|
|
|
|
}
|
2023-10-25 04:14:32 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
gitReleases, err := git.GetReleases(p.URL, p.Forge)
|
|
|
|
if err != nil {
|
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
for _, release := range gitReleases {
|
|
|
|
p.Releases = append(p.Releases, Release{
|
2023-12-22 22:59:19 +00:00
|
|
|
ID: genReleaseID(p.URL, release.URL, release.Tag),
|
2023-10-25 04:14:32 +00:00
|
|
|
Tag: release.Tag,
|
|
|
|
Content: release.Content,
|
|
|
|
URL: release.URL,
|
|
|
|
Date: release.Date,
|
|
|
|
})
|
2023-10-29 14:41:00 +00:00
|
|
|
err = upsert(dbConn, p.URL, p.Releases)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error upserting release: %v", err)
|
|
|
|
return p, err
|
|
|
|
}
|
2023-10-25 04:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-29 00:19:14 +00:00
|
|
|
p.Releases = SortReleases(p.Releases)
|
2023-10-25 04:14:32 +00:00
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
|
2023-11-29 00:19:14 +00:00
|
|
|
func SortReleases(releases []Release) []Release {
|
|
|
|
sort.Slice(releases, func(i, j int) bool {
|
|
|
|
return !flexver.Less(releases[i].Tag, releases[j].Tag)
|
|
|
|
})
|
|
|
|
return releases
|
|
|
|
}
|
|
|
|
|
2023-10-29 14:41:00 +00:00
|
|
|
// upsert updates or inserts a project release into the database
|
|
|
|
func upsert(dbConn *sql.DB, url string, releases []Release) error {
|
|
|
|
for _, release := range releases {
|
|
|
|
date := release.Date.Format("2006-01-02 15:04:05")
|
2023-12-20 19:18:12 +00:00
|
|
|
id := genReleaseID(url, release.URL, release.Tag)
|
2023-10-29 14:41:00 +00:00
|
|
|
err := db.UpsertRelease(dbConn, id, url, release.URL, release.Tag, release.Content, date)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error upserting release: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-20 19:18:12 +00:00
|
|
|
func genReleaseID(projectURL, releaseURL, tag string) string {
|
|
|
|
idByte := sha256.Sum256([]byte(projectURL + releaseURL + tag))
|
|
|
|
return fmt.Sprintf("%x", idByte)
|
|
|
|
}
|
|
|
|
|
2023-10-25 04:14:32 +00:00
|
|
|
func Track(dbConn *sql.DB, manualRefresh *chan struct{}, name, url, forge, release string) {
|
|
|
|
err := db.UpsertProject(dbConn, url, name, forge, release)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error upserting project:", err)
|
|
|
|
}
|
|
|
|
*manualRefresh <- struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Untrack(dbConn *sql.DB, manualRefresh *chan struct{}, url string) {
|
|
|
|
err := db.DeleteProject(dbConn, url)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error deleting project:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
*manualRefresh <- struct{}{}
|
|
|
|
|
|
|
|
err = git.RemoveRepo(url)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func RefreshLoop(dbConn *sql.DB, interval int, manualRefresh, req *chan struct{}, res *chan []Project) {
|
|
|
|
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
|
|
|
|
|
|
|
fetch := func() []Project {
|
|
|
|
projectsList, err := GetProjects(dbConn)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error getting projects:", err)
|
|
|
|
}
|
|
|
|
for i, p := range projectsList {
|
2023-10-29 14:41:00 +00:00
|
|
|
p, err := fetchReleases(dbConn, p)
|
2023-10-25 04:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
projectsList[i] = p
|
|
|
|
}
|
|
|
|
sort.Slice(projectsList, func(i, j int) bool {
|
|
|
|
return strings.ToLower(projectsList[i].Name) < strings.ToLower(projectsList[j].Name)
|
|
|
|
})
|
|
|
|
for i := range projectsList {
|
2023-10-29 14:41:00 +00:00
|
|
|
err = upsert(dbConn, projectsList[i].URL, projectsList[i].Releases)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error upserting release:", err)
|
|
|
|
continue
|
2023-10-25 04:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return projectsList
|
|
|
|
}
|
|
|
|
|
|
|
|
projects := fetch()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
projects = fetch()
|
|
|
|
case <-*manualRefresh:
|
|
|
|
ticker.Reset(time.Second * 3600)
|
|
|
|
projects = fetch()
|
|
|
|
case <-*req:
|
|
|
|
projectsCopy := make([]Project, len(projects))
|
|
|
|
copy(projectsCopy, projects)
|
|
|
|
*res <- projectsCopy
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProject returns a project from the database
|
|
|
|
func GetProject(dbConn *sql.DB, url string) (Project, error) {
|
|
|
|
projectDB, err := db.GetProject(dbConn, url)
|
|
|
|
if err != nil {
|
2023-11-29 01:01:26 +00:00
|
|
|
return Project{}, err
|
2023-10-25 04:14:32 +00:00
|
|
|
}
|
2023-11-29 01:01:26 +00:00
|
|
|
p := Project{
|
2023-10-25 04:14:32 +00:00
|
|
|
URL: projectDB["url"],
|
|
|
|
Name: projectDB["name"],
|
|
|
|
Forge: projectDB["forge"],
|
|
|
|
Running: projectDB["version"],
|
|
|
|
}
|
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProjects returns a list of all projects from the database
|
|
|
|
func GetProjects(dbConn *sql.DB) ([]Project, error) {
|
|
|
|
projectsDB, err := db.GetProjects(dbConn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
projects := make([]Project, len(projectsDB))
|
|
|
|
for i, p := range projectsDB {
|
|
|
|
projects[i] = Project{
|
|
|
|
URL: p["url"],
|
|
|
|
Name: p["name"],
|
|
|
|
Forge: p["forge"],
|
|
|
|
Running: p["version"],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return projects, nil
|
|
|
|
}
|