diff --git a/app.go b/app.go index 31bb295..3675453 100644 --- a/app.go +++ b/app.go @@ -7,8 +7,9 @@ import ( "log" "os" "sync" + "time" - "github.com/tylertravisty/go-utils/random" + "github.com/tylertravisty/rum-goggles/internal/api" "github.com/tylertravisty/rum-goggles/internal/config" rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go" ) @@ -22,17 +23,20 @@ type App struct { ctx context.Context cfg *config.App cfgMu sync.Mutex + api *api.Api + apiMu sync.Mutex } // NewApp creates a new App application struct func NewApp() *App { - return &App{} + return &App{api: api.NewApi()} } // startup is called when the app starts. The context is saved // so we can call the runtime methods 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 @@ -55,7 +59,7 @@ func (a *App) loadConfig() error { } func (a *App) newConfig() error { - cfg := &config.App{Channels: []config.Channel{}} + cfg := &config.App{Channels: map[string]config.Channel{}} err := cfg.Save(configFilepath) if err != nil { return fmt.Errorf("error saving new config: %v", err) @@ -93,11 +97,15 @@ func (a *App) AddChannel(url string) (*config.App, error) { name = resp.ChannelName } - channel := config.Channel{ApiUrl: url, Name: name} - a.cfgMu.Lock() defer a.cfgMu.Unlock() - a.cfg.Channels = append(a.cfg.Channels, channel) + _, 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") + } + err = a.cfg.Save(configFilepath) if err != nil { // TODO: log error @@ -108,26 +116,24 @@ func (a *App) AddChannel(url string) (*config.App, error) { return a.cfg, nil } -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - random, err := random.String(10) - if err != nil { - fmt.Println("random.Alphabetic err:", err) - return name +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) + return fmt.Errorf("channel CID not found") } - //return fmt.Sprintf("Hello %s, It's show time!", name) - return fmt.Sprintf("Hello %s, It's show time!", random) + + err := a.api.Start(channel.ApiUrl, channel.Interval*time.Second) + if err != nil { + // TODO: log error + fmt.Println("error starting api:", err) + return fmt.Errorf("error starting API") + } + + return nil } -// func (a *App) QueryAPI(url string) (*rumblelivestreamlib.Followers, error) { -// fmt.Println("QueryAPI") -// client := rumblelivestreamlib.Client{StreamKey: url} -// resp, err := client.Request() -// if err != nil { -// // TODO: log error -// fmt.Println("client.Request err:", err) -// return nil, fmt.Errorf("API request failed") -// } - -// return &resp.Followers, nil -// } +func (a *App) StopApi() { + a.api.Stop() +} diff --git a/frontend/src/components/ChannelList.jsx b/frontend/src/components/ChannelList.jsx index ea11bca..8a64348 100644 --- a/frontend/src/components/ChannelList.jsx +++ b/frontend/src/components/ChannelList.jsx @@ -3,8 +3,13 @@ import './ChannelList.css'; function ChannelList(props) { const sortChannelsAlpha = () => { - let sorted = [...props.channels].sort((a, b) => - a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1 + let keys = Object.keys(props.channels); + // let sorted = [...props.channels].sort((a, b) => + // a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1 + // ); + + let sorted = [...keys].sort((a, b) => + props.channels[a].name.toLowerCase() > props.channels[b].name.toLowerCase() ? 1 : -1 ); return sorted; }; @@ -17,9 +22,9 @@ function ChannelList(props) {
))} diff --git a/frontend/src/screens/Dashboard.jsx b/frontend/src/screens/Dashboard.jsx index 98c3ff0..643a54a 100644 --- a/frontend/src/screens/Dashboard.jsx +++ b/frontend/src/screens/Dashboard.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Navigate, useLocation, useNavigate } from 'react-router-dom'; -import { Start, Stop } from '../../wailsjs/go/api/Api'; +import { StartApi, StopApi } from '../../wailsjs/go/main/App'; import './Dashboard.css'; import { EventsEmit, EventsOn } from '../../wailsjs/runtime/runtime'; @@ -17,7 +17,7 @@ function Dashboard() { const navigate = useNavigate(); const [refresh, setRefresh] = useState(false); const [active, setActive] = useState(false); - const [streamKey, setStreamKey] = useState(location.state.streamKey); + const [cid, setCID] = useState(location.state.cid); const [username, setUsername] = useState(''); const [channelName, setChannelName] = useState(''); const [followers, setFollowers] = useState({}); @@ -41,7 +41,7 @@ function Dashboard() { useEffect(() => { console.log('use effect start'); - Start(streamKey); + StartApi(cid); setActive(true); EventsOn('QueryResponse', (response) => { @@ -77,7 +77,7 @@ function Dashboard() { }, []); const home = () => { - Stop() + StopApi() .then(() => setActive(false)) .then(() => { navigate(NavSignIn); @@ -89,7 +89,7 @@ function Dashboard() { const startQuery = () => { console.log('start'); - Start(streamKey) + StartApi(cid) .then(() => { setActive(true); }) @@ -100,7 +100,7 @@ function Dashboard() { const stopQuery = () => { console.log('stop'); - Stop().then(() => { + StopApi().then(() => { setActive(false); }); }; diff --git a/frontend/src/screens/SignIn.jsx b/frontend/src/screens/SignIn.jsx index a8a8072..39538c6 100644 --- a/frontend/src/screens/SignIn.jsx +++ b/frontend/src/screens/SignIn.jsx @@ -8,7 +8,7 @@ import ChannelList from '../components/ChannelList'; function SignIn() { const navigate = useNavigate(); - const [config, setConfig] = useState({ channels: [] }); + const [config, setConfig] = useState({ channels: {} }); const [streamKey, setStreamKey] = useState(''); const updateStreamKey = (event) => setStreamKey(event.target.value); const [showStreamKey, setShowStreamKey] = useState(false); @@ -17,10 +17,10 @@ function SignIn() { useEffect(() => { Config() .then((response) => { - console.log(response); setConfig(response); }) .catch((err) => { + // TODO: display error to user console.log('error getting config', err); }); }, []); @@ -37,8 +37,8 @@ function SignIn() { }); }; - const openStreamDashboard = (key) => { - navigate(NavDashboard, { state: { streamKey: key } }); + const openStreamDashboard = (cid) => { + navigate(NavDashboard, { state: { cid: cid } }); }; return ( diff --git a/internal/api/api.go b/internal/api/api.go index 99a78fa..77e0483 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -11,27 +11,22 @@ import ( ) type Api struct { - ctx context.Context - cancel context.CancelFunc - cancelMu sync.Mutex - querying bool - queryingMu sync.Mutex - queryInterval time.Duration - queryIntervalMu sync.Mutex + ctx context.Context + cancel context.CancelFunc + cancelMu sync.Mutex + querying bool + queryingMu sync.Mutex } func NewApi() *Api { - return &Api{queryInterval: 10 * time.Second} + return &Api{} } func (a *Api) Startup(ctx context.Context) { a.ctx = ctx - runtime.EventsOn(ctx, "StopQuery", func(optionalData ...interface{}) { - a.Stop() - }) } -func (a *Api) Start(url string) error { +func (a *Api) Start(url string, interval time.Duration) error { fmt.Println("Api.Start") if url == "" { return fmt.Errorf("empty stream key") @@ -48,7 +43,7 @@ func (a *Api) Start(url string) error { a.cancelMu.Lock() a.cancel = cancel a.cancelMu.Unlock() - a.start(ctx, url) + go a.start(ctx, url, interval) } else { fmt.Println("Querying already started") } @@ -65,12 +60,9 @@ func (a *Api) Stop() { a.cancelMu.Unlock() } -func (a *Api) start(ctx context.Context, url string) { +func (a *Api) start(ctx context.Context, url string, interval time.Duration) { for { a.query(url) - a.queryIntervalMu.Lock() - interval := a.queryInterval - a.queryIntervalMu.Unlock() timer := time.NewTimer(interval) select { case <-ctx.Done(): diff --git a/internal/config/config.go b/internal/config/config.go index 7e8e351..d82d435 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,15 +4,39 @@ import ( "encoding/json" "fmt" "os" + "time" + + "github.com/tylertravisty/go-utils/random" +) + +const ( + CIDLen = 8 + DefaultInterval = 10 ) type Channel struct { - ApiUrl string `json:"api_url"` - Name string `json:"name"` + ID string `json:"id"` + ApiUrl string `json:"api_url"` + Name string `json:"name"` + Interval time.Duration `json:"interval"` +} + +func (a *App) NewChannel(url string, name string) (string, error) { + for { + id, err := random.String(CIDLen) + if err != nil { + return "", fmt.Errorf("config: error generating ID: %v", err) + } + + if _, exists := a.Channels[id]; !exists { + a.Channels[id] = Channel{id, url, name, DefaultInterval} + return id, nil + } + } } type App struct { - Channels []Channel `json:"channels"` + Channels map[string]Channel `json:"channels"` } func Load(filepath string) (*App, error) { diff --git a/main.go b/main.go index a848514..e9600bb 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,8 @@ package main import ( - "context" "embed" - "github.com/tylertravisty/rum-goggles/internal/api" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -15,7 +13,6 @@ var assets embed.FS func main() { // Create an instance of the app structure - api := api.NewApi() app := NewApp() // Create application with options @@ -27,13 +24,9 @@ func main() { Assets: assets, }, BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, - OnStartup: func(ctx context.Context) { - app.startup(ctx) - api.Startup(ctx) - }, + OnStartup: app.startup, Bind: []interface{}{ app, - api, }, })