Fixed concurrency issue; moved api instance into app

This commit is contained in:
tyler 2023-12-21 15:20:43 -05:00
parent 0e97fe4ea7
commit d329c36653
7 changed files with 88 additions and 68 deletions

58
app.go
View file

@ -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()
}

View file

@ -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) {
<div className='channel' style={index === 0 ? { borderTop: 'none' } : {}}>
<button
className='channel-button'
onClick={() => props.openStreamDashboard(channel.api_url)}
onClick={() => props.openStreamDashboard(props.channels[channel].id)}
>
{channel.name}
{props.channels[channel].name}
</button>
</div>
))}

View file

@ -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);
});
};

View file

@ -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 (

View file

@ -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():

View file

@ -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) {

View file

@ -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,
},
})