diff --git a/components/notifications/form/form.templ b/components/notifications/form/form.templ
index 3a1aedb..4cddb99 100644
--- a/components/notifications/form/form.templ
+++ b/components/notifications/form/form.templ
@@ -127,6 +127,7 @@ templ NotificationForm(ctx context.Context, data types.NotificationFormData) {
+
= 3 {
+ fileName := strings.TrimSpace(matches[1])
+ hashValue := strings.TrimSpace(matches[2])
+ if fileName != "" && hashValue != "" {
+ fileHashMap[fileName] = hashValue
+ te.logger.LogDebug("Extracted hash for %s: %s", fileName, hashValue)
+ }
+ }
+ }
+ // --- End Hash Extraction ---
+
+ // --- Second Pass: Process Copied Files and Create Metadata ---
+ // Regex to find lines like: "INFO : path/to/file.txt: Copied (new)" or "INFO : path/to/file.txt: Copied (replaced existing)"
+ // It captures the filename (group 1)
+ copyLogRegex := regexp.MustCompile(`INFO\s*:\s*(.*?):\s*Copied`)
+ processedFilesInLog := make(map[string]bool) // Track files found in log to avoid duplicates
+
+ for _, line := range logLines {
+ matches := copyLogRegex.FindStringSubmatch(line)
+ if len(matches) >= 2 {
+ fileName := strings.TrimSpace(matches[1])
+ if fileName == "" || processedFilesInLog[fileName] {
+ continue // Skip empty or duplicate filenames within the log
+ }
+ processedFilesInLog[fileName] = true // Mark as processed in this log
+
+ // Construct destination path for metadata
+ var destPathForDB string
+ destFile := fileName // Assume filename is the same unless output pattern is used (not handled here)
+ if config.DestinationType == "local" {
+ destPathForDB = filepath.Join(config.DestinationPath, destFile)
+ } else if config.DestinationType == "s3" || config.DestinationType == "minio" || config.DestinationType == "b2" {
+ if config.DestinationPath != "" && config.DestinationPath != "/" {
+ destPathForDB = fmt.Sprintf("%s/%s/%s", config.DestBucket, config.DestinationPath, destFile)
+ } else {
+ destPathForDB = fmt.Sprintf("%s/%s", config.DestBucket, destFile)
+ }
+ } else {
+ destPathForDB = fmt.Sprintf("%s/%s", config.DestinationPath, destFile)
+ }
+
+ // Get hash from the map
+ fileHash := fileHashMap[fileName] // Will be empty string if not found
+
+ // Create FileMetadata record
+ now := time.Now()
+ metadata := &db.FileMetadata{
+ JobID: job.ID,
+ ConfigID: config.ID,
+ FileName: fileName,
+ OriginalPath: config.SourcePath, // Base source path
+ FileSize: 0, // Unknown from log
+ FileHash: fileHash, // Use extracted hash
+ CreationTime: now, // Approximation
+ ModTime: now, // Approximation
+ ProcessedTime: now,
+ DestinationPath: destPathForDB,
+ Status: "processed", // Assumed success based on log line
+ ErrorMessage: "",
+ }
+
+ if err := te.db.CreateFileMetadata(metadata); err != nil {
+ te.logger.LogError("Error creating file metadata from log for %s: %v", fileName, err)
+ // Don't stop processing other files
+ } else {
+ filesProcessedFromLog++
+ te.logger.LogDebug("Created file metadata from log for %s (ID: %d, Hash: %s)", fileName, metadata.ID, fileHash)
+ }
+ }
+ }
+ te.logger.LogInfo("Processed %d files based on rclone log for job %d, config %d", filesProcessedFromLog, job.ID, config.ID)
+ // --- End Metadata Creation ---
+ }
+ // --- End Log Parsing ---
+
+ }
+
// Update history with basic info
history.EndTime = &time.Time{}
*history.EndTime = startTime.Add(duration)
@@ -839,7 +957,7 @@ func (te *TransferExecutor) executeSimpleCommand(cmdName string, cmdType string,
history.FilesTransferred = lines
history.Status = "completed"
} else if cmdType == "transfer" {
- // Try to extract transfer statistics from command output
+ // Try to extract transfer statistics from command output (stderr)
history.Status = "completed"
// Look for metrics in stderr which is where rclone puts stats
@@ -858,6 +976,17 @@ func (te *TransferExecutor) executeSimpleCommand(cmdName string, cmdType string,
history.FilesTransferred = filesTransferred
}
}
+
+ // If stats weren't found in stderr OR the log parsing yielded a count, use the log count
+ // This prioritizes the count derived from actual file processing logs.
+ if filesProcessedFromLog > 0 {
+ history.FilesTransferred = filesProcessedFromLog
+ te.logger.LogDebug("Updated FilesTransferred count to %d based on log parsing", filesProcessedFromLog)
+ } else if history.FilesTransferred == 0 && logReadErr == nil {
+ // Fallback message if stats were 0 and log parsing also yielded 0 (or wasn't applicable)
+ te.logger.LogDebug("Could not determine FilesTransferred from stderr stats or log parsing.")
+ }
+
} else {
// For other commands, we don't have file counts, but the command completed
history.Status = "completed"
diff --git a/internal/web/handlers/settings_handlers.go b/internal/web/handlers/settings_handlers.go
index 679a82b..a850894 100644
--- a/internal/web/handlers/settings_handlers.go
+++ b/internal/web/handlers/settings_handlers.go
@@ -1307,11 +1307,44 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
return
}
+ // --- DEBUG: Print received form data ---
+ log.Println("--- Received POST Form Data (Update Notification) ---")
+ if err := c.Request.ParseForm(); err == nil {
+ postData := c.Request.PostForm
+ if len(postData) > 0 {
+ for key, values := range postData {
+ log.Printf(" %s: %v\n", key, values)
+ }
+ } else {
+ log.Println(" (No POST form data found or parsed)")
+ }
+ } else {
+ log.Printf(" Error parsing form: %v\n", err)
+ }
+ log.Println("-------------------------------------------------")
+ // --- END DEBUG ---
+
// Parse form data
name := c.PostForm("name")
serviceType := c.PostForm("type")
description := c.PostForm("description")
- isEnabled := c.PostForm("is_enabled") == "on"
+
+ // Correctly handle boolean checkbox with hidden input
+ isEnabled := false // Default to false
+ if err := c.Request.ParseForm(); err == nil { // Ensure form is parsed
+ isEnabledValues := c.Request.PostForm["is_enabled"] // Get slice of values
+ for _, v := range isEnabledValues {
+ if v == "true" {
+ isEnabled = true // Checkbox was checked if "true" is present
+ break
+ }
+ }
+ } else {
+ log.Printf("Error parsing form during update: %v", err)
+ // Decide if this is a fatal error or if we can proceed assuming false
+ // For now, we proceed with isEnabled = false
+ }
+
eventTriggers := c.PostFormArray("event_triggers[]")
// Validate required fields
@@ -1325,7 +1358,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
service.Type = serviceType
service.Description = description
service.EventTriggers = eventTriggers
- service.SetIsEnabled(isEnabled) // Use setter
+ service.SetIsEnabled(isEnabled) // Use the correctly determined boolean
// Update type-specific fields based on service type
switch serviceType {