From ca277ba7735dd75cc94b26c1c2357f7d3192e26b Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sat, 31 May 2025 21:48:11 +0000 Subject: [PATCH] Add entry refresh feature; fix ClampTrailingWhitespace ignoring the initial trim --- core/edit.go | 6 ++-- core/utilitiesMisc.go | 72 ++++++++++++++++++++++++++++++++++++++----- global/deviceIDs.go | 2 +- syncserver/server.go | 4 +-- 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/core/edit.go b/core/edit.go index cdcddc2..7adcf0d 100644 --- a/core/edit.go +++ b/core/edit.go @@ -13,16 +13,16 @@ func GetOldEntryData(targetLocation string, field int) ([]string, error) { back.TargetIsFile(targetLocation, true, 2) // read old entry data - unencryptedEntry, err := crypt.DecryptFileToSlice(targetLocation) + decryptedEntry, 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), nil + return ensureSliceLength(decryptedEntry, field), nil } else { - return unencryptedEntry, nil + return decryptedEntry, nil } } diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index cc90871..bf77366 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -8,26 +8,83 @@ import ( "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/crypt" "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/synccommon" ) // WriteEntry writes entryData to an encrypted file at targetLocation. -func WriteEntry(targetLocation string, entryData []byte) error { - encBytes := crypt.EncryptBytes(entryData) - err := os.WriteFile(targetLocation, encBytes, 0600) +func WriteEntry(targetLocation string, decBytes []byte) error { + err := os.WriteFile(targetLocation, crypt.EncryptBytes(decBytes), 0600) if err != nil { return errors.New("unable to write to file: " + err.Error()) } return nil } +// EntryRefresh re-encrypts all libmutton entries with a new passphrase +// and optimizes each entry to ensure they are as slim as possible. +// This includes stripping trailing whitespace/newlines/carriage returns +// from each field and running each note through ClampTrailingWhitespace +// to ensure each note line is optimized as possible without breaking +// Markdown formatting. +func EntryRefresh(oldRCWPassphrase, newRCWPassphrase []byte, removeOldDir bool) error { + // ensure global.EntryRoot+"-new" and global.EntryRoot-"old" do not exist + dirEnds := []string{"-new", "-old"} + for i, dirEnd := range dirEnds { + if i == 1 && !removeOldDir { + if _, err := os.Stat(global.EntryRoot + dirEnd); !os.IsNotExist(err) { + return errors.New("unable to refresh entries: \"" + global.EntryRoot + "-new\" already exists") + } + } + os.RemoveAll(global.EntryRoot + "-new") + } + + // create output directory structure (global.EntryRoot + "-new"/*) + entries, folders, err := synccommon.WalkEntryDir() + if err != nil { + return errors.New("unable to walk entry directory: " + err.Error()) + } + for _, folder := range folders { + fullPath := global.EntryRoot + "-new" + strings.ReplaceAll(folder, "/", global.PathSeparator) + err := os.MkdirAll(fullPath, 0700) + if err != nil { + return errors.New("unable to create temporary directory \"" + fullPath + "\": " + err.Error()) + } + } + + // decrypt, optimize, and re-encrypt each entry + for _, entry := range entries { + decryptedEntry, err := crypt.DecryptFileToSlice(global.TargetLocationFormat(entry)) + if err != nil { + return err + } + // strip trailing whitespace... + fieldsMain := decryptedEntry[:4] + fieldsNote := back.RemoveTrailingEmptyStrings(decryptedEntry[4:]) + // ...from each non-note field + for i, line := range fieldsMain { + fieldsMain[i] = strings.TrimRight(line, " \t\r\n") + } + // ...and from each note line (preserve Markdown formatting) + ClampTrailingWhitespace(fieldsNote) + + // re-combine fields + decryptedEntry = append(fieldsMain, fieldsNote...) + + // write the entry to the new directory + WriteEntry(global.EntryRoot+"-new"+strings.ReplaceAll(entry, "/", global.PathSeparator), []byte(strings.Join(decryptedEntry, "\n"))) + } + + return nil +} + // ClampTrailingWhitespace strips trailing newlines, carriage returns, and tabs from each line in a note. // Additionally, it removes single trailing spaces and truncates multiple trailing spaces to two (for Markdown formatting). func ClampTrailingWhitespace(note []string) { for i, line := range note { // remove trailing tabs, carriage returns, and newlines - note[i] = strings.TrimRight(line, "\t\r\n") + line = strings.TrimRight(line, "\t\r\n") - // determine the number of trailing spaces + // determine the number of trailing spaces in the trimmed line var endSpacesCount int for j := len(line) - 1; j >= 0; j-- { if line[j] != ' ' { @@ -39,9 +96,10 @@ func ClampTrailingWhitespace(note []string) { // remove single spaces, truncate multiple spaces (leave two for Markdown formatting) switch endSpacesCount { case 0: - // do nothing + // no trailing spaces + note[i] = line case 1: - // remove the trailing space + // remove the single trailing space note[i] = strings.TrimRight(line, " ") default: // truncate the trailing spaces to two diff --git a/global/deviceIDs.go b/global/deviceIDs.go index 83ca4bd..8e82ad9 100644 --- a/global/deviceIDs.go +++ b/global/deviceIDs.go @@ -28,7 +28,7 @@ func GenDeviceIDList() ([]fs.DirEntry, error) { // create a slice of all registered devices deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices") if err != nil { - return nil, errors.New("unable to read the devices directory: " + err.Error()) + return nil, errors.New("unable to read devices directory: " + err.Error()) } return deviceIDList, nil } diff --git a/syncserver/server.go b/syncserver/server.go index ab32c78..a9f55af 100644 --- a/syncserver/server.go +++ b/syncserver/server.go @@ -17,12 +17,12 @@ import ( func GetRemoteDataFromServer(clientDeviceID string) error { entryList, dirList, err := synccommon.WalkEntryDir() if err != nil { - return errors.New("unable to walk the entry directory: " + err.Error()) + return errors.New("unable to walk entry directory: " + err.Error()) } modList := synccommon.GetModTimes(entryList) deletionsList, err := os.ReadDir(global.ConfigDir + global.PathSeparator + "deletions") if err != nil { - return errors.New("unable to read the deletions directory: " + err.Error()) + return errors.New("unable to read deletions directory: " + err.Error()) } // print the current UNIX timestamp to stdout