From 5c818fc3d929ba9424e0f59f5dc7d562dc9a5a8d Mon Sep 17 00:00:00 2001 From: sloum Date: Sat, 21 Mar 2020 23:07:28 -0700 Subject: [PATCH] Basic steps working --- main.go | 188 +++++++++++++++++++++++++++++++++++++++++++++++++-- spacewalk.md | 2 +- 2 files changed, 185 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 3081a86..27c4c5b 100644 --- a/main.go +++ b/main.go @@ -2,12 +2,19 @@ package main import ( "bufio" + "bytes" + "crypto/md5" + "crypto/tls" "encoding/csv" "fmt" + "io" + "io/ioutil" "os" "os/user" "path/filepath" + "sort" "strings" + "time" ) var swPath, fPath string @@ -137,6 +144,119 @@ func readManifest() [][]string { return records } +func writeFlight(flight string, records [][]string) error { + p := filepath.Join(fPath, flight) + f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + w := csv.NewWriter(f) + w.WriteAll(records) + if err := w.Error(); err != nil { + return err + } + return nil +} + +func readFlightLog(flight string) [][]string { + p := filepath.Join(fPath, flight) + f, err := os.Open(p) + ifErrExit(err, "Could not open flight log") + defer f.Close() + r := csv.NewReader(f) + records, err := r.ReadAll() + ifErrExit(err, "Could not read from flight log") + return records +} + +func findItemRow(name string, position int, csv [][]string) []string { + for _, row := range csv { + if len(row)-1 < position { + earlyExit("Invalid array position passed to findItemRow") + } + if row[position] == name { + return row + } + } + earlyExit(fmt.Sprintf("Item %q was not found", name)) + return []string{} +} + +func retrieve(addr string) (string, error) { + addr = strings.TrimSpace(addr) + noprot := strings.Replace(addr, "gemini://", "", 1) + hostResource := strings.SplitN(noprot, "/", 2) + if strings.LastIndex(hostResource[0], ":") == -1 { + hostResource[0] = hostResource[0] + "1965" + } + conf := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", hostResource[0], conf) + if err != nil { + return "", fmt.Errorf("TLS Dial Error: %s", err.Error()) + } + + defer conn.Close() + + send := "gemini://" + hostResource[0] + "/" + hostResource[1] + "\r\n" + + _, err = conn.Write([]byte(send)) + if err != nil { + return "", err + } + + result, err := ioutil.ReadAll(conn) + if err != nil { + return "", err + } + + resp := strings.SplitN(string(result), "\r\n", 2) + if resp[0][0] != '2' || len(resp) < 2 { + return "", fmt.Errorf("Invalid server response") + } + h := md5.New() + io.WriteString(h, resp[1]) + + return string(h.Sum(nil)), nil +} + +func checkForUpdate(c []string, r chan []string) { + chksum, err := retrieve(c[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "Error updating capsule %q: %s\n", c[0], err.Error()) + r <- c + return + } + + if chksum == c[2] { + r <- c + return + } + + c[2] = chksum + currentTime := time.Now() + c[3] = currentTime.Format("2006-01-02") + r <- c +} + +func getHeaderFooter(addr string) []byte { + addr = strings.TrimSpace(addr) + if addr == "none" { + return make([]byte, 0) + } + + content, err := ioutil.ReadFile(addr) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading %q\n", addr) + return make([]byte, 0) + } + + return content +} + /////////////////////////////// // Command Execution ////////////////////////////// @@ -152,6 +272,7 @@ func create(flight string) { } fmt.Printf("Creating flight: %q\n", flight) + usr, _ := user.Current() happy := false for !happy { @@ -161,16 +282,20 @@ func create(flight string) { fmt.Println("Launch path cannot be empty") continue } + lp = strings.Replace(lp, "~", usr.HomeDir, -1) + hp, err = getLine("Enter the header path, or leave blank for none: ") ifErrExit(err, "Error reading from stdin") if hp == "" { hp = "none" } + hp = strings.Replace(hp, "~", usr.HomeDir, -1) fp, err = getLine("Enter the footer path, or leave blank for none: ") ifErrExit(err, "Error reading from stdin") if fp == "" { fp = "none" } + fp = strings.Replace(fp, "~", usr.HomeDir, -1) fmt.Println("Are you happy with the following:") fmt.Printf("Launch path: %s\nHeader path: %s\nFooter path: %s\n", lp, hp, fp) yesNo, err := getLine("Type 'yes' to accept, anything else to redo: ") @@ -203,14 +328,14 @@ func remove(flight string) { } func add(flight, url, title string) { - p := filepath.Join(fPath, "flights", flight) + p := filepath.Join(fPath, flight) f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY, 0644) ifErrExit(err, fmt.Sprintf("Could not open flight log for %s", flight)) defer f.Close() ln := fmt.Sprintf("%s, %s, %s, %s\n", title, url, "nil", "0") _, err = f.WriteString(ln) ifErrExit(err, "Unable to save new capsule to data file") - fmt.Printf("Capsule %q added to %s's flight log", title, flight) + fmt.Printf("Capsule %q added to %s's flight log\n", title, flight) } func del(flight, item string) { @@ -218,11 +343,57 @@ func del(flight, item string) { } func launchFlights() { - fmt.Println("Hello") + records := readManifest() + fmt.Println("Launching all flights") + for _, fl := range records { + launchFlight(fl[0]) + } + fmt.Println("All flights have been launched and statuses displayed") } func launchFlight(flight string) { - fmt.Println(flight) + fmt.Printf("Launching %s\n", flight) + manifest := findItemRow(flight, 0, readManifest()) + data := readFlightLog(flight) + ch := make(chan []string) + count := 0 + for _, capsule := range data { + go checkForUpdate(capsule, ch) + count++ + } + + updatedData := make([][]string, 0, count) + + for count > 0 { + row := <-ch + updatedData = append(updatedData, row) + count-- + } + + sort.SliceStable(updatedData, func(i, j int) bool { return updatedData[i][3] > updatedData[j][3] }) + + err := writeFlight(flight, updatedData) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: Unable to write %s to file. Continuing with launch, but data will be out of date at next launch. %s\n", flight, err.Error()) + } + + header := getHeaderFooter(manifest[2]) + footer := getHeaderFooter(manifest[3]) + + var out bytes.Buffer + out.Write(header) + + for _, capsule := range updatedData { + ln := fmt.Sprintf("=> %s %s - %s\r\n", capsule[1], capsule[3], capsule[0]) + out.WriteString(ln) + } + + out.Write(footer) + + err = ioutil.WriteFile(strings.TrimSpace(manifest[1]), out.Bytes(), 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR, cannot launch %q: %s\n", flight, err.Error()) + } } func showFlights(flight string) { @@ -236,6 +407,15 @@ func showFlights(flight string) { fmt.Printf("\033[1mHeader Path:\033[0m %s\n", row[2]) fmt.Printf("\033[1mFooter Path:\033[0m %s\n--\n", row[3]) } + + if flight != "" { + fmt.Println() + r := readFlightLog(flight) + for _, fl := range r { + fmt.Printf("\033[1m%s\033[0m: %s\n", fl[0], fl[1]) + } + + } } func main() { diff --git a/spacewalk.md b/spacewalk.md index dbe7048..1534e80 100644 --- a/spacewalk.md +++ b/spacewalk.md @@ -54,7 +54,7 @@ Checks for updates and builds the directory capsule. Takes a flight name to laun Paths: $XDG\_DATA\_HOME/spacewalk/flight-config || ~/.local/share/spacewalk/flight-config Format: csv -Structure: flight-name, flight-slug, launch-path, header-path, footer-path +Structure: flight-name, launch-path, header-path, footer-path ### flight data