From c17b5f6f7b6b28c890764688ff5e966ecebece63 Mon Sep 17 00:00:00 2001
From: "kali kaneko (leap communications)" <kali@leap.se>
Date: Tue, 16 Jun 2020 21:28:48 +0200
Subject: [PATCH] [feat] re-implement donation reminders

first pass on giving functionality to the donation reminder
---
 gui/backend.go           |  11 +++--
 gui/handlers.cpp         |  16 +++++-
 gui/handlers.h           |   4 +-
 gui/qml/DonateDialog.qml |   5 +-
 gui/qml/main.qml         |   7 +--
 pkg/backend/api.go       |  29 +++++++----
 pkg/backend/bitmask.go   |  19 ++++++--
 pkg/backend/donate.go    |  35 +++++++++++++
 pkg/backend/status.go    | 103 +++++++++++++++++++--------------------
 pkg/config/gui.go        |  14 +++---
 10 files changed, 158 insertions(+), 85 deletions(-)
 create mode 100644 pkg/backend/donate.go

diff --git a/gui/backend.go b/gui/backend.go
index 9c65025a..ab96edbe 100644
--- a/gui/backend.go
+++ b/gui/backend.go
@@ -31,9 +31,14 @@ func Quit() {
 
 }
 
-//export ToggleDonate
-func ToggleDonate() {
-	backend.ToggleDonate()
+//export DonateAccepted
+func DonateAccepted() {
+	backend.DonateAccepted()
+}
+
+//export DonateRejected
+func DonateRejected() {
+	backend.DonateRejected()
 }
 
 //export SubscribeToEvent
diff --git a/gui/handlers.cpp b/gui/handlers.cpp
index e37de2d1..39a57469 100644
--- a/gui/handlers.cpp
+++ b/gui/handlers.cpp
@@ -1,5 +1,7 @@
 #include <QTimer>
 #include <QDebug>
+#include <QDesktopServices>
+#include <QUrl>
 
 #include "handlers.h"
 #include "lib/libgoshim.h"
@@ -23,9 +25,19 @@ void Backend::unblock()
     Unblock();
 }
 
-void Backend::toggleDonate()
+void Backend::donateAccepted()
 {
-    ToggleDonate();
+    DonateAccepted();
+}
+
+void Backend::donateRejected()
+{
+    DonateRejected();
+}
+
+void Backend::openURL(QString link)
+{
+    QDesktopServices::openUrl(QUrl(link));
 }
 
 void Backend::quit()
diff --git a/gui/handlers.h b/gui/handlers.h
index 3fe9eed5..e65fd5eb 100644
--- a/gui/handlers.h
+++ b/gui/handlers.h
@@ -33,7 +33,9 @@ public slots:
     void switchOn();
     void switchOff();
     void unblock();
-    void toggleDonate();
+    void donateAccepted();
+    void donateRejected();
+    void openURL(QString link);
     void quit();
 };
 
diff --git a/gui/qml/DonateDialog.qml b/gui/qml/DonateDialog.qml
index b7431abe..eb761a4a 100644
--- a/gui/qml/DonateDialog.qml
+++ b/gui/qml/DonateDialog.qml
@@ -16,12 +16,13 @@ MessageDialog {
 
     onAccepted: {
         if (backend) {
-            backend.donateAccepted(true)
+            backend.openURL(ctx.donateURL)
+            backend.donateAccepted()
         }
     }
     onRejected: {
         if (backend) {
-            backend.donateAccepted(false)
+            backend.donateRejected()
         }
     }
 }
diff --git a/gui/qml/main.qml b/gui/qml/main.qml
index 98eac800..65c09cb9 100644
--- a/gui/qml/main.qml
+++ b/gui/qml/main.qml
@@ -16,10 +16,9 @@ ApplicationWindow {
         target: jsonModel
         onDataChanged: {
             ctx = JSON.parse(jsonModel.getJson());
-            if (ctx.donate == 'true') {
+            if (ctx.donateDialog == 'true') {
                 console.debug(jsonModel.getJson())
                 donate.visible = true
-                backend.toggleDonate()
             }
         }
     }
@@ -171,7 +170,9 @@ ApplicationWindow {
             MenuItem {
                 text: qsTr("Donate...")
                 visible: true
-                //onTriggered: donate.open()
+                onTriggered: {
+                    donate.visible = true
+                }
             }
 
             MenuItem {
diff --git a/pkg/backend/api.go b/pkg/backend/api.go
index f924cbda..5cb0304d 100644
--- a/pkg/backend/api.go
+++ b/pkg/backend/api.go
@@ -6,6 +6,7 @@ import (
 	"C"
 	"fmt"
 	"log"
+	"time"
 	"unsafe"
 
 	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
@@ -23,18 +24,24 @@ func SwitchOff() {
 }
 
 func Unblock() {
+	//TODO
 	fmt.Println("unblock... [not implemented]")
 }
 
 func Quit() {
 	if ctx.Status != off {
 		go setStatus(stopping)
+		ctx.cfg.SetUserStoppedVPN(true)
 		stopVPN()
 	}
 }
 
-func ToggleDonate() {
-	toggleDonate()
+func DonateAccepted() {
+	donateAccepted()
+}
+
+func DonateRejected() {
+	donateRejected()
 }
 
 func SubscribeToEvent(event string, f unsafe.Pointer) {
@@ -42,21 +49,23 @@ func SubscribeToEvent(event string, f unsafe.Pointer) {
 }
 
 func InitializeBitmaskContext() {
-	pi := bitmask.GetConfiguredProvider()
+	p := bitmask.GetConfiguredProvider()
 
 	initOnce.Do(func() {
-		initializeContext(pi.Provider, pi.AppName)
+		initializeContext(
+			p.Provider, p.AppName)
 	})
 	go ctx.updateStatus()
 
-	/* DEBUG
-	timer := time.NewTimer(time.Second * 3)
 	go func() {
-		<-timer.C
-		fmt.Println("donate timer fired")
-		toggleDonate()
+		if needsDonationReminder() {
+			// wait a bit before launching reminder
+			timer := time.NewTimer(time.Minute * 5)
+			<-timer.C
+			showDonate()
+		}
+
 	}()
-	*/
 }
 
 func RefreshContext() *C.char {
diff --git a/pkg/backend/bitmask.go b/pkg/backend/bitmask.go
index 07d27ea6..8fd2367a 100644
--- a/pkg/backend/bitmask.go
+++ b/pkg/backend/bitmask.go
@@ -5,6 +5,7 @@ import (
 	"os"
 
 	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+	"0xacab.org/leap/bitmask-vpn/pkg/config"
 )
 
 func initializeBitmask() {
@@ -19,6 +20,7 @@ func initializeBitmask() {
 		log.Println("error: cannot initialize bitmask")
 	}
 	ctx.bm = b
+	ctx.cfg = config.ParseConfig()
 }
 
 func startVPN() {
@@ -36,16 +38,25 @@ func stopVPN() {
 	}
 }
 
+func wantDonations() bool {
+	if config.AskForDonations == "true" {
+		return true
+	}
+	return false
+}
+
 // initializeContext initializes an empty connStatus and assigns it to the
 // global ctx holder. This is expected to be called only once, so the public
 // api uses the sync.Once primitive to call this.
 func initializeContext(provider, appName string) {
 	var st status = off
 	ctx = &connectionCtx{
-		AppName:  appName,
-		Provider: provider,
-		Donate:   false,
-		Status:   st,
+		AppName:         appName,
+		Provider:        provider,
+		DonateURL:       config.DonateURL,
+		AskForDonations: wantDonations(),
+		DonateDialog:    false,
+		Status:          st,
 	}
 	go trigger(OnStatusChanged)
 	initializeBitmask()
diff --git a/pkg/backend/donate.go b/pkg/backend/donate.go
new file mode 100644
index 00000000..d2166876
--- /dev/null
+++ b/pkg/backend/donate.go
@@ -0,0 +1,35 @@
+package backend
+
+import (
+	"log"
+	"time"
+)
+
+func needsDonationReminder() bool {
+	return ctx.cfg.NeedsDonationReminder()
+}
+
+func donateAccepted() {
+	stmut.Lock()
+	defer stmut.Unlock()
+	ctx.DonateDialog = false
+	log.Println("marking as donated")
+	ctx.cfg.SetDonated()
+	go trigger(OnStatusChanged)
+}
+
+func donateRejected() {
+	timer := time.NewTimer(time.Hour)
+	go func() {
+		<-timer.C
+		showDonate()
+	}()
+}
+
+func showDonate() {
+	stmut.Lock()
+	defer stmut.Unlock()
+	ctx.DonateDialog = true
+	ctx.cfg.SetLastReminded()
+	go trigger(OnStatusChanged)
+}
diff --git a/pkg/backend/status.go b/pkg/backend/status.go
index e2d31dbe..7e9f211f 100644
--- a/pkg/backend/status.go
+++ b/pkg/backend/status.go
@@ -6,6 +6,7 @@ import (
 	"log"
 
 	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+	"0xacab.org/leap/bitmask-vpn/pkg/config"
 )
 
 const (
@@ -20,60 +21,20 @@ const (
 // if we ever switch again to a provider-agnostic app, we should keep a map here.
 var ctx *connectionCtx
 
-// the status type reflects the current VPN status. Go code is responsible for updating
-// it; the C gui just watches its changes and pulls its updates via the serialized
-// context object.
-
-type status int
-
-const (
-	off status = iota
-	starting
-	on
-	stopping
-	failed
-	unknown
-)
-
-func (s status) String() string {
-	return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
-}
-
-func (s status) MarshalJSON() ([]byte, error) {
-	b := bytes.NewBufferString(`"`)
-	b.WriteString(s.String())
-	b.WriteString(`"`)
-	return b.Bytes(), nil
-}
-
-func (s status) fromString(st string) status {
-	switch st {
-	case offStr:
-		return off
-	case startingStr:
-		return starting
-	case onStr:
-		return on
-	case stoppingStr:
-		return stopping
-	case failedStr:
-		return failed
-	default:
-		return unknown
-	}
-}
-
 // The connectionCtx keeps the global state that is passed around to C-land. It
 // also serves as the primary way of passing requests from the frontend to the
 // Go-core, by letting the UI write some of these variables and processing
 // them.
 
 type connectionCtx struct {
-	AppName  string `json:"appName"`
-	Provider string `json:"provider"`
-	Donate   bool   `json:"donate"`
-	Status   status `json:"status"`
-	bm       bitmask.Bitmask
+	AppName         string `json:"appName"`
+	Provider        string `json:"provider"`
+	AskForDonations bool   `json:"askForDonations"`
+	DonateDialog    bool   `json:"donateDialog"`
+	DonateURL       string `json:"donateURL"`
+	Status          status `json:"status"`
+	bm              bitmask.Bitmask
+	cfg             *config.Config
 }
 
 func (c connectionCtx) toJson() ([]byte, error) {
@@ -110,11 +71,47 @@ func setStatus(st status) {
 	go trigger(OnStatusChanged)
 }
 
-func toggleDonate() {
-	stmut.Lock()
-	defer stmut.Unlock()
-	ctx.Donate = !ctx.Donate
-	go trigger(OnStatusChanged)
+// the status type reflects the current VPN status. Go code is responsible for updating
+// it; the C gui just watches its changes and pulls its updates via the serialized
+// context object.
+
+type status int
+
+const (
+	off status = iota
+	starting
+	on
+	stopping
+	failed
+	unknown
+)
+
+func (s status) String() string {
+	return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
+}
+
+func (s status) MarshalJSON() ([]byte, error) {
+	b := bytes.NewBufferString(`"`)
+	b.WriteString(s.String())
+	b.WriteString(`"`)
+	return b.Bytes(), nil
+}
+
+func (s status) fromString(st string) status {
+	switch st {
+	case offStr:
+		return off
+	case startingStr:
+		return starting
+	case onStr:
+		return on
+	case stoppingStr:
+		return stopping
+	case failedStr:
+		return failed
+	default:
+		return unknown
+	}
 }
 
 func setStatusFromStr(stStr string) {
diff --git a/pkg/config/gui.go b/pkg/config/gui.go
index ce3f14d6..5fa4886f 100644
--- a/pkg/config/gui.go
+++ b/pkg/config/gui.go
@@ -37,7 +37,7 @@ var (
 // Config holds the configuration of the systray
 type Config struct {
 	file struct {
-		LastNotification  time.Time
+		LastReminded      time.Time
 		Donated           time.Time
 		SelectGateway     bool
 		Obfs4             bool
@@ -77,16 +77,16 @@ func (c *Config) SetUserStoppedVPN(vpnStopped bool) error {
 	return c.save()
 }
 
-func (c *Config) HasDonated() bool {
-	return c.file.Donated.Add(oneMonth).After(time.Now())
+func (c *Config) NeedsDonationReminder() bool {
+	return !c.hasDonated() && c.file.LastReminded.Add(oneDay).Before(time.Now())
 }
 
-func (c *Config) NeedsNotification() bool {
-	return !c.HasDonated() && c.file.LastNotification.Add(oneDay).Before(time.Now())
+func (c *Config) hasDonated() bool {
+	return c.file.Donated.Add(oneMonth).After(time.Now())
 }
 
-func (c *Config) SetNotification() error {
-	c.file.LastNotification = time.Now()
+func (c *Config) SetLastReminded() error {
+	c.file.LastReminded = time.Now()
 	return c.save()
 }
 
-- 
GitLab