Rename "backend" package to "core"

This commit is contained in:
2024-08-08 16:49:34 -04:00
parent ba4f7f2340
commit c83a3aa018
25 changed files with 109 additions and 107 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"os" "os"
@@ -1,6 +1,6 @@
//go:build !windows //go:build !windows
package backend package core
// EntryRoot path to libmutton entry directory // EntryRoot path to libmutton entry directory
var EntryRoot = Home + "/.local/share/libmutton" var EntryRoot = Home + "/.local/share/libmutton"
@@ -1,6 +1,6 @@
//go:build windows //go:build windows
package backend package core
import ( import (
"os" "os"
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"fmt" "fmt"
+2 -2
View File
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"bufio" "bufio"
@@ -67,7 +67,7 @@ func ClipClearArgument() {
oldContents := clipScanner.Text() oldContents := clipScanner.Text()
clipClear(oldContents) clipClear(oldContents)
} else { } else {
os.Exit(0) // use os.Exit instead of backend.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
//go:build darwin //go:build darwin
package backend package core
import ( import (
"fmt" "fmt"
@@ -55,5 +55,5 @@ func clipClear(oldContents string) {
os.Exit(1) os.Exit(1)
} }
} }
os.Exit(0) // use os.Exit instead of backend.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
+2 -2
View File
@@ -1,6 +1,6 @@
//go:build linux && termux //go:build linux && termux
package backend package core
import ( import (
"fmt" "fmt"
@@ -53,5 +53,5 @@ func clipClear(oldContents string) {
os.Exit(1) os.Exit(1)
} }
} }
os.Exit(0) // use os.Exit instead of backend.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
@@ -1,6 +1,6 @@
//go:build !windows && !darwin && !termux && !wsl //go:build !windows && !darwin && !termux && !wsl
package backend package core
import ( import (
"fmt" "fmt"
@@ -77,5 +77,5 @@ func clipClear(oldContents string) {
os.Exit(1) os.Exit(1)
} }
} }
os.Exit(0) // use os.Exit instead of backend.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
+2 -2
View File
@@ -1,6 +1,6 @@
//go:build windows || (linux && wsl) //go:build windows || (linux && wsl)
package backend package core
import ( import (
"fmt" "fmt"
@@ -51,5 +51,5 @@ func clipClear(oldContents string) {
os.Exit(1) os.Exit(1)
} }
} }
os.Exit(0) // use os.Exit instead of backend.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
+1 -1
View File
@@ -1,4 +1,4 @@
package backend package core
// GetOldEntryData decrypts and returns old entry data (with all required lines present) // GetOldEntryData decrypts and returns old entry data (with all required lines present)
func GetOldEntryData(targetLocation string, field int) []string { func GetOldEntryData(targetLocation string, field int) []string {
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build !returnOnExit //go:build !returnOnExit
package backend package core
import "os" import "os"
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build returnOnExit //go:build returnOnExit
package backend package core
func Exit(code int) { func Exit(code int) {
return code return code
+1 -1
View File
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"fmt" "fmt"
+1 -1
View File
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"fmt" "fmt"
@@ -1,6 +1,6 @@
//go:build !windows //go:build !windows
package backend package core
// TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform // TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform
func TargetLocationFormat(targetLocationIncomplete string) string { func TargetLocationFormat(targetLocationIncomplete string) string {
@@ -1,6 +1,6 @@
//go:build windows //go:build windows
package backend package core
import "strings" import "strings"
@@ -1,4 +1,4 @@
package backend package core
import ( import (
"crypto/rand" "crypto/rand"
+3 -3
View File
@@ -1,6 +1,6 @@
package sync package sync
import "github.com/rwinkhart/MUTN/src/backend" import "github.com/rwinkhart/libmutton/core"
// define field separator constants // define field separator constants
const ( const (
@@ -9,5 +9,5 @@ const (
FSMisc = "\u259f" // ▟ misc. field separator (if \u259d is already used) FSMisc = "\u259f" // ▟ misc. field separator (if \u259d is already used)
) )
// rootLength stores length of backend.EntryRoot string // rootLength stores length of core.EntryRoot string
var rootLength = len(backend.EntryRoot) var rootLength = len(core.EntryRoot)
+45 -45
View File
@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/pkg/sftp" "github.com/pkg/sftp"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/libmutton/core"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts" "golang.org/x/crypto/ssh/knownhosts"
) )
@@ -31,7 +31,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) {
} else { } else {
missingValueError = "0" missingValueError = "0"
} }
sshUserConfig = backend.ParseConfig([][2]string{{"LIBMUTTON", "sshUser"}, {"LIBMUTTON", "sshIP"}, {"LIBMUTTON", "sshPort"}, {"LIBMUTTON", "sshKey"}, {"LIBMUTTON", "sshKeyProtected"}, {"LIBMUTTON", "sshEntryRoot"}, {"LIBMUTTON", "sshIsWindows"}}, missingValueError) sshUserConfig = core.ParseConfig([][2]string{{"LIBMUTTON", "sshUser"}, {"LIBMUTTON", "sshIP"}, {"LIBMUTTON", "sshPort"}, {"LIBMUTTON", "sshKey"}, {"LIBMUTTON", "sshKeyProtected"}, {"LIBMUTTON", "sshEntryRoot"}, {"LIBMUTTON", "sshIsWindows"}}, missingValueError)
var user, ip, port, keyFile, keyFileProtected, entryRoot string var user, ip, port, keyFile, keyFileProtected, entryRoot string
var isWindows bool var isWindows bool
@@ -57,7 +57,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) {
// read private key // read private key
key, err := os.ReadFile(keyFile) key, err := os.ReadFile(keyFile)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - unable to read private key file:", keyFile+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - unable to read private key file:", keyFile+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -69,14 +69,14 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) {
parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, inputKeyFilePassphrase()) parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, inputKeyFilePassphrase())
} }
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to parse private key:", keyFile+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to parse private key:", keyFile+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
// read known hosts file // read known hosts file
hostKeyCallback, err := knownhosts.New(backend.Home + backend.PathSeparator + ".ssh" + backend.PathSeparator + "known_hosts") hostKeyCallback, err := knownhosts.New(core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts")
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Sync failed - Unable to read known hosts file:" + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Sync failed - Unable to read known hosts file:" + err.Error() + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -92,7 +92,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) {
// connect to SSH server // connect to SSH server
sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig) sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to connect to remote server:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to connect to remote server:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -108,7 +108,7 @@ func GetSSHOutput(cmd, stdin string, manualSync bool) string {
// create a session // create a session
sshSession, err := sshClient.NewSession() sshSession, err := sshClient.NewSession()
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to establish SSH session:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to establish SSH session:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -119,7 +119,7 @@ func GetSSHOutput(cmd, stdin string, manualSync bool) string {
var output []byte var output []byte
output, err = sshSession.CombinedOutput(cmd) output, err = sshSession.CombinedOutput(cmd)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to run SSH command:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to run SSH command:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -133,13 +133,13 @@ func GetSSHOutput(cmd, stdin string, manualSync bool) 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 // 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(manualSync bool) (map[string]int64, []string, []string, int64, int64) { func getRemoteDataFromClient(manualSync bool) (map[string]int64, []string, []string, int64, int64) {
// get remote output over SSH // get remote output over SSH
clientDeviceID, _ := os.ReadDir(backend.ConfigDir + backend.PathSeparator + "devices") clientDeviceID, _ := os.ReadDir(core.ConfigDir + core.PathSeparator + "devices")
if len(clientDeviceID) == 0 { if len(clientDeviceID) == 0 {
if manualSync { if manualSync {
fmt.Println(joinErrorWithEXE("Sync failed - No device ID found; run \"", " init\" to generate a device ID")) fmt.Println(joinErrorWithEXE("Sync failed - No device ID found; run \"", " init\" to generate a device ID"))
os.Exit(1) os.Exit(1)
} else { } else {
backend.Exit(0) // exit silently if the sync job was called automatically, as the user may just be in offline mode core.Exit(0) // exit silently if the sync job was called automatically, as the user may just be in offline mode
} }
} }
clientTime := time.Now().Unix() // get client time now to avoid accuracy issues caused by unpredictable sync time clientTime := time.Now().Unix() // get client time now to avoid accuracy issues caused by unpredictable sync time
@@ -150,7 +150,7 @@ func getRemoteDataFromClient(manualSync bool) (map[string]int64, []string, []str
// parse output/re-form lists // parse output/re-form lists
if len(outputSlice) != 5 { // ensure information from server is complete if len(outputSlice) != 5 { // ensure information from server is complete
fmt.Println(backend.AnsiError + "Sync failed - Unable to fetch remote data; server returned an unexpected response" + backend.AnsiReset) fmt.Println(core.AnsiError + "Sync failed - Unable to fetch remote data; server returned an unexpected response" + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
serverTime, _ := strconv.ParseInt(outputSlice[0], 10, 64) serverTime, _ := strconv.ParseInt(outputSlice[0], 10, 64)
@@ -211,7 +211,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
// create an SFTP client // create an SFTP client
sftpClient, err := sftp.NewClient(sshClient) sftpClient, err := sftp.NewClient(sshClient)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to establish SFTP session:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to establish SFTP session:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
defer sftpClient.Close() defer sftpClient.Close()
@@ -221,7 +221,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
for _, entryName := range downloadList { 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) filesTransferred = true // set a flag to indicate that files have been downloaded (used to determine whether to print a gap between download and upload messages)
fmt.Println("Downloading " + ansiDownload + entryName + backend.AnsiReset) fmt.Println("Downloading " + ansiDownload + entryName + core.AnsiReset)
// store path to remote entry // store path to remote entry
remoteEntryFullPath := targetLocationFormatSFTP(entryName, sshEntryRoot, sshIsWindows) remoteEntryFullPath := targetLocationFormatSFTP(entryName, sshEntryRoot, sshIsWindows)
@@ -230,7 +230,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var fileInfo os.FileInfo var fileInfo os.FileInfo
fileInfo, err = sftpClient.Stat(remoteEntryFullPath) fileInfo, err = sftpClient.Stat(remoteEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to get remote file info (mod time):", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to get remote file info (mod time):", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
modTime := fileInfo.ModTime() modTime := fileInfo.ModTime()
@@ -239,25 +239,25 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var remoteFile *sftp.File var remoteFile *sftp.File
remoteFile, err = sftpClient.Open(remoteEntryFullPath) remoteFile, err = sftpClient.Open(remoteEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to open remote file:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to open remote file:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
// store path to local entry // store path to local entry
localEntryFullPath := backend.TargetLocationFormat(entryName) localEntryFullPath := core.TargetLocationFormat(entryName)
// create local file // create local file
var localFile *os.File var localFile *os.File
localFile, err = os.Create(localEntryFullPath) localFile, err = os.Create(localEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to create local file:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to create local file:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
// download the file // download the file
_, err = remoteFile.WriteTo(localFile) _, err = remoteFile.WriteTo(localFile)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to download remote file:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to download remote file:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -278,16 +278,16 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
for _, entryName := range uploadList { 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) filesTransferred = true // set a flag to indicate that files have been uploaded (used to determine whether to print a gap between upload and sync complete messages)
fmt.Println("Uploading " + ansiUpload + entryName + backend.AnsiReset) fmt.Println("Uploading " + ansiUpload + entryName + core.AnsiReset)
// store path to local entry // store path to local entry
localEntryFullPath := backend.TargetLocationFormat(entryName) localEntryFullPath := core.TargetLocationFormat(entryName)
// save modification time of local file // save modification time of local file
var fileInfo os.FileInfo var fileInfo os.FileInfo
fileInfo, err = os.Stat(localEntryFullPath) fileInfo, err = os.Stat(localEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to get local file info (mod time):", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to get local file info (mod time):", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
modTime := fileInfo.ModTime() modTime := fileInfo.ModTime()
@@ -296,7 +296,7 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var localFile *os.File var localFile *os.File
localFile, err = os.Open(localEntryFullPath) localFile, err = os.Open(localEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to open local file:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to open local file:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -307,14 +307,14 @@ func sftpSync(downloadList, uploadList []string, manualSync bool) {
var remoteFile *sftp.File var remoteFile *sftp.File
remoteFile, err = sftpClient.Create(remoteEntryFullPath) remoteFile, err = sftpClient.Create(remoteEntryFullPath)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to create remote file ("+remoteEntryFullPath+"):", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to create remote file ("+remoteEntryFullPath+"):", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
// upload the file // upload the file
_, err = localFile.WriteTo(remoteFile) _, err = localFile.WriteTo(remoteFile)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to upload local file:", err.Error()+backend.AnsiReset) fmt.Println(core.AnsiError+"Sync failed - Unable to upload local file:", err.Error()+core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -342,7 +342,7 @@ func syncLists(localEntryModMap, remoteEntryModMap map[string]int64, manualSync
timeDiff := serverTime - clientTime timeDiff := serverTime - clientTime
if timeDiff < -45 || timeDiff > 45 { if timeDiff < -45 || timeDiff > 45 {
timeSynced = false timeSynced = false
fmt.Print(backend.AnsiError + "Client and server clocks are out of sync.\n\nPlease ensure both clocks are correct before attempting to sync again.\n\nA dry sync output will be printed below (if any operations would have been performed). It is strongly recommended to review it and manually update the modification times as applicable to ensure the correct version of each entry is kept.\n\nIf the client's clock is at fault, update the modification times of any entries pending upload, even if the correct (upload) operation is being performed on them. Failure to do so could result in entries being uploaded to the server with the incorrect modification times (could result in data loss).\n\n" + backend.AnsiReset) fmt.Print(core.AnsiError + "Client and server clocks are out of sync.\n\nPlease ensure both clocks are correct before attempting to sync again.\n\nA dry sync output will be printed below (if any operations would have been performed). It is strongly recommended to review it and manually update the modification times as applicable to ensure the correct version of each entry is kept.\n\nIf the client's clock is at fault, update the modification times of any entries pending upload, even if the correct (upload) operation is being performed on them. Failure to do so could result in entries being uploaded to the server with the incorrect modification times (could result in data loss).\n\n" + core.AnsiReset)
} }
// iterate over client entries // iterate over client entries
@@ -351,23 +351,23 @@ func syncLists(localEntryModMap, remoteEntryModMap map[string]int64, manualSync
if remoteModTime, present := remoteEntryModMap[entry]; present { if remoteModTime, present := remoteEntryModMap[entry]; present {
// entry exists on both client and server, compare mod times // entry exists on both client and server, compare mod times
if remoteModTime > localModTime { if remoteModTime > localModTime {
fmt.Println(ansiDownload+entry+backend.AnsiReset, "is newer on server, adding to download list") fmt.Println(ansiDownload+entry+core.AnsiReset, "is newer on server, adding to download list")
downloadList = append(downloadList, entry) downloadList = append(downloadList, entry)
} else if remoteModTime < localModTime { } else if remoteModTime < localModTime {
fmt.Println(ansiUpload+entry+backend.AnsiReset, "is newer on client, adding to upload list") fmt.Println(ansiUpload+entry+core.AnsiReset, "is newer on client, adding to upload list")
uploadList = append(uploadList, entry) uploadList = append(uploadList, entry)
} }
// remove entry from remoteEntryModMap (process of elimination) // remove entry from remoteEntryModMap (process of elimination)
delete(remoteEntryModMap, entry) delete(remoteEntryModMap, entry)
} else { } else {
fmt.Println(ansiUpload+entry+backend.AnsiReset, "does not exist on server, adding to upload list") fmt.Println(ansiUpload+entry+core.AnsiReset, "does not exist on server, adding to upload list")
uploadList = append(uploadList, entry) uploadList = append(uploadList, entry)
} }
} }
// iterate over remaining entries in remoteEntryModMap // iterate over remaining entries in remoteEntryModMap
for entry := range remoteEntryModMap { for entry := range remoteEntryModMap {
fmt.Println(ansiDownload+entry+backend.AnsiReset, "does not exist on client, adding to download list") fmt.Println(ansiDownload+entry+core.AnsiReset, "does not exist on client, adding to download list")
downloadList = append(downloadList, entry) downloadList = append(downloadList, entry)
} }
@@ -377,7 +377,7 @@ func syncLists(localEntryModMap, remoteEntryModMap map[string]int64, manualSync
sftpSync(downloadList, uploadList, manualSync) sftpSync(downloadList, uploadList, manualSync)
} else if !timeSynced { } else if !timeSynced {
// do not call sftpSync if the client and server times are out of sync // do not call sftpSync if the client and server times are out of sync
backend.Exit(1) core.Exit(1)
} }
fmt.Println("Client is synchronized with server") fmt.Println("Client is synchronized with server")
@@ -388,8 +388,8 @@ func deletionSync(deletions []string) {
var filesDeleted bool var filesDeleted bool
for _, deletion := range deletions { 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) 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+backend.AnsiReset, "has been sheared, removing locally (if it exists)") fmt.Println(ansiDelete+deletion+core.AnsiReset, "has been sheared, removing locally (if it exists)")
os.RemoveAll(backend.TargetLocationFormat(deletion)) os.RemoveAll(core.TargetLocationFormat(deletion))
} }
if filesDeleted { if filesDeleted {
@@ -405,10 +405,10 @@ func ShearRemoteFromClient(targetLocationIncomplete string) {
if deviceID != "" { // ensure a device ID exists (online mode) if deviceID != "" { // ensure a device ID exists (online mode)
// call the server to remotely shear the target and add it to the deletions list // call the server to remotely shear the target and add it to the deletions list
GetSSHOutput("libmuttonserver shear", deviceID+"\n"+ GetSSHOutput("libmuttonserver shear", deviceID+"\n"+
strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, FSPath), false) strings.ReplaceAll(targetLocationIncomplete, core.PathSeparator, FSPath), false)
} }
backend.Exit(0) // sync is not required after shearing since the target has already been removed from the local system core.Exit(0) // sync is not required after shearing since the target has already been removed from the local system
} }
// 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 // 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
@@ -421,35 +421,35 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string)
// call the server to move the target on the remote system and add the old target to the deletions list // call the server to move the target on the remote system and add the old target to the deletions list
GetSSHOutput("libmuttonserver rename", GetSSHOutput("libmuttonserver rename",
(*deviceIDList)[0].Name()+"\n"+ (*deviceIDList)[0].Name()+"\n"+
strings.ReplaceAll(oldLocationIncomplete, backend.PathSeparator, FSPath)+"\n"+ strings.ReplaceAll(oldLocationIncomplete, core.PathSeparator, FSPath)+"\n"+
strings.ReplaceAll(newLocationIncomplete, backend.PathSeparator, FSPath), false) strings.ReplaceAll(newLocationIncomplete, core.PathSeparator, FSPath), false)
} }
backend.Exit(0) core.Exit(0)
} }
// AddFolderRemoteFromClient creates a new entry-containing directory on the local system and calls the server to create the folder remotely // AddFolderRemoteFromClient creates a new entry-containing directory on the local system and calls the server to create the folder remotely
// 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) // 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) { func AddFolderRemoteFromClient(targetLocationIncomplete string) {
AddFolderLocal(targetLocationIncomplete) // add the folder on the local system AddFolderLocal(targetLocationIncomplete) // add the folder on the local system
GetSSHOutput("libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, backend.PathSeparator, FSPath), false) // call the server to create the folder remotely GetSSHOutput("libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, core.PathSeparator, FSPath), false) // call the server to create the folder remotely
backend.Exit(0) core.Exit(0)
} }
// folderSync creates folders on the client (from the given list of folder names) // folderSync creates folders on the client (from the given list of folder names)
func folderSync(folders []string) { func folderSync(folders []string) {
for _, folder := range folders { for _, folder := range folders {
// store the full local path of the folder // store the full local path of the folder
folderFullPath := backend.TargetLocationFormat(folder) folderFullPath := core.TargetLocationFormat(folder)
// check if folder already exists // check if folder already exists
isFile, isAccessible := backend.TargetIsFile(folderFullPath, false, 1) isFile, isAccessible := core.TargetIsFile(folderFullPath, false, 1)
if !isFile && !isAccessible { if !isFile && !isAccessible {
os.MkdirAll(folderFullPath, 0700) os.MkdirAll(folderFullPath, 0700)
} else if isFile { } else if isFile {
fmt.Println(backend.AnsiError + "Sync failed - Failed to create folder \"" + folder + "\" - a file with the same name already exists" + backend.AnsiReset) fmt.Println(core.AnsiError + "Sync failed - Failed to create folder \"" + folder + "\" - a file with the same name already exists" + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
} }
@@ -473,5 +473,5 @@ func RunJob(manualSync bool) {
syncLists(localEntryModMap, remoteEntryModMap, manualSync, serverTime, clientTime) syncLists(localEntryModMap, remoteEntryModMap, manualSync, serverTime, clientTime)
// exit program after successful sync // exit program after successful sync
backend.Exit(0) core.Exit(0)
} }
+17 -17
View File
@@ -6,14 +6,14 @@ import (
"os" "os"
"strings" "strings"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/libmutton/core"
) )
// getModTimes returns a list of all entry modification times // getModTimes returns a list of all entry modification times
func getModTimes(entryList []string) []int64 { func getModTimes(entryList []string) []int64 {
var modList []int64 var modList []int64
for _, file := range entryList { for _, file := range entryList {
modTime, _ := os.Stat(backend.TargetLocationFormat(file)) modTime, _ := os.Stat(core.TargetLocationFormat(file))
modList = append(modList, modTime.ModTime().Unix()) modList = append(modList, modTime.ModTime().Unix())
} }
@@ -23,9 +23,9 @@ func getModTimes(entryList []string) []int64 {
// genDeviceIDList returns a pointer to a slice of all registered device IDs // genDeviceIDList returns a pointer to a slice of all registered device IDs
func genDeviceIDList() *[]fs.DirEntry { func genDeviceIDList() *[]fs.DirEntry {
// create a slice of all registered devices // create a slice of all registered devices
deviceIDList, err := os.ReadDir(backend.ConfigDir + backend.PathSeparator + "devices") deviceIDList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "devices")
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to read the devices directory: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to read the devices directory: " + err.Error() + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
return &deviceIDList return &deviceIDList
@@ -48,7 +48,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string {
if onServer { if onServer {
for _, device := range *deviceIDList { for _, device := range *deviceIDList {
if device.Name() != clientDeviceID { if device.Name() != clientDeviceID {
_, err := os.Create(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + device.Name() + FSSpace + strings.ReplaceAll(targetLocationIncomplete, "/", FSPath)) _, err := os.Create(core.ConfigDir + core.PathSeparator + "deletions" + core.PathSeparator + device.Name() + FSSpace + strings.ReplaceAll(targetLocationIncomplete, "/", FSPath))
if err != nil { if err != nil {
// do not print error as there is currently no way of seeing server-side errors // do not print error as there is currently no way of seeing server-side errors
// failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical) // failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical)
@@ -59,13 +59,13 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string {
} }
// get the full targetLocation path and remove the target // get the full targetLocation path and remove the target
targetLocationComplete := backend.TargetLocationFormat(targetLocationIncomplete) targetLocationComplete := core.TargetLocationFormat(targetLocationIncomplete)
if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist
backend.TargetIsFile(targetLocationComplete, true, 0) core.TargetIsFile(targetLocationComplete, true, 0)
} }
err := os.RemoveAll(targetLocationComplete) err := os.RemoveAll(targetLocationComplete)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to remove local target: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to remove local target: " + err.Error() + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -81,24 +81,24 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string {
// this function should only be used directly by the server binary // this function should only be used directly by the server binary
func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldLocationExists bool) { func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldLocationExists bool) {
// get full paths for both locations // get full paths for both locations
oldLocation := backend.TargetLocationFormat(oldLocationIncomplete) oldLocation := core.TargetLocationFormat(oldLocationIncomplete)
newLocation := backend.TargetLocationFormat(newLocationIncomplete) newLocation := core.TargetLocationFormat(newLocationIncomplete)
if verifyOldLocationExists { if verifyOldLocationExists {
backend.TargetIsFile(oldLocation, true, 0) core.TargetIsFile(oldLocation, true, 0)
} }
// ensure newLocation does not exist // ensure newLocation does not exist
_, isAccessible := backend.TargetIsFile(newLocation, false, 0) _, isAccessible := core.TargetIsFile(newLocation, false, 0)
if isAccessible { if isAccessible {
fmt.Println(backend.AnsiError + "\"" + newLocation + "\" already exists" + backend.AnsiReset) fmt.Println(core.AnsiError + "\"" + newLocation + "\" already exists" + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
// rename oldLocation to newLocation // rename oldLocation to newLocation
err := os.Rename(oldLocation, newLocation) err := os.Rename(oldLocation, newLocation)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to rename - does the target containing directory exist?" + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to rename - does the target containing directory exist?" + core.AnsiReset)
} }
// do not exit program, as this function is used as part of RenameRemoteFromClient // do not exit program, as this function is used as part of RenameRemoteFromClient
@@ -108,14 +108,14 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL
// this function should only be used directly by the server binary // this function should only be used directly by the server binary
func AddFolderLocal(targetLocationIncomplete string) { func AddFolderLocal(targetLocationIncomplete string) {
// get the full targetLocation path and create the target // get the full targetLocation path and create the target
targetLocationComplete := backend.TargetLocationFormat(targetLocationIncomplete) targetLocationComplete := core.TargetLocationFormat(targetLocationIncomplete)
err := os.Mkdir(targetLocationComplete, 0700) err := os.Mkdir(targetLocationComplete, 0700)
if err != nil { if err != nil {
if os.IsExist(err) { if os.IsExist(err) {
fmt.Println(backend.AnsiError + "Directory already exists" + backend.AnsiReset) fmt.Println(core.AnsiError + "Directory already exists" + core.AnsiReset)
os.Exit(1) os.Exit(1)
} else { } else {
fmt.Println(backend.AnsiError + "Failed to create directory: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to create directory: " + err.Error() + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
} }
+6 -5
View File
@@ -4,10 +4,11 @@ package sync
import ( import (
"fmt" "fmt"
"github.com/rwinkhart/MUTN/src/backend"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"github.com/rwinkhart/libmutton/core"
) )
// WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists) // WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists)
@@ -18,16 +19,16 @@ func WalkEntryDir() ([]string, []string) {
var dirList []string var dirList []string
// walk entry directory // walk entry directory
_ = filepath.WalkDir(backend.EntryRoot, _ = filepath.WalkDir(core.EntryRoot,
func(fullPath string, entry fs.DirEntry, err error) error { func(fullPath string, entry fs.DirEntry, err error) error {
// check for errors encountered while walking directory // check for errors encountered while walking directory
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
fmt.Println(backend.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+backend.AnsiReset) fmt.Println(core.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+core.AnsiReset)
} else { } else {
// otherwise, print the source of the error // otherwise, print the source of the error
fmt.Println(backend.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset)
} }
os.Exit(1) os.Exit(1)
} }
@@ -50,5 +51,5 @@ func WalkEntryDir() ([]string, []string) {
// joinErrorWithEXE joins and returns the two strings it is provided (in error format) with the executable name inserted between them // joinErrorWithEXE joins and returns the two strings it is provided (in error format) with the executable name inserted between them
func joinErrorWithEXE(firstHalf, secondHalf string) string { func joinErrorWithEXE(firstHalf, secondHalf string) string {
return backend.AnsiError + firstHalf + os.Args[0] + secondHalf + backend.AnsiReset return core.AnsiError + firstHalf + os.Args[0] + secondHalf + core.AnsiReset
} }
+5 -4
View File
@@ -4,11 +4,12 @@ package sync
import ( import (
"fmt" "fmt"
"github.com/rwinkhart/MUTN/src/backend"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/rwinkhart/libmutton/core"
) )
// WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists) // WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists)
@@ -19,7 +20,7 @@ func WalkEntryDir() ([]string, []string) {
var dirList []string var dirList []string
// walk entry directory // walk entry directory
_ = filepath.WalkDir(backend.EntryRoot, _ = filepath.WalkDir(core.EntryRoot,
func(fullPath string, entry fs.DirEntry, err error) error { func(fullPath string, entry fs.DirEntry, err error) error {
// check for errors encountered while walking directory // check for errors encountered while walking directory
@@ -28,7 +29,7 @@ func WalkEntryDir() ([]string, []string) {
fmt.Println(joinErrorWithEXE("The entry directory does not exist - run \"", " init"+"\" to create it")) fmt.Println(joinErrorWithEXE("The entry directory does not exist - run \"", " init"+"\" to create it"))
} else { } else {
// otherwise, print the source of the error // otherwise, print the source of the error
fmt.Println(backend.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset)
} }
os.Exit(1) os.Exit(1)
} }
@@ -51,5 +52,5 @@ func WalkEntryDir() ([]string, []string) {
// joinErrorWithEXE joins and returns the two strings it is provided (in error format) with the executable name inserted between them // joinErrorWithEXE joins and returns the two strings it is provided (in error format) with the executable name inserted between them
func joinErrorWithEXE(firstHalf, secondHalf string) string { func joinErrorWithEXE(firstHalf, secondHalf string) string {
return backend.AnsiError + firstHalf + os.Args[0][strings.LastIndex(os.Args[0], "\\")+1:] + secondHalf + backend.AnsiReset return core.AnsiError + firstHalf + os.Args[0][strings.LastIndex(os.Args[0], "\\")+1:] + secondHalf + core.AnsiReset
} }
+5 -5
View File
@@ -8,20 +8,20 @@ import (
"strings" "strings"
"time" "time"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/libmutton/core"
) )
// DeviceIDGen generates a new client device ID and registers it with the server // DeviceIDGen generates a new client device ID and registers it with the server
// device IDs are only needed for online synchronization // device IDs are only needed for online synchronization
// device IDs are guaranteed unique as the current UNIX time is appended to them // device IDs are guaranteed unique as the current UNIX time is appended to them
// returns the remote EntryRoot and OS type (OS type is a bool: backend.IsWindows) // returns the remote EntryRoot and OS type (OS type is a bool: core.IsWindows)
func DeviceIDGen() (string, string) { func DeviceIDGen() (string, string) {
deviceIDPrefix, _ := os.Hostname() deviceIDPrefix, _ := os.Hostname()
deviceIDSuffix := backend.StringGen(rand.Intn(32)+48, true, 0.2, true) + "-" + strconv.FormatInt(time.Now().Unix(), 10) deviceIDSuffix := core.StringGen(rand.Intn(32)+48, true, 0.2, true) + "-" + strconv.FormatInt(time.Now().Unix(), 10)
deviceID := deviceIDPrefix + "-" + deviceIDSuffix deviceID := deviceIDPrefix + "-" + deviceIDSuffix
_, err := os.Create(backend.ConfigDir + backend.PathSeparator + "devices" + backend.PathSeparator + deviceID) // TODO remove existing device ID file if it exists (from both client and server) _, err := os.Create(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + deviceID) // TODO remove existing device ID file if it exists (from both client and server)
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to create local device ID file: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to create local device ID file: " + err.Error() + core.AnsiReset)
} }
// register device ID with server and fetch remote EntryRoot and OS type // register device ID with server and fetch remote EntryRoot and OS type
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/libmutton/core"
) )
// GetRemoteDataFromServer prints to stdout the remote entries, mod times, folders, and deletions // GetRemoteDataFromServer prints to stdout the remote entries, mod times, folders, and deletions
@@ -15,9 +15,9 @@ import (
func GetRemoteDataFromServer(clientDeviceID string) { func GetRemoteDataFromServer(clientDeviceID string) {
entryList, dirList := WalkEntryDir() entryList, dirList := WalkEntryDir()
modList := getModTimes(entryList) modList := getModTimes(entryList)
deletionsList, err := os.ReadDir(backend.ConfigDir + backend.PathSeparator + "deletions") deletionsList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "deletions")
if err != nil { if err != nil {
fmt.Println(backend.AnsiError + "Failed to read the deletions directory: " + err.Error() + backend.AnsiReset) fmt.Println(core.AnsiError + "Failed to read the deletions directory: " + err.Error() + core.AnsiReset)
os.Exit(1) os.Exit(1)
} }
@@ -53,7 +53,7 @@ func GetRemoteDataFromServer(clientDeviceID string) {
fmt.Print(FSMisc + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], FSPath, "/")) fmt.Print(FSMisc + strings.ReplaceAll(affectedIDTargetLocationIncomplete[1], FSPath, "/"))
// assume successful client deletion and remove deletions file (if assumption is somehow false, worst case scenario is that the client will re-upload the deleted entry) // assume successful client deletion and remove deletions file (if assumption is somehow false, worst case scenario is that the client will re-upload the deleted entry)
_ = os.Remove(backend.ConfigDir + backend.PathSeparator + "deletions" + backend.PathSeparator + deletion.Name()) // error ignored; function not run from a user-facing argument and thus the error would not be visible _ = 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
} }
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@
libmutton was designed to be usable as a library for building other compatible password managers off of. [MUTN](https://github.com/rwinkhart/MUTN) is the official reference CLI password manager, however libmutton can be implemented in many other unique ways. libmutton was designed to be usable as a library for building other compatible password managers off of. [MUTN](https://github.com/rwinkhart/MUTN) is the official reference CLI password manager, however libmutton can be implemented in many other unique ways.
All functionality in the `backend` and `sync` packages are designed to be used in other implementations. All functionality in the `core` and `sync` packages are designed to be used in other implementations.
If any functionality in these two packages proves to be difficult to implement in a third-party client, please open an issue so that it can be addressed. If any functionality in these two packages proves to be difficult to implement in a third-party client, please open an issue so that it can be addressed.
@@ -17,7 +17,7 @@ These are as follows:
## Required Arguments ## Required Arguments
libmutton-based password manager clients should accept at least one specific required argument, as well as another recommended one: libmutton-based password manager clients should accept at least one specific required argument, as well as another recommended one:
- `clipclear`: This argument is required for correct functionality. In order to clear the clipboard on a timer, 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 is processed before the launch of any interactive interface. All this argument needs to do is call `backend.ClipClearArgument()`. - `clipclear`: This argument is required for correct functionality. In order to clear the clipboard on a timer, 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 is processed before the launch of any interactive interface. All this argument needs to do is call `core.ClipClearArgument()`.
- `init`: This argument is optional, but recommended for CLI interfaces. Some error messages request the user to use the `init` argument to fix configuration issues. If the argument does not exist, this may confuse the user. - `init`: This argument is optional, but recommended for CLI interfaces. Some error messages request the user to use the `init` argument to fix configuration issues. If the argument does not exist, this may confuse the user.
## Configuration ## Configuration