From 171a58cf7d4183d22ea7f5c1f8002bd2fc37c64d Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Thu, 16 May 2024 16:35:05 -0400 Subject: [PATCH] Implement initial server-side shearing logic --- src/backend/configParser.go | 1 - src/backend/initUNIX.go | 4 ++-- src/backend/initWIN.go | 1 + src/backend/shear.go | 17 -------------- src/sync/client.go | 21 ++++++++++++----- src/sync/common.go | 45 +++++++++++++++++++++++++++++++++++++ src/sync/server.go | 2 +- 7 files changed, 64 insertions(+), 27 deletions(-) delete mode 100644 src/backend/shear.go diff --git a/src/backend/configParser.go b/src/backend/configParser.go index cfd2c36..7655e72 100644 --- a/src/backend/configParser.go +++ b/src/backend/configParser.go @@ -50,4 +50,3 @@ func ReadConfig(readKeys []string, missingValueError string) []string { // sshPort = // sshIdentity = // netPinEnabled = -// deviceID = diff --git a/src/backend/initUNIX.go b/src/backend/initUNIX.go index e6cf4de..93bbfc2 100644 --- a/src/backend/initUNIX.go +++ b/src/backend/initUNIX.go @@ -18,8 +18,8 @@ func dirInit() { os.Exit(1) } - // create config directory - err = os.MkdirAll(ConfigDir, 0700) + // create config directory w/devices subdirectory + err = os.MkdirAll(ConfigDir+"/devices", 0700) if err != nil { fmt.Println(AnsiError + "Failed to create \"" + ConfigDir + "\":" + err.Error() + AnsiReset) os.Exit(1) diff --git a/src/backend/initWIN.go b/src/backend/initWIN.go index c228495..b4c1aa8 100644 --- a/src/backend/initWIN.go +++ b/src/backend/initWIN.go @@ -17,6 +17,7 @@ func dirInit() { fmt.Println(AnsiError + "Failed to create \"" + EntryRoot + "\":" + err.Error() + AnsiReset) os.Exit(1) } + os.MkdirAll(ConfigDir+"\\devices", 0700) } // textEditorFallback returns FallbackEditor diff --git a/src/backend/shear.go b/src/backend/shear.go deleted file mode 100644 index fcd5479..0000000 --- a/src/backend/shear.go +++ /dev/null @@ -1,17 +0,0 @@ -package backend - -import ( - "fmt" - "os" -) - -func Shear(targetLocation string) { - // TODO If in online mode, remove from server and add to shear list - TargetIsFile(targetLocation, true, 0) // needed because os.RemoveAll does not return an error if target does not exist - err := os.RemoveAll(targetLocation) - if err != nil { - fmt.Println(AnsiError + "Failed to remove target: " + err.Error() + AnsiReset) - os.Exit(1) - } - os.Exit(0) -} diff --git a/src/sync/client.go b/src/sync/client.go index 405d638..f2f0851 100644 --- a/src/sync/client.go +++ b/src/sync/client.go @@ -12,8 +12,9 @@ import ( // global constants used only in this file const ( - ansiUpload = "\033[38;5;4m" + ansiDelete = "\033[38;5;1m" ansiDownload = "\033[38;5;2m" + ansiUpload = "\033[38;5;4m" ) // getSSHOutput runs a command over SSH and returns the output @@ -54,7 +55,7 @@ func getSSHOutput(cmd string, manualSync bool) string { } // read known hosts file - hostKeyCallback, err := knownhosts.New(backend.Home + "/.ssh/known_hosts") + hostKeyCallback, err := knownhosts.New(backend.Home + backend.PathSeparator + ".ssh" + backend.PathSeparator + "known_hosts") if err != nil { fmt.Println(backend.AnsiError + "Sync failed - Unable to read known hosts file:" + err.Error() + backend.AnsiReset) os.Exit(1) @@ -101,7 +102,7 @@ func getSSHOutput(cmd string, manualSync bool) string { // getRemoteDataFromClient returns a map of remote entries to their modification times, a list of remote folders, and a list of queued deletions func getRemoteDataFromClient(manualSync bool) (map[string]int64, []string, []string) { // get remote output over SSH - output := getSSHOutput("libmuttonserver", manualSync) + output := getSSHOutput("libmuttonserver fetch", manualSync) // split output into slice based on occurrences of "\x1d" outputSlice := strings.Split(output, "\x1d") @@ -176,13 +177,21 @@ func syncLists(localEntryModMap, remoteEntryModMap map[string]int64) { } } +func deletionSync(deletions []string) { + for _, deletion := range deletions { + fmt.Println(ansiDelete+deletion+backend.AnsiReset, "has been sheared, removing...") + //os.RemoveAll(backend.EntryRoot + deletion) TODO uncomment after testing + } +} + // RunJob runs the SSH sync job func RunJob(manualSync bool) { // fetch remote lists - remoteEntryModMap, remoteFolders, remoteDeletions := getRemoteDataFromClient(manualSync) - fmt.Println(remoteFolders, remoteDeletions) // TODO placeholder + remoteEntryModMap, remoteFolders, deletions := getRemoteDataFromClient(manualSync) + fmt.Println(remoteFolders) // TODO placeholder - // TODO sync deletions and folders + // sync deletions + deletionSync(deletions) // fetch local lists localEntryModMap := getLocalData() diff --git a/src/sync/common.go b/src/sync/common.go index a167298..f83ffcb 100644 --- a/src/sync/common.go +++ b/src/sync/common.go @@ -6,6 +6,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" ) // WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists) @@ -56,3 +57,47 @@ func getModTimes(entryList []string) []int64 { return modList } + +// Shear removes the target file or directory from the system +// if running on the server, it will also add the target to the deletions list +// if running on the client, it will call the server to add the target to the deletions list +func Shear(targetLocationIncomplete string, deviceID string) { + // get the full targetLocation path and remove the target + targetLocationComplete := backend.TargetLocationFormat(targetLocationIncomplete[1:]) + backend.TargetIsFile(targetLocationComplete, true, 0) // needed because os.RemoveAll does not return an error if target does not exist + err := os.RemoveAll(targetLocationComplete) + if err != nil { + fmt.Println(backend.AnsiError + "Failed to remove local target: " + err.Error() + backend.AnsiReset) + os.Exit(1) + } + + // read the devices directory + var deviceIDList []os.DirEntry + deviceIDList, err = os.ReadDir(backend.ConfigDir + backend.PathSeparator + "devices") + if err != nil { + fmt.Println(backend.AnsiError + "Failed to read the devices directory: " + err.Error() + backend.AnsiReset) + os.Exit(1) + } + + // add the sheared target (incomplete, vanity) to the deletions list + if deviceID != "" { // if running on the server... + for _, device := range deviceIDList { + if device.Name() != deviceID { + _, err = os.Create(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + device.Name() + "\x1d" + strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, "\x1e")) + if err != nil { + // do not print error as there is currently no way of seeing server-side errors + os.Exit(1) + } + } + } + } else { + backend.ReadConfig([]string{"sshUser"}, "0") // exits if value does not exist (indicates offline mode) + // if running on the client in online mode... + // determine client device ID (to send to server, avoids creating a deletion file for the client device) + deviceID = deviceIDList[0].Name() + // below: deviceID and targetLocationIncomplete are separated by \x1d, path separators are replaced with \x1e, and spaces are replaced with \x1f + getSSHOutput("libmuttonserver shear "+deviceID+"\x1d"+strings.ReplaceAll(strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, "\x1e"), " ", "\x1f"), true) + } + + os.Exit(0) +} diff --git a/src/sync/server.go b/src/sync/server.go index bb24915..8ad3f77 100644 --- a/src/sync/server.go +++ b/src/sync/server.go @@ -12,7 +12,7 @@ import ( func GetRemoteDataFromServer() { entryList, dirList := WalkEntryDir() modList := getModTimes(entryList) - deletionsList, err := os.ReadDir(backend.ConfigDir + "/deletions") + deletionsList, err := os.ReadDir(backend.ConfigDir + backend.PathSeparator + "deletions") if err != nil { fmt.Println(backend.AnsiError + "Failed to read the deletions directory: " + err.Error() + backend.AnsiReset) os.Exit(1)