From 02a9f9683b00a92287e751860f897a59b0f5ecca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsman?= Date: Fri, 21 May 2021 11:15:50 +0200 Subject: [PATCH 01/14] Fix bug preventing message from appearing --- internal/pkg/communication/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/communication/logger.go b/internal/pkg/communication/logger.go index fa52893..cd36035 100644 --- a/internal/pkg/communication/logger.go +++ b/internal/pkg/communication/logger.go @@ -207,7 +207,7 @@ func (l *stdoutLogger) LoadingFailure(tunnelID string, err error) { func (l *stdoutLogger) NewVersionAvailable(availableVersion string) { l.messageMutex.Lock() defer l.messageMutex.Unlock() - fmt.Fprint(l.colorableOutput, aurora.Cyan(fmt.Sprintf("There is new version available, to get it please visit %s", + fmt.Fprintln(l.colorableOutput, aurora.Cyan(fmt.Sprintf("There is new version available, to get it please visit %s", fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion)))) } From 5f604ce66f2b662b53912f04a54b0155e7d8345b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20K=C3=B6nigsmann?= Date: Tue, 25 May 2021 13:04:17 +0200 Subject: [PATCH 02/14] Add popup for desktop, move CheckVersion function --- cmd/http.go | 2 +- cmd/path.go | 2 +- cmd/virtual-serve.go | 23 ----------------------- cmd/webdav.go | 2 +- go.mod | 1 + go.sum | 2 ++ internal/app/loophole/loophole.go | 31 +++++++++++++++++++++++++++++++ ui/ui.go | 1 + 8 files changed, 38 insertions(+), 26 deletions(-) diff --git a/cmd/http.go b/cmd/http.go index d613ab5..81439f5 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -29,7 +29,7 @@ To expose port running on some local host e.g. 192.168.1.20 use 'loophole http < idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - checkVersion() + loophole.CheckVersion() localEndpointSpecs.Host = "127.0.0.1" if len(args) > 1 { diff --git a/cmd/path.go b/cmd/path.go index 205d8f5..cac9193 100644 --- a/cmd/path.go +++ b/cmd/path.go @@ -26,7 +26,7 @@ To expose local directory (e.g. /data/my-data) simply use 'loophole path /data/m idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - checkVersion() + loophole.CheckVersion() dirEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/cmd/virtual-serve.go b/cmd/virtual-serve.go index fdb6004..84da4c0 100644 --- a/cmd/virtual-serve.go +++ b/cmd/virtual-serve.go @@ -10,10 +10,8 @@ import ( "strings" "github.com/beevik/guid" - "github.com/blang/semver/v4" "github.com/loophole/cli/config" lm "github.com/loophole/cli/internal/app/loophole/models" - "github.com/loophole/cli/internal/pkg/apiclient" "github.com/loophole/cli/internal/pkg/cache" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/inpututil" @@ -90,24 +88,3 @@ func parseBasicAuthFlags(flagset *pflag.FlagSet) error { return nil } - -func checkVersion() { - availableVersion, err := apiclient.GetLatestAvailableVersion() - if err != nil { - communication.Debug("There was a problem obtaining info response, skipping further checking") - return - } - currentVersionParsed, err := semver.Make(config.Config.Version) - if err != nil { - communication.Debug(fmt.Sprintf("Cannot parse current version '%s' as semver version, skipping further checking", config.Config.Version)) - return - } - availableVersionParsed, err := semver.Make(availableVersion.Version) - if err != nil { - communication.Debug(fmt.Sprintf("Cannot parse available version '%s' as semver version, skipping further checking", availableVersion)) - return - } - if currentVersionParsed.LT(availableVersionParsed) { - communication.NewVersionAvailable(availableVersion.Version) - } -} diff --git a/cmd/webdav.go b/cmd/webdav.go index 75cea17..7fc3e33 100644 --- a/cmd/webdav.go +++ b/cmd/webdav.go @@ -28,7 +28,7 @@ To expose local directory via webdav (e.g. /data/my-data) simply use 'loophole w idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - checkVersion() + loophole.CheckVersion() webdavEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/go.mod b/go.mod index 45c25ca..76dcc63 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/mdp/qrterminal v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/ncruces/zenity v0.5.2 + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rs/zerolog v1.19.0 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 diff --git a/go.sum b/go.sum index faa032b..72f13d4 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/ncruces/zenity v0.5.2/go.mod h1:FPwYbb/qb/eMG2psReJl+L1+0LtXeDkj4R+pi github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/internal/app/loophole/loophole.go b/internal/app/loophole/loophole.go index 6f63d1c..2d691c9 100644 --- a/internal/app/loophole/loophole.go +++ b/internal/app/loophole/loophole.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/blang/semver/v4" "github.com/loophole/cli/config" lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/apiclient" @@ -14,6 +15,8 @@ import ( "github.com/loophole/cli/internal/pkg/httpserver" "github.com/loophole/cli/internal/pkg/keys" "github.com/loophole/cli/internal/pkg/urlmaker" + "github.com/ncruces/zenity" + "github.com/pkg/browser" "golang.org/x/crypto/ssh" ) @@ -35,6 +38,34 @@ var remoteEndpoint = lm.Endpoint{ Port: 80, } +func CheckVersion() { + availableVersion, err := apiclient.GetLatestAvailableVersion() + if err != nil { + communication.Debug("There was a problem obtaining info response, skipping further checking") + return + } + currentVersionParsed, err := semver.Make(config.Config.Version) + if err != nil { + communication.Debug(fmt.Sprintf("Cannot parse current version '%s' as semver version, skipping further checking", config.Config.Version)) + return + } + availableVersionParsed, err := semver.Make(availableVersion.Version) + if err != nil { + communication.Debug(fmt.Sprintf("Cannot parse available version '%s' as semver version, skipping further checking", availableVersion)) + return + } + if currentVersionParsed.LT(availableVersionParsed) { + if config.Config.ClientMode == "cli" { + communication.NewVersionAvailable(availableVersion.Version) + } else { + response, _ := zenity.Question(fmt.Sprintf("A new version is available at https://github.com/loophole/cli/releases/tag/%s \nDo you want to open the link in your browser now?", availableVersion.Version), zenity.NoWrap(), zenity.Title("New version available!")) + if response { + browser.OpenURL(fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion.Version)) + } + } + } +} + func handleClient(tunnelID string, client net.Conn, local net.Conn) { defer client.Close() chDone := make(chan bool) diff --git a/ui/ui.go b/ui/ui.go index 653a479..7bb9006 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -259,6 +259,7 @@ func Display() { } defer ui.Close() + loophole.CheckVersion() <-ui.Done() } From b0dc43dcc9b1a8b9d00f2ad8ebdc285cc3d73ab2 Mon Sep 17 00:00:00 2001 From: Denny Date: Tue, 1 Jun 2021 12:45:25 +0200 Subject: [PATCH 03/14] Limit desktop update reminder to once a day --- go.mod | 5 ++-- go.sum | 10 +------- internal/app/loophole/loophole.go | 38 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 76dcc63..8bba1a1 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,13 @@ require ( github.com/mdp/qrterminal v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/ncruces/zenity v0.5.2 - github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 + github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.19.0 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.4.0 github.com/zserge/lorca v0.1.9 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/net v0.0.0-20200301022130-244492dfa37a diff --git a/go.sum b/go.sum index 72f13d4..07aeb21 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= @@ -17,7 +18,6 @@ github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -38,7 +38,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/here v0.6.2 h1:ZtCqC7F9ou3moLbYfHM1Tj+gwHGgWhjyRjVjsir9BE0= github.com/gobuffalo/here v0.6.2/go.mod h1:D75Sq0p2BVHdgQu3vCRsXbg85rx943V19urJpqAVWjI= @@ -66,7 +65,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -134,7 +132,6 @@ github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -192,15 +189,12 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -222,14 +216,12 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/internal/app/loophole/loophole.go b/internal/app/loophole/loophole.go index 2d691c9..d64872e 100644 --- a/internal/app/loophole/loophole.go +++ b/internal/app/loophole/loophole.go @@ -15,8 +15,10 @@ import ( "github.com/loophole/cli/internal/pkg/httpserver" "github.com/loophole/cli/internal/pkg/keys" "github.com/loophole/cli/internal/pkg/urlmaker" + "github.com/mitchellh/go-homedir" "github.com/ncruces/zenity" "github.com/pkg/browser" + "github.com/spf13/viper" "golang.org/x/crypto/ssh" ) @@ -58,6 +60,10 @@ func CheckVersion() { if config.Config.ClientMode == "cli" { communication.NewVersionAvailable(availableVersion.Version) } else { + remind, _ := remindAgainCheck() + if !remind { + return + } response, _ := zenity.Question(fmt.Sprintf("A new version is available at https://github.com/loophole/cli/releases/tag/%s \nDo you want to open the link in your browser now?", availableVersion.Version), zenity.NoWrap(), zenity.Title("New version available!")) if response { browser.OpenURL(fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion.Version)) @@ -66,6 +72,38 @@ func CheckVersion() { } } +func remindAgainCheck() (bool, error) { //TODO: Error handling, moving this function to a more appropriate file (probably to config pkg) + home, err := homedir.Dir() + if err != nil { + return true, err + } + + layout := "2006-02-01" //golangs arcane time format string + viper.SetDefault("last-reminder", time.Time{}.Format(layout)) //zero value for time + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet + viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + } else { + return true, err + } + } + + t, err := time.Parse(layout, fmt.Sprintf("%v", viper.Get("last-reminder"))) + if err != nil { + return true, err + } + if (t.Year() < time.Now().Year()) || (t.YearDay() < time.Now().YearDay()) { //check if reminder has been done today + viper.Set("last-reminder", time.Now().Format(layout)) + viper.WriteConfigAs("/home/work/.loophole/config.json") + return true, nil + } + + return false, nil +} + func handleClient(tunnelID string, client net.Conn, local net.Conn) { defer client.Close() chDone := make(chan bool) From 7e93ac3bebd569b78038c486a4f2948cb2efce80 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 4 Jun 2021 10:15:49 +0200 Subject: [PATCH 04/14] Replace link to release page with one to specific binary --- internal/app/loophole/loophole.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/app/loophole/loophole.go b/internal/app/loophole/loophole.go index d64872e..d43c0ca 100644 --- a/internal/app/loophole/loophole.go +++ b/internal/app/loophole/loophole.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/http" + "runtime" "time" "github.com/blang/semver/v4" @@ -64,14 +65,36 @@ func CheckVersion() { if !remind { return } - response, _ := zenity.Question(fmt.Sprintf("A new version is available at https://github.com/loophole/cli/releases/tag/%s \nDo you want to open the link in your browser now?", availableVersion.Version), zenity.NoWrap(), zenity.Title("New version available!")) + dlLink := getDownloadLink(availableVersion.Version) + response, _ := zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \nDo you want to open the link in your browser now?", dlLink), zenity.NoWrap(), zenity.Title("New version available!")) if response { - browser.OpenURL(fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion.Version)) + browser.OpenURL(dlLink) } } } } +func getDownloadLink(availableVersion string) string { + archiveExt := ".tar.gz" + arch := runtime.GOARCH + if arch == "windows" { + archiveExt = ".zip" + } else if arch == "darwin" { + arch = "macos" + } + if arch == "amd64" { + arch = "64bit" + } else if arch == "386" { + arch = "32bit" + } else { + communication.Error("There was an error detecting your system architecture.") //if arch is unexpected, only link to the release page + return fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion) + } + res := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, runtime.GOOS, arch, archiveExt) + fmt.Println(res) + return res +} + func remindAgainCheck() (bool, error) { //TODO: Error handling, moving this function to a more appropriate file (probably to config pkg) home, err := homedir.Dir() if err != nil { From e8026c90b6900941a54a164515953f69c878dc6d Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 7 Jun 2021 10:16:13 +0200 Subject: [PATCH 05/14] Move updatecheck to pkg --- cmd/http.go | 3 +- cmd/path.go | 3 +- cmd/webdav.go | 3 +- internal/app/loophole/loophole.go | 92 --------------------- internal/pkg/updatecheck/updatecheck.go | 105 ++++++++++++++++++++++++ ui/ui.go | 3 +- 6 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 internal/pkg/updatecheck/updatecheck.go diff --git a/cmd/http.go b/cmd/http.go index 81439f5..6eddf73 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -11,6 +11,7 @@ import ( lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/token" + "github.com/loophole/cli/internal/pkg/updatecheck" "github.com/spf13/cobra" ) @@ -29,7 +30,7 @@ To expose port running on some local host e.g. 192.168.1.20 use 'loophole http < idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - loophole.CheckVersion() + updatecheck.CheckVersion() localEndpointSpecs.Host = "127.0.0.1" if len(args) > 1 { diff --git a/cmd/path.go b/cmd/path.go index cac9193..bb509c2 100644 --- a/cmd/path.go +++ b/cmd/path.go @@ -9,6 +9,7 @@ import ( lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/token" + "github.com/loophole/cli/internal/pkg/updatecheck" "github.com/spf13/cobra" ) @@ -26,7 +27,7 @@ To expose local directory (e.g. /data/my-data) simply use 'loophole path /data/m idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - loophole.CheckVersion() + updatecheck.CheckVersion() dirEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/cmd/webdav.go b/cmd/webdav.go index 7fc3e33..9d1b019 100644 --- a/cmd/webdav.go +++ b/cmd/webdav.go @@ -9,6 +9,7 @@ import ( lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/token" + "github.com/loophole/cli/internal/pkg/updatecheck" "github.com/spf13/cobra" ) @@ -28,7 +29,7 @@ To expose local directory via webdav (e.g. /data/my-data) simply use 'loophole w idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - loophole.CheckVersion() + updatecheck.CheckVersion() webdavEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/internal/app/loophole/loophole.go b/internal/app/loophole/loophole.go index d43c0ca..6f63d1c 100644 --- a/internal/app/loophole/loophole.go +++ b/internal/app/loophole/loophole.go @@ -5,10 +5,8 @@ import ( "io" "net" "net/http" - "runtime" "time" - "github.com/blang/semver/v4" "github.com/loophole/cli/config" lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/apiclient" @@ -16,10 +14,6 @@ import ( "github.com/loophole/cli/internal/pkg/httpserver" "github.com/loophole/cli/internal/pkg/keys" "github.com/loophole/cli/internal/pkg/urlmaker" - "github.com/mitchellh/go-homedir" - "github.com/ncruces/zenity" - "github.com/pkg/browser" - "github.com/spf13/viper" "golang.org/x/crypto/ssh" ) @@ -41,92 +35,6 @@ var remoteEndpoint = lm.Endpoint{ Port: 80, } -func CheckVersion() { - availableVersion, err := apiclient.GetLatestAvailableVersion() - if err != nil { - communication.Debug("There was a problem obtaining info response, skipping further checking") - return - } - currentVersionParsed, err := semver.Make(config.Config.Version) - if err != nil { - communication.Debug(fmt.Sprintf("Cannot parse current version '%s' as semver version, skipping further checking", config.Config.Version)) - return - } - availableVersionParsed, err := semver.Make(availableVersion.Version) - if err != nil { - communication.Debug(fmt.Sprintf("Cannot parse available version '%s' as semver version, skipping further checking", availableVersion)) - return - } - if currentVersionParsed.LT(availableVersionParsed) { - if config.Config.ClientMode == "cli" { - communication.NewVersionAvailable(availableVersion.Version) - } else { - remind, _ := remindAgainCheck() - if !remind { - return - } - dlLink := getDownloadLink(availableVersion.Version) - response, _ := zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \nDo you want to open the link in your browser now?", dlLink), zenity.NoWrap(), zenity.Title("New version available!")) - if response { - browser.OpenURL(dlLink) - } - } - } -} - -func getDownloadLink(availableVersion string) string { - archiveExt := ".tar.gz" - arch := runtime.GOARCH - if arch == "windows" { - archiveExt = ".zip" - } else if arch == "darwin" { - arch = "macos" - } - if arch == "amd64" { - arch = "64bit" - } else if arch == "386" { - arch = "32bit" - } else { - communication.Error("There was an error detecting your system architecture.") //if arch is unexpected, only link to the release page - return fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion) - } - res := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, runtime.GOOS, arch, archiveExt) - fmt.Println(res) - return res -} - -func remindAgainCheck() (bool, error) { //TODO: Error handling, moving this function to a more appropriate file (probably to config pkg) - home, err := homedir.Dir() - if err != nil { - return true, err - } - - layout := "2006-02-01" //golangs arcane time format string - viper.SetDefault("last-reminder", time.Time{}.Format(layout)) //zero value for time - viper.SetConfigName("config") // name of config file (without extension) - viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name - viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet - viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) - } else { - return true, err - } - } - - t, err := time.Parse(layout, fmt.Sprintf("%v", viper.Get("last-reminder"))) - if err != nil { - return true, err - } - if (t.Year() < time.Now().Year()) || (t.YearDay() < time.Now().YearDay()) { //check if reminder has been done today - viper.Set("last-reminder", time.Now().Format(layout)) - viper.WriteConfigAs("/home/work/.loophole/config.json") - return true, nil - } - - return false, nil -} - func handleClient(tunnelID string, client net.Conn, local net.Conn) { defer client.Close() chDone := make(chan bool) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go new file mode 100644 index 0000000..86e4ef1 --- /dev/null +++ b/internal/pkg/updatecheck/updatecheck.go @@ -0,0 +1,105 @@ +package updatecheck + +import ( + "fmt" + "runtime" + "time" + + "github.com/blang/semver/v4" + "github.com/loophole/cli/config" + "github.com/loophole/cli/internal/pkg/apiclient" + "github.com/loophole/cli/internal/pkg/communication" + "github.com/mitchellh/go-homedir" + "github.com/ncruces/zenity" + "github.com/pkg/browser" + "github.com/spf13/viper" +) + +func CheckVersion() { + availableVersion, err := apiclient.GetLatestAvailableVersion() + if err != nil { + communication.Debug("There was a problem obtaining info response, skipping further checking") + return + } + currentVersionParsed, err := semver.Make(config.Config.Version) + if err != nil { + communication.Debug(fmt.Sprintf("Cannot parse current version '%s' as semver version, skipping further checking", config.Config.Version)) + return + } + availableVersionParsed, err := semver.Make(availableVersion.Version) + if err != nil { + communication.Debug(fmt.Sprintf("Cannot parse available version '%s' as semver version, skipping further checking", availableVersion)) + return + } + if currentVersionParsed.LT(availableVersionParsed) { + if config.Config.ClientMode == "cli" { + communication.NewVersionAvailable(availableVersion.Version) + } else { + remind, err := remindAgainCheck() + if err != nil { + communication.Error(err.Error()) //errors in retrieving a download link should be noted, but not interrupt the program + } + if !remind { + return + } + dlLink := getDownloadLink(availableVersion.Version) + response, _ := zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \nDo you want to open the link in your browser now?", dlLink), zenity.NoWrap(), zenity.Title("New version available!")) + if response { + browser.OpenURL(dlLink) + } + } + } +} + +func getDownloadLink(availableVersion string) string { + archiveExt := ".tar.gz" + arch := runtime.GOARCH + if arch == "windows" { + archiveExt = ".zip" + } else if arch == "darwin" { + arch = "macos" + } + if arch == "amd64" { + arch = "64bit" + } else if arch == "386" { + arch = "32bit" + } else { + communication.Error("There was an error detecting your system architecture.") //if arch is unexpected, only link to the release page + return fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion) + } + res := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, runtime.GOOS, arch, archiveExt) + fmt.Println(res) + return res +} + +func remindAgainCheck() (bool, error) { + home, err := homedir.Dir() + if err != nil { + return true, err + } + + layout := "2006-02-01" //golangs arcane time format string + viper.SetDefault("last-reminder", time.Time{}.Format(layout)) //zero value for time + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet + viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + } else { + return true, err + } + } + + t, err := time.Parse(layout, fmt.Sprintf("%v", viper.Get("last-reminder"))) + if err != nil { + return true, err + } + if (t.Year() < time.Now().Year()) || (t.YearDay() < time.Now().YearDay()) { //check if reminder has been done today + viper.Set("last-reminder", time.Now().Format(layout)) + viper.WriteConfigAs("/home/work/.loophole/config.json") + return true, nil + } + + return false, nil +} diff --git a/ui/ui.go b/ui/ui.go index 7bb9006..78241f0 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -21,6 +21,7 @@ import ( "github.com/loophole/cli/internal/pkg/cache" "github.com/loophole/cli/internal/pkg/communication" "github.com/loophole/cli/internal/pkg/token" + "github.com/loophole/cli/internal/pkg/updatecheck" ) var upgrader = websocket.Upgrader{} // use default options @@ -259,7 +260,7 @@ func Display() { } defer ui.Close() - loophole.CheckVersion() + updatecheck.CheckVersion() <-ui.Done() } From 52fd658b154bfe6f2bcdae174b666cfcacae6c8e Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 7 Jun 2021 13:03:25 +0200 Subject: [PATCH 06/14] Rename variables, replace prompt with notify --- internal/pkg/updatecheck/updatecheck.go | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index 86e4ef1..a6b2bd5 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -11,7 +11,6 @@ import ( "github.com/loophole/cli/internal/pkg/communication" "github.com/mitchellh/go-homedir" "github.com/ncruces/zenity" - "github.com/pkg/browser" "github.com/spf13/viper" ) @@ -42,34 +41,35 @@ func CheckVersion() { if !remind { return } - dlLink := getDownloadLink(availableVersion.Version) - response, _ := zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \nDo you want to open the link in your browser now?", dlLink), zenity.NoWrap(), zenity.Title("New version available!")) - if response { - browser.OpenURL(dlLink) + downloadlink := getDownloadLink(availableVersion.Version) + err = zenity.Notify(fmt.Sprintf("A new version is available for you at \n%s \n", downloadlink), zenity.Title("New version available!")) + if err != nil { + communication.Debug(err.Error()) //errors in showing a download link should be noted, but not interrupt the program } } } } func getDownloadLink(availableVersion string) string { - archiveExt := ".tar.gz" - arch := runtime.GOARCH - if arch == "windows" { - archiveExt = ".zip" - } else if arch == "darwin" { - arch = "macos" + archiveType := ".tar.gz" + operatingSystem := runtime.GOOS + architecture := runtime.GOARCH + if operatingSystem == "windows" { + archiveType = ".zip" + } else if operatingSystem == "darwin" { + operatingSystem = "macos" //rename for use in downloadlink } - if arch == "amd64" { - arch = "64bit" - } else if arch == "386" { - arch = "32bit" + if architecture == "amd64" { + architecture = "64bit" + } else if architecture == "386" { + architecture = "32bit" } else { communication.Error("There was an error detecting your system architecture.") //if arch is unexpected, only link to the release page return fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion) } - res := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, runtime.GOOS, arch, archiveExt) - fmt.Println(res) - return res + link := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, operatingSystem, operatingSystem, archiveType) + fmt.Println(link) + return link } func remindAgainCheck() (bool, error) { @@ -81,7 +81,7 @@ func remindAgainCheck() (bool, error) { layout := "2006-02-01" //golangs arcane time format string viper.SetDefault("last-reminder", time.Time{}.Format(layout)) //zero value for time viper.SetConfigName("config") // name of config file (without extension) - viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name + viper.SetConfigType("json") viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet From b416b0c707ab3e4f5831fd63871fa5ca7f934e36 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 10 Jun 2021 12:23:14 +0200 Subject: [PATCH 07/14] Switch notification behavior, Use direct viper getters, Fix download link --- internal/pkg/updatecheck/updatecheck.go | 46 +++++++++++++++++-------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index a6b2bd5..2d09968 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -11,6 +11,7 @@ import ( "github.com/loophole/cli/internal/pkg/communication" "github.com/mitchellh/go-homedir" "github.com/ncruces/zenity" + "github.com/pkg/browser" "github.com/spf13/viper" ) @@ -34,7 +35,7 @@ func CheckVersion() { if config.Config.ClientMode == "cli" { communication.NewVersionAvailable(availableVersion.Version) } else { - remind, err := remindAgainCheck() + remind, usePrompt, err := remindAgainCheck(availableVersion.Version) if err != nil { communication.Error(err.Error()) //errors in retrieving a download link should be noted, but not interrupt the program } @@ -42,7 +43,15 @@ func CheckVersion() { return } downloadlink := getDownloadLink(availableVersion.Version) - err = zenity.Notify(fmt.Sprintf("A new version is available for you at \n%s \n", downloadlink), zenity.Title("New version available!")) + if usePrompt { + openLink := false + openLink, err = zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \n Do you want to open this link in your browser?", downloadlink), zenity.NoWrap(), zenity.Title("New version available!")) + if openLink { + browser.OpenURL(downloadlink) + } + } else { + err = zenity.Notify(fmt.Sprintf("A new version is available for you at \n%s \n", downloadlink), zenity.Title("New version available!")) + } if err != nil { communication.Debug(err.Error()) //errors in showing a download link should be noted, but not interrupt the program } @@ -67,39 +76,46 @@ func getDownloadLink(availableVersion string) string { communication.Error("There was an error detecting your system architecture.") //if arch is unexpected, only link to the release page return fmt.Sprintf("https://github.com/loophole/cli/releases/tag/%s", availableVersion) } - link := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, operatingSystem, operatingSystem, archiveType) + link := fmt.Sprintf("https://github.com/loophole/cli/releases/download/%s/loophole-desktop_%s_%s_%s%s", availableVersion, availableVersion, operatingSystem, architecture, archiveType) fmt.Println(link) return link } -func remindAgainCheck() (bool, error) { +func remindAgainCheck(availableVersion string) (bool, bool, error) { home, err := homedir.Dir() if err != nil { - return true, err + return true, false, err } - layout := "2006-02-01" //golangs arcane time format string - viper.SetDefault("last-reminder", time.Time{}.Format(layout)) //zero value for time - viper.SetConfigName("config") // name of config file (without extension) + viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default zero value for time + viper.SetDefault("availableversion", "1.0.0-beta.14") //TODO: last seen latest available version + viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder + viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigType("json") viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) } else { - return true, err + return true, false, err } } - t, err := time.Parse(layout, fmt.Sprintf("%v", viper.Get("last-reminder"))) if err != nil { - return true, err + return true, false, err } - if (t.Year() < time.Now().Year()) || (t.YearDay() < time.Now().YearDay()) { //check if reminder has been done today - viper.Set("last-reminder", time.Now().Format(layout)) + lastReminder := viper.GetTime("lastreminder") + if (lastReminder.Year() < time.Now().Year()) || (lastReminder.YearDay() < time.Now().YearDay()) { //check if reminder has been done today + viper.Set("lastreminder", time.Now()) viper.WriteConfigAs("/home/work/.loophole/config.json") - return true, nil + if viper.GetInt("remindercount") < 1 { + return true, false, nil + } else { + viper.Set("remindercount", viper.GetInt("remindercount")-1) + viper.WriteConfigAs("/home/work/.loophole/config.json") + return true, true, nil + } } - return false, nil + return false, false, nil } From 4b09154933b2ab0cf27655e309558c880aed7bdd Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 10 Jun 2021 12:54:39 +0200 Subject: [PATCH 08/14] Reset reminder when newer version is out --- internal/pkg/updatecheck/updatecheck.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index 2d09968..2135010 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -35,7 +35,7 @@ func CheckVersion() { if config.Config.ClientMode == "cli" { communication.NewVersionAvailable(availableVersion.Version) } else { - remind, usePrompt, err := remindAgainCheck(availableVersion.Version) + remind, usePrompt, err := remindAgainCheck(availableVersionParsed) if err != nil { communication.Error(err.Error()) //errors in retrieving a download link should be noted, but not interrupt the program } @@ -81,14 +81,14 @@ func getDownloadLink(availableVersion string) string { return link } -func remindAgainCheck(availableVersion string) (bool, bool, error) { +func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) { home, err := homedir.Dir() if err != nil { return true, false, err } viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default zero value for time - viper.SetDefault("availableversion", "1.0.0-beta.14") //TODO: last seen latest available version + viper.SetDefault("availableversion", "1.0.0-beta.14") //last seen latest version viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder viper.SetConfigName("config") // name of config file (without extension) viper.SetConfigType("json") @@ -101,6 +101,11 @@ func remindAgainCheck(availableVersion string) (bool, bool, error) { } } + lastSeenLatestVersion, err := semver.Make(viper.GetString("availableversion")) + if availableVersionParsed.GT(lastSeenLatestVersion) { //reset reminder count if new version is out + viper.Set("availableversion", availableVersionParsed.String()) + viper.Set("remindercount", 3) + } if err != nil { return true, false, err } From 60435ede7808b7a261e8a9222c893c29e9f3e461 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 11 Jun 2021 12:39:55 +0200 Subject: [PATCH 09/14] Rename CheckVersion to CheckForUpdates --- cmd/http.go | 2 +- cmd/path.go | 2 +- cmd/webdav.go | 2 +- internal/pkg/updatecheck/updatecheck.go | 12 ++++++------ ui/ui.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/http.go b/cmd/http.go index 6eddf73..e9b49fd 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -30,7 +30,7 @@ To expose port running on some local host e.g. 192.168.1.20 use 'loophole http < idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - updatecheck.CheckVersion() + updatecheck.CheckForUpdates() localEndpointSpecs.Host = "127.0.0.1" if len(args) > 1 { diff --git a/cmd/path.go b/cmd/path.go index bb509c2..9106f46 100644 --- a/cmd/path.go +++ b/cmd/path.go @@ -27,7 +27,7 @@ To expose local directory (e.g. /data/my-data) simply use 'loophole path /data/m idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - updatecheck.CheckVersion() + updatecheck.CheckForUpdates() dirEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/cmd/webdav.go b/cmd/webdav.go index 9d1b019..e162531 100644 --- a/cmd/webdav.go +++ b/cmd/webdav.go @@ -29,7 +29,7 @@ To expose local directory via webdav (e.g. /data/my-data) simply use 'loophole w idToken := token.GetIdToken() communication.ApplicationStart(loggedIn, idToken) - updatecheck.CheckVersion() + updatecheck.CheckForUpdates() webdavEndpointSpecs.Path = args[0] quitChannel := make(chan bool) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index 2135010..636f8d1 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -15,7 +15,7 @@ import ( "github.com/spf13/viper" ) -func CheckVersion() { +func CheckForUpdates() { availableVersion, err := apiclient.GetLatestAvailableVersion() if err != nil { communication.Debug("There was a problem obtaining info response, skipping further checking") @@ -37,14 +37,14 @@ func CheckVersion() { } else { remind, usePrompt, err := remindAgainCheck(availableVersionParsed) if err != nil { - communication.Error(err.Error()) //errors in retrieving a download link should be noted, but not interrupt the program + communication.Error(err.Error()) //errors in determining the type reminder should be noted, but not interrupt the program } if !remind { return } downloadlink := getDownloadLink(availableVersion.Version) - if usePrompt { - openLink := false + if usePrompt { //either use a notification that the user needs to click away, or use a notification they can ignore + openLink := false //needs to be declared here instead of below with := so we can still have access to err outside of this scope openLink, err = zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \n Do you want to open this link in your browser?", downloadlink), zenity.NoWrap(), zenity.Title("New version available!")) if openLink { browser.OpenURL(downloadlink) @@ -66,7 +66,7 @@ func getDownloadLink(availableVersion string) string { if operatingSystem == "windows" { archiveType = ".zip" } else if operatingSystem == "darwin" { - operatingSystem = "macos" //rename for use in downloadlink + operatingSystem = "macos" //rename for use in download url } if architecture == "amd64" { architecture = "64bit" @@ -90,7 +90,7 @@ func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default zero value for time viper.SetDefault("availableversion", "1.0.0-beta.14") //last seen latest version viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder - viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigName("config") //name of config file (without extension) viper.SetConfigType("json") viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) if err := viper.ReadInConfig(); err != nil { diff --git a/ui/ui.go b/ui/ui.go index 78241f0..6b147ed 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -260,7 +260,7 @@ func Display() { } defer ui.Close() - updatecheck.CheckVersion() + updatecheck.CheckForUpdates() <-ui.Done() } From fdd4e7d15e9dfa6d2b2b2b17a2dca688c4874583 Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 14 Jun 2021 10:49:04 +0200 Subject: [PATCH 10/14] Fix wrong config path, Adjust download link --- internal/pkg/updatecheck/updatecheck.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index 636f8d1..990a96f 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -42,15 +42,16 @@ func CheckForUpdates() { if !remind { return } - downloadlink := getDownloadLink(availableVersion.Version) if usePrompt { //either use a notification that the user needs to click away, or use a notification they can ignore + downloadlink := getDownloadLink(availableVersion.Version) openLink := false //needs to be declared here instead of below with := so we can still have access to err outside of this scope openLink, err = zenity.Question(fmt.Sprintf("A new version is available for you at \n%s \n Do you want to open this link in your browser?", downloadlink), zenity.NoWrap(), zenity.Title("New version available!")) if openLink { browser.OpenURL(downloadlink) } } else { - err = zenity.Notify(fmt.Sprintf("A new version is available for you at \n%s \n", downloadlink), zenity.Title("New version available!")) + downloadlink := "https://loophole.cloud/download" //this notification isn't clickable, so the link should be something the user can remember + err = zenity.Notify(fmt.Sprintf("A new version is available for you, please visit \n%s \n", downloadlink), zenity.Title("New version available!")) } if err != nil { communication.Debug(err.Error()) //errors in showing a download link should be noted, but not interrupt the program @@ -112,12 +113,18 @@ func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) lastReminder := viper.GetTime("lastreminder") if (lastReminder.Year() < time.Now().Year()) || (lastReminder.YearDay() < time.Now().YearDay()) { //check if reminder has been done today viper.Set("lastreminder", time.Now()) - viper.WriteConfigAs("/home/work/.loophole/config.json") + err = viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + if err != nil { + return true, false, err + } if viper.GetInt("remindercount") < 1 { return true, false, nil } else { viper.Set("remindercount", viper.GetInt("remindercount")-1) - viper.WriteConfigAs("/home/work/.loophole/config.json") + err = viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + if err != nil { + return true, false, err + } return true, true, nil } } From ca949a35a682158c25965d1f261db4ff174b6a90 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 18 Jun 2021 10:48:24 +0200 Subject: [PATCH 11/14] Move viper to config pkg functions --- cmd/root.go | 5 +++ config/config.go | 41 +++++++++++++++++++++++++ internal/pkg/updatecheck/updatecheck.go | 24 ++------------- ui/ui.go | 5 +++ 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a99feff..c8853f8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,6 +10,7 @@ import ( "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/cache" + "github.com/loophole/cli/internal/pkg/communication" "github.com/mattn/go-colorable" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -56,6 +57,10 @@ func initLogger() { // Execute runs command parsing chain func Execute() { rootCmd.Version = fmt.Sprintf("%s (%s)", config.Config.Version, config.Config.CommitHash) + err := config.SetupViperConfig() + if err != nil { + communication.Error(fmt.Sprintf("Error while setting up viper: %s", err.Error())) + } if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/config/config.go b/config/config.go index 122efc8..9179fe8 100644 --- a/config/config.go +++ b/config/config.go @@ -1,7 +1,12 @@ package config import ( + "fmt" + "time" + "github.com/loophole/cli/internal/app/loophole/models" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" ) // OAuthConfig defined OAuth settings shape @@ -33,3 +38,39 @@ type ApplicationConfig struct { APIEndpoint models.Endpoint `json:"apiConfig"` GatewayEndpoint models.Endpoint `json:"gatewayConfig"` } + +func SetupViperConfig() error { + home, err := homedir.Dir() + if err != nil { + return err + } + viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default is zero value for time + viper.SetDefault("availableversion", "1.0.0-beta.14") //last seen latest version + viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder + viper.SetConfigName("config") + viper.SetConfigType("json") + viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet + err = SaveViperConfig() + if err != nil { + return err + } + } else { + return err + } + } + return nil +} + +func SaveViperConfig() error { + home, err := homedir.Dir() + if err != nil { + return err + } + err = viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + if err != nil { + return err + } + return nil +} diff --git a/internal/pkg/updatecheck/updatecheck.go b/internal/pkg/updatecheck/updatecheck.go index 990a96f..33e71fc 100644 --- a/internal/pkg/updatecheck/updatecheck.go +++ b/internal/pkg/updatecheck/updatecheck.go @@ -9,7 +9,6 @@ import ( "github.com/loophole/cli/config" "github.com/loophole/cli/internal/pkg/apiclient" "github.com/loophole/cli/internal/pkg/communication" - "github.com/mitchellh/go-homedir" "github.com/ncruces/zenity" "github.com/pkg/browser" "github.com/spf13/viper" @@ -83,25 +82,6 @@ func getDownloadLink(availableVersion string) string { } func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) { - home, err := homedir.Dir() - if err != nil { - return true, false, err - } - - viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default zero value for time - viper.SetDefault("availableversion", "1.0.0-beta.14") //last seen latest version - viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder - viper.SetConfigName("config") //name of config file (without extension) - viper.SetConfigType("json") - viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { //create a config if none exist yet - viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) - } else { - return true, false, err - } - } - lastSeenLatestVersion, err := semver.Make(viper.GetString("availableversion")) if availableVersionParsed.GT(lastSeenLatestVersion) { //reset reminder count if new version is out viper.Set("availableversion", availableVersionParsed.String()) @@ -113,7 +93,7 @@ func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) lastReminder := viper.GetTime("lastreminder") if (lastReminder.Year() < time.Now().Year()) || (lastReminder.YearDay() < time.Now().YearDay()) { //check if reminder has been done today viper.Set("lastreminder", time.Now()) - err = viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + err = config.SaveViperConfig() if err != nil { return true, false, err } @@ -121,7 +101,7 @@ func remindAgainCheck(availableVersionParsed semver.Version) (bool, bool, error) return true, false, nil } else { viper.Set("remindercount", viper.GetInt("remindercount")-1) - err = viper.WriteConfigAs(fmt.Sprintf("%s/.loophole/config.json", home)) + err = config.SaveViperConfig() if err != nil { return true, false, err } diff --git a/ui/ui.go b/ui/ui.go index 6b147ed..6efb784 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -16,6 +16,7 @@ import ( "github.com/gorilla/websocket" + "github.com/loophole/cli/config" "github.com/loophole/cli/internal/app/loophole" lm "github.com/loophole/cli/internal/app/loophole/models" "github.com/loophole/cli/internal/pkg/cache" @@ -235,6 +236,10 @@ func websocketHandler(w http.ResponseWriter, r *http.Request) { // Display shows the main app window func Display() { + err := config.SetupViperConfig() + if err != nil { + communication.Error(fmt.Sprintf("Error while setting up viper: %s", err.Error())) + } chromeLocation := lorca.LocateChrome() if chromeLocation == "" { message := "Chrome/Chromium >= 70 is required." From afe33c612ef9e7a32a5a7aea3e193d3d3c004367 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 18 Jun 2021 10:17:48 +0200 Subject: [PATCH 12/14] Add basic hostname saving --- config/config.go | 2 ++ internal/app/loophole/loophole.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/config/config.go b/config/config.go index 9179fe8..97a62d3 100644 --- a/config/config.go +++ b/config/config.go @@ -47,6 +47,8 @@ func SetupViperConfig() error { viper.SetDefault("lastreminder", time.Time{}) //date of last reminder, default is zero value for time viper.SetDefault("availableversion", "1.0.0-beta.14") //last seen latest version viper.SetDefault("remindercount", 3) //counts to zero, then switches from prompt to notification reminder + viper.SetDefault("savehostnames", true) + viper.SetDefault("usedhostnames", []string{}) viper.SetConfigName("config") viper.SetConfigType("json") viper.AddConfigPath(fmt.Sprintf("%s/.loophole/", home)) diff --git a/internal/app/loophole/loophole.go b/internal/app/loophole/loophole.go index 6f63d1c..e9bb67b 100644 --- a/internal/app/loophole/loophole.go +++ b/internal/app/loophole/loophole.go @@ -14,6 +14,7 @@ import ( "github.com/loophole/cli/internal/pkg/httpserver" "github.com/loophole/cli/internal/pkg/keys" "github.com/loophole/cli/internal/pkg/urlmaker" + "github.com/spf13/viper" "golang.org/x/crypto/ssh" ) @@ -70,6 +71,15 @@ func handleClient(tunnelID string, client net.Conn, local net.Conn) { <-chDone } +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + func registerDomain(publicKey *ssh.PublicKey, requestedSiteID string, tunnelID string) (*apiclient.RegistrationSuccessResponse, error) { communication.LoadingStart(tunnelID, "Registering your domain...") registrationResult, err := apiclient.RegisterSite(*publicKey, requestedSiteID) @@ -85,6 +95,16 @@ func registerDomain(publicKey *ssh.PublicKey, requestedSiteID string, tunnelID s } return nil, err } + if viper.GetBool("savehostnames") { + hostnames := viper.GetStringSlice("usedhostnames") + if !contains(hostnames, requestedSiteID) && requestedSiteID != "" { + viper.Set("usedhostnames", append(hostnames, requestedSiteID)) + err := viper.WriteConfig() + if err != nil { + communication.Error(fmt.Sprintf("Error occured while saving config: %s\n", err.Error())) + } + } + } communication.LoadingSuccess(tunnelID) return registrationResult, nil } From 4b5b522cd883041957b620e0c2e05381650b53ca Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 21 Jun 2021 12:08:25 +0200 Subject: [PATCH 13/14] Add cli commands to interact with the saved hostnames --- cmd/list.go | 29 +++++++++++++++++++++++++++++ cmd/list_clear.go | 24 ++++++++++++++++++++++++ cmd/list_toggle.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 cmd/list.go create mode 100644 cmd/list_clear.go create mode 100644 cmd/list_toggle.go diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..eac5d81 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,29 @@ +// +build !desktop + +package cmd + +import ( + "fmt" + + "github.com/loophole/cli/internal/pkg/communication" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// listCommand represents the command that lists previously used hostnames +var listCommand = &cobra.Command{ + Use: "list", + Short: "Show used hostnames", + Long: `Show previously used hostnames that were successfully used.`, + Run: func(cmd *cobra.Command, args []string) { + hostnames := viper.GetStringSlice("usedhostnames") + communication.Info(fmt.Sprintf("The following %d hostnames have been used:", len(hostnames))) + for _, hostname := range hostnames { + communication.Info(hostname) + } + }, +} + +func init() { + rootCmd.AddCommand(listCommand) +} diff --git a/cmd/list_clear.go b/cmd/list_clear.go new file mode 100644 index 0000000..680a088 --- /dev/null +++ b/cmd/list_clear.go @@ -0,0 +1,24 @@ +// +build !desktop + +package cmd + +import ( + "github.com/loophole/cli/config" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// listclearCommand represents the command that clears the list of previously used hostnames +var listclearCommand = &cobra.Command{ + Use: "clear", + Short: "Delete the current list of saved hostnames", + Long: `Delete the current list of saved hostnames`, + Run: func(cmd *cobra.Command, args []string) { + viper.Set("usedhostnames", []string{}) + config.SaveViperConfig() + }, +} + +func init() { + listCommand.AddCommand(listclearCommand) +} diff --git a/cmd/list_toggle.go b/cmd/list_toggle.go new file mode 100644 index 0000000..b6c94d1 --- /dev/null +++ b/cmd/list_toggle.go @@ -0,0 +1,34 @@ +// +build !desktop + +package cmd + +import ( + "github.com/loophole/cli/config" + "github.com/loophole/cli/internal/pkg/communication" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// listtoggleCommand represents the command that toggles whether we save previously used hostnames +var listtoggleCommand = &cobra.Command{ + Use: "toggle", + Short: "Stop or start saving used hostnames.", + Long: `Stop or start saving used hostnames. +By default, a list of hostnames you successfully used is saved locally for your convenience. +With this command, you can stop it or resume it if you stopped it before. +This function is not related to the timed reservation of hostnames.`, + Run: func(cmd *cobra.Command, args []string) { + oldState := viper.GetBool("savehostnames") + viper.Set("savehostnames", !oldState) + config.SaveViperConfig() + if oldState { + communication.Info("Hostname saving is now turned off.") + } else { + communication.Info("Hostname saving is now turned on.") + } + }, +} + +func init() { + listCommand.AddCommand(listtoggleCommand) +} From 9660f81b7d8f2ea7967d48d766eb8915189b03ba Mon Sep 17 00:00:00 2001 From: Denny Date: Tue, 29 Jun 2021 11:56:11 +0200 Subject: [PATCH 14/14] Add list locked hostnames functionality --- cmd/list_locked.go | 33 +++++++++++++++++++++++ internal/pkg/apiclient/apiclient.go | 42 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 cmd/list_locked.go diff --git a/cmd/list_locked.go b/cmd/list_locked.go new file mode 100644 index 0000000..c9adb1d --- /dev/null +++ b/cmd/list_locked.go @@ -0,0 +1,33 @@ +// +build !desktop + +package cmd + +import ( + "fmt" + + "github.com/loophole/cli/internal/pkg/apiclient" + "github.com/loophole/cli/internal/pkg/communication" + "github.com/spf13/cobra" +) + +// listlockedCommand represents the command that lists the hostnames that are currently locked for the user +var listlockedCommand = &cobra.Command{ + Use: "locked", + Short: "List the hostnames that are currently locked for you.", + Long: `List the hostnames that are currently locked for you.`, + Run: func(cmd *cobra.Command, args []string) { + hostnames, err := apiclient.GetLockedHostnames() + if err != nil { + communication.Error(fmt.Sprintf("Error while trying to retrieve locked hostnames: %s", err.Error())) + return + } + communication.Info(fmt.Sprintf("The following %d hostnames are currently locked for you:", len(hostnames))) + for _, hostname := range hostnames { + communication.Info(hostname) + } + }, +} + +func init() { + listCommand.AddCommand(listlockedCommand) +} diff --git a/internal/pkg/apiclient/apiclient.go b/internal/pkg/apiclient/apiclient.go index 93f896c..9dfe57b 100644 --- a/internal/pkg/apiclient/apiclient.go +++ b/internal/pkg/apiclient/apiclient.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/ioutil" "net" "net/http" + "regexp" "runtime" "time" @@ -50,6 +52,46 @@ var getAccessToken = token.GetAccessToken var tokenWasRefreshed = false var apiURL = config.Config.APIEndpoint.URI() +// GetLockedHostnames is a function used to obtain the locked hostnames for the user +func GetLockedHostnames() ([]string, error) { + accessToken, err := getAccessToken() + if err != nil { + return nil, err + } + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/site/locked", apiURL), nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", userAgent()) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + var netTransport = &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 10 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + } + var netClient = &http.Client{ + Timeout: time.Second * 30, + Transport: netTransport, + } + + resp, err := netClient.Do(req) + if err != nil { + return nil, err + } + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + respString := string(respBytes) + reg := regexp.MustCompile(`\[*",*"*\]*`) + hostnames := reg.Split(respString, -1) + if len(hostnames) > 2 { + hostnames = hostnames[1 : len(hostnames)-1] + } + return hostnames, nil +} + // RegisterSite is a funtion used to obtain site id and register keys in the gateway func RegisterSite(publicKey ssh.PublicKey, requestedSiteID string) (*RegistrationSuccessResponse, error) { publicKeyString := publicKey.Type() + " " + base64.StdEncoding.EncodeToString(publicKey.Marshal())