diff --git a/core/clipClearProcessGeneric.go b/core/clipClearProcessGeneric.go index 5cd81ce..2e63653 100644 --- a/core/clipClearProcessGeneric.go +++ b/core/clipClearProcessGeneric.go @@ -7,6 +7,7 @@ import ( "time" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" ) // clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed. @@ -17,7 +18,7 @@ func clipClearProcess(assignedContents string) { clearClipboard := func() { err := cmdClear.Run() if err != nil { - back.PrintError("Failed to clear clipboard", ErrorClipboard, true) + back.PrintError("Failed to clear clipboard", global.ErrorClipboard, true) } back.Exit(0) } @@ -33,7 +34,7 @@ func clipClearProcess(assignedContents string) { newContents, err := cmdPaste.Output() if err != nil { - back.PrintError("Failed to read clipboard contents", ErrorClipboard, true) + back.PrintError("Failed to read clipboard contents", global.ErrorClipboard, true) } if assignedContents == strings.TrimRight(string(newContents), "\r\n") { diff --git a/core/configParser.go b/core/configParser.go index f232da0..a041215 100644 --- a/core/configParser.go +++ b/core/configParser.go @@ -2,17 +2,16 @@ package core import ( "fmt" - "io/fs" - "os" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" "gopkg.in/ini.v1" ) // loadConfig loads the libmutton.ini file and returns the configuration. // It is a utility function for ParseConfig and WriteConfig; do not call directly. func loadConfig() *ini.File { - cfg, err := ini.Load(ConfigPath) + cfg, err := ini.Load(global.ConfigPath) if err != nil { back.PrintError("Failed to load libmutton.ini: "+err.Error(), back.ErrorRead, true) } @@ -54,21 +53,6 @@ func ParseConfig(valuesRequested [][2]string, missingValueError string) ([]strin return config, err } -// GenDeviceIDList returns a pointer to a slice of all registered device IDs. -// Requires: errorOnFail (set to true to throw an error if the devices directory cannot be read/does not exist) -func GenDeviceIDList(errorOnFail bool) *[]fs.DirEntry { - // create a slice of all registered devices - deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices") - if err != nil { - if errorOnFail { - back.PrintError("Failed to read the devices directory: "+err.Error(), back.ErrorRead, true) - } else { - return nil // a nil return value indicates that the devices directory could not be read/does not exist - } - } - return &deviceIDList -} - // WriteConfig writes the provided key-value pairs under the specified section headers in the libmutton.ini file. // Requires: valuesToWrite (a slice of length 3 arrays each containing a section, a key name, and a value), // prune (a slice similar to valuesToWrite to allow removing the specified keys from an existing config), @@ -108,7 +92,7 @@ func WriteConfig(valuesToWrite [][3]string, keysToPrune [][2]string, append bool } // save to libmutton.ini - err := cfg.SaveTo(ConfigPath) + err := cfg.SaveTo(global.ConfigPath) if err != nil { back.PrintError("Failed to save libmutton.ini: "+err.Error(), back.ErrorWrite, true) } diff --git a/core/copy.go b/core/copy.go index 8e3685a..2e78f18 100644 --- a/core/copy.go +++ b/core/copy.go @@ -9,13 +9,14 @@ import ( steamtotp "github.com/fortis/go-steam-totp" "github.com/pquerna/otp/totp" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/crypt" ) // CopyArgument copies a field from an entry to the clipboard. func CopyArgument(targetLocation string, field int) { if isFile, _ := back.TargetIsFile(targetLocation, true, 2); isFile { - decryptedEntry := DecryptFileToSlice(targetLocation) + decryptedEntry := crypt.DecryptFileToSlice(targetLocation) var copySubject string // will store data to be copied // ensure field exists in entry diff --git a/core/copyDARWIN.go b/core/copyDARWIN.go index 56219d1..00e9579 100644 --- a/core/copyDARWIN.go +++ b/core/copyDARWIN.go @@ -6,6 +6,7 @@ import ( "os/exec" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" ) // copyString copies a string to the clipboard. @@ -14,7 +15,7 @@ func copyString(continuous bool, copySubject string) { back.WriteToStdin(cmd, copySubject) err := cmd.Run() if err != nil { - back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) + back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true) } if !continuous { diff --git a/core/copyTERMUX.go b/core/copyTERMUX.go index 8f5ab88..1cab319 100644 --- a/core/copyTERMUX.go +++ b/core/copyTERMUX.go @@ -6,6 +6,7 @@ import ( "os/exec" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" ) // copyString copies a string to the clipboard. @@ -14,7 +15,7 @@ func copyString(continuous bool, copySubject string) { back.WriteToStdin(cmd, copySubject) err := cmd.Run() if err != nil { - back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) + back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true) } if !continuous { diff --git a/core/copyUNIX.go b/core/copyUNIX.go index 6cd5231..5da84af 100644 --- a/core/copyUNIX.go +++ b/core/copyUNIX.go @@ -7,6 +7,7 @@ import ( "os/exec" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" ) // copyString copies a string to the clipboard. @@ -20,13 +21,13 @@ func copyString(continuous bool, copySubject string) { } else if _, envSet = os.LookupEnv("DISPLAY"); envSet { cmdCopy = exec.Command("xclip", "-sel", "c", "-t", "text/plain") } else { - back.PrintError("Clipboard platform could not be determined", ErrorClipboard, true) + back.PrintError("Clipboard platform could not be determined", global.ErrorClipboard, true) } back.WriteToStdin(cmdCopy, copySubject) err := cmdCopy.Run() if err != nil { - back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) + back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true) } if !continuous { diff --git a/core/copyWIN.go b/core/copyWIN.go index cb8c753..11fde29 100644 --- a/core/copyWIN.go +++ b/core/copyWIN.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" ) // copyString copies a string to the clipboard. @@ -15,7 +16,7 @@ func copyString(continuous bool, copySubject string) { cmd := exec.Command("powershell.exe", "-c", fmt.Sprintf("echo '%s' | Set-Clipboard", strings.ReplaceAll(copySubject, "'", "''"))) err := cmd.Run() if err != nil { - back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) + back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true) } if !continuous { diff --git a/core/edit.go b/core/edit.go index 1b9bf4b..340839a 100644 --- a/core/edit.go +++ b/core/edit.go @@ -1,6 +1,9 @@ package core -import "github.com/rwinkhart/go-boilerplate/back" +import ( + "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/crypt" +) // GetOldEntryData decrypts and returns old entry data (with all required lines present). func GetOldEntryData(targetLocation string, field int) []string { @@ -8,7 +11,7 @@ func GetOldEntryData(targetLocation string, field int) []string { back.TargetIsFile(targetLocation, true, 2) // read old entry data - unencryptedEntry := DecryptFileToSlice(targetLocation) + unencryptedEntry := crypt.DecryptFileToSlice(targetLocation) // return the old entry data with all required lines present if field > 0 { diff --git a/core/init.go b/core/init.go index a79d8d0..ce5dca7 100644 --- a/core/init.go +++ b/core/init.go @@ -1,61 +1,15 @@ package core import ( - "os" - "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" "github.com/rwinkhart/rcw/wrappers" ) // RCWSanityCheckGen generates the RCW sanity check file for libmutton. func RCWSanityCheckGen(passphrase []byte) { - err := wrappers.GenSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase) + err := wrappers.GenSanityCheck(global.ConfigDir+global.PathSeparator+"sanity.rcw", passphrase) if err != nil { back.PrintError("Failed to generate sanity check file: "+err.Error(), back.ErrorWrite, true) } } - -// DirInit creates the libmutton directories. -// Returns: oldDeviceID (from before the directory reset; will be FSMisc if there is no pre-existing ID). -func DirInit(preserveOldConfigDir bool) string { - // create EntryRoot - err := os.MkdirAll(EntryRoot, 0700) - if err != nil { - back.PrintError("Failed to create \""+EntryRoot+"\": "+err.Error(), back.ErrorWrite, true) - } - - // get old device ID before its potential removal - oldDeviceID := GetCurrentDeviceID() - - // remove existing config directory (if it exists and not in append mode) - if !preserveOldConfigDir { - _, isAccessible := back.TargetIsFile(ConfigDir, false, 1) - if isAccessible { - err = os.RemoveAll(ConfigDir) - if err != nil { - back.PrintError("Failed to remove existing config directory: "+err.Error(), back.ErrorWrite, true) - } - } - } - - // create config directory w/devices subdirectory - err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700) - if err != nil { - back.PrintError("Failed to create \""+ConfigDir+"\": "+err.Error(), back.ErrorWrite, true) - } - - return oldDeviceID -} - -// GetOldDeviceID returns the current device ID or -// FSMisc if there is no device ID (e.g. first run). -func GetCurrentDeviceID() string { - deviceIDList := GenDeviceIDList(false) // errorOnFail is false so that nil is received when the devices directory does not exist - var deviceID string - if deviceIDList != nil && len(*deviceIDList) > 0 { // ensure not derferencing nil, which occurs when the devices directory does not exist - deviceID = (*deviceIDList)[0].Name() - } else { - deviceID = FSMisc // indicates to server that no device ID is being replaced - } - return deviceID -} diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index f767c20..ab8bc5a 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -9,11 +9,13 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/crypt" + "github.com/rwinkhart/libmutton/global" ) // WriteEntry writes entryData to an encrypted file at targetLocation. func WriteEntry(targetLocation string, entryData []byte) { - encBytes := EncryptBytes(entryData) + encBytes := crypt.EncryptBytes(entryData) err := os.WriteFile(targetLocation, encBytes, 0600) if err != nil { back.PrintError("Failed to write to file: "+err.Error(), back.ErrorWrite, true) @@ -57,13 +59,13 @@ func EntryAddPrecheck(targetLocation string) uint8 { // ensure target location does not already exist _, isAccessible := back.TargetIsFile(targetLocation, false, 0) if isAccessible { - back.PrintError("Target location already exists", ErrorTargetExists, false) + back.PrintError("Target location already exists", global.ErrorTargetExists, false) return 1 // inform interactive clients that the target location already exists } // ensure target containing directory exists and is a directory (not a file) - containingDir := targetLocation[:strings.LastIndex(targetLocation, PathSeparator)] - isFile, isAccisAccessible := back.TargetIsFile(containingDir, false, 1) - if isFile || !isAccisAccessible { + containingDir := targetLocation[:strings.LastIndex(targetLocation, global.PathSeparator)] + isFile, isAccessible := back.TargetIsFile(containingDir, false, 1) + if isFile || !isAccessible { back.PrintError("\""+containingDir+"\" is not a valid containing directory", back.ErrorTargetWrongType, false) return 2 // inform interactive clients that the containing directory is invalid } diff --git a/core/rcw.go b/crypt/rcw.go similarity index 89% rename from core/rcw.go rename to crypt/rcw.go index 05da23c..9587023 100644 --- a/core/rcw.go +++ b/crypt/rcw.go @@ -1,4 +1,4 @@ -package core +package crypt import ( "fmt" @@ -7,6 +7,7 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" + "github.com/rwinkhart/libmutton/global" "github.com/rwinkhart/rcw/daemon" "github.com/rwinkhart/rcw/wrappers" ) @@ -38,7 +39,7 @@ func DecryptFileToSlice(targetLocation string) []string { // directly to avoid waiting for socket file creation decBytes, err := wrappers.Decrypt(encBytes, passphrase) if err != nil { - back.PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) + back.PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), global.ErrorDecryption, true) } return strings.Split(string(decBytes), "\n") } @@ -63,8 +64,8 @@ func launchRCWDProcess() []byte { } var passphrase []byte for { - passphrase = GetPassphrase("RCW Passphrase:") - err := wrappers.RunSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase) + passphrase = global.GetPassphrase("RCW Passphrase:") + err := wrappers.RunSanityCheck(global.ConfigDir+global.PathSeparator+"sanity.rcw", passphrase) if err == nil { break } diff --git a/core/1globals.go b/global/1globals.go similarity index 97% rename from core/1globals.go rename to global/1globals.go index 36770b5..2df853e 100644 --- a/core/1globals.go +++ b/global/1globals.go @@ -1,4 +1,4 @@ -package core +package global type ByteInputFetcher func(prompt string) []byte diff --git a/core/2globalsUNIX.go b/global/2globalsUNIX.go similarity index 96% rename from core/2globalsUNIX.go rename to global/2globalsUNIX.go index 5ea98a2..18eb431 100644 --- a/core/2globalsUNIX.go +++ b/global/2globalsUNIX.go @@ -1,6 +1,6 @@ //go:build !windows -package core +package global import "github.com/rwinkhart/go-boilerplate/back" diff --git a/core/2globalsWIN.go b/global/2globalsWIN.go similarity index 97% rename from core/2globalsWIN.go rename to global/2globalsWIN.go index c9c3c0a..de746b7 100644 --- a/core/2globalsWIN.go +++ b/global/2globalsWIN.go @@ -1,6 +1,6 @@ //go:build windows -package core +package global import "github.com/rwinkhart/go-boilerplate/back" diff --git a/global/deviceIDs.go b/global/deviceIDs.go new file mode 100644 index 0000000..6990c59 --- /dev/null +++ b/global/deviceIDs.go @@ -0,0 +1,36 @@ +package global + +import ( + "io/fs" + "os" + + "github.com/rwinkhart/go-boilerplate/back" +) + +// GetOldDeviceID returns the current device ID or +// FSMisc if there is no device ID (e.g. first run). +func GetCurrentDeviceID() string { + deviceIDList := GenDeviceIDList(false) // errorOnFail is false so that nil is received when the devices directory does not exist + var deviceID string + if len(deviceIDList) > 0 { + deviceID = (deviceIDList)[0].Name() + } else { + deviceID = FSMisc // indicates to server that no device ID is being replaced + } + return deviceID +} + +// GenDeviceIDList returns a slice of all registered device IDs. +// Requires: errorOnFail (set to true to throw an error if the devices directory cannot be read/does not exist) +func GenDeviceIDList(errorOnFail bool) []fs.DirEntry { + // create a slice of all registered devices + deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices") + if err != nil { + if errorOnFail { + back.PrintError("Failed to read the devices directory: "+err.Error(), back.ErrorRead, true) + } else { + return nil // a nil return value indicates that the devices directory could not be read/does not exist + } + } + return deviceIDList +} diff --git a/global/init.go b/global/init.go new file mode 100644 index 0000000..0ed0b1f --- /dev/null +++ b/global/init.go @@ -0,0 +1,39 @@ +package global + +import ( + "os" + + "github.com/rwinkhart/go-boilerplate/back" +) + +// DirInit creates the libmutton directories. +// Returns: oldDeviceID (from before the directory reset; will be FSMisc if there is no pre-existing ID). +func DirInit(preserveOldConfigDir bool) string { + // create EntryRoot + err := os.MkdirAll(EntryRoot, 0700) + if err != nil { + back.PrintError("Failed to create \""+EntryRoot+"\": "+err.Error(), back.ErrorWrite, true) + } + + // get old device ID before its potential removal + oldDeviceID := GetCurrentDeviceID() + + // remove existing config directory (if it exists and not in append mode) + if !preserveOldConfigDir { + _, isAccessible := back.TargetIsFile(ConfigDir, false, 1) + if isAccessible { + err = os.RemoveAll(ConfigDir) + if err != nil { + back.PrintError("Failed to remove existing config directory: "+err.Error(), back.ErrorWrite, true) + } + } + } + + // create config directory w/devices subdirectory + err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700) + if err != nil { + back.PrintError("Failed to create \""+ConfigDir+"\": "+err.Error(), back.ErrorWrite, true) + } + + return oldDeviceID +} diff --git a/core/targetLocationFormatUNIX.go b/global/targetLocationFormatUNIX.go similarity index 94% rename from core/targetLocationFormatUNIX.go rename to global/targetLocationFormatUNIX.go index ca99a30..c04214d 100644 --- a/core/targetLocationFormatUNIX.go +++ b/global/targetLocationFormatUNIX.go @@ -1,6 +1,6 @@ //go:build !windows -package core +package global // TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform. func TargetLocationFormat(targetLocationIncomplete string) string { diff --git a/core/targetLocationFormatWIN.go b/global/targetLocationFormatWIN.go similarity index 88% rename from core/targetLocationFormatWIN.go rename to global/targetLocationFormatWIN.go index bfe1101..1d5e4d0 100644 --- a/core/targetLocationFormatWIN.go +++ b/global/targetLocationFormatWIN.go @@ -1,8 +1,10 @@ //go:build windows -package core +package global -import "strings" +import ( + "strings" +) // TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform. func TargetLocationFormat(targetLocationIncomplete string) string { diff --git a/libmuttonserver.go b/libmuttonserver.go index 7a90054..880b4af 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -8,8 +8,9 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" - "github.com/rwinkhart/libmutton/sync" + "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/synccommon" + "github.com/rwinkhart/libmutton/syncserver" ) const ansiBold = "\033[1m" @@ -37,38 +38,38 @@ func main() { case "fetch": // 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]) + syncserver.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 FSPath representing path separators - Always pass in UNIX format // stdin[2] is expected to be the NEW incomplete target location with FSPath representing path separators - Always pass in UNIX format - sync.RenameLocal(strings.ReplaceAll(stdin[1], core.FSPath, "/"), strings.ReplaceAll(stdin[2], core.FSPath, "/"), true) + synccommon.RenameLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), strings.ReplaceAll(stdin[2], global.FSPath, "/"), 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 // stdin[1] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format - sync.ShearLocal(strings.ReplaceAll(stdin[1], core.FSPath, "/"), stdin[0]) + synccommon.ShearLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), stdin[0]) case "addfolder": // add a new folder to the server // stdin[0] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format - sync.AddFolderLocal(strings.ReplaceAll(stdin[0], core.FSPath, "/")) + synccommon.AddFolderLocal(strings.ReplaceAll(stdin[0], global.FSPath, "/")) case "register": // register a new device ID // stdin[0] is expected to be the device ID // stdin[1] is expected to be the old device ID (for removal) - fileToClose, _ := os.OpenFile(core.ConfigDir+core.PathSeparator+"devices"+core.PathSeparator+stdin[0], os.O_CREATE|os.O_WRONLY, 0600) // errors ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible + fileToClose, _ := os.OpenFile(global.ConfigDir+global.PathSeparator+"devices"+global.PathSeparator+stdin[0], os.O_CREATE|os.O_WRONLY, 0600) // errors ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible _ = fileToClose.Close() - if stdin[1] != core.FSMisc { // sync.FSMisc is used to indicate that no device ID is being replaced - _ = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + stdin[1]) + if stdin[1] != global.FSMisc { // sync.FSMisc is used to indicate that no device ID is being replaced + _ = os.RemoveAll(global.ConfigDir + global.PathSeparator + "devices" + global.PathSeparator + stdin[1]) } // print EntryRoot and bool indicating OS type to stdout for client to store in config - fmt.Print(core.EntryRoot + core.FSSpace + strconv.FormatBool(core.IsWindows)) + fmt.Print(global.EntryRoot + global.FSSpace + strconv.FormatBool(global.IsWindows)) case "init": // create the necessary directories for libmuttonserver to function - core.DirInit(false) - _ = os.MkdirAll(core.ConfigDir+core.PathSeparator+"deletions", 0700) // error ignored; failure would have occurred by this point in core.DirInit + global.DirInit(false) + _ = os.MkdirAll(global.ConfigDir+global.PathSeparator+"deletions", 0700) // error ignored; failure would have occurred by this point in core.DirInit fmt.Println("libmuttonserver directories initialized") case "version": versionServer() @@ -117,5 +118,5 @@ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.` + "\n\n---------------------------------------------------------") - fmt.Print(ansiBold + "\n\n libmuttonserver" + back.AnsiReset + " Version " + core.LibmuttonVersion + "\n\n Copyright (c) 2024-2025: Randall Winkhart" + "\n\n") + fmt.Print(ansiBold + "\n\n libmuttonserver" + back.AnsiReset + " Version " + global.LibmuttonVersion + "\n\n Copyright (c) 2024-2025: Randall Winkhart" + "\n\n") } diff --git a/sync/1globals.go b/sync/1globals.go deleted file mode 100644 index d6b9900..0000000 --- a/sync/1globals.go +++ /dev/null @@ -1,5 +0,0 @@ -package sync - -import "github.com/rwinkhart/libmutton/core" - -var RootLength = len(core.EntryRoot) // length of core.EntryRoot string diff --git a/sync/client.go b/syncclient/client.go similarity index 86% rename from sync/client.go rename to syncclient/client.go index d7df6e0..8ea448f 100644 --- a/sync/client.go +++ b/syncclient/client.go @@ -1,4 +1,4 @@ -package sync +package syncclient import ( "fmt" @@ -10,17 +10,12 @@ import ( "github.com/pkg/sftp" "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/synccommon" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" ) -// ANSI color constants used only in this file -const ( - ansiDelete = "\033[38;5;1m" - ansiDownload = "\033[38;5;2m" - ansiUpload = "\033[38;5;4m" -) - // GetSSHClient returns an SSH client connection to the server (also returns the remote EntryRoot and an indicator of the server's OS). // Only supports key-based authentication (passphrases are supported for CLI-based implementations). func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { @@ -70,7 +65,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { if keyFileProtected != "true" { parsedKey, err = ssh.ParsePrivateKey(key) } else { - parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, core.GetPassphrase("Enter passphrase for your SSH keyfile:")) + parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, global.GetPassphrase("Enter passphrase for your SSH keyfile:")) } if err != nil { back.PrintError("Sync failed - Unable to parse private key: "+keyFile, back.ErrorRead, true) @@ -78,7 +73,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { // read known hosts file var hostKeyCallback ssh.HostKeyCallback - hostKeyCallback, err = knownhosts.New(back.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts") + hostKeyCallback, err = knownhosts.New(back.Home + global.PathSeparator + ".ssh" + global.PathSeparator + "known_hosts") if err != nil { back.PrintError("Sync failed - Unable to read known hosts file: "+err.Error(), back.ErrorRead, true) } @@ -96,7 +91,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { // connect to SSH server sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig) if err != nil { - back.PrintError("Sync failed - Unable to connect to remote server: "+err.Error(), core.ErrorServerConnection, false) // do not crash/close interactive clients + back.PrintError("Sync failed - Unable to connect to remote server: "+err.Error(), global.ErrorServerConnection, false) // do not crash/close interactive clients return nil, "", false } @@ -108,7 +103,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { // create a session sshSession, err := sshClient.NewSession() if err != nil { - back.PrintError("Sync failed - Unable to establish SSH session: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to establish SSH session: "+err.Error(), global.ErrorServerConnection, true) } // provide stdin data for session @@ -118,7 +113,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { var output []byte output, err = sshSession.CombinedOutput(cmd) if err != nil { - back.PrintError("Sync failed - Unable to run SSH command: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to run SSH command: "+err.Error(), global.ErrorSyncProcess, true) } // convert the output to a string and remove leading/trailing whitespace @@ -131,8 +126,8 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { // getRemoteDataFromClient returns a map of remote entries to their modification times, a list of remote folders, a list of queued deletions, and the current server&client times as UNIX timestamps. func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string]int64, []string, []string, int64, int64) { // get remote output over SSH - deviceIDList := core.GenDeviceIDList(true) - if len(*deviceIDList) == 0 { + deviceIDList := global.GenDeviceIDList(true) + if len(deviceIDList) == 0 { if manualSync { back.PrintError("Sync failed - No device ID found", back.ErrorTargetNotFound, true) } else { @@ -140,23 +135,23 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string } } clientTime := time.Now().Unix() // get client time now to avoid accuracy issues caused by unpredictable sync time - output := GetSSHOutput(sshClient, "libmuttonserver fetch", (*deviceIDList)[0].Name()) + output := GetSSHOutput(sshClient, "libmuttonserver fetch", (deviceIDList)[0].Name()) // split output into slice based on occurrences of FSSpace - outputSlice := strings.Split(output, core.FSSpace) + outputSlice := strings.Split(output, global.FSSpace) // parse output/re-form lists if len(outputSlice) != 5 { // ensure information from server is complete - back.PrintError("Sync failed - Unable to fetch remote data; server returned an unexpected response", core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to fetch remote data; server returned an unexpected response", global.ErrorSyncProcess, true) } serverTime, err := strconv.ParseInt(outputSlice[0], 10, 64) if err != nil { back.PrintError("Sync failed - Unable to parse server time: "+err.Error(), back.ErrorRead, true) } - entries := strings.Split(outputSlice[1], core.FSMisc)[1:] - modsStrings := strings.Split(outputSlice[2], core.FSMisc)[1:] - folders := strings.Split(outputSlice[3], core.FSMisc)[1:] - deletions := strings.Split(outputSlice[4], core.FSMisc)[1:] + entries := strings.Split(outputSlice[1], global.FSMisc)[1:] + modsStrings := strings.Split(outputSlice[2], global.FSMisc)[1:] + folders := strings.Split(outputSlice[3], global.FSMisc)[1:] + deletions := strings.Split(outputSlice[4], global.FSMisc)[1:] // convert the mod times to int64 var mods []int64 @@ -181,10 +176,10 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string // getLocalData returns a map of local entries to their modification times. func getLocalData() map[string]int64 { // get a list of all entries - entries, _ := WalkEntryDir() + entries, _ := synccommon.WalkEntryDir() // get a list of all entry modification times - modList := getModTimes(entries) + modList := synccommon.GetModTimes(entries) // map the entries to their modification times entryModMap := make(map[string]int64) @@ -210,12 +205,12 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // create an SFTP client from sshClient sftpClient, err := sftp.NewClient(sshClient) if err != nil { - back.PrintError("Sync failed - Unable to establish SFTP session: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to establish SFTP session: "+err.Error(), global.ErrorServerConnection, true) } defer func(sftpClient *sftp.Client) { err = sftpClient.Close() if err != nil { - back.PrintError("Sync failed - Unable to close SFTP client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SFTP client: "+err.Error(), global.ErrorServerConnection, true) } }(sftpClient) @@ -224,7 +219,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow for _, entryName := range downloadList { 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 + back.AnsiReset) + fmt.Println("Downloading " + synccommon.AnsiDownload + entryName + back.AnsiReset) // store path to remote entry remoteEntryFullPath := targetLocationFormatSFTP(entryName, sshEntryRoot, sshIsWindows) @@ -245,7 +240,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow } // store path to local entry - localEntryFullPath := core.TargetLocationFormat(entryName) + localEntryFullPath := global.TargetLocationFormat(entryName) // create local file var localFile *os.File @@ -257,7 +252,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // download the file _, err = remoteFile.WriteTo(localFile) if err != nil { - back.PrintError("Sync failed - Unable to download remote file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to download remote file: "+err.Error(), global.ErrorSyncProcess, true) } // close the files @@ -277,10 +272,10 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow for _, entryName := range uploadList { 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 + back.AnsiReset) + fmt.Println("Uploading " + synccommon.AnsiUpload + entryName + back.AnsiReset) // store path to local entry - localEntryFullPath := core.TargetLocationFormat(entryName) + localEntryFullPath := global.TargetLocationFormat(entryName) // save modification time of local file var fileInfo os.FileInfo @@ -310,7 +305,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // upload the file _, err = localFile.WriteTo(remoteFile) if err != nil { - back.PrintError("Sync failed - Unable to upload local file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to upload local file: "+err.Error(), global.ErrorSyncProcess, true) } // close the files @@ -320,7 +315,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // set permissions on remote file err = sftpClient.Chmod(remoteEntryFullPath, 0600) if err != nil { - back.PrintError("Sync failed - Unable to set permissions on remote file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to set permissions on remote file: "+err.Error(), global.ErrorSyncProcess, true) } // set the modification time of the remote file to match the value saved from the local file (from before the upload) @@ -344,23 +339,23 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSyn if remoteModTime, present := remoteEntryModMap[entry]; present { // entry exists on both client and server, compare mod times if remoteModTime > localModTime { - fmt.Println(ansiDownload+entry+back.AnsiReset, "is newer on server, adding to download list") + fmt.Println(synccommon.AnsiDownload+entry+back.AnsiReset, "is newer on server, adding to download list") downloadList = append(downloadList, entry) } else if remoteModTime < localModTime { - fmt.Println(ansiUpload+entry+back.AnsiReset, "is newer on client, adding to upload list") + fmt.Println(synccommon.AnsiUpload+entry+back.AnsiReset, "is newer on client, adding to upload list") uploadList = append(uploadList, entry) } // remove entry from remoteEntryModMap (process of elimination) delete(remoteEntryModMap, entry) } else { - fmt.Println(ansiUpload+entry+back.AnsiReset, "does not exist on server, adding to upload list") + fmt.Println(synccommon.AnsiUpload+entry+back.AnsiReset, "does not exist on server, adding to upload list") uploadList = append(uploadList, entry) } } // iterate over remaining entries in remoteEntryModMap for entry := range remoteEntryModMap { - fmt.Println(ansiDownload+entry+back.AnsiReset, "does not exist on client, adding to download list") + fmt.Println(synccommon.AnsiDownload+entry+back.AnsiReset, "does not exist on client, adding to download list") downloadList = append(downloadList, entry) } @@ -386,8 +381,8 @@ func deletionSync(deletions []string) { var filesDeleted bool for _, deletion := range deletions { filesDeleted = true // set a flag to indicate that files have been deleted (used to determine whether to print a gap between deletion and other messages) - fmt.Println(ansiDelete+deletion+back.AnsiReset, "has been sheared, removing locally (if it exists)") - err := os.RemoveAll(core.TargetLocationFormat(deletion)) + fmt.Println(synccommon.AnsiDelete+deletion+back.AnsiReset, "has been sheared, removing locally (if it exists)") + err := os.RemoveAll(global.TargetLocationFormat(deletion)) if err != nil { back.PrintError("Sync failed - Failed to shear "+deletion+" locally: "+err.Error(), back.ErrorWrite, true) } @@ -402,7 +397,7 @@ func deletionSync(deletions []string) { func folderSync(folders []string) { for _, folder := range folders { // store the full local path of the folder - folderFullPath := core.TargetLocationFormat(folder) + folderFullPath := global.TargetLocationFormat(folder) // check if folder already exists isFile, isAccessible := back.TargetIsFile(folderFullPath, false, 1) @@ -413,7 +408,7 @@ func folderSync(folders []string) { back.PrintError("Sync failed - Failed to create folder ("+folder+"): "+err.Error(), back.ErrorWrite, true) } } else if isFile { - back.PrintError("Sync failed - Failed to create folder ("+folder+") - A file with the same name already exists", core.ErrorTargetExists, true) + back.PrintError("Sync failed - Failed to create folder ("+folder+") - A file with the same name already exists", global.ErrorTargetExists, true) } } } @@ -430,7 +425,7 @@ func RunJob(manualSync, returnLists bool) [3][]string { defer func(sshClient *ssh.Client) { err := sshClient.Close() if err != nil { - back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), global.ErrorServerConnection, true) } }(sshClient) diff --git a/sync/init.go b/syncclient/init.go similarity index 78% rename from sync/init.go rename to syncclient/init.go index ac13db0..f9cd4ce 100644 --- a/sync/init.go +++ b/syncclient/init.go @@ -1,4 +1,4 @@ -package sync +package syncclient import ( "math/rand" @@ -9,6 +9,7 @@ import ( "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" ) // DeviceIDGen generates a new client device ID and registers it with the server (will replace existing one). @@ -22,14 +23,14 @@ func DeviceIDGen(oldDeviceID string) (string, string) { newDeviceID := deviceIDPrefix + "-" + deviceIDSuffix // create new device ID file (locally) - fileToClose, err := os.OpenFile(core.ConfigDir+core.PathSeparator+"devices"+core.PathSeparator+newDeviceID, os.O_CREATE|os.O_WRONLY, 0600) + fileToClose, err := os.OpenFile(global.ConfigDir+global.PathSeparator+"devices"+global.PathSeparator+newDeviceID, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { back.PrintError("Failed to create local device ID file: "+err.Error(), back.ErrorWrite, true) } _ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed // remove old device ID file (locally; may not exist) - err = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + oldDeviceID) + err = os.RemoveAll(global.ConfigDir + global.PathSeparator + "devices" + global.PathSeparator + oldDeviceID) if err != nil { back.PrintError("Failed to remove old device ID file (locally): "+err.Error(), back.ErrorWrite, true) } @@ -38,10 +39,10 @@ func DeviceIDGen(oldDeviceID string) (string, string) { // also removes the old device ID file (remotely) // manualSync is true so the user is alerted if device ID registration fails sshClient, _, _ := GetSSHClient(true) - sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput(sshClient, "libmuttonserver register", newDeviceID+"\n"+oldDeviceID), core.FSSpace) + sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput(sshClient, "libmuttonserver register", newDeviceID+"\n"+oldDeviceID), global.FSSpace) err = sshClient.Close() if err != nil { - back.PrintError("Init failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Init failed - Unable to close SSH client: "+err.Error(), global.ErrorServerConnection, true) } return sshEntryRootSSHIsWindows[0], sshEntryRootSSHIsWindows[1] diff --git a/sync/oneOff.go b/syncclient/oneOff.go similarity index 70% rename from sync/oneOff.go rename to syncclient/oneOff.go index 2fe1538..2b0290a 100644 --- a/sync/oneOff.go +++ b/syncclient/oneOff.go @@ -1,16 +1,17 @@ -package sync +package syncclient import ( "strings" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/synccommon" ) // ShearRemoteFromClient removes the target file or directory from the local system and calls the server to remove it remotely and add it to the deletions list. // It can safely be called in offline mode, as well, so this is the intended interface for shearing (ShearLocal should only be used directly by the server binary). func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) { - deviceID, isDir := ShearLocal(targetLocationIncomplete, "") // remove the target from the local system and get the device ID of the client + deviceID, isDir := synccommon.ShearLocal(targetLocationIncomplete, "") // remove the target from the local system and get the device ID of the client if !forceOffline && deviceID != "" { // ensure a device ID exists (online mode) // create an SSH client; manualSync is false in case a device ID exists but SSH is not configured @@ -22,12 +23,12 @@ func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) { } // call the server to remotely shear the target and add it to the deletions list - GetSSHOutput(sshClient, "libmuttonserver shear", deviceID+"\n"+strings.ReplaceAll(targetLocationIncomplete, core.PathSeparator, core.FSPath)) + GetSSHOutput(sshClient, "libmuttonserver shear", deviceID+"\n"+strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) // close the SSH client err := sshClient.Close() if err != nil { - back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), global.ErrorServerConnection, true) } } @@ -37,23 +38,23 @@ func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) { // RenameRemoteFromClient renames oldLocationIncomplete to newLocationIncomplete on the local system and calls the server to perform the rename remotely and add the old target to the deletions list. // It can safely be called in offline mode, as well, so this is the intended interface for renaming (RenameLocal should only be used directly by the server binary). func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, forceOffline bool) { - RenameLocal(oldLocationIncomplete, newLocationIncomplete, false) // move the target on the local system + synccommon.RenameLocal(oldLocationIncomplete, newLocationIncomplete, false) // move the target on the local system - deviceIDList := core.GenDeviceIDList(true) - if !forceOffline && len(*deviceIDList) > 0 { // ensure a device ID exists (online mode) + deviceIDList := global.GenDeviceIDList(true) + if !forceOffline && len(deviceIDList) > 0 { // ensure a device ID exists (online mode) // create an SSH client; manualSync is false in case a device ID exists but SSH is not configured sshClient, _, _ := GetSSHClient(false) // call the server to move the target on the remote system and add the old target to the deletions list GetSSHOutput(sshClient, "libmuttonserver rename", - (*deviceIDList)[0].Name()+"\n"+ - strings.ReplaceAll(oldLocationIncomplete, core.PathSeparator, core.FSPath)+"\n"+ - strings.ReplaceAll(newLocationIncomplete, core.PathSeparator, core.FSPath)) + (deviceIDList)[0].Name()+"\n"+ + strings.ReplaceAll(oldLocationIncomplete, global.PathSeparator, global.FSPath)+"\n"+ + strings.ReplaceAll(newLocationIncomplete, global.PathSeparator, global.FSPath)) // close the SSH client err := sshClient.Close() if err != nil { - back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), global.ErrorServerConnection, true) } } @@ -63,20 +64,20 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, // AddFolderRemoteFromClient creates a new entry-containing directory on the local system and calls the server to create the folder remotely. // It 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, forceOffline bool) { - AddFolderLocal(targetLocationIncomplete) // add the folder on the local system + synccommon.AddFolderLocal(targetLocationIncomplete) // add the folder on the local system - deviceIDList := core.GenDeviceIDList(true) - if !forceOffline && len(*deviceIDList) > 0 { // ensure a device ID exists (online mode) + deviceIDList := global.GenDeviceIDList(true) + if !forceOffline && len(deviceIDList) > 0 { // ensure a device ID exists (online mode) // create an SSH client; manualSync is false in case a device ID exists but SSH is not configured sshClient, _, _ := GetSSHClient(false) // call the server to create the folder remotely - GetSSHOutput(sshClient, "libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, core.PathSeparator, core.FSPath)) // call the server to create the folder remotely + GetSSHOutput(sshClient, "libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) // call the server to create the folder remotely // close the SSH client err := sshClient.Close() if err != nil { - back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), global.ErrorServerConnection, true) } } diff --git a/sync/common.go b/synccommon/common.go similarity index 70% rename from sync/common.go rename to synccommon/common.go index 28e8d7e..e1fe430 100644 --- a/sync/common.go +++ b/synccommon/common.go @@ -1,4 +1,4 @@ -package sync +package synccommon import ( "fmt" @@ -6,14 +6,23 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" ) -// getModTimes returns a list of all entry modification times. -func getModTimes(entryList []string) []int64 { +// ANSI color constants used only in this file +const ( + AnsiDelete = "\033[38;5;1m" + AnsiDownload = "\033[38;5;2m" + AnsiUpload = "\033[38;5;4m" +) + +var RootLength = len(global.EntryRoot) // length of global.EntryRoot string + +// GetModTimes returns a list of all entry modification times. +func GetModTimes(entryList []string) []int64 { var modList []int64 for _, file := range entryList { - modTime, _ := os.Stat(core.TargetLocationFormat(file)) + modTime, _ := os.Stat(global.TargetLocationFormat(file)) modList = append(modList, modTime.ModTime().Unix()) } @@ -32,13 +41,13 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) onServer = true } - deviceIDList := core.GenDeviceIDList(true) + deviceIDList := global.GenDeviceIDList(true) // 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 { - fileToClose, err := os.OpenFile(core.ConfigDir+core.PathSeparator+"deletions"+core.PathSeparator+device.Name()+core.FSSpace+strings.ReplaceAll(targetLocationIncomplete, "/", core.FSPath), os.O_CREATE|os.O_WRONLY, 0600) + fileToClose, err := os.OpenFile(global.ConfigDir+global.PathSeparator+"deletions"+global.PathSeparator+device.Name()+global.FSSpace+strings.ReplaceAll(targetLocationIncomplete, "/", global.FSPath), os.O_CREATE|os.O_WRONLY, 0600) 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) @@ -50,7 +59,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) } // get the full targetLocation path and remove the target - targetLocationComplete := core.TargetLocationFormat(targetLocationIncomplete) + targetLocationComplete := global.TargetLocationFormat(targetLocationIncomplete) var isFile bool if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist isFile, _ = back.TargetIsFile(targetLocationComplete, true, 0) @@ -60,8 +69,8 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) back.PrintError("Failed to remove local target: "+err.Error(), back.ErrorWrite, true) } - 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(), !isFile + 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(), !isFile } return "", true @@ -72,8 +81,8 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) // This function should only be used directly by the server binary. func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldLocationExists bool) { // get full paths for both locations - oldLocation := core.TargetLocationFormat(oldLocationIncomplete) - newLocation := core.TargetLocationFormat(newLocationIncomplete) + oldLocation := global.TargetLocationFormat(oldLocationIncomplete) + newLocation := global.TargetLocationFormat(newLocationIncomplete) if verifyOldLocationExists { back.TargetIsFile(oldLocation, true, 0) @@ -82,7 +91,7 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL // ensure newLocation does not exist _, isAccessible := back.TargetIsFile(newLocation, false, 0) if isAccessible { - back.PrintError("\""+newLocation+"\" already exists", core.ErrorTargetExists, true) + back.PrintError("\""+newLocation+"\" already exists", global.ErrorTargetExists, true) } // rename oldLocation to newLocation @@ -98,11 +107,11 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL // This function should only be used directly by the server binary. func AddFolderLocal(targetLocationIncomplete string) { // get the full targetLocation path and create the target - targetLocationComplete := core.TargetLocationFormat(targetLocationIncomplete) + targetLocationComplete := global.TargetLocationFormat(targetLocationIncomplete) err := os.Mkdir(targetLocationComplete, 0700) if err != nil { if os.IsExist(err) { - fmt.Println(ansiUpload + "Directory already exists - libmutton will still ensure it exists on the server") + fmt.Println(AnsiUpload + "Directory already exists - libmutton will still ensure it exists on the server") } else { back.PrintError("Failed to create directory: "+err.Error(), back.ErrorWrite, true) } diff --git a/sync/commonUNIX.go b/synccommon/walkUNIX.go similarity index 92% rename from sync/commonUNIX.go rename to synccommon/walkUNIX.go index 077c647..510f4e5 100644 --- a/sync/commonUNIX.go +++ b/synccommon/walkUNIX.go @@ -1,6 +1,6 @@ //go:build !windows -package sync +package synccommon import ( "io/fs" @@ -8,7 +8,7 @@ import ( "path/filepath" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" ) // WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists). @@ -19,7 +19,7 @@ func WalkEntryDir() ([]string, []string) { var dirList []string // walk entry directory - _ = filepath.WalkDir(core.EntryRoot, + _ = filepath.WalkDir(global.EntryRoot, func(fullPath string, entry fs.DirEntry, err error) error { // check for errors encountered while walking directory diff --git a/sync/commonWIN.go b/synccommon/walkWIN.go similarity index 93% rename from sync/commonWIN.go rename to synccommon/walkWIN.go index 3a82be5..6f61666 100644 --- a/sync/commonWIN.go +++ b/synccommon/walkWIN.go @@ -1,6 +1,6 @@ //go:build windows -package sync +package synccommon import ( "io/fs" @@ -9,7 +9,7 @@ import ( "strings" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" ) // WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists). @@ -20,7 +20,7 @@ func WalkEntryDir() ([]string, []string) { var dirList []string // walk entry directory - _ = filepath.WalkDir(core.EntryRoot, + _ = filepath.WalkDir(global.EntryRoot, func(fullPath string, entry fs.DirEntry, err error) error { // check for errors encountered while walking directory diff --git a/sync/server.go b/syncserver/server.go similarity index 59% rename from sync/server.go rename to syncserver/server.go index b7fdfe0..e79672a 100644 --- a/sync/server.go +++ b/syncserver/server.go @@ -1,4 +1,4 @@ -package sync +package syncserver import ( "fmt" @@ -7,16 +7,17 @@ import ( "time" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/libmutton/core" + "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/synccommon" ) // GetRemoteDataFromServer prints to stdout the remote entries, mod times, folders, and deletions. // 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() - modList := getModTimes(entryList) - deletionsList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "deletions") + entryList, dirList := synccommon.WalkEntryDir() + modList := synccommon.GetModTimes(entryList) + deletionsList, err := os.ReadDir(global.ConfigDir + global.PathSeparator + "deletions") if err != nil { back.PrintError("Failed to read the deletions directory: "+err.Error(), back.ErrorRead, true) } @@ -26,34 +27,34 @@ func GetRemoteDataFromServer(clientDeviceID string) { // print the lists to stdout // entry list - fmt.Print(core.FSSpace) + fmt.Print(global.FSSpace) for _, entry := range entryList { - fmt.Print(core.FSMisc + entry) + fmt.Print(global.FSMisc + entry) } // modification time list - fmt.Print(core.FSSpace) + fmt.Print(global.FSSpace) for _, mod := range modList { - fmt.Print(core.FSMisc) + fmt.Print(global.FSMisc) fmt.Print(mod) } // directory/folder list - fmt.Print(core.FSSpace) + fmt.Print(global.FSSpace) for _, dir := range dirList { - fmt.Print(core.FSMisc + dir) + fmt.Print(global.FSMisc + dir) } // deletions list - fmt.Print(core.FSSpace) + fmt.Print(global.FSSpace) for _, deletion := range deletionsList { // print deletion if it is relevant to the current client device - affectedIDTargetLocationIncomplete := strings.Split(deletion.Name(), core.FSSpace) + affectedIDTargetLocationIncomplete := strings.Split(deletion.Name(), global.FSSpace) if affectedIDTargetLocationIncomplete[0] == clientDeviceID { - fmt.Print(core.FSMisc + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], core.FSPath, "/")) + fmt.Print(global.FSMisc + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], global.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(core.ConfigDir + core.PathSeparator + "deletions" + core.PathSeparator + deletion.Name()) // error ignored; function not run from a user-facing argument and thus the error would not be visible + _ = os.Remove(global.ConfigDir + global.PathSeparator + "deletions" + global.PathSeparator + deletion.Name()) // error ignored; function not run from a user-facing argument and thus the error would not be visible } } } diff --git a/wiki/developers.md b/wiki/developers.md index 91f66eb..220ffc4 100644 --- a/wiki/developers.md +++ b/wiki/developers.md @@ -16,7 +16,7 @@ These are as follows: - `termux`: Allows creating an Android binary that can interact with the Termux clipboard (for Android) ## Required Global Variable Manipulation -libmutton provides a `GetPassphrase` global variable that all clients must set. This approach allows for different types of clients (CLI, GUI, TUI) to prompt for the passphrase in the most appropriate way. +libmutton provides a `global.GetPassphrase` global variable that all clients must set. This approach allows for different types of clients (CLI, GUI, TUI) to prompt for the passphrase in the most appropriate way. ## Required Arguments - `clipclear`: Should be accepted by all non-interactive CLI libmutton implementations (not required for interactive GUI/TUI implementations). In order to clear the clipboard on a timer, non-interactive libmutton-based password managers call another instance of their executable with the `clipclear` argument (e.g. `mutn clipclear`) with the intended clipboard contents provided via STDIN. If after 30 seconds the clipboard contents have not changed, they are cleared. Please accept a `clipclear` argument that calls `core.ClipClearArgument()`.