From f86c2d8153b411de95f5f0ecd3ad65d3e7737e90 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sat, 11 May 2024 20:52:38 -0400 Subject: [PATCH] Initial groundwork for native sync support --- main.go | 3 +++ src/backend/configParser.go | 29 ++++++++++++++--------- src/backend/gpg.go | 2 +- src/cli/1globals.go | 2 -- src/cli/edit.go | 2 +- src/cli/entryList.go | 35 ++-------------------------- src/cli/init.go | 23 ++++++++++++++++++- src/cli/printInfo.go | 4 ++-- src/sync/1globals.go | 6 +++++ src/sync/sync.go | 42 +++++++++++++++++++++++++++++++++ src/sync/walk.go | 46 +++++++++++++++++++++++++++++++++++++ 11 files changed, 143 insertions(+), 51 deletions(-) create mode 100644 src/sync/1globals.go create mode 100644 src/sync/sync.go create mode 100644 src/sync/walk.go diff --git a/main.go b/main.go index 0b0c8cc..99eb3a3 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/MUTN/src/cli" + "github.com/rwinkhart/MUTN/src/sync" "os" "strings" ) @@ -162,6 +163,8 @@ func main() { fmt.Println(backend.AnsiError + "\"sync\" is not yet implemented" + backend.AnsiReset) } os.Exit(0) + case "synctest": // TODO Remove after native sync is implemented + sync.RunJob(true) case "init": cli.TempInitCli() case "tweak": diff --git a/src/backend/configParser.go b/src/backend/configParser.go index a5d1002..b897288 100644 --- a/src/backend/configParser.go +++ b/src/backend/configParser.go @@ -6,10 +6,11 @@ import ( "os" ) -// ReadConfig reads the libmutton.ini file and returns a map of the requested values -// requires readMap: a map of section names to key names (indicates requested values) -// returns configMap: a map of key names to values (sections are irrelevant) -func ReadConfig(readKeys []string) []string { +// ReadConfig reads the libmutton.ini file and returns a slice of values for the specified keys +// requires readKeys: a slice of key names (indicates requested values) +// requires missingValueError: an error message to display if a key is missing a value, set to "" for auto-generated or "0" to exit silently with status 0 +// returns config: a slice of values for the specified keys +func ReadConfig(readKeys []string, missingValueError string) []string { cfg, err := ini.Load(ConfigPath) if err != nil { fmt.Println(AnsiError + "Failed to load libmutton.ini" + AnsiReset) @@ -23,7 +24,14 @@ func ReadConfig(readKeys []string) []string { // ensure specified key has a value if keyConfig == "" { - fmt.Println(AnsiError + "Failed to find value for key \"" + key + "\" in section \"[LIBMUTTON]\" in libmutton.ini" + AnsiReset) + switch missingValueError { + case "": + fmt.Println(AnsiError + "Failed to find value for key \"" + key + "\" in section \"[LIBMUTTON]\" in libmutton.ini" + AnsiReset) + case "0": + os.Exit(0) + default: + fmt.Println(AnsiError + missingValueError + AnsiReset) + } os.Exit(1) } @@ -37,10 +45,9 @@ func ReadConfig(readKeys []string) []string { // [LIBMUTTON] // gpgID = // textEditor = -// onlineMode = -// sshError = +// sshUser = +// sshIP = +// sshPort = +// sshIdentity = // netPinEnabled = -// remoteUser = -// remoteIP = -// remotePort = -// identityFile = +// deviceID = TODO remove from config file diff --git a/src/backend/gpg.go b/src/backend/gpg.go index b398dd4..6ad1f33 100644 --- a/src/backend/gpg.go +++ b/src/backend/gpg.go @@ -24,7 +24,7 @@ func DecryptGPG(targetLocation string) []string { // EncryptGPG encrypts a slice of strings using GPG and returns the encrypted data as a byte slice func EncryptGPG(input []string) []byte { - cmd := exec.Command("gpg", "-q", "-r", ReadConfig([]string{"gpgID"})[0], "-e") + cmd := exec.Command("gpg", "-q", "-r", ReadConfig([]string{"gpgID"}, "")[0], "-e") writeToStdin(cmd, strings.Join(input, "\n")) encryptedBytes, err := cmd.Output() if err != nil { diff --git a/src/cli/1globals.go b/src/cli/1globals.go index 04918fa..7a7ab43 100644 --- a/src/cli/1globals.go +++ b/src/cli/1globals.go @@ -1,14 +1,12 @@ package cli import ( - "github.com/rwinkhart/MUTN/src/backend" termy "golang.org/x/crypto/ssh/terminal" "os" ) // global variables used across multiple files var ( - rootLength = len(backend.EntryRoot) width, _, _ = termy.GetSize(int(os.Stdout.Fd())) ) diff --git a/src/cli/edit.go b/src/cli/edit.go index c5d52a5..0b8d384 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -70,7 +70,7 @@ func GenUpdate(targetLocation string, hidePassword bool) { func editNote(baseNote []string) ([]string, bool) { tempFile := backend.CreateTempFile() defer os.Remove(tempFile.Name()) - editor := backend.ReadConfig([]string{"textEditor"})[0] + editor := backend.ReadConfig([]string{"textEditor"}, "")[0] // write baseNote to tempFile (if it is not empty) if len(baseNote) > 0 { diff --git a/src/cli/entryList.go b/src/cli/entryList.go index 170785e..4f5fe84 100644 --- a/src/cli/entryList.go +++ b/src/cli/entryList.go @@ -3,9 +3,8 @@ package cli import ( "fmt" "github.com/rwinkhart/MUTN/src/backend" - "io/fs" + "github.com/rwinkhart/MUTN/src/sync" "os" - "path/filepath" "strings" ) @@ -79,37 +78,7 @@ func printFileEntry(entry string, lastSlash int, charCounter int, colorAlternato // EntryListGen generates and displays full libmutton entry list func EntryListGen() { - // define file/directory containing slices so that they may be accessed by the anonymous WalkDir function - var fileList []string - var dirList []string - - // walk entry directory - _ = filepath.WalkDir(backend.EntryRoot, - func(fullPath string, entry fs.DirEntry, err error) error { - - // check for errors encountered while walking directory - if err != nil { - if os.IsNotExist(err) { - fmt.Println(backend.AnsiError + "\nThe entry directory does not exist - run \"mutn init\" to create it" + backend.AnsiReset) - } else { - // otherwise, print the source of the error - fmt.Println(backend.AnsiError + "\nAn unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset) - } - os.Exit(1) - } - - // trim root path from each path before storing - trimmedPath := fullPath[rootLength:] - - // create separate slices for entries and directories - if !entry.IsDir() { - fileList = append(fileList, trimmedPath) - } else { - dirList = append(dirList, trimmedPath) - } - - return nil - }) + fileList, dirList := sync.WalkEntryDir() // print header bar w/total entry count fmt.Print("\n"+ansiBlackOnWhite, len(fileList), " libmutton entries:"+backend.AnsiReset) diff --git a/src/cli/init.go b/src/cli/init.go index ebd7752..0c3852d 100644 --- a/src/cli/init.go +++ b/src/cli/init.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "github.com/rwinkhart/MUTN/src/backend" + "math/rand" "os" ) @@ -26,5 +27,25 @@ func TempInitCli() { // textEditor textEditor := input("Text editor (leave blank for $EDITOR, falls back to \"" + backend.FallbackEditor + "\"):") - backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID}) + // SSH info + configSSH := inputBinary("Configure SSH settings (for synchronization)?") + if configSSH { + // client device ID + deviceIDPrefix, _ := os.Hostname() + deviceIDSuffix := backend.StringGen(rand.Intn(48)+48, true, 0.2) + deviceID := deviceIDPrefix + "-" + deviceIDSuffix // TODO do not save in config file, encrypt in config directory + + // necessary SSH info + sshUser := input("Remote SSH username:") + sshIP := input("Remote SSH IP address:") + sshPort := input("Remote SSH port:") + sshIdentity := input("SSH private identity file path:") // TODO implement generator and selector + + // write config file + backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID, "deviceID": deviceID, "sshUser": sshUser, "sshIP": sshIP, "sshPort": sshPort, "sshIdentity": sshIdentity}) + } else { + // write config file + backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID}) + } + } diff --git a/src/cli/printInfo.go b/src/cli/printInfo.go index 02ededa..1d1b2f6 100644 --- a/src/cli/printInfo.go +++ b/src/cli/printInfo.go @@ -142,8 +142,8 @@ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED " ◎-◎ // \\\\ " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥" + backend.AnsiReset + " // \\\\ /|\\\n" + ansiVersionOutline + "<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + "\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" + - "\\" + ansiBlackOnWhite + " MUTN Version 0.1.0 " + backend.AnsiReset + ansiVersionOutline + "/\n" + - "\\" + ansiBlackOnWhite + " The Butchered Update " + backend.AnsiReset + ansiVersionOutline + "/\n" + + "\\" + ansiBlackOnWhite + " MUTN Version 0.2.X " + backend.AnsiReset + ansiVersionOutline + "/\n" + + "\\" + ansiBlackOnWhite + " The Placeholder Update " + backend.AnsiReset + ansiVersionOutline + "/\n" + "\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" + "\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + backend.AnsiReset + ansiVersionOutline + "/\n" + "\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" + diff --git a/src/sync/1globals.go b/src/sync/1globals.go new file mode 100644 index 0000000..2a2a0dd --- /dev/null +++ b/src/sync/1globals.go @@ -0,0 +1,6 @@ +package sync + +import "github.com/rwinkhart/MUTN/src/backend" + +// RootLength store length of backend.EntryRoot string +var rootLength = len(backend.EntryRoot) diff --git a/src/sync/sync.go b/src/sync/sync.go new file mode 100644 index 0000000..f6c08a7 --- /dev/null +++ b/src/sync/sync.go @@ -0,0 +1,42 @@ +package sync + +import ( + "fmt" + "github.com/rwinkhart/MUTN/src/backend" + "os" +) + +// returns lists of local entries and mod times (two separate lists) +func getLocalData() { + fileList, _ := WalkEntryDir() + fmt.Println(fileList) // TODO placeholder +} + +// RunJob runs the SSH sync job +func RunJob(manualSync bool) { + // get SSH config info, exit if not configured (displaying an error if the sync job was called manually) + var sshUserIPPortIdentity []string + if manualSync { + sshUserIPPortIdentity = backend.ReadConfig([]string{"sshUser", "sshIP", "sshPort", "sshIdentity"}, "SSH settings not configured - run \"mutn init\" to configure") + } else { + sshUserIPPortIdentity = backend.ReadConfig([]string{"sshUser", "sshIP", "sshPort", "sshIdentity"}, "0") + } + + var sshUser, sshIP, sshPort, sshIdentity string + for i, key := range sshUserIPPortIdentity { + switch i { + case 0: + sshUser = key + case 1: + sshIP = key + case 2: + sshPort = key + case 3: + sshIdentity = key + } + } + + fmt.Println(sshUser, sshIP, sshPort, sshIdentity) // TODO placeholder + + os.Exit(0) +} diff --git a/src/sync/walk.go b/src/sync/walk.go new file mode 100644 index 0000000..b29bbb0 --- /dev/null +++ b/src/sync/walk.go @@ -0,0 +1,46 @@ +package sync + +import ( + "fmt" + "github.com/rwinkhart/MUTN/src/backend" + "io/fs" + "os" + "path/filepath" +) + +// WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists) +func WalkEntryDir() ([]string, []string) { + // define file/directory containing slices so that they may be accessed by the anonymous WalkDir function + var fileList []string + var dirList []string + + // walk entry directory + _ = filepath.WalkDir(backend.EntryRoot, + func(fullPath string, entry fs.DirEntry, err error) error { + + // check for errors encountered while walking directory + if err != nil { + if os.IsNotExist(err) { + fmt.Println(backend.AnsiError + "\nThe entry directory does not exist - run \"mutn init\" to create it" + backend.AnsiReset) + } else { + // otherwise, print the source of the error + fmt.Println(backend.AnsiError + "\nAn unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset) + } + os.Exit(1) + } + + // trim root path from each path before storing + trimmedPath := fullPath[rootLength:] + + // create separate slices for entries and directories + if !entry.IsDir() { + fileList = append(fileList, trimmedPath) + } else { + dirList = append(dirList, trimmedPath) + } + + return nil + }) + + return fileList, dirList +}