Destinations are now DownloadOptions
This commit is contained in:
parent
fa978fecc2
commit
a1e6421842
@ -27,6 +27,12 @@ type DownloadProfile struct {
|
||||
Args []string `yaml:"args" json:"args"`
|
||||
}
|
||||
|
||||
// DownloadOption contains configuration for extra arguments to pass to the download command
|
||||
type DownloadOption struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Args []string `yaml:"args" json:"args"`
|
||||
}
|
||||
|
||||
// UI holds the configuration for the user interface
|
||||
type UI struct {
|
||||
PopupWidth int `yaml:"popup_width" json:"popup_width"`
|
||||
@ -44,8 +50,9 @@ type Config struct {
|
||||
ConfigVersion int `yaml:"config_version" json:"config_version"`
|
||||
Server Server `yaml:"server" json:"server"`
|
||||
UI UI `yaml:"ui" json:"ui"`
|
||||
Destinations []Destination `yaml:"destinations" json:"destinations"`
|
||||
Destinations []Destination `yaml:"destinations" json:"destinations"` // no longer in use, see DownloadOptions
|
||||
DownloadProfiles []DownloadProfile `yaml:"profiles" json:"profiles"`
|
||||
DownloadOptions []DownloadOption `yaml:"download_options" json:"download_options"`
|
||||
}
|
||||
|
||||
// ConfigService is a struct to handle configuration requests, allowing for the
|
||||
@ -88,7 +95,8 @@ func (cs *ConfigService) LoadDefaultConfig() {
|
||||
|
||||
defaultConfig.Server.MaximumActiveDownloads = 2
|
||||
|
||||
defaultConfig.Destinations = make([]Destination, 0)
|
||||
defaultConfig.Destinations = nil
|
||||
defaultConfig.DownloadOptions = make([]DownloadOption, 0)
|
||||
|
||||
defaultConfig.ConfigVersion = 3
|
||||
|
||||
@ -96,7 +104,7 @@ func (cs *ConfigService) LoadDefaultConfig() {
|
||||
|
||||
}
|
||||
|
||||
// ProfileCalled returns the corresponding profile, or nil if it does not exist
|
||||
// ProfileCalled returns the corresponding DownloadProfile, or nil if it does not exist
|
||||
func (c *Config) ProfileCalled(name string) *DownloadProfile {
|
||||
for _, p := range c.DownloadProfiles {
|
||||
if p.Name == name {
|
||||
@ -106,11 +114,11 @@ func (c *Config) ProfileCalled(name string) *DownloadProfile {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DestinationCalled returns the corresponding destination, or nil if it does not exist
|
||||
func (c *Config) DestinationCalled(name string) *Destination {
|
||||
for _, p := range c.Destinations {
|
||||
if p.Name == name {
|
||||
return &p
|
||||
// DownloadOptionCalled returns the corresponding DownloadOption, or nil if it does not exist
|
||||
func (c *Config) DownloadOptionCalled(name string) *DownloadOption {
|
||||
for _, o := range c.DownloadOptions {
|
||||
if o.Name == name {
|
||||
return &o
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -187,17 +195,6 @@ func (c *Config) UpdateFromJSON(j []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
// check destinations
|
||||
for _, dest := range newConfig.Destinations {
|
||||
s, err := os.Stat(dest.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destination '%s' (%s) is bad: %s", dest.Name, dest.Path, err)
|
||||
}
|
||||
if !s.IsDir() {
|
||||
return fmt.Errorf("destination '%s' (%s) is not a directory", dest.Name, dest.Path)
|
||||
}
|
||||
}
|
||||
|
||||
*c = newConfig
|
||||
return nil
|
||||
}
|
||||
@ -295,6 +292,20 @@ func (cs *ConfigService) LoadConfig() error {
|
||||
log.Print("migrated config from version 2 => 3")
|
||||
}
|
||||
|
||||
if c.ConfigVersion == 3 {
|
||||
c.ConfigVersion = 4
|
||||
for i := range c.Destinations {
|
||||
newDownloadOption := DownloadOption{
|
||||
Name: c.Destinations[i].Name,
|
||||
Args: []string{"-o", fmt.Sprintf("%s/%%(title)s [%%(id)s].%%(ext)s", c.Destinations[i].Path)},
|
||||
}
|
||||
c.DownloadOptions = append(c.DownloadOptions, newDownloadOption)
|
||||
c.Destinations = nil
|
||||
}
|
||||
configMigrated = true
|
||||
log.Print("migrated config from version 3 => 4")
|
||||
}
|
||||
|
||||
if configMigrated {
|
||||
log.Print("Writing new config after version migration")
|
||||
cs.WriteConfig()
|
||||
|
@ -3,10 +3,12 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrationV1toV3(t *testing.T) {
|
||||
v2Config := `config_version: 1
|
||||
func TestMigrationV1toV4(t *testing.T) {
|
||||
v1Config := `config_version: 1
|
||||
server:
|
||||
port: 6123
|
||||
address: http://localhost:6123
|
||||
@ -31,12 +33,12 @@ profiles:
|
||||
- --audio-format
|
||||
- mp3
|
||||
`
|
||||
cs := configServiceFromString(v2Config)
|
||||
cs := configServiceFromString(v1Config)
|
||||
err := cs.LoadConfig()
|
||||
if err != nil {
|
||||
t.Errorf("got error when loading config: %s", err)
|
||||
}
|
||||
if cs.Config.ConfigVersion != 3 {
|
||||
if cs.Config.ConfigVersion != 4 {
|
||||
t.Errorf("did not migrate version (it is '%d')", cs.Config.ConfigVersion)
|
||||
}
|
||||
if cs.Config.Server.MaximumActiveDownloads != 2 {
|
||||
@ -48,6 +50,58 @@ profiles:
|
||||
os.Remove(cs.ConfigPath)
|
||||
}
|
||||
|
||||
func TestMigrateV3toV4(t *testing.T) {
|
||||
v3Config := `config_version: 3
|
||||
server:
|
||||
port: 6123
|
||||
address: http://localhost:6123
|
||||
download_path: /tmp/Downloads
|
||||
maximum_active_downloads_per_domain: 2
|
||||
ui:
|
||||
popup_width: 900
|
||||
popup_height: 900
|
||||
destinations:
|
||||
- name: cool destination
|
||||
path: /tmp/coolness
|
||||
profiles:
|
||||
- name: standard video
|
||||
command: yt-dlp
|
||||
args:
|
||||
- --newline
|
||||
- --write-info-json
|
||||
- -f
|
||||
- bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best
|
||||
- name: standard mp3
|
||||
command: yt-dlp
|
||||
args:
|
||||
- --newline
|
||||
- --write-info-json
|
||||
- --extract-audio
|
||||
- --audio-format
|
||||
- mp3`
|
||||
cs := configServiceFromString(v3Config)
|
||||
err := cs.LoadConfig()
|
||||
if err != nil {
|
||||
t.Errorf("got error when loading config: %s", err)
|
||||
}
|
||||
if cs.Config.ConfigVersion != 4 {
|
||||
t.Errorf("did not migrate version (it is '%d')", cs.Config.ConfigVersion)
|
||||
}
|
||||
if cs.Config.Server.MaximumActiveDownloads != 2 {
|
||||
t.Error("did not add MaximumActiveDownloads")
|
||||
}
|
||||
if len(cs.Config.Destinations) != 0 {
|
||||
t.Error("incorrect number of destinations from migrated file")
|
||||
}
|
||||
if assert.Len(t, cs.Config.DownloadOptions, 1) {
|
||||
if assert.Len(t, cs.Config.DownloadOptions[0].Args, 2) {
|
||||
assert.Equal(t, "-o", cs.Config.DownloadOptions[0].Args[0])
|
||||
assert.Equal(t, "/tmp/coolness/%(title)s [%(id)s].%(ext)s", cs.Config.DownloadOptions[0].Args[1])
|
||||
}
|
||||
}
|
||||
os.Remove(cs.ConfigPath)
|
||||
}
|
||||
|
||||
func configServiceFromString(configString string) *ConfigService {
|
||||
tmpFile, _ := os.CreateTemp("", "gropple_test_*.yml")
|
||||
tmpFile.Write([]byte(configString))
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -27,7 +26,7 @@ type Download struct {
|
||||
ExitCode int `json:"exit_code"`
|
||||
State State `json:"state"`
|
||||
DownloadProfile config.DownloadProfile `json:"download_profile"`
|
||||
Destination *config.Destination `json:"destination"`
|
||||
DownloadOption *config.DownloadOption `json:"download_option"`
|
||||
Finished bool `json:"finished"`
|
||||
FinishedTS time.Time `json:"finished_ts"`
|
||||
Files []string `json:"files"`
|
||||
@ -82,7 +81,6 @@ func (m *Manager) ManageQueue() {
|
||||
m.Lock.Lock()
|
||||
|
||||
m.startQueued(m.MaxPerDomain)
|
||||
m.moveToDest()
|
||||
m.cleanup()
|
||||
m.Lock.Unlock()
|
||||
|
||||
@ -102,32 +100,6 @@ func (m *Manager) DownloadsAsJSON() ([]byte, error) {
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (m *Manager) moveToDest() {
|
||||
|
||||
// move any downloads that are complete and have a dest
|
||||
for _, dl := range m.Downloads {
|
||||
|
||||
dl.Lock.Lock()
|
||||
if dl.Destination != nil && dl.State == STATE_COMPLETE {
|
||||
dl.State = STATE_MOVED
|
||||
for _, fn := range dl.Files {
|
||||
src := filepath.Join(dl.Config.Server.DownloadPath, fn)
|
||||
dst := filepath.Join(dl.Destination.Path, fn)
|
||||
err := os.Rename(src, dst)
|
||||
if err != nil {
|
||||
log.Printf("%s", err)
|
||||
dl.Log = append(dl.Log, fmt.Sprintf("Could not move %s to %s - %s", fn, dl.Destination.Path, err))
|
||||
break
|
||||
} else {
|
||||
dl.Log = append(dl.Log, fmt.Sprintf("Moved %s to %s", fn, dl.Destination.Path))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
dl.Lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// startQueued starts any downloads that have been queued, we would not exceed
|
||||
// maxRunning. If maxRunning is 0, there is no limit.
|
||||
func (m *Manager) startQueued(maxRunning int) {
|
||||
@ -202,17 +174,6 @@ func (m *Manager) Queue(dl *Download) {
|
||||
dl.State = STATE_QUEUED
|
||||
}
|
||||
|
||||
func (m *Manager) ChangeDestination(dl *Download, dest *config.Destination) {
|
||||
dl.Lock.Lock()
|
||||
// we can only change destination is certain cases...
|
||||
if dl.State != STATE_FAILED && dl.State != STATE_MOVED {
|
||||
dl.Destination = dest
|
||||
}
|
||||
|
||||
dl.Lock.Unlock()
|
||||
|
||||
}
|
||||
|
||||
func NewDownload(url string, conf *config.Config) *Download {
|
||||
atomic.AddInt32(&downloadId, 1)
|
||||
dl := Download{
|
||||
|
@ -237,15 +237,15 @@ func TestUpdateMetadataPlaylist(t *testing.T) {
|
||||
|
||||
output := `
|
||||
start of log...
|
||||
[download] Downloading playlist: niceuser
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 1
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 2
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 3
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 4
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 5
|
||||
[RedGifsUser] niceuser: Downloading JSON metadata page 6
|
||||
[info] Writing playlist metadata as JSON to: niceuser [niceuser].info.json
|
||||
[RedGifsUser] playlist niceuser: Downloading 3 videos
|
||||
[download] Downloading playlist: nice_user
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 1
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 2
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 3
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 4
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 5
|
||||
[RedGifsUser] nice_user: Downloading JSON metadata page 6
|
||||
[info] Writing playlist metadata as JSON to: nice_user [nice_user].info.json
|
||||
[RedGifsUser] playlist nice_user: Downloading 3 videos
|
||||
[download] Downloading video 1 of 3
|
||||
[info] wrongpreciouschrysomelid: Downloading 1 format(s): hd
|
||||
[info] Writing video metadata as JSON to: Splendid Wonderful Speaker Power Chocolate Drop [wrongpreciouschrysomelid].info.json
|
||||
@ -279,8 +279,8 @@ start of log...
|
||||
[download] 69.1% of 2.89MiB at 11.63MiB/s ETA 00:00
|
||||
[download] 100% of 2.89MiB at 14.25MiB/s ETA 00:00
|
||||
[download] 100% of 2.89MiB in 00:00
|
||||
[info] Writing updated playlist metadata as JSON to: niceuser [niceuser].info.json
|
||||
[download] Finished downloading playlist: niceuser
|
||||
[info] Writing updated playlist metadata as JSON to: nice_user [nice_user].info.json
|
||||
[download] Finished downloading playlist: nice_user
|
||||
`
|
||||
newD := Download{}
|
||||
|
||||
|
7
go.mod
7
go.mod
@ -4,6 +4,13 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/mod v0.14.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
8
go.sum
8
go.sum
@ -1,8 +1,16 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -11,14 +11,14 @@
|
||||
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1">
|
||||
<button class="pure-button pure-button-primary" @click="save_config();" href="#">Save Config</button>
|
||||
<button class="button-small pure-button button-small pure-button-primary" @click="save_config();" href="#">Save Config</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-g">
|
||||
|
||||
<div class="pure-u-lg-1-3 pure-u-1 l-box">
|
||||
|
||||
|
||||
<form class="pure-form pure-form-stacked gropple-config">
|
||||
<fieldset>
|
||||
|
||||
@ -65,8 +65,8 @@
|
||||
|
||||
<legend>Download Profiles</legend>
|
||||
|
||||
<p>Gropple supports multiple download profiles. Each profile specifies a different youtube-dl
|
||||
compatible command, and arguments. When starting a download, you may choose which profile
|
||||
<p>Gropple supports multiple download profiles. Each profile specifies a different youtube-dl
|
||||
compatible command, and arguments. When starting a download, you may choose which profile
|
||||
to use. The URL will be appended to the argument list at the end.
|
||||
</p>
|
||||
|
||||
@ -75,10 +75,10 @@
|
||||
<template x-for="(profile, i) in config.profiles">
|
||||
<div>
|
||||
<label x-bind:for="'config-profiles-'+i+'-name'">Name of profile <span x-text="i+1"></span>
|
||||
</label>
|
||||
|
||||
</label>
|
||||
|
||||
<input type="text" x-bind:id="'config-profiles-'+i+'-name'" class="input-long" placeholder="name" x-model="profile.name" />
|
||||
<button class="pure-button button-del" href="#" @click.prevent="config.profiles.splice(i, 1);;">delete profile</button>
|
||||
<button class="button-small pure-button button-del" href="#" @click.prevent="config.profiles.splice(i, 1);;">delete profile</button>
|
||||
|
||||
<span class="pure-form-message">The name of this profile. For your information only.</span>
|
||||
|
||||
@ -92,11 +92,11 @@
|
||||
<template x-for="(arg, j) in profile.args">
|
||||
<div>
|
||||
<input type="text" x-bind:id="'config-profiles-'+i+'-arg-'+j" placeholder="arg" x-model="profile.args[j]" />
|
||||
<button class="pure-button button-del" href="#" @click.prevent="profile.args.splice(j, 1);;">delete arg</button>
|
||||
<button class="button-small pure-button button-del" href="#" @click.prevent="profile.args.splice(j, 1);;">delete arg</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button class="pure-button button-add" href="#" @click.prevent="profile.args.push('');">add arg</button>
|
||||
<button class="button-small pure-button button-add" href="#" @click.prevent="profile.args.push('');">add arg</button>
|
||||
<span class="pure-form-message">Arguments for the command. Note that the shell is not used, so there is no need to quote or escape arguments, including those with spaces.</span>
|
||||
|
||||
<hr>
|
||||
@ -104,7 +104,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button class="pure-button button-add" href="#" @click.prevent="config.profiles.push({name: 'new profile', command: 'youtube-dl', args: []});">add profile</button>
|
||||
<button class="button-small pure-button button-add" href="#" @click.prevent="config.profiles.push({name: 'new profile', command: 'youtube-dl', args: []});">add profile</button>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
@ -113,39 +113,44 @@
|
||||
<div class="pure-u-lg-1-3 pure-u-1 l-box">
|
||||
<form class="pure-form gropple-config">
|
||||
<fieldset>
|
||||
<legend>Destinations</legend>
|
||||
<p>You can specify custom destinations (directories) here. Downloads can be
|
||||
moved to one of these directories after completion from the index page,
|
||||
if you do not want them to be left in the download path above.</p>
|
||||
<legend>Download Options</legend>
|
||||
<p>You can specify custom download options here. These are (optionally) selectable in addition
|
||||
to the profile when starting a download. They append extra arguments to the downloader command.
|
||||
The most common use is to specify a particular <tt>-o</tt> argument to <tt>yt-dlp</tt> to allow files to be downloaded
|
||||
to a custom path.</p>
|
||||
</p>
|
||||
<template x-for="(dest, i) in config.destinations">
|
||||
<template x-for="(download_option, i) in config.download_options">
|
||||
<div>
|
||||
<label x-bind:for="'config-destinations-'+i+'-name'">Name of destination <span x-text="i+1"></span>
|
||||
</label>
|
||||
|
||||
<input type="text" x-bind:id="'config-destinations-'+i+'-name'" class="input-long" placeholder="name" x-model="dest.name" />
|
||||
<label x-bind:for="'config-download-option-'+i+'-name'">Name of option <span x-text="i+1"></span>
|
||||
</label>
|
||||
|
||||
<span class="pure-form-message">The name of this destination. For your information only.</span>
|
||||
<input type="text" x-bind:id="'config-download-option-'+i+'-name'" class="input-long" placeholder="name" x-model="download_option.name" />
|
||||
|
||||
<label x-bind:for="'config-destinations-'+i+'-command'">Path</label>
|
||||
<input type="text" x-bind:id="'config-destinations-'+i+'-command'" class="input-long" placeholder="name" x-model="dest.path" />
|
||||
<span class="pure-form-message">Path to move completed downloads to.</span>
|
||||
<span class="pure-form-message">The name of this option. For your information only.</span>
|
||||
|
||||
<button class="pure-button button-del" href="#" @click.prevent="config.destinations.splice(i, 1);">delete destination</button>
|
||||
<label>Arguments</label>
|
||||
|
||||
<template x-for="(arg, j) in download_option.args">
|
||||
<div>
|
||||
<input type="text" x-bind:id="'config-download-option-'+i+'-arg-'+j" placeholder="arg" x-model="download_option.args[j]" />
|
||||
<button class="button-small pure-button button-del" href="#" @click.prevent="download_option.args.splice(j, 1);;">delete arg</button>
|
||||
</div>
|
||||
</template>
|
||||
<button class="button-small pure-button button-del" href="#" @click.prevent="config.download_options.splice(i, 1);">delete option</button>
|
||||
|
||||
<hr>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button class="pure-button button-add" href="#" @click.prevent="config.destinations.push({name: 'new destination', path: '/tmp'});">add destination</button>
|
||||
<button class="button-small pure-button button-add" href="#" @click.prevent="config.download_options.push({name: 'new option', args: ['-o', 'someting']});">add option</button>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1">
|
||||
<button class="pure-button pure-button-primary" @click="save_config();" href="#">Save Config</button>
|
||||
<button class="button-small pure-button button-small pure-button-primary" @click="save_config();" href="#">Save Config</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -157,8 +162,8 @@
|
||||
{{ define "js" }}
|
||||
<script>
|
||||
function config() {
|
||||
return {
|
||||
config: { server : {}, ui : {}, profiles: [], destinations: []},
|
||||
return {
|
||||
config: { server : {}, ui : {}, profiles: [], download_options: []},
|
||||
error_message: '',
|
||||
success_message: '',
|
||||
|
||||
|
@ -24,13 +24,23 @@
|
||||
<table class="pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th><th>filename</th><th>url</th><th>show</th><th>state</th><th>percent</th><th>eta</th><th>finished</th>
|
||||
<th>id</th>
|
||||
<th>filename</th>
|
||||
<th>url</th>
|
||||
<th>state</th>
|
||||
<th>percent</th>
|
||||
<th>eta</th>
|
||||
<th>finished</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="item in items">
|
||||
<tr>
|
||||
<td x-text="item.id"></td>
|
||||
<td>
|
||||
<a class="int-link" @click="show_popup(item)" href="#">
|
||||
<span x-text="item.id">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span x-show="item.files && item.files.length > 0">
|
||||
<ul>
|
||||
@ -43,18 +53,17 @@
|
||||
x-text="item.url">
|
||||
</span>
|
||||
</td>
|
||||
<td><a class="int-link" x-bind:href="item.url">↗</a></td>
|
||||
<td><a class="int-link" @click="show_popup(item)" href="#">📄</a></td>
|
||||
<td><a class="int-link" x-bind:href="item.url">🔗</a></td>
|
||||
<td :class="'state-'+item.state" x-text="item.state"></td>
|
||||
<td x-text="item.percent"></td>
|
||||
<td x-text="item.eta"></td>
|
||||
<td x-text="item.finished ? '✔' : '-'"></td>
|
||||
</tr>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
@ -62,7 +71,7 @@
|
||||
{{ define "js" }}
|
||||
<script>
|
||||
function index() {
|
||||
return {
|
||||
return {
|
||||
items: [], version: {}, popups: {},
|
||||
fetch_version() {
|
||||
fetch('/rest/version')
|
||||
|
@ -5,9 +5,24 @@
|
||||
<title>gropple</title>
|
||||
<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">
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/grids-responsive-min.css">
|
||||
<style>
|
||||
|
||||
:root {
|
||||
font-family: Inter, sans-serif;
|
||||
font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */
|
||||
}
|
||||
@supports (font-variation-settings: normal) {
|
||||
:root { font-family: InterVariable, sans-serif; }
|
||||
}
|
||||
|
||||
.button-small {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.pure-g > div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@ -45,9 +60,6 @@
|
||||
.gropple-config input.input-long {
|
||||
width: 27em;
|
||||
}
|
||||
.gropple-config button {
|
||||
border-radius: 12px;
|
||||
}
|
||||
.gropple-config button.button-del {
|
||||
background: rgb(202, 60, 60);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div id="layout" class="pure-g pure-u-1" x-data="popup()" x-init="fetch_data()">
|
||||
<h2>Download started</h2>
|
||||
<p>Fetching <tt>{{ .dl.Url }}</tt></p>
|
||||
<form class="pure-form">
|
||||
<table class="pure-table" >
|
||||
<tr>
|
||||
<th>profile</th>
|
||||
@ -9,9 +10,9 @@
|
||||
</tr>
|
||||
<tr><th>current filename</th><td x-text="filename"></td></tr>
|
||||
<tr>
|
||||
<th>destination</th>
|
||||
<th>option</th>
|
||||
<td>
|
||||
{{ if .dl.Destination }} {{ .dl.Destination.Name }} {{ else }} leave in {{ .config.Server.DownloadPath }} {{ end }}
|
||||
{{ if .dl.DownloadOption }} {{ .dl.DownloadOption.Name }} {{ else }} n/a {{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th>state</th><td x-text="state"></td></tr>
|
||||
@ -22,8 +23,9 @@
|
||||
</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>
|
||||
{{ if .canStop }}
|
||||
<button x-show="state=='Downloading'" class="pure-button" @click="stop()">stop</button>
|
||||
<button x-show="state=='Downloading'" class="button-small pure-button" @click="stop()">stop</button>
|
||||
{{ end }}
|
||||
</form>
|
||||
<div>
|
||||
<h4>Logs</h4>
|
||||
<pre x-text="log" style="height: auto;">
|
||||
|
@ -19,11 +19,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>destination</th>
|
||||
<th>download option</th>
|
||||
<td>
|
||||
<select class="pure-input-1-2" x-model="destination_chosen">
|
||||
<option value="">leave in {{ .config.Server.DownloadPath }}</option>
|
||||
{{ range $i := .config.Destinations }}
|
||||
<select class="pure-input-1-2" x-model="download_option_chosen">
|
||||
<option value="">no option</option>
|
||||
{{ range $i := .config.DownloadOptions }}
|
||||
<option>{{ $i.Name }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
@ -32,7 +32,7 @@
|
||||
<tr>
|
||||
<th> </th>
|
||||
<td>
|
||||
<button class="pure-button" @click="start()">start download</button>
|
||||
<button class="button-small pure-button" @click="start()">start download</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -44,12 +44,12 @@
|
||||
function popup_create() {
|
||||
return {
|
||||
profile_chosen: "",
|
||||
destination_chosen: "",
|
||||
download_option_chosen: "",
|
||||
error_message: "",
|
||||
start() {
|
||||
let op = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({action: 'start', url: '{{ .url }}', profile: this.profile_chosen, destination: this.destination_chosen}),
|
||||
body: JSON.stringify({action: 'start', url: '{{ .url }}', profile: this.profile_chosen, download_option: this.download_option_chosen}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
fetch('/fetch', op)
|
||||
|
18
web/web.go
18
web/web.go
@ -317,9 +317,9 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
} else if method == "POST" {
|
||||
// creating a new one
|
||||
type reqType struct {
|
||||
URL string `json:"url"`
|
||||
ProfileChosen string `json:"profile"`
|
||||
DestinationChosen string `json:"destination"`
|
||||
URL string `json:"url"`
|
||||
ProfileChosen string `json:"profile"`
|
||||
DownloadOptionChosen string `json:"download_option"`
|
||||
}
|
||||
|
||||
req := reqType{}
|
||||
@ -356,20 +356,12 @@ func fetchHandler(cs *config.ConfigService, vm *version.Manager, dm *download.Ma
|
||||
return
|
||||
}
|
||||
|
||||
destination := cs.Config.DestinationCalled(req.DestinationChosen)
|
||||
if req.DestinationChosen != "" && destination == nil {
|
||||
w.WriteHeader(400)
|
||||
json.NewEncoder(w).Encode(errorResponse{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("no such destination: '%s'", req.DestinationChosen),
|
||||
})
|
||||
return
|
||||
}
|
||||
option := cs.Config.DownloadOptionCalled(req.DownloadOptionChosen)
|
||||
|
||||
// create the new download
|
||||
newDL := download.NewDownload(req.URL, cs.Config)
|
||||
id := newDL.Id
|
||||
newDL.Destination = destination
|
||||
newDL.DownloadOption = option
|
||||
newDL.DownloadProfile = *profile
|
||||
dm.AddDownload(newDL)
|
||||
dm.Queue(newDL)
|
||||
|
Loading…
x
Reference in New Issue
Block a user