Fix initial setup issue issues (#1380)

* Refactor initial setup behaviour
* Adjust wizard
This commit is contained in:
WithoutPants 2021-05-13 22:15:21 +10:00 committed by GitHub
parent 5a37e6cf52
commit e0623eb302
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 34 deletions

View file

@ -12,5 +12,6 @@ query SystemStatus {
databasePath
appSchema
status
configPath
}
}

View file

@ -116,6 +116,7 @@ enum SystemStatusEnum {
type SystemStatus {
databaseSchema: Int
databasePath: String
configPath: String
appSchema: Int!
status: SystemStatusEnum!
}

View file

@ -141,7 +141,9 @@ func (e MissingConfigError) Error() string {
return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", "))
}
type Instance struct{}
type Instance struct {
isNewSystem bool
}
var instance *Instance
@ -152,6 +154,10 @@ func GetInstance() *Instance {
return instance
}
func (i *Instance) IsNewSystem() bool {
return i.isNewSystem
}
func (i *Instance) SetConfigFile(fn string) {
viper.SetConfigFile(fn)
}
@ -687,29 +693,26 @@ func (i *Instance) Validate() error {
return nil
}
func (i *Instance) setDefaultValues() {
func (i *Instance) setDefaultValues() error {
viper.SetDefault(ParallelTasks, parallelTasksDefault)
viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault)
viper.SetDefault(PreviewSegments, previewSegmentsDefault)
viper.SetDefault(PreviewExcludeStart, previewExcludeStartDefault)
viper.SetDefault(PreviewExcludeEnd, previewExcludeEndDefault)
// #1356 - only set these defaults once config file exists
if i.GetConfigFile() != "" {
viper.SetDefault(Database, i.GetDefaultDatabaseFilePath())
viper.SetDefault(Database, i.GetDefaultDatabaseFilePath())
// Set generated to the metadata path for backwards compat
viper.SetDefault(Generated, viper.GetString(Metadata))
// Set generated to the metadata path for backwards compat
viper.SetDefault(Generated, viper.GetString(Metadata))
// Set default scrapers and plugins paths
viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath())
viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath())
viper.WriteConfig()
}
// Set default scrapers and plugins paths
viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath())
viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath())
return viper.WriteConfig()
}
// SetInitialConfig fills in missing required config fields
func (i *Instance) SetInitialConfig() {
func (i *Instance) SetInitialConfig() error {
// generate some api keys
const apiKeyLength = 32
@ -723,5 +726,9 @@ func (i *Instance) SetInitialConfig() {
i.Set(SessionStoreKey, sessionStoreKey)
}
i.setDefaultValues()
return i.setDefaultValues()
}
func (i *Instance) FinalizeSetup() {
i.isNewSystem = false
}

View file

@ -1,6 +1,7 @@
package config
import (
"fmt"
"net"
"os"
"sync"
@ -24,8 +25,22 @@ func Initialize() (*Instance, error) {
instance = &Instance{}
flags := initFlags()
err = initConfig(flags)
if err = initConfig(flags); err != nil {
return
}
initEnvs()
if instance.isNewSystem {
if instance.Validate() == nil {
// system has been initialised by the environment
instance.isNewSystem = false
}
}
if !instance.isNewSystem {
err = instance.SetInitialConfig()
}
})
return instance, err
}
@ -34,26 +49,47 @@ func initConfig(flags flagStruct) error {
// The config file is called config. Leave off the file extension.
viper.SetConfigName("config")
if flagConfigFileExists, _ := utils.FileExists(flags.configFilePath); flagConfigFileExists {
viper.SetConfigFile(flags.configFilePath)
}
viper.AddConfigPath(".") // Look for config in the working directory
viper.AddConfigPath("$HOME/.stash") // Look for the config in the home directory
// for Docker compatibility, if STASH_CONFIG_FILE is set, then touch the
// given filename
configFile := ""
envConfigFile := os.Getenv("STASH_CONFIG_FILE")
if envConfigFile != "" {
utils.Touch(envConfigFile)
viper.SetConfigFile(envConfigFile)
if flags.configFilePath != "" {
configFile = flags.configFilePath
} else if envConfigFile != "" {
configFile = envConfigFile
}
if configFile != "" {
viper.SetConfigFile(configFile)
// if file does not exist, assume it is a new system
if exists, _ := utils.FileExists(configFile); !exists {
instance.isNewSystem = true
// ensure we can write to the file
if err := utils.Touch(configFile); err != nil {
return fmt.Errorf(`could not write to provided config path "%s": %s`, configFile, err.Error())
} else {
// remove the file
os.Remove(configFile)
}
return nil
}
}
err := viper.ReadInConfig() // Find and read the config file
// continue, but set an error to be handled by caller
// if not found, assume its a new system
if _, isMissing := err.(viper.ConfigFileNotFoundError); isMissing {
instance.isNewSystem = true
return nil
} else if err != nil {
return err
}
instance.SetInitialConfig()
return err
return nil
}
func initFlags() flagStruct {

View file

@ -49,6 +49,11 @@ func Initialize() *singleton {
once.Do(func() {
_ = utils.EnsureDir(paths.GetStashHomeDirectory())
cfg, err := config.Initialize()
if err != nil {
panic(fmt.Sprintf("error initializing configuration: %s", err.Error()))
}
initLog()
instance = &singleton{
@ -59,8 +64,7 @@ func Initialize() *singleton {
TxnManager: sqlite.NewTransactionManager(),
}
cfgFile := cfg.GetConfigFile()
if cfgFile != "" {
if !cfg.IsNewSystem() {
logger.Infof("using config file: %s", cfg.GetConfigFile())
if err == nil {
@ -75,7 +79,11 @@ func Initialize() *singleton {
}
}
} else {
logger.Warn("config file not found. Assuming new system...")
cfgFile := cfg.GetConfigFile()
if cfgFile != "" {
cfgFile = cfgFile + " "
}
logger.Warnf("config file %snot found. Assuming new system...", cfgFile)
}
initFFMPEG()
@ -235,6 +243,8 @@ func (s *singleton) Setup(input models.SetupInput) error {
return fmt.Errorf("error initializing the database: %s", err.Error())
}
s.Config.FinalizeSetup()
return nil
}
@ -283,8 +293,9 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus {
dbSchema := int(database.Version())
dbPath := database.DatabasePath()
appSchema := int(database.AppSchemaVersion())
configFile := s.Config.GetConfigFile()
if s.Config.GetConfigFile() == "" {
if s.Config.IsNewSystem() {
status = models.SystemStatusEnumSetup
} else if dbSchema < appSchema {
status = models.SystemStatusEnumNeedsMigration
@ -295,5 +306,6 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus {
DatabasePath: &dbPath,
AppSchema: appSchema,
Status: status,
ConfigPath: &configFile,
}
}

View file

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
Alert,
Button,
@ -27,6 +27,12 @@ export const Setup: React.FC = () => {
const { data: systemStatus, loading: statusLoading } = useSystemStatus();
useEffect(() => {
if (systemStatus?.systemStatus.configPath) {
setConfigLocation(systemStatus.systemStatus.configPath);
}
}, [systemStatus]);
const discordLink = (
<a href="https://discord.gg/2TsNFKt" target="_blank" rel="noreferrer">
Discord
@ -59,6 +65,38 @@ export const Setup: React.FC = () => {
setStep(step + 1);
}
function renderWelcomeSpecificConfig() {
return (
<>
<section>
<h2 className="mb-5">Welcome to Stash</h2>
<p className="lead text-center">
If you&apos;re reading this, then Stash couldn&apos;t find the
configuration file specified at the command line or the environment.
This wizard will guide you through the process of setting up a new
configuration.
</p>
<p>
Stash will use the following configuration file path:{" "}
<code>{configLocation}</code>
</p>
<p>
When you&apos;re ready to proceed with setting up a new system,
click Next.
</p>
</section>
<section className="mt-5">
<div className="d-flex justify-content-center">
<Button variant="primary mx-2 p-5" onClick={() => next()}>
Next
</Button>
</div>
</section>
</>
);
}
function renderWelcome() {
return (
<>
@ -433,8 +471,6 @@ export const Setup: React.FC = () => {
return renderSuccess();
}
const steps = [renderWelcome, renderSetPaths, renderConfirm, renderFinish];
// only display setup wizard if system is not setup
if (statusLoading) {
return <LoadingIndicator />;
@ -450,6 +486,12 @@ export const Setup: React.FC = () => {
return <LoadingIndicator />;
}
const welcomeStep =
systemStatus && systemStatus.systemStatus.configPath !== ""
? renderWelcomeSpecificConfig
: renderWelcome;
const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish];
return (
<Container>
{maybeRenderGeneratedSelectDialog()}