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
	
	 tyler
						tyler