diff options
author | lawl <github@dumbinter.net> | 2020-07-16 11:26:19 +0200 |
---|---|---|
committer | lawl <github@dumbinter.net> | 2020-07-16 11:26:19 +0200 |
commit | b11e2e69dc3b724e3264e512a3ec460ea4d364c0 (patch) | |
tree | 38bc1e680cf80e3a47018c1e51ebc674a949551d | |
parent | 9d406a600c483dbf7c6e7043c62f2601c077f256 (diff) | |
download | noisetorch-b11e2e69dc3b724e3264e512a3ec460ea4d364c0.tar.gz noisetorch-b11e2e69dc3b724e3264e512a3ec460ea4d364c0.zip |
Add updater
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | config.go | 3 | ||||
-rw-r--r-- | main.go | 6 | ||||
-rw-r--r-- | ui.go | 28 | ||||
-rw-r--r-- | untar.go | 170 | ||||
-rw-r--r-- | update.go | 96 |
7 files changed, 309 insertions, 7 deletions
@@ -20,6 +20,8 @@ release: rnnoise cd tmp/; \ tar cvzf ../bin/NoiseTorch_x64.tgz . rm -rf tmp/ + go run scripts/signer.go -s + git describe --tags > bin/version.txt rnnoise: cd librnnoise_ladspa/; \ cmake . -DBUILD_VST_PLUGIN=OFF -DBUILD_LV2_PLUGIN=OFF -DBUILD_LADSPA_PLUGIN=ON; \ @@ -15,7 +15,7 @@ Don't forget to ~~like, comment and subscribe~~ leave a star ⭐ if this sounds * Two click setup of your virtual denoising microphone * A single, small, statically linked, self-contained binary -## Download +## Download & Install [Download the latest release from GitHub](https://github.com/lawl/NoiseTorch/releases). @@ -32,6 +32,12 @@ With gnome this can be done with: You now have a `noisetorch` binary and desktop entry on your system. +#### Uninstall + + rm .local/bin/noisetorch + rm .local/share/applications/noisetorch.desktop + rm .local/share/icons/hicolor/256x256/apps/noisetorch.png + ## Usage Select the microphone you want to denoise, and click "Load NoiseTorch", NoiseTorch will create a virtual microphone called "NoiseTorch Microphone" that you can select in any application. @@ -66,6 +72,9 @@ Install the Go compiler from [golang.org](https://golang.org/). And make sure yo make # build it ``` +If you build from source, it's recommended that you disable automatic update checks. +In `~/.config/noisetorch/config.toml` set `EnableUpdates = false`. + ## Special thanks to * [xiph.org](https://xiph.org)/[Mozilla's](https://mozilla.org) excellent [RNNoise](https://jmvalin.ca/demo/rnnoise/). @@ -13,6 +13,7 @@ import ( type config struct { Threshold int DisplayMonitorSources bool + EnableUpdates bool } const configDir = ".config/noisetorch/" @@ -20,7 +21,7 @@ const configFile = "config.toml" func initializeConfigIfNot() { log.Println("Checking if config needs to be initialized") - conf := config{Threshold: 95, DisplayMonitorSources: false} + conf := config{Threshold: 95, DisplayMonitorSources: false, EnableUpdates: true} configdir := filepath.Join(os.Getenv("HOME"), configDir) ok, err := exists(configdir) if err != nil { @@ -28,7 +28,7 @@ type input struct { func main() { - f, err := os.OpenFile("/tmp/noisetorch.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + f, err := os.OpenFile("/tmp/noisetorch.log", os.O_RDWR|os.O_CREATE, 0644) if err != nil { log.Fatalf("error opening file: %v\n", err) } @@ -46,6 +46,10 @@ func main() { ui.config = readConfig() ui.librnnoise = rnnoisefile + if ui.config.EnableUpdates { + go updateCheck(&ui) + } + paClient, err := pulseaudio.NewClient() defer paClient.Close() @@ -25,8 +25,13 @@ type uistate struct { versionScreen bool licenseTextArea nucular.TextEditor masterWindow *nucular.MasterWindow + update updateui } +var green = color.RGBA{34, 187, 69, 255} +var red = color.RGBA{255, 70, 70, 255} +var orange = color.RGBA{255, 140, 0, 255} + func updatefn(w *nucular.Window, ui *uistate) { if ui.loadingScreen { @@ -46,7 +51,7 @@ func updatefn(w *nucular.Window, ui *uistate) { w.MenubarBegin() - w.Row(10).Dynamic(1) + w.Row(10).Dynamic(2) if w := w.Menu(label.TA("About", "LC"), 120, nil); w != nil { w.Row(10).Dynamic(1) if w.MenuItem(label.T("Licenses")) { @@ -65,11 +70,26 @@ func updatefn(w *nucular.Window, ui *uistate) { w.Row(15).Dynamic(1) if ui.noiseSupressorState == loaded { - w.LabelColored("NoiseTorch active", "RC", color.RGBA{34, 187, 69, 255} /*green*/) + w.LabelColored("NoiseTorch active", "RC", green) } else if ui.noiseSupressorState == unloaded { - w.LabelColored("NoiseTorch inactive", "RC", color.RGBA{255, 70, 70, 255} /*red*/) + w.LabelColored("NoiseTorch inactive", "RC", red) } else if ui.noiseSupressorState == inconsistent { - w.LabelColored("Inconsistent state, please unload first.", "RC", color.RGBA{255, 140, 0, 255} /*orange*/) + w.LabelColored("Inconsistent state, please unload first.", "RC", orange) + } + + if ui.update.available && !ui.update.triggered { + w.Row(20).Ratio(0.9, 0.1) + w.LabelColored("Update available! Click to install version: "+ui.update.serverVersion, "LC", green) + if w.ButtonText("Update") { + ui.update.triggered = true + go update(ui) + (*ui.masterWindow).Changed() + } + } + + if ui.update.triggered { + w.Row(20).Dynamic(1) + w.Label(ui.update.updatingText, "CC") } if w.TreePush(nucular.TreeTab, "Settings", true) { diff --git a/untar.go b/untar.go new file mode 100644 index 0000000..80f3395 --- /dev/null +++ b/untar.go @@ -0,0 +1,170 @@ +/* +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// This is yoinked from https://github.com/golang/build/blob/master/internal/untar/untar.go +// which is unfortunately an internal package we can't import, but it's exactly what we need. +// So just copy paste it. + +package main + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "log" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +// TODO(bradfitz): this was copied from x/build/cmd/buildlet/buildlet.go +// but there were some buildlet-specific bits in there, so the code is +// forked for now. Unfork and add some opts arguments here, so the +// buildlet can use this code somehow. + +// Untar reads the gzip-compressed tar file from r and writes it into dir. +func Untar(r io.Reader, dir string) error { + return untar(r, dir) +} + +func untar(r io.Reader, dir string) (err error) { + t0 := time.Now() + nFiles := 0 + madeDir := map[string]bool{} + defer func() { + td := time.Since(t0) + if err == nil { + log.Printf("extracted tarball into %s: %d files, %d dirs (%v)", dir, nFiles, len(madeDir), td) + } else { + log.Printf("error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err) + } + }() + zr, err := gzip.NewReader(r) + if err != nil { + return fmt.Errorf("requires gzip-compressed body: %v", err) + } + tr := tar.NewReader(zr) + loggedChtimesError := false + for { + f, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + log.Printf("tar reading error: %v", err) + return fmt.Errorf("tar error: %v", err) + } + if !validRelPath(f.Name) { + return fmt.Errorf("tar contained invalid name error %q", f.Name) + } + rel := filepath.FromSlash(f.Name) + abs := filepath.Join(dir, rel) + + fi := f.FileInfo() + mode := fi.Mode() + switch { + case mode.IsRegular(): + // Make the directory. This is redundant because it should + // already be made by a directory entry in the tar + // beforehand. Thus, don't check for errors; the next + // write will fail with the same error. + dir := filepath.Dir(abs) + if !madeDir[dir] { + if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil { + return err + } + madeDir[dir] = true + } + wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) + if err != nil { + return err + } + n, err := io.Copy(wf, tr) + if closeErr := wf.Close(); closeErr != nil && err == nil { + err = closeErr + } + if err != nil { + return fmt.Errorf("error writing to %s: %v", abs, err) + } + if n != f.Size { + return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) + } + modTime := f.ModTime + if modTime.After(t0) { + // Clamp modtimes at system time. See + // golang.org/issue/19062 when clock on + // buildlet was behind the gitmirror server + // doing the git-archive. + modTime = t0 + } + if !modTime.IsZero() { + if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError { + // benign error. Gerrit doesn't even set the + // modtime in these, and we don't end up relying + // on it anywhere (the gomote push command relies + // on digests only), so this is a little pointless + // for now. + log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err) + loggedChtimesError = true // once is enough + } + } + nFiles++ + case mode.IsDir(): + if err := os.MkdirAll(abs, 0755); err != nil { + return err + } + madeDir[abs] = true + default: + return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode) + } + } + return nil +} + +func validRelativeDir(dir string) bool { + if strings.Contains(dir, `\`) || path.IsAbs(dir) { + return false + } + dir = path.Clean(dir) + if strings.HasPrefix(dir, "../") || strings.HasSuffix(dir, "/..") || dir == ".." { + return false + } + return true +} + +func validRelPath(p string) bool { + if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") { + return false + } + return true +} diff --git a/update.go b/update.go new file mode 100644 index 0000000..ede7c39 --- /dev/null +++ b/update.go @@ -0,0 +1,96 @@ +package main + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" +) + +var updateURL = "https://noisetorch.epicgamer.org" +var publicKeyString = "3mL+rBi4yBZ1wGimQ/oSQCjxELzgTh+673H4JdzQBOk=" + +type updateui struct { + serverVersion string + available bool + triggered bool + updatingText string +} + +func updateCheck(ui *uistate) { + log.Println("Checking for updates") + bodybuf, err := fetchFile("version.txt") + if err != nil { + log.Println("Couldn't fetch version", err) + return + } + body := strings.TrimSpace(string(bodybuf)) + + ui.update.serverVersion = body + ui.update.available = true + +} + +func update(ui *uistate) { + sig, err := fetchFile("NoiseTorch_x64.tgz.sig") + if err != nil { + log.Println("Couldn't fetch signature", err) + ui.update.updatingText = "Update failed!" + (*ui.masterWindow).Changed() + return + } + + tgz, err := fetchFile("NoiseTorch_x64.tgz") + if err != nil { + log.Println("Couldn't fetch tgz", err) + ui.update.updatingText = "Update failed!" + (*ui.masterWindow).Changed() + return + } + + verified := ed25519.Verify(publickey(), tgz, sig) + + log.Printf("VERIFIED UPDATE: %t\n", verified) + + if !verified { + log.Printf("SIGNATURE VERIFICATION FAILED, ABORTING UPDATE!\n") + ui.update.updatingText = "Update failed!" + (*ui.masterWindow).Changed() + return + } + + untar(bytes.NewReader(tgz), os.Getenv("HOME")) + + log.Printf("Update installed!\n") + ui.update.updatingText = "Update installed!" + (*ui.masterWindow).Changed() +} + +func fetchFile(file string) ([]byte, error) { + resp, err := http.Get(updateURL + "/" + file) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received on 200 status code when fetching %s. Status: %s", file, resp.Status) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil + +} + +func publickey() []byte { + pub, err := base64.StdEncoding.DecodeString(publicKeyString) + if err != nil { + panic(err) // it's hardcoded, we should never hit this, panic if we do + } + return pub +} |