Skip to content

Commit 2b4b36d

Browse files
authored
Merge pull request #6 from 0ne-zero/0-refactor
Codebase refactored
2 parents f02279d + 63cf181 commit 2b4b36d

File tree

5 files changed

+264
-145
lines changed

5 files changed

+264
-145
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
OUTPUT_DIR = build
2-
SOURCE = main.go
2+
SOURCE = ./
33

44
all: linux windows mac
55

flag.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/ohmydevops/arvancloud-radar-notif/radar"
10+
)
11+
12+
type Config struct {
13+
Service string
14+
ShowServices bool
15+
}
16+
17+
func parseFlags() (*Config, error) {
18+
var cfg Config
19+
20+
flag.StringVar(&cfg.Service, "service", "", "Service name to monitor (e.g. google, github, etc.)")
21+
flag.BoolVar(&cfg.ShowServices, "services", false, "Show list of available services")
22+
flag.Parse()
23+
24+
if cfg.ShowServices {
25+
return &cfg, nil
26+
}
27+
28+
// normalize to lowercase
29+
cfg.Service = strings.ToLower(cfg.Service)
30+
31+
if cfg.Service == "" {
32+
return nil, errors.New("must specify a service")
33+
}
34+
// Validate service
35+
if _, ok := radar.ParseService(cfg.Service); !ok {
36+
return nil, fmt.Errorf("invalid service: %s", cfg.Service)
37+
}
38+
return &cfg, nil
39+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module arvancloud-radar-notif
1+
module github.com/ohmydevops/arvancloud-radar-notif
22

33
go 1.24.5
44

main.go

Lines changed: 74 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,186 +1,117 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"flag"
65
"fmt"
7-
"io"
8-
"net/http"
9-
"net/http/cookiejar"
6+
"log"
107
"os"
8+
"sync"
119
"time"
1210

1311
"github.com/gen2brain/beeep"
12+
"github.com/ohmydevops/arvancloud-radar-notif/radar"
1413
)
1514

16-
const baseURL = "https://radar.arvancloud.ir/api/v1/internet-monitoring?isp="
15+
// Max consecutive errors to consider outage
16+
const maxISPError = 3
1717

1818
var (
19-
errorCounts = make(map[string]int)
20-
erroredDataCenters = make(map[string]bool)
21-
serviceIndicator string
19+
ISPErrorCounts = make(map[radar.Datacenter]int)
20+
erroredISPs = make(map[radar.Datacenter]bool)
21+
mu sync.Mutex // protects ISPErrorCounts & erroredISPs
2222
)
2323

24-
var availableServices = []string{
25-
"google",
26-
"wikipedia",
27-
"playstation",
28-
"bing",
29-
"github",
30-
"digikala",
31-
"divar",
32-
"aparat",
33-
}
34-
35-
var dataCenters = []string{
36-
"sindad-buf",
37-
"sindad-thr-fanava",
38-
"sindad-thr",
39-
"bertina-xrx",
40-
"ajk-abrbaran",
41-
"tehran-3",
42-
"tehran-2",
43-
"bertina-thr",
44-
"hostiran",
45-
"parsonline",
46-
"afranet",
47-
"mci",
48-
"irancell",
49-
}
50-
51-
func printServices() {
52-
fmt.Println("Usage:")
53-
fmt.Println(" --service=N Run directly without prompt, where N is the service number (see below)")
54-
fmt.Println(" --help Show this help message and available services")
55-
fmt.Println()
56-
fmt.Println("Available services:")
57-
for i, s := range availableServices {
58-
fmt.Printf(" %2d) %s\n", i+1, s)
24+
func main() {
25+
cfg, err := parseFlags()
26+
if err != nil {
27+
fmt.Println("❌", err)
28+
flag.Usage()
29+
os.Exit(1)
5930
}
60-
fmt.Println()
61-
fmt.Println("Examples:")
62-
fmt.Println(" ./radar-linux # Interactive mode (asks for service)")
63-
fmt.Println(" ./radar-linux --service=3 # Monitor playstation directly")
64-
fmt.Println(" ./radar-linux --help # Show this help message")
65-
}
6631

67-
func chooseServiceInteractive() string {
68-
printServices()
69-
var choice int
70-
fmt.Print("Enter number: ")
71-
_, err := fmt.Scanln(&choice)
72-
if err != nil || choice < 1 || choice > len(availableServices) {
73-
fmt.Println("⚠️ Invalid choice. Defaulting to 'google'")
74-
return "google"
32+
if cfg.ShowServices {
33+
printServices()
34+
os.Exit(0)
7535
}
76-
return availableServices[choice-1]
77-
}
7836

79-
func fetchData(client *http.Client, dataCenter string) (float64, error) {
80-
url := baseURL + dataCenter
37+
fmt.Println("📡 Arvan Cloud Radar Monitor")
38+
39+
fmt.Printf("✅ Monitoring service: %s\n", cfg.Service)
8140

82-
req, err := http.NewRequest("GET", url, nil)
83-
if err != nil {
84-
return 0, fmt.Errorf("⚠️ request creation error: %v", err)
85-
}
41+
waitUntilNextMinute()
8642

87-
resp, err := client.Do(req)
88-
if err != nil {
89-
return 0, fmt.Errorf("⚠️ request error: %v", err)
90-
}
91-
defer resp.Body.Close()
43+
for {
44+
fmt.Printf("⏰ %s\n", time.Now().Format("15:04:05"))
9245

93-
if resp.StatusCode != http.StatusOK {
94-
return 0, fmt.Errorf("⚠️ unexpected status code: %d", resp.StatusCode)
95-
}
46+
var wg sync.WaitGroup
9647

97-
body, err := io.ReadAll(resp.Body)
98-
if err != nil {
99-
return 0, fmt.Errorf("⚠️ error reading response: %v", err)
100-
}
48+
for _, isp := range radar.AllDatacenters {
49+
wg.Add(1)
50+
go func(isp radar.Datacenter) {
51+
defer wg.Done()
52+
checkISP(isp, radar.Service(cfg.Service))
53+
}(isp)
54+
}
10155

102-
var data map[string][]float64
103-
if err := json.Unmarshal(body, &data); err != nil {
104-
return 0, fmt.Errorf("⚠️ JSON parse error: %v", err)
56+
wg.Wait()
57+
time.Sleep(1 * time.Minute)
10558
}
59+
}
10660

107-
values, ok := data[serviceIndicator]
108-
if !ok || values == nil || len(values) == 0 {
109-
return 0, fmt.Errorf("-")
61+
// checkISP handles checking & notification for a single ISP
62+
func checkISP(isp radar.Datacenter, service radar.Service) {
63+
stats, err := radar.CheckDatacenterServiceStatistics(isp, service)
64+
if err != nil {
65+
fmt.Printf("[%s] ⚠️ %v\n", isp, err)
66+
return
11067
}
11168

112-
return values[len(values)-1], nil
113-
}
69+
mu.Lock()
70+
defer mu.Unlock()
11471

115-
func checkStatus(dataCenter string, value float64) {
116-
fmt.Printf("[%s] => Value: %.2f\n", dataCenter, value)
117-
beeep.AppName = "Arvan Cloud Radar"
118-
if value != 0 {
119-
errorCounts[dataCenter]++
120-
if errorCounts[dataCenter] >= 3 && !erroredDataCenters[dataCenter] {
121-
err := beeep.Notify("🔴 Internet Outage", fmt.Sprintf("%s unreachable from %s", serviceIndicator, dataCenter), "./icon.png")
122-
if err != nil {
123-
fmt.Printf("[%s] ⚠️ Notification error: %v\n", dataCenter, err)
124-
}
125-
erroredDataCenters[dataCenter] = true
72+
if stats.IsAccessibleNow() {
73+
if erroredISPs[isp] {
74+
notifyRestored(service, isp)
12675
}
76+
erroredISPs[isp] = false
77+
ISPErrorCounts[isp] = 0
12778
} else {
128-
if erroredDataCenters[dataCenter] {
129-
err := beeep.Notify("🟢 Internet Restored", fmt.Sprintf("%s is reachable again from %s", serviceIndicator, dataCenter), "./icon.png")
130-
if err != nil {
131-
fmt.Printf("[%s] ⚠️ Notification error: %v\n", dataCenter, err)
132-
}
133-
fmt.Printf("[%s] 🟢 %s is reachable again\n", dataCenter, serviceIndicator)
79+
ISPErrorCounts[isp]++
80+
if ISPErrorCounts[isp] > maxISPError && !erroredISPs[isp] {
81+
notifyOutage(service, isp)
82+
erroredISPs[isp] = true
13483
}
135-
errorCounts[dataCenter] = 0
136-
erroredDataCenters[dataCenter] = false
13784
}
13885
}
13986

140-
func waitUntilNextMinute() {
141-
now := time.Now()
142-
delay := time.Until(now.Truncate(time.Minute).Add(time.Minute))
143-
time.Sleep(delay)
87+
// notifyOutage sends notification when service becomes unreachable
88+
func notifyOutage(service radar.Service, isp radar.Datacenter) {
89+
msg := fmt.Sprintf("%s unreachable from %s", service, isp)
90+
if err := beeep.Notify("🔴 Internet Outage", msg, "./icon.png"); err != nil {
91+
fmt.Printf("[%s] ❌ Notification error: %v", isp, err)
92+
}
93+
fmt.Printf("[%s] 🔴 %s outage detected", isp, service)
14494
}
14595

146-
func main() {
147-
fmt.Println("📡 Arvan Cloud Radar Monitor")
148-
serviceFlag := flag.Int("service", 0, "Service number to monitor (e.g. 1 for google, 2 for wikipedia...)")
149-
helpFlag := flag.Bool("help", false, "Show available services")
150-
flag.Parse()
151-
152-
if *helpFlag {
153-
printServices()
154-
return
96+
// notifyRestored sends notification when service is reachable again
97+
func notifyRestored(service radar.Service, isp radar.Datacenter) {
98+
msg := fmt.Sprintf("%s is reachable again from %s", service, isp)
99+
if err := beeep.Notify("🟢 Internet Restored", msg, "./icon.png"); err != nil {
100+
log.Printf("[%s] ❌ Notification error: %v", isp, err)
155101
}
102+
log.Printf("[%s] 🟢 %s restored", isp, service)
103+
}
156104

157-
if *serviceFlag > 0 && *serviceFlag <= len(availableServices) {
158-
serviceIndicator = availableServices[*serviceFlag-1]
159-
} else if *serviceFlag != 0 {
160-
fmt.Println("⚠️ Invalid service number. Use --help to see available options.")
161-
os.Exit(1)
162-
} else {
163-
serviceIndicator = chooseServiceInteractive()
105+
// printServices prints available services
106+
func printServices() {
107+
fmt.Println("Available services:")
108+
for _, s := range radar.AllServices {
109+
fmt.Printf(" - %s\n", s)
164110
}
165111

166-
fmt.Printf("✅ Monitoring service: %s\n", serviceIndicator)
167-
168-
jar, _ := cookiejar.New(nil)
169-
client := &http.Client{Jar: jar}
112+
}
170113

171-
waitUntilNextMinute()
172-
for {
173-
fmt.Printf("⏰ %s\n", time.Now().Format("15:04:05"))
174-
for _, dataCenter := range dataCenters {
175-
go func(dc string) {
176-
value, err := fetchData(client, dc)
177-
if err != nil {
178-
fmt.Printf("[%s] %v\n", dc, err)
179-
return
180-
}
181-
checkStatus(dc, value)
182-
}(dataCenter)
183-
}
184-
time.Sleep(1 * time.Minute)
185-
}
114+
// waitUntilNextMinute sleeps until next full minute
115+
func waitUntilNextMinute() {
116+
time.Sleep(time.Until(time.Now().Truncate(time.Minute).Add(time.Minute)))
186117
}

0 commit comments

Comments
 (0)