From be1f602770717c4b5a71d8ec7c8fda3afaaed315 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 9 Jul 2024 23:18:13 -0400 Subject: [PATCH] Implement synchronized renaming --- libmuttonserver.go | 9 +++++- mutn.go | 2 +- src/backend/edit.go | 24 ---------------- src/cli/edit.go | 14 ++++------ src/sync/common.go | 67 +++++++++++++++++++++++++++++++++++++-------- 5 files changed, 70 insertions(+), 46 deletions(-) diff --git a/libmuttonserver.go b/libmuttonserver.go index 8856201..eff79f8 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -40,6 +40,13 @@ func main() { // print all information needed for syncing to stdout for interpretation by the client // stdin[0] is expected to be the device ID sync.GetRemoteDataFromServer(stdin[0]) + case "rename": + // move an entry to a new location before using fallthrough to add its previous iteration to the deletions directory + // stdin[0] is evaluated after fallthrough + // stdin[1] is expected to be the OLD incomplete target location with "\x1d" representing path separators - always pass in UNIX format + // stdin[2] is expected to be the NEW incomplete target location with "\x1d" representing path separators - always pass in UNIX format + sync.Rename(strings.ReplaceAll(stdin[1], "\x1d", "/"), strings.ReplaceAll(stdin[2], "\x1d", "/"), true) + fallthrough // fallthrough to add the old entry to the deletions directory case "shear": // shear an entry from the server and add it to the deletions directory // stdin[0] is expected to be the device ID @@ -73,7 +80,7 @@ This software exists under the MIT license; you may redistribute it under certai This program comes with absolutely no warranty; type "libmuttonserver version" for details. ` + cli.AnsiBold + "Usage:" + backend.AnsiReset + ` libmuttonserver - + ` + cli.AnsiBold + "Arguments (user):" + backend.AnsiReset + ` help|-h Bring up this menu version|-v Display version and license information diff --git a/mutn.go b/mutn.go index 175e4fd..a4447ae 100644 --- a/mutn.go +++ b/mutn.go @@ -97,7 +97,7 @@ func main() { case "note", "-n": field = 4 case "rename", "-r": - cli.RenameCli(targetLocation) + cli.RenameCli(args[1]) // pass the incomplete path as the server and all clients (reading from the deletions directory) will have a different home directory default: cli.HelpEdit() } diff --git a/src/backend/edit.go b/src/backend/edit.go index add05e7..ac9a938 100644 --- a/src/backend/edit.go +++ b/src/backend/edit.go @@ -1,10 +1,5 @@ package backend -import ( - "fmt" - "os" -) - // GetOldEntryData decrypts and returns old entry data (with all required lines present) func GetOldEntryData(targetLocation string, field int) []string { // ensure targetLocation exists @@ -21,25 +16,6 @@ func GetOldEntryData(targetLocation string, field int) []string { } } -// Rename renames oldLocation to newLocation -func Rename(oldLocation, newLocation string) { - // ensure newLocation does not exist - _, isAccessible := TargetIsFile(newLocation, false, 0) - if isAccessible { - fmt.Println(AnsiError + "\"" + newLocation + "\" already exists" + AnsiReset) - os.Exit(1) - } - - // rename oldLocation to newLocation - err := os.Rename(oldLocation, newLocation) - if err != nil { - fmt.Println(AnsiError + "Failed to rename - does the target containing directory exists?" + AnsiReset) - } - - // TODO implement synced renaming - Exit(0) -} - // EnsureSliceLength ensures slice is long enough to contain the specified index func EnsureSliceLength(slice []string, index int) []string { for len(slice) <= index { diff --git a/src/cli/edit.go b/src/cli/edit.go index 583f967..69328e9 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -4,22 +4,20 @@ import ( "bufio" "fmt" "github.com/rwinkhart/MUTN/src/backend" + "github.com/rwinkhart/MUTN/src/sync" "os" "os/exec" "reflect" "strings" ) -// RenameCli renames an entry at oldLocation to a new location (user input) -func RenameCli(oldLocation string) { - // ensure targetLocation exists - backend.TargetIsFile(oldLocation, true, 0) - +// RenameCli renames an entry at oldLocation to a new location (user input) on both the client and the server +func RenameCli(oldLocationIncomplete string) { // prompt user for new location and rename - newLocation := backend.TargetLocationFormat(input("New location:")) - backend.Rename(oldLocation, newLocation) + newLocationIncomplete := input("New location:") + sync.Rename(oldLocationIncomplete, newLocationIncomplete, false) - // exit is done from backend.Rename + // exit is done from sync.Rename } // EditEntryField edits a field of an entry at targetLocation (user input) diff --git a/src/sync/common.go b/src/sync/common.go index a494cbb..58aec5c 100644 --- a/src/sync/common.go +++ b/src/sync/common.go @@ -3,12 +3,13 @@ package sync import ( "fmt" "github.com/rwinkhart/MUTN/src/backend" + "io/fs" "os" "strings" ) +// getModTimes returns a list of all entry modification times func getModTimes(entryList []string) []int64 { - // get a list of all entry modification times var modList []int64 for _, file := range entryList { modTime, _ := os.Stat(backend.TargetLocationFormat(file)) @@ -18,6 +19,53 @@ func getModTimes(entryList []string) []int64 { return modList } +// genDeviceIDList returns a pointer to a slice of all registered device IDs +func genDeviceIDList() *[]fs.DirEntry { + // create a slice of all registered devices + 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) + } + return &deviceIDList +} + +// Rename renames oldLocation to newLocation +// if running on the client, also calls the server to rename the entry and add the old entry name to the deletions list +func Rename(oldLocationIncomplete, newLocationIncomplete string, onServer bool) { + // get full paths for both locations + oldLocation := backend.TargetLocationFormat(oldLocationIncomplete) + newLocation := backend.TargetLocationFormat(newLocationIncomplete) + + // ensure oldLocation exists + backend.TargetIsFile(oldLocation, true, 0) + + // ensure newLocation does not exist + _, isAccessible := backend.TargetIsFile(newLocation, false, 0) + if isAccessible { + fmt.Println(backend.AnsiError + "\"" + newLocation + "\" already exists" + backend.AnsiReset) + os.Exit(1) + } + + // rename oldLocation to newLocation + err := os.Rename(oldLocation, newLocation) + if err != nil { + fmt.Println(backend.AnsiError + "Failed to rename - does the target containing directory exist?" + backend.AnsiReset) + } + + // call server to complete rename + if !onServer { + deviceIDList := genDeviceIDList() + if len(*deviceIDList) > 0 { // ensure a device ID exists (online mode) + GetSSHOutput("libmuttonserver rename", + (*deviceIDList)[0].Name()+"\n"+ + strings.ReplaceAll(oldLocationIncomplete, backend.PathSeparator, "\x1d")+"\n"+ + strings.ReplaceAll(newLocationIncomplete, backend.PathSeparator, "\x1d"), false) + } + backend.Exit(0) // do not exit program on server, as fallthrough is used to add the old entry name to the deletions list + } +} + // ShearLocal removes the target file or directory from the local system // returns: deviceID (on client), for use in ShearRemoteFromClient // if the local system is a server, it will also add the target to the deletions list for all clients (except the requesting client) @@ -29,18 +77,13 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { onServer = true } - // create a slice of all registered devices - 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) - } + deviceIDList := genDeviceIDList() // add the sheared target (incomplete, vanity) to the deletions list (if running on a server) if onServer { - for _, device := range deviceIDList { + for _, device := range *deviceIDList { if device.Name() != clientDeviceID { - _, err = os.Create(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + device.Name() + "\x1e" + strings.ReplaceAll(targetLocationIncomplete, "/", "\x1d")) + _, err := os.Create(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + device.Name() + "\x1e" + strings.ReplaceAll(targetLocationIncomplete, "/", "\x1d")) if err != nil { // do not print error as there is currently no way of seeing server-side errors // failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical) @@ -55,14 +98,14 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist backend.TargetIsFile(targetLocationComplete, true, 0) } - err = os.RemoveAll(targetLocationComplete) + err := os.RemoveAll(targetLocationComplete) if err != nil { fmt.Println(backend.AnsiError + "Failed to remove local target: " + err.Error() + backend.AnsiReset) os.Exit(1) } - if !onServer && len(deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode) - return deviceIDList[0].Name() + if !onServer && len(*deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode) + return (*deviceIDList)[0].Name() } return ""