Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Added multiple goroutines to thread different types of work, updating…
Browse files Browse the repository at this point in the history
…, querying api and getting tokens....
  • Loading branch information
ianmarmour committed Oct 1, 2020
1 parent c9bea09 commit 7cb493f
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 17 deletions.
60 changes: 45 additions & 15 deletions cmd/nvidia-clerk/nvidia-clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (
"net/http"
"os/exec"
"runtime"
"sync"
"time"

"github.com/ianmarmour/nvidia-clerk/internal/alert"
"github.com/ianmarmour/nvidia-clerk/internal/config"
"github.com/ianmarmour/nvidia-clerk/internal/rest"
"github.com/ianmarmour/nvidia-clerk/internal/update"
)

func main() {
Expand All @@ -32,31 +34,56 @@ func main() {
telegram := flag.Bool("telegram", false, "Enable Telegram webhook notifications for whenever SKU is in stock.")
remote := flag.Bool("remote", false, "Enable remote notification only mode.")
desktop := flag.Bool("desktop", false, "Enable desktop notifications, disabled by default.")
update := flag.Bool("update", true, "Enable automatic updates, enabled by default.")
autoUpdate := flag.Bool("update", true, "Disable automatic updates, enabled by default.")
flag.Parse()

config, configErr := config.Get(region, model, delay, *twilio, *discord, *twitter, *telegram, *desktop, false, *update)
config, configErr := config.Get(region, model, delay, *twilio, *discord, *twitter, *telegram, *desktop, false, *autoUpdate)
if configErr != nil {
log.Fatal(configErr)
}
client := &http.Client{Timeout: 10 * time.Second}

token, err := rest.GetSessionToken(client)
if err != nil {
log.Println("Error getting session token from NVIDIA retrying...")
}
var (
mu sync.Mutex
token rest.SessionToken
)
var wg sync.WaitGroup

// For when NVIDIAs store APIs are down.
for token == nil {
sleep(delay)
token, err = rest.GetSessionToken(client)
wg.Add(3)
go update.FetchApply(config.SystemConfig.UpdateURL, &wg)
go getToken(client, delay, &token, &mu, &wg)
go getGPU(client, config, model, *remote, delay, &wg)

wg.Wait()
}

func getToken(client *http.Client, delay int64, token *rest.SessionToken, mu *sync.Mutex, wg *sync.WaitGroup) error {
defer wg.Done()

for {
newToken, err := rest.GetSessionToken(client)
if err != nil {
log.Printf("Error getting session token from NVIDIA retrying...")
continue
log.Println("Error getting session token from NVIDIA retrying...")
}

break
if token == nil {
mu.Lock()
token = newToken
mu.Unlock()
} else {
if token.Value != newToken.Value {
mu.Lock()
token = newToken
mu.Unlock()
}
}

sleep(delay)
}
}

func getGPU(client *http.Client, config *config.Config, model string, remote bool, delay int64, wg *sync.WaitGroup) error {
defer wg.Done()

for {
sleep(delay)
Expand All @@ -79,6 +106,7 @@ func main() {

if info.Products.Product[0].InventoryStatus.Status == "PRODUCT_INVENTORY_IN_STOCK" {
var cartURL string

switch model {
case "2060":
cartURL = fmt.Sprintf("https://www.nvidia.com/%s/geforce/graphics-cards/rtx-%s-super/", config.NvidiaLocale, model)
Expand All @@ -96,13 +124,13 @@ func main() {
cartURL = "https://www.nvidia.com/"
}

err = notify(info.Products.Product[0].Name, fmt.Sprintf(cartURL, model), *remote, config, client)
err = notify(info.Products.Product[0].Name, fmt.Sprintf(cartURL, model), remote, config, client)
if err != nil {
log.Println("Error attempting to send notification retrying...")
continue
}

if *remote != true {
if remote != true {
err = openbrowser(cartURL)
if err != nil {
log.Fatal("Error attempting to open browser.", err)
Expand All @@ -112,6 +140,8 @@ func main() {
break
}
}

return nil
}

func notify(id string, url string, remote bool, config *config.Config, client *http.Client) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/alert/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (d *DiscordProductMessage) Get() string {

// Set takes in a product URL and returns the JSON body for a Discord POST request
func (d *DiscordProductMessage) Set(url string, status string) {
d.body = "<" + url + ">"
d.body = url
}

// JSON returns the JSON encoded bytes of a DiscordProductMessage
Expand Down
2 changes: 1 addition & 1 deletion internal/alert/toast.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func linuxToast(name string) error {
}

func darwinToast(name string) error {
notification := fmt.Sprintf("display notification \"NVIDIA Clerk\" with title \"NVIDIA Clerk Inventory Alert\" subtitle \"%s\"", fmt.Sprintf("%s Is ready for checkout", name))
notification := fmt.Sprintf("display notification \"NVIDIA Clerk\" with title \"NVIDIA Clerk Inventory Alert\" subtitle \"%s\" sound name \"default\"", fmt.Sprintf("%s Is ready for checkout", name))
err := execCommand("osascript", "-e", notification).Start()
if err != nil {
return err
Expand Down
161 changes: 161 additions & 0 deletions internal/rest/clerks-app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package rest

import (
"encoding/json"
"net/http"
)

type Config struct {
Sections []Section `json:"sections"`
}

//JSON Returns the Marshalled Version of the Response
func (r *Config) JSON() ([]byte, error) {
payload, err := json.Marshal(r)
if err != nil {
return nil, err
}

return payload, nil
}

type Section struct {
ID int64 `json:"id"`
Name string `json:"name"`
Components []Component `json:"components"`
}

type Component struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Value interface{} `json:"value"`
}

type Status struct {
Healthy bool `json:"status"`
}

//JSON Returns the Marshalled Version of the Response
func (r *Status) JSON() ([]byte, error) {
payload, err := json.Marshal(r)
if err != nil {
return nil, err
}

return payload, nil
}

func StatusResponse(w http.ResponseWriter, r *http.Request) {
resBody := Status{
Healthy: true,
}

json, _ := resBody.JSON()
w.Header().Set("Content-Type", "application/json")
w.Write(json)
}

func ConfigResponseMain(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// Declare a new Person struct.
var sect Section

// Try to decode the request body into the struct. If there is an error,
// respond to the client with the error message and a 400 status code.
err := json.NewDecoder(r.Body).Decode(&sect)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

}
}

func ConfigResponse(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
resBody := Config{
Sections: []Section{
{
ID: 15,
Name: "Main",
Components: []Component{
{
ID: 1,
Name: "Region",
Type: "select",
Value: []string{"GBR", "USA"},
},
{
ID: 1,
Name: "Model",
Type: "select",
Value: []string{"3080", "3090"},
},
},
},
{
ID: 15,
Name: "Discord",
Components: []Component{
{
ID: 1,
Name: "Webhook URL",
Type: "input",
},
},
},
{
ID: 15,
Name: "Twilio",
Components: []Component{
{
ID: 1,
Name: "Source",
Type: "input",
},
{
ID: 1,
Name: "Destination",
Type: "input",
},
{
ID: 1,
Name: "API Key",
Type: "input",
},
{
ID: 1,
Name: "Account SID",
Type: "input",
},
},
},
{
ID: 15,
Name: "Telegram",
Components: []Component{
{
ID: 1,
Name: "API Key",
Type: "input",
},
{
ID: 1,
Name: "Chat ID",
Type: "input",
},
},
},
},
}

json, _ := resBody.JSON()
w.Header().Set("Content-Type", "application/json")
w.Write(json)
}

if r.Method == http.MethodPost {

}
}
46 changes: 46 additions & 0 deletions internal/update/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package update

import (
"log"
"math/rand"
"net/http"
"sync"
"time"

"github.com/inconshreveable/go-update"
)

// FetchApply Fetches and applys any updates from GitHub releases to this program in place.
func FetchApply(url string, wg *sync.WaitGroup) error {
defer wg.Done()

for {
log.Println("Attempting to fetch updates from github")
doUpdate(url)
sleep(60000)
}
}

func doUpdate(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()

err = update.Apply(resp.Body, update.Options{})
if err != nil {
log.Println("Error applying updates from github.")
}
return err
}

func sleep(delay int64) {
// Force a randomized jitter of up to 5 seconds to avoid looking like a bot.
rand.Seed(time.Now().UnixNano())
n := rand.Intn(5)

ns := time.Duration(n) * time.Second
ds := time.Duration(delay/1000) * time.Second
time.Sleep(time.Duration(ns + ds))
}

0 comments on commit 7cb493f

Please sign in to comment.