From 05763bc23db4b0dc814e92e17563e5bfff01fc48 Mon Sep 17 00:00:00 2001 From: Xpl0itU <24777100+Xpl0itU@users.noreply.github.com> Date: Sat, 20 Jan 2024 12:54:18 +0100 Subject: [PATCH] Initial aria2go implementation (#80) --- .gitignore | 2 + certificate.go | 9 +- cmd/WiiUDownloader/main.go | 16 +- cmd/WiiUDownloader/mainwindow.go | 23 +- cmd/WiiUDownloader/progressWindow.go | 18 +- downloader.go | 142 +++++++----- go.mod | 9 + go.sum | 4 + grabTitles.py | 24 +- pkg/aria2go/LICENSE | 201 ++++++++++++++++ pkg/aria2go/aria2_c.cc | 323 ++++++++++++++++++++++++++ pkg/aria2go/aria2_c.h | 72 ++++++ pkg/aria2go/go.mod | 8 + pkg/aria2go/go.sum | 4 + pkg/aria2go/libaria2.go | 331 +++++++++++++++++++++++++++ pkg/aria2go/models.go | 51 +++++ pkg/aria2go/notifier.go | 58 +++++ prepare_aria.sh | 134 +++++++++++ 18 files changed, 1343 insertions(+), 86 deletions(-) create mode 100644 pkg/aria2go/LICENSE create mode 100644 pkg/aria2go/aria2_c.cc create mode 100644 pkg/aria2go/aria2_c.h create mode 100644 pkg/aria2go/go.mod create mode 100644 pkg/aria2go/go.sum create mode 100644 pkg/aria2go/libaria2.go create mode 100644 pkg/aria2go/models.go create mode 100644 pkg/aria2go/notifier.go create mode 100755 prepare_aria.sh diff --git a/.gitignore b/.gitignore index b710d90..3b599dc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ main *.a out/ log.txt +aria2-lib/ +_obj/ \ No newline at end of file diff --git a/certificate.go b/certificate.go index 205a2e2..5125f68 100644 --- a/certificate.go +++ b/certificate.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "net/http" "os" "path" ) @@ -28,12 +27,12 @@ func getCert(tmdData []byte, id int, numContents uint16) ([]byte, error) { } } -func getDefaultCert(cancelCtx context.Context, progressReporter ProgressReporter, client *http.Client, buffer []byte) ([]byte, error) { +func getDefaultCert(cancelCtx context.Context, progressReporter ProgressReporter, buffer []byte, ariaSessionPath string) ([]byte, error) { if len(cetkData) >= 0x350+0x300 { return cetkData[0x350 : 0x350+0x300], nil } cetkDir := path.Join(os.TempDir(), "cetk") - if err := downloadFile(cancelCtx, progressReporter, client, "http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/000500101000400a/cetk", cetkDir, true, buffer); err != nil { + if err := downloadFile(cancelCtx, progressReporter, "http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/000500101000400a/cetk", cetkDir, true, buffer, ariaSessionPath); err != nil { return nil, err } cetkData, err := os.ReadFile(cetkDir) @@ -51,7 +50,7 @@ func getDefaultCert(cancelCtx context.Context, progressReporter ProgressReporter return nil, fmt.Errorf("failed to download OSv10 cetk, length: %d", len(cetkData)) } -func GenerateCert(tmdData []byte, contentCount uint16, progressReporter ProgressReporter, client *http.Client, cancelCtx context.Context, buffer []byte) (bytes.Buffer, error) { +func GenerateCert(tmdData []byte, contentCount uint16, progressReporter ProgressReporter, cancelCtx context.Context, buffer []byte, ariaSessionPath string) (bytes.Buffer, error) { cert := bytes.Buffer{} cert0, err := getCert(tmdData, 0, contentCount) @@ -66,7 +65,7 @@ func GenerateCert(tmdData []byte, contentCount uint16, progressReporter Progress } cert.Write(cert1) - defaultCert, err := getDefaultCert(cancelCtx, progressReporter, client, buffer) + defaultCert, err := getDefaultCert(cancelCtx, progressReporter, buffer, ariaSessionPath) if err != nil { return bytes.Buffer{}, err } diff --git a/cmd/WiiUDownloader/main.go b/cmd/WiiUDownloader/main.go index 7ffcbab..256acaf 100644 --- a/cmd/WiiUDownloader/main.go +++ b/cmd/WiiUDownloader/main.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "net/http" "os" "path/filepath" "runtime" - "time" wiiudownloader "github.com/Xpl0itU/WiiUDownloader" "github.com/gotk3/gotk3/glib" @@ -42,17 +40,15 @@ func main() { logger.Fatal(err.Error()) } - client := &http.Client{ - Transport: &http.Transport{ - MaxIdleConns: 1000, - MaxIdleConnsPerHost: 1000, - MaxConnsPerHost: 100, - }, - Timeout: 30 * time.Second, + tmpDir, err := os.MkdirTemp("", "wiiudownloader") + if err != nil { + logger.Fatal(err.Error()) } + defer os.RemoveAll(tmpDir) + ariaSessionPath := filepath.Join(tmpDir, "wiiudownloader.session") app.Connect("activate", func() { - win := NewMainWindow(app, wiiudownloader.GetTitleEntries(wiiudownloader.TITLE_CATEGORY_GAME), logger, client) + win := NewMainWindow(app, wiiudownloader.GetTitleEntries(wiiudownloader.TITLE_CATEGORY_GAME), logger, ariaSessionPath) win.ShowAll() app.AddWindow(win.window) app.GetActiveWindow().Show() diff --git a/cmd/WiiUDownloader/mainwindow.go b/cmd/WiiUDownloader/mainwindow.go index 3fc598c..9fd06ca 100644 --- a/cmd/WiiUDownloader/mainwindow.go +++ b/cmd/WiiUDownloader/mainwindow.go @@ -5,7 +5,6 @@ import ( "context" "encoding/binary" "fmt" - "net/http" "os" "path/filepath" "strconv" @@ -40,10 +39,10 @@ type MainWindow struct { titles []wiiudownloader.TitleEntry decryptContents bool currentRegion uint8 - client *http.Client + ariaSessionPath string } -func NewMainWindow(app *gtk.Application, entries []wiiudownloader.TitleEntry, logger *wiiudownloader.Logger, client *http.Client) *MainWindow { +func NewMainWindow(app *gtk.Application, entries []wiiudownloader.TitleEntry, logger *wiiudownloader.Logger, ariaSessionPath string) *MainWindow { gSettings, err := gtk.SettingsGetDefault() if err != nil { logger.Error(err.Error()) @@ -67,13 +66,13 @@ func NewMainWindow(app *gtk.Application, entries []wiiudownloader.TitleEntry, lo } mainWindow := MainWindow{ - window: win, - titles: entries, - searchEntry: searchEntry, - currentRegion: wiiudownloader.MCP_REGION_EUROPE | wiiudownloader.MCP_REGION_JAPAN | wiiudownloader.MCP_REGION_USA, - logger: logger, - lastSearchText: "", - client: client, + window: win, + titles: entries, + searchEntry: searchEntry, + currentRegion: wiiudownloader.MCP_REGION_EUROPE | wiiudownloader.MCP_REGION_JAPAN | wiiudownloader.MCP_REGION_USA, + logger: logger, + lastSearchText: "", + ariaSessionPath: ariaSessionPath, } searchEntry.Connect("changed", mainWindow.onSearchEntryChanged) @@ -265,7 +264,7 @@ func (mw *MainWindow) ShowAll() { wiiudownloader.GenerateTicket(filepath.Join(parentDir, "title.tik"), titleID, titleKey, titleVersion) - cert, err := wiiudownloader.GenerateCert(tmdData, contentCount, mw.progressWindow, http.DefaultClient, context.Background(), make([]byte, 0)) + cert, err := wiiudownloader.GenerateCert(tmdData, contentCount, mw.progressWindow, context.Background(), make([]byte, 0), mw.ariaSessionPath) if err != nil { return } @@ -760,7 +759,7 @@ func (mw *MainWindow) onDownloadQueueClicked(selectedPath string) error { } tidStr := fmt.Sprintf("%016x", title.TitleID) titlePath := filepath.Join(selectedPath, fmt.Sprintf("%s [%s] [%s]", normalizeFilename(title.Name), wiiudownloader.GetFormattedKind(title.TitleID), tidStr)) - if err := wiiudownloader.DownloadTitle(queueCtx, tidStr, titlePath, mw.decryptContents, mw.progressWindow, mw.getDeleteEncryptedContents(), mw.logger, mw.client); err != nil && err != context.Canceled { + if err := wiiudownloader.DownloadTitle(queueCtx, tidStr, titlePath, mw.decryptContents, mw.progressWindow, mw.getDeleteEncryptedContents(), mw.logger, mw.ariaSessionPath); err != nil && err != context.Canceled { return err } diff --git a/cmd/WiiUDownloader/progressWindow.go b/cmd/WiiUDownloader/progressWindow.go index 779ea2e..2d2dc6b 100644 --- a/cmd/WiiUDownloader/progressWindow.go +++ b/cmd/WiiUDownloader/progressWindow.go @@ -3,12 +3,15 @@ package main import ( "context" "fmt" + "time" "github.com/dustin/go-humanize" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" ) +const smoothingFactor = 0.1 + type ProgressWindow struct { Window *gtk.Window box *gtk.Box @@ -19,6 +22,8 @@ type ProgressWindow struct { cancelFunc context.CancelFunc totalToDownload int64 totalDownloaded int64 + lastUpdateTime time.Time + averageSpeed float64 } func (pw *ProgressWindow) SetGameTitle(title string) { @@ -35,8 +40,19 @@ func (pw *ProgressWindow) UpdateDownloadProgress(downloaded, speed int64, filePa pw.cancelButton.SetSensitive(true) currentDownload := downloaded + pw.totalDownloaded pw.bar.SetFraction(float64(currentDownload) / float64(pw.totalToDownload)) - pw.bar.SetText(fmt.Sprintf("Downloading %s (%s/%s) (%s/s)", filePath, humanize.Bytes(uint64(currentDownload)), humanize.Bytes(uint64(pw.totalToDownload)), humanize.Bytes(uint64(speed)))) + + pw.averageSpeed = smoothingFactor*float64(speed) + (1-smoothingFactor)*pw.averageSpeed + + pw.bar.SetText(fmt.Sprintf("Downloading %s (%s/%s) (%s/s)", + filePath, + humanize.Bytes(uint64(currentDownload)), + humanize.Bytes(uint64(pw.totalToDownload)), + humanize.Bytes(uint64(pw.averageSpeed)), + )) + + pw.lastUpdateTime = time.Now() }) + for gtk.EventsPending() { gtk.MainIteration() } diff --git a/downloader.go b/downloader.go index 113ccfb..fe06d4f 100644 --- a/downloader.go +++ b/downloader.go @@ -8,12 +8,14 @@ import ( "encoding/binary" "encoding/hex" "fmt" - "io" - "net/http" "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" + + "github.com/jaskaranSM/aria2go" ) const ( @@ -33,6 +35,38 @@ type ProgressReporter interface { AddToTotalDownloaded(toAdd int64) } +type Aria2gocNotifier struct { + start chan string + complete chan bool +} + +func newAria2goNotifier(start chan string, complete chan bool) aria2go.Notifier { + return Aria2gocNotifier{ + start: start, + complete: complete, + } +} + +func (n Aria2gocNotifier) OnStart(gid string) { + n.start <- gid +} + +func (n Aria2gocNotifier) OnPause(gid string) { + return +} + +func (n Aria2gocNotifier) OnStop(gid string) { + return +} + +func (n Aria2gocNotifier) OnComplete(gid string) { + n.complete <- false +} + +func (n Aria2gocNotifier) OnError(gid string) { + n.complete <- true +} + func calculateDownloadSpeed(downloaded int64, startTime, endTime time.Time) int64 { duration := endTime.Sub(startTime).Seconds() if duration > 0 { @@ -41,73 +75,75 @@ func calculateDownloadSpeed(downloaded int64, startTime, endTime time.Time) int6 return 0 } -func downloadFile(ctx context.Context, progressReporter ProgressReporter, client *http.Client, downloadURL, dstPath string, doRetries bool, buffer []byte) error { - filePath := filepath.Base(dstPath) +func downloadFile(ctx context.Context, progressReporter ProgressReporter, downloadURL, dstPath string, doRetries bool, buffer []byte, ariaSessionPath string) error { + fileName := filepath.Base(dstPath) var startTime time.Time for attempt := 1; attempt <= maxRetries; attempt++ { - req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) + client := aria2go.NewAria2(aria2go.Config{ + Options: aria2go.Options{ + "save-session": ariaSessionPath, + }, + }) + + gid, err := client.AddUri(downloadURL, aria2go.Options{ + "dir": filepath.Dir(dstPath), + "out": fileName, + "continue": "true", + }) if err != nil { return err } - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() + go func() { + defer client.Shutdown() + client.Run() + }() - if resp.StatusCode != http.StatusOK { - if doRetries && attempt < maxRetries { - time.Sleep(retryDelay) - continue - } - return fmt.Errorf("download error after %d attempts, status code: %d", attempt, resp.StatusCode) - } + startNotif := make(chan string) + completeNotif := make(chan bool) + go func() { + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) - file, err := os.Create(dstPath) - if err != nil { - return err - } - defer file.Close() - - var downloaded int64 + <-quit + completeNotif <- true + }() + client.SetNotifier(newAria2goNotifier(startNotif, completeNotif)) startTime = time.Now() + ticker := time.NewTicker(time.Millisecond * 500) + defer ticker.Stop() + loop: for { - n, err := resp.Body.Read(buffer) - if err != nil && err != io.EOF { - if doRetries && attempt < maxRetries { - time.Sleep(retryDelay) - break + select { + case id := <-startNotif: + gid = id + case <-ticker.C: + downloaded := client.GetDownloadInfo(gid).BytesCompleted + progressReporter.UpdateDownloadProgress(downloaded, calculateDownloadSpeed(downloaded, startTime, time.Now()), fileName) + case errored := <-completeNotif: + if errored { + if doRetries && attempt < maxRetries { + time.Sleep(retryDelay) + break loop + } + return fmt.Errorf("write error after %d attempts: %+v", attempt, client.GetDownloadInfo(gid).ErrorCode) } - return fmt.Errorf("download error after %d attempts: %+v", attempt, err) + downloaded := client.GetDownloadInfo(gid).BytesCompleted + progressReporter.UpdateDownloadProgress(downloaded, calculateDownloadSpeed(downloaded, startTime, time.Now()), fileName) + return nil + case <-ctx.Done(): + return nil } - - if n == 0 { - break - } - - _, err = file.Write(buffer[:n]) - if err != nil { - if doRetries && attempt < maxRetries { - time.Sleep(retryDelay) - break - } - return fmt.Errorf("write error after %d attempts: %+v", attempt, err) - } - - downloaded += int64(n) - progressReporter.UpdateDownloadProgress(downloaded, calculateDownloadSpeed(downloaded, startTime, time.Now()), filePath) } - break } return nil } -func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, doDecryption bool, progressReporter ProgressReporter, deleteEncryptedContents bool, logger *Logger, client *http.Client) error { +func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, doDecryption bool, progressReporter ProgressReporter, deleteEncryptedContents bool, logger *Logger, ariaSessionPath string) error { titleEntry := getTitleEntryFromTid(titleID) progressReporter.SetTotalDownloaded(0) @@ -127,7 +163,7 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d buffer := make([]byte, bufferSize) tmdPath := filepath.Join(outputDir, "title.tmd") - if err := downloadFile(cancelCtx, progressReporter, client, fmt.Sprintf("%s/%s", baseURL, "tmd"), tmdPath, true, buffer); err != nil { + if err := downloadFile(cancelCtx, progressReporter, fmt.Sprintf("%s/%s", baseURL, "tmd"), tmdPath, true, buffer, ariaSessionPath); err != nil { if progressReporter.Cancelled() { return nil } @@ -145,7 +181,7 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d } tikPath := filepath.Join(outputDir, "title.tik") - if err := downloadFile(cancelCtx, progressReporter, client, fmt.Sprintf("%s/%s", baseURL, "cetk"), tikPath, false, buffer); err != nil { + if err := downloadFile(cancelCtx, progressReporter, fmt.Sprintf("%s/%s", baseURL, "cetk"), tikPath, false, buffer, ariaSessionPath); err != nil { if progressReporter.Cancelled() { return nil } @@ -184,7 +220,7 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d progressReporter.SetDownloadSize(int64(titleSize)) - cert, err := GenerateCert(tmdData, contentCount, progressReporter, client, cancelCtx, buffer) + cert, err := GenerateCert(tmdData, contentCount, progressReporter, cancelCtx, buffer, ariaSessionPath) if err != nil { if progressReporter.Cancelled() { return nil @@ -228,7 +264,7 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d return err } filePath := filepath.Join(outputDir, fmt.Sprintf("%08X.app", id)) - if err := downloadFile(cancelCtx, progressReporter, client, fmt.Sprintf("%s/%08X", baseURL, id), filePath, true, buffer); err != nil { + if err := downloadFile(cancelCtx, progressReporter, fmt.Sprintf("%s/%08X", baseURL, id), filePath, true, buffer, ariaSessionPath); err != nil { if progressReporter.Cancelled() { break } @@ -238,7 +274,7 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d if tmdData[offset+7]&0x2 == 2 { filePath = filepath.Join(outputDir, fmt.Sprintf("%08X.h3", id)) - if err := downloadFile(cancelCtx, progressReporter, client, fmt.Sprintf("%s/%08X.h3", baseURL, id), filePath, true, buffer); err != nil { + if err := downloadFile(cancelCtx, progressReporter, fmt.Sprintf("%s/%08X.h3", baseURL, id), filePath, true, buffer, ariaSessionPath); err != nil { if progressReporter.Cancelled() { break } diff --git a/go.mod b/go.mod index a376617..cd35228 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,15 @@ require ( golang.org/x/crypto v0.17.0 ) +require github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb // indirect + +require ( + github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b // indirect + github.com/jaskaranSM/aria2go v0.0.0-20210417130736-a4fd19b6cb10 +) + +replace github.com/jaskaranSM/aria2go => ./pkg/aria2go + require ( github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d // indirect golang.org/x/sync v0.5.0 diff --git a/go.sum b/go.sum index be69bf4..6a9c0cf 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,14 @@ github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d h1:2xp1BQbqcDDaik github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= github.com/Xpl0itU/dialog v0.0.0-20230805114139-ec888310aded h1:GkBw5aNvID1+SKAD3xC5fU4EwMgOmkrvICy5NX3Rqvw= github.com/Xpl0itU/dialog v0.0.0-20230805114139-ec888310aded/go.mod h1:Yl652wzqaetwEMJ8FnDRKBK1+CisE+PU5BGJXItbYFg= +github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b h1:5JgaFtHFRnOPReItxvhMDXbvuBkjSWE+9glJyF466yw= +github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b/go.mod h1:eMD2XUcPsHYbakFEocKrWZp47G0MRJYoC60qFblGjpA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gotk3/gotk3 v0.6.2 h1:sx/PjaKfKULJPTPq8p2kn2ZbcNFxpOJqi4VLzMbEOO8= github.com/gotk3/gotk3 v0.6.2/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb h1:asfjGoPvNgSPvgbBiwFqMUOgWgid8xlQGCGHfgM/PAs= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= diff --git a/grabTitles.py b/grabTitles.py index 9de6908..87386a0 100644 --- a/grabTitles.py +++ b/grabTitles.py @@ -6,11 +6,13 @@ import ssl # Don't edit below this line + def checkAndDeleteFile(file): if os.path.exists(file): print(f"Deleting {file}") os.remove(file) + # Disable certificate verification ssl_context = ssl.create_default_context() ssl_context.check_hostname = False @@ -22,13 +24,25 @@ urllib.request.install_opener(opener) checkAndDeleteFile("gtitles/gtitles.c") urllib.request.urlretrieve("https://napi.nbg01.v10lator.de/db", "gtitles/gtitles.c") -os.system("gcc -c -Wall -fpic -Ofast -pipe -Igtitles -o gtitles/gtitles.o gtitles/gtitles.c") +os.system( + "gcc -c -Wall -fpic -Ofast -pipe -Igtitles -o gtitles/gtitles.o gtitles/gtitles.c" +) os.system("ar rcs libgtitles.a gtitles/gtitles.o") os.system("gcc -shared -o gtitles/libgtitles.so gtitles/gtitles.o") -os.system("gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/aes.o cdecrypt/aes.c") -os.system("gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/cdecrypt.o cdecrypt/cdecrypt.c") -os.system("gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/sha1.o cdecrypt/sha1.c") -os.system("gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/util.o cdecrypt/util.c") +os.system( + "gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/aes.o cdecrypt/aes.c" +) +os.system( + "gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/cdecrypt.o cdecrypt/cdecrypt.c" +) +os.system( + "gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/sha1.o cdecrypt/sha1.c" +) +os.system( + "gcc -c -Wall -fpic -Ofast -pipe -UNDEBUG -DAES_ROM_TABLES -D_GNU_SOURCE -Icdecrypt -o cdecrypt/util.o cdecrypt/util.c" +) os.system("ar rcs libcdecrypt.a cdecrypt/*.o") os.system("gcc -shared -o cdecrypt/libcdecrypt.so cdecrypt/*.o") + +os.system("./prepare_aria.sh") diff --git a/pkg/aria2go/LICENSE b/pkg/aria2go/LICENSE new file mode 100644 index 0000000..20be111 --- /dev/null +++ b/pkg/aria2go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2019 Vincent Chueng (coolingfall@gmail.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/pkg/aria2go/aria2_c.cc b/pkg/aria2go/aria2_c.cc new file mode 100644 index 0000000..ab95ed4 --- /dev/null +++ b/pkg/aria2go/aria2_c.cc @@ -0,0 +1,323 @@ +#include "aria2_c.h" +#include "_cgo_export.h" +#include +#include +#include +#include +#include + +pthread_mutex_t access_mutex; + + + +std::vector splitBySemicolon(std::string in) { + std::string val; + std::stringstream is(in); + std::vector out; + + while (getline(is, val, ';')) { + out.push_back(val); + } + return out; +} + +const char *toCStr(std::string in) { + int len = in.length(); + char *val = new char[len + 1]; + memcpy(val, in.data(), len); + val[len] = '\0'; + return val; +} + +const char *getFileName(std::string dir, std::string path) { + if (path.find(dir, 0) == std::string::npos) { + return toCStr(path); + } + int index = dir.size(); + std::string name = path.substr(index + 1); + return toCStr(name); +} + +const char *toAria2goOptions(aria2::KeyVals options) { + std::vector>::iterator it; + + std::string cOptions; + for (it = options.begin(); it != options.end(); it++) { + std::pair p = *it; + cOptions += p.first + ";" + p.second + ";"; + } + + return toCStr(cOptions); +} + +aria2::KeyVals toAria2Options(const char *options) { + aria2::KeyVals aria2Options; + + if (options == nullptr) { + return aria2Options; + } + + std::vector o = splitBySemicolon(std::string(options)); + /* key and val should be pair */ + if (o.size() % 2 != 0) { + return aria2Options; + } + + for (int i = 0; i < (int)o.size(); i += 2) { + std::string key = o[i]; + std::string val = o[i + 1]; + aria2Options.push_back(std::make_pair(key, val)); + } + + return aria2Options; +} + +struct FileInfo *parseFileData(aria2::DownloadHandle *dh) { + std::string dir = dh->getDir(); + std::vector files = dh->getFiles(); + int numFiles = dh->getNumFiles(); + struct FileInfo *allFiles = new FileInfo[numFiles]; + for (int i = 0; i < numFiles; i++) { + aria2::FileData file = files[i]; + struct FileInfo *fi = new FileInfo(); + fi->index = file.index; + fi->name = getFileName(dir, file.path); + fi->length = file.length; + fi->completedLength = file.completedLength; + fi->selected = file.selected; + + allFiles[i] = *fi; + delete fi; + } + return allFiles; +} + +/* retrieve all BitTorrent meta information */ +struct MetaInfo *parseMetaInfo(aria2::BtMetaInfoData btMetaInfo) { + struct MetaInfo *mi = new MetaInfo(); + mi->name = toCStr(btMetaInfo.name); + mi->comment = toCStr(btMetaInfo.comment); + mi->creationUnix = btMetaInfo.creationDate; + std::vector> announceList = btMetaInfo.announceList; + std::vector>::iterator it; + std::string cAnnounceList; + for (it = announceList.begin(); it != announceList.end(); it++) { + std::vector::iterator cit; + std::vector childList = *it; + for (cit = childList.begin(); cit != childList.end(); cit++) { + cAnnounceList += *cit; + if (it != announceList.end() - 1 || cit != childList.end() - 1) { + cAnnounceList += ";"; + } + } + } + mi->announceList = toCStr(cAnnounceList); + return mi; +} + +/** + * Global aria2 session. + */ +aria2::Session *session; +/** + * Global aria2go go pointer. + */ +uint64_t aria2goPointer; + +/** + * Download event callback for aria2. + */ +int downloadEventCallback(aria2::Session *session, aria2::DownloadEvent event, + const aria2::A2Gid gid, void *userData) { + notifyEvent(aria2goPointer, gid, event); + return 0; +} + +/** + * Initial aria2 library. + */ +int init(uint64_t pointer, const char *options) { + aria2goPointer = pointer; + int ret = aria2::libraryInit(); + aria2::SessionConfig config; + config.keepRunning = true; + /* do not use signal handler, cause c will block go */ + config.useSignalHandler = false; + config.downloadEventCallback = downloadEventCallback; + session = aria2::sessionNew(toAria2Options(options), config); + return ret; +} + +/** + * Shutdown schedules. This will cause run finished. + */ +int shutdownSchedules(bool force) { return aria2::shutdown(session, force); } + +/** + * Deinit aria2 library, this must be invoked when process exit(signal handler + * is not used), so aria2 will be able to save session config. + */ +int deinit() { + int ret = aria2::sessionFinal(session); + session = nullptr; + aria2::libraryDeinit(); + return ret; +} + +/** + * Adds new HTTP(S)/FTP/BitTorrent Magnet URI. See `addUri` in aria2. + * + * @param uri uri to add + */ +uint64_t addUri(char *uri, const char *options) { + std::vector uris = {uri}; + aria2::A2Gid gid; + pthread_mutex_lock(&access_mutex); + int ret = aria2::addUri(session, &gid, uris, toAria2Options(options)); + pthread_mutex_unlock(&access_mutex); + if (ret < 0) { + return 0; + } + + return gid; +} + +/** + * Add bit torrent file. See `addTorrent` in aria2. + */ +uint64_t addTorrent(char *fp, const char *options) { + aria2::A2Gid gid; + pthread_mutex_lock(&access_mutex); + int ret = aria2::addTorrent(session, &gid, fp, toAria2Options(options)); + pthread_mutex_unlock(&access_mutex); + if (ret < 0) { + return 0; + } + + return gid; +} + +/** + * Change aria2 options. See `changeOption` in aria2. + */ +bool changeOptions(uint64_t gid, const char *options) { + pthread_mutex_lock(&access_mutex); + bool ret = aria2::changeOption(session, gid, toAria2Options(options)) == 0; + pthread_mutex_unlock(&access_mutex); + return ret; +} + +/** + * Get options for given gid. see `getOptions` in aria2. + */ +const char *getOptions(uint64_t gid) { + pthread_mutex_lock(&access_mutex); + aria2::DownloadHandle *dh = aria2::getDownloadHandle(session, gid); + pthread_mutex_unlock(&access_mutex); + if (!dh) { + return nullptr; + } + + return toAria2goOptions(dh->getOptions()); +} + +/** + * Change global options. See `changeGlobalOption` in aria2. + */ +bool changeGlobalOptions(const char *options) { + pthread_mutex_lock(&access_mutex); + bool ret = aria2::changeGlobalOption(session, toAria2Options(options)); + pthread_mutex_unlock(&access_mutex); + return ret; +} + +/** + * Get global options. see `getGlobalOptions` in aria2. + */ +const char *getGlobalOptions() { + pthread_mutex_lock(&access_mutex); + aria2::KeyVals options = aria2::getGlobalOptions(session); + pthread_mutex_unlock(&access_mutex); + return toAria2goOptions(options); +} + +/** + * Performs event polling and actions for them. + */ +int run() { return aria2::run(session, aria2::RUN_DEFAULT); } + +/** + * Pause an active download with given gid. This will mark the download to + * `DOWNLOAD_PAUSED`. See `resume`. + */ +bool pause(uint64_t gid) { + pthread_mutex_lock(&access_mutex); + bool ret = aria2::pauseDownload(session, gid) == 0; + pthread_mutex_unlock(&access_mutex); + return ret; +} + +/** + * Resume a paused download with given gid. See `pause`. + */ +bool resume(uint64_t gid) { + pthread_mutex_lock(&access_mutex); + bool ret = aria2::unpauseDownload(session, gid) == 0; + pthread_mutex_unlock(&access_mutex); + return ret; +} + +/** + * Remove a download in queue with given gid. This will stop downloading and + * seeding(for torrent). + */ +bool removeDownload(uint64_t gid) { + pthread_mutex_lock(&access_mutex); + bool ret = aria2::removeDownload(session, gid) == 0; + pthread_mutex_unlock(&access_mutex); + return ret; +} + +/** + * Get download information for current download with given gid. + */ +struct DownloadInfo *getDownloadInfo(uint64_t gid) { + if (session == nullptr) { + return nullptr; + } + pthread_mutex_lock(&access_mutex); + aria2::DownloadHandle *dh = aria2::getDownloadHandle(session, gid); + pthread_mutex_unlock(&access_mutex); + if (!dh) { + return nullptr; + } + struct DownloadInfo *di = new DownloadInfo(); + di->status = dh->getStatus(); + di->totalLength = dh->getTotalLength(); + di->bytesCompleted = dh->getCompletedLength(); + di->uploadLength = dh->getUploadLength(); + di->downloadSpeed = dh->getDownloadSpeed(); + di->uploadSpeed = dh->getUploadSpeed(); + di->pieceLength = dh->getPieceLength(); + di->numPieces = dh->getNumPieces(); + di->connections = dh->getConnections(); + di->numFiles = dh->getNumFiles(); + di->infoHash = toCStr(dh->getInfoHash()); + di->metaInfo = parseMetaInfo(dh->getBtMetaInfo()); + di->files = parseFileData(dh); + di->errorCode = dh->getErrorCode(); + std::vector gids = dh->getFollowedBy(); + if (gids.size() != 0) { + di->followedByGid = gids[0]; + } else { + di->followedByGid = 0; + } + // std::cout << "status" << dh->getStatus() << std::endl; + // std::cout << "Error: " << dh->getErrorCode() << std::endl; + // std::cout << "Completed: " << dh->getCompletedLength() << std::endl; + // std::cout << "Total: " << dh->getTotalLength() << std::endl; + /* delete download handle */ + aria2::deleteDownloadHandle(dh); + return di; +} + diff --git a/pkg/aria2go/aria2_c.h b/pkg/aria2go/aria2_c.h new file mode 100644 index 0000000..20d113a --- /dev/null +++ b/pkg/aria2go/aria2_c.h @@ -0,0 +1,72 @@ +#ifndef ARIA2_C_H +#define ARIA2_C_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Type definition for file information in torrent. + */ +struct FileInfo { + int index; + const char *name; + int64_t length; + int64_t completedLength; + bool selected; +}; + +/** + * Type definition for BitTorrent meta information. + */ +struct MetaInfo { + const char *name; + const char *comment; + int64_t creationUnix; + const char *announceList; +}; + +/** + * Type definition for download information. + */ +struct DownloadInfo { + int status; + int64_t totalLength; + int64_t bytesCompleted; + int64_t uploadLength; + int downloadSpeed; + int uploadSpeed; + int pieceLength; + int numPieces; + int connections; + int numFiles; + const char *infoHash; + struct MetaInfo *metaInfo; + struct FileInfo *files; + int errorCode; + uint64_t followedByGid; +}; + +int init(uint64_t aria2goPointer, const char *options); +int shutdownSchedules(bool force); +int deinit(); +uint64_t addUri(char *uri, const char *options); +uint64_t addTorrent(char *fp, const char *options); +bool changeOptions(uint64_t gid, const char *options); +const char *getOptions(uint64_t gid); +bool changeGlobalOptions(const char *options); +const char *getGlobalOptions(); +int run(); +bool pause(uint64_t gid); +bool resume(uint64_t gid); +bool removeDownload(uint64_t gid); +struct DownloadInfo *getDownloadInfo(uint64_t gid); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/pkg/aria2go/go.mod b/pkg/aria2go/go.mod new file mode 100644 index 0000000..1b0732f --- /dev/null +++ b/pkg/aria2go/go.mod @@ -0,0 +1,8 @@ +module github.com/jaskaranSM/aria2go + +go 1.12 + +require ( + github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b // indirect + github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb // indirect +) diff --git a/pkg/aria2go/go.sum b/pkg/aria2go/go.sum new file mode 100644 index 0000000..45f19f7 --- /dev/null +++ b/pkg/aria2go/go.sum @@ -0,0 +1,4 @@ +github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b h1:5JgaFtHFRnOPReItxvhMDXbvuBkjSWE+9glJyF466yw= +github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b/go.mod h1:eMD2XUcPsHYbakFEocKrWZp47G0MRJYoC60qFblGjpA= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb h1:asfjGoPvNgSPvgbBiwFqMUOgWgid8xlQGCGHfgM/PAs= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20231130194700-cfcb2fd150eb/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= diff --git a/pkg/aria2go/libaria2.go b/pkg/aria2go/libaria2.go new file mode 100644 index 0000000..d54f2fd --- /dev/null +++ b/pkg/aria2go/libaria2.go @@ -0,0 +1,331 @@ +// Copyright (C) 2019 Vincent Chueng (coolingfall@gmail.com). + +package aria2go + +/* + #cgo CXXFLAGS: -std=c++11 -I./aria2-lib/include -Werror -Wall + #cgo LDFLAGS: -L./aria2-lib/lib + #cgo LDFLAGS: -lcrypto -lssl -lcares -lz -laria2 -framework Security + #include + #include "aria2_c.h" +*/ +import "C" +import ( + "errors" + "fmt" + "log" + "strconv" + "strings" + "sync" + "unsafe" + + _ "github.com/benesch/cgosymbolizer" +) + +// Type definition for lib aria2, it holds a notifier. +type Aria2 struct { + notifier Notifier + shutdownNotification chan bool + shouldShutdown bool + m_mutex sync.Mutex +} + +// Type definition of configuration for aria2. +type Config struct { + Options Options + Notifier Notifier +} + +// NewAria2 creates a new instance of aria2. +func NewAria2(config Config) *Aria2 { + a := &Aria2{ + notifier: newDefaultNotifier(), + shutdownNotification: make(chan bool), + } + a.SetNotifier(config.Notifier) + + C.init(C.uint64_t(uintptr(unsafe.Pointer(a))), + C.CString(a.fromOptions(config.Options))) + return a +} + +// Shutdown aria2, this must be invoked when process exit(signal handler is not +// used), so aria2 will be able to save session config. +func (a *Aria2) Shutdown() int { + C.shutdownSchedules(true) + a.shouldShutdown = true + + // do nothing, just make thread waiting + select { + case <-a.shutdownNotification: + break + } + + return int(C.deinit()) +} + +// Run starts event pooling. Note this will block current thread. +func (a *Aria2) Run() { + for { + if C.run() != 1 && a.shouldShutdown { + break + } + } + a.shutdownNotification <- true +} + +// SetNotifier sets notifier to receive download notification from aria2. +func (a *Aria2) SetNotifier(notifier Notifier) { + if notifier == nil { + return + } + a.notifier = notifier +} + +// AddUri adds a new download. The uris is an array of HTTP/FTP/SFTP/BitTorrent +// URIs (strings) pointing to the same resource. When adding BitTorrent Magnet +// URIs, uris must have only one element and it should be BitTorrent Magnet URI. +func (a *Aria2) AddUri(uri string, options Options) (gid string, err error) { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + cUri := C.CString(uri) + cOptions := C.CString(a.fromOptions(options)) + defer C.free(unsafe.Pointer(cUri)) + defer C.free(unsafe.Pointer(cOptions)) + + ret := C.addUri(cUri, cOptions) + if ret == 0 { + return "", errors.New("libaria2: add uri failed") + } + return fmt.Sprintf("%x", uint64(ret)), nil +} + +// AddTorrent adds a MetaInfo download with given torrent file path. +// This will return gid and files in torrent file if add successfully. +// User can choose specified files to download, change directory and so on. +func (a *Aria2) AddTorrent(filepath string, options Options) (gid string, err error) { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + cFilepath := C.CString(filepath) + cOptions := C.CString(a.fromOptions(options)) + defer C.free(unsafe.Pointer(cFilepath)) + defer C.free(unsafe.Pointer(cOptions)) + + ret := C.addTorrent(cFilepath, cOptions) + if ret == 0 { + return "", errors.New("libaria2: add torrent failed") + } + return fmt.Sprintf("%x", uint64(ret)), nil +} + +// ChangeOptions can change the options for aria2. See available options in +// https://aria2.github.io/manual/en/html/aria2c.html#input-file. +func (a *Aria2) ChangeOptions(gid string, options Options) error { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + cOptions := C.CString(a.fromOptions(options)) + defer C.free(unsafe.Pointer(cOptions)) + + if !C.changeOptions(a.hexToGid(gid), cOptions) { + return errors.New("libaria2: change options error") + } + + return nil +} + +// GetOptions gets all options for given gid. +func (a *Aria2) GetOptions(gid string) Options { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + cOptions := C.getOptions(a.hexToGid(gid)) + if cOptions == nil { + return make(Options) + } + + return a.toOptions(C.GoString(cOptions)) +} + +// ChangeGlobalOptions changes global options. See available options in +// https://aria2.github.io/manual/en/html/aria2c.html#input-file except for +// `checksum`, `index-out`, `out`, `pause` and `select-file`. +func (a *Aria2) ChangeGlobalOptions(options Options) error { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + cOptions := C.CString(a.fromOptions(options)) + defer C.free(unsafe.Pointer(cOptions)) + + if !C.changeGlobalOptions(cOptions) { + return errors.New("libaria2: change global options error") + } + + return nil +} + +// GetGlobalOptions gets all global options of aria2. +func (a *Aria2) GetGlobalOptions() Options { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + return a.toOptions(C.GoString(C.getGlobalOptions())) +} + +// Pause pauses an active download for given gid. The status of the download +// will become `DOWNLOAD_PAUSED`. Use `Resume` to restart download. +func (a *Aria2) Pause(gid string) bool { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + return bool(C.pause(a.hexToGid(gid))) +} + +// Resume resumes an paused download for given gid. +func (a *Aria2) Resume(gid string) bool { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + return bool(C.resume(a.hexToGid(gid))) +} + +// Remove removes download no matter what status it was. This will stop +// downloading and stop seeding(for torrent). +func (a *Aria2) Remove(gid string) bool { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + return bool(C.removeDownload(a.hexToGid(gid))) +} + +// GetDownloadInfo gets current download information for given gid. +func (a *Aria2) GetDownloadInfo(gid string) DownloadInfo { + a.m_mutex.Lock() + defer a.m_mutex.Unlock() + ret := C.getDownloadInfo(a.hexToGid(gid)) + if ret == nil { + return DownloadInfo{} + } + defer C.free(unsafe.Pointer(ret)) + + // convert info hash to hex string + infoHash := fmt.Sprintf("%x", []byte(C.GoString(ret.infoHash))) + C.free(unsafe.Pointer(ret.infoHash)) + // retrieve BitTorrent meta information + var metaInfo = MetaInfo{} + mi := ret.metaInfo + defer C.free(unsafe.Pointer(mi)) + if mi != nil { + announceList := strings.Split(C.GoString(mi.announceList), ";") + metaInfo = MetaInfo{ + Name: C.GoString(mi.name), + Comment: C.GoString(mi.comment), + CreationUnix: int64(mi.creationUnix), + AnnounceList: announceList, + } + C.free(unsafe.Pointer(mi.name)) + C.free(unsafe.Pointer(mi.comment)) + C.free(unsafe.Pointer(mi.announceList)) + } + return DownloadInfo{ + Status: int(ret.status), + TotalLength: int64(ret.totalLength), + BytesCompleted: int64(ret.bytesCompleted), + BytesUpload: int64(ret.uploadLength), + DownloadSpeed: int(ret.downloadSpeed), + UploadSpeed: int(ret.uploadSpeed), + NumPieces: int(ret.numPieces), + Connections: int(ret.connections), + InfoHash: infoHash, + MetaInfo: metaInfo, + Files: a.parseFiles(ret.files, ret.numFiles), + ErrorCode: int(ret.errorCode), + FollowedByGid: fmt.Sprintf("%x", uint64(ret.followedByGid)), + } +} + +// fromOptions converts `Options` to string with ';' separator. +func (a *Aria2) fromOptions(options Options) string { + if options == nil { + return "" + } + + var cOptions string + for k, v := range options { + cOptions += k + ";" + cOptions += v + ";" + } + + return strings.TrimSuffix(cOptions, ";") +} + +// fromOptions converts options string with ';' separator to `Options`. +func (a *Aria2) toOptions(cOptions string) Options { + coptions := strings.Split(strings.TrimSuffix(cOptions, ";"), ";") + var options = make(Options) + var index int + for index = 0; index < len(coptions); index += 2 { + options[coptions[index]] = coptions[index+1] + } + + return options +} + +// hexToGid convert hex to uint64 type gid. +func (a *Aria2) hexToGid(hex string) C.uint64_t { + id, err := strconv.ParseUint(hex, 16, 64) + if err != nil { + return 0 + } + return C.uint64_t(id) +} + +// parseFiles parses all files information from aria2. +func (a *Aria2) parseFiles(filesPointer *C.struct_FileInfo, length C.int) (files []File) { + cfiles := (*[1 << 20]C.struct_FileInfo)(unsafe.Pointer(filesPointer))[:length:length] + if cfiles == nil { + return + } + + for _, f := range cfiles { + files = append(files, File{ + Index: int(f.index), + Length: int64(f.length), + CompletedLength: int64(f.completedLength), + Name: C.GoString(f.name), + Selected: bool(f.selected), + }) + C.free(unsafe.Pointer(f.name)) + } + + // free c pointer resource + C.free(unsafe.Pointer(filesPointer)) + + return +} + +// noinspection GoUnusedFunction +// +//export notifyEvent +func notifyEvent(ariagoPointer uint64, id uint64, event int) { + a := (*Aria2)(unsafe.Pointer(uintptr(ariagoPointer))) + if a == nil || a.notifier == nil { + return + } + + // convert id to hex string + gid := fmt.Sprintf("%x", uint64(id)) + + switch event { + case onStart: + a.notifier.OnStart(gid) + case onPause: + a.notifier.OnPause(gid) + case onStop: + a.notifier.OnStop(gid) + case onComplete: + a.notifier.OnComplete(gid) + case onError: + a.notifier.OnError(gid) + } +} + +// noinspection GoUnusedFunction +// +//export goLog +func goLog(msg *C.char) { + log.Println(C.GoString(msg)) +} diff --git a/pkg/aria2go/models.go b/pkg/aria2go/models.go new file mode 100644 index 0000000..ca54941 --- /dev/null +++ b/pkg/aria2go/models.go @@ -0,0 +1,51 @@ +// Copyright (C) 2019 Vincent Chueng (coolingfall@gmail.com). + +package aria2go + +// Type definition for download information. +type DownloadInfo struct { + Status int + TotalLength int64 + BytesCompleted int64 + BytesUpload int64 + DownloadSpeed int + UploadSpeed int + NumPieces int + Connections int + BitField string + InfoHash string + MetaInfo MetaInfo + Files []File + ErrorCode int + FollowedByGid string +} + +// Type definition for BitTorrent meta information. +type MetaInfo struct { + Name string + AnnounceList []string + Comment string + CreationUnix int64 + Mode string +} + +// Type definition for file in torrent. +type File struct { + Index int + Name string + Length int64 + CompletedLength int64 + Selected bool +} + +type Options map[string]string + +// Type definition for download event, this will keep the same with aria2. +const ( + onStart = iota + 1 + onPause + onStop + onComplete + onError + onBTComplete +) diff --git a/pkg/aria2go/notifier.go b/pkg/aria2go/notifier.go new file mode 100644 index 0000000..c0eaa4e --- /dev/null +++ b/pkg/aria2go/notifier.go @@ -0,0 +1,58 @@ +// Copyright (C) 2019 Vincent Chueng (coolingfall@gmail.com). + +package aria2go + +import ( + "log" +) + +// Type definition for notifier which can be used in aria2c json rpc notification. +type Notifier interface { + // OnStart will be invoked when aria2 started to download + OnStart(gid string) + + // OnPause will be invoked when aria2 paused one download + OnPause(gid string) + + // OnPause will be invoked when aria2 stopped one download + OnStop(gid string) + + // OnComplete will be invoked when download completed + OnComplete(gid string) + + // OnError will be invoked when an error occoured + OnError(gid string) +} + +// Type definition for default notifier which dose nothing. +type DefaultNotifier struct{} + +// newDefaultNotifier creates a new instance of default Notifier. +func newDefaultNotifier() Notifier { + return DefaultNotifier{} +} + +// OnStart implements Notifier interface. +func (n DefaultNotifier) OnStart(gid string) { + log.Printf("on start %v", gid) +} + +// OnPause implements Notifier interface. +func (n DefaultNotifier) OnPause(gid string) { + log.Printf("on pause: %v", gid) +} + +// OnPause implements Notifier interface. +func (n DefaultNotifier) OnStop(gid string) { + log.Printf("on stop: %v", gid) +} + +// OnComplete implements Notifier interface. +func (n DefaultNotifier) OnComplete(gid string) { + log.Printf("on complete: %v", gid) +} + +// OnError implements Notifier interface. +func (n DefaultNotifier) OnError(gid string) { + log.Printf("on error: %v", gid) +} diff --git a/prepare_aria.sh b/prepare_aria.sh new file mode 100755 index 0000000..7438b64 --- /dev/null +++ b/prepare_aria.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +# In this configuration, the following dependent libraries are compiled: +# +# * zlib +# * c-ares +# * openSSL + +# Compiler and path +PREFIX=$PWD/pkg/aria2go/aria2-lib +C_COMPILER="gcc" +CXX_COMPILER="g++" + +NPROC=`nproc` +# check if nproc is a command +if ! [[ -x "$(command -v nproc)" ]]; then + echo 'Error: nproc is not installed. Using sysctl for macOS' >&2 + $NPROC=`sysctl -n hw.logicalcpu` # macOS +fi + +# Check tool for download +aria2c --help > /dev/null +if [[ "$?" -eq 0 ]]; then + DOWNLOADER="aria2c --check-certificate=false" +else + DOWNLOADER="wget -c" +fi + +echo "Remove old libs..." +rm -rf ${PREFIX} +rm -rf _obj + +## Version +ZLIB_V=1.2.11 +OPENSSL_V=1.1.1w +C_ARES_V=1.24.0 +ARIA2_V=1.37.0 + +## Dependencies +ZLIB=http://sourceforge.net/projects/libpng/files/zlib/${ZLIB_V}/zlib-${ZLIB_V}.tar.gz +OPENSSL=http://www.openssl.org/source/openssl-${OPENSSL_V}.tar.gz +C_ARES=http://c-ares.haxx.se/download/c-ares-${C_ARES_V}.tar.gz +ARIA2=https://github.com/aria2/aria2/releases/download/release-${ARIA2_V}/aria2-${ARIA2_V}.tar.bz2 + +## Config +BUILD_DIRECTORY=/tmp/ + +## Build +cd ${BUILD_DIRECTORY} + +# zlib build +if ! [[ -e zlib-${ZLIB_V}.tar.gz ]]; then + ${DOWNLOADER} ${ZLIB} +fi +tar zxvf zlib-${ZLIB_V}.tar.gz +cd zlib-${ZLIB_V} +PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig/ \ + LD_LIBRARY_PATH=${PREFIX}/lib/ CC="$C_COMPILER" CXX="$CXX_COMPILER" \ + ./configure --prefix=${PREFIX} --static +make -j`nproc` +make install +cd .. + +# c-ares build +if ! [[ -e c&&res-${C_ARES_V}.tar.gz ]]; then + ${DOWNLOADER} ${C_ARES} +fi +tar zxvf c-ares-${C_ARES_V}.tar.gz +cd c-ares-${C_ARES_V} +PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig/ \ + LD_LIBRARY_PATH=${PREFIX}/lib/ CC="$C_COMPILER" CXX="$CXX_COMPILER" \ + ./configure --prefix=${PREFIX} --enable-static --disable-shared +make -j`nproc` +make install +cd .. + +# openssl build +if ! [[ -e openssl-${OPENSSL_V}.tar.gz ]]; then + ${DOWNLOADER} ${OPENSSL} +fi +tar zxvf openssl-${OPENSSL_V}.tar.gz +cd openssl-${OPENSSL_V} +PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig/ \ + LD_LIBRARY_PATH=${PREFIX}/lib/ CC="$C_COMPILER" CXX="$CXX_COMPILER" \ + ./config --prefix=${PREFIX} +make -j`nproc` +make install_sw +cd .. + +# build aria2 static library +if ! [[ -e aria2-${ARIA2_V}.tar.bz2 ]]; then + ${DOWNLOADER} ${ARIA2} +fi +tar jxvf aria2-${ARIA2_V}.tar.bz2 +cd aria2-${ARIA2_V}/ +# set specific ldflags if macOS +if [[ "$OSTYPE" == "darwin"* ]]; then + export LDFLAGS="-L${PREFIX}/lib -framework Security" +fi +PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig/ \ + LD_LIBRARY_PATH=${PREFIX}/lib/ \ + CC="$C_COMPILER" \ + CXX="$CXX_COMPILER" \ + ./configure \ + --prefix=${PREFIX} \ + --without-sqlite3 \ + --without-libxml2 \ + --without-libexpat \ + --without-libgcrypt \ + --without-libssh2 \ + --with-openssl \ + --without-appletls \ + --without-libnettle \ + --without-gnutls \ + --without-libgmp \ + --enable-libaria2 \ + --enable-shared=no \ + --enable-static=yes +make -j`nproc` +make install +cd .. + +# cleaning +rm -rf zlib-${ZLIB_V} +rm -rf c-ares-${C_ARES_V} +rm -rf openssl-${OPENSSL_V} +rm -rf aria2-${ARIA2_V} +rm -rf ${PREFIX}/bin + +# generate files for c +cd ${PREFIX}/../ +go tool cgo libaria2.go + +echo "Prepare finished!" \ No newline at end of file