Implements a self-hosted update mechanism, allowing the application to be updated from a corporate network share without relying on third-party services. This includes: - Functionality to check for updates - Download installers - Verify checksums - Install updates with UAC elevation Configuration is managed via the config.ini file, with automatic checks on startup. A new settings UI is also included.
212 lines
6.6 KiB
Go
212 lines
6.6 KiB
Go
// Package main provides the core EMLy application.
|
|
// EMLy is a desktop email viewer for .eml and .msg files built with Wails v2.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"emly/backend/utils"
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
// =============================================================================
|
|
// App - Core Application Structure
|
|
// =============================================================================
|
|
|
|
// App is the main application struct that holds the application state and
|
|
// provides methods that are exposed to the frontend via Wails bindings.
|
|
//
|
|
// The struct manages:
|
|
// - Application context for Wails runtime calls
|
|
// - File paths for startup and currently loaded emails
|
|
// - Tracking of open viewer windows to prevent duplicates
|
|
type App struct {
|
|
// ctx is the Wails application context, used for runtime calls like dialogs
|
|
ctx context.Context
|
|
|
|
// StartupFilePath is set when the app is launched with an email file argument
|
|
StartupFilePath string
|
|
|
|
// CurrentMailFilePath tracks the currently loaded mail file path
|
|
// Used for bug reports to include the relevant email file
|
|
CurrentMailFilePath string
|
|
|
|
// openImages tracks which images are currently open in viewer windows
|
|
// The key is the filename, preventing duplicate viewers for the same file
|
|
openImagesMux sync.Mutex
|
|
openImages map[string]bool
|
|
|
|
// openPDFs tracks which PDFs are currently open in viewer windows
|
|
openPDFsMux sync.Mutex
|
|
openPDFs map[string]bool
|
|
|
|
// openEMLs tracks which EML attachments are currently open in viewer windows
|
|
openEMLsMux sync.Mutex
|
|
openEMLs map[string]bool
|
|
}
|
|
|
|
// =============================================================================
|
|
// Constructor & Lifecycle
|
|
// =============================================================================
|
|
|
|
// NewApp creates and initializes a new App instance.
|
|
// This is called from main.go before the Wails application starts.
|
|
func NewApp() *App {
|
|
return &App{
|
|
openImages: make(map[string]bool),
|
|
openPDFs: make(map[string]bool),
|
|
openEMLs: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
// startup is called by Wails when the application starts.
|
|
// It receives the application context which is required for Wails runtime calls.
|
|
//
|
|
// This method:
|
|
// - Saves the context for later use
|
|
// - Syncs CurrentMailFilePath with StartupFilePath if a file was opened via CLI
|
|
// - Logs the startup mode (main app vs viewer window)
|
|
func (a *App) startup(ctx context.Context) {
|
|
a.ctx = ctx
|
|
|
|
// Sync CurrentMailFilePath with StartupFilePath if a file was opened via command line
|
|
if a.StartupFilePath != "" {
|
|
a.CurrentMailFilePath = a.StartupFilePath
|
|
}
|
|
|
|
// Check if this instance is running as a viewer (image/PDF) rather than main app
|
|
isViewer := false
|
|
for _, arg := range os.Args {
|
|
if strings.Contains(arg, "--view-image") || strings.Contains(arg, "--view-pdf") {
|
|
isViewer = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if isViewer {
|
|
Log("Viewer instance started")
|
|
} else {
|
|
Log("EMLy main application started")
|
|
|
|
// Automatic update check on startup (if enabled)
|
|
go func() {
|
|
// Wait 5 seconds after startup to avoid blocking the UI
|
|
time.Sleep(5 * time.Second)
|
|
|
|
config := a.GetConfig()
|
|
if config == nil {
|
|
log.Printf("Failed to load config for auto-update check")
|
|
return
|
|
}
|
|
|
|
// Check if auto-update is enabled
|
|
if config.EMLy.UpdateAutoCheck == "true" && config.EMLy.UpdateCheckEnabled == "true" {
|
|
log.Println("Performing automatic update check...")
|
|
status, err := a.CheckForUpdates()
|
|
if err != nil {
|
|
log.Printf("Auto-update check failed: %v", err)
|
|
return
|
|
}
|
|
|
|
// Emit event if update is available
|
|
if status.UpdateAvailable {
|
|
log.Printf("Update available: %s -> %s", status.CurrentVersion, status.AvailableVersion)
|
|
runtime.EventsEmit(ctx, "update:available", status)
|
|
} else {
|
|
log.Println("No updates available")
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// shutdown is called by Wails when the application is closing.
|
|
// Used for cleanup operations.
|
|
func (a *App) shutdown(ctx context.Context) {
|
|
// Best-effort cleanup - currently no resources require explicit cleanup
|
|
}
|
|
|
|
// QuitApp terminates the application.
|
|
// It first calls Wails Quit to properly close the window,
|
|
// then forces an exit with a specific code.
|
|
func (a *App) QuitApp() {
|
|
runtime.Quit(a.ctx)
|
|
// Exit with code 133 (133 + 5 = 138, SIGTRAP-like exit)
|
|
os.Exit(133)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Configuration Management
|
|
// =============================================================================
|
|
|
|
// GetConfig loads and returns the application configuration from config.ini.
|
|
// Returns nil if the configuration cannot be loaded.
|
|
func (a *App) GetConfig() *utils.Config {
|
|
cfgPath := utils.DefaultConfigPath()
|
|
cfg, err := utils.LoadConfig(cfgPath)
|
|
if err != nil {
|
|
Log("Failed to load config:", err)
|
|
return nil
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// SaveConfig persists the provided configuration to config.ini.
|
|
// Returns an error if saving fails.
|
|
func (a *App) SaveConfig(cfg *utils.Config) error {
|
|
cfgPath := utils.DefaultConfigPath()
|
|
if err := utils.SaveConfig(cfgPath, cfg); err != nil {
|
|
Log("Failed to save config:", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Startup File Management
|
|
// =============================================================================
|
|
|
|
// GetStartupFile returns the file path if the app was launched with an email file argument.
|
|
// Returns an empty string if no file was specified at startup.
|
|
func (a *App) GetStartupFile() string {
|
|
return a.StartupFilePath
|
|
}
|
|
|
|
// SetCurrentMailFilePath updates the path of the currently loaded mail file.
|
|
// This is called when the user opens a file via the file dialog.
|
|
func (a *App) SetCurrentMailFilePath(filePath string) {
|
|
a.CurrentMailFilePath = filePath
|
|
}
|
|
|
|
// GetCurrentMailFilePath returns the path of the currently loaded mail file.
|
|
// Used by bug reports to include the relevant email file.
|
|
func (a *App) GetCurrentMailFilePath() string {
|
|
return a.CurrentMailFilePath
|
|
}
|
|
|
|
// =============================================================================
|
|
// System Information
|
|
// =============================================================================
|
|
|
|
// GetMachineData retrieves system information about the current machine.
|
|
// Returns hostname, OS version, hardware ID, etc.
|
|
func (a *App) GetMachineData() *utils.MachineInfo {
|
|
data, _ := utils.GetMachineInfo()
|
|
return data
|
|
}
|
|
|
|
// IsDebuggerRunning checks if a debugger is attached to the application.
|
|
// Used for anti-debugging protection in production builds.
|
|
func (a *App) IsDebuggerRunning() bool {
|
|
if a == nil {
|
|
return false
|
|
}
|
|
return utils.IsDebugged()
|
|
}
|