-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* enable data cap on desktop * Add datacap package * Fix TestServeCap * remove test code * make ffiFunction optional * clean-ups
- Loading branch information
Showing
8 changed files
with
292 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package datacap | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/getlantern/flashlight/v7/bandwidth" | ||
"github.com/getlantern/flashlight/v7/common" | ||
"github.com/getlantern/golog" | ||
"github.com/getlantern/i18n" | ||
notify "github.com/getlantern/notifier" | ||
|
||
"github.com/getlantern/lantern-client/desktop/notifier" | ||
"github.com/getlantern/lantern-client/desktop/ws" | ||
) | ||
|
||
var ( | ||
// These just make sure we only sent a single notification at each percentage | ||
// level. | ||
oneFifty = &sync.Once{} | ||
oneEighty = &sync.Once{} | ||
oneFull = &sync.Once{} | ||
|
||
dataCapListeners = make([]func(hitDataCap bool), 0) | ||
dataCapListenersMx sync.RWMutex | ||
log = golog.LoggerFor("lantern-desktop.datacap") | ||
|
||
translationAppName = strings.ToUpper(common.DefaultAppName) | ||
|
||
uncapped = &bandwidth.Quota{ | ||
MiBAllowed: 0, | ||
MiBUsed: 0, | ||
AsOf: time.Now(), | ||
TTLSeconds: 0, | ||
} | ||
) | ||
|
||
type dataCap struct { | ||
iconURL func() string | ||
clickURL func() string | ||
isPro func() (bool, bool) | ||
} | ||
|
||
// ServeDataCap starts serving data cap data to the frontend. | ||
func ServeDataCap(channel ws.UIChannel, iconURL func() string, clickURL func() string, isPro func() (bool, bool)) error { | ||
helloFn := func(write func(interface{})) { | ||
q, _ := bandwidth.GetQuota() | ||
if q == nil { | ||
// On client first connecting, if we don't have a datacap, assume we're uncapped | ||
q = uncapped | ||
} | ||
log.Debugf("Sending current bandwidth quota to new client: %v", q) | ||
write(q) | ||
} | ||
bservice, err := channel.Register("bandwidth", helloFn) | ||
if err != nil { | ||
log.Errorf("Error registering with UI? %v", err) | ||
return err | ||
} | ||
dc := &dataCap{iconURL: iconURL, clickURL: clickURL, isPro: isPro} | ||
go func() { | ||
for quota := range bandwidth.Updates { | ||
dc.processQuota(bservice.Out, quota) | ||
} | ||
}() | ||
return nil | ||
} | ||
|
||
func (dc *dataCap) processQuota(out chan<- interface{}, quota *bandwidth.Quota) { | ||
log.Debugf("Sending update...%+v", quota) | ||
out <- quota | ||
isFull := dc.isFull(quota) | ||
dataCapListenersMx.RLock() | ||
listeners := dataCapListeners | ||
dataCapListenersMx.RUnlock() | ||
for _, l := range listeners { | ||
l(isFull) | ||
} | ||
if isFull { | ||
oneFull.Do(func() { | ||
go dc.notifyCapHit() | ||
}) | ||
} else if dc.isEightyOrMore(quota) { | ||
oneEighty.Do(func() { | ||
go dc.notifyEighty() | ||
}) | ||
} else if dc.isFiftyOrMore(quota) { | ||
oneFifty.Do(func() { | ||
go dc.notifyFifty() | ||
}) | ||
} | ||
} | ||
|
||
// AddDataCapListener adds a listener for any updates to the data cap. | ||
func AddDataCapListener(l func(hitDataCap bool)) { | ||
dataCapListenersMx.Lock() | ||
dataCapListeners = append(dataCapListeners, l) | ||
dataCapListenersMx.Unlock() | ||
} | ||
|
||
func (dc *dataCap) isEightyOrMore(quota *bandwidth.Quota) bool { | ||
return dc.checkPercent(quota, 0.8) | ||
} | ||
|
||
func (dc *dataCap) isFiftyOrMore(quota *bandwidth.Quota) bool { | ||
return dc.checkPercent(quota, 0.5) | ||
} | ||
|
||
func (dc *dataCap) isFull(quota *bandwidth.Quota) bool { | ||
return (quota.MiBUsed > 0 && quota.MiBAllowed <= quota.MiBUsed) | ||
} | ||
|
||
func (dc *dataCap) checkPercent(quota *bandwidth.Quota, percent float64) bool { | ||
return (float64(quota.MiBUsed) / float64(quota.MiBAllowed)) > percent | ||
} | ||
|
||
func (dc *dataCap) notifyEighty() { | ||
dc.notifyPercent(80) | ||
} | ||
|
||
func (dc *dataCap) notifyFifty() { | ||
dc.notifyPercent(50) | ||
} | ||
|
||
func (dc *dataCap) percentFormatted(percent int) string { | ||
return strconv.Itoa(percent) + "%" | ||
} | ||
|
||
func (dc *dataCap) notifyPercent(percent int) { | ||
title := i18n.T("BACKEND_DATA_PERCENT_TITLE", dc.percentFormatted(percent), i18n.T(translationAppName)) | ||
msg := i18n.T("BACKEND_DATA_PERCENT_MESSAGE", dc.percentFormatted(percent), i18n.T(translationAppName)) | ||
|
||
dc.notifyFreeUser(title, msg, "data-cap-"+strconv.Itoa(percent)) | ||
} | ||
|
||
func (dc *dataCap) notifyCapHit() { | ||
title := i18n.T("BACKEND_DATA_TITLE", i18n.T(translationAppName)) | ||
msg := i18n.T("BACKEND_DATA_MESSAGE", i18n.T(translationAppName)) | ||
|
||
dc.notifyFreeUser(title, msg, "data-cap-100") | ||
} | ||
|
||
func (dc *dataCap) notifyFreeUser(title, msg, campaign string) { | ||
if isPro, ok := dc.isPro(); !ok { | ||
log.Debug("user status is unknown, skip showing notification") | ||
return | ||
} else if isPro { | ||
log.Debug("Not showing desktop notification for pro user") | ||
return | ||
} | ||
|
||
note := ¬ify.Notification{ | ||
Title: title, | ||
Message: msg, | ||
ClickURL: dc.clickURL(), | ||
IconURL: dc.iconURL(), | ||
} | ||
notifier.ShowNotification(note, campaign) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package datacap | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/getlantern/flashlight/v7/bandwidth" | ||
"github.com/getlantern/lantern-client/desktop/ws" | ||
) | ||
|
||
func TestServeCap(t *testing.T) { | ||
err := ServeDataCap(ws.NewUIChannel(), func() string { | ||
return "" | ||
}, func() string { | ||
return "" | ||
}, func() (bool, bool) { | ||
return false, false | ||
}) | ||
assert.NoError(t, err) | ||
|
||
dc := &dataCap{iconURL: func() string { | ||
return "" | ||
}, clickURL: func() string { | ||
return "" | ||
}, isPro: func() (bool, bool) { | ||
return false, false | ||
}} | ||
|
||
hit := false | ||
listener := func(hitDataCap bool) { | ||
hit = hitDataCap | ||
} | ||
AddDataCapListener(listener) | ||
out := make(chan<- interface{}, 2) | ||
quota := &bandwidth.Quota{MiBAllowed: 10, MiBUsed: 10, AsOf: time.Now()} | ||
dc.processQuota(out, quota) | ||
|
||
assert.True(t, hit) | ||
|
||
quota = &bandwidth.Quota{MiBAllowed: 10, MiBUsed: 1, AsOf: time.Now()} | ||
dc.processQuota(out, quota) | ||
} | ||
|
||
func TestPercents(t *testing.T) { | ||
ns := dataCap{} | ||
|
||
quota := &bandwidth.Quota{ | ||
MiBAllowed: 1000, | ||
MiBUsed: 801, | ||
} | ||
|
||
assert.True(t, ns.isEightyOrMore(quota)) | ||
|
||
quota.MiBUsed = 501 | ||
assert.False(t, ns.isEightyOrMore(quota)) | ||
assert.True(t, ns.isFiftyOrMore(quota)) | ||
|
||
msg := "you have used %s of your data" | ||
expected := "you have used 80% of your data" | ||
assert.Equal(t, expected, percentMsg(msg, 80)) | ||
} | ||
|
||
func percentMsg(msg string, percent int) string { | ||
str := strconv.Itoa(percent) + "%" | ||
return fmt.Sprintf(msg, str) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.