2021-05-02 12:29:43 +00:00
package main
import (
"embed"
"fmt"
2022-01-27 15:49:18 +00:00
"log"
2021-09-18 20:06:22 +00:00
"math/rand"
2022-01-02 10:45:09 +00:00
"os"
2022-02-08 14:13:45 +00:00
"path/filepath"
2021-05-02 12:29:43 +00:00
"time"
2022-02-08 14:13:45 +00:00
"github.com/cockroachdb/pebble"
2022-11-16 11:23:54 +00:00
"github.com/urfave/cli/v2"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"go.uber.org/zap"
2022-01-27 15:49:18 +00:00
"go.uber.org/zap/zapcore"
2022-11-16 11:23:54 +00:00
kv "github.com/strimertul/kilovolt/v8"
2022-01-02 10:45:09 +00:00
2021-05-02 12:29:43 +00:00
"github.com/strimertul/strimertul/modules"
2022-11-16 11:23:54 +00:00
"github.com/strimertul/strimertul/modules/database"
2021-11-23 10:34:02 +00:00
"github.com/strimertul/strimertul/modules/http"
2021-05-02 12:29:43 +00:00
"github.com/strimertul/strimertul/modules/loyalty"
2021-05-14 14:37:54 +00:00
"github.com/strimertul/strimertul/modules/stulbe"
2021-05-14 11:15:38 +00:00
"github.com/strimertul/strimertul/modules/twitch"
2021-05-02 12:29:43 +00:00
2021-06-05 23:18:31 +00:00
_ "net/http/pprof"
2021-05-02 12:29:43 +00:00
)
2022-11-16 11:23:54 +00:00
const databaseDefaultDriver = "pebble"
2021-05-02 12:29:43 +00:00
2021-12-06 13:47:38 +00:00
var appVersion = "v0.0.0-UNKNOWN"
2022-11-16 11:23:54 +00:00
var logger * zap . Logger
2021-05-02 12:29:43 +00:00
//go:embed frontend/dist/*
var frontend embed . FS
2021-12-09 10:45:10 +00:00
type ModuleConstructor = func ( manager * modules . Manager ) error
var moduleList = map [ modules . ModuleID ] ModuleConstructor {
modules . ModuleStulbe : stulbe . Register ,
modules . ModuleLoyalty : loyalty . Register ,
2022-01-12 11:02:54 +00:00
modules . ModuleTwitch : twitch . Register ,
2021-12-09 10:45:10 +00:00
}
2022-02-01 11:35:34 +00:00
type dbOptions struct {
backupDir string
backupInterval int
2022-02-01 13:55:05 +00:00
maxBackups int
2022-02-01 11:35:34 +00:00
}
2021-05-02 12:29:43 +00:00
func main ( ) {
2022-11-16 11:23:54 +00:00
app := & cli . App {
Name : "strimertul" ,
Usage : "the small broadcasting suite for Twitch" ,
Version : appVersion ,
Action : cliMain ,
Flags : [ ] cli . Flag {
& cli . BoolFlag { Name : "debug" , Aliases : [ ] string { "d" } , Usage : "print more logs (for debugging)" , Value : false } ,
& cli . BoolFlag { Name : "json-log" , Usage : "print logs in JSON format" , Value : false } ,
& cli . StringFlag { Name : "driver" , Usage : "specify database driver" , Value : "auto" } ,
& cli . StringFlag { Name : "database-dir" , Aliases : [ ] string { "db-dir" } , Usage : "specify database directory" , Value : "data" } ,
& cli . StringFlag { Name : "backup-dir" , Aliases : [ ] string { "b-dir" } , Usage : "specify backup directory" , Value : "backups" } ,
& cli . IntFlag { Name : "backup-interval" , Aliases : [ ] string { "b-i" } , Usage : "specify backup interval (in minutes, 0 to disable)" , Value : 60 } ,
& cli . IntFlag { Name : "max-backups" , Aliases : [ ] string { "b-max" } , Usage : "maximum number of backups to keep, older ones will be deleted, set to 0 to keep all" , Value : 20 } ,
} ,
Commands : [ ] * cli . Command {
{
Name : "import" ,
Usage : "import database from JSON file" ,
ArgsUsage : "[-f input.json]" ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "file" , Aliases : [ ] string { "f" } , Usage : "file to open" , DefaultText : "STDIN" } ,
} ,
Action : cliImport ,
} ,
{
Name : "export" ,
Usage : "export database as JSON file" ,
ArgsUsage : "[-f output.json]" ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "file" , Aliases : [ ] string { "f" } , Usage : "file to save to" , DefaultText : "STDOUT" } ,
} ,
Action : cliExport ,
} ,
{
Name : "restore" ,
Usage : "restore database from backup" ,
ArgsUsage : "[-f backup.db]" ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "file" , Aliases : [ ] string { "f" } , Usage : "backup to open" , DefaultText : "STDOUT" } ,
} ,
Action : cliRestore ,
} ,
} ,
Before : func ( ctx * cli . Context ) error {
// Seed RNG
rand . Seed ( time . Now ( ) . UnixNano ( ) )
// Initialize logger with global flags
initLogger ( ctx . Bool ( "debug" ) , ctx . Bool ( "json-log" ) )
return nil
} ,
After : func ( ctx * cli . Context ) error {
logger . Sync ( )
zap . RedirectStdLog ( logger ) ( )
return nil
} ,
}
if err := app . Run ( os . Args ) ; err != nil {
log . Fatal ( err )
}
}
func initLogger ( debug bool , json bool ) {
if debug {
2022-01-27 15:49:18 +00:00
cfg := zap . NewDevelopmentConfig ( )
2022-11-16 11:23:54 +00:00
if json {
2022-01-27 15:49:18 +00:00
cfg . Encoding = "json"
}
logger , _ = cfg . Build ( )
2021-12-07 00:22:45 +00:00
} else {
2022-01-27 15:49:18 +00:00
cfg := zap . NewProductionConfig ( )
2022-11-16 11:23:54 +00:00
if ! json {
2022-01-27 15:49:18 +00:00
cfg . Encoding = "console"
cfg . EncoderConfig . EncodeTime = zapcore . ISO8601TimeEncoder
cfg . EncoderConfig . CallerKey = zapcore . OmitKey
}
logger , _ = cfg . Build ( )
2021-05-02 19:33:37 +00:00
}
2022-11-16 11:23:54 +00:00
}
2021-05-02 19:33:37 +00:00
2022-11-16 11:23:54 +00:00
func getDatabaseDriver ( ctx * cli . Context ) string {
driver := ctx . String ( "driver" )
if driver != "auto" {
return driver
2021-11-23 10:34:02 +00:00
}
2022-11-16 11:23:54 +00:00
dbdir := ctx . String ( "database-dir" )
file , err := os . ReadFile ( filepath . Join ( dbdir , "stul-driver" ) )
if err != nil {
// No driver file found (or file corrupted), use default driver
return databaseDefaultDriver
}
return string ( file )
}
func cliMain ( ctx * cli . Context ) error {
2021-11-23 10:34:02 +00:00
// Create module manager
2022-01-27 15:49:18 +00:00
manager := modules . NewManager ( logger )
2021-05-02 12:29:43 +00:00
2022-02-01 11:35:34 +00:00
// Make KV hub
var hub * kv . Hub
var err error
2022-11-16 11:23:54 +00:00
dbopts := dbOptions {
backupDir : ctx . String ( "backup-dir" ) ,
backupInterval : ctx . Int ( "backup-interval" ) ,
maxBackups : ctx . Int ( "max-backups" ) ,
2022-02-08 14:13:45 +00:00
}
2022-11-16 11:23:54 +00:00
dbdir := ctx . String ( "database-dir" )
driver := getDatabaseDriver ( ctx )
logger . Info ( "opening database" , zap . String ( "driver" , driver ) )
switch driver {
2022-02-01 11:35:34 +00:00
case "badger" :
2022-11-16 11:23:54 +00:00
return cli . Exit ( "Badger is not supported anymore as a database driver" , 64 )
2022-02-08 14:13:45 +00:00
case "pebble" :
var db * pebble . DB
2022-11-16 11:23:54 +00:00
db , hub , err = makePebbleHub ( dbdir , dbopts )
2022-02-08 14:13:45 +00:00
if err != nil {
2022-11-16 11:23:54 +00:00
return fatalError ( err , "failed to open database" )
2022-02-08 14:13:45 +00:00
}
defer pebbleClose ( db )
2022-02-01 11:35:34 +00:00
default :
2022-11-16 11:23:54 +00:00
return cli . Exit ( fmt . Sprintf ( "Unknown database driver: %s" , driver ) , 64 )
2021-05-02 12:29:43 +00:00
}
2022-02-01 11:35:34 +00:00
go hub . Run ( )
db , err := database . NewDBModule ( hub , manager )
2022-11-16 11:23:54 +00:00
if err != nil {
return fatalError ( err , "Failed to initialize database module" )
2022-01-02 10:45:09 +00:00
}
2021-12-09 10:45:10 +00:00
// Set meta keys
2022-02-01 11:35:34 +00:00
_ = db . PutKey ( "stul-meta/version" , appVersion )
2021-05-02 12:29:43 +00:00
2021-12-09 10:45:10 +00:00
for module , constructor := range moduleList {
err := constructor ( manager )
2021-11-19 18:37:42 +00:00
if err != nil {
2022-02-01 11:35:34 +00:00
logger . Error ( "Could not register module" , zap . String ( "module" , string ( module ) ) , zap . Error ( err ) )
2021-11-19 18:37:42 +00:00
} else {
2021-12-09 10:45:10 +00:00
//goland:noinspection GoDeferInLoop
defer func ( ) {
if err := manager . Modules [ module ] . Close ( ) ; err != nil {
2022-01-27 15:49:18 +00:00
logger . Error ( "Could not close module" , zap . String ( "module" , string ( module ) ) , zap . Error ( err ) )
2021-12-09 10:45:10 +00:00
}
} ( )
2021-05-02 12:29:43 +00:00
}
}
// Create logger and endpoints
2021-11-23 10:34:02 +00:00
httpServer , err := http . NewServer ( manager )
2022-11-16 11:23:54 +00:00
if err != nil {
return fatalError ( err , "Could not initialize http server" )
}
2021-12-09 12:32:58 +00:00
defer func ( ) {
if err := httpServer . Close ( ) ; err != nil {
2022-01-27 15:49:18 +00:00
logger . Error ( "Could not close DB" , zap . Error ( err ) )
2021-12-09 12:32:58 +00:00
}
} ( )
2021-05-02 12:29:43 +00:00
2022-11-16 11:23:54 +00:00
// Run HTTP server
go failOnError ( httpServer . Listen ( ) , "HTTP server stopped" )
// Create an instance of the app structure
app := NewApp ( )
// Create application with options
err = wails . Run ( & options . App {
Title : "strimertul" ,
Width : 1024 ,
Height : 768 ,
AssetServer : & assetserver . Options {
Assets : frontend ,
} ,
BackgroundColour : & options . RGBA { R : 27 , G : 38 , B : 54 , A : 1 } ,
OnStartup : app . startup ,
Bind : [ ] interface { } {
app ,
} ,
} )
2021-05-02 12:29:43 +00:00
2022-11-16 11:23:54 +00:00
if err != nil {
return fatalError ( err , "App exited unexpectedly" )
}
return nil
}
2021-05-02 12:29:43 +00:00
2022-11-16 11:23:54 +00:00
func fatalError ( err error , text string ) error {
return cli . Exit ( fmt . Errorf ( "%s: %w" , text , err ) , 1 )
2021-05-02 12:29:43 +00:00
}
func failOnError ( err error , text string ) {
if err != nil {
2022-11-16 11:23:54 +00:00
log . Fatal ( fatalError ( err , text ) )
2021-05-02 12:29:43 +00:00
}
}