diff --git a/main.go b/main.go index 27e1c13..f111544 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,13 @@ package main import ( - // "database/sql" "bufio" "bytes" "crypto/aes" "crypto/cipher" "crypto/pbkdf2" "crypto/sha256" + "database/sql" "encoding/base64" "encoding/json" "errors" @@ -34,6 +34,7 @@ var ( password string dataDir string dataPath string + cacheDir string cachePath string scanner bufio.Scanner ) @@ -48,7 +49,7 @@ type Note struct { func check(err error, format string, a ...any) bool { if err != nil { - log.Fatalf(format, a, err) + log.Fatalf("\033[31m"+format+"\033[0m: ", a, err) return false } return true @@ -106,14 +107,25 @@ func main() { if len(cachePath) < 1 { cachePath = home + "/.cache" } + cacheDir = cachePath + "/notes" cachePath += "/notes/notes.db" dataDir = dataPath + "/notes" dataPath += "/notes/userinfo.json" - // db, err := sql.Open("sqlite", cachePath) - // if err != nil { - // log.Fatal("Error opening sqlite database for notes cache at ", cachePath, ": ", err) - // } + err := os.Mkdir(cacheDir, 0700) + if !errors.Is(err, os.ErrExist) { + check(err, "Error creating cache directory") + } + db, err := sql.Open("sqlite", cachePath) + check(err, "Error opening cache database at '%s'", cachePath) + check(db.Ping(), "Ping of cache database failed!") + db.Exec(` +CREATE TABLE notes ( + id TEXT PRIMARY KEY, + data TEXT +); + `) + args := os.Args[1:] var command string if len(args) < 1 { @@ -122,49 +134,38 @@ func main() { command = args[0] } switch command { - case "l", "ls", "list", "search", "find": + case "ls": userDataBytes, err := os.ReadFile(dataPath) if errors.Is(err, fs.ErrNotExist) { login() } - check(err, "Error reading user data at '%s': %s", dataPath) + check(err, "Error reading user data at '%s'", dataPath) var userData []string err = json.Unmarshal(userDataBytes, &userData) - check(err, "Error unmarshalling user data: %s") + check(err, "Error unmarshalling user data") serverAddress, username, password = userData[0], userData[1], userData[2] - - response, err := http.Get(fmt.Sprintf("https://%s/get?user=%s", serverAddress, url.QueryEscape(username))) - check(err, "Error getting notes from '%s' as '%s': %s", serverAddress, username) - defer response.Body.Close() - body, err := io.ReadAll(response.Body) - check(err, "Error reading response body while getting notes: %s") - var jsonBody [][2]string - err = json.Unmarshal(body, &jsonBody) - check(err, "Error unmarshalling response body: %s") - - fzf := exec.Command("fzf") - stdin, err := fzf.StdinPipe() - check(err, "Error executing fzf: %s") - check(fzf.Start(), "Error starting fzf: %s") - - decryptedBody := make([]Note, len(jsonBody)) - for i := range len(jsonBody) { - err := json.Unmarshal([]byte(decrypt(jsonBody[i][1], password)), &decryptedBody[i]) - if err != nil { - continue - } - if len(decryptedBody[i].Title) > 0 && len(decryptedBody[i].Body) > 0 { - fmt.Fprintf(stdin, "%s | %s\n", decryptedBody[i].Title, strings.ReplaceAll(decryptedBody[i].Body, "\n", " ")) - } - } - fzf.Wait() - + jsonBody := load(db) + // jsonBody := fetch() + // cache(db, jsonBody) + fmt.Println(jsonBody) + list(jsonBody) default: login() } } +func cache(db *sql.DB, jsonData [][2]string) { + for i := range len(jsonData) { + stmt, err := db.Prepare("INSERT INTO notes VALUES(?, ?)") + check(err, "Error preparing to insert note into cache database at %s", cachePath) + defer stmt.Close() + + _, err = stmt.Exec(jsonData[i][0], jsonData[i][1]) + check(err, "Error executing insert note into cache database at %s", cachePath) + } +} + func login() { fmt.Println("Welcome to Notes! To get started, choose your server and user.") fmt.Print("Server Address [notes.miningtcup.me]: ") @@ -179,24 +180,24 @@ func login() { fmt.Print("Password: ") passwordBytes, err := readPassword() - check(err, "Error reading user input (password): %s") + check(err, "Error reading user input (password)") password = url.QueryEscape(string(passwordBytes)) jsonData, err := json.Marshal([]string{serverAddress, username, password}) - check(err, "Error marshalling server address and credentials: %s") + check(err, "Error marshalling server address and credentials") err = os.Mkdir(dataDir, 0700) if !errors.Is(err, fs.ErrExist) { - check(err, "Error creating data directory at '%s': %s", dataDir) + check(err, "Error creating data directory at '%s'", dataDir) } err = os.WriteFile(dataPath, jsonData, 0600) - check(err, "Error writing to '%s': ", dataPath) + check(err, "Error writing to '%s'", dataPath) fmt.Println("\nAwesome! To get started, run 'notes add' or 'notes list'") } func decrypt(data string, password string) string { dataBytes, err := base64.StdEncoding.DecodeString(data) - check(err, "Error decoding base64 data: %s") + check(err, "Error decoding base64 data") if len(dataBytes) < 32 { return "" @@ -212,9 +213,9 @@ func decrypt(data string, password string) string { } key, err := pbkdf2.Key(sha256.New, password, salt, 65536, 32) - check(err, "Error deriving pbkdf2-sha256 key from password: %s") + check(err, "Error deriving pbkdf2-sha256 key from password") block, err := aes.NewCipher(key) - check(err, "Error creating AES key: %s") + check(err, "Error creating AES key") mode := cipher.NewCBCDecrypter(block, iv) plaintext := make([]byte, len(ciphertext)) @@ -226,3 +227,62 @@ func decrypt(data string, password string) string { } return string(strippedPlaintext) } + +func fetch() [][2]string { + response, err := http.Get(fmt.Sprintf("https://%s/get?user=%s", serverAddress, url.QueryEscape(username))) + check(err, "Error getting notes from '%s' as '%s'", serverAddress, username) + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + check(err, "Error reading response body while getting notes") + + var jsonBody [][2]string + err = json.Unmarshal(body, &jsonBody) + check(err, "Error unmarshalling response body") + + return jsonBody +} + +func load(db *sql.DB) [][2]string { + var rowCount int + err := db.QueryRow("SELECT COUNT(*) FROM notes").Scan(&rowCount) + if err != nil { + log.Fatal(err) + } + const limit = 100 + if rowCount > limit { // change this if there's ever an option to increase limit + rowCount = limit // change this if there's ever an option to increase limit + } + + rows, err := db.Query("SELECT * FROM notes LIMIT ?", rowCount) + check(err, "Error querying cache database", err) + defer rows.Close() + + notes := make([][2]string, rowCount) + + for i := 0; rows.Next(); i++ { + var currentRow [2]string + check(rows.Scan(¤tRow[0], ¤tRow[1]), "Error scanning cache database rows") + notes[i] = currentRow + } + + return notes +} + +func list(jsonBody [][2]string) { + fzf := exec.Command("fzf") + stdin, err := fzf.StdinPipe() + check(err, "Error executing fzf") + check(fzf.Start(), "Error starting fzf") + + decryptedBody := make([]Note, len(jsonBody)) + for i := range len(jsonBody) { + err := json.Unmarshal([]byte(decrypt(jsonBody[i][1], password)), &decryptedBody[i]) + if err != nil { + continue + } + if len(decryptedBody[i].Title) > 0 && len(decryptedBody[i].Body) > 0 { + fmt.Fprintf(stdin, "%s | %s\n", decryptedBody[i].Title, strings.ReplaceAll(decryptedBody[i].Body, "\n", " ")) + } + } + fzf.Wait() +}