Compare commits
12 Commits
v1.1.0-alp
...
v1.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b23ff356c | |||
| 94b57fc327 | |||
| 36607b43ab | |||
| b466157cd0 | |||
| d9a979b782 | |||
| 3dec93c4f4 | |||
| 3353d3d923 | |||
| 7b326d72b1 | |||
| bef753d7ee | |||
| a66ab08431 | |||
| b0048a5764 | |||
| 73833a1a14 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ gropple
|
|||||||
release
|
release
|
||||||
dist
|
dist
|
||||||
.env
|
.env
|
||||||
|
dist/
|
||||||
|
|||||||
@@ -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:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
# You may remove this if you don't use go modules.
|
# 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`.
|
# 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 }}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
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.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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -172,3 +174,41 @@ func configServiceFromString(configString string) *ConfigService {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
|
cmd = "./sleep"
|
||||||
|
path, err = AbsPathToExecutable(cmd)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, cmdPath, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -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.2"},
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
65
web/web.go
65
web/web.go
@@ -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" {
|
||||||
@@ -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")
|
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 +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")
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user