Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-12-16 02:24:55
Commit1547a71d1d9e9b38a26ba2fd5073ef41efdb4d98
Parent2d1243ed20cccba9b3cc7d6daf1c0fd17813ef64

Merge import page into repository create

Diffstat

M res/index.html | 1 -
M res/repo/create.html | 16 ++++++++++++++++
D res/repo/import.html | 64 ----------------------------------------------------------------
M res/res.go | 3 ---
M res/style.css | 13 +++++++++++--
M src/cron/cron.go | 57 ++++++++++++++++++++++++++++++++++-----------------------
M src/goit/goit.go | 7 ++++---
M src/goit/http.go | 1 -
M src/goit/repo.go | 3 +++
M src/main.go | 2 --
M src/repo/create.go | 36 +++++++++++++++++++++++++++++++-----
D src/repo/import.go | 88 --------------------------------------------------------------------------------

12 files changed, 99 insertions, 192 deletions

diff --git a/res/index.html b/res/index.html
index 68bd5da..df35113 100644
--- a/res/index.html
+++ b/res/index.html
@@ -14,7 +14,6 @@
 					<a href="/">Repositories</a>
 					{{if .Auth}}
 						| <a href="/repo/create">Create</a>
-						| <a href="/repo/import">Import</a>
 						| <a href="/user/sessions">User</a>
 					{{end}}
 					{{if .Admin}}
diff --git a/res/repo/create.html b/res/repo/create.html
index 2ecbae7..918190f 100644
--- a/res/repo/create.html
+++ b/res/repo/create.html
@@ -31,6 +31,22 @@
 						</select>
 					</td>
 				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="url">URL</label></td>
+					<td><input type="text" name="url"></td>
+				</tr>
+				<!-- <tr>
+					<td style="text-align: right;"><label for="username">Username</label></td>
+					<td><input type="text" name="username"></td>
+				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="password">Password</label></td>
+					<td><input type="password" name="password"></td>
+				</tr> -->
+				<tr>
+					<td style="text-align: right;"><label for="mirror">Mirror</label></td>
+					<td><input type="checkbox" name="mirror" value="mirror" {{if .IsMirror}}checked{{end}}></td>
+				</tr>
 				<tr>
 					<td></td>
 					<td>
diff --git a/res/repo/import.html b/res/repo/import.html
deleted file mode 100644
index 0a9f1ca..0000000
--- a/res/repo/import.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html>
-<head lang="en-GB">{{template "base/head" .}}</head>
-<body>
-	<header>
-		<table>
-			<tr>
-				<td rowspan="2"><a href="/"><img src="/static/favicon.png" style="max-height: 24px"></a></td>
-				<td><h1>{{.Title}}</h1></td>
-			</tr>
-			<tr><td></td></tr>
-		</table>
-	</header>
-	<main>
-		<form action="/repo/import" method="post">
-			{{.CsrfField}}
-			<table>
-				<tr>
-					<td style="text-align: right;"><label for="url">URL</label></td>
-					<td><input type="text" name="url"></td>
-				</tr>
-				<tr>
-					<td style="text-align: right;"><label for="username">Username</label></td>
-					<td><input type="text" name="username"></td>
-				</tr>
-				<tr>
-					<td style="text-align: right;"><label for="password">Password</label></td>
-					<td><input type="password" name="password"></td>
-				</tr>
-				<tr>
-					<td style="text-align: right;"><label for="mirror">Mirror</label></td>
-					<td><input type="checkbox" name="mirror" value="mirror" {{if .IsMirror}}checked{{end}}></td>
-				</tr>
-				<tr>
-					<td style="text-align: right;"><label for="reponame">Name</label></td>
-					<td><input type="text" name="reponame"></td>
-				</tr>
-				<tr>
-					<td style="text-align: right; vertical-align: top;"><label for="description">Description</label></td>
-					<td><textarea name="description"></textarea></td>
-				</tr>
-				<tr>
-					<td style="text-align: right;"><label for="visibility">Visibility</label></td>
-					<td>
-						<select name="visibility">
-							<option value="public">Public</option>
-							<option value="private" {{if .IsPrivate}}selected{{end}}>Private</option>
-						</select>
-					</td>
-				</tr>
-				<tr>
-					<td></td>
-					<td>
-						<input type="submit" value="Import">
-						<a href="/" style="color: inherit;">Cancel</a>
-					</td>
-				</tr>
-				<tr>
-					<td></td>
-					<td style="color: #AA0000">{{.Message}}</td>
-				</tr>
-			</table>
-		</form>
-	</main>
-</body>
diff --git a/res/res.go b/res/res.go
index 489ab59..d257f88 100644
--- a/res/res.go
+++ b/res/res.go
@@ -52,9 +52,6 @@ var RepoHeader string
 //go:embed repo/create.html
 var RepoCreate string
 
-//go:embed repo/import.html
-var RepoImport string
-
 //go:embed repo/edit.html
 var RepoEdit string
 
diff --git a/res/style.css b/res/style.css
index 2578fd7..e5bb2d1 100644
--- a/res/style.css
+++ b/res/style.css
@@ -21,14 +21,23 @@ table input { border: 2px solid #333333; border-radius: 3px; background-color: #
 table input[type="text"] { color: #888888; width: 24em; }
 table input[type="password"] { color: #888888; width: 24em; }
 table input[type="submit"] { color: #FF7E00; padding: 2px 1.6em; }
+table input[type="checkbox"] {
+	appearance: none; border: 2px solid #333333; border-radius: 3px; display:block; height: 1.375rem; margin: 0;
+	padding: 2px; width: 1.375rem;
+}
+table input[type="checkbox"]:hover { background-color: #222222; cursor: pointer; }
+table input[type="checkbox"]::after { content: ""; display: none; position:relative; }
+table input[type="checkbox"]:checked::after {
+	display: block; background-color: #FF7E00; border: solid #FF7E00; border-radius: 3px; height: 60%; width: 60%;
+}
 
 table select {
 	border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; padding: 2px;
 	width: 24em;
 }
 table textarea {
-	border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; max-height: 18rem;
-	min-height: 6rem; padding: 2px; resize: vertical; width: 24em;
+	border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; display: block;
+	max-height: 18rem; min-height: 6rem; padding: 2px; resize: vertical; width: 24em;
 }
 
 .term-fg1 { font-weight: bold; } /* Bold */
diff --git a/src/cron/cron.go b/src/cron/cron.go
index 06c558b..f768fd3 100644
--- a/src/cron/cron.go
+++ b/src/cron/cron.go
@@ -7,6 +7,7 @@ import (
 	"log"
 	"slices"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/Jamozed/Goit/src/util"
@@ -16,7 +17,7 @@ type Cron struct {
 	jobs    []Job
 	stop    chan struct{}
 	update  chan struct{}
-	running bool
+	running atomic.Bool
 	mutex   sync.Mutex
 	lastId  uint64
 	waiter  sync.WaitGroup
@@ -24,6 +25,7 @@ type Cron struct {
 
 type Job struct {
 	id       uint64
+	rid      int64
 	schedule Schedule
 	next     time.Time
 	fn       func()
@@ -45,12 +47,10 @@ func (c *Cron) Start() {
 	defer c.mutex.Unlock()
 	defer util.Debugln("[cron.Start] Cron mutex unlock")
 
-	if c.running {
+	if !c.running.CompareAndSwap(false, true) {
 		return
 	}
 
-	c.running = true
-
 	for _, job := range c.jobs {
 		job.next = job.schedule.Next(time.Now().UTC())
 	}
@@ -85,12 +85,13 @@ func (c *Cron) Start() {
 						break
 					}
 
-					log.Println("[cron] running job", job.id)
+					log.Println("[cron] running job", job.id, job.rid)
 
+					j := job
 					c.waiter.Add(1)
 					go func() {
 						defer c.waiter.Done()
-						job.fn()
+						j.fn()
 					}()
 
 					if !job.schedule.IsImmediate() {
@@ -101,8 +102,8 @@ func (c *Cron) Start() {
 
 				c.jobs = tmp
 
-				c.mutex.Unlock()
 				util.Debugln("[cron.now] Cron mutex unlock")
+				c.mutex.Unlock()
 
 				c._update()
 
@@ -111,10 +112,12 @@ func (c *Cron) Start() {
 
 				c.mutex.Lock()
 				util.Debugln("[cron.stop] Cron mutex lock")
+
 				c.waiter.Wait()
-				c.running = false
-				c.mutex.Unlock()
+				c.running.Store(false)
+
 				util.Debugln("[cron.stop] Cron mutex unlock")
+				c.mutex.Unlock()
 
 				return
 
@@ -126,12 +129,7 @@ func (c *Cron) Start() {
 }
 
 func (c *Cron) Stop() {
-	c.mutex.Lock()
-	util.Debugln("[cron.Stop] Cron mutex lock")
-	defer c.mutex.Unlock()
-	defer util.Debugln("[cron.Stop] Cron mutex unlock")
-
-	if !c.running {
+	if !c.running.Load() {
 		return
 	}
 
@@ -139,12 +137,7 @@ func (c *Cron) Stop() {
 }
 
 func (c *Cron) Update() {
-	c.mutex.Lock()
-	util.Debugln("[cron.Update] Cron mutex lock")
-	defer c.mutex.Unlock()
-	defer util.Debugln("[cron.Update] Cron mutex unlock")
-
-	if !c.running {
+	if !c.running.Load() {
 		return
 	}
 
@@ -163,7 +156,7 @@ func (c *Cron) _update() {
 	})
 }
 
-func (c *Cron) Add(schedule Schedule, fn func()) uint64 {
+func (c *Cron) Add(rid int64, schedule Schedule, fn func()) uint64 {
 	c.mutex.Lock()
 	util.Debugln("[cron.Add] Cron mutex lock")
 	defer c.mutex.Unlock()
@@ -171,9 +164,27 @@ func (c *Cron) Add(schedule Schedule, fn func()) uint64 {
 
 	c.lastId += 1
 
-	job := Job{id: c.lastId, schedule: schedule, fn: fn}
+	job := Job{id: c.lastId, rid: rid, schedule: schedule, fn: fn}
 	job.next = job.schedule.Next(time.Now().UTC())
 	c.jobs = append(c.jobs, job)
 
 	return job.id
 }
+
+func (c *Cron) RemoveFor(rid int64) {
+	c.mutex.Lock()
+	util.Debugln("[cron.RemoveFor] Cron mutex lock")
+	defer c.mutex.Unlock()
+	defer util.Debugln("[cron.RemoveFor] Cron mutex unlock")
+
+	tmp := c.jobs[:0]
+	for _, job := range c.jobs {
+		if job.rid != rid {
+			tmp = append(tmp, job)
+		} else {
+			log.Println("[cron] removing job", job.id, "for", job.rid)
+		}
+	}
+
+	c.jobs = tmp
+}
diff --git a/src/goit/goit.go b/src/goit/goit.go
index f2d405c..37405f8 100644
--- a/src/goit/goit.go
+++ b/src/goit/goit.go
@@ -119,7 +119,7 @@ func Goit(conf string) (err error) {
 	Cron.Start()
 
 	/* Periodically clean up expired sessions */
-	Cron.Add(cron.Hourly, func() { CleanupSessions() })
+	Cron.Add(-1, cron.Hourly, CleanupSessions)
 
 	/* Add cron jobs for mirror repositories */
 	repos, err := GetRepos()
@@ -129,11 +129,12 @@ func Goit(conf string) (err error) {
 
 	for _, r := range repos {
 		if r.IsMirror {
-			util.Debugln("Adding cron job for", r.Name)
-			Cron.Add(cron.Daily, func() {
+			util.Debugln("Adding mirror cron job for", r.Name)
+			Cron.Add(r.Id, cron.Daily, func() {
 				if err := Pull(r.Id); err != nil {
 					log.Println("[cron:mirror]", err.Error())
 				}
+				log.Println("[cron:mirror] updated", r.Name)
 			})
 		}
 	}
diff --git a/src/goit/http.go b/src/goit/http.go
index e9d6e9e..8bf43d0 100644
--- a/src/goit/http.go
+++ b/src/goit/http.go
@@ -32,7 +32,6 @@ func init() {
 
 	template.Must(Tmpl.New("repo/header").Parse(res.RepoHeader))
 	template.Must(Tmpl.New("repo/create").Parse(res.RepoCreate))
-	template.Must(Tmpl.New("repo/import").Parse(res.RepoImport))
 	template.Must(Tmpl.New("repo/edit").Parse(res.RepoEdit))
 
 	template.Must(Tmpl.New("repo/log").Parse(res.RepoLog))
diff --git a/src/goit/repo.go b/src/goit/repo.go
index 2fe48aa..ac52cfa 100644
--- a/src/goit/repo.go
+++ b/src/goit/repo.go
@@ -142,6 +142,9 @@ func DelRepo(rid int64) error {
 		return err
 	}
 
+	Cron.RemoveFor(rid)
+	Cron.Update()
+
 	return nil
 }
 
diff --git a/src/main.go b/src/main.go
index 1a53f50..14784ec 100644
--- a/src/main.go
+++ b/src/main.go
@@ -110,8 +110,6 @@ func main() {
 		r.Post("/user/edit", user.HandleEdit)
 		r.Get("/repo/create", repo.HandleCreate)
 		r.Post("/repo/create", repo.HandleCreate)
-		r.Get("/repo/import", repo.HandleImport)
-		r.Post("/repo/import", repo.HandleImport)
 		r.Get("/admin", admin.HandleIndex)
 		r.Get("/admin/users", admin.HandleUsers)
 		r.Get("/admin/user/create", admin.HandleUserCreate)
diff --git a/src/repo/create.go b/src/repo/create.go
index 7564260..43fba60 100644
--- a/src/repo/create.go
+++ b/src/repo/create.go
@@ -10,7 +10,9 @@ import (
 	"slices"
 	"strings"
 
+	"github.com/Jamozed/Goit/src/cron"
 	"github.com/Jamozed/Goit/src/goit"
+	"github.com/Jamozed/Goit/src/util"
 	"github.com/gorilla/csrf"
 )
 
@@ -27,9 +29,9 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := struct {
-		Title, Message    string
-		Name, Description string
-		IsPrivate         bool
+		Title, Message         string
+		Name, Description, Url string
+		IsPrivate, IsMirror    bool
 
 		CsrfField template.HTML
 	}{
@@ -41,7 +43,9 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 	if r.Method == http.MethodPost {
 		data.Name = r.FormValue("reponame")
 		data.Description = r.FormValue("description")
+		data.Url = r.FormValue("url")
 		data.IsPrivate = r.FormValue("visibility") == "private"
+		data.IsMirror = r.FormValue("mirror") == "mirror"
 
 		if data.Name == "" {
 			data.Message = "Name cannot be empty"
@@ -53,13 +57,35 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 			return
 		} else if exists {
 			data.Message = "Name \"" + data.Name + "\" is taken"
-		} else if _, err := goit.CreateRepo(goit.Repo{
-			OwnerId: user.Id, Name: data.Name, Description: data.Description, IsPrivate: data.IsPrivate,
+		} else if rid, err := goit.CreateRepo(goit.Repo{
+			OwnerId: user.Id, Name: data.Name, Description: data.Description, Upstream: data.Url,
+			IsPrivate: data.IsPrivate, IsMirror: data.IsMirror,
 		}); err != nil {
 			log.Println("[/repo/create]", err.Error())
 			goit.HttpError(w, http.StatusInternalServerError)
 			return
 		} else {
+			if data.Url != "" {
+				goit.Cron.Add(rid, cron.Immediate, func() {
+					if err := goit.Pull(rid); err != nil {
+						log.Println("[cron:import]", err.Error())
+					}
+					log.Println("[cron:import] imported", data.Name)
+				})
+
+				if data.IsMirror {
+					util.Debugln("Adding mirror cron job for", data.Name)
+					goit.Cron.Add(rid, cron.Daily, func() {
+						if err := goit.Pull(rid); err != nil {
+							log.Println("[cron:mirror]", err.Error())
+						}
+						log.Println("[cron:mirror] updated", data.Name)
+					})
+				}
+
+				goit.Cron.Update()
+			}
+
 			http.Redirect(w, r, "/"+data.Name, http.StatusFound)
 			return
 		}
diff --git a/src/repo/import.go b/src/repo/import.go
deleted file mode 100644
index a3279e2..0000000
--- a/src/repo/import.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (C) 2023, Jakob Wakeling
-// All rights reserved.
-
-package repo
-
-import (
-	"html/template"
-	"log"
-	"net/http"
-	"slices"
-	"strings"
-
-	"github.com/Jamozed/Goit/src/cron"
-	"github.com/Jamozed/Goit/src/goit"
-	"github.com/Jamozed/Goit/src/util"
-	"github.com/gorilla/csrf"
-)
-
-func HandleImport(w http.ResponseWriter, r *http.Request) {
-	auth, user, err := goit.Auth(w, r, true)
-	if err != nil {
-		log.Println("[/repo/import]", err.Error())
-		goit.HttpError(w, http.StatusInternalServerError)
-	}
-
-	if !auth {
-		goit.HttpError(w, http.StatusUnauthorized)
-		return
-	}
-
-	data := struct {
-		Title, Message         string
-		Name, Description, Url string
-		IsPrivate, IsMirror    bool
-
-		CsrfField template.HTML
-	}{
-		Title: "Repository - Create",
-
-		CsrfField: csrf.TemplateField(r),
-	}
-
-	if r.Method == http.MethodPost {
-		data.Name = r.FormValue("reponame")
-		data.Description = r.FormValue("description")
-		data.Url = r.FormValue("url")
-		data.IsPrivate = r.FormValue("visibility") == "private"
-		data.IsMirror = r.FormValue("mirror") == "mirror"
-
-		if data.Url == "" {
-			data.Message = "URL cannot be empty"
-		} else if data.Name == "" {
-			data.Message = "Name cannot be empty"
-		} else if slices.Contains(goit.Reserved, strings.SplitN(data.Name, "/", 2)[0]) || !goit.IsLegal(data.Name) {
-			data.Message = "Name \"" + data.Name + "\" is illegal"
-		} else if exists, err := goit.RepoExists(data.Name); err != nil {
-			log.Println("[/repo/import]", err.Error())
-			goit.HttpError(w, http.StatusInternalServerError)
-			return
-		} else if exists {
-			data.Message = "Name \"" + data.Name + "\" is taken"
-		} else if rid, err := goit.CreateRepo(goit.Repo{
-			OwnerId: user.Id, Name: data.Name, Description: data.Description, Upstream: data.Url,
-			IsPrivate: data.IsPrivate, IsMirror: data.IsMirror,
-		}); err != nil {
-			log.Println("[/repo/import]", err.Error())
-			goit.HttpError(w, http.StatusInternalServerError)
-			return
-		} else {
-			if data.Url != "" {
-				goit.Cron.Add(util.If(data.IsMirror, cron.Daily, cron.Immediate), func() {
-					if err := goit.Pull(rid); err != nil {
-						log.Println("[cron:import]", err.Error())
-					}
-				})
-
-				goit.Cron.Update()
-			}
-
-			http.Redirect(w, r, "/"+data.Name, http.StatusFound)
-			return
-		}
-	}
-
-	if err := goit.Tmpl.ExecuteTemplate(w, "repo/import", data); err != nil {
-		log.Println("[/repo/import]", err.Error())
-	}
-}