feat: Refactor of Go backend code
This commit is contained in:
429
app_viewer.go
Normal file
429
app_viewer.go
Normal file
@@ -0,0 +1,429 @@
|
||||
// Package main provides viewer window functionality for EMLy.
|
||||
// This file contains methods for opening attachments in viewer windows
|
||||
// or with external applications.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// Viewer Data Types
|
||||
// =============================================================================
|
||||
|
||||
// ImageViewerData contains the data needed to display an image in the viewer window.
|
||||
type ImageViewerData struct {
|
||||
// Data is the base64-encoded image data
|
||||
Data string `json:"data"`
|
||||
// Filename is the original filename of the image
|
||||
Filename string `json:"filename"`
|
||||
}
|
||||
|
||||
// PDFViewerData contains the data needed to display a PDF in the viewer window.
|
||||
type PDFViewerData struct {
|
||||
// Data is the base64-encoded PDF data
|
||||
Data string `json:"data"`
|
||||
// Filename is the original filename of the PDF
|
||||
Filename string `json:"filename"`
|
||||
}
|
||||
|
||||
// ViewerData is a union type that contains either image or PDF viewer data.
|
||||
// Used by the viewer page to determine which type of content to display.
|
||||
type ViewerData struct {
|
||||
// ImageData is set when viewing an image (mutually exclusive with PDFData)
|
||||
ImageData *ImageViewerData `json:"imageData,omitempty"`
|
||||
// PDFData is set when viewing a PDF (mutually exclusive with ImageData)
|
||||
PDFData *PDFViewerData `json:"pdfData,omitempty"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Built-in Viewer Window Methods
|
||||
// =============================================================================
|
||||
|
||||
// OpenEMLWindow opens an EML attachment in a new EMLy window.
|
||||
// The EML data is saved to a temp file and a new EMLy instance is launched.
|
||||
//
|
||||
// This method tracks open EML files to prevent duplicate windows for the same file.
|
||||
// The tracking is released when the viewer window is closed.
|
||||
//
|
||||
// Parameters:
|
||||
// - base64Data: Base64-encoded EML file content
|
||||
// - filename: The original filename of the EML attachment
|
||||
//
|
||||
// Returns:
|
||||
// - error: Error if the file is already open or if launching fails
|
||||
func (a *App) OpenEMLWindow(base64Data string, filename string) error {
|
||||
// Check if this EML is already open
|
||||
a.openEMLsMux.Lock()
|
||||
if a.openEMLs[filename] {
|
||||
a.openEMLsMux.Unlock()
|
||||
return fmt.Errorf("eml '%s' is already open", filename)
|
||||
}
|
||||
a.openEMLs[filename] = true
|
||||
a.openEMLsMux.Unlock()
|
||||
|
||||
// Decode base64 data
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
a.openEMLsMux.Lock()
|
||||
delete(a.openEMLs, filename)
|
||||
a.openEMLsMux.Unlock()
|
||||
return fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// Save to temp file with timestamp to avoid conflicts
|
||||
tempDir := os.TempDir()
|
||||
timestamp := time.Now().Format("20060102_150405")
|
||||
tempFile := filepath.Join(tempDir, fmt.Sprintf("%s_%s_%s", "emly_attachment", timestamp, filename))
|
||||
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
||||
a.openEMLsMux.Lock()
|
||||
delete(a.openEMLs, filename)
|
||||
a.openEMLsMux.Unlock()
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
|
||||
// Launch new EMLy instance with the file path
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
a.openEMLsMux.Lock()
|
||||
delete(a.openEMLs, filename)
|
||||
a.openEMLsMux.Unlock()
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(exe, tempFile)
|
||||
if err := cmd.Start(); err != nil {
|
||||
a.openEMLsMux.Lock()
|
||||
delete(a.openEMLs, filename)
|
||||
a.openEMLsMux.Unlock()
|
||||
return fmt.Errorf("failed to start viewer: %w", err)
|
||||
}
|
||||
|
||||
// Monitor process in background to release lock when closed
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
a.openEMLsMux.Lock()
|
||||
delete(a.openEMLs, filename)
|
||||
a.openEMLsMux.Unlock()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenImageWindow opens an image attachment in a new EMLy viewer window.
|
||||
// The image data is saved to a temp file and a new EMLy instance is launched
|
||||
// with the --view-image flag.
|
||||
//
|
||||
// This method tracks open images to prevent duplicate windows for the same file.
|
||||
//
|
||||
// Parameters:
|
||||
// - base64Data: Base64-encoded image data
|
||||
// - filename: The original filename of the image
|
||||
//
|
||||
// Returns:
|
||||
// - error: Error if the image is already open or if launching fails
|
||||
func (a *App) OpenImageWindow(base64Data string, filename string) error {
|
||||
// Check if this image is already open
|
||||
a.openImagesMux.Lock()
|
||||
if a.openImages[filename] {
|
||||
a.openImagesMux.Unlock()
|
||||
return fmt.Errorf("image '%s' is already open", filename)
|
||||
}
|
||||
a.openImages[filename] = true
|
||||
a.openImagesMux.Unlock()
|
||||
|
||||
// Decode base64 data
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
a.openImagesMux.Lock()
|
||||
delete(a.openImages, filename)
|
||||
a.openImagesMux.Unlock()
|
||||
return fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// Save to temp file
|
||||
tempDir := os.TempDir()
|
||||
timestamp := time.Now().Format("20060102_150405")
|
||||
tempFile := filepath.Join(tempDir, fmt.Sprintf("%s_%s", timestamp, filename))
|
||||
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
||||
a.openImagesMux.Lock()
|
||||
delete(a.openImages, filename)
|
||||
a.openImagesMux.Unlock()
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
|
||||
// Launch new EMLy instance in image viewer mode
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
a.openImagesMux.Lock()
|
||||
delete(a.openImages, filename)
|
||||
a.openImagesMux.Unlock()
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(exe, "--view-image="+tempFile)
|
||||
if err := cmd.Start(); err != nil {
|
||||
a.openImagesMux.Lock()
|
||||
delete(a.openImages, filename)
|
||||
a.openImagesMux.Unlock()
|
||||
return fmt.Errorf("failed to start viewer: %w", err)
|
||||
}
|
||||
|
||||
// Monitor process in background to release lock when closed
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
a.openImagesMux.Lock()
|
||||
delete(a.openImages, filename)
|
||||
a.openImagesMux.Unlock()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenPDFWindow opens a PDF attachment in a new EMLy viewer window.
|
||||
// The PDF data is saved to a temp file and a new EMLy instance is launched
|
||||
// with the --view-pdf flag.
|
||||
//
|
||||
// This method tracks open PDFs to prevent duplicate windows for the same file.
|
||||
//
|
||||
// Parameters:
|
||||
// - base64Data: Base64-encoded PDF data
|
||||
// - filename: The original filename of the PDF
|
||||
//
|
||||
// Returns:
|
||||
// - error: Error if the PDF is already open or if launching fails
|
||||
func (a *App) OpenPDFWindow(base64Data string, filename string) error {
|
||||
// Check if this PDF is already open
|
||||
a.openPDFsMux.Lock()
|
||||
if a.openPDFs[filename] {
|
||||
a.openPDFsMux.Unlock()
|
||||
return fmt.Errorf("pdf '%s' is already open", filename)
|
||||
}
|
||||
a.openPDFs[filename] = true
|
||||
a.openPDFsMux.Unlock()
|
||||
|
||||
// Decode base64 data
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
a.openPDFsMux.Lock()
|
||||
delete(a.openPDFs, filename)
|
||||
a.openPDFsMux.Unlock()
|
||||
return fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// Save to temp file
|
||||
tempDir := os.TempDir()
|
||||
tempFile := filepath.Join(tempDir, filename)
|
||||
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
||||
a.openPDFsMux.Lock()
|
||||
delete(a.openPDFs, filename)
|
||||
a.openPDFsMux.Unlock()
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
|
||||
// Launch new EMLy instance in PDF viewer mode
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
a.openPDFsMux.Lock()
|
||||
delete(a.openPDFs, filename)
|
||||
a.openPDFsMux.Unlock()
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(exe, "--view-pdf="+tempFile)
|
||||
if err := cmd.Start(); err != nil {
|
||||
a.openPDFsMux.Lock()
|
||||
delete(a.openPDFs, filename)
|
||||
a.openPDFsMux.Unlock()
|
||||
return fmt.Errorf("failed to start viewer: %w", err)
|
||||
}
|
||||
|
||||
// Monitor process in background to release lock when closed
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
a.openPDFsMux.Lock()
|
||||
delete(a.openPDFs, filename)
|
||||
a.openPDFsMux.Unlock()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// External Application Methods
|
||||
// =============================================================================
|
||||
|
||||
// OpenPDF saves a PDF to temp and opens it with the system's default PDF application.
|
||||
// This is used when the user prefers external viewers over the built-in viewer.
|
||||
//
|
||||
// Parameters:
|
||||
// - base64Data: Base64-encoded PDF data
|
||||
// - filename: The original filename of the PDF
|
||||
//
|
||||
// Returns:
|
||||
// - error: Error if saving or launching fails
|
||||
func (a *App) OpenPDF(base64Data string, filename string) error {
|
||||
if base64Data == "" {
|
||||
return fmt.Errorf("no data provided")
|
||||
}
|
||||
|
||||
// Decode base64 data
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// Save to temp file with timestamp for uniqueness
|
||||
tempDir := os.TempDir()
|
||||
timestamp := time.Now().Format("20060102_150405")
|
||||
tempFile := filepath.Join(tempDir, fmt.Sprintf("%s_%s", timestamp, filename))
|
||||
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
|
||||
// Open with Windows default application
|
||||
cmd := exec.Command("cmd", "/c", "start", "", tempFile)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenImage saves an image to temp and opens it with the system's default image viewer.
|
||||
// This is used when the user prefers external viewers over the built-in viewer.
|
||||
//
|
||||
// Parameters:
|
||||
// - base64Data: Base64-encoded image data
|
||||
// - filename: The original filename of the image
|
||||
//
|
||||
// Returns:
|
||||
// - error: Error if saving or launching fails
|
||||
func (a *App) OpenImage(base64Data string, filename string) error {
|
||||
if base64Data == "" {
|
||||
return fmt.Errorf("no data provided")
|
||||
}
|
||||
|
||||
// Decode base64 data
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// Save to temp file with timestamp for uniqueness
|
||||
tempDir := os.TempDir()
|
||||
timestamp := time.Now().Format("20060102_150405")
|
||||
tempFile := filepath.Join(tempDir, fmt.Sprintf("%s_%s", timestamp, filename))
|
||||
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
|
||||
// Open with Windows default application
|
||||
cmd := exec.Command("cmd", "/c", "start", "", tempFile)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Viewer Mode Detection
|
||||
// =============================================================================
|
||||
|
||||
// GetImageViewerData checks CLI arguments and returns image data if running in image viewer mode.
|
||||
// This is called by the viewer page on startup to get the image to display.
|
||||
//
|
||||
// Returns:
|
||||
// - *ImageViewerData: Image data if in viewer mode, nil otherwise
|
||||
// - error: Error if reading the image file fails
|
||||
func (a *App) GetImageViewerData() (*ImageViewerData, error) {
|
||||
for _, arg := range os.Args {
|
||||
if strings.HasPrefix(arg, "--view-image=") {
|
||||
filePath := strings.TrimPrefix(arg, "--view-image=")
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image file: %w", err)
|
||||
}
|
||||
// Return as base64 for consistent frontend handling
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
return &ImageViewerData{
|
||||
Data: encoded,
|
||||
Filename: filepath.Base(filePath),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetPDFViewerData checks CLI arguments and returns PDF data if running in PDF viewer mode.
|
||||
// This is called by the viewer page on startup to get the PDF to display.
|
||||
//
|
||||
// Returns:
|
||||
// - *PDFViewerData: PDF data if in viewer mode, nil otherwise
|
||||
// - error: Error if reading the PDF file fails
|
||||
func (a *App) GetPDFViewerData() (*PDFViewerData, error) {
|
||||
for _, arg := range os.Args {
|
||||
if strings.HasPrefix(arg, "--view-pdf=") {
|
||||
filePath := strings.TrimPrefix(arg, "--view-pdf=")
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read PDF file: %w", err)
|
||||
}
|
||||
// Return as base64 for consistent frontend handling
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
return &PDFViewerData{
|
||||
Data: encoded,
|
||||
Filename: filepath.Base(filePath),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetViewerData checks CLI arguments and returns viewer data for any viewer mode.
|
||||
// This is a unified method that detects both image and PDF viewer modes.
|
||||
//
|
||||
// Returns:
|
||||
// - *ViewerData: Contains either ImageData or PDFData depending on mode
|
||||
// - error: Error if reading the file fails
|
||||
func (a *App) GetViewerData() (*ViewerData, error) {
|
||||
for _, arg := range os.Args {
|
||||
// Check for image viewer mode
|
||||
if strings.HasPrefix(arg, "--view-image=") {
|
||||
filePath := strings.TrimPrefix(arg, "--view-image=")
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image file: %w", err)
|
||||
}
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
return &ViewerData{
|
||||
ImageData: &ImageViewerData{
|
||||
Data: encoded,
|
||||
Filename: filepath.Base(filePath),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check for PDF viewer mode
|
||||
if strings.HasPrefix(arg, "--view-pdf=") {
|
||||
filePath := strings.TrimPrefix(arg, "--view-pdf=")
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read PDF file: %w", err)
|
||||
}
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
return &ViewerData{
|
||||
PDFData: &PDFViewerData{
|
||||
Data: encoded,
|
||||
Filename: filepath.Base(filePath),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user