17 Commits

13 changed files with 195 additions and 68 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.17 go-version: 1.22
- name: Build - name: Build
run: go build -v ./... run: go build -v ./...

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ gropple
release release
dist dist
.env .env
dist/

View File

@@ -1,18 +1,8 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 1
before: before:
hooks: hooks:
# You may remove this if you don't use go modules.
- go mod tidy - go mod tidy
- go test ./... - go test ./...
- golangci-lint run
builds: builds:
- env: - env:
@@ -27,7 +17,7 @@ archives:
# this name template makes the OS and Arch compatible with the results of `uname`. # this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >- name_template: >-
{{ .ProjectName }}_ {{ .ProjectName }}_
{{- title .Os }}_ {{- .Os }}_
{{- if eq .Arch "amd64" }}x86_64 {{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386 {{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }} {{- else }}{{ .Arch }}{{ end }}

View File

@@ -2,7 +2,15 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [v1.1.1] - 2023-11-26 ## [v1.1.3] - 2024-03-17
- Code cleanups, better error checking
## [v1.1.2] - 2024-03-16
- Fix a crash for a certain pattern of log line
## [v1.1.1] - 2023-12-08
- Fix bug where a brand-new config was created with an out-of-date version - Fix bug where a brand-new config was created with an out-of-date version
- Fix for portable mode and using executable in the current working directory - Fix for portable mode and using executable in the current working directory

View File

@@ -189,9 +189,10 @@ func (c *Config) UpdateFromJSON(j []byte) error {
} }
// check the command exists // check the command exists
_, err := exec.LookPath(newConfig.DownloadProfiles[i].Command)
_, err := AbsPathToExecutable(newConfig.DownloadProfiles[i].Command)
if err != nil { if err != nil {
return fmt.Errorf("Could not find %s on the path", newConfig.DownloadProfiles[i].Command) return fmt.Errorf("problem with command '%s': %s", newConfig.DownloadProfiles[i].Command, err)
} }
} }
@@ -318,7 +319,8 @@ func (cs *ConfigService) LoadConfig() error {
func (cs *ConfigService) WriteConfig() { func (cs *ConfigService) WriteConfig() {
s, err := yaml.Marshal(cs.Config) s, err := yaml.Marshal(cs.Config)
if err != nil { if err != nil {
panic(err) log.Printf("error writing config: %s", err)
os.Exit(1)
} }
path := cs.ConfigPath path := cs.ConfigPath
@@ -337,3 +339,28 @@ func (cs *ConfigService) WriteConfig() {
} }
file.Close() file.Close()
} }
// AbsPathToExecutable takes a command name, which may or may not be path-qualified,
// and returns the fully qualified path to it, or an error if could not be found, or
// if it does not appear to be a file.
func AbsPathToExecutable(cmd string) (string, error) {
pathCmd, err := exec.LookPath(cmd)
if err != nil {
return "", fmt.Errorf("could not LookPath '%s': %w", cmd, err)
}
execAbsolutePath, err := filepath.Abs(pathCmd)
if err != nil {
return "", fmt.Errorf("could not get absolute path to '%s': %w", cmd, err)
}
fi, err := os.Stat(execAbsolutePath)
if err != nil {
return "", fmt.Errorf("could not get stat '%s': %w", cmd, err)
}
if !fi.Mode().IsRegular() {
return "", fmt.Errorf("'%s' is not a regular file: %w", cmd, err)
}
return execAbsolutePath, nil
}

View File

@@ -1,7 +1,10 @@
package config package config
import ( import (
"errors"
"os" "os"
"os/exec"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -164,11 +167,54 @@ profiles:
func configServiceFromString(configString string) *ConfigService { func configServiceFromString(configString string) *ConfigService {
tmpFile, _ := os.CreateTemp("", "gropple_test_*.yml") tmpFile, _ := os.CreateTemp("", "gropple_test_*.yml")
tmpFile.Write([]byte(configString)) _, err1 := tmpFile.Write([]byte(configString))
tmpFile.Close() err2 := tmpFile.Close()
if errors.Join(err1, err2) != nil {
panic("got unexpected error")
}
cs := ConfigService{ cs := ConfigService{
Config: &Config{}, Config: &Config{},
ConfigPath: tmpFile.Name(), ConfigPath: tmpFile.Name(),
} }
return &cs return &cs
} }
func TestLookForExecutable(t *testing.T) {
cmdPath, err := exec.LookPath("sleep")
if err != nil {
t.Errorf("cannot run this test without knowing about sleep: %s", err)
t.FailNow()
}
cmdDir := filepath.Dir(cmdPath)
cmd := "sleep"
path, err := AbsPathToExecutable(cmd)
if assert.NoError(t, err) {
assert.Equal(t, cmdPath, path)
}
cmd = cmdPath
path, err = AbsPathToExecutable(cmd)
if assert.NoError(t, err) {
assert.Equal(t, cmdPath, path)
}
cmd = "../../../../../../../../.." + cmdPath
path, err = AbsPathToExecutable(cmd)
if assert.NoError(t, err) {
assert.Equal(t, cmdPath, path)
}
cmd = "./sleep"
_, err = AbsPathToExecutable(cmd)
assert.Error(t, err)
os.Chdir(cmdDir) //nolint
cmd = "./sleep"
path, err = AbsPathToExecutable(cmd)
if assert.NoError(t, err) {
assert.Equal(t, cmdPath, path)
}
}

View File

@@ -281,16 +281,21 @@ func (dl *Download) Begin() {
cmdSlice = append(cmdSlice, dl.Url) cmdSlice = append(cmdSlice, dl.Url)
} }
dl.Log = append(dl.Log, fmt.Sprintf("executing: %s with args: %s", dl.DownloadProfile.Command, strings.Join(cmdSlice, " "))) cmdPath, err := config.AbsPathToExecutable(dl.DownloadProfile.Command)
execAbsolutePath, err := filepath.Abs(dl.DownloadProfile.Command)
if err != nil { if err != nil {
panic(err) dl.State = STATE_FAILED
dl.Finished = true
dl.FinishedTS = time.Now()
dl.Log = append(dl.Log, fmt.Sprintf("error finding executable for downloader: %s", err.Error()))
dl.Lock.Unlock()
return
} }
cmd := exec.Command(execAbsolutePath, cmdSlice...) dl.Log = append(dl.Log, fmt.Sprintf("executing: %s (%s) with args: %s", dl.DownloadProfile.Command, cmdPath, strings.Join(cmdSlice, " ")))
cmd := exec.Command(cmdPath, cmdSlice...)
cmd.Dir = dl.Config.Server.DownloadPath cmd.Dir = dl.Config.Server.DownloadPath
log.Printf("Executing command: %v (executable: %s) in %s", cmd, execAbsolutePath, dl.Config.Server.DownloadPath) log.Printf("Executing command executable: %s) in %s", cmdPath, dl.Config.Server.DownloadPath)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
@@ -299,7 +304,6 @@ func (dl *Download) Begin() {
dl.FinishedTS = time.Now() dl.FinishedTS = time.Now()
dl.Log = append(dl.Log, fmt.Sprintf("error setting up stdout pipe: %v", err)) dl.Log = append(dl.Log, fmt.Sprintf("error setting up stdout pipe: %v", err))
dl.Lock.Unlock() dl.Lock.Unlock()
return return
} }
@@ -371,7 +375,6 @@ func (dl *Download) Begin() {
} }
} }
dl.Lock.Unlock() dl.Lock.Unlock()
} }
// updateDownload updates the download based on data from the reader. Expects the // updateDownload updates the download based on data from the reader. Expects the
@@ -426,8 +429,6 @@ func (dl *Download) updateMetadata(s string) {
p, err := strconv.ParseFloat(matches[1], 32) p, err := strconv.ParseFloat(matches[1], 32)
if err == nil { if err == nil {
dl.Percent = float32(p) dl.Percent = float32(p)
} else {
panic(err)
} }
} }

View File

@@ -21,11 +21,11 @@ func TestUpdateMetadata(t *testing.T) {
// eta's might be xx:xx:xx or xx:xx // eta's might be xx:xx:xx or xx:xx
newD.updateMetadata("[download] 0.0% of 504.09MiB at 135.71KiB/s ETA 01:03:36") newD.updateMetadata("[download] 0.0% of 504.09MiB at 135.71KiB/s ETA 01:03:36")
if newD.Eta != "01:03:36" { if newD.Eta != "01:03:36" {
t.Fatalf("bad long eta in dl\n%#v", newD) t.Fatalf("bad long eta in dl\n%#v", newD) //nolint
} }
newD.updateMetadata("[download] 0.0% of 504.09MiB at 397.98KiB/s ETA 21:38") newD.updateMetadata("[download] 0.0% of 504.09MiB at 397.98KiB/s ETA 21:38")
if newD.Eta != "21:38" { if newD.Eta != "21:38" {
t.Fatalf("bad short eta in dl\n%#v", newD) t.Fatalf("bad short eta in dl\n%#v", newD) //nolint
} }
// added a new file, now we are tracking two // added a new file, now we are tracking two
@@ -44,7 +44,7 @@ func TestUpdateMetadata(t *testing.T) {
// different download // different download
newD.updateMetadata("[download] 99.3% of ~1.42GiB at 320.87KiB/s ETA 00:07 (frag 212/214)") newD.updateMetadata("[download] 99.3% of ~1.42GiB at 320.87KiB/s ETA 00:07 (frag 212/214)")
if newD.Eta != "00:07" { if newD.Eta != "00:07" {
t.Fatalf("bad short eta in dl with frag\n%v", newD) t.Fatalf("bad short eta in dl with frag\n%v", newD) //nolint
} }
// [FixupM3u8] Fixing MPEG-TS in MP4 container of "file [-168849776_456239489].mp4" // [FixupM3u8] Fixing MPEG-TS in MP4 container of "file [-168849776_456239489].mp4"
@@ -317,7 +317,7 @@ func TestUpdateMetadataSingle(t *testing.T) {
[youtube] 2WoDQBhJCVQ: Downloading android player API JSON [youtube] 2WoDQBhJCVQ: Downloading android player API JSON
[info] 2WoDQBhJCVQ: Downloading 1 format(s): 137+140 [info] 2WoDQBhJCVQ: Downloading 1 format(s): 137+140
[info] Writing video metadata as JSON to: The Greatest Shot In Television [2WoDQBhJCVQ].info.json [info] Writing video metadata as JSON to: The Greatest Shot In Television [2WoDQBhJCVQ].info.json
[download] Destination: The Greatest Shot In Television [2WoDQBhJCVQ].f137.mp4 [debug] Invoking hlsnative downloader on "https://example.org/urls/1.2.3.4%
[download] 0.0% of 12.82MiB at 510.94KiB/s ETA 00:26 [download] 0.0% of 12.82MiB at 510.94KiB/s ETA 00:26
[download] 0.0% of 12.82MiB at 966.50KiB/s ETA 00:13 [download] 0.0% of 12.82MiB at 966.50KiB/s ETA 00:13
[download] 0.1% of 12.82MiB at 1.54MiB/s ETA 00:08 [download] 0.1% of 12.82MiB at 1.54MiB/s ETA 00:08

6
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/tardisx/gropple module github.com/tardisx/gropple
go 1.20 go 1.22
require ( require (
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
golang.org/x/mod v0.14.0 golang.org/x/mod v0.16.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )

8
go.sum
View File

@@ -4,10 +4,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@@ -15,7 +15,7 @@ import (
func main() { func main() {
versionInfo := &version.Manager{ versionInfo := &version.Manager{
VersionInfo: version.Info{CurrentVersion: "v1.1.1-alpha.1"}, VersionInfo: version.Info{CurrentVersion: "v1.1.3"},
} }
log.Printf("Starting gropple %s - https://github.com/tardisx/gropple", versionInfo.GetInfo().CurrentVersion) log.Printf("Starting gropple %s - https://github.com/tardisx/gropple", versionInfo.GetInfo().CurrentVersion)

View File

@@ -84,7 +84,10 @@
<label x-bind:for="'config-profiles-'+i+'-command'">Command to run</label> <label x-bind:for="'config-profiles-'+i+'-command'">Command to run</label>
<input type="text" x-bind:id="'config-profiles-'+i+'-command'" class="input-long" placeholder="name" x-model="profile.command" /> <input type="text" x-bind:id="'config-profiles-'+i+'-command'" class="input-long" placeholder="name" x-model="profile.command" />
<span class="pure-form-message">Which command to run. Your path will be searched, or you can specify the full path here.</span> <span class="pure-form-message">Which command to run. Your path will be searched, or you can specify the full path here.
If you are using gropple in portable mode and store the executables with the gropple executable, use a prefix of
<tt>./</tt>, for instance <tt>yt-dlp.exe</tt>.
</span>
<label>Arguments</label> <label>Arguments</label>

View File

@@ -92,7 +92,10 @@ func homeHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/index.tmpl") t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/index.tmpl")
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
type Info struct { type Info struct {
@@ -113,7 +116,10 @@ func homeHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
defer dm.Lock.Unlock() defer dm.Lock.Unlock()
err = t.ExecuteTemplate(w, "layout", info) err = t.ExecuteTemplate(w, "layout", info)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
} }
} }
@@ -149,12 +155,18 @@ func configHandler() func(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/config.tmpl") t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/config.tmpl")
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
err = t.ExecuteTemplate(w, "layout", nil) err = t.ExecuteTemplate(w, "layout", nil)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
} }
} }
@@ -167,7 +179,10 @@ func configRESTHandler(cs *config.ConfigService) func(w http.ResponseWriter, r *
log.Printf("Updating config") log.Printf("Updating config")
b, err := io.ReadAll(r.Body) b, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
err = cs.Config.UpdateFromJSON(b) err = cs.Config.UpdateFromJSON(b)
@@ -221,7 +236,10 @@ func fetchInfoOneRESTHandler(cs *config.ConfigService, dm *download.Manager) fun
b, err := io.ReadAll(r.Body) b, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
err = json.Unmarshal(b, &thisReq) err = json.Unmarshal(b, &thisReq)
@@ -271,7 +289,10 @@ func fetchInfoRESTHandler(dm *download.Manager) func(w http.ResponseWriter, r *h
b, err := dm.DownloadsAsJSON() b, err := dm.DownloadsAsJSON()
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
_, err = w.Write(b) _, err = w.Write(b)
if err != nil { if err != nil {
@@ -307,14 +328,20 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/popup.tmpl") t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/popup.tmpl")
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
templateData := map[string]interface{}{"dl": dl, "config": cs.Config, "canStop": download.CanStopDownload, "Version": vm.GetInfo()} templateData := map[string]interface{}{"dl": dl, "config": cs.Config, "canStop": download.CanStopDownload, "Version": vm.GetInfo()}
err = t.ExecuteTemplate(w, "layout", templateData) err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
return return
} else if method == "POST" { } else if method == "POST" {
@@ -326,23 +353,29 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
} }
req := reqType{} req := reqType{}
json.NewDecoder(r.Body).Decode(&req) err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("error decoding body of request: %s", err)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
log.Printf("popup POST request: %#v", req) log.Printf("popup POST request: %#v", req)
if req.URL == "" { if req.URL == "" {
w.WriteHeader(400) w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: "No URL supplied", Error: "No URL supplied",
}) })
return return
} else { } else {
if req.ProfileChosen == "" { if req.ProfileChosen == "" {
w.WriteHeader(400) w.WriteHeader(400)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: "you must choose a profile", Error: "you must choose a profile",
}) })
@@ -352,7 +385,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
profile := cs.Config.ProfileCalled(req.ProfileChosen) profile := cs.Config.ProfileCalled(req.ProfileChosen)
if profile == nil { if profile == nil {
w.WriteHeader(400) w.WriteHeader(400)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen), Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen),
}) })
@@ -370,7 +403,7 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
dm.Queue(newDL) dm.Queue(newDL)
w.WriteHeader(200) w.WriteHeader(200)
json.NewEncoder(w).Encode(queuedResponse{ _ = json.NewEncoder(w).Encode(queuedResponse{
Success: true, Success: true,
Location: fmt.Sprintf("/fetch/%d", id), Location: fmt.Sprintf("/fetch/%d", id),
}) })
@@ -389,13 +422,19 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/popup_create.tmpl") t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/popup_create.tmpl")
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
templateData := map[string]interface{}{"config": cs.Config, "url": url[0], "Version": vm.GetInfo()} templateData := map[string]interface{}{"config": cs.Config, "url": url[0], "Version": vm.GetInfo()}
err = t.ExecuteTemplate(w, "layout", templateData) err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
} }
@@ -412,13 +451,19 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/bulk.tmpl") t, err := template.ParseFS(webFS, "data/templates/layout.tmpl", "data/templates/menu.tmpl", "data/templates/bulk.tmpl")
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
templateData := map[string]interface{}{"config": cs.Config, "Version": vm.GetInfo()} templateData := map[string]interface{}{"config": cs.Config, "Version": vm.GetInfo()}
err = t.ExecuteTemplate(w, "layout", templateData) err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil { if err != nil {
panic(err) log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
} }
return return
@@ -431,13 +476,19 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
} }
req := reqBulkType{} req := reqBulkType{}
json.NewDecoder(r.Body).Decode(&req) err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("error decoding request body: %s", err)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
log.Printf("bulk POST request: %#v", req) log.Printf("bulk POST request: %#v", req)
if req.URLs == "" { if req.URLs == "" {
w.WriteHeader(400) w.WriteHeader(400)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: "No URLs supplied", Error: "No URLs supplied",
}) })
@@ -447,7 +498,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
if req.ProfileChosen == "" { if req.ProfileChosen == "" {
w.WriteHeader(400) w.WriteHeader(400)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: "you must choose a profile", Error: "you must choose a profile",
}) })
@@ -457,7 +508,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
profile := cs.Config.ProfileCalled(req.ProfileChosen) profile := cs.Config.ProfileCalled(req.ProfileChosen)
if profile == nil { if profile == nil {
w.WriteHeader(400) w.WriteHeader(400)
json.NewEncoder(w).Encode(errorResponse{ _ = json.NewEncoder(w).Encode(errorResponse{
Success: false, Success: false,
Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen), Error: fmt.Sprintf("no such profile: '%s'", req.ProfileChosen),
}) })
@@ -483,7 +534,7 @@ func bulkHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
} }
w.WriteHeader(200) w.WriteHeader(200)
json.NewEncoder(w).Encode(successResponse{ _ = json.NewEncoder(w).Encode(successResponse{
Success: true, Success: true,
Message: fmt.Sprintf("queued %d downloads", count), Message: fmt.Sprintf("queued %d downloads", count),
}) })