Moved config to its own directory

This commit is contained in:
tyler 2023-12-24 16:18:42 -05:00
parent d329c36653
commit 5ddf50a152
10 changed files with 250 additions and 55 deletions

87
app.go
View file

@ -14,22 +14,34 @@ import (
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
)
const (
configFilepath = "./config.json"
)
type chat struct {
username string
password string
url string
}
// App struct
type App struct {
ctx context.Context
cfg *config.App
cfgMu sync.Mutex
api *api.Api
apiMu sync.Mutex
ctx context.Context
cfg *config.App
cfgMu sync.Mutex
api *api.Api
apiMu sync.Mutex
logError *log.Logger
logInfo *log.Logger
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{api: api.NewApi()}
app := &App{}
err := app.initLog()
if err != nil {
log.Fatal("error initializing log")
}
app.api = api.NewApi(app.logError, app.logInfo)
return app
}
// startup is called when the app starts. The context is saved
@ -37,15 +49,31 @@ func NewApp() *App {
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.api.Startup(ctx)
err := a.loadConfig()
if err != nil {
// TODO: handle error better on startup
log.Fatal("error loading config: ", err)
a.logError.Fatal("error loading config: ", err)
}
}
func (a *App) initLog() error {
fp, err := config.LogFile()
if err != nil {
return fmt.Errorf("error getting filepath for log file")
}
f, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("error opening log file")
}
a.logInfo = log.New(f, "[info]", log.LstdFlags|log.Lshortfile)
a.logError = log.New(f, "[error]", log.LstdFlags|log.Lshortfile)
return nil
}
func (a *App) loadConfig() error {
cfg, err := config.Load(configFilepath)
cfg, err := config.Load()
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("error loading config: %v", err)
@ -60,7 +88,7 @@ func (a *App) loadConfig() error {
func (a *App) newConfig() error {
cfg := &config.App{Channels: map[string]config.Channel{}}
err := cfg.Save(configFilepath)
err := cfg.Save()
if err != nil {
return fmt.Errorf("error saving new config: %v", err)
}
@ -73,23 +101,12 @@ func (a *App) Config() *config.App {
return a.cfg
}
func (a *App) SaveConfig() error {
err := a.cfg.Save(configFilepath)
if err != nil {
// TODO: log error; return user error
return fmt.Errorf("Error saving config")
}
return nil
}
func (a *App) AddChannel(url string) (*config.App, error) {
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
// TODO: log error
fmt.Println("error requesting api:", err)
return nil, fmt.Errorf("error querying API")
a.logError.Println("error executing api request:", err)
return nil, fmt.Errorf("Error querying API. Verify key and try again.")
}
name := resp.Username
@ -101,16 +118,14 @@ func (a *App) AddChannel(url string) (*config.App, error) {
defer a.cfgMu.Unlock()
_, err = a.cfg.NewChannel(url, name)
if err != nil {
// TODO: log error
fmt.Println("error creating new channel:", err)
return nil, fmt.Errorf("error creating new channel")
a.logError.Println("error creating new channel:", err)
return nil, fmt.Errorf("Error creating new channel. Try again.")
}
err = a.cfg.Save(configFilepath)
err = a.cfg.Save()
if err != nil {
// TODO: log error
fmt.Println("error saving config:", err)
return nil, fmt.Errorf("error saving new channel")
a.logError.Println("error saving config:", err)
return nil, fmt.Errorf("Error saving channel information. Try again.")
}
return a.cfg, nil
@ -119,15 +134,13 @@ func (a *App) AddChannel(url string) (*config.App, error) {
func (a *App) StartApi(cid string) error {
channel, found := a.cfg.Channels[cid]
if !found {
// TODO: log error
fmt.Println("could not find channel CID:", cid)
a.logError.Println("could not find channel CID:", cid)
return fmt.Errorf("channel CID not found")
}
err := a.api.Start(channel.ApiUrl, channel.Interval*time.Second)
if err != nil {
// TODO: log error
fmt.Println("error starting api:", err)
a.logError.Println("error starting api:", err)
return fmt.Errorf("error starting API")
}

View file

@ -3,12 +3,34 @@
height: 100%;
}
.stream-chat-add-button {
align-items: center;
background-color: rgba(6,23,38,1);
border: none;
display: flex;
justify-content: center;
padding: 0px;
}
.stream-chat-add-button:hover {
cursor: pointer;
}
.stream-chat-add-icon {
height: 24px;
width: 24px;
}
.stream-chat-header {
text-align: left;
align-items: center;
background-color: rgba(6,23,38,1);
border-bottom: 1px solid #495a6a;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 19px;
padding: 10px 20px;
text-align: left;
}
.stream-chat-title {

View file

@ -1,3 +1,4 @@
import { PlusCircle } from '../assets/icons';
import './StreamChat.css';
function StreamChat(props) {
@ -5,6 +6,12 @@ function StreamChat(props) {
<div className='stream-chat'>
<div className='stream-chat-header'>
<span className='stream-chat-title'>{props.title}</span>
<button
onClick={() => console.log('Add chat bot')}
className='stream-chat-add-button'
>
<img className='stream-chat-add-icon' src={PlusCircle} />
</button>
</div>
</div>
);

View file

@ -0,0 +1,21 @@
.modal-chat {
align-items: center;
background-color: red;
color: black;
display: flex;
height: 50%;
justify-content: center;
opacity: 1;
width: 50%;
}
.modal-container {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100vw;
}

View file

@ -0,0 +1,13 @@
import './StreamChatMessage.css';
export function StreamChatMessageModal() {
return (
<div className='modal-container'>
<div className='modal-chat'>
<span>hello world</span>
</div>
</div>
);
}
export function StreamChatMessageItem() {}

View file

@ -11,6 +11,7 @@ import StreamActivity from '../components/StreamActivity';
import StreamChat from '../components/StreamChat';
import StreamInfo from '../components/StreamInfo';
import { NavSignIn } from './Navigation';
import { StreamChatMessageItem, StreamChatMessageModal } from '../components/StreamChatMessage';
function Dashboard() {
const location = useLocation();
@ -131,6 +132,7 @@ function Dashboard() {
return (
<>
<StreamChatMessageModal />
<div className='modal' style={{ zIndex: modalZ ? 10 : -10 }}>
<span>show this instead</span>
<button onClick={closeModal}>close</button>

View file

@ -7,6 +7,19 @@
height: 100vh;
}
.add-channel-description {
font-family: sans-serif;
font-size: 12px;
padding-bottom: 5px;
}
.add-channel-error {
color: red;
font-family: sans-serif;
font-size: 12px;
padding-top: 5px;
}
.signin-input-box {
display: flex;
flex-direction: column;

View file

@ -9,6 +9,7 @@ import ChannelList from '../components/ChannelList';
function SignIn() {
const navigate = useNavigate();
const [config, setConfig] = useState({ channels: {} });
const [addChannelError, setAddChannelError] = useState('');
const [streamKey, setStreamKey] = useState('');
const updateStreamKey = (event) => setStreamKey(event.target.value);
const [showStreamKey, setShowStreamKey] = useState(false);
@ -34,6 +35,7 @@ function SignIn() {
})
.catch((err) => {
console.log('error adding channel', err);
setAddChannelError(err);
});
};
@ -52,6 +54,9 @@ function SignIn() {
</div>
<div className='signin-input-box'>
<label className='signin-label'>Add Channel</label>
<span className='add-channel-description'>
Copy your API key from your Rumble account
</span>
<div className='signin-input-button'>
<input
id='StreamKey'
@ -71,6 +76,9 @@ function SignIn() {
Save
</button>
</div>
<span className='add-channel-error'>
{addChannelError ? addChannelError : '\u00A0'}
</span>
</div>
<div className='signin-footer'></div>
</div>

View file

@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"log"
"sync"
"time"
@ -14,12 +15,14 @@ type Api struct {
ctx context.Context
cancel context.CancelFunc
cancelMu sync.Mutex
logError *log.Logger
logInfo *log.Logger
querying bool
queryingMu sync.Mutex
}
func NewApi() *Api {
return &Api{}
func NewApi(logError *log.Logger, logInfo *log.Logger) *Api {
return &Api{logError: logError, logInfo: logInfo}
}
func (a *Api) Startup(ctx context.Context) {
@ -27,7 +30,7 @@ func (a *Api) Startup(ctx context.Context) {
}
func (a *Api) Start(url string, interval time.Duration) error {
fmt.Println("Api.Start")
a.logInfo.Println("Api.Start")
if url == "" {
return fmt.Errorf("empty stream key")
}
@ -38,21 +41,21 @@ func (a *Api) Start(url string, interval time.Duration) error {
a.queryingMu.Unlock()
if start {
fmt.Println("Starting querying")
a.logInfo.Println("Start querying")
ctx, cancel := context.WithCancel(context.Background())
a.cancelMu.Lock()
a.cancel = cancel
a.cancelMu.Unlock()
go a.start(ctx, url, interval)
} else {
fmt.Println("Querying already started")
a.logInfo.Println("Querying already started")
}
return nil
}
func (a *Api) Stop() {
fmt.Println("stop querying")
a.logInfo.Println("Stop querying")
a.cancelMu.Lock()
if a.cancel != nil {
a.cancel()
@ -77,12 +80,12 @@ func (a *Api) start(ctx context.Context, url string, interval time.Duration) {
}
func (a *Api) query(url string) {
fmt.Println("QueryAPI")
a.logInfo.Println("QueryAPI")
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
// TODO: log error
fmt.Println("client.Request err:", err)
a.logError.Println("api: error executing client request:", err)
a.Stop()
runtime.EventsEmit(a.ctx, "QueryResponseError", "Failed to query API")
return

View file

@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/tylertravisty/go-utils/random"
@ -12,13 +14,69 @@ import (
const (
CIDLen = 8
DefaultInterval = 10
configDir = ".rum-goggles"
configDirWin = "RumGoggles"
configFile = "config.json"
logFile = "logs.txt"
)
func LogFile() (string, error) {
dir, err := buildConfigDir()
if err != nil {
return "", fmt.Errorf("config: error getting config directory: %v", err)
}
return filepath.Join(dir, logFile), nil
}
func buildConfigDir() (string, error) {
userDir, err := userDir()
if err != nil {
return "", fmt.Errorf("error getting user directory: %v", err)
}
var dir string
switch runtime.GOOS {
case "windows":
dir = filepath.Join(userDir, configDirWin)
default:
dir = filepath.Join(userDir, configDir)
}
return dir, nil
}
func userDir() (string, error) {
var dir string
var err error
switch runtime.GOOS {
case "windows":
dir, err = os.UserCacheDir()
default:
dir, err = os.UserHomeDir()
}
return dir, err
}
type ChatMessage struct {
AsChannel bool `json:"as_channel"`
Text string `json:"text"`
Interval time.Duration `json:"interval"`
}
type ChatBot struct {
Messages []ChatMessage `json:"messages"`
// Commands []ChatCommand
}
type Channel struct {
ID string `json:"id"`
ApiUrl string `json:"api_url"`
Name string `json:"name"`
Interval time.Duration `json:"interval"`
ChatBot ChatBot `json:"chat_bot"`
}
func (a *App) NewChannel(url string, name string) (string, error) {
@ -29,7 +87,7 @@ func (a *App) NewChannel(url string, name string) (string, error) {
}
if _, exists := a.Channels[id]; !exists {
a.Channels[id] = Channel{id, url, name, DefaultInterval}
a.Channels[id] = Channel{id, url, name, DefaultInterval, ChatBot{[]ChatMessage{}}}
return id, nil
}
}
@ -39,31 +97,66 @@ type App struct {
Channels map[string]Channel `json:"channels"`
}
func Load(filepath string) (*App, error) {
func Load() (*App, error) {
dir, err := buildConfigDir()
if err != nil {
return nil, fmt.Errorf("config: error getting config directory: %v", err)
}
fp := filepath.Join(dir, configFile)
app, err := load(fp)
if err != nil {
return nil, fmt.Errorf("config: error loading config: %w", err)
}
return app, nil
}
func load(filepath string) (*App, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("config: error opening file: %w", err)
return nil, fmt.Errorf("error opening file: %w", err)
}
var app App
decoder := json.NewDecoder(f)
err = decoder.Decode(&app)
if err != nil {
return nil, fmt.Errorf("config: error decoding file into json: %v", err)
return nil, fmt.Errorf("error decoding file into json: %v", err)
}
return &app, nil
}
func (app *App) Save(filepath string) error {
b, err := json.MarshalIndent(app, "", "\t")
func (a *App) Save() error {
dir, err := buildConfigDir()
if err != nil {
return fmt.Errorf("config: error encoding config into json: %v", err)
return fmt.Errorf("config: error getting config directory: %v", err)
}
err = os.WriteFile(filepath, b, 0666)
err = os.MkdirAll(dir, 0750)
if err != nil {
return fmt.Errorf("config: error writing config file: %v", err)
return fmt.Errorf("config: error making config directory: %v", err)
}
fp := filepath.Join(dir, configFile)
err = a.save(fp)
if err != nil {
return fmt.Errorf("config: error saving config: %v", err)
}
return nil
}
func (app *App) save(filepath string) error {
b, err := json.MarshalIndent(app, "", "\t")
if err != nil {
return fmt.Errorf("error encoding config into json: %v", err)
}
err = os.WriteFile(filepath, b, 0666)
if err != nil {
return fmt.Errorf("error writing config file: %v", err)
}
return nil