summary refs log tree commit diff stats
path: root/cmd
diff options
context:
space:
mode:
authorAndinus <andinus@nand.sh>2020-03-25 00:13:51 +0530
committerAndinus <andinus@nand.sh>2020-03-25 00:13:51 +0530
commit3ee3715ae7777dd6c1804974e2e293406fd1fe54 (patch)
treef035e76ff9ce48d03299381d03906eaff76140dc /cmd
parent28e35c0b0b996e17bd94a5b9e78fda90086ba85e (diff)
downloadcetus-3ee3715ae7777dd6c1804974e2e293406fd1fe54.tar.gz
Add apod support & fix errors
Diffstat (limited to 'cmd')
-rw-r--r--cmd/cetus/apod.go176
-rw-r--r--cmd/cetus/env.go18
-rw-r--r--cmd/cetus/main.go96
-rw-r--r--cmd/cetus/usage.go15
4 files changed, 305 insertions, 0 deletions
diff --git a/cmd/cetus/apod.go b/cmd/cetus/apod.go
new file mode 100644
index 0000000..c1af115
--- /dev/null
+++ b/cmd/cetus/apod.go
@@ -0,0 +1,176 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+
+	"tildegit.org/andinus/cetus/apod"
+	"tildegit.org/andinus/cetus/background"
+	"tildegit.org/andinus/cetus/cache"
+	"tildegit.org/andinus/cetus/notification"
+)
+
+var (
+	err     error
+	body    string
+	file    string
+	reqInfo map[string]string
+)
+
+func execAPOD() {
+	apodApi := getEnv("APOD_API", "https://api.nasa.gov/planetary/apod")
+	apodKey := getEnv("APOD_KEY", "DEMO_KEY")
+
+	// reqInfo holds all the parameters that needs to be sent with
+	// the request. GetJson() will pack apiKey & date in params
+	// map before sending it to another function. Adding params
+	// here will not change the behaviour of the function, changes
+	// have to be made in GetJson() too.
+	reqInfo = make(map[string]string)
+	reqInfo["api"] = apodApi
+	reqInfo["apiKey"] = apodKey
+	reqInfo["date"] = apodDate
+
+	if random {
+		reqInfo["date"] = apod.RandDate()
+	}
+
+	cacheDir := fmt.Sprintf("%s/%s", cache.GetDir(), "apod")
+	os.MkdirAll(cacheDir, os.ModePerm)
+
+	// Check if the file is available locally, if it is then don't
+	// download it again and get it from disk
+	file = fmt.Sprintf("%s/%s.json", cacheDir, reqInfo["date"])
+
+	if _, err := os.Stat(file); err == nil {
+		data, err := ioutil.ReadFile(file)
+
+		// Not being able to read from the cache file is a
+		// small error and the program shouldn't exit but
+		// should continue after printing the log so that the
+		// user can investigate it later.
+		if err != nil {
+			err = fmt.Errorf("%s%s\n%s",
+				"apod.go: failed to read file to data: ", file,
+				err.Error())
+			log.Println(err)
+			dlAndCacheBody()
+		}
+		body = string(data)
+
+	} else if os.IsNotExist(err) {
+		dlAndCacheBody()
+
+	} else {
+		// If file existed then that is handled by the if
+		// block, if it didn't exist then that is handled by
+		// the else if block. If we reach here then that means
+		// it's Schrödinger's file & something else went
+		// wrong.
+		log.Fatal(err)
+	}
+
+	if dump {
+		fmt.Printf(body)
+	}
+
+	res := apod.APOD{}
+	err = apod.UnmarshalJson(&res, body)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// res.Msg will be returned when there is error on user input
+	// or the api server.
+	if len(res.Msg) != 0 {
+		fmt.Printf("Message: %s", res.Msg)
+		os.Exit(1)
+	}
+
+	// Send a desktop notification if notify flag was passed.
+	if notify {
+		n := notification.Notif{}
+		n.Title = res.Title
+		n.Message = fmt.Sprintf("%s\n\n%s",
+			res.Date,
+			res.Explanation)
+
+		err = n.Notify()
+		if err != nil {
+			log.Println(err)
+		}
+	}
+
+	if print {
+		fmt.Printf("Title: %s\n\n", res.Title)
+		if len(res.Copyright) != 0 {
+			fmt.Printf("Copyright: %s\n", res.Copyright)
+		}
+		fmt.Printf("Date: %s\n\n", res.Date)
+		fmt.Printf("Media Type: %s\n", res.MediaType)
+		if res.MediaType == "image" {
+			fmt.Printf("URL: %s\n\n", res.HDURL)
+		} else {
+			fmt.Printf("URL: %s\n\n", res.URL)
+		}
+		fmt.Printf("Explanation: %s\n", res.Explanation)
+	}
+
+	// Proceed only if the command was set because if it was fetch
+	// then it's already finished & should exit now.
+	if os.Args[1] == "fetch" {
+		os.Exit(0)
+	}
+
+	// Try to set background only if the media type is an image.
+	// First it downloads the image to the cache directory and
+	// then tries to set it with feh. If the download fails then
+	// it exits with a non-zero exit code.
+	if res.MediaType != "image" {
+		os.Exit(0)
+	}
+	imgFile := fmt.Sprintf("%s/%s", cacheDir, res.Title)
+
+	// Check if the file is available locally, if it is then don't
+	// download it again and set it from disk.
+	if _, err := os.Stat(imgFile); os.IsNotExist(err) {
+		err = background.Download(imgFile, res.HDURL)
+		if err != nil {
+			log.Fatal(err)
+		}
+	} else {
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	err = background.SetFromFile(imgFile)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func dlAndCacheBody() {
+	body, err = apod.GetJson(reqInfo)
+	if err != nil {
+		err = fmt.Errorf("%s\n%s",
+			"apod.go: failed to get json response from api",
+			err.Error())
+		log.Fatal(err)
+	}
+
+	// Write body to the cache so that it can be read later.
+	err = ioutil.WriteFile(file, []byte(body), 0644)
+
+	// Not being able to write to the cache file is a small error
+	// and the program shouldn't exit but should continue after
+	// printing the log so that the user can investigate it later.
+	if err != nil {
+		err = fmt.Errorf("%s\n%s",
+			"apod.go: failed to write body to file: ", file,
+			err.Error())
+		log.Println(err)
+	}
+}
diff --git a/cmd/cetus/env.go b/cmd/cetus/env.go
new file mode 100644
index 0000000..9288c35
--- /dev/null
+++ b/cmd/cetus/env.go
@@ -0,0 +1,18 @@
+package main
+
+import "os"
+
+// getEnv will check if the the key exists, if it does then it'll
+// return the value otherwise it will return fallback string.
+func getEnv(key, fallback string) string {
+	// We use os.LookupEnv instead of using os.GetEnv and checking
+	// if the length equals 0 because environment variable can be
+	// set and be of length 0. User could've set key="" which
+	// means the variable was set but the length is 0. There is no
+	// reason why user would want to do this over here though.
+	value, exists := os.LookupEnv(key)
+	if !exists {
+		value = fallback
+	}
+	return value
+}
diff --git a/cmd/cetus/main.go b/cmd/cetus/main.go
new file mode 100644
index 0000000..c7ffd40
--- /dev/null
+++ b/cmd/cetus/main.go
@@ -0,0 +1,96 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"math/rand"
+	"os"
+	"time"
+)
+
+var (
+	version string = "v0.6.0"
+	dump    bool
+	random  bool
+	notify  bool
+	print   bool
+
+	apodDate string
+	// bpodApi string = getEnv("BPOD_API", "https://www.bing.com/HPImageArchive.aspx")
+)
+
+func main() {
+
+	// Early Check: If command was not passed then print usage and
+	// exit. Later command & service both are checked, this check
+	// is for version command. If not checked then running cetus
+	// without any args will fail because os.Args[1] will panic
+	// the program & produce runtime error.
+	if len(os.Args) == 1 {
+		printUsage()
+		os.Exit(0)
+	}
+
+	parseArgs()
+}
+
+// parseArgs will be parsing the arguments, it will verify if they are
+// correct. Flag values are also set by parseArgs.
+func parseArgs() {
+	// Running just `cetus` would've paniced the program if length
+	// of os.Args was not checked beforehand because there would
+	// be no os.Args[1].
+	switch os.Args[1] {
+	case "version", "-version", "--version", "-v":
+		fmt.Printf("Cetus %s\n", version)
+		os.Exit(0)
+
+	case "help", "-help", "--help", "-h":
+		// If help was passed then the program shouldn't exit
+		// with non-zero error code.
+		printUsage()
+		os.Exit(0)
+
+	case "set", "fetch":
+		// If command & service was not passed then print
+		// usage and exit.
+		if len(os.Args) < 3 {
+			printUsage()
+			os.Exit(1)
+		}
+
+	default:
+		fmt.Printf("Invalid command: %q\n", os.Args[1])
+		printUsage()
+		os.Exit(1)
+	}
+
+	rand.Seed(time.Now().Unix())
+
+	// If the program has reached this far then that means a valid
+	// command was passed & now we should check if a valid service
+	// was passed and parse the flags.
+	cetus := flag.NewFlagSet("cetus", flag.ExitOnError)
+
+	// We first declare common flags then service specific flags.
+	cetus.BoolVar(&dump, "dump", false, "Dump the response")
+	cetus.BoolVar(&notify, "notify", false, "Send a desktop notification with info")
+	cetus.BoolVar(&print, "print", false, "Print information")
+	cetus.BoolVar(&random, "random", false, "Choose a random image")
+
+	switch os.Args[2] {
+	case "apod", "nasa":
+		defDate := time.Now().UTC().Format("2006-01-02")
+		cetus.StringVar(&apodDate, "date", defDate, "Date of NASA APOD to retrieve")
+		cetus.Parse(os.Args[3:])
+
+		execAPOD()
+	// case "bpod", "bing":
+	// 	parseFlags()
+	// 	execBPOD()
+	default:
+		fmt.Printf("Invalid service: %q\n", os.Args[2])
+		printUsage()
+		os.Exit(1)
+	}
+}
diff --git a/cmd/cetus/usage.go b/cmd/cetus/usage.go
new file mode 100644
index 0000000..e9e2f71
--- /dev/null
+++ b/cmd/cetus/usage.go
@@ -0,0 +1,15 @@
+package main
+
+import "fmt"
+
+func printUsage() {
+	fmt.Println("Usage: cetus <command> <service> [<flags>]")
+	fmt.Println("\nCommands: ")
+	fmt.Println(" set     Set the background")
+	fmt.Println(" fetch   Fetch the response only")
+	fmt.Println(" help    Print help")
+	fmt.Println(" version Print Cetus version")
+	fmt.Println("\nServices: ")
+	fmt.Println(" apod   NASA Astronomy Picture of the Day")
+	fmt.Println(" bpod   Bing Photo of the Day")
+}