From cb72be6acae091a6b7a722111d4d781a5333e664 Mon Sep 17 00:00:00 2001
From: milarin <milaring@proton.me>
Date: Wed, 19 Jul 2023 17:35:51 +0200
Subject: [PATCH] refactoring

---
 .gitignore               |   2 +
 filter_changed_states.go |  11 +---
 go.mod                   |   6 +--
 go.sum                   |   6 ++-
 main.go                  |  41 ++++++--------
 mdstat.go                | 113 ++++++++++++++++++++++-----------------
 6 files changed, 91 insertions(+), 88 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5f868c1..6c5f7a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 raidcheck
+.env
+mdstat
\ No newline at end of file
diff --git a/filter_changed_states.go b/filter_changed_states.go
index aca8b7f..4f09259 100644
--- a/filter_changed_states.go
+++ b/filter_changed_states.go
@@ -2,8 +2,6 @@ package main
 
 import (
 	"reflect"
-
-	"git.tordarus.net/Tordarus/dockerhealth"
 )
 
 func filterChanges(in <-chan RaidState) <-chan RaidState {
@@ -14,17 +12,12 @@ func filterChanges(in <-chan RaidState) <-chan RaidState {
 
 		currentStates := map[string]RaidState{}
 
-		first := true
-
 		for state := range in {
-			if oldState, ok := currentStates[state.Name]; !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) {
-				dockerhealth.Healthy = first
+			oldState, ok := currentStates[state.Name]
+			if !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) {
 				currentStates[state.Name] = state
 				out <- state
-			} else {
-				dockerhealth.Healthy = true
 			}
-			first = false
 		}
 	}(in, out)
 
diff --git a/go.mod b/go.mod
index bdf6f54..5e35d3b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,9 @@
-module git.tordarus.net/Tordarus/raidcheck
+module git.milar.in/milarin/raidcheck
 
 go 1.17
 
 require (
-	git.tordarus.net/Tordarus/adverr v0.2.0
-	git.tordarus.net/Tordarus/dockerhealth v0.0.1
+	git.milar.in/milarin/adverr v1.1.0
+	git.milar.in/milarin/envvars/v2 v2.0.0
 	github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
 )
diff --git a/go.sum b/go.sum
index 83a6e6b..c3b1554 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
-git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA=
-git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw=
+git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
+git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
+git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE=
+git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY=
 git.tordarus.net/Tordarus/dockerhealth v0.0.1 h1:rxkwmCW5PDe9gbnXap7d3n5rK1Qyr6xpmJPZFom/ZXc=
 git.tordarus.net/Tordarus/dockerhealth v0.0.1/go.mod h1:U0IPsBJHAjqWgNyehWwGfYqTwPJgqBNhzk/eBpaZsnE=
 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
diff --git a/main.go b/main.go
index 6b37080..c7da792 100644
--- a/main.go
+++ b/main.go
@@ -3,56 +3,47 @@ package main
 import (
 	"fmt"
 	"os"
-	"strconv"
 	"time"
 
-	"git.tordarus.net/Tordarus/adverr"
-	"git.tordarus.net/Tordarus/dockerhealth"
+	"git.milar.in/milarin/adverr"
+	"git.milar.in/milarin/envvars/v2"
 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
 )
 
 var (
-	interval      = 1 * time.Minute
-	telegramToken string
-	chatID        int64
+	MdstatFile   = envvars.String("MDSTAT_FILE", "/proc/mdstat")
+	PollInterval = envvars.Duration("POLL_INTERVAL", time.Minute)
+	BotToken     = envvars.String("TELEGRAM_API_TOKEN", "")
+	ChatID       = envvars.Int64("TELEGRAM_CHAT_ID", 0)
 )
 
 func main() {
-	if i, ok := os.LookupEnv("POLL_INTERVAL"); ok {
-		if interval2, err := time.ParseDuration(i); err == nil {
-			interval = interval2
-		}
+	if BotToken == "" {
+		fmt.Fprintln(os.Stderr, "$TELEGRAM_API_TOKEN not set")
+		os.Exit(1)
 	}
 
-	if tt, ok := os.LookupEnv("TELEGRAM_API_TOKEN"); ok {
-		telegramToken = tt
+	if ChatID == 0 {
+		fmt.Fprintln(os.Stderr, "$TELEGRAM_CHAT_ID not set")
+		os.Exit(1)
 	}
 
-	if id, ok := os.LookupEnv("TELEGRAM_CHAT_ID"); ok {
-		if chatId2, err := strconv.Atoi(id); err == nil {
-			chatID = int64(chatId2)
-		}
-	}
-
-	bot, err := tgbotapi.NewBotAPI(telegramToken)
+	bot, err := tgbotapi.NewBotAPI(BotToken)
 	if err != nil {
 		adverr.Fatalln(err, 1)
 	}
 
-	for state := range filterChanges(mdstat()) {
+	for state := range filterChanges(mdstat(MdstatFile, PollInterval)) {
 		str := state.String()
 		fmt.Println(str)
-		message := tgbotapi.NewMessage(chatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str))
+		message := tgbotapi.NewMessage(ChatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str))
 		message.Entities = append(message.Entities, tgbotapi.MessageEntity{
 			Type:   "code",
 			Offset: len("Der Zustand des RAID Systems %s hat sich verändert:\n\n"),
 			Length: len(str),
 		})
-		_, err := bot.Send(message)
-		if err != nil {
+		if _, err := bot.Send(message); err != nil {
 			adverr.Println(err)
 		}
 	}
-
-	dockerhealth.Healthy = false
 }
diff --git a/mdstat.go b/mdstat.go
index 119b5c8..87282cf 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -16,59 +16,74 @@ var (
 	deviceRegex = regexp.MustCompile(`^([\w\d]+)\[(\d+)\]$`)
 )
 
-func mdstat() <-chan RaidState {
+func mdstat(file string, pollInterval time.Duration) <-chan RaidState {
 	ch := make(chan RaidState, 4)
-
-	go func(out chan<- RaidState) {
-		defer close(ch)
-
-		for {
-			data, err := os.ReadFile("/proc/mdstat")
-			if err != nil {
-				panic(err)
-			}
-			lines := strings.Split(string(data), "\n")
-
-			state := RaidState{}
-			for _, line := range lines {
-				if match := firstLineRegex.FindStringSubmatch(line); match != nil {
-					state.Name = match[1]
-					state.State = match[2]
-					state.Level = match[3]
-
-					devices := strings.Split(match[4], " ")
-					state.Devices = make([]string, len(devices))
-					for _, device := range devices {
-						if match := deviceRegex.FindStringSubmatch(device); match != nil {
-							deviceIndex, _ := strconv.Atoi(match[2])
-							state.Devices[deviceIndex] = match[1]
-						}
-					}
-				} else if match := secondLineRegex.FindStringSubmatch(line); match != nil {
-					state.RegisteredDeviceCount, _ = strconv.Atoi(match[1])
-					state.UsedDeviceCount, _ = strconv.Atoi(match[2])
-					state.DevicesUp = map[string]bool{}
-					for i, rn := range match[3] {
-						state.DevicesUp[state.Devices[i]] = rn == 'U'
-					}
-				} else if match := actionLineRegex.FindStringSubmatch(line); match != nil {
-					state.Action = new(RaidAction)
-					state.Action.Name = match[1]
-					state.Action.Progress, _ = strconv.ParseFloat(match[2], 64)
-					state.Action.Duration = parseFinished(match[3], match[4])
-					state.Action.DurationFormatted = state.Action.Duration.String()
-					state.Action.Finished = time.Now().Add(state.Action.Duration)
-				}
-			}
-
-			out <- state
-			time.Sleep(time.Minute)
-		}
-	}(ch)
-
+	go parseMdstatOutput(file, pollInterval, ch)
 	return ch
 }
 
+func parseMdstatOutput(file string, pollInterval time.Duration, out chan<- RaidState) {
+	defer close(out)
+
+	ticker := time.NewTicker(pollInterval)
+	for range ticker.C {
+		data, err := os.ReadFile(file)
+		if err != nil {
+			panic(err)
+		}
+
+		lines := strings.Split(string(data), "\n")
+		state := RaidState{}
+		for _, line := range lines {
+			parseLine(&state, line)
+		}
+
+		out <- state
+	}
+}
+
+func parseLine(state *RaidState, line string) {
+	if matches := firstLineRegex.FindStringSubmatch(line); matches != nil {
+		parseFirstLine(state, matches)
+	} else if matches := secondLineRegex.FindStringSubmatch(line); matches != nil {
+		parseSecondLine(state, matches)
+	} else if match := actionLineRegex.FindStringSubmatch(line); match != nil {
+		parseActionLine(state, matches)
+	}
+}
+
+func parseFirstLine(state *RaidState, matches []string) {
+	state.Name = matches[1]
+	state.State = matches[2]
+	state.Level = matches[3]
+
+	devices := strings.Split(matches[4], " ")
+	state.Devices = make([]string, len(devices))
+	for deviceIndex, device := range devices {
+		if match := deviceRegex.FindStringSubmatch(device); match != nil {
+			state.Devices[deviceIndex] = match[1]
+		}
+	}
+}
+
+func parseSecondLine(state *RaidState, matches []string) {
+	state.RegisteredDeviceCount, _ = strconv.Atoi(matches[1])
+	state.UsedDeviceCount, _ = strconv.Atoi(matches[2])
+	state.DevicesUp = map[string]bool{}
+	for i, rn := range matches[3] {
+		state.DevicesUp[state.Devices[i]] = rn == 'U'
+	}
+}
+
+func parseActionLine(state *RaidState, matches []string) {
+	state.Action = new(RaidAction)
+	state.Action.Name = matches[1]
+	state.Action.Progress, _ = strconv.ParseFloat(matches[2], 64)
+	state.Action.Duration = parseFinished(matches[3], matches[4])
+	state.Action.DurationFormatted = state.Action.Duration.String()
+	state.Action.Finished = time.Now().Add(state.Action.Duration)
+}
+
 func parseFinished(value, unit string) time.Duration {
 	switch unit {
 	case "sec":