project.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: Apache-2.0
  4
  5package project
  6
  7import (
  8	"database/sql"
  9	"fmt"
 10	"log"
 11	"sort"
 12	"strings"
 13	"time"
 14
 15	"git.sr.ht/~amolith/willow/db"
 16	"git.sr.ht/~amolith/willow/git"
 17	"git.sr.ht/~amolith/willow/rss"
 18)
 19
 20type Project struct {
 21	URL      string
 22	Name     string
 23	Forge    string
 24	Running  string
 25	Releases []Release
 26}
 27
 28type Release struct {
 29	URL     string
 30	Tag     string
 31	Content string
 32	Date    time.Time
 33}
 34
 35// GetReleases returns a list of all releases for a project from the database
 36func GetReleases(dbConn *sql.DB, proj Project) (Project, error) {
 37	ret, err := db.GetReleases(dbConn, proj.URL)
 38	if err != nil {
 39		return proj, err
 40	}
 41
 42	if len(ret) == 0 {
 43		return fetchReleases(proj)
 44	}
 45
 46	for _, row := range ret {
 47		proj.Releases = append(proj.Releases, Release{
 48			Tag:     row["tag"],
 49			Content: row["content"],
 50			URL:     row["release_url"],
 51			Date:    time.Time{},
 52		})
 53	}
 54	sort.Slice(proj.Releases, func(i, j int) bool {
 55		return proj.Releases[i].Date.After(proj.Releases[j].Date)
 56	})
 57	return proj, nil
 58}
 59
 60// fetchReleases fetches releases from a project's forge given its URI
 61func fetchReleases(p Project) (Project, error) {
 62	var err error
 63	switch p.Forge {
 64	case "github", "gitea", "forgejo":
 65		rssReleases, err := rss.GetReleases(p.URL)
 66		if err != nil {
 67			fmt.Println("Error getting RSS releases:", err)
 68			return p, err
 69		}
 70		for _, release := range rssReleases {
 71			p.Releases = append(p.Releases, Release{
 72				Tag:     release.Tag,
 73				Content: release.Content,
 74				URL:     release.URL,
 75				Date:    release.Date,
 76			})
 77		}
 78	default:
 79		gitReleases, err := git.GetReleases(p.URL, p.Forge)
 80		if err != nil {
 81			return p, err
 82		}
 83		for _, release := range gitReleases {
 84			p.Releases = append(p.Releases, Release{
 85				Tag:     release.Tag,
 86				Content: release.Content,
 87				URL:     release.URL,
 88				Date:    release.Date,
 89			})
 90		}
 91	}
 92	sort.Slice(p.Releases, func(i, j int) bool {
 93		return p.Releases[i].Date.After(p.Releases[j].Date)
 94	})
 95	return p, err
 96}
 97
 98func Track(dbConn *sql.DB, manualRefresh *chan struct{}, name, url, forge, release string) {
 99	err := db.UpsertProject(dbConn, url, name, forge, release)
100	if err != nil {
101		fmt.Println("Error upserting project:", err)
102	}
103	*manualRefresh <- struct{}{}
104}
105
106func Untrack(dbConn *sql.DB, manualRefresh *chan struct{}, url string) {
107	err := db.DeleteProject(dbConn, url)
108	if err != nil {
109		fmt.Println("Error deleting project:", err)
110	}
111
112	*manualRefresh <- struct{}{}
113
114	err = git.RemoveRepo(url)
115	if err != nil {
116		log.Println(err)
117	}
118}
119
120func RefreshLoop(dbConn *sql.DB, interval int, manualRefresh, req *chan struct{}, res *chan []Project) {
121	ticker := time.NewTicker(time.Second * time.Duration(interval))
122
123	fetch := func() []Project {
124		projectsList, err := GetProjects(dbConn)
125		if err != nil {
126			fmt.Println("Error getting projects:", err)
127		}
128		for i, p := range projectsList {
129			p, err := fetchReleases(p)
130			if err != nil {
131				fmt.Println(err)
132				continue
133			}
134			projectsList[i] = p
135		}
136		sort.Slice(projectsList, func(i, j int) bool {
137			return strings.ToLower(projectsList[i].Name) < strings.ToLower(projectsList[j].Name)
138		})
139		for i := range projectsList {
140			for j := range projectsList[i].Releases {
141				err = db.UpsertRelease(dbConn, projectsList[i].URL, projectsList[i].Releases[j].URL, projectsList[i].Releases[j].Tag, projectsList[i].Releases[j].Content, projectsList[i].Releases[j].Date.Format("2006-01-02 15:04:05"))
142				if err != nil {
143					fmt.Println("Error upserting release:", err)
144					continue
145				}
146			}
147		}
148		return projectsList
149	}
150
151	projects := fetch()
152
153	for {
154		select {
155		case <-ticker.C:
156			projects = fetch()
157		case <-*manualRefresh:
158			ticker.Reset(time.Second * 3600)
159			projects = fetch()
160		case <-*req:
161			projectsCopy := make([]Project, len(projects))
162			copy(projectsCopy, projects)
163			*res <- projectsCopy
164		}
165	}
166}
167
168// GetProject returns a project from the database
169func GetProject(dbConn *sql.DB, url string) (Project, error) {
170	var p Project
171	projectDB, err := db.GetProject(dbConn, url)
172	if err != nil {
173		return p, err
174	}
175	p = Project{
176		URL:     projectDB["url"],
177		Name:    projectDB["name"],
178		Forge:   projectDB["forge"],
179		Running: projectDB["version"],
180	}
181	return p, err
182}
183
184// GetProjects returns a list of all projects from the database
185func GetProjects(dbConn *sql.DB) ([]Project, error) {
186	projectsDB, err := db.GetProjects(dbConn)
187	if err != nil {
188		return nil, err
189	}
190
191	projects := make([]Project, len(projectsDB))
192	for i, p := range projectsDB {
193		projects[i] = Project{
194			URL:     p["url"],
195			Name:    p["name"],
196			Forge:   p["forge"],
197			Running: p["version"],
198		}
199	}
200
201	return projects, nil
202}