Fix recursive lock
This commit is contained in:
parent
8bf9f42416
commit
3dc33cd441
@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
|
||||
## [Unreleased]
|
||||
|
||||
- Check the chosen command exists when configuring a profile
|
||||
- Add a stop button in the popup to abort a download
|
||||
- Move included JS to local app instead of accessing from a CDN
|
||||
- Make the simultaneous download limit apply to each unique domain
|
||||
|
||||
## [v0.5.3] - 2021-11-21
|
||||
|
||||
|
@ -3,11 +3,14 @@ package download
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tardisx/gropple/config"
|
||||
@ -28,6 +31,7 @@ type Download struct {
|
||||
Percent float32 `json:"percent"`
|
||||
Log []string `json:"log"`
|
||||
Config *config.Config
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type Downloads []*Download
|
||||
@ -35,26 +39,31 @@ type Downloads []*Download
|
||||
// StartQueued starts any downloads that have been queued, we would not exceed
|
||||
// maxRunning. If maxRunning is 0, there is no limit.
|
||||
func (dls Downloads) StartQueued(maxRunning int) {
|
||||
active := 0
|
||||
queued := 0
|
||||
active := make(map[string]int)
|
||||
|
||||
for _, dl := range dls {
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
if dl.State == "downloading" {
|
||||
active++
|
||||
}
|
||||
if dl.State == "queued" {
|
||||
queued++
|
||||
active[dl.domain()]++
|
||||
}
|
||||
dl.mutex.Unlock()
|
||||
|
||||
}
|
||||
|
||||
// there is room, so start one
|
||||
if queued > 0 && (active < maxRunning || maxRunning == 0) {
|
||||
for _, dl := range dls {
|
||||
if dl.State == "queued" {
|
||||
dl.State = "downloading"
|
||||
go func() { dl.Begin() }()
|
||||
return
|
||||
}
|
||||
for _, dl := range dls {
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
if dl.State == "queued" && (maxRunning == 0 || active[dl.domain()] < maxRunning) {
|
||||
dl.State = "downloading"
|
||||
active[dl.domain()]++
|
||||
log.Printf("Starting download for %#v", dl)
|
||||
dl.mutex.Unlock()
|
||||
go func() { dl.Begin() }()
|
||||
} else {
|
||||
dl.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,23 +74,57 @@ func (dls Downloads) StartQueued(maxRunning int) {
|
||||
func (dls Downloads) Cleanup() Downloads {
|
||||
newDLs := Downloads{}
|
||||
for _, dl := range dls {
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
if dl.Finished && time.Since(dl.FinishedTS) > time.Duration(time.Hour) {
|
||||
// do nothing
|
||||
} else {
|
||||
newDLs = append(newDLs, dl)
|
||||
}
|
||||
dl.mutex.Unlock()
|
||||
|
||||
}
|
||||
return newDLs
|
||||
}
|
||||
|
||||
// Queue queues a download
|
||||
func (dl *Download) Queue() {
|
||||
|
||||
dl.mutex.Lock()
|
||||
defer dl.mutex.Unlock()
|
||||
|
||||
dl.State = "queued"
|
||||
|
||||
}
|
||||
|
||||
func (dl *Download) Stop() {
|
||||
log.Printf("stopping the download")
|
||||
dl.mutex.Lock()
|
||||
defer dl.mutex.Unlock()
|
||||
|
||||
syscall.Kill(dl.Pid, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
func (dl *Download) domain() string {
|
||||
|
||||
// note that we expect to already have the mutex locked by the caller
|
||||
url, err := url.Parse(dl.Url)
|
||||
if err != nil {
|
||||
log.Printf("Unknown domain for url: %s", dl.Url)
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return url.Hostname()
|
||||
|
||||
}
|
||||
|
||||
// Begin starts a download, by starting the command specified in the DownloadProfile.
|
||||
// It blocks until the download is complete.
|
||||
func (dl *Download) Begin() {
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
dl.State = "downloading"
|
||||
cmdSlice := []string{}
|
||||
cmdSlice = append(cmdSlice, dl.DownloadProfile.Args...)
|
||||
@ -112,6 +155,7 @@ func (dl *Download) Begin() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Starting %v", cmd)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
dl.State = "failed"
|
||||
@ -124,6 +168,8 @@ func (dl *Download) Begin() {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
dl.mutex.Unlock()
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@ -138,6 +184,8 @@ func (dl *Download) Begin() {
|
||||
wg.Wait()
|
||||
cmd.Wait()
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
dl.State = "complete"
|
||||
dl.Finished = true
|
||||
dl.FinishedTS = time.Now()
|
||||
@ -146,6 +194,7 @@ func (dl *Download) Begin() {
|
||||
if dl.ExitCode != 0 {
|
||||
dl.State = "failed"
|
||||
}
|
||||
dl.mutex.Unlock()
|
||||
|
||||
}
|
||||
|
||||
@ -164,9 +213,13 @@ func (dl *Download) updateDownload(r io.Reader) {
|
||||
continue
|
||||
}
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
// append the raw log
|
||||
dl.Log = append(dl.Log, l)
|
||||
|
||||
dl.mutex.Unlock()
|
||||
|
||||
// look for the percent and eta and other metadata
|
||||
dl.updateMetadata(l)
|
||||
}
|
||||
@ -179,6 +232,10 @@ func (dl *Download) updateDownload(r io.Reader) {
|
||||
|
||||
func (dl *Download) updateMetadata(s string) {
|
||||
|
||||
dl.mutex.Lock()
|
||||
|
||||
defer dl.mutex.Unlock()
|
||||
|
||||
// [download] 49.7% of ~15.72MiB at 5.83MiB/s ETA 00:07
|
||||
etaRE := regexp.MustCompile(`download.+ETA +(\d\d:\d\d(?::\d\d)?)$`)
|
||||
matches := etaRE.FindStringSubmatch(s)
|
||||
|
@ -71,7 +71,7 @@ func TestQueue(t *testing.T) {
|
||||
new1 := Download{Id: 1, State: "queued", DownloadProfile: conf.DownloadProfiles[0], Config: conf}
|
||||
new2 := Download{Id: 2, State: "queued", DownloadProfile: conf.DownloadProfiles[0], Config: conf}
|
||||
new3 := Download{Id: 3, State: "queued", DownloadProfile: conf.DownloadProfiles[0], Config: conf}
|
||||
new4 := Download{Id: 4, State: "queued", DownloadProfile: conf.DownloadProfiles[0], Config: conf}
|
||||
new4 := Download{Id: 4, Url: "http://company.org/", State: "queued", DownloadProfile: conf.DownloadProfiles[0], Config: conf}
|
||||
|
||||
dls := Downloads{&new1, &new2, &new3, &new4}
|
||||
dls.StartQueued(1)
|
||||
@ -81,6 +81,9 @@ func TestQueue(t *testing.T) {
|
||||
if dls[1].State != "queued" {
|
||||
t.Error("#2 is not queued")
|
||||
}
|
||||
if dls[3].State == "queued" {
|
||||
t.Error("#4 is not started")
|
||||
}
|
||||
|
||||
// this should start no more, as one is still going
|
||||
dls.StartQueued(1)
|
||||
@ -93,4 +96,9 @@ func TestQueue(t *testing.T) {
|
||||
t.Error("#2 was not started but it should be")
|
||||
}
|
||||
|
||||
dls.StartQueued(2)
|
||||
if dls[3].State == "queued" {
|
||||
t.Error("#4 was not started but it should be")
|
||||
}
|
||||
|
||||
}
|
||||
|
30
main.go
30
main.go
@ -53,6 +53,7 @@ func main() {
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", homeHandler)
|
||||
r.HandleFunc("/static/{filename}", staticHandler)
|
||||
r.HandleFunc("/config", configHandler)
|
||||
r.HandleFunc("/fetch", fetchHandler)
|
||||
r.HandleFunc("/fetch/{id}", fetchHandler)
|
||||
@ -136,6 +137,26 @@ func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// staticHandler handles requests for static files
|
||||
func staticHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
filename := vars["filename"]
|
||||
log.Printf("WOw :%s", filename)
|
||||
if strings.Index(filename, ".js") == len(filename)-3 {
|
||||
f, err := webFS.Open("web/" + filename)
|
||||
if err != nil {
|
||||
log.Printf("error accessing %s - %v", filename, err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.Copy(w, f)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
// configHandler returns the configuration page
|
||||
func configHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -236,6 +257,15 @@ func fetchInfoOneRESTHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(succResB)
|
||||
return
|
||||
}
|
||||
|
||||
if thisReq.Action == "stop" {
|
||||
|
||||
thisDownload.Stop()
|
||||
succRes := successResponse{Success: true, Message: "download stopped"}
|
||||
succResB, _ := json.Marshal(succRes)
|
||||
w.Write(succResB)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// just a get, return the object
|
||||
|
5
web/alpine.min.js
vendored
Normal file
5
web/alpine.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -33,9 +33,9 @@
|
||||
<input type="text" id="config-server-downloadpath" placeholder="path" class="input-long" x-model="config.server.download_path" />
|
||||
<span class="pure-form-message">The path on the server to download files to.</span>
|
||||
|
||||
<label for="config-server-max-downloads">Maximum active downloads</label>
|
||||
<label for="config-server-max-downloads">Maximum active downloads per domain</label>
|
||||
<input type="text" id="config-server-max-downloads" placeholder="2" class="input-long" x-model.number="config.server.maximum_active_downloads_per_domain" />
|
||||
<span class="pure-form-message">How many downloads can be simultaneously active. Use '0' for no limit.</span>
|
||||
<span class="pure-form-message">How many downloads can be simultaneously active. Use '0' for no limit. This limit is applied per domain that you download from.</span>
|
||||
|
||||
<legend>UI</legend>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>gropple</title>
|
||||
<script src="//unpkg.com/alpinejs" defer></script>
|
||||
<script src="/static/alpine.min.js" defer></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.6/build/pure-min.css" integrity="sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.6/build/grids-responsive-min.css">
|
||||
|
@ -18,8 +18,10 @@
|
||||
<tr><th>state</th><td x-text="state"></td></tr>
|
||||
<tr><th>progress</th><td x-text="percent"></td></tr>
|
||||
<tr><th>ETA</th><td x-text="eta"></td></tr>
|
||||
|
||||
</table>
|
||||
<p>You can close this window and your download will continue. Check the <a href="/" target="_gropple_status">Status page</a> to see all downloads in progress.</p>
|
||||
<button x-show="state=='downloading'" class="pure-button" @click="stop()">stop</button>
|
||||
<div>
|
||||
<h4>Logs</h4>
|
||||
<pre x-text="log">
|
||||
@ -50,6 +52,18 @@
|
||||
console.log(info)
|
||||
})
|
||||
},
|
||||
stop() {
|
||||
let op = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({action: 'stop'}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
fetch('/rest/fetch/{{ .dl.Id }}', op)
|
||||
.then(response => response.json())
|
||||
.then(info => {
|
||||
console.log(info)
|
||||
})
|
||||
},
|
||||
fetch_data() {
|
||||
fetch('/rest/fetch/{{ .dl.Id }}')
|
||||
.then(response => response.json())
|
||||
|
Loading…
x
Reference in New Issue
Block a user