feat: initial commit for G-Launcher based on Firefly with Horoyoi enhancements
This commit is contained in:
commit
a99470a69f
155 files changed
+19061
No files matched your search
@@ -0,0 +1,271 @@
|
||||
package gitService
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
const (
|
||||
minGitReleaseAPI = "https://api.github.com/repos/git-for-windows/git/releases/latest"
|
||||
minGitDirectory = "./server/tools/mingit"
|
||||
)
|
||||
|
||||
type gitRelease struct {
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
// ensureGit provides a private, portable Git runtime when the user has not
|
||||
// installed Git system-wide. It requires no administrator privileges.
|
||||
func ensureGit() (string, error) {
|
||||
if git, err := exec.LookPath("git"); err == nil {
|
||||
return git, nil
|
||||
}
|
||||
|
||||
gitPath := filepath.Join(minGitDirectory, "cmd", "git.exe")
|
||||
if _, err := os.Stat(gitPath); err == nil {
|
||||
return gitPath, nil
|
||||
}
|
||||
if runtime.GOOS != "windows" || runtime.GOARCH != "amd64" {
|
||||
return "", fmt.Errorf("Git was not found on PATH, and automatic portable Git setup is currently supported on Windows x64 only")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 2 * time.Minute}
|
||||
request, err := http.NewRequest(http.MethodGet, minGitReleaseAPI, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create Git download request: %w", err)
|
||||
}
|
||||
request.Header.Set("Accept", "application/vnd.github+json")
|
||||
request.Header.Set("User-Agent", "G-Launcher")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find a portable Git release: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("find a portable Git release: GitHub returned %s", response.Status)
|
||||
}
|
||||
var release gitRelease
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 4<<20)).Decode(&release); err != nil {
|
||||
return "", fmt.Errorf("read portable Git release metadata: %w", err)
|
||||
}
|
||||
var archiveURL string
|
||||
for _, asset := range release.Assets {
|
||||
if strings.HasPrefix(asset.Name, "MinGit-") && strings.HasSuffix(asset.Name, "-64-bit.zip") {
|
||||
archiveURL = asset.URL
|
||||
break
|
||||
}
|
||||
}
|
||||
if archiveURL == "" {
|
||||
return "", fmt.Errorf("the latest Git for Windows release does not include a MinGit x64 package")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(minGitDirectory), 0755); err != nil {
|
||||
return "", fmt.Errorf("create portable Git directory: %w", err)
|
||||
}
|
||||
archive, err := os.CreateTemp(filepath.Dir(minGitDirectory), "mingit-*.zip")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create portable Git archive: %w", err)
|
||||
}
|
||||
archivePath := archive.Name()
|
||||
defer os.Remove(archivePath)
|
||||
downloadRequest, err := http.NewRequest(http.MethodGet, archiveURL, nil)
|
||||
if err != nil {
|
||||
archive.Close()
|
||||
return "", fmt.Errorf("create portable Git download request: %w", err)
|
||||
}
|
||||
downloadRequest.Header.Set("User-Agent", "G-Launcher")
|
||||
downloadResponse, err := client.Do(downloadRequest)
|
||||
if err != nil {
|
||||
archive.Close()
|
||||
return "", fmt.Errorf("download portable Git: %w", err)
|
||||
}
|
||||
if downloadResponse.StatusCode != http.StatusOK {
|
||||
downloadResponse.Body.Close()
|
||||
archive.Close()
|
||||
return "", fmt.Errorf("download portable Git: GitHub returned %s", downloadResponse.Status)
|
||||
}
|
||||
_, copyErr := io.Copy(archive, io.LimitReader(downloadResponse.Body, 300<<20))
|
||||
responseErr := downloadResponse.Body.Close()
|
||||
closeErr := archive.Close()
|
||||
if copyErr != nil {
|
||||
return "", fmt.Errorf("save portable Git: %w", copyErr)
|
||||
}
|
||||
if responseErr != nil {
|
||||
return "", fmt.Errorf("finish portable Git download: %w", responseErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return "", fmt.Errorf("save portable Git: %w", closeErr)
|
||||
}
|
||||
|
||||
stagingDir, err := os.MkdirTemp(filepath.Dir(minGitDirectory), "mingit-install-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create portable Git staging directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(stagingDir)
|
||||
if err := extractMinGit(archivePath, stagingDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(stagingDir, "cmd", "git.exe")); err != nil {
|
||||
return "", fmt.Errorf("portable Git archive is missing cmd/git.exe")
|
||||
}
|
||||
if err := os.RemoveAll(minGitDirectory); err != nil {
|
||||
return "", fmt.Errorf("replace incomplete portable Git setup: %w", err)
|
||||
}
|
||||
if err := os.Rename(stagingDir, minGitDirectory); err != nil {
|
||||
return "", fmt.Errorf("install portable Git: %w", err)
|
||||
}
|
||||
return gitPath, nil
|
||||
}
|
||||
|
||||
func gitCommand(gitPath string, args ...string) *exec.Cmd {
|
||||
command := exec.Command(gitPath, args...)
|
||||
if filepath.IsAbs(gitPath) && strings.EqualFold(filepath.Base(filepath.Dir(gitPath)), "cmd") {
|
||||
root := filepath.Dir(filepath.Dir(gitPath))
|
||||
command.Env = append(os.Environ(), "PATH="+filepath.Join(root, "mingw64", "bin")+";"+filepath.Join(root, "usr", "bin")+";"+os.Getenv("PATH"))
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
var gitProgressPattern = regexp.MustCompile(`(?i)(receiving objects|resolving deltas):\s*(\d+)%`)
|
||||
|
||||
func runGitWithProgress(command *exec.Cmd, action string) ([]byte, error) {
|
||||
stdout, err := command.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read Git output: %w", err)
|
||||
}
|
||||
stderr, err := command.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read Git progress: %w", err)
|
||||
}
|
||||
if err := command.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var stdoutOutput bytes.Buffer
|
||||
var stderrOutput bytes.Buffer
|
||||
stdoutDone := make(chan error, 1)
|
||||
go func() {
|
||||
_, copyErr := io.Copy(&stdoutOutput, stdout)
|
||||
stdoutDone <- copyErr
|
||||
}()
|
||||
application.Get().Event.Emit("download:server", map[string]interface{}{
|
||||
"percent": "0",
|
||||
"speed": "",
|
||||
"message": action,
|
||||
})
|
||||
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
scanner.Buffer(make([]byte, 4096), 1024*1024)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
stderrOutput.WriteString(line)
|
||||
stderrOutput.WriteByte('\n')
|
||||
match := gitProgressPattern.FindStringSubmatch(line)
|
||||
if len(match) != 3 {
|
||||
continue
|
||||
}
|
||||
phasePercent, parseErr := strconv.Atoi(match[2])
|
||||
if parseErr != nil {
|
||||
continue
|
||||
}
|
||||
percent := phasePercent * 90 / 100
|
||||
if strings.EqualFold(match[1], "resolving deltas") {
|
||||
percent = 90 + phasePercent/10
|
||||
}
|
||||
application.Get().Event.Emit("download:server", map[string]interface{}{
|
||||
"percent": fmt.Sprintf("%d", percent),
|
||||
"speed": "",
|
||||
"message": action + " · " + strings.ToLower(match[1]),
|
||||
})
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
command.Process.Kill()
|
||||
_ = command.Wait()
|
||||
<-stdoutDone
|
||||
return append(stdoutOutput.Bytes(), stderrOutput.Bytes()...), fmt.Errorf("read Git progress: %w", err)
|
||||
}
|
||||
stdoutErr := <-stdoutDone
|
||||
waitErr := command.Wait()
|
||||
if stdoutErr != nil {
|
||||
return append(stdoutOutput.Bytes(), stderrOutput.Bytes()...), fmt.Errorf("read Git output: %w", stdoutErr)
|
||||
}
|
||||
if waitErr != nil {
|
||||
return append(stdoutOutput.Bytes(), stderrOutput.Bytes()...), waitErr
|
||||
}
|
||||
application.Get().Event.Emit("download:server", map[string]interface{}{
|
||||
"percent": "100",
|
||||
"speed": "",
|
||||
"message": action + " · complete",
|
||||
})
|
||||
return append(stdoutOutput.Bytes(), stderrOutput.Bytes()...), nil
|
||||
}
|
||||
|
||||
func extractMinGit(archivePath, destination string) error {
|
||||
reader, err := zip.OpenReader(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open portable Git archive: %w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
root, err := filepath.Abs(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve portable Git staging directory: %w", err)
|
||||
}
|
||||
for _, file := range reader.File {
|
||||
name := filepath.Clean(filepath.FromSlash(file.Name))
|
||||
if filepath.IsAbs(name) || name == "." || name == ".." || strings.HasPrefix(name, ".."+string(filepath.Separator)) {
|
||||
return fmt.Errorf("portable Git archive contains an unsafe path")
|
||||
}
|
||||
path := filepath.Join(root, name)
|
||||
if !strings.HasPrefix(path, root+string(filepath.Separator)) {
|
||||
return fmt.Errorf("portable Git archive contains an unsafe path")
|
||||
}
|
||||
if file.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("extract portable Git directory: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return fmt.Errorf("create portable Git directory: %w", err)
|
||||
}
|
||||
source, err := file.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read portable Git archive: %w", err)
|
||||
}
|
||||
output, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
source.Close()
|
||||
return fmt.Errorf("extract portable Git file: %w", err)
|
||||
}
|
||||
_, copyErr := io.Copy(output, source)
|
||||
sourceErr := source.Close()
|
||||
outputErr := output.Close()
|
||||
if copyErr != nil {
|
||||
return fmt.Errorf("extract portable Git file: %w", copyErr)
|
||||
}
|
||||
if sourceErr != nil {
|
||||
return fmt.Errorf("read portable Git archive: %w", sourceErr)
|
||||
}
|
||||
if outputErr != nil {
|
||||
return fmt.Errorf("write portable Git file: %w", outputErr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package gitService
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"g-launcher/pkg/constant"
|
||||
"g-launcher/pkg/models"
|
||||
"io"
|
||||
"net/http"
|
||||
"github.com/minio/selfupdate"
|
||||
|
||||
)
|
||||
|
||||
type GitService struct{}
|
||||
|
||||
|
||||
func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
|
||||
resp, err := http.Get(constant.LauncherGitUrl)
|
||||
if err != nil {
|
||||
return false, "", err.Error()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
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) UpdateLauncherProgress(version string) (bool, string) {
|
||||
asset, ok := g.getReleaseAsset(version, constant.LauncherGitUrl, constant.LauncherFile)
|
||||
if !ok {
|
||||
return false, "no release found"
|
||||
}
|
||||
|
||||
resp, err := http.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = selfupdate.Apply(resp.Body, selfupdate.Options{})
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package gitService
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"g-launcher/pkg/constant"
|
||||
"g-launcher/pkg/models"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func (g *GitService) GetLatestProxyVersion() (bool, string, string) {
|
||||
resp, err := http.Get(constant.ProxyGitUrl)
|
||||
if err != nil {
|
||||
return false, "", err.Error()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
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) DownloadProxyProgress(version string) (bool, string) {
|
||||
asset, ok := g.getReleaseAsset(version, constant.ProxyGitUrl, constant.ProxyFile)
|
||||
if !ok {
|
||||
return false, "no release found"
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(constant.ProxyStorageUrl, 0755); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
saveFile := filepath.Join(constant.ProxyStorageUrl, asset.Name)
|
||||
tmpPath, err := g.downloadFileParallel(saveFile, asset.BrowserDownloadURL, 4, func(percent float64, speed string) {
|
||||
application.Get().Event.Emit("download:proxy", map[string]interface{}{
|
||||
"percent": fmt.Sprintf("%.2f", percent),
|
||||
"speed": speed,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := os.Rename(tmpPath, saveFile); err == nil {
|
||||
return true, ""
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
return false, "failed to rename tmp file after retries"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package gitService
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"g-launcher/pkg/constant"
|
||||
"g-launcher/pkg/models"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func (g *GitService) GetLatestServerVersion() (bool, string, string) {
|
||||
resp, err := http.Get(constant.ServerGitUrl)
|
||||
if err != nil {
|
||||
return false, "", err.Error()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
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) {
|
||||
asset, ok := g.getReleaseAsset(version, constant.ServerGitUrl, constant.ServerZipFile)
|
||||
if !ok {
|
||||
return false, "no release found"
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(constant.ServerStorageUrl, 0755); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
saveFile := filepath.Join(constant.ServerStorageUrl, asset.Name)
|
||||
tmpPath, err := g.downloadFileParallel(saveFile, asset.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, err.Error()
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := os.Rename(tmpPath, saveFile); err == nil {
|
||||
return true, ""
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
return false, "failed to rename tmp file after retries"
|
||||
}
|
||||
|
||||
func (g *GitService) UnzipServer() {
|
||||
g.unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl)
|
||||
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile))
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package gitService
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"g-launcher/pkg/models"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
)
|
||||
|
||||
func HumanFormat(bytes float64) string {
|
||||
if math.IsNaN(bytes) || math.IsInf(bytes, 0) {
|
||||
return fmt.Sprintf("%v", bytes)
|
||||
}
|
||||
if bytes == 0 {
|
||||
return "0B"
|
||||
}
|
||||
|
||||
neg := bytes < 0
|
||||
if neg {
|
||||
bytes = -bytes
|
||||
}
|
||||
|
||||
units := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
i := 0
|
||||
for bytes >= 1024 && i < len(units)-1 {
|
||||
bytes /= 1024
|
||||
i++
|
||||
}
|
||||
|
||||
res := fmt.Sprintf("%.1f%s", bytes, units[i])
|
||||
if neg {
|
||||
res = "-" + res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type WriteCounter struct {
|
||||
Total uint64
|
||||
StartTime time.Time
|
||||
OnEmit func(percent float64, speed string)
|
||||
TotalSize int64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewWriteCounter(total int64, onEmit func(percent float64, speed string)) *WriteCounter {
|
||||
return &WriteCounter{
|
||||
StartTime: time.Now(),
|
||||
TotalSize: total,
|
||||
OnEmit: onEmit,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) Add(n int) {
|
||||
wc.mu.Lock()
|
||||
defer wc.mu.Unlock()
|
||||
wc.Total += uint64(n)
|
||||
wc.PrintProgress()
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) PrintProgress() {
|
||||
elapsed := time.Since(wc.StartTime).Seconds()
|
||||
if elapsed < 0.001 {
|
||||
elapsed = 0.001
|
||||
}
|
||||
speed := float64(wc.Total) / elapsed
|
||||
percent := float64(wc.Total) / float64(wc.TotalSize) * 100
|
||||
if wc.OnEmit != nil {
|
||||
wc.OnEmit(percent, fmt.Sprintf("%s/s", HumanFormat(speed)))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitService) downloadFileParallel(filePath, url string, numParts int, onEmit func(percent float64, speed string)) (tmpPath string, err error) {
|
||||
resp, err := http.Head(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get head: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
size := resp.ContentLength
|
||||
tmpPath = filePath + ".tmp"
|
||||
|
||||
out, err := os.Create(tmpPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create tmp file: %w", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
counter := NewWriteCounter(size, onEmit)
|
||||
partSize := size / int64(numParts)
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
for i := 0; i < numParts; i++ {
|
||||
start := int64(i) * partSize
|
||||
end := start + partSize - 1
|
||||
if i == numParts-1 {
|
||||
end = size - 1
|
||||
}
|
||||
|
||||
_ = start
|
||||
_ = end
|
||||
|
||||
wg.Go(func() {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf := make([]byte, 32*1024)
|
||||
var written int64
|
||||
for {
|
||||
n, err := resp.Body.Read(buf)
|
||||
if n > 0 {
|
||||
mu.Lock()
|
||||
out.Seek(start+written, 0)
|
||||
out.Write(buf[:n])
|
||||
mu.Unlock()
|
||||
written += int64(n)
|
||||
counter.Add(n)
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return tmpPath, nil
|
||||
}
|
||||
|
||||
// --- Helper getReleaseAsset ---
|
||||
func (g *GitService) getReleaseAsset(version, url, fileName string) (models.AssetType, bool) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return models.AssetType{}, false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var releases []*models.ReleaseType
|
||||
if err := json.Unmarshal(body, &releases); err != nil || len(releases) == 0 {
|
||||
return models.AssetType{}, false
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
if release.TagName == version {
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == fileName {
|
||||
return asset, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return models.AssetType{}, false
|
||||
}
|
||||
|
||||
func (g *GitService) unzipParallel(src string, dest string) error {
|
||||
numCPU := runtime.NumCPU()
|
||||
|
||||
reserved := 1
|
||||
if numCPU > 4 {
|
||||
reserved = 2
|
||||
}
|
||||
maxWorkers := numCPU - reserved
|
||||
if maxWorkers < 1 {
|
||||
maxWorkers = 1
|
||||
}
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
err = os.MkdirAll(dest, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type job struct {
|
||||
f *zip.File
|
||||
}
|
||||
jobs := make(chan job)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < maxWorkers; i++ {
|
||||
|
||||
wg.Go(func() {
|
||||
for j := range jobs {
|
||||
err := g.extractFile(j.f, dest)
|
||||
if err != nil {
|
||||
fmt.Printf("Error extracting %s: %v\n", j.f.Name, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
jobs <- job{f}
|
||||
}
|
||||
close(jobs)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitService) extractFile(f *zip.File, dest string) error {
|
||||
fp := filepath.Join(dest, f.Name)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
return os.MkdirAll(fp, f.Mode())
|
||||
}
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fp), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
out, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, rc)
|
||||
return err
|
||||
}
|
||||
Reference in new issue
Block a user