package qbittorrent import ( "bytes" "context" "errors" "fmt" "io" "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" binName = "qbittorrent-nox" ) func runQBittorrent(binDir string, profileDir string, stdout, stderr io.Writer) (*os.Process, error) { cmd := exec.Command( path.Join(binDir, binName), fmt.Sprintf("--profile=%s", profileDir), ) 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 downloadLatestRelease(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 } return downloadFile(binPath, downloadUrl) } func downloadFile(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()) { return nil } } // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Get the data resp, err := http.Get(webUrl) 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 }