Author | Jakob Wakeling <[email protected]> |
Date | 2023-12-16 02:24:55 |
Commit | 1547a71d1d9e9b38a26ba2fd5073ef41efdb4d98 |
Parent | 2d1243ed20cccba9b3cc7d6daf1c0fd17813ef64 |
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()) - } -}