aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlawl <github@dumbinter.net>2020-07-16 11:26:19 +0200
committerlawl <github@dumbinter.net>2020-07-16 11:26:19 +0200
commitb11e2e69dc3b724e3264e512a3ec460ea4d364c0 (patch)
tree38bc1e680cf80e3a47018c1e51ebc674a949551d
parent9d406a600c483dbf7c6e7043c62f2601c077f256 (diff)
downloadnoisetorch-b11e2e69dc3b724e3264e512a3ec460ea4d364c0.tar.gz
noisetorch-b11e2e69dc3b724e3264e512a3ec460ea4d364c0.zip
Add updater
-rw-r--r--Makefile2
-rw-r--r--README.md11
-rw-r--r--config.go3
-rw-r--r--main.go6
-rw-r--r--ui.go28
-rw-r--r--untar.go170
-rw-r--r--update.go96
7 files changed, 309 insertions, 7 deletions
diff --git a/Makefile b/Makefile
index 2725cf7..75f6ced 100644
--- a/Makefile
+++ b/Makefile
@@ -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; \
diff --git a/README.md b/README.md
index dcbe0a7..982cbfe 100644
--- a/README.md
+++ b/README.md
@@ -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/).
diff --git a/config.go b/config.go
index 7b098ef..6482b04 100644
--- a/config.go
+++ b/config.go
@@ -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 {
diff --git a/main.go b/main.go
index 2d920e7..9b5934a 100644
--- a/main.go
+++ b/main.go
@@ -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()
diff --git a/ui.go b/ui.go
index 5b9aa37..3e363fb 100644
--- a/ui.go
+++ b/ui.go
@@ -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
+}