Fixed concurrency issue; moved api instance into app
This commit is contained in:
parent
0e97fe4ea7
commit
d329c36653
58
app.go
58
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()
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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) {
|
||||
|
|
9
main.go
9
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,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue