Return errors, rather than printing them

This commit is contained in:
2025-05-30 01:08:43 +00:00
parent 74b8261bc8
commit 40f0f35f45
21 changed files with 253 additions and 183 deletions
+12 -7
View File
@@ -3,30 +3,31 @@
package core
import (
"errors"
"strings"
"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.
// assignedContents can be omitted to clear the clipboard immediately and unconditionally.
func clipClearProcess(assignedContents string) {
func clipClearProcess(assignedContents string) error {
cmdPaste, cmdClear := getClipCommands()
clearClipboard := func() {
clearClipboard := func() error {
err := cmdClear.Run()
if err != nil {
back.PrintError("Failed to clear clipboard", global.ErrorClipboard, true)
return errors.New("unable to clear clipboard")
}
back.Exit(0)
return nil
}
// if assignedContents is empty, clear the clipboard immediately and unconditionally
if assignedContents == "" {
clearClipboard()
return
return nil
}
// wait 30 seconds before checking clipboard contents
@@ -34,10 +35,14 @@ func clipClearProcess(assignedContents string) {
newContents, err := cmdPaste.Output()
if err != nil {
back.PrintError("Failed to read clipboard contents", global.ErrorClipboard, true)
return errors.New("unable to read clipboard contents")
}
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
clearClipboard()
err := clearClipboard()
if err != nil {
return err
}
}
return nil
}
+17 -8
View File
@@ -1,6 +1,7 @@
package core
import (
"errors"
"fmt"
"os"
"strings"
@@ -13,10 +14,13 @@ import (
)
// CopyArgument copies a field from an entry to the clipboard.
func CopyArgument(targetLocation string, field int) {
func CopyArgument(targetLocation string, field int) error {
if isFile, _ := back.TargetIsFile(targetLocation, true, 2); isFile {
decryptedEntry := crypt.DecryptFileToSlice(targetLocation)
decryptedEntry, err := crypt.DecryptFileToSlice(targetLocation)
if err != nil {
return errors.New("unable to decrypt entry: " + err.Error())
}
var copySubject string // will store data to be copied
// ensure field exists in entry
@@ -24,7 +28,7 @@ func CopyArgument(targetLocation string, field int) {
// ensure field is not empty
if decryptedEntry[field] == "" {
back.PrintError("Field is empty", back.ErrorTargetNotFound, true)
return errors.New("field is empty")
}
if field != 2 {
@@ -44,18 +48,23 @@ func CopyArgument(targetLocation string, field int) {
for { // keep token copied to clipboard, refresh on 30-second intervals
currentTime := time.Now()
copyString(true, GenTOTP(secret, currentTime, forSteam))
token, err := GenTOTP(secret, currentTime, forSteam)
if err != nil {
return err
}
copyString(true, token)
// sleep until next 30-second interval
time.Sleep(time.Duration(30-(currentTime.Second()%30)) * time.Second)
}
}
} else {
back.PrintError("Field does not exist in entry", back.ErrorTargetNotFound, true)
return errors.New("field does not exist in entry")
}
// copy field to clipboard, launch clipboard clearing process
copyString(false, copySubject)
}
return nil
}
// ClipClearArgument reads the assigned clipboard contents from stdin and passes them to clipClearProcess.
@@ -68,7 +77,7 @@ func ClipClearArgument() {
}
// GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP).
func GenTOTP(secret string, time time.Time, forSteam bool) string {
func GenTOTP(secret string, time time.Time, forSteam bool) (string, error) {
var totpToken string
var err error
@@ -79,8 +88,8 @@ func GenTOTP(secret string, time time.Time, forSteam bool) string {
}
if err != nil {
back.PrintError("Error generating TOTP code: "+err.Error(), back.ErrorOther, true)
return "", errors.New("error generating TOTP code: " + err.Error())
}
return totpToken
return totpToken, nil
}
+4 -4
View File
@@ -3,24 +3,24 @@
package core
import (
"errors"
"os/exec"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/libmutton/global"
)
// copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) {
func copyString(continuous bool, copySubject string) error {
cmd := exec.Command("pbcopy")
back.WriteToStdin(cmd, copySubject)
err := cmd.Run()
if err != nil {
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
return errors.New("unable to copy to clipboard: " + err.Error())
}
if !continuous {
LaunchClipClearProcess(copySubject)
}
return nil
}
// getClipCommands returns the commands for pasting and clearing the clipboard contents.
-2
View File
@@ -6,8 +6,6 @@ import (
"golang.design/x/clipboard"
)
// TODO Investigate background clipboard clearing and on-app-close clipboard clearing for Android
// copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) {
clipboard.Write(clipboard.FmtText, []byte(copySubject))
+4 -4
View File
@@ -3,24 +3,24 @@
package core
import (
"errors"
"os/exec"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/libmutton/global"
)
// copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) {
func copyString(continuous bool, copySubject string) error {
cmd := exec.Command("termux-clipboard-set")
back.WriteToStdin(cmd, copySubject)
err := cmd.Run()
if err != nil {
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
return errors.New("unable to copy to clipboard: " + err.Error())
}
if !continuous {
LaunchClipClearProcess(copySubject)
}
return nil
}
// getClipCommands returns the commands for pasting and clearing the clipboard contents.
+6 -6
View File
@@ -3,36 +3,36 @@
package core
import (
"errors"
"os"
"os/exec"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/libmutton/global"
)
// copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) {
func copyString(continuous bool, copySubject string) error {
// determine whether to use wl-copy (Wayland) or xclip (X11)
var envSet, isWayland bool // track whether environment variables are set
var cmdCopy *exec.Cmd
// determine whether to use wl-copy (Wayland) or xclip (X11)
if _, envSet = os.LookupEnv("WAYLAND_DISPLAY"); envSet {
cmdCopy = exec.Command("wl-copy", "-t", "text/plain")
isWayland = true
} 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", global.ErrorClipboard, true)
return errors.New("clipboard platform could not be determined")
}
back.WriteToStdin(cmdCopy, copySubject)
err := cmdCopy.Run()
if err != nil {
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
return errors.New("unable to copy to clipboard: " + err.Error())
}
if !continuous {
LaunchClipClearProcess(copySubject, isWayland)
}
return nil
}
// getClipCommands returns the commands for pasting and clearing the clipboard contents.
+4 -6
View File
@@ -3,25 +3,23 @@
package core
import (
"errors"
"fmt"
"os/exec"
"strings"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/libmutton/global"
)
// copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) {
func copyString(continuous bool, copySubject string) error {
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(), global.ErrorClipboard, true)
return errors.New("unable to copy to clipboard: " + err.Error())
}
if !continuous {
LaunchClipClearProcess(copySubject)
}
return nil
}
// getClipCommands returns the commands for pasting and clearing the clipboard contents.
+9 -4
View File
@@ -1,23 +1,28 @@
package core
import (
"errors"
"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 {
func GetOldEntryData(targetLocation string, field int) ([]string, error) {
// ensure targetLocation exists
back.TargetIsFile(targetLocation, true, 2)
// read old entry data
unencryptedEntry := crypt.DecryptFileToSlice(targetLocation)
unencryptedEntry, err := crypt.DecryptFileToSlice(targetLocation)
if err != nil {
return nil, errors.New("unable to decrypt entry: " + err.Error())
}
// return the old entry data with all required lines present
if field > 0 {
return ensureSliceLength(unencryptedEntry, field)
return ensureSliceLength(unencryptedEntry, field), nil
} else {
return unencryptedEntry
return unencryptedEntry, nil
}
}
+7 -4
View File
@@ -23,7 +23,7 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][
sshKeyPath := cmp.Or(back.ExpandPathWithHome(inputCB(back.AnsiBold+"Note:"+back.AnsiReset+" Only key-based authentication is supported (keys may optionally be passphrase-protected).\n The remote server must already be in your ~"+global.PathSeparator+".ssh"+global.PathSeparator+"known_hosts file.\n\nSSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey)
sshKeyIsFile, _ := back.TargetIsFile(sshKeyPath, false, 0)
if !sshKeyIsFile {
return errors.New("ssh identity file not found: " + sshKeyPath)
return errors.New("SSH identity file not found: " + sshKeyPath)
}
// get other ssh info from user
@@ -38,7 +38,10 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][
// perform operations based on collected user input
//// initialize libmutton directories
oldDeviceID := global.DirInit(preserveOldConfigDir)
oldDeviceID, err := global.DirInit(preserveOldConfigDir)
if err != nil {
return errors.New("unable to initialize libmutton directories: " + err.Error())
}
//// write config file
//// temporarily assign sshEntryRoot and sshIsWindows to null to pass initial device ID registration
cfg.WriteConfig(append(
@@ -54,7 +57,7 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][
// generate and register device ID
sshEntryRoot, sshIsWindows, err := synccycles.DeviceIDGen(oldDeviceID)
if err != nil {
return errors.New("failed to generate device ID: " + err.Error())
return errors.New("unable to generate device ID: " + err.Error())
}
cfg.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, nil, true)
} else {
@@ -69,7 +72,7 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][
if len(rcwPassphrase) > 0 {
err := wrappers.GenSanityCheck(global.ConfigDir+global.PathSeparator+"sanity.rcw", rcwPassphrase)
if err != nil {
return errors.New("failed to generate sanity check file: " + err.Error())
return errors.New("unable to generate sanity check file: " + err.Error())
}
}
return nil
+8 -8
View File
@@ -1,6 +1,7 @@
package core
import (
"errors"
"os"
"strings"
@@ -10,12 +11,13 @@ import (
)
// WriteEntry writes entryData to an encrypted file at targetLocation.
func WriteEntry(targetLocation string, entryData []byte) {
func WriteEntry(targetLocation string, entryData []byte) error {
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)
return errors.New("unable to write to file: " + err.Error())
}
return nil
}
// ClampTrailingWhitespace strips trailing newlines, carriage returns, and tabs from each line in a note.
@@ -51,21 +53,19 @@ func ClampTrailingWhitespace(note []string) {
// EntryAddPrecheck ensures the directory meant to contain a new
// entry exists and that the target entry location is not already used.
// Returns: statusCode (0 = success, 1 = target location already exists, 2 = containing directory is invalid).
func EntryAddPrecheck(targetLocation string) uint8 {
func EntryAddPrecheck(targetLocation string) (uint8, error) {
// ensure target location does not already exist
_, isAccessible := back.TargetIsFile(targetLocation, false, 0)
if isAccessible {
back.PrintError("Target location already exists", global.ErrorTargetExists, false)
return 1 // inform interactive clients that the target location already exists
return 1, errors.New("target location already exists")
}
// ensure target containing directory exists and is a directory (not a file)
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
return 2, errors.New("\"" + containingDir + "\" is not a valid containing directory")
}
return 0
return 0, nil
}
// EntryIsNotEmpty iterates through entryData and returns true if any line is not empty.