Allow for adding/deleting profiles, add a bunch of sanity checks for config changes.

This commit is contained in:
Justin Hawkins 2021-09-30 17:04:12 +09:30
parent bf127f6cc2
commit 89b142a150
5 changed files with 102 additions and 34 deletions

View File

@ -3,8 +3,10 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"strings"
"gopkg.in/yaml.v2"
)
@ -35,15 +37,20 @@ type Config struct {
func 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",
"--write-info-json",
"-f",
"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, mp3Profile)
defaultConfig.Server.Port = 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)
return err
}
log.Printf("Config is unmarshalled ok")
// other checks
// sanity checks
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
@ -137,12 +192,14 @@ func (c *Config) WriteConfig() {
file, err := os.Create(
path,
)
if err != nil {
log.Fatalf("Could not open config file")
}
defer file.Close()
file.Write(s)
file.Close()
log.Printf("Stored in %s", path)
log.Printf("Wrote configuration out to %s", path)
}

View File

@ -161,8 +161,8 @@ func ConfigRESTHandler(w http.ResponseWriter, r *http.Request) {
w.Write(errorResB)
return
}
}
conf.WriteConfig()
}
b, _ := json.Marshal(conf)
w.Write(b)
}

View File

@ -3,18 +3,13 @@
<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 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>
<p>Changes are not saved until "Save Changes" is pressed at the bottom of the page.</p>
<div class="pure-g">
<div class="pure-u-md-1-2 pure-u-1">
<form class="pure-form pure-form-stacked gropple-config">
@ -64,8 +59,12 @@
<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 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" />
<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>
<label x-bind:for="'config-profiles-'+i+'-command'">Command to run</label>
@ -78,18 +77,19 @@
<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);;">del</button>
<button class="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>
<hr>
</div>
</template>
<button class="pure-button button-add" href="#" @click.prevent="config.profiles.push({name: 'new profile', command: 'youtube-dl', args: []});">add profile</button>
</fieldset>
</form>
</div>
@ -110,8 +110,8 @@
function config() {
return {
config: { server : {}, ui : {}, profiles: [] },
version: {},
error_message: '',
success_message: '',
fetch_config() {
fetch('/rest/config')
@ -124,7 +124,6 @@
});
},
save_config() {
console.log(this.config);
let op = {
method: 'POST',
body: JSON.stringify(this.config),
@ -132,18 +131,22 @@
}
fetch('/rest/config', op)
.then(response => {
if (response.status >= 200 && response.status <= 299) {
return response.json();
})
.then(response => {
if (response.error) {
this.error_message = response.error;
this.success_message = '';
document.body.scrollTop = document.documentElement.scrollTop = 0;
} else {
throw Error(response);
}})
.then(config => {
console.log('fixing object');
this.config = config;
this.error_message = '';
this.success_message = 'configuration saved';
document.body.scrollTop = document.documentElement.scrollTop = 0;
this.config = response;
}
})
.catch(error => {
this.error_message = error.error;
console.log('failed to update config', error);
console.log('exception' ,error);
});
}
}

View File

@ -3,9 +3,7 @@
<div x-data="index()" x-init="fetch_data(); fetch_version()">
<h2>gropple</h2>
<p x-show="version && version.upgrade_available">
<p x-cloak 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

View File

@ -36,11 +36,21 @@
padding-top: .5em;
padding-bottom: 1.5em;
}
.error {
color: red;
}
.success {
color: green;
}
[x-cloak] { display: none !important; }
</style>
</head>
<body style="margin:4; padding:4">
<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">
<li class="pure-menu-item">
<a href="/" class="pure-menu-link">Home</a>