package gitService import ( "encoding/json" "fmt" "g-launcher/pkg/constant" "g-launcher/pkg/logger" "g-launcher/pkg/models" "io" "net/http" "os" "path/filepath" "strings" "time" "github.com/wailsapp/wails/v3/pkg/application" ) func (g *GitService) GetLatestServerVersion() (bool, string, string) { client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Get(constant.ServerGitUrl) if err != nil { return false, "", err.Error() } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return false, "", err.Error() } var releases []models.ReleaseType err = json.Unmarshal(body, &releases) if err != nil { return false, "", err.Error() } if len(releases) == 0 { return false, "", "no releases found" } return true, releases[0].TagName, "" } func (g *GitService) DownloadServerProgress(version string) (bool, string) { client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Get(constant.ServerGitUrl) if err != nil { return false, fmt.Sprintf("fetch server releases: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return false, fmt.Sprintf("read server releases: %v", err) } var releases []*models.ReleaseType if err := json.Unmarshal(body, &releases); err != nil || len(releases) == 0 { return false, "no releases found on server repository" } var targetRelease *models.ReleaseType for _, rel := range releases { if rel.TagName == version { targetRelease = rel break } } if targetRelease == nil && len(releases) > 0 { targetRelease = releases[0] } if targetRelease == nil { return false, "could not find server release matching version " + version } if err := os.MkdirAll(constant.ServerStorageUrl, 0755); err != nil { return false, err.Error() } // 1. Search for .zip asset var zipAsset *models.AssetType for _, asset := range targetRelease.Assets { if strings.HasSuffix(strings.ToLower(asset.Name), ".zip") { aCopy := asset zipAsset = &aCopy break } } if zipAsset != nil { // Case A: ZIP archive found saveZipPath := filepath.Join(constant.ServerStorageUrl, zipAsset.Name) tmpPath, err := g.downloadFileParallel(saveZipPath, zipAsset.BrowserDownloadURL, 4, func(percent float64, speed string) { application.Get().Event.Emit("download:server", map[string]interface{}{ "percent": fmt.Sprintf("%.2f", percent), "speed": speed, }) }) if err != nil { return false, fmt.Sprintf("download server zip: %v", err) } renamed := false for i := 0; i < 3; i++ { if err := os.Rename(tmpPath, saveZipPath); err == nil { renamed = true break } time.Sleep(300 * time.Millisecond) } if !renamed { os.Remove(tmpPath) return false, "failed to rename downloaded server zip" } // Unzip immediately if err := g.unzipParallel(saveZipPath, constant.ServerStorageUrl); err != nil { return false, fmt.Sprintf("unzip server: %v", err) } os.Remove(saveZipPath) g.organizeExtractedServer(constant.ServerStorageUrl) return true, "" } // Case B: No ZIP archive, download sdkserver and server executables var sdkAsset, serverAsset *models.AssetType for _, asset := range targetRelease.Assets { nameLower := strings.ToLower(asset.Name) if strings.Contains(nameLower, "sdkserver") { aCopy := asset sdkAsset = &aCopy } else if strings.Contains(nameLower, "server") { aCopy := asset serverAsset = &aCopy } } if sdkAsset == nil && serverAsset == nil { return false, "no zip or server executable assets found in release" } if sdkAsset != nil { saveSdk := filepath.Join(constant.ServerStorageUrl, sdkAsset.Name) tmpPath, err := g.downloadFileParallel(saveSdk, sdkAsset.BrowserDownloadURL, 4, func(percent float64, speed string) { application.Get().Event.Emit("download:server", map[string]interface{}{ "percent": fmt.Sprintf("%.2f", percent*0.5), "speed": speed, }) }) if err == nil { _ = os.Rename(tmpPath, saveSdk) } } if serverAsset != nil { saveServer := filepath.Join(constant.ServerStorageUrl, serverAsset.Name) tmpPath, err := g.downloadFileParallel(saveServer, serverAsset.BrowserDownloadURL, 4, func(percent float64, speed string) { application.Get().Event.Emit("download:server", map[string]interface{}{ "percent": fmt.Sprintf("%.2f", 50.0+percent*0.5), "speed": speed, }) }) if err == nil { _ = os.Rename(tmpPath, saveServer) } } return true, "" } func (g *GitService) UnzipServer() { entries, err := os.ReadDir(constant.ServerStorageUrl) if err != nil { return } for _, entry := range entries { if !entry.IsDir() && strings.HasSuffix(strings.ToLower(entry.Name()), ".zip") { zipPath := filepath.Join(constant.ServerStorageUrl, entry.Name()) _ = g.unzipParallel(zipPath, constant.ServerStorageUrl) _ = os.Remove(zipPath) } } g.organizeExtractedServer(constant.ServerStorageUrl) } func (g *GitService) organizeExtractedServer(dest string) { forShipPath := filepath.Join(dest, "for ship") if info, err := os.Stat(forShipPath); err == nil && info.IsDir() { logger.Info("organizeExtractedServer: found 'for ship' subdirectory in %s, synchronizing files", dest) entries, err := os.ReadDir(forShipPath) if err == nil { for _, entry := range entries { src := filepath.Join(forShipPath, entry.Name()) dst := filepath.Join(dest, entry.Name()) if !entry.IsDir() { if _, err := os.Stat(dst); os.IsNotExist(err) { data, readErr := os.ReadFile(src) if readErr == nil { _ = os.WriteFile(dst, data, 0755) logger.Info("organizeExtractedServer: copied %s -> %s", entry.Name(), dst) } } } } } } } func (g *GitService) FindServerExecutables() (string, string) { var candidateRoots []string if exePath, err := os.Executable(); err == nil { exeDir := filepath.Dir(exePath) candidateRoots = append(candidateRoots, filepath.Join(exeDir, "server")) candidateRoots = append(candidateRoots, filepath.Join(filepath.Dir(exeDir), "server")) } candidateRoots = append(candidateRoots, constant.ServerStorageUrl, "./bin/server", "server", "bin/server") var gameServer, sdkServer string seen := make(map[string]bool) for _, root := range candidateRoots { absRoot, err := filepath.Abs(root) if err != nil || seen[absRoot] { continue } seen[absRoot] = true if info, err := os.Stat(absRoot); err != nil || !info.IsDir() { continue } _ = filepath.Walk(absRoot, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } nameLower := strings.ToLower(info.Name()) // Search SDK server if sdkServer == "" { if nameLower == "sdkserver-go.exe" || nameLower == "sdkserver.exe" || (strings.Contains(nameLower, "sdkserver") && strings.HasSuffix(nameLower, ".exe")) { sdkServer = path } } // Search Game server if gameServer == "" { if nameLower == "gameserver-go.exe" || nameLower == "server.exe" || nameLower == "gameserver.exe" || nameLower == "firefly-go_win.exe" || (strings.Contains(nameLower, "gameserver") && strings.HasSuffix(nameLower, ".exe")) { gameServer = path } } return nil }) if gameServer != "" && sdkServer != "" { break } } logger.Info("FindServerExecutables: found gameServer=%s, sdkServer=%s", gameServer, sdkServer) return gameServer, sdkServer }