Compare commits
2 Commits
33938a31e8
...
56ca9d0cb8
Author | SHA1 | Date | |
---|---|---|---|
56ca9d0cb8 | |||
2aa18e77e3 |
154
main.go
154
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"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -34,6 +35,7 @@ var (
|
||||
password string
|
||||
dataDir string
|
||||
dataPath string
|
||||
cacheDir string
|
||||
cachePath string
|
||||
scanner bufio.Scanner
|
||||
)
|
||||
@@ -48,7 +50,10 @@ type Note struct {
|
||||
|
||||
func check(err error, format string, a ...any) bool {
|
||||
if err != nil {
|
||||
log.Fatalf(format, a, err)
|
||||
if writeErr := os.WriteFile("error.log", []byte(fmt.Sprintf(format+": %s", a, err)), 0600); writeErr != nil {
|
||||
log.Fatalf("Error writing error '%s' to error.log: %s\n", err, writeErr)
|
||||
}
|
||||
log.Fatalf("\033[31m"+format+"\033[0m: %s\n", a, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -106,14 +111,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 +138,58 @@ 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")
|
||||
check(err, "Error getting stdin pipe for 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", " "))
|
||||
cacheJsonBody := load(db)
|
||||
listAll(cacheJsonBody, stdin)
|
||||
fetchJsonBody := fetch()
|
||||
|
||||
for i := range fetchJsonBody {
|
||||
if !slices.Contains(cacheJsonBody, fetchJsonBody[i]) {
|
||||
go list(fetchJsonBody[i][1], stdin, Note{})
|
||||
}
|
||||
}
|
||||
|
||||
cache(db, fetchJsonBody)
|
||||
fzf.Wait()
|
||||
|
||||
default:
|
||||
login()
|
||||
}
|
||||
}
|
||||
|
||||
func cache(db *sql.DB, jsonData [][2]string) {
|
||||
for i := range jsonData {
|
||||
var id string
|
||||
err := db.QueryRow("SELECT id FROM notes WHERE id = ?", jsonData[i][0]).Scan(&id)
|
||||
if err != sql.ErrNoRows {
|
||||
check(err, "Error scanning cache database at %s for duplicates", cachePath)
|
||||
continue
|
||||
}
|
||||
|
||||
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 +204,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 +237,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 +251,60 @@ 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 listAll(jsonBody [][2]string, fzfStdin io.WriteCloser) {
|
||||
decryptedBody := make([]Note, len(jsonBody))
|
||||
for i := range jsonBody {
|
||||
go list(jsonBody[i][1], fzfStdin, decryptedBody[i])
|
||||
}
|
||||
}
|
||||
|
||||
func list(jsonBody string, fzfStdin io.WriteCloser, decryptedBody Note) {
|
||||
err := json.Unmarshal([]byte(decrypt(jsonBody, password)), &decryptedBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(decryptedBody.Title) > 0 && len(decryptedBody.Body) > 0 {
|
||||
fmt.Fprintf(fzfStdin, "%s | %s\n", decryptedBody.Title, strings.ReplaceAll(decryptedBody.Body, "\n", " "))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user