feat: initial commit
This commit is contained in:
172
pkg/wails3kit/updates/platform/installer.go
Normal file
172
pkg/wails3kit/updates/platform/installer.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
const helperFlag = "--wails3kit-update-helper"
|
||||
|
||||
type Detector func(app updates.AppDescriptor) (updates.InstallRoot, error)
|
||||
|
||||
type Installer struct {
|
||||
detector Detector
|
||||
}
|
||||
|
||||
func New(detector Detector) *Installer {
|
||||
return &Installer{detector: detector}
|
||||
}
|
||||
|
||||
func (installer *Installer) DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if installer.detector == nil {
|
||||
return updates.InstallRoot{}, errors.New("missing install root detector")
|
||||
}
|
||||
return installer.detector(app)
|
||||
}
|
||||
|
||||
func (installer *Installer) Stage(ctx context.Context, root updates.InstallRoot, file updates.DownloadedFile, release updates.Release) (updates.StagedArtifact, error) {
|
||||
stageDir, bundleManifest, err := extractBundle(ctx, file.Path, release.Artifact.Format)
|
||||
if err != nil {
|
||||
return updates.StagedArtifact{}, err
|
||||
}
|
||||
|
||||
return updates.StagedArtifact{
|
||||
Path: stageDir,
|
||||
Root: root,
|
||||
Release: release,
|
||||
Bundle: bundleManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (installer *Installer) SpawnApplyAndRestart(_ context.Context, req updates.ApplyRequest) error {
|
||||
helperDir, err := os.MkdirTemp("", "wails3kit-helper-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helperBinary := filepath.Join(helperDir, filepath.Base(req.App.ExecutablePath))
|
||||
if err := copyFile(req.App.ExecutablePath, helperBinary, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := helperRequest{
|
||||
InstallRoot: req.Root.Path,
|
||||
StagedPath: req.Staged.Path,
|
||||
Bundle: req.Staged.Bundle,
|
||||
Args: append([]string(nil), req.App.Args...),
|
||||
WorkingDirectory: req.App.WorkingDirectory,
|
||||
}
|
||||
|
||||
requestPath := filepath.Join(helperDir, "request.json")
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(requestPath, encoded, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
command := exec.Command(helperBinary, helperFlag, requestPath)
|
||||
command.Dir = helperDir
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Start()
|
||||
}
|
||||
|
||||
func MaybeRun(args []string) (bool, error) {
|
||||
if len(args) < 3 || args[1] != helperFlag {
|
||||
return false, nil
|
||||
}
|
||||
return true, runHelper(args[2])
|
||||
}
|
||||
|
||||
func NewCurrent() (*Installer, error) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return New(detectDarwinInstallRoot), nil
|
||||
case "windows":
|
||||
return New(detectDirectoryInstallRoot), nil
|
||||
case "linux":
|
||||
return New(detectDirectoryInstallRoot), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
type helperRequest struct {
|
||||
InstallRoot string `json:"installRoot"`
|
||||
StagedPath string `json:"stagedPath"`
|
||||
Bundle updates.BundleManifest `json:"bundle"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
}
|
||||
|
||||
func runHelper(requestPath string) error {
|
||||
bytes, err := os.ReadFile(requestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var request helperRequest
|
||||
if err := json.Unmarshal(bytes, &request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(15 * time.Second)
|
||||
var applyErr error
|
||||
for time.Now().Before(deadline) {
|
||||
applyErr = applyBundle(request)
|
||||
if applyErr == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if applyErr != nil {
|
||||
return applyErr
|
||||
}
|
||||
|
||||
relaunch := filepath.Join(request.InstallRoot, filepath.FromSlash(request.Bundle.EntryPoint))
|
||||
command := exec.Command(relaunch, request.Args...)
|
||||
if request.WorkingDirectory != "" {
|
||||
command.Dir = request.WorkingDirectory
|
||||
} else {
|
||||
command.Dir = request.InstallRoot
|
||||
}
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Start()
|
||||
}
|
||||
|
||||
func detectDirectoryInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
return updates.InstallRoot{Path: filepath.Dir(app.ExecutablePath)}, nil
|
||||
}
|
||||
|
||||
func detectDarwinInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
|
||||
current := filepath.Dir(app.ExecutablePath)
|
||||
for {
|
||||
if filepath.Ext(current) == ".app" {
|
||||
return updates.InstallRoot{Path: current}, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
return updates.InstallRoot{}, fmt.Errorf("unable to locate .app bundle from %s", app.ExecutablePath)
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user