Replace use of ASCII control characters with unicode characters allowed in Windows filenames (fixes shearing with a Windows server)

This commit is contained in:
2024-07-10 15:23:13 -04:00
parent 6b3a136c5e
commit a2a92985af
5 changed files with 37 additions and 31 deletions
+8 -1
View File
@@ -2,5 +2,12 @@ package sync
import "github.com/rwinkhart/MUTN/src/backend"
// RootLength store length of backend.EntryRoot string
// define field separator constants
const (
FSSpace = "\u259d" // ▝ space/list separator
FSPath = "\u259e" // ▞ path separator
FSMisc = "\u259f" // ▟ misc. field separator (if \u259d is already used)
)
// rootLength stores length of backend.EntryRoot string
var rootLength = len(backend.EntryRoot)
+18 -19
View File
@@ -143,18 +143,18 @@ func getRemoteDataFromClient(manualSync bool) (map[string]int64, []string, []str
}
output := GetSSHOutput("libmuttonserver fetch", clientDeviceID[0].Name(), manualSync)
// split output into slice based on occurrences of "\x1e"
outputSlice := strings.Split(output, "\x1e")
// split output into slice based on occurrences of FSSpace
outputSlice := strings.Split(output, FSSpace)
// re-form the lists
if len(outputSlice) != 4 { // ensure information from server is complete
fmt.Println(backend.AnsiError + "Sync failed - Unable to fetch remote data; server returned an unexpected response" + backend.AnsiReset)
os.Exit(1)
}
entries := strings.Split(outputSlice[0], "\x1f")[1:]
modsStrings := strings.Split(outputSlice[1], "\x1f")[1:]
folders := strings.Split(outputSlice[2], "\x1f")[1:]
deletions := strings.Split(outputSlice[3], "\x1f")[1:]
entries := strings.Split(outputSlice[0], FSMisc)[1:]
modsStrings := strings.Split(outputSlice[1], FSMisc)[1:]
folders := strings.Split(outputSlice[2], FSMisc)[1:]
deletions := strings.Split(outputSlice[3], FSMisc)[1:]
// convert the mod times to int64
var mods []int64
@@ -200,7 +200,6 @@ func targetLocationFormatSFTP(targetName, serverEntryRoot string, serverIsWindow
}
// sftpSync takes two slices of entries (one for downloads and one for uploads) and syncs them between the client and server using SFTP
// TODO test Windows server hosting support
func sftpSync(downloadList, uploadList []string, manualSync bool) {
// establish an SSH connection for transfers
sshClient, sshEntryRoot, sshIsWindows := getSSHClient(manualSync)
@@ -215,9 +214,9 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
defer sftpClient.Close()
// iterate over the download list
var filesTransfered bool
var filesTransferred bool
for _, entryName := range downloadList {
filesTransfered = true // set a flag to indicate that files have been downloaded (used to determine whether to print a gap between download and upload messages)
filesTransferred = true // set a flag to indicate that files have been downloaded (used to determine whether to print a gap between download and upload messages)
fmt.Println("Downloading " + ansiDownload + entryName + backend.AnsiReset)
@@ -228,7 +227,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var fileInfo os.FileInfo
fileInfo, err = sftpClient.Stat(remoteEntryFullPath)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to get remote file info (modtime):", err.Error()+backend.AnsiReset)
fmt.Println(backend.AnsiError+"Sync failed - Unable to get remote file info (mod time):", err.Error()+backend.AnsiReset)
os.Exit(1)
}
modTime := fileInfo.ModTime()
@@ -267,14 +266,14 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
err = os.Chtimes(localEntryFullPath, time.Now(), modTime)
}
if filesTransfered {
if filesTransferred {
fmt.Println() // add a gap between download and upload messages
}
// iterate over the upload list
filesTransfered = false
filesTransferred = false
for _, entryName := range uploadList {
filesTransfered = true // set a flag to indicate that files have been uploaded (used to determine whether to print a gap between upload and sync complete messages)
filesTransferred = true // set a flag to indicate that files have been uploaded (used to determine whether to print a gap between upload and sync complete messages)
fmt.Println("Uploading " + ansiUpload + entryName + backend.AnsiReset)
@@ -285,7 +284,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var fileInfo os.FileInfo
fileInfo, err = os.Stat(localEntryFullPath)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to get local file info (modtime):", err.Error()+backend.AnsiReset)
fmt.Println(backend.AnsiError+"Sync failed - Unable to get local file info (mod time):", err.Error()+backend.AnsiReset)
os.Exit(1)
}
modTime := fileInfo.ModTime()
@@ -324,7 +323,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
err = sftpClient.Chtimes(remoteEntryFullPath, time.Now(), modTime)
}
if filesTransfered {
if filesTransferred {
fmt.Println() // add a gap between upload and sync complete messages
}
}
@@ -392,7 +391,7 @@ func ShearRemoteFromClient(targetLocationIncomplete string) {
if deviceID != "" { // ensure a device ID exists (online mode)
// call the server to remotely shear the target and add it to the deletions list
GetSSHOutput("libmuttonserver shear", deviceID+"\n"+
strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, "\x1d"), false)
strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, FSPath), false)
}
backend.Exit(0) // sync is not required after shearing since the target has already been removed from the local system
@@ -408,8 +407,8 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string)
// call the server to move the target on the remote system and add the old target to the deletions list
GetSSHOutput("libmuttonserver rename",
(*deviceIDList)[0].Name()+"\n"+
strings.ReplaceAll(oldLocationIncomplete, backend.PathSeparator, "\x1d")+"\n"+
strings.ReplaceAll(newLocationIncomplete, backend.PathSeparator, "\x1d"), false)
strings.ReplaceAll(oldLocationIncomplete, backend.PathSeparator, FSPath)+"\n"+
strings.ReplaceAll(newLocationIncomplete, backend.PathSeparator, FSPath), false)
}
backend.Exit(0)
@@ -419,7 +418,7 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string)
// can safely be called in offline mode, as well, so this is the intended interface for adding folders (AddFolderLocal should only be used directly by the server binary)
func AddFolderRemoteFromClient(targetLocationIncomplete string) {
AddFolderLocal(targetLocationIncomplete) // add the folder on the local system
GetSSHOutput("libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, "\x1d"), false) // call the server to create the folder remotely
GetSSHOutput("libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, FSPath), false) // call the server to create the folder remotely
backend.Exit(0)
}
+1 -1
View File
@@ -47,7 +47,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string {
if onServer {
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() + FSSpace + strings.ReplaceAll(targetLocationIncomplete, "/", FSPath))
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)
+1 -1
View File
@@ -25,7 +25,7 @@ func DeviceIDGen() (string, string) {
// register device ID with server and fetch remote EntryRoot and OS type
//manualSync is true so the user is alerted if device ID registration fails
sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput("libmuttonserver register", deviceID, true), "\x1e")
sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput("libmuttonserver register", deviceID, true), FSSpace)
return sshEntryRootSSHIsWindows[0], sshEntryRootSSHIsWindows[1]
}
+9 -9
View File
@@ -8,7 +8,7 @@ import (
)
// GetRemoteDataFromServer prints to stdout the remote entries, mod times, folders, and deletions
// lists in output are separated by "\x1e"
// lists in output are separated by FSSpace
// output is meant to be captured over SSH for interpretation by the client
func GetRemoteDataFromServer(clientDeviceID string) {
entryList, dirList := WalkEntryDir()
@@ -23,29 +23,29 @@ func GetRemoteDataFromServer(clientDeviceID string) {
// entry list
for _, entry := range entryList {
fmt.Print("\x1f" + entry)
fmt.Print(FSMisc + entry)
}
// modification time list
fmt.Print("\x1e")
fmt.Print(FSSpace)
for _, mod := range modList {
fmt.Print("\x1f")
fmt.Print(FSMisc)
fmt.Print(mod)
}
// directory/folder list
fmt.Print("\x1e")
fmt.Print(FSSpace)
for _, dir := range dirList {
fmt.Print("\x1f" + dir)
fmt.Print(FSMisc + dir)
}
// deletions list
fmt.Print("\x1e")
fmt.Print(FSSpace)
for _, deletion := range deletionsList {
// print deletion if it is relevant to the current client device
affectedIDTargetLocationIncomplete := strings.Split(deletion.Name(), "\x1e")
affectedIDTargetLocationIncomplete := strings.Split(deletion.Name(), FSSpace)
if affectedIDTargetLocationIncomplete[0] == clientDeviceID {
fmt.Print("\x1f" + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], "\x1d", "/"))
fmt.Print(FSMisc + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], FSPath, "/"))
// assume successful client deletion and remove deletions file (if assumption is somehow false, worst case scenario is that the client will re-upload the deleted entry)
_ = os.Remove(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + deletion.Name()) // error ignored; function not run from a user-facing argument and thus the error would not be visible