mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-04 12:32:58 +00:00
first test
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UpdateInfo represents information about an available update
|
||||
type UpdateInfo struct {
|
||||
Version string `json:"version"`
|
||||
ReleaseNotes string `json:"releaseNotes"`
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
Mandatory bool `json:"mandatory"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
// UpdateStatus represents the current status of an update
|
||||
type UpdateStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Downloading bool `json:"downloading"`
|
||||
Installing bool `json:"installing"`
|
||||
Completed bool `json:"completed"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Progress float64 `json:"progress"`
|
||||
}
|
||||
|
||||
// UpdateRequest represents an update installation request
|
||||
type UpdateRequest struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// Global update state
|
||||
var (
|
||||
updateMutex sync.RWMutex
|
||||
currentUpdate *UpdateInfo
|
||||
updateProgress *UpdateStatus
|
||||
)
|
||||
|
||||
func init() {
|
||||
updateProgress = &UpdateStatus{
|
||||
Available: false,
|
||||
Downloading: false,
|
||||
Installing: false,
|
||||
Completed: false,
|
||||
Error: "",
|
||||
Progress: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckForUpdates checks if a new version is available
|
||||
func CheckForUpdates(c *gin.Context) {
|
||||
updateMutex.Lock()
|
||||
defer updateMutex.Unlock()
|
||||
|
||||
// Get current version from environment or default
|
||||
currentVersion := os.Getenv("APP_VERSION")
|
||||
if currentVersion == "" {
|
||||
currentVersion = "1.0.0"
|
||||
}
|
||||
|
||||
// In a real implementation, this would check against a remote update server
|
||||
// For demo purposes, we'll simulate checking for updates
|
||||
latestVersion, updateAvailable := simulateUpdateCheck(currentVersion)
|
||||
|
||||
if updateAvailable {
|
||||
currentUpdate = &UpdateInfo{
|
||||
Version: latestVersion,
|
||||
ReleaseNotes: "• New AI features added\n• Performance improvements\n• Bug fixes and security patches\n• Enhanced user interface",
|
||||
DownloadURL: "https://github.com/trackeep/trackeep/releases/latest",
|
||||
Mandatory: false,
|
||||
Size: "~25MB",
|
||||
}
|
||||
updateProgress.Available = true
|
||||
} else {
|
||||
currentUpdate = nil
|
||||
updateProgress.Available = false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"updateAvailable": updateAvailable,
|
||||
"currentVersion": currentVersion,
|
||||
"latestVersion": latestVersion,
|
||||
"updateInfo": currentUpdate,
|
||||
})
|
||||
}
|
||||
|
||||
// InstallUpdate starts the update installation process
|
||||
func InstallUpdate(c *gin.Context) {
|
||||
var req UpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
updateMutex.Lock()
|
||||
defer updateMutex.Unlock()
|
||||
|
||||
if currentUpdate == nil || currentUpdate.Version != req.Version {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Update not available"})
|
||||
return
|
||||
}
|
||||
|
||||
if updateProgress.Downloading || updateProgress.Installing {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Update already in progress"})
|
||||
return
|
||||
}
|
||||
|
||||
// Start update process in background
|
||||
go performUpdate(currentUpdate)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Update started",
|
||||
"version": req.Version,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUpdateProgress returns the current update progress
|
||||
func GetUpdateProgress(c *gin.Context) {
|
||||
updateMutex.RLock()
|
||||
defer updateMutex.RUnlock()
|
||||
|
||||
c.JSON(http.StatusOK, updateProgress)
|
||||
}
|
||||
|
||||
// WebSocket endpoint for real-time update progress
|
||||
func UpdateProgressWebSocket(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "WebSocket support not implemented, using polling instead",
|
||||
"progress": updateProgress,
|
||||
})
|
||||
}
|
||||
|
||||
// simulateUpdateCheck simulates checking for updates
|
||||
func simulateUpdateCheck(currentVersion string) (string, bool) {
|
||||
// Simulate version check - in reality this would call an update API
|
||||
versions := []string{"1.0.1", "1.1.0", "1.2.0"}
|
||||
|
||||
// For demo, always return a newer version
|
||||
if len(versions) > 0 {
|
||||
return versions[0], true
|
||||
}
|
||||
|
||||
return currentVersion, false
|
||||
}
|
||||
|
||||
// performUpdate performs the actual update process
|
||||
func performUpdate(updateInfo *UpdateInfo) {
|
||||
updateMutex.Lock()
|
||||
updateProgress.Downloading = true
|
||||
updateProgress.Progress = 0
|
||||
updateProgress.Error = ""
|
||||
updateMutex.Unlock()
|
||||
|
||||
// Broadcast progress update
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
|
||||
// Simulate download
|
||||
for i := 0; i <= 100; i += 10 {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
updateMutex.Lock()
|
||||
updateProgress.Progress = float64(i)
|
||||
updateMutex.Unlock()
|
||||
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
}
|
||||
|
||||
// Start installation
|
||||
updateMutex.Lock()
|
||||
updateProgress.Downloading = false
|
||||
updateProgress.Installing = true
|
||||
updateProgress.Progress = 0
|
||||
updateMutex.Unlock()
|
||||
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
|
||||
// Backup user data
|
||||
if err := backupUserData(); err != nil {
|
||||
updateMutex.Lock()
|
||||
updateProgress.Installing = false
|
||||
updateProgress.Error = fmt.Sprintf("Failed to backup user data: %v", err)
|
||||
updateMutex.Unlock()
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
return
|
||||
}
|
||||
|
||||
// Simulate installation
|
||||
for i := 0; i <= 100; i += 20 {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
updateMutex.Lock()
|
||||
updateProgress.Progress = float64(i)
|
||||
updateMutex.Unlock()
|
||||
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
}
|
||||
|
||||
// Perform the actual update
|
||||
if err := applyUpdate(updateInfo); err != nil {
|
||||
updateMutex.Lock()
|
||||
updateProgress.Installing = false
|
||||
updateProgress.Error = fmt.Sprintf("Failed to apply update: %v", err)
|
||||
updateMutex.Unlock()
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
return
|
||||
}
|
||||
|
||||
// Mark as completed
|
||||
updateMutex.Lock()
|
||||
updateProgress.Installing = false
|
||||
updateProgress.Completed = true
|
||||
updateProgress.Progress = 100
|
||||
updateMutex.Unlock()
|
||||
|
||||
log.Printf("Update progress: %.1f%% downloading", updateProgress.Progress)
|
||||
|
||||
// Trigger application restart after a delay
|
||||
time.Sleep(2 * time.Second)
|
||||
restartApplication()
|
||||
}
|
||||
|
||||
// backupUserData creates a backup of user data
|
||||
func backupUserData() error {
|
||||
backupDir := filepath.Join(os.TempDir(), "trackeep_backup", time.Now().Format("20060102_150405"))
|
||||
|
||||
// Create backup directory
|
||||
if err := os.MkdirAll(backupDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create backup directory: %w", err)
|
||||
}
|
||||
|
||||
// Backup database
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "./trackeep.db"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
backupDBPath := filepath.Join(backupDir, "trackeep.db")
|
||||
if err := copyFile(dbPath, backupDBPath); err != nil {
|
||||
return fmt.Errorf("failed to backup database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Backup uploads directory
|
||||
uploadsDir := "./uploads"
|
||||
if _, err := os.Stat(uploadsDir); err == nil {
|
||||
backupUploadsDir := filepath.Join(backupDir, "uploads")
|
||||
if err := copyDirectory(uploadsDir, backupUploadsDir); err != nil {
|
||||
return fmt.Errorf("failed to backup uploads: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Backup configuration files
|
||||
configFiles := []string{".env", "docker-compose.yml"}
|
||||
for _, file := range configFiles {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
backupFile := filepath.Join(backupDir, file)
|
||||
if err := copyFile(file, backupFile); err != nil {
|
||||
log.Printf("Warning: failed to backup %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("User data backed up to: %s", backupDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyUpdate applies the update
|
||||
func applyUpdate(updateInfo *UpdateInfo) error {
|
||||
// In a real implementation, this would:
|
||||
// 1. Download the new version
|
||||
// 2. Verify checksums
|
||||
// 3. Extract/update files
|
||||
// 4. Run database migrations if needed
|
||||
// 5. Restore user data if necessary
|
||||
|
||||
log.Printf("Applying update to version %s", updateInfo.Version)
|
||||
|
||||
// Simulate file update
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Update version in environment
|
||||
os.Setenv("APP_VERSION", updateInfo.Version)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restartApplication restarts the application
|
||||
func restartApplication() {
|
||||
log.Println("Restarting application to complete update...")
|
||||
|
||||
// Create a new process to replace the current one
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get executable path: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use different commands based on OS
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("powershell", "-Command", "Start-Sleep 2; "+executable)
|
||||
default:
|
||||
cmd = exec.Command("sh", "-c", fmt.Sprintf("sleep 2 && %s", executable))
|
||||
}
|
||||
|
||||
// Start the new process
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("Failed to start new process: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Exit the current process
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// broadcastProgress broadcasts update progress to all WebSocket clients (simplified version)
|
||||
func broadcastProgress() {
|
||||
updateMutex.RLock()
|
||||
progress := *updateProgress
|
||||
updateMutex.RUnlock()
|
||||
|
||||
log.Printf("Update progress: %.1f%% - Status: %v", progress.Progress, getUpdateStatusString(progress))
|
||||
}
|
||||
|
||||
// getUpdateStatusString returns a human-readable status string
|
||||
func getUpdateStatusString(status UpdateStatus) string {
|
||||
if status.Completed {
|
||||
return "Completed"
|
||||
}
|
||||
if status.Error != "" {
|
||||
return "Error: " + status.Error
|
||||
}
|
||||
if status.Installing {
|
||||
return "Installing"
|
||||
}
|
||||
if status.Downloading {
|
||||
return "Downloading"
|
||||
}
|
||||
if status.Available {
|
||||
return "Available"
|
||||
}
|
||||
return "Not Available"
|
||||
}
|
||||
|
||||
// copyFile copies a file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, sourceFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// copyDirectory copies a directory recursively
|
||||
func copyDirectory(src, dst string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dst, relPath)
|
||||
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(dstPath, info.Mode())
|
||||
}
|
||||
|
||||
return copyFile(path, dstPath)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user