tstor/daemons/qbittorrent/install.go
2024-11-24 20:33:44 +03:00

150 lines
3.1 KiB
Go

package qbittorrent
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"os/exec"
"path"
"runtime"
"time"
"github.com/google/go-github/v63/github"
"golang.org/x/sys/cpu"
)
const (
repoOwner = "userdocs"
repoName = "qbittorrent-nox-static"
)
func runQBittorrent(binPath string, profileDir string, port int, stdout, stderr io.Writer) (*os.Process, error) {
err := os.Chmod(binPath, 0755)
if err != nil {
return nil, err
}
cmd := exec.Command(binPath, fmt.Sprintf("--profile=%s", profileDir), fmt.Sprintf("--webui-port=%d", port))
cmd.Stdin = bytes.NewReader([]byte("y\n"))
cmd.Stdout = stdout
cmd.Stderr = stderr
err = cmd.Start()
if err != nil {
return nil, err
}
return cmd.Process, nil
}
func downloadLatestQbitRelease(ctx context.Context, binPath string) error {
client := github.NewClient(nil)
rel, _, err := client.Repositories.GetLatestRelease(ctx, repoOwner, repoName)
if err != nil {
return err
}
arch := ""
switch runtime.GOARCH {
case "amd64":
arch = "x86_64"
case "arm":
arch = "armhf" // this is a safe version, go does not distinguish between armv6 and armv7
if cpu.ARM.HasNEON {
arch = "armv7"
}
case "arm64":
arch = "aarch64"
}
if arch == "" {
return errors.New("unsupported architecture")
}
binName := arch + "-qbittorrent-nox"
var targetRelease *github.ReleaseAsset
for _, v := range rel.Assets {
if v.GetName() == binName {
targetRelease = v
break
}
}
if targetRelease == nil {
return fmt.Errorf("target asset %s not found", binName)
}
downloadUrl := targetRelease.GetBrowserDownloadURL()
if downloadUrl == "" {
return errors.New("download url is empty")
}
err = os.MkdirAll(path.Dir(binPath), 0755)
if err != nil {
return err
}
slog.InfoContext(ctx, "downloading latest qbittorrent-nox release", slog.String("url", downloadUrl))
return downloadFile(ctx, binPath, downloadUrl)
}
func downloadFile(ctx context.Context, filepath string, webUrl string) error {
if stat, err := os.Stat(filepath); err == nil {
resp, err := http.Head(webUrl)
if err != nil {
return err
}
defer resp.Body.Close()
var lastModified time.Time
lastModifiedHeader := resp.Header.Get("Last-Modified")
if lastModifiedHeader != "" {
lastModified, err = time.Parse(http.TimeFormat, lastModifiedHeader)
if err != nil {
return err
}
}
if resp.ContentLength == stat.Size() && lastModified.Before(stat.ModTime()) {
slog.InfoContext(ctx, "there is already newest version of the file", slog.String("filepath", filepath))
return nil
}
}
// Create the file
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, webUrl, nil)
if err != nil {
return err
}
// Get the data
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
// Writer the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}