12 Commits

10 changed files with 147 additions and 42 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
gropple
release
dist
.env
.env
dist/

View File

@@ -1,13 +1,3 @@
# 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:
hooks:
# You may remove this if you don't use go modules.
@@ -27,7 +17,7 @@ archives:
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}

View File

@@ -2,7 +2,11 @@
All notable changes to this project will be documented in this file.
## [v1.1.1] - 2023-11-26
## [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 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
_, err := exec.LookPath(newConfig.DownloadProfiles[i].Command)
_, err := AbsPathToExecutable(newConfig.DownloadProfiles[i].Command)
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() {
s, err := yaml.Marshal(cs.Config)
if err != nil {
panic(err)
log.Printf("error writing config: %s", err)
os.Exit(1)
}
path := cs.ConfigPath
@@ -337,3 +339,28 @@ func (cs *ConfigService) WriteConfig() {
}
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

@@ -2,6 +2,8 @@ package config
import (
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -172,3 +174,41 @@ func configServiceFromString(configString string) *ConfigService {
}
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)
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)
}
dl.Log = append(dl.Log, fmt.Sprintf("executing: %s with args: %s", dl.DownloadProfile.Command, strings.Join(cmdSlice, " ")))
execAbsolutePath, err := filepath.Abs(dl.DownloadProfile.Command)
cmdPath, err := config.AbsPathToExecutable(dl.DownloadProfile.Command)
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
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()
if err != nil {
@@ -299,7 +304,6 @@ func (dl *Download) Begin() {
dl.FinishedTS = time.Now()
dl.Log = append(dl.Log, fmt.Sprintf("error setting up stdout pipe: %v", err))
dl.Lock.Unlock()
return
}
@@ -371,7 +375,6 @@ func (dl *Download) Begin() {
}
}
dl.Lock.Unlock()
}
// 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)
if err == nil {
dl.Percent = float32(p)
} else {
panic(err)
}
}

View File

@@ -317,7 +317,7 @@ func TestUpdateMetadataSingle(t *testing.T) {
[youtube] 2WoDQBhJCVQ: Downloading android player API JSON
[info] 2WoDQBhJCVQ: Downloading 1 format(s): 137+140
[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 966.50KiB/s ETA 00:13
[download] 0.1% of 12.82MiB at 1.54MiB/s ETA 00:08

View File

@@ -15,7 +15,7 @@ import (
func main() {
versionInfo := &version.Manager{
VersionInfo: version.Info{CurrentVersion: "v1.1.1-alpha.1"},
VersionInfo: version.Info{CurrentVersion: "v1.1.2"},
}
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>
<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>

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")
if err != nil {
panic(err)
log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
type Info struct {
@@ -113,7 +116,10 @@ func homeHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Man
defer dm.Lock.Unlock()
err = t.ExecuteTemplate(w, "layout", info)
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")
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)
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")
b, err := io.ReadAll(r.Body)
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)
@@ -221,7 +236,10 @@ func fetchInfoOneRESTHandler(cs *config.ConfigService, dm *download.Manager) fun
b, err := io.ReadAll(r.Body)
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)
@@ -271,7 +289,10 @@ func fetchInfoRESTHandler(dm *download.Manager) func(w http.ResponseWriter, r *h
b, err := dm.DownloadsAsJSON()
if err != nil {
panic(err)
log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
_, err = w.Write(b)
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")
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()}
err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil {
panic(err)
log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
return
} else if method == "POST" {
@@ -389,13 +416,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")
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()}
err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil {
panic(err)
log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
}
@@ -412,13 +445,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")
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()}
err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil {
panic(err)
log.Printf("error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
return