cordless/config/config.go

398 lines
12 KiB
Go

package config
import (
"encoding/json"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Bios-Marcel/cordless/util/files"
)
const (
//AppNameLowercase is the representative name, but lowercase.
//It us used for filepaths and such.
AppNameLowercase = "cordless"
//HourMinuteAndSeconds is the time format HH:MM:SS.
HourMinuteAndSeconds = 0
//HourAndMinute is the time format HH:MM.
HourAndMinute = 1
//NoTime means that not time at all will be displayed.
NoTime = 2
// DoNothingOnTypeInList means that when typing in a list (treeview) simply
// nothing will happen.
DoNothingOnTypeInList = 0
// SearchOnTypeInList will cause the widget to search through it's
// children, trying to find anything that is prefixed with the
// previously entered characters.
SearchOnTypeInList = 1
// FocusMessageInputOnTypeInList will automatically focus the message input
// component and transfer the typed character into it as well.
FocusMessageInputOnTypeInList = 2
// NoColor will render all usernames using the PrimaryTextColor defined in
// the theme that's currently loaded.
NoColor UserColor = "none"
// SingleColor causes cordless to take the color specified in the theme
SingleColor UserColor = "single"
// RandomColor causes cordless to take a random color from the theme
// specified pool of usable random colors.
RandomColor UserColor = "random"
// RoleColor attempts to use the first colored role it finds for a user.
RoleColor UserColor = "role"
)
// UserColor represents available configurations for rendering a users color.
type UserColor string
var (
//Current is the currently loaded configuration.
Current = createDefaultConfig()
)
//Config contains all possible configuration for the application.
type Config struct {
//Token is the authorization token for accessing the discord API.
Token string
//Autocomplete decides whether the chat automatically offers autocomplete
//values for the currently given text.
Autocomplete bool
//Times decides on the time format (none, short and long).
Times int
//UserColors decides how cordless determines in which color it displays
//a user in the chat or the user tree.
UserColors UserColor
//FocusChannelAfterGuildSelection will cause the widget focus to move over
//to the channel tree after selecting a guild.
FocusChannelAfterGuildSelection bool
//FocusMessageInputAfterChannelSelection will cause the widget focus to
//move over to the message input widget after channel selection
FocusMessageInputAfterChannelSelection bool
//ShowUserContainer decides whether the user container is part of the
//layout or not.
ShowUserContainer bool
//UseFixedLayout defines whether the FixedSizeLeft and FixedSizeRight
//values will be applied or not.
UseFixedLayout bool
//FixedSizeLeft determines the size of the guilds/channels/friends
//container on the left side of the layout.
FixedSizeLeft int
//FixedSizeRight defines the size of the users container on the right.
FixedSizeRight int
// OnTypeInListBehaviour defines whether the application focus the input
// input field on typing, searches the list or does nothing.
OnTypeInListBehaviour int
// MouseEnabled decides whether the mouse is usable or not.
MouseEnabled bool
// ShortenLinks decides whether cordless starts a local webserver in order
// to be able to shorten links
ShortenLinks bool
// ShortenWithExtension defines whether the suffix is added to the shortened
// url
ShortenWithExtension bool
// ShortenerPort defines the port, that the webserver for the linkshortener
// will be using.
ShortenerPort int
// DesktopNotifications decides whether a popup will be shown in the users
// system when a notification needs to be sent.
DesktopNotifications bool
// DesktopNotificationsUserInactivityThreshold defines how many seconds
// have to pass between now and the last user input (key stroke) in order
// for a notification to be sent.
DesktopNotificationsUserInactivityThreshold int
// DesktopNotificationsForLoadedChannel Defines whether notifications are
// also sent for the currently selected (loaded) channel.
DesktopNotificationsForLoadedChannel bool
// ShowPlaceholderForBlockedMessages will cause blocked message to shown
// as a placeholder message, replacing user and message with generic text.
// The time of the message will still be correct in order to not mess up
// the timeline of messages.
ShowPlaceholderForBlockedMessages bool
// ShowUpdateNotifications decides whether update notifications are
// shown at all.
ShowUpdateNotifications bool
// DontShowUpdateNotificationFor decides what version to skip update
// notifications for. Since there's always only one latest version, this
// is a string and not an array of strings.
DontShowUpdateNotificationFor string
// Accounts contains all saved accounts, allowing the user to dynamically
// switch between the accounts.
Accounts []*Account
// Show a padlock prefix of the channels that have access restriction
IndicateChannelAccessRestriction bool
// ShowBottomBar decides whether an informational line is shown at the
// bottom of cordless or not.
ShowBottomBar bool
// ShowNicknames decides whether nicknames are shown throughout the
// application, as there are some childish goons that deem it funny
// to impersonate people or change their name every 5 minutes.
ShowNicknames bool
// ShowReactionsInline decides whether reactions are displayed below a
// message.
ShowReactionsInline bool
// FileHandlers allow registering specific file-handers for certain
FileOpenHandlers map[string]string
// FileOpenSaveFilesPermanently decides whether opened files are saved
// in the system cache (temporary) or in the user specified path.
FileOpenSaveFilesPermanently bool
// FileDownloadSaveLocation defines the folder where cordless generally
// download files to. If FileOpenSaveFilesPermanently has been set to
// true, then all opened files are saved in this folder for example.
FileDownloadSaveLocation string
}
// Account has a name and a token. The name is just for the users recognition.
// The token is the actual token used to authenticate against the discord API.
type Account struct {
Name string
Token string
}
// GetAccountToken returns the token for the given account or an empty string
// if the account can't be found or has no token set up.
func (config *Config) GetAccountToken(account string) string {
for _, acc := range config.Accounts {
if strings.EqualFold(acc.Name, account) {
return acc.Token
}
}
return ""
}
var cachedConfigDir string
var cachedConfigFile string
var cachedScriptDir string
func createDefaultConfig() *Config {
// The values here are the defaults which can / will be overwritten
// by loading the config file. This can also be used to reset the
// active set of settings during runtime.
return &Config{
Autocomplete: true,
Times: HourMinuteAndSeconds,
UserColors: SingleColor,
ShowUserContainer: true,
UseFixedLayout: false,
FixedSizeLeft: 12,
FixedSizeRight: 12,
FocusChannelAfterGuildSelection: true,
FocusMessageInputAfterChannelSelection: true,
OnTypeInListBehaviour: SearchOnTypeInList,
MouseEnabled: true,
ShortenLinks: false,
ShortenWithExtension: false,
ShortenerPort: 63212,
DesktopNotifications: true,
DesktopNotificationsUserInactivityThreshold: 10,
DesktopNotificationsForLoadedChannel: true,
ShowPlaceholderForBlockedMessages: true,
DontShowUpdateNotificationFor: "",
ShowUpdateNotifications: true,
IndicateChannelAccessRestriction: false,
ShowBottomBar: true,
ShowNicknames: true,
ShowReactionsInline: true,
FileOpenHandlers: make(map[string]string),
FileOpenSaveFilesPermanently: false,
FileDownloadSaveLocation: "~/Downloads",
}
}
// SetConfigFile sets the config file path cache to the
// entered value
func SetConfigFile(configFilePath string) error {
// get parent directory of config file
parent := filepath.Dir(configFilePath)
err := files.EnsureDirectoryExists(parent)
if err == nil {
cachedConfigFile = configFilePath
} else {
absolute, err := getAbsolutePath(parent)
if err != nil {
return err
}
err = files.EnsureDirectoryExists(absolute)
if err == nil {
cachedConfigFile = configFilePath
}
}
return err
}
// GetConfigFile retrieves the config file path from cache
// or sets it to the default config file location
func GetConfigFile() (string, error) {
if cachedConfigFile != "" {
return cachedConfigFile, nil
}
configDir, configError := GetConfigDirectory()
if configError != nil {
return "", configError
}
cachedConfigFile = filepath.Join(configDir, "config.json")
return cachedConfigFile, nil
}
// SetScriptDirectory sets the script directory cache
// to the specified value
func SetScriptDirectory(directoryPath string) error {
err := files.EnsureDirectoryExists(directoryPath)
if err == nil {
cachedScriptDir = directoryPath
} else {
absolute, err := getAbsolutePath(directoryPath)
if err != nil {
return err
}
err = files.EnsureDirectoryExists(absolute)
if err == nil {
cachedConfigFile = absolute
}
}
return err
}
// GetScriptDirectory retrieves the script path from cache
// or sets it to the default script directory location
func GetScriptDirectory() string {
if cachedScriptDir != "" {
return cachedScriptDir
}
cachedScriptDir = filepath.Join(cachedConfigDir, "scripts")
return cachedScriptDir
}
// SetConfigDirectory sets the directory cache
func SetConfigDirectory(directoryPath string) error {
err := files.EnsureDirectoryExists(directoryPath)
if err == nil {
cachedConfigDir = directoryPath
} else {
absolute, err := getAbsolutePath(directoryPath)
if err != nil {
return err
}
err = files.EnsureDirectoryExists(absolute)
if err == nil {
cachedConfigFile = absolute
}
}
return err
}
// GetConfigDirectory retrieves the directory that stores
// cordless' settings from cache or sets it to the default
// location
func GetConfigDirectory() (string, error) {
if cachedConfigDir != "" {
return cachedConfigDir, nil
}
directory, err := getDefaultConfigDirectory()
if err != nil {
return "", err
}
statError := files.EnsureDirectoryExists(directory)
if statError != nil {
return "", statError
}
//After first retrieval, we will save this, as we needn't redo all that
//stuff over and over again.
cachedConfigDir = directory
return cachedConfigDir, nil
}
func getAbsolutePath(directoryPath string) (string, error) {
absolutePath, resolveError := files.ToAbsolutePath(directoryPath)
if resolveError != nil {
return "", resolveError
}
return absolutePath, resolveError
}
// LoadConfig loads the configuration. After loading the configuration, it can
// be accessed via config.Current.
func LoadConfig() error {
configFilePath, configError := GetConfigFile()
if configError != nil {
return configError
}
configFile, openError := os.Open(configFilePath)
if os.IsNotExist(openError) {
return nil
}
if openError != nil {
return openError
}
defer configFile.Close()
decoder := json.NewDecoder(configFile)
configLoadError := decoder.Decode(Current)
//io.EOF would mean empty, therefore we use defaults.
if configLoadError != nil && configLoadError != io.EOF {
return configLoadError
}
return nil
}
// UpdateCurrentToken updates the current token and all accounts where the
// token was also used.
func UpdateCurrentToken(newToken string) {
oldToken := Current.Token
Current.Token = newToken
for _, account := range Current.Accounts {
if account.Token == oldToken {
account.Token = newToken
}
}
}
//PersistConfig saves the current configuration onto the filesystem.
func PersistConfig() error {
configFilePath, configError := GetConfigFile()
if configError != nil {
return configError
}
configAsJSON, jsonError := json.MarshalIndent(Current, "", " ")
if jsonError != nil {
return jsonError
}
writeError := ioutil.WriteFile(configFilePath, configAsJSON, 0666)
if writeError != nil {
return writeError
}
return nil
}