From a867d0e28fee6d6f8d0d4e4ce636ffec60f76f62 Mon Sep 17 00:00:00 2001 From: djoh Date: Tue, 7 May 2024 22:38:41 +0300 Subject: [PATCH] release 2.4.0 --- CHANGELOG.md | 7 +++ README.md | 3 ++ constants/constants.go | 2 +- downloader/downloader.go | 28 ++++++++++++ go.mod | 7 +-- go.sum | 8 ++-- main.go | 11 ++++- updater/tui.go | 85 +++++++++++++++++++++++++++++++++++ updater/updater.go | 96 ++++++++++++++++++++++++++++++++++++---- 9 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 downloader/downloader.go create mode 100644 updater/tui.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7abc3..9ad0a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2.4.0 (2024-05-07) + +- Added automatic download of `SII_Decrypt.exe` +- Added a progressbar for file downloads + +**Full Changelog**: https://github.com/djohts/tpc-truckersmp/compare/v2.3.0...v2.4.0 + ## 2.3.0 (2024-05-07) > [!WARNING] diff --git a/README.md b/README.md index f887bbd..1fa6652 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ ETS2 and ATS on Windows x64 ## Download +> [!NOTE] +> Starting from version 2.4.0, the app will automatically download `SII_Decrypt.exe` for you. + > [!WARNING] > You need to download both `SII_Decrypt.exe` and `tpc.exe` to use the app. Make sure they are in the same directory and that `SII_Decrypt.exe` is not renamed. diff --git a/constants/constants.go b/constants/constants.go index ca9996e..8b8c2b2 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -1,7 +1,7 @@ package constants const ( - APP_VERSION = `2.3.0` + APP_VERSION = `2.4.0` ETS = `Euro Truck Simulator 2` ATS = `American Truck Simulator` diff --git a/downloader/downloader.go b/downloader/downloader.go new file mode 100644 index 0000000..c17af61 --- /dev/null +++ b/downloader/downloader.go @@ -0,0 +1,28 @@ +package downloader + +import ( + "github.com/djohts/tpc-truckersmp/updater" + "github.com/djohts/tpc-truckersmp/utils" + "github.com/google/go-github/v61/github" +) + +func DownloadSiiDecrypt() (bool, error) { + release, err := updater.GetLatestRelease() + if err != nil { + return false, err + } + + asset := utils.FindOne(release.Assets, func(asset **github.ReleaseAsset) bool { + return *(*asset).Name == "SII_Decrypt.exe" + }) + if asset == nil { + return false, nil + } + + err = updater.DownloadFile(*(*asset).BrowserDownloadURL, "SII_Decrypt.exe") + if err != nil { + return false, err + } + + return true, nil +} diff --git a/go.mod b/go.mod index 9fbeb25..4c50456 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,10 @@ go 1.22 require ( git.tcp.direct/kayos/sendkeys v0.0.0-20211216174833-565f782ccf21 github.com/bradhe/stopwatch v0.0.0-20190618212248-a58cccc508ea + github.com/charmbracelet/bubbles v0.18.0 + github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/huh v0.3.0 + github.com/charmbracelet/lipgloss v0.10.0 github.com/charmbracelet/log v0.4.0 github.com/coreos/go-semver v0.3.1 github.com/creasty/defaults v1.7.0 @@ -21,9 +24,7 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.2.0 // indirect - github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6 // indirect - github.com/charmbracelet/bubbletea v0.25.0 // indirect - github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1a161b9..20e454c 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,12 @@ github.com/bradhe/stopwatch v0.0.0-20190618212248-a58cccc508ea h1:+GIgqdjrcKMHK1 github.com/bradhe/stopwatch v0.0.0-20190618212248-a58cccc508ea/go.mod h1:P/j2DSP/kCOakHBACzMqmOdrTEieqdSiB3U9fqk7qgc= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= -github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6 h1:6nVCV8pqGaeyxetur3gpX3AAaiyKgzjIoCPV3NXKZBE= -github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= @@ -37,8 +39,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= diff --git a/main.go b/main.go index 795aa11..8176166 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/charmbracelet/log" "github.com/djohts/tpc-truckersmp/config" "github.com/djohts/tpc-truckersmp/constants" + "github.com/djohts/tpc-truckersmp/downloader" "github.com/djohts/tpc-truckersmp/updater" "github.com/djohts/tpc-truckersmp/utils" "github.com/djohts/tpc-truckersmp/watcher" @@ -19,7 +20,15 @@ func main() { fmt.Println("tpc-truckersmp", "v"+constants.APP_VERSION, "by djohts") if !utils.IsFile("SII_Decrypt.exe") { - utils.HandleError(errors.New("SII_Decrypt.exe does not exist")) + log.Warn("SII_Decrypt.exe not found, downloading...") + success, err := downloader.DownloadSiiDecrypt() + if err != nil { + utils.HandleError(err) + } else if success { + log.Info("SII_Decrypt.exe downloaded successfully") + } else { + utils.HandleError(errors.New("failed to download SII_Decrypt.exe")) + } } log.Info("================= TPC For TruckersMP =================") diff --git a/updater/tui.go b/updater/tui.go new file mode 100644 index 0000000..5154f88 --- /dev/null +++ b/updater/tui.go @@ -0,0 +1,85 @@ +package updater + +import ( + "strings" + "time" + + "github.com/charmbracelet/bubbles/progress" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#626262")).Render + +const ( + padding = 2 + maxWidth = 80 +) + +type progressMsg float64 + +type progressErrMsg struct{ err error } + +func finalPause() tea.Cmd { + return tea.Tick(time.Millisecond*750, func(_ time.Time) tea.Msg { + return nil + }) +} + +type model struct { + pw *progressWriter + progress progress.Model + err error +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + return m, tea.Quit + + case tea.WindowSizeMsg: + m.progress.Width = msg.Width - padding*2 - 4 + if m.progress.Width > maxWidth { + m.progress.Width = maxWidth + } + return m, nil + + case progressErrMsg: + m.err = msg.err + return m, tea.Quit + + case progressMsg: + var cmds []tea.Cmd + + if msg >= 1.0 { + cmds = append(cmds, tea.Sequence(finalPause(), tea.Quit)) + } + + cmds = append(cmds, m.progress.SetPercent(float64(msg))) + return m, tea.Batch(cmds...) + + // FrameMsg is sent when the progress bar wants to animate itself + case progress.FrameMsg: + progressModel, cmd := m.progress.Update(msg) + m.progress = progressModel.(progress.Model) + return m, cmd + + default: + return m, nil + } +} + +func (m model) View() string { + if m.err != nil { + return "Error downloading: " + m.err.Error() + "\n" + } + + pad := strings.Repeat(" ", padding) + return "\n" + + pad + m.progress.View() + "\n\n" + + pad + helpStyle("Press any key to quit") +} diff --git a/updater/updater.go b/updater/updater.go index 8bafb42..0ed569f 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -2,8 +2,13 @@ package updater import ( "context" + "fmt" + "io" "net/http" + "os" + "github.com/charmbracelet/bubbles/progress" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/log" "github.com/coreos/go-semver/semver" "github.com/djohts/tpc-truckersmp/constants" @@ -12,24 +17,26 @@ import ( "github.com/minio/selfupdate" ) +var p *tea.Program + func CheckUpdates() (bool, string, error) { - latestRelease, err := getLatestRelease() + release, err := GetLatestRelease() if err != nil { return false, "", err } - needsUpdate := semver.New(constants.APP_VERSION).LessThan(*semver.New((*latestRelease.TagName)[1:])) + needsUpdate := semver.New(constants.APP_VERSION).LessThan(*semver.New((*release.TagName)[1:])) - return needsUpdate, *latestRelease.TagName, nil + return needsUpdate, *release.TagName, nil } func UpdateSelf() (bool, error) { - latestRelease, err := getLatestRelease() + release, err := GetLatestRelease() if err != nil { return false, err } - asset := utils.FindOne(latestRelease.Assets, func(asset **github.ReleaseAsset) bool { + asset := utils.FindOne(release.Assets, func(asset **github.ReleaseAsset) bool { return *(*asset).Name == "tpc.exe" }) if asset == nil { @@ -37,14 +44,20 @@ func UpdateSelf() (bool, error) { } log.Info("Downloading latest version...") - res, err := http.Get(*(*asset).BrowserDownloadURL) + filename := "tpc.exe.tmp" + err = DownloadFile(*(*asset).BrowserDownloadURL, filename) if err != nil { return false, err } - defer res.Body.Close() log.Info("Applying update...") - err = selfupdate.Apply(res.Body, selfupdate.Options{}) + file, err := os.Open(filename) + if err != nil { + return false, err + } + err = selfupdate.Apply(file, selfupdate.Options{}) + file.Close() + os.Remove(filename) if err != nil { return false, err } @@ -52,7 +65,48 @@ func UpdateSelf() (bool, error) { return true, nil } -func getLatestRelease() (*github.RepositoryRelease, error) { +func DownloadFile(url, filename string) error { + res, err := http.Get(url) + if err != nil { + return err + } + defer res.Body.Close() + + file, err := os.Create(filename) + if err != nil { + fmt.Println("could not create file:", err) + os.Exit(1) + } + defer file.Close() + + pw := &progressWriter{ + total: int(res.ContentLength), + file: file, + reader: res.Body, + onProgress: func(ratio float64) { + p.Send(progressMsg(ratio)) + }, + } + + m := model{ + pw: pw, + progress: progress.New(progress.WithDefaultGradient()), + } + // Start Bubble Tea + p = tea.NewProgram(m) + + // Start the download + go pw.Start() + + if _, err := p.Run(); err != nil { + fmt.Println("error running program:", err) + os.Exit(1) + } + + return nil +} + +func GetLatestRelease() (*github.RepositoryRelease, error) { client := github.NewClient(nil) releases, _, err := client.Repositories.ListReleases(context.Background(), "djohts", "tpc-truckersmp", nil) if err != nil { @@ -65,3 +119,27 @@ func getLatestRelease() (*github.RepositoryRelease, error) { return releases[0], nil } + +type progressWriter struct { + total int + downloaded int + file *os.File + reader io.Reader + onProgress func(float64) +} + +func (pw *progressWriter) Start() { + // TeeReader calls pw.Write() each time a new response is received + _, err := io.Copy(pw.file, io.TeeReader(pw.reader, pw)) + if err != nil { + p.Send(progressErrMsg{err}) + } +} + +func (pw *progressWriter) Write(p []byte) (int, error) { + pw.downloaded += len(p) + if pw.total > 0 && pw.onProgress != nil { + pw.onProgress(float64(pw.downloaded) / float64(pw.total)) + } + return len(p), nil +}