Skip to content

Implement BLAKE3 Caching and Refactor Core Rclone Utilities#121

Merged
Amazing-Stardom merged 11 commits intomainfrom
b2m-v3
Feb 13, 2026
Merged

Implement BLAKE3 Caching and Refactor Core Rclone Utilities#121
Amazing-Stardom merged 11 commits intomainfrom
b2m-v3

Conversation

@Amazing-Stardom
Copy link
Copy Markdown
Contributor

Description

Fixes # (issue)

Type of Change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Style/UI improvements
  • Code refactoring
  • Performance improvements
  • Test additions/updates

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.

  • Test A
  • Test B

Screenshots (if applicable)

Add screenshots to help explain your changes.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Additional Notes

Add any other context about the pull request here.

@livereviewbot
Copy link
Copy Markdown

Implement XXHash Caching and Distributed Locks

Overview

This change introduces a persistent XXHash caching mechanism for local files. It implements a new distributed locking system for B2 storage. Core rclone operations and configuration initialization are significantly refactored.

Technical Highlights

  • b2-manager/core/hash.go: Implements XXHash algorithm and a persistent hash.json cache for local file integrity checks.
  • b2-manager/core/handleFiles.go: Introduces a distributed locking mechanism using remote .lock files on B2 storage.
  • b2-manager/core/rclone.go: Refactors rclone operations into generic RcloneCopy, LsfRclone, and RcloneDeleteFile helpers.
  • b2-manager/config/config.go: Modularizes configuration initialization and establishes a new .b2m local data storage directory.
  • b2-manager/core/status.go: Changes database status data fetching from parallel to sequential execution, leveraging LsfRclone.
  • b2-manager/core/upload.go: Adds a pre-upload safety check to prevent accidental overwrites of newer remote databases.

Impact

  • Functionality:
    • Enables manual database locking for maintenance and provides concurrent update warnings during downloads.
    • Accelerates local file integrity checks through XXHash and a persistent hash cache.
    • Improves upload safety by preventing overwrites of newer remote database versions.
    • Streamlines remote operations with generalized rclone helpers and optimized status fetching.
  • Risk:
    • Lock acquisition is not atomic, potentially leading to race conditions for concurrent operations.
    • Switching from SHA256 to XXHash reduces cryptographic strength for integrity checks.
    • status.go removed explicit error handling for errLocal, errRemote, etc., which needs verification.
    • force unlock requires robust dbName validation to prevent unintended remote file deletions.

SyncStatusDifferent = "*"
)

// InitializeConfig sets up global configuration variables
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed sync status constants. Clarify if these constants are replaced or if the sync status logic has been removed entirely.

Comment thread b2-manager/config/config.go Outdated
}

// Load config from b2m.toml
// Load config from b2m.toml
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is duplicated.

Comment thread b2-manager/config/config.go Outdated
// Fetch user details
fetchUserDetails()

AppConfig.LocalDBDir = filepath.Join(AppConfig.ProjectRoot, "db", "all_dbs")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path db/all_dbs is hardcoded. Consider making all_dbs a configurable constant if it might change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will use in toml file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Failed to generate AI response

This issue has been logged and will be investigated.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good plan. Moving all_dbs to the toml file will make it configurable and addresses the concern about hardcoding.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. Using the TOML file for all_dbs will effectively address the hardcoding concern. Please ensure this change is implemented to make the path configurable.

Comment thread b2-manager/config/config.go Outdated
fetchUserDetails()

AppConfig.LocalDBDir = filepath.Join(AppConfig.ProjectRoot, "db", "all_dbs")
AppConfig.LocalVersionDir = filepath.Join(AppConfig.LocalDBDir, ".b2m", "version")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new .b2m directory is introduced. Ensure this change is communicated to users regarding local file structure.

return nil
}

func findProjectRoot() (string, error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The findProjectRoot function now searches for db or go.mod. This is a robust approach, but go.mod might not always indicate the project root for non-Go specific parts.

Comment thread b2-manager/config/config.go Outdated
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("root not found (searched for 'db' dir or 'go.mod')")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message root not found is generic. Specify which of the two conditions (db dir or go.mod) failed.

LogError("DownloadDatabase rclone copy failed for %s: %v", dbName, err)
return fmt.Errorf("download of %s failed: %w", dbName, err)
}
description := "Downloading " + dbName
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description parameter for RcloneCopy is hardcoded. Consider making it more dynamic or passing it from the caller for better context in logs.

Comment thread b2-manager/core/download.go Outdated
// 3.1. Calculate Local Hash of the newly downloaded file
localDBPath := filepath.Join(config.AppConfig.LocalDBDir, dbName)
localHash, err := CalculateSHA256(localDBPath)
localHash, err := CalculateXXHash(localDBPath)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from CalculateSHA256 to CalculateXXHash. XXHash is faster but not cryptographically secure. Confirm if this change aligns with the security requirements for integrity checks.

)

// LockDatabase creates a .lock file
func LockDatabase(ctx context.Context, dbName, owner, host, intent string, force bool) error {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The force parameter for LockDatabase allows overriding existing locks. This could lead to data corruption if not used carefully.

filename := fmt.Sprintf("%s.%s.%s.lock", dbName, owner, host)

// If forcing, we first clean up ALL existing locks for this DB to ensure we start fresh.
if force {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When force is true, all existing locks for the specific database are cleaned up. This scope (dbName.*.lock) is crucial and prevents accidental deletion of other database locks.

}
defer os.Remove(tmpFile)

// Use RcloneCopy to upload the lock file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FetchLocks followed by RcloneCopy is not an atomic operation. A race condition could occur where another client acquires a lock between the FetchLocks call and the RcloneCopy operation.

}

// UnlockDatabase removes the .lock file
func UnlockDatabase(ctx context.Context, dbName, owner string, force bool) error {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The force parameter for UnlockDatabase allows deleting all locks for a database. This is powerful and potentially dangerous if misused.

if force {
// Use rclone delete with include pattern
// Pattern: dbName.*.lock
pattern := fmt.Sprintf("%s.*.lock", dbName)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern for rclone delete uses dbName.*.lock. If dbName itself contains wildcards, this could lead to unintended deletions.

Comment thread b2-manager/core/hash.go

// cachedHash stores the hash and file stat info to avoid re-hashing unchanged files
type cachedHash struct {
Hash string
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using time.Time directly for ModTime instead of int64 (UnixNano). This improves type safety and readability.

Comment thread b2-manager/core/hash.go Outdated
}

var (
fileHashCache = make(map[string]cachedHash)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global variables for cache and mutex can lead to tight coupling and make testing difficult.

Comment thread b2-manager/core/hash.go Outdated
func CalculateXXHash(filePath string) (string, error) {
info, err := os.Stat(filePath)
if err != nil {
LogError("CalculateXXHash: Failed to stat file %s: %v", filePath, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogError call duplicates the error return. The caller will also receive the error.

Comment thread b2-manager/core/hash.go

if ok && cached.ModTime == info.ModTime().UnixNano() && cached.Size == info.Size() {
return cached.Hash, nil
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else keyword is unnecessary here. The if block returns, so the following code is implicitly the else branch.

Comment thread b2-manager/core/hash.go
if ok && cached.ModTime == info.ModTime().UnixNano() && cached.Size == info.Size() {
return cached.Hash, nil
} else {
LogInfo("Cache miss for %s. Cached: %v, Current: ModTime=%d, Size=%d", filepath.Base(filePath), ok, info.ModTime().UnixNano(), info.Size())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging filepath.Base(filePath) might be insufficient for debugging if multiple files with the same base name exist in different directories.

Comment thread b2-manager/core/hash.go Outdated
// Calculate hash
f, err := os.Open(filePath)
if err != nil {
LogError("CalculateXXHash: Failed to open file %s: %v", filePath, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogError call duplicates the error return. The caller will also receive the error.

Comment thread b2-manager/core/hash.go Outdated
// Use streaming digest
h := xxh3.New()
if _, err := io.Copy(h, f); err != nil {
LogError("CalculateXXHash: io.Copy failed for %s: %v", filePath, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogError call duplicates the error return. The caller will also receive the error.

Comment thread b2-manager/core/hash.go Outdated
}

// Sum64 returns uint64, format as hex string for compatibility
hash := fmt.Sprintf("%016x", h.Sum64())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash is formatted as a 16-character hexadecimal string. Ensure this format is consistent with how hashes are stored and compared elsewhere in the system.

Comment thread b2-manager/core/hash.go

data, err := os.ReadFile(cachePath)
if err != nil {
LogError("LoadHashCache: Failed to read cache file: %v", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogError call duplicates the error return. The caller will also receive the error.

Comment thread b2-manager/core/hash.go Outdated
fileHashCacheMu.Lock()
defer fileHashCacheMu.Unlock()

if err := json.Unmarshal(data, &fileHashCache); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogError call duplicates the error return. The caller will also receive the error.

Comment thread b2-manager/core/hash.go

if err := json.Unmarshal(data, &fileHashCache); err != nil {
LogError("LoadHashCache: Failed to unmarshal cache: %v", err)
return fmt.Errorf("failed to unmarshal cache: %w", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a wrapped error fmt.Errorf("failed to unmarshal cache: %w", err) is good practice.

Comment thread b2-manager/config/init.go
}

// Set version
model.AppConfig.ToolVersion = "2.0"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The tool version is hardcoded here.

Suggestions:

  1. Consider making the ToolVersion configurable via a build flag or a dedicated version file to avoid hardcoding.

Comment thread b2-manager/config/init.go
model.AppConfig.ToolVersion = "2.0"

// Load hash cache
if err := core.LoadHashCache(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Loading the hash cache early is good for performance, and handling a warning allows the application to proceed.

Comment thread b2-manager/config/init.go Outdated

case "--generate-hash":
// Dependencies check
if err := core.CheckB3SumAvailability(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Dependency checks are duplicated here and later for normal startup.

Suggestions:

  1. Consider extracting common dependency checks into a single function to avoid duplication and ensure consistency.

Comment thread b2-manager/config/init.go
}

// Warning and Confirmation
fmt.Println("\nWARNING: This operation regenerates all metadata from local files.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The warning and confirmation for --generate-hash is crucial for preventing accidental data loss.

Comment thread b2-manager/config/init.go
}

// Clean up .b2m before generating metadata
if err := core.CleanupLocalMetadata(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Cleaning up local metadata before generating new hashes is a correct step for this command.

Comment thread b2-manager/config/init.go
}

// Explicitly clear hash cache
core.ClearHashCache()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Explicitly clearing the hash cache is important when regenerating metadata.

Comment thread b2-manager/config/init.go
core.LogError("Startup Warning: %v", err)
}
core.HandleBatchMetadataGeneration()
Cleanup()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Calling Cleanup() ensures resources are properly released after a CLI command completes.

Comment thread b2-manager/config/init.go
case "--reset":
fmt.Println("Resetting system state...")
// Clean up .b2m before starting normal execution
if err := core.CleanupLocalMetadata(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Cleaning up local metadata for --reset is correct to ensure a fresh start.

Comment thread b2-manager/config/init.go
os.Exit(1)
}

// Explicitly clear hash cache
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Clearing the hash cache for --reset ensures consistency.

Comment thread b2-manager/config/init.go
// Explicitly clear hash cache
core.ClearHashCache()

Cleanup()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Calling Cleanup() ensures resources are properly released after a CLI command completes.

Comment thread b2-manager/config/init.go
}

// Setup cancellation handling
sigHandler := core.NewSignalHandler()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Using NewSignalHandler encapsulates context management, improving modularity.

Comment thread b2-manager/config/init.go Outdated
core.LogError("Error: %v", err)
os.Exit(1)
}
if err := core.CheckRclone(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Logging a warning for rclone if it's not found suggests it might be optional for some features, but critical for others.

Suggestions:

  1. Clarify in documentation which features require rclone.

Comment thread b2-manager/config/init.go Outdated
// fmt.Println("Warning: rclone not found or error:", err)
core.LogError("Warning: rclone not found or error: %v", err)
}
if !core.CheckRcloneConfig() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Logging a warning for rclone config if it's not found suggests it might be optional for some features, but critical for others.

Suggestions:

  1. Clarify in documentation which features require rclone.

Comment thread b2-manager/config/init.go

// Cleanup saves the hash cache and closes the logger.
// This should be called (usually deferred) by the main function.
func Cleanup() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The Cleanup function provides a single point for graceful shutdown, ensuring resources like the hash cache and logger are properly closed.

globalCancel context.CancelFunc
)
// SignalHandler manages the application lifecycle context triggered by OS signals
type SignalHandler struct {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Encapsulating context and cancel function within a struct is a good pattern for managing lifecycle.

type SignalHandler struct {
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Using a sync.Mutex ensures thread-safe access to the context and cancel function.

if globalCtx == nil {
SetupCancellation()
// Reset creates a new context watching for signals (clearing previous cancellation)
func (h *SignalHandler) Reset() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The Reset() method correctly cancels the old context before creating a new one, preventing resource leaks.

LogInfo("📝 Recording cancellation...")
meta, err := GenerateLocalMetadata(dbName, uploadDuration, "cancelled")

// strategy: Try to get existing remote metadata to preserve the "last valid state"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

This comment block provides excellent context and reasoning for the refactor, highlighting a potential bug in previous logic.

eventMeta, err := GenerateLocalMetadata(dbName, uploadDuration, "cancelled")
if err != nil {
LogError("⚠️ Failed to generate cancellation metadata: %v", err)
LogError("CleanupOnCancel: Failed to generate cancellation metadata for %s: %v", dbName, err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: critical

Robust error handling for GenerateLocalMetadata is crucial to ensure cancellation is recorded.

Suggestions:

  1. Return the error immediately to prevent further incorrect processing if metadata generation fails.

ctx := context.Background()

// Try loading from Local Anchor (most reliable for "last known good state")
if localAnchor, err := GetLocalVersion(dbName); err == nil && localAnchor != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Prioritizing local anchor metadata ensures the most recent known good state is used as a base.

LogError("⚠️ Failed to append cancellation event: %v", err)
// Fallback: Try fetching single remote metadata
LogInfo("CleanupOnCancel: Local anchor missing for %s, trying remote fetch...", dbName)
remoteMeta, err := FetchSingleRemoteMetadata(ctx, dbName)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Falling back to remote metadata if local anchor is missing provides resilience.

// 3. Construct the final metadata object
if baseMeta != nil {
// Use the base (remote/anchor) as the primary object to preserve Hash/Timestamp
meta = baseMeta
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Setting meta = baseMeta is key to preserving the existing top-level metadata fields, preventing data loss.

Status: "cancelled",
}
meta.Events = append(meta.Events, newEvent)
meta.Status = "cancelled" // Mark top-level status as cancelled to indicate last op failed
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Explicitly setting meta.Status = "cancelled" at the top level clearly indicates the last operation's outcome.

// Use the local info as the only record.
meta = eventMeta
// Ensure event #1 is present
if len(meta.Events) == 0 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

This conditional ensures that even if GenerateLocalMetadata somehow didn't produce an event, a 'cancelled' event is added for new files.

Suggestions:

  1. This is a defensive check; consider if GenerateLocalMetadata should guarantee at least one event.

// 2. Download: Execute `rclone copy` to pull the file from B2.
// 3. Anchor: Construct a local "Verified Anchor" (LocalVersion) to mark this state as synced.
func DownloadDatabase(ctx context.Context, dbName string, onProgress func(model.RcloneProgress)) error {
func DownloadDatabase(ctx context.Context, dbName string, quiet bool, onProgress func(model.RcloneProgress)) error {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Adding a quiet parameter allows for more flexible control over download logging or progress display.

Suggestions:

  1. Ensure all call sites for DownloadDatabase are updated to pass the new quiet parameter.

filename := fmt.Sprintf("%s.%s.%s.%s", dbName, entry.Owner, entry.Hostname, entry.Type)

// Safety check: ensure we are only deleting a .lock file
if !strings.HasSuffix(filename, ".lock") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

This safety check is redundant and potentially misleading. The entry.Type field, when properly parsed, should already ensure filename ends with .lock. The RcloneDeleteFile function should be responsible for ensuring operations occur within the LockDir and on valid lock files.

Suggestions:

  1. Remove this explicit strings.HasSuffix check if RcloneDeleteFile is robustly designed to only delete files within the LockDir and with expected lock file patterns.
  2. Alternatively, strengthen RcloneDeleteFile to enforce path and naming constraints, then this check becomes unnecessary.

Comment thread b2-manager/core/hash.go Outdated
startTime := time.Now()

// Calculate hash using b3sum with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

A 10-second timeout might be too short for very large files or systems with slow I/O, potentially leading to unnecessary failures.

Suggestions:

  1. Consider making the b3sum timeout configurable via model.AppConfig.
  2. Increase the default timeout to a more conservative value (e.g., 30-60 seconds) to accommodate larger files.

t.Render()
func sendDiscord(ctx context.Context, content string) {
payload := map[string]string{"content": content}
data, _ := json.Marshal(payload)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

json.Marshal can return an error; ignoring it might lead to curl sending malformed data if marshalling fails.

Suggestions:

  1. Check the error returned by json.Marshal and handle it, e.g., by logging the error and returning early or sending a generic error message to Discord.

@Amazing-Stardom Amazing-Stardom merged commit 510964b into main Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants