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}