summaryrefslogtreecommitdiff
path: root/internal/utils.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-06 11:12:03 +0300
committerPaul Buetow <paul@buetow.org>2026-04-06 11:12:03 +0300
commit616beecc41b573503dad9f5bfd9f353c6f826a8a (patch)
tree3189ae3f048dfc4e8ff79b83caab8ea43c2d7492 /internal/utils.go
parent05f54cc0cb8cf3535698ab5027d200842bdb28e3 (diff)
refactor: extract shared CardStore into internal/store to eliminate duplication
FindCardDirectory, FindOrCreateCardDirectory, GenerateCardID and the ScanWords helper previously existed in both internal/utils.go (as standalone functions) and were partially duplicated in internal/gui/card_service.go (readWordFromDir, ScanExistingWords). Introduce internal/store.CardStore as the single source of truth for all on-disk card-directory operations. Both internal/processor and internal/gui now hold a *store.CardStore field and delegate to it, removing the last copy of the directory-scanning loop from card_service.go. internal/utils.go keeps thin forwarding wrappers for callers that import the root internal package. Also adds table-driven unit tests for the new package covering FindCardDirectory (including legacy _word.txt fallback), FindOrCreateCardDirectory, and CardStore.ScanWords. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/utils.go')
-rw-r--r--internal/utils.go73
1 files changed, 10 insertions, 63 deletions
diff --git a/internal/utils.go b/internal/utils.go
index b83d46a..e16e10e 100644
--- a/internal/utils.go
+++ b/internal/utils.go
@@ -1,85 +1,32 @@
package internal
import (
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "os"
- "path/filepath"
"strings"
- "time"
+
+ "codeberg.org/snonux/totalrecall/internal/store"
)
-// GenerateCardID creates a unique ID for a card based on timestamp and Bulgarian word
+// GenerateCardID creates a unique ID for a card based on timestamp and Bulgarian word.
+// Delegates to store.GenerateCardID which is the single source of truth.
// Format: epochMillis_md5(word)[:8]
func GenerateCardID(bulgarianWord string) string {
- // Get current timestamp in milliseconds
- now := time.Now()
- epochMillis := now.UnixNano() / 1000000
-
- // Calculate MD5 hash of the word
- hash := md5.Sum([]byte(bulgarianWord))
- hashStr := hex.EncodeToString(hash[:])[:8] // Use first 8 chars of MD5
-
- // Combine timestamp and hash
- return fmt.Sprintf("%d_%s", epochMillis, hashStr)
+ return store.GenerateCardID(bulgarianWord)
}
// FindCardDirectory searches outputDir for a subdirectory whose word.txt
// (or legacy _word.txt) matches the given word. Returns the directory path
// or an empty string if not found.
+// Delegates to store.FindCardDirectory which is the single source of truth.
func FindCardDirectory(outputDir, word string) string {
- entries, err := os.ReadDir(outputDir)
- if err != nil {
- return ""
- }
-
- for _, entry := range entries {
- if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
- continue
- }
-
- dirPath := filepath.Join(outputDir, entry.Name())
- wordFile := filepath.Join(dirPath, "word.txt")
-
- if data, err := os.ReadFile(wordFile); err == nil {
- if strings.TrimSpace(string(data)) == word {
- return dirPath
- }
- } else {
- // Backward-compatible fallback: old format used _word.txt
- wordFile = filepath.Join(dirPath, "_word.txt")
- if data, err := os.ReadFile(wordFile); err == nil {
- if strings.TrimSpace(string(data)) == word {
- return dirPath
- }
- }
- }
- }
-
- return ""
+ return store.FindCardDirectory(outputDir, word)
}
// FindOrCreateCardDirectory returns the existing card directory for word inside
// outputDir, or creates a new one with a generated card ID. It also writes
// word.txt so subsequent calls can find the directory.
+// Delegates to store.FindOrCreateCardDirectory which is the single source of truth.
func FindOrCreateCardDirectory(outputDir, word string) string {
- if dir := FindCardDirectory(outputDir, word); dir != "" {
- return dir
- }
-
- cardID := GenerateCardID(word)
- wordDir := filepath.Join(outputDir, cardID)
- if err := os.MkdirAll(wordDir, 0755); err != nil {
- fmt.Printf("Warning: failed to create word directory: %v\n", err)
- return outputDir
- }
-
- if err := os.WriteFile(filepath.Join(wordDir, "word.txt"), []byte(word), 0644); err != nil {
- fmt.Printf("Warning: failed to save word metadata: %v\n", err)
- }
-
- return wordDir
+ return store.FindOrCreateCardDirectory(outputDir, word)
}
// SanitizeFilename creates a safe filename from a string.
@@ -97,7 +44,7 @@ func SanitizeFilename(s string) string {
return b.String()
}
-// isAlphaNumeric checks if a rune is alphanumeric
+// isAlphaNumeric checks if a rune is alphanumeric (Latin or Cyrillic).
func isAlphaNumeric(r rune) bool {
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
(r >= '0' && r <= '9') || (r >= 'а' && r <= 'я') ||