Merge pull request #77 from sqshq/remove_backend_interaction
Remove all backend interaction
This commit is contained in:
commit
e3d5b9dc0a
|
@ -1,153 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"github.com/sqshq/sampler/metadata"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
backendUrl = "http://localhost/api/v1"
|
|
||||||
installationPath = "/telemetry/installation"
|
|
||||||
statisticsPath = "/telemetry/statistics"
|
|
||||||
crashPath = "/telemetry/crash"
|
|
||||||
registrationPath = "/license/registration"
|
|
||||||
verificationPath = "/license/verification"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BackendClient is used to verify license and to send telemetry
|
|
||||||
// for analyses (anonymous usage data statistics and crash reports)
|
|
||||||
type BackendClient struct {
|
|
||||||
client http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBackendClient() *BackendClient {
|
|
||||||
return &BackendClient{
|
|
||||||
client: http.Client{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BackendClient) ReportInstallation(statistics *metadata.Statistics) {
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(statistics)
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), statistics)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sendRequest(backendUrl+installationPath, buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), statistics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BackendClient) ReportStatistics(statistics *metadata.Statistics) {
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(statistics)
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), statistics)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sendRequest(backendUrl+statisticsPath, buf)
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), statistics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BackendClient) ReportCrash(error string, statistics *metadata.Statistics) {
|
|
||||||
|
|
||||||
req := struct {
|
|
||||||
Error string
|
|
||||||
Statistics *metadata.Statistics
|
|
||||||
}{
|
|
||||||
error,
|
|
||||||
statistics,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = sendRequest(backendUrl+crashPath, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BackendClient) RegisterLicenseKey(licenseKey string, statistics *metadata.Statistics) (*metadata.License, error) {
|
|
||||||
|
|
||||||
req := struct {
|
|
||||||
LicenseKey string
|
|
||||||
Statistics *metadata.Statistics
|
|
||||||
}{
|
|
||||||
licenseKey,
|
|
||||||
statistics,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(req)
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), statistics)
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := sendRequest(backendUrl+registrationPath, buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
|
||||||
return nil, errors.New(string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
var license metadata.License
|
|
||||||
json.NewDecoder(response.Body).Decode(&license)
|
|
||||||
|
|
||||||
return &license, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BackendClient) VerifyLicenseKey(licenseKey string) (*metadata.License, error) {
|
|
||||||
|
|
||||||
req := struct {
|
|
||||||
LicenseKey string
|
|
||||||
}{
|
|
||||||
licenseKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(req)
|
|
||||||
if err != nil {
|
|
||||||
c.ReportCrash(err.Error(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := sendRequest(backendUrl+verificationPath, buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
|
||||||
return nil, errors.New(string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
var license metadata.License
|
|
||||||
json.NewDecoder(response.Body).Decode(&license)
|
|
||||||
|
|
||||||
return &license, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendRequest(url string, body *bytes.Buffer) (resp *http.Response, err error) {
|
|
||||||
c := http.DefaultClient
|
|
||||||
req, err := http.NewRequest("POST", url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return c.Do(req)
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
package component
|
|
||||||
|
|
||||||
import (
|
|
||||||
ui "github.com/gizak/termui/v3"
|
|
||||||
"github.com/sqshq/sampler/component/util"
|
|
||||||
"github.com/sqshq/sampler/console"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Intro struct {
|
|
||||||
*ui.Block
|
|
||||||
page IntroPage
|
|
||||||
option introOption
|
|
||||||
palette console.Palette
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntroPage rune
|
|
||||||
|
|
||||||
const (
|
|
||||||
IntroPageWelcome IntroPage = 0
|
|
||||||
IntroPageCommercial IntroPage = 1
|
|
||||||
IntroPagePersonal IntroPage = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type introOption rune
|
|
||||||
|
|
||||||
const (
|
|
||||||
introOptionCommercial introOption = 0
|
|
||||||
introOptionPersonal introOption = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
buttonCommercial string = " COMMERCIAL USE "
|
|
||||||
buttonPersonal string = " PERSONAL USE "
|
|
||||||
buttonOk string = " OK "
|
|
||||||
)
|
|
||||||
|
|
||||||
func (intro *Intro) Up() {
|
|
||||||
intro.option = introOptionCommercial
|
|
||||||
}
|
|
||||||
|
|
||||||
func (intro *Intro) Down() {
|
|
||||||
intro.option = introOptionPersonal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (intro *Intro) NextPage() {
|
|
||||||
if intro.option == introOptionCommercial {
|
|
||||||
intro.page = IntroPageCommercial
|
|
||||||
} else {
|
|
||||||
intro.page = IntroPagePersonal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (intro *Intro) GetSelectedPage() IntroPage {
|
|
||||||
return intro.page
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIntro(palette console.Palette) *Intro {
|
|
||||||
return &Intro{
|
|
||||||
Block: NewBlock("", false, palette),
|
|
||||||
palette: palette,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (intro *Intro) Draw(buffer *ui.Buffer) {
|
|
||||||
|
|
||||||
introText := append(util.AsciiLogo, []string{
|
|
||||||
"", "", "",
|
|
||||||
"Welcome.",
|
|
||||||
"",
|
|
||||||
"Sampler is an OSS project, and it needs funding to be alive and keep developing",
|
|
||||||
"Before the first start, please explore our licensing options below. For more details, visit WWW.SAMPLER.DEV",
|
|
||||||
"", "", "",
|
|
||||||
"How do you plan to use Sampler?",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
commericalText := append(util.AsciiLogo, []string{
|
|
||||||
"", "", "", "",
|
|
||||||
"With Sampler, you can easily save time and solve some of your business problems.",
|
|
||||||
"That's why support of the project is in the interest of your organization.",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"We are offering commercial licenses which provide priority support and technical assistance.",
|
|
||||||
"After entering the licence key, your company name will appear in the status bar.",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"To make a purchase, please visit WWW.SAMPLER.DEV",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
personalText := append(util.AsciiLogo, []string{
|
|
||||||
"", "", "", "",
|
|
||||||
"Sampler is always free to use, but you can support the project and donate any amount to get a personal license.",
|
|
||||||
"Once it is activated, your name will appear in the status bar.",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"To become a sponsor, please visit WWW.SAMPLER.DEV",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
text := introText
|
|
||||||
|
|
||||||
switch intro.page {
|
|
||||||
case IntroPageWelcome:
|
|
||||||
text = introText
|
|
||||||
case IntroPageCommercial:
|
|
||||||
text = commericalText
|
|
||||||
case IntroPagePersonal:
|
|
||||||
text = personalText
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, a := range text {
|
|
||||||
util.PrintString(
|
|
||||||
a,
|
|
||||||
ui.NewStyle(intro.palette.BaseColor),
|
|
||||||
util.GetMiddlePoint(intro.Block.Rectangle, a, i-15),
|
|
||||||
buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
highlightedStyle := ui.NewStyle(intro.palette.ReverseColor, intro.palette.BaseColor)
|
|
||||||
regularStyle := ui.NewStyle(intro.palette.BaseColor, intro.palette.ReverseColor)
|
|
||||||
|
|
||||||
if intro.page == IntroPageWelcome {
|
|
||||||
|
|
||||||
commercialButtonStyle := highlightedStyle
|
|
||||||
if intro.option == introOptionPersonal {
|
|
||||||
commercialButtonStyle = regularStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
personalButtonStyle := highlightedStyle
|
|
||||||
if intro.option == introOptionCommercial {
|
|
||||||
personalButtonStyle = regularStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.SetString(buttonCommercial, commercialButtonStyle,
|
|
||||||
util.GetMiddlePoint(intro.Block.Rectangle, buttonCommercial, 5))
|
|
||||||
buffer.SetString(buttonPersonal, personalButtonStyle,
|
|
||||||
util.GetMiddlePoint(intro.Block.Rectangle, buttonPersonal, 7))
|
|
||||||
} else {
|
|
||||||
buffer.SetString(buttonOk, highlightedStyle,
|
|
||||||
util.GetMiddlePoint(intro.Block.Rectangle, buttonOk, 7))
|
|
||||||
}
|
|
||||||
|
|
||||||
intro.Block.Draw(buffer)
|
|
||||||
}
|
|
|
@ -19,8 +19,6 @@ type Layout struct {
|
||||||
Components []*component.Component
|
Components []*component.Component
|
||||||
statusbar *component.StatusBar
|
statusbar *component.StatusBar
|
||||||
menu *component.Menu
|
menu *component.Menu
|
||||||
intro *component.Intro
|
|
||||||
nag *component.NagWindow
|
|
||||||
ChangeModeEvents chan Mode
|
ChangeModeEvents chan Mode
|
||||||
mode Mode
|
mode Mode
|
||||||
selection int
|
selection int
|
||||||
|
@ -33,28 +31,24 @@ type Mode rune
|
||||||
const (
|
const (
|
||||||
ModeDefault Mode = 0
|
ModeDefault Mode = 0
|
||||||
ModeIntro Mode = 1
|
ModeIntro Mode = 1
|
||||||
ModeNag Mode = 2
|
ModePause Mode = 2
|
||||||
ModePause Mode = 3
|
ModeComponentSelect Mode = 3
|
||||||
ModeComponentSelect Mode = 4
|
ModeMenuOptionSelect Mode = 4
|
||||||
ModeMenuOptionSelect Mode = 5
|
ModeComponentMove Mode = 5
|
||||||
ModeComponentMove Mode = 6
|
ModeComponentResize Mode = 6
|
||||||
ModeComponentResize Mode = 7
|
ModeChartPinpoint Mode = 7
|
||||||
ModeChartPinpoint Mode = 8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minDimension = 3
|
minDimension = 3
|
||||||
statusbarHeight = 1
|
statusbarHeight = 1
|
||||||
nagWindowDurationSec = 5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLayout(statusline *component.StatusBar, menu *component.Menu, intro *component.Intro, nag *component.NagWindow) *Layout {
|
func NewLayout(statusline *component.StatusBar, menu *component.Menu) *Layout {
|
||||||
|
|
||||||
width, height := ui.TerminalDimensions()
|
width, height := ui.TerminalDimensions()
|
||||||
block := *ui.NewBlock()
|
block := *ui.NewBlock()
|
||||||
block.SetRect(0, 0, width, height)
|
block.SetRect(0, 0, width, height)
|
||||||
intro.SetRect(0, 0, width, height)
|
|
||||||
nag.SetRect(0, 0, width, height)
|
|
||||||
statusline.SetRect(0, height-statusbarHeight, width, height)
|
statusline.SetRect(0, height-statusbarHeight, width, height)
|
||||||
|
|
||||||
return &Layout{
|
return &Layout{
|
||||||
|
@ -62,8 +56,6 @@ func NewLayout(statusline *component.StatusBar, menu *component.Menu, intro *com
|
||||||
Components: make([]*component.Component, 0),
|
Components: make([]*component.Component, 0),
|
||||||
statusbar: statusline,
|
statusbar: statusline,
|
||||||
menu: menu,
|
menu: menu,
|
||||||
intro: intro,
|
|
||||||
nag: nag,
|
|
||||||
mode: ModeDefault,
|
mode: ModeDefault,
|
||||||
selection: 0,
|
selection: 0,
|
||||||
ChangeModeEvents: make(chan Mode, 10),
|
ChangeModeEvents: make(chan Mode, 10),
|
||||||
|
@ -79,10 +71,6 @@ func (l *Layout) StartWithIntro() {
|
||||||
l.mode = ModeIntro
|
l.mode = ModeIntro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layout) StartWithNagWindow() {
|
|
||||||
l.mode = ModeNag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layout) changeMode(m Mode) {
|
func (l *Layout) changeMode(m Mode) {
|
||||||
if m == ModeComponentResize || m == ModeComponentMove {
|
if m == ModeComponentResize || m == ModeComponentMove {
|
||||||
l.positionsChanged = true
|
l.positionsChanged = true
|
||||||
|
@ -92,7 +80,7 @@ func (l *Layout) changeMode(m Mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layout) HandleMouseClick(x int, y int) {
|
func (l *Layout) HandleMouseClick(x int, y int) {
|
||||||
if l.mode == ModeIntro || l.mode == ModeNag {
|
if l.mode == ModeIntro {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.menu.Idle()
|
l.menu.Idle()
|
||||||
|
@ -151,15 +139,6 @@ func (l *Layout) HandleKeyboardEvent(e string) {
|
||||||
l.menu.Idle()
|
l.menu.Idle()
|
||||||
l.changeMode(ModeDefault)
|
l.changeMode(ModeDefault)
|
||||||
break
|
break
|
||||||
case ModeIntro:
|
|
||||||
page := l.intro.GetSelectedPage()
|
|
||||||
if page == component.IntroPageWelcome {
|
|
||||||
l.intro.NextPage()
|
|
||||||
} else {
|
|
||||||
l.changeMode(ModeDefault)
|
|
||||||
}
|
|
||||||
case ModeNag:
|
|
||||||
l.nag.Accept()
|
|
||||||
}
|
}
|
||||||
case console.KeyEsc:
|
case console.KeyEsc:
|
||||||
l.resetAlerts()
|
l.resetAlerts()
|
||||||
|
@ -222,8 +201,6 @@ func (l *Layout) HandleKeyboardEvent(e string) {
|
||||||
selected.Move(0, -1)
|
selected.Move(0, -1)
|
||||||
case ModeComponentResize:
|
case ModeComponentResize:
|
||||||
selected.Resize(0, -1)
|
selected.Resize(0, -1)
|
||||||
case ModeIntro:
|
|
||||||
l.intro.Up()
|
|
||||||
}
|
}
|
||||||
case console.KeyDown:
|
case console.KeyDown:
|
||||||
switch l.mode {
|
switch l.mode {
|
||||||
|
@ -239,8 +216,6 @@ func (l *Layout) HandleKeyboardEvent(e string) {
|
||||||
selected.Move(0, 1)
|
selected.Move(0, 1)
|
||||||
case ModeComponentResize:
|
case ModeComponentResize:
|
||||||
selected.Resize(0, 1)
|
selected.Resize(0, 1)
|
||||||
case ModeIntro:
|
|
||||||
l.intro.Down()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,22 +305,6 @@ func (l *Layout) Draw(buffer *ui.Buffer) {
|
||||||
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)
|
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.mode == ModeIntro {
|
|
||||||
l.intro.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
|
|
||||||
l.intro.Draw(buffer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.mode == ModeNag {
|
|
||||||
if l.nag.IsAccepted() && time.Since(l.startupTime).Seconds() > nagWindowDurationSec {
|
|
||||||
l.mode = ModeDefault
|
|
||||||
} else {
|
|
||||||
l.nag.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
|
|
||||||
l.nag.Draw(buffer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range l.Components {
|
for _, c := range l.Components {
|
||||||
c.Draw(buffer)
|
c.Draw(buffer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
package component
|
|
||||||
|
|
||||||
import (
|
|
||||||
ui "github.com/gizak/termui/v3"
|
|
||||||
"github.com/sqshq/sampler/component/util"
|
|
||||||
"github.com/sqshq/sampler/console"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NagWindow struct {
|
|
||||||
*ui.Block
|
|
||||||
palette console.Palette
|
|
||||||
accepted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNagWindow(palette console.Palette) *NagWindow {
|
|
||||||
return &NagWindow{
|
|
||||||
Block: NewBlock("", false, palette),
|
|
||||||
palette: palette,
|
|
||||||
accepted: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NagWindow) Accept() {
|
|
||||||
n.accepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NagWindow) IsAccepted() bool {
|
|
||||||
return n.accepted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NagWindow) Draw(buffer *ui.Buffer) {
|
|
||||||
|
|
||||||
text := append(util.AsciiLogo, []string{
|
|
||||||
"", "", "",
|
|
||||||
"Thank you for using Sampler.",
|
|
||||||
"It is always free, but you can sponsor the project and buy the personal or commercial license",
|
|
||||||
"for priority support and technical assistance.",
|
|
||||||
"",
|
|
||||||
"Please visit www.sampler.dev",
|
|
||||||
}...)
|
|
||||||
|
|
||||||
for i, a := range text {
|
|
||||||
util.PrintString(
|
|
||||||
a,
|
|
||||||
ui.NewStyle(n.palette.BaseColor),
|
|
||||||
util.GetMiddlePoint(n.Block.Rectangle, a, i-15),
|
|
||||||
buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.SetString(buttonOk, ui.NewStyle(n.palette.ReverseColor, n.palette.BaseColor),
|
|
||||||
util.GetMiddlePoint(n.Block.Rectangle, buttonOk, 4))
|
|
||||||
|
|
||||||
n.Block.Draw(buffer)
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
"github.com/sqshq/sampler/console"
|
"github.com/sqshq/sampler/console"
|
||||||
"github.com/sqshq/sampler/metadata"
|
|
||||||
"image"
|
"image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,24 +19,11 @@ type StatusBar struct {
|
||||||
pause bool
|
pause bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatusBar(configFileName string, palette console.Palette, license *metadata.License) *StatusBar {
|
func NewStatusBar(configFileName string, palette console.Palette) *StatusBar {
|
||||||
|
|
||||||
block := *ui.NewBlock()
|
block := *ui.NewBlock()
|
||||||
block.Border = false
|
block.Border = false
|
||||||
text := fmt.Sprintf(" %s %s | ", console.AppTitle, console.AppVersion)
|
text := fmt.Sprintf(" %s %s | %s", console.AppTitle, console.AppVersion, configFileName)
|
||||||
|
|
||||||
if license == nil || !license.Valid || license.Type == nil {
|
|
||||||
text += console.AppLicenseWarning
|
|
||||||
} else if *license.Type == metadata.TypePersonal {
|
|
||||||
text += fmt.Sprintf("%s | personal license: %s", configFileName, *license.Username)
|
|
||||||
} else if license.Username != nil {
|
|
||||||
text += fmt.Sprintf("%s | licensed to %s", configFileName, *license.Username)
|
|
||||||
if license.Company != nil {
|
|
||||||
text += fmt.Sprintf(", %s", *license.Company)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text += fmt.Sprintf("%s | licensed to %s", configFileName, *license.Company)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &StatusBar{
|
return &StatusBar{
|
||||||
Block: NewBlock("", false, palette),
|
Block: NewBlock("", false, palette),
|
||||||
|
|
|
@ -34,14 +34,10 @@ func LoadConfig() (*Config, Options) {
|
||||||
console.Exit(console.AppVersion)
|
console.Exit(console.AppVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.ConfigFile == nil && opt.LicenseKey == nil {
|
if opt.ConfigFile == nil {
|
||||||
console.Exit("Please specify config file using --config flag. Example: sampler --config example.yml")
|
console.Exit("Please specify config file using --config flag. Example: sampler --config example.yml")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.LicenseKey != nil {
|
|
||||||
return nil, opt
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := readFile(opt.ConfigFile)
|
cfg := readFile(opt.ConfigFile)
|
||||||
cfg.validate()
|
cfg.validate()
|
||||||
cfg.setDefaults()
|
cfg.setDefaults()
|
||||||
|
|
|
@ -3,8 +3,6 @@ package config
|
||||||
// Options with cli flags
|
// Options with cli flags
|
||||||
type Options struct {
|
type Options struct {
|
||||||
ConfigFile *string `short:"c" long:"config" description:"Path to YAML config file"`
|
ConfigFile *string `short:"c" long:"config" description:"Path to YAML config file"`
|
||||||
LicenseKey *string `short:"l" long:"license" description:"License key. Visit www.sampler.dev for details"`
|
|
||||||
Environment []string `short:"e" long:"env" description:"Specify name=value variable to use in script placeholder as $name. This flag takes precedence over the same name variables, specified in config yml"`
|
Environment []string `short:"e" long:"env" description:"Specify name=value variable to use in script placeholder as $name. This flag takes precedence over the same name variables, specified in config yml"`
|
||||||
Version bool `short:"v" long:"version" description:"Print version"`
|
Version bool `short:"v" long:"version" description:"Print version"`
|
||||||
DisableTelemetry bool `long:"disable-telemetry" description:"Disable anonymous usage statistics and errors to be sent to Sampler online service for analyses"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ const (
|
||||||
RowsCount = 40
|
RowsCount = 40
|
||||||
AppTitle = "sampler"
|
AppTitle = "sampler"
|
||||||
AppVersion = "1.0.3"
|
AppVersion = "1.0.3"
|
||||||
AppLicenseWarning = "NOT ACTIVATED. PLEASE CONSIDER TO PURCHASE PERSONAL OR COMMERCIAL LICENSE. WWW.SAMPLER.DEV "
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
65
main.go
65
main.go
|
@ -1,10 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
"github.com/sqshq/sampler/asset"
|
"github.com/sqshq/sampler/asset"
|
||||||
"github.com/sqshq/sampler/client"
|
|
||||||
"github.com/sqshq/sampler/component"
|
"github.com/sqshq/sampler/component"
|
||||||
"github.com/sqshq/sampler/component/asciibox"
|
"github.com/sqshq/sampler/component/asciibox"
|
||||||
"github.com/sqshq/sampler/component/barchart"
|
"github.com/sqshq/sampler/component/barchart"
|
||||||
|
@ -17,8 +15,6 @@ import (
|
||||||
"github.com/sqshq/sampler/console"
|
"github.com/sqshq/sampler/console"
|
||||||
"github.com/sqshq/sampler/data"
|
"github.com/sqshq/sampler/data"
|
||||||
"github.com/sqshq/sampler/event"
|
"github.com/sqshq/sampler/event"
|
||||||
"github.com/sqshq/sampler/metadata"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,14 +67,6 @@ func (s *Starter) start(drawable ui.Drawable, consumer *data.Consumer, component
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
cfg, opt := config.LoadConfig()
|
cfg, opt := config.LoadConfig()
|
||||||
bc := client.NewBackendClient()
|
|
||||||
|
|
||||||
statistics := metadata.GetStatistics(cfg)
|
|
||||||
license := metadata.GetLicense()
|
|
||||||
|
|
||||||
if opt.LicenseKey != nil {
|
|
||||||
registerLicense(statistics, opt, bc)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.Init()
|
console.Init()
|
||||||
defer console.Close()
|
defer console.Close()
|
||||||
|
@ -88,28 +76,8 @@ func main() {
|
||||||
defer player.Close()
|
defer player.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
defer handleCrash(statistics, opt, bc)
|
|
||||||
defer updateStatistics(cfg, time.Now())
|
|
||||||
|
|
||||||
palette := console.GetPalette(*cfg.Theme)
|
palette := console.GetPalette(*cfg.Theme)
|
||||||
lout := layout.NewLayout(component.NewStatusBar(*opt.ConfigFile, palette, license),
|
lout := layout.NewLayout(component.NewStatusBar(*opt.ConfigFile, palette), component.NewMenu(palette))
|
||||||
component.NewMenu(palette), component.NewIntro(palette), component.NewNagWindow(palette))
|
|
||||||
|
|
||||||
if statistics.LaunchCount == 0 {
|
|
||||||
if !opt.DisableTelemetry {
|
|
||||||
go bc.ReportInstallation(statistics)
|
|
||||||
}
|
|
||||||
lout.StartWithIntro()
|
|
||||||
} else if statistics.LaunchCount%20 == 0 { // once in a while
|
|
||||||
if license == nil || !license.Valid {
|
|
||||||
lout.StartWithNagWindow()
|
|
||||||
} else {
|
|
||||||
go verifyLicense(license, bc)
|
|
||||||
}
|
|
||||||
if !opt.DisableTelemetry {
|
|
||||||
go bc.ReportStatistics(statistics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
starter := &Starter{player, lout, palette, opt, *cfg}
|
starter := &Starter{player, lout, palette, opt, *cfg}
|
||||||
samplers := starter.startAll()
|
samplers := starter.startAll()
|
||||||
|
@ -117,34 +85,3 @@ func main() {
|
||||||
handler := event.NewHandler(samplers, opt, lout)
|
handler := event.NewHandler(samplers, opt, lout)
|
||||||
handler.HandleEvents()
|
handler.HandleEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCrash(statistics *metadata.Statistics, opt config.Options, bc *client.BackendClient) {
|
|
||||||
if rec := recover(); rec != nil {
|
|
||||||
err := rec.(error)
|
|
||||||
if !opt.DisableTelemetry {
|
|
||||||
bc.ReportCrash(fmt.Sprintf("%s\n%s", err.Error(), string(debug.Stack())), statistics)
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateStatistics(cfg *config.Config, startTime time.Time) {
|
|
||||||
metadata.PersistStatistics(cfg, time.Since(startTime))
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerLicense(statistics *metadata.Statistics, opt config.Options, bc *client.BackendClient) {
|
|
||||||
lc, err := bc.RegisterLicenseKey(*opt.LicenseKey, statistics)
|
|
||||||
if err != nil {
|
|
||||||
console.Exit("License registration failed: " + err.Error())
|
|
||||||
} else {
|
|
||||||
metadata.SaveLicense(*lc)
|
|
||||||
console.Exit("License successfully verified, Sampler can be restarted without --license flag now. Thank you.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyLicense(license *metadata.License, bc *client.BackendClient) {
|
|
||||||
verifiedLicense, _ := bc.VerifyLicenseKey(*license.Key)
|
|
||||||
if verifiedLicense != nil {
|
|
||||||
metadata.SaveLicense(*verifiedLicense)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
package metadata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type License struct {
|
|
||||||
Key *string `yaml:"k"`
|
|
||||||
Username *string `yaml:"u"`
|
|
||||||
Company *string `yaml:"c"`
|
|
||||||
Type *LicenseType `yaml:"t"`
|
|
||||||
Valid bool `yaml:"v"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicenseType rune
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypePersonal LicenseType = 0
|
|
||||||
TypeCommercial LicenseType = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const licenseFileName = "license.yml"
|
|
||||||
|
|
||||||
func GetLicense() *License {
|
|
||||||
|
|
||||||
if !fileExists(licenseFileName) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
file := readStorageFile(getPlatformStoragePath(licenseFileName))
|
|
||||||
|
|
||||||
license := new(License)
|
|
||||||
err := yaml.Unmarshal(file, license)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read license file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return license
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveLicense(license License) {
|
|
||||||
|
|
||||||
initStorage()
|
|
||||||
|
|
||||||
file, err := yaml.Marshal(license)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to marshal license file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveStorageFile(file, licenseFileName)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package metadata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_getEmptyLicense(t *testing.T) {
|
|
||||||
|
|
||||||
cleanupPlatformStorage()
|
|
||||||
license := GetLicense()
|
|
||||||
|
|
||||||
if license != nil {
|
|
||||||
t.Errorf("expected to be nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_saveAndGetExistingLicense(t *testing.T) {
|
|
||||||
|
|
||||||
cleanupPlatformStorage()
|
|
||||||
|
|
||||||
original := License{
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveLicense(original)
|
|
||||||
|
|
||||||
retrieved := *GetLicense()
|
|
||||||
|
|
||||||
if original != retrieved {
|
|
||||||
t.Errorf("read file != saved file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupPlatformStorage() {
|
|
||||||
_ = os.RemoveAll(getPlatformStoragePath(""))
|
|
||||||
_ = os.Remove(getPlatformStoragePath(""))
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
package metadata
|
|
||||||
|
|
||||||
import (
|
|
||||||
ui "github.com/gizak/termui/v3"
|
|
||||||
"github.com/sqshq/sampler/config"
|
|
||||||
"github.com/sqshq/sampler/console"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"log"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Statistics represents anonymous usage data, which we collect for analyses and improvements
|
|
||||||
// User can disable it, along with crash reports, using --disable-telemetry flag
|
|
||||||
type Statistics struct {
|
|
||||||
Version string
|
|
||||||
OS string
|
|
||||||
WindowWidth int `yaml:"ww"`
|
|
||||||
WindowHeight int `yaml:"wh"`
|
|
||||||
LaunchCount int `yaml:"lc"`
|
|
||||||
UsageTime int `yaml:"ut"`
|
|
||||||
ComponentsCount map[string]int `yaml:"cc"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const statisticsFileName = "statistics.yml"
|
|
||||||
|
|
||||||
func PersistStatistics(config *config.Config, uptime time.Duration) *Statistics {
|
|
||||||
|
|
||||||
statistics := new(Statistics)
|
|
||||||
w, h := ui.TerminalDimensions()
|
|
||||||
|
|
||||||
if fileExists(statisticsFileName) {
|
|
||||||
file := readStorageFile(getPlatformStoragePath(statisticsFileName))
|
|
||||||
err := yaml.Unmarshal(file, statistics)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read statistics file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config != nil {
|
|
||||||
statistics.ComponentsCount = countComponentsPerType(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
statistics.Version = console.AppVersion
|
|
||||||
statistics.WindowWidth = w
|
|
||||||
statistics.WindowHeight = h
|
|
||||||
statistics.LaunchCount += 1
|
|
||||||
statistics.UsageTime += int(uptime.Seconds())
|
|
||||||
|
|
||||||
} else {
|
|
||||||
statistics = &Statistics{
|
|
||||||
Version: console.AppVersion,
|
|
||||||
OS: runtime.GOOS,
|
|
||||||
WindowWidth: w,
|
|
||||||
WindowHeight: h,
|
|
||||||
LaunchCount: 1,
|
|
||||||
UsageTime: 0,
|
|
||||||
ComponentsCount: countComponentsPerType(config),
|
|
||||||
}
|
|
||||||
initStorage()
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := yaml.Marshal(statistics)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to marshal statistics file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveStorageFile(file, statisticsFileName)
|
|
||||||
|
|
||||||
return statistics
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStatistics(cfg *config.Config) *Statistics {
|
|
||||||
|
|
||||||
if !fileExists(statisticsFileName) {
|
|
||||||
return &Statistics{
|
|
||||||
Version: console.AppVersion,
|
|
||||||
OS: runtime.GOOS,
|
|
||||||
LaunchCount: 0,
|
|
||||||
WindowWidth: 0,
|
|
||||||
WindowHeight: 0,
|
|
||||||
ComponentsCount: countComponentsPerType(cfg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file := readStorageFile(getPlatformStoragePath(statisticsFileName))
|
|
||||||
license := new(Statistics)
|
|
||||||
|
|
||||||
err := yaml.Unmarshal(file, license)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read statistics file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return license
|
|
||||||
}
|
|
||||||
|
|
||||||
func countComponentsPerType(config *config.Config) map[string]int {
|
|
||||||
|
|
||||||
m := make(map[string]int)
|
|
||||||
|
|
||||||
if config == nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
m["runcharts"] = len(config.RunCharts)
|
|
||||||
m["sparkLines"] = len(config.SparkLines)
|
|
||||||
m["barcharts"] = len(config.BarCharts)
|
|
||||||
m["gauges"] = len(config.Gauges)
|
|
||||||
m["asciiboxes"] = len(config.AsciiBoxes)
|
|
||||||
m["textboxes"] = len(config.TextBoxes)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
Loading…
Reference in New Issue