Implement synchronized renaming

This commit is contained in:
2024-07-09 23:18:13 -04:00
parent 42bed109d6
commit be1f602770
5 changed files with 70 additions and 46 deletions
+8 -1
View File
@@ -40,6 +40,13 @@ func main() {
// print all information needed for syncing to stdout for interpretation by the client // print all information needed for syncing to stdout for interpretation by the client
// stdin[0] is expected to be the device ID // stdin[0] is expected to be the device ID
sync.GetRemoteDataFromServer(stdin[0]) 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": case "shear":
// shear an entry from the server and add it to the deletions directory // shear an entry from the server and add it to the deletions directory
// stdin[0] is expected to be the device ID // 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. This program comes with absolutely no warranty; type "libmuttonserver version" for details.
` + cli.AnsiBold + "Usage:" + backend.AnsiReset + ` libmuttonserver <argument> ` + cli.AnsiBold + "Usage:" + backend.AnsiReset + ` libmuttonserver <argument>
` + cli.AnsiBold + "Arguments (user):" + backend.AnsiReset + ` ` + cli.AnsiBold + "Arguments (user):" + backend.AnsiReset + `
help|-h Bring up this menu help|-h Bring up this menu
version|-v Display version and license information version|-v Display version and license information
+1 -1
View File
@@ -97,7 +97,7 @@ func main() {
case "note", "-n": case "note", "-n":
field = 4 field = 4
case "rename", "-r": 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: default:
cli.HelpEdit() cli.HelpEdit()
} }
-24
View File
@@ -1,10 +1,5 @@
package backend package backend
import (
"fmt"
"os"
)
// GetOldEntryData decrypts and returns old entry data (with all required lines present) // GetOldEntryData decrypts and returns old entry data (with all required lines present)
func GetOldEntryData(targetLocation string, field int) []string { func GetOldEntryData(targetLocation string, field int) []string {
// ensure targetLocation exists // 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 // EnsureSliceLength ensures slice is long enough to contain the specified index
func EnsureSliceLength(slice []string, index int) []string { func EnsureSliceLength(slice []string, index int) []string {
for len(slice) <= index { for len(slice) <= index {
+6 -8
View File
@@ -4,22 +4,20 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/sync"
"os" "os"
"os/exec" "os/exec"
"reflect" "reflect"
"strings" "strings"
) )
// RenameCli renames an entry at oldLocation to a new location (user input) // RenameCli renames an entry at oldLocation to a new location (user input) on both the client and the server
func RenameCli(oldLocation string) { func RenameCli(oldLocationIncomplete string) {
// ensure targetLocation exists
backend.TargetIsFile(oldLocation, true, 0)
// prompt user for new location and rename // prompt user for new location and rename
newLocation := backend.TargetLocationFormat(input("New location:")) newLocationIncomplete := input("New location:")
backend.Rename(oldLocation, newLocation) 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) // EditEntryField edits a field of an entry at targetLocation (user input)
+55 -12
View File
@@ -3,12 +3,13 @@ package sync
import ( import (
"fmt" "fmt"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/MUTN/src/backend"
"io/fs"
"os" "os"
"strings" "strings"
) )
// getModTimes returns a list of all entry modification times
func getModTimes(entryList []string) []int64 { func getModTimes(entryList []string) []int64 {
// get a list of all entry modification times
var modList []int64 var modList []int64
for _, file := range entryList { for _, file := range entryList {
modTime, _ := os.Stat(backend.TargetLocationFormat(file)) modTime, _ := os.Stat(backend.TargetLocationFormat(file))
@@ -18,6 +19,53 @@ func getModTimes(entryList []string) []int64 {
return modList 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 // ShearLocal removes the target file or directory from the local system
// returns: deviceID (on client), for use in ShearRemoteFromClient // 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) // 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 onServer = true
} }
// create a slice of all registered devices deviceIDList := genDeviceIDList()
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 running on a server) // add the sheared target (incomplete, vanity) to the deletions list (if running on a server)
if onServer { if onServer {
for _, device := range deviceIDList { for _, device := range *deviceIDList {
if device.Name() != clientDeviceID { 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 { if err != nil {
// do not print error as there is currently no way of seeing server-side errors // 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) // 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 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) backend.TargetIsFile(targetLocationComplete, true, 0)
} }
err = os.RemoveAll(targetLocationComplete) err := os.RemoveAll(targetLocationComplete)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to remove local target: " + err.Error() + backend.AnsiReset) fmt.Println(backend.AnsiError + "Failed to remove local target: " + err.Error() + backend.AnsiReset)
os.Exit(1) os.Exit(1)
} }
if !onServer && len(deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode) 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 (*deviceIDList)[0].Name()
} }
return "" return ""