Skip to content

Commit

Permalink
implement hybrid and server application types
Browse files Browse the repository at this point in the history
  • Loading branch information
tmclane committed May 1, 2024
1 parent a0e05e6 commit cc6158b
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 0 deletions.
209 changes: 209 additions & 0 deletions v2/internal/app/app_hybrid_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//go:build hybrid || server
// +build hybrid server

package app

import (
"context"
"embed"
"fmt"
iofs "io/fs"
"os"
"path/filepath"

"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
"github.com/wailsapp/wails/v2/internal/frontend/hybrid"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/options"
)

// App defines a Wails application structure
type App struct {
frontend frontend.Frontend
logger *logger.Logger
options *options.App

menuManager *menumanager.Manager

// Indicates if the app is in debug mode
debug bool

// OnStartup/OnShutdown
startupCallback func(ctx context.Context)
shutdownCallback func(ctx context.Context)
ctx context.Context
}

func (a *App) Run() error {
err := a.frontend.Run(a.ctx)
if a.shutdownCallback != nil {
a.shutdownCallback(a.ctx)
}
return err
}

func (a *App) Shutdown() {
if a.shutdownCallback != nil {
a.shutdownCallback(a.ctx)
}
a.frontend.Quit()
}

// CreateApp creates the app!
func CreateApp(appoptions *options.App) (*App, error) {
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)

// If no assetdir has been defined, let's try to infer it from the project root and the asset FS.
assetdir, err := tryInferAssetDirFromFS(appoptions.Assets)
if err != nil {
return nil, err
}

host := "localhost"
port := int32(3112)
if appoptions.Server != nil {
host = appoptions.Server.Host
port = appoptions.Server.Port
}

serverURI := fmt.Sprintf("%s:%d", host, port)
ctx := context.Background()
ctx = context.WithValue(ctx, "starturl", serverURI)

// This is to enable a backend server
// ctx = context.WithValue(ctx, "frontenddevserverurl", serverURI)

myLogger.Info("Frontend available at '%s'", serverURI)

// configure devserver
ctx = context.WithValue(ctx, "devserver", fmt.Sprintf("%s:%d", host, port))

// Let's override the assets to serve from on disk, if needed
absdir, err := filepath.Abs(assetdir)
if err != nil {
return nil, err
}

myLogger.Info("Serving assets from disk: %s", absdir)
appoptions.Assets = os.DirFS(absdir)

ctx = context.WithValue(ctx, "assetdir", assetdir)

// Attach logger to context
ctx = context.WithValue(ctx, "logger", myLogger)
ctx = context.WithValue(ctx, "buildtype", "hybrid")

// Preflight checks
err = PreflightChecks(appoptions, myLogger)
if err != nil {
return nil, err
}

// Merge default options

options.MergeDefaults(appoptions)

var menuManager *menumanager.Manager

// Process the application menu
if appoptions.Menu != nil {
// Create the menu manager
menuManager = menumanager.NewManager()
err = menuManager.SetApplicationMenu(appoptions.Menu)
if err != nil {
return nil, err
}
}

// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.OnStartup, appoptions.OnShutdown, appoptions.OnDomReady}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)

err = generateBindings(appBindings)
if err != nil {
return nil, err
}
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler)
appFrontend := hybrid.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
eventHandler.AddFrontend(appFrontend)

result := &App{
ctx: ctx,
frontend: appFrontend,
logger: myLogger,
menuManager: menuManager,
startupCallback: appoptions.OnStartup,
shutdownCallback: appoptions.OnShutdown,
}

result.options = appoptions

return result, nil

}

func generateBindings(bindings *binding.Bindings) error {

cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}

targetDir := filepath.Join(projectConfig.WailsJSDir, "wailsjs", "go")
err = os.RemoveAll(targetDir)
if err != nil {
return err
}
_ = fs.MkDirs(targetDir)

err = bindings.GenerateGoBindings(targetDir)
if err != nil {
return err
}
return nil
}

func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
if _, isEmbedFs := assets.(embed.FS); !isEmbedFs {
// We only infer the assetdir for embed.FS assets
return "", nil
}

path, err := fs.FindPathToFile(assets, "index.html")
if err != nil {
return "", err
}

path, err = filepath.Abs(path)
if err != nil {
return "", err
}

if _, err := os.Stat(filepath.Join(path, "index.html")); err != nil {
if os.IsNotExist(err) {
err = fmt.Errorf(
"inferred assetdir '%s' does not exist or does not contain an 'index.html' file, "+
"please specify it with -assetdir or set it in wails.json",
path)
}
return "", err
}

return path, nil
}
22 changes: 22 additions & 0 deletions v2/internal/frontend/hybrid/hybrid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//go:build hybrid
// +build hybrid

package hybrid

import (
"context"

"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)

// New returns a new Hybrid frontend
// A hybrid Frontend implementation contains a devserver.Frontend wrapping a desktop.Frontend
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
appFrontend := desktop.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
return devserver.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher, nil, appFrontend)
}
21 changes: 21 additions & 0 deletions v2/internal/frontend/hybrid/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build server
// +build server

package hybrid

import (
"context"

"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/null"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)

// New returns a new Server frontend
// A server Frontend implementation contains a devserver.Frontend wrapping a null.Frontend
func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
return devserver.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher, nil, null.NewFrontend(ctx))
}

0 comments on commit cc6158b

Please sign in to comment.