Allow for adding/deleting profiles, add a bunch of sanity checks for config changes.
This commit is contained in:
parent
bf127f6cc2
commit
89b142a150
@ -3,8 +3,10 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
@ -35,15 +37,20 @@ type Config struct {
|
|||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
defaultConfig := Config{}
|
defaultConfig := Config{}
|
||||||
stdProfile := DownloadProfile{Name: "standard youtube-dl video", Command: "youtube-dl", Args: []string{
|
stdProfile := DownloadProfile{Name: "standard video", Command: "youtube-dl", Args: []string{
|
||||||
"--newline",
|
"--newline",
|
||||||
"--write-info-json",
|
"--write-info-json",
|
||||||
"-f",
|
"-f",
|
||||||
"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
||||||
}}
|
}}
|
||||||
|
mp3Profile := DownloadProfile{Name: "standard mp3", Command: "youtube-dl", Args: []string{
|
||||||
|
"–extract-audio",
|
||||||
|
"--audio-format", "mp3",
|
||||||
|
"--prefer-ffmpeg",
|
||||||
|
}}
|
||||||
|
|
||||||
defaultConfig.DownloadProfiles = append(defaultConfig.DownloadProfiles, stdProfile)
|
defaultConfig.DownloadProfiles = append(defaultConfig.DownloadProfiles, stdProfile)
|
||||||
defaultConfig.DownloadProfiles = append(defaultConfig.DownloadProfiles, stdProfile)
|
defaultConfig.DownloadProfiles = append(defaultConfig.DownloadProfiles, mp3Profile)
|
||||||
|
|
||||||
defaultConfig.Server.Port = 6123
|
defaultConfig.Server.Port = 6123
|
||||||
defaultConfig.Server.Address = "http://localhost:6123"
|
defaultConfig.Server.Address = "http://localhost:6123"
|
||||||
@ -64,11 +71,59 @@ func (c *Config) UpdateFromJSON(j []byte) error {
|
|||||||
log.Printf("Unmarshal error in config: %v", err)
|
log.Printf("Unmarshal error in config: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Config is unmarshalled ok")
|
|
||||||
|
|
||||||
// other checks
|
// sanity checks
|
||||||
if newConfig.UI.PopupHeight < 100 || newConfig.UI.PopupHeight > 2000 {
|
if newConfig.UI.PopupHeight < 100 || newConfig.UI.PopupHeight > 2000 {
|
||||||
return errors.New("bad popup height")
|
return errors.New("invalid popup height - should be 100-2000")
|
||||||
|
}
|
||||||
|
if newConfig.UI.PopupWidth < 100 || newConfig.UI.PopupWidth > 2000 {
|
||||||
|
return errors.New("invalid popup width - should be 100-2000")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check listen port
|
||||||
|
if newConfig.Server.Port < 1 || newConfig.Server.Port > 65535 {
|
||||||
|
return errors.New("invalid server listen port")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check download path
|
||||||
|
fi, err := os.Stat(newConfig.Server.DownloadPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("path '%s' does not exist", newConfig.Server.DownloadPath)
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return fmt.Errorf("path '%s' is not a directory", newConfig.Server.DownloadPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check profile name uniqueness
|
||||||
|
for i, p1 := range newConfig.DownloadProfiles {
|
||||||
|
for j, p2 := range newConfig.DownloadProfiles {
|
||||||
|
if i != j && p1.Name == p2.Name {
|
||||||
|
return fmt.Errorf("duplicate download profile name '%s'", p1.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove leading/trailing spaces from args and commands and check for emptiness
|
||||||
|
for i := range newConfig.DownloadProfiles {
|
||||||
|
newConfig.DownloadProfiles[i].Name = strings.TrimSpace(newConfig.DownloadProfiles[i].Name)
|
||||||
|
|
||||||
|
if newConfig.DownloadProfiles[i].Name == "" {
|
||||||
|
return errors.New("profile name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfig.DownloadProfiles[i].Command = strings.TrimSpace(newConfig.DownloadProfiles[i].Command)
|
||||||
|
if newConfig.DownloadProfiles[i].Command == "" {
|
||||||
|
return fmt.Errorf("command in profile %s cannot be empty", newConfig.DownloadProfiles[i].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the args
|
||||||
|
for j := range newConfig.DownloadProfiles[i].Args {
|
||||||
|
newConfig.DownloadProfiles[i].Args[j] = strings.TrimSpace(newConfig.DownloadProfiles[i].Args[j])
|
||||||
|
if newConfig.DownloadProfiles[i].Args[j] == "" {
|
||||||
|
return fmt.Errorf("argument %d of profile %s is empty", j+1, newConfig.DownloadProfiles[i].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*c = newConfig
|
*c = newConfig
|
||||||
@ -137,12 +192,14 @@ func (c *Config) WriteConfig() {
|
|||||||
file, err := os.Create(
|
file, err := os.Create(
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not open config file")
|
log.Fatalf("Could not open config file")
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
file.Write(s)
|
file.Write(s)
|
||||||
file.Close()
|
file.Close()
|
||||||
|
|
||||||
log.Printf("Stored in %s", path)
|
log.Printf("Wrote configuration out to %s", path)
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -161,8 +161,8 @@ func ConfigRESTHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(errorResB)
|
w.Write(errorResB)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
conf.WriteConfig()
|
||||||
}
|
}
|
||||||
conf.WriteConfig()
|
|
||||||
b, _ := json.Marshal(conf)
|
b, _ := json.Marshal(conf)
|
||||||
w.Write(b)
|
w.Write(b)
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,13 @@
|
|||||||
|
|
||||||
<div x-data="config()" x-init="fetch_config();">
|
<div x-data="config()" x-init="fetch_config();">
|
||||||
|
|
||||||
<h2>gropple config</h2>
|
<p class="error" x-text="error_message"></p>
|
||||||
|
<p class="success" x-text="success_message"></p>
|
||||||
|
|
||||||
<p x-text="error_message"></p>
|
<p>Changes are not saved until "Save Changes" is pressed at the bottom of the page.</p>
|
||||||
|
|
||||||
<p x-show="version && version.upgrade_available">
|
|
||||||
<a href="https://github.com/tardisx/gropple/releases">Upgrade is available</a> -
|
|
||||||
you have
|
|
||||||
<span x-text="version.current_version"></span> and
|
|
||||||
<span x-text="version.github_version"></span>
|
|
||||||
is available.</p>
|
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
|
|
||||||
<div class="pure-u-md-1-2 pure-u-1">
|
<div class="pure-u-md-1-2 pure-u-1">
|
||||||
|
|
||||||
<form class="pure-form pure-form-stacked gropple-config">
|
<form class="pure-form pure-form-stacked gropple-config">
|
||||||
@ -64,8 +59,12 @@
|
|||||||
|
|
||||||
<template x-for="(profile, i) in config.profiles">
|
<template x-for="(profile, i) in config.profiles">
|
||||||
<div>
|
<div>
|
||||||
<label x-bind:for="'config-profiles-'+i+'-name'">Name of profile <span x-text="i+1"></span></label>
|
<label x-bind:for="'config-profiles-'+i+'-name'">Name of profile <span x-text="i+1"></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" x-bind:id="'config-profiles-'+i+'-name'" class="input-long" placeholder="name" x-model="profile.name" />
|
<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>
|
||||||
|
|
||||||
<span class="pure-form-message">The name of this profile. For your information only.</span>
|
<span class="pure-form-message">The name of this profile. For your information only.</span>
|
||||||
|
|
||||||
<label x-bind:for="'config-profiles-'+i+'-command'">Command to run</label>
|
<label x-bind:for="'config-profiles-'+i+'-command'">Command to run</label>
|
||||||
@ -78,18 +77,19 @@
|
|||||||
<template x-for="(arg, j) in profile.args">
|
<template x-for="(arg, j) in profile.args">
|
||||||
<div>
|
<div>
|
||||||
<input type="text" x-bind:id="'config-profiles-'+i+'-arg-'+j" placeholder="arg" x-model="profile.args[j]" />
|
<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);;">del</button>
|
<button class="pure-button button-del" href="#" @click.prevent="profile.args.splice(j, 1);;">delete arg</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button class="pure-button button-add" href="#" @click.prevent="profile.args.push('');">add arg</button>
|
<button class="pure-button button-add" href="#" @click.prevent="profile.args.push('');">add arg</button>
|
||||||
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<button class="pure-button button-add" href="#" @click.prevent="config.profiles.push({name: 'new profile', command: 'youtube-dl', args: []});">add profile</button>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -110,8 +110,8 @@
|
|||||||
function config() {
|
function config() {
|
||||||
return {
|
return {
|
||||||
config: { server : {}, ui : {}, profiles: [] },
|
config: { server : {}, ui : {}, profiles: [] },
|
||||||
version: {},
|
|
||||||
error_message: '',
|
error_message: '',
|
||||||
|
success_message: '',
|
||||||
|
|
||||||
fetch_config() {
|
fetch_config() {
|
||||||
fetch('/rest/config')
|
fetch('/rest/config')
|
||||||
@ -124,7 +124,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
save_config() {
|
save_config() {
|
||||||
console.log(this.config);
|
|
||||||
let op = {
|
let op = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(this.config),
|
body: JSON.stringify(this.config),
|
||||||
@ -132,18 +131,22 @@
|
|||||||
}
|
}
|
||||||
fetch('/rest/config', op)
|
fetch('/rest/config', op)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.status >= 200 && response.status <= 299) {
|
return response.json();
|
||||||
return response.json();
|
})
|
||||||
} else {
|
.then(response => {
|
||||||
throw Error(response);
|
if (response.error) {
|
||||||
}})
|
this.error_message = response.error;
|
||||||
.then(config => {
|
this.success_message = '';
|
||||||
console.log('fixing object');
|
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||||
this.config = config;
|
} else {
|
||||||
|
this.error_message = '';
|
||||||
|
this.success_message = 'configuration saved';
|
||||||
|
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||||
|
this.config = response;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.error_message = error.error;
|
console.log('exception' ,error);
|
||||||
console.log('failed to update config', error);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
<div x-data="index()" x-init="fetch_data(); fetch_version()">
|
<div x-data="index()" x-init="fetch_data(); fetch_version()">
|
||||||
|
|
||||||
<h2>gropple</h2>
|
<p x-cloak x-show="version && version.upgrade_available">
|
||||||
|
|
||||||
<p x-show="version && version.upgrade_available">
|
|
||||||
<a href="https://github.com/tardisx/gropple/releases">Upgrade is available</a> -
|
<a href="https://github.com/tardisx/gropple/releases">Upgrade is available</a> -
|
||||||
you have
|
you have
|
||||||
<span x-text="version.current_version"></span> and
|
<span x-text="version.current_version"></span> and
|
||||||
|
@ -36,11 +36,21 @@
|
|||||||
padding-top: .5em;
|
padding-top: .5em;
|
||||||
padding-bottom: 1.5em;
|
padding-bottom: 1.5em;
|
||||||
}
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[x-cloak] { display: none !important; }
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin:4; padding:4">
|
<body style="margin:4; padding:4">
|
||||||
<div class="pure-menu pure-menu-horizontal" style="height: 2em;">
|
<div class="pure-menu pure-menu-horizontal" style="height: 2em;">
|
||||||
|
<a href="#" class="pure-menu-heading pure-menu-link">gropple</a>
|
||||||
<ul class="pure-menu-list">
|
<ul class="pure-menu-list">
|
||||||
<li class="pure-menu-item">
|
<li class="pure-menu-item">
|
||||||
<a href="/" class="pure-menu-link">Home</a>
|
<a href="/" class="pure-menu-link">Home</a>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user