Refactor: Split into more packages to reduce server binary size

This commit is contained in:
2025-05-12 16:26:03 +00:00
parent 84d227b803
commit 36bbf88690
28 changed files with 242 additions and 212 deletions
+3 -2
View File
@@ -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") {
+3 -19
View File
@@ -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)
}
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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 {
+2 -1
View File
@@ -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 {
+3 -2
View File
@@ -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 {
+2 -1
View File
@@ -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 {
+5 -2
View File
@@ -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 {
+2 -48
View File
@@ -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
}
+7 -5
View File
@@ -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
}
+5 -4
View File
@@ -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
}
+1 -1
View File
@@ -1,4 +1,4 @@
package core
package global
type ByteInputFetcher func(prompt string) []byte
@@ -1,6 +1,6 @@
//go:build !windows
package core
package global
import "github.com/rwinkhart/go-boilerplate/back"
@@ -1,6 +1,6 @@
//go:build windows
package core
package global
import "github.com/rwinkhart/go-boilerplate/back"
+36
View File
@@ -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
}
+39
View File
@@ -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
}
@@ -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 {
@@ -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 {
+14 -13
View File
@@ -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")
}
-5
View File
@@ -1,5 +0,0 @@
package sync
import "github.com/rwinkhart/libmutton/core"
var RootLength = len(core.EntryRoot) // length of core.EntryRoot string
+37 -42
View File
@@ -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)
+6 -5
View File
@@ -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]
+18 -17
View File
@@ -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)
}
}
+25 -16
View File
@@ -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)
}
@@ -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
+3 -3
View File
@@ -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
+16 -15
View File
@@ -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
}
}
}
+1 -1
View File
@@ -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()`.