diff --git a/cmd/server/main.go b/cmd/server/main.go index 7da2731..bcf0cc9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -8,7 +8,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "encoding/json" // Added for JSON handling + "encoding/json" // Added for JSON handling "flag" "fmt" @@ -34,7 +34,7 @@ import ( "github.com/disintegration/imaging" "github.com/dutchcoders/go-clamd" // ClamAV integration - "github.com/fsnotify/fsnotify" // Added for directory monitoring + "github.com/fsnotify/fsnotify" // Added for directory monitoring "github.com/go-redis/redis/v8" // Redis integration "github.com/patrickmn/go-cache" "github.com/prometheus/client_golang/prometheus" @@ -154,10 +154,10 @@ type ThumbnailsConfig struct { } type ISOConfig struct { - Enabled bool `mapstructure:"enabled"` - Size string `mapstructure:"size"` - MountPoint string `mapstructure:"mountpoint"` - Charset string `mapstructure:"charset"` + Enabled bool `mapstructure:"enabled"` + Size string `mapstructure:"size"` + MountPoint string `mapstructure:"mountpoint"` + Charset string `mapstructure:"charset"` ContainerFile string `mapstructure:"containerfile"` } @@ -206,12 +206,12 @@ type RedisConfig struct { } type WorkersConfig struct { - NumWorkers int `mapstructure:"numworkers"` - UploadQueueSize int `mapstructure:"uploadqueuesize"` - MaxConcurrentOperations int `mapstructure:"max_concurrent_operations"` - NetworkEventBuffer int `mapstructure:"network_event_buffer"` + NumWorkers int `mapstructure:"numworkers"` + UploadQueueSize int `mapstructure:"uploadqueuesize"` + MaxConcurrentOperations int `mapstructure:"max_concurrent_operations"` + NetworkEventBuffer int `mapstructure:"network_event_buffer"` PerformanceMonitorInterval string `mapstructure:"performance_monitor_interval"` - MetricsUpdateInterval string `mapstructure:"metrics_update_interval"` + MetricsUpdateInterval string `mapstructure:"metrics_update_interval"` } type FileConfig struct { @@ -275,9 +275,9 @@ type FileMetadata struct { var ( conf Config versionString string - log = logrus.New() - uploadQueue = make(chan UploadTask, 100) - networkEvents = make(chan NetworkEvent, 100) + log = logrus.New() + uploadQueue = make(chan UploadTask, 100) + networkEvents = make(chan NetworkEvent, 100) fileInfoCache *cache.Cache fileMetadataCache *cache.Cache clamClient *clamd.Clamd @@ -438,7 +438,7 @@ func main() { // Set log level based on configuration level, err := logrus.ParseLevel(conf.Logging.Level) - if (err != nil) { + if err != nil { log.Warnf("Invalid log level '%s', defaulting to 'info'", conf.Logging.Level) level = logrus.InfoLevel } @@ -1509,7 +1509,7 @@ func processUpload(task UploadTask) error { } // Generate thumbnail after deduplication - err = generateThumbnail(task.AbsFilename, conf.Thumbnails.Directory, conf.Thumbnails.Size) + err = generateThumbnail(task.AbsFilename, conf.Thumbnails.Size) if err != nil { log.Errorf("Thumbnail generation failed for %s: %v", task.AbsFilename, err) return err // Return early to avoid logging processing completion @@ -2221,7 +2221,7 @@ func setupGracefulShutdown(server *http.Server, cancel context.CancelFunc) { } func initRedis() { - if (!conf.Redis.RedisEnabled) { + if !conf.Redis.RedisEnabled { log.Info("Redis is disabled in configuration.") return } @@ -2264,7 +2264,7 @@ func MonitorRedisHealth(ctx context.Context, client *redis.Client, checkInterval } redisConnected = false } else { - if (!redisConnected) { + if !redisConnected { log.Info("Redis reconnected successfully") } redisConnected = true @@ -2832,9 +2832,9 @@ func monitorDirectoryChanges(dir string) { } } -func generateThumbnail(originalPath, thumbnailDir, size string) error { +func generateThumbnail(originalPath, size string) error { // Check if thumbnail generation is enabled - if (!conf.Thumbnails.Enabled) { + if !conf.Thumbnails.Enabled { return nil } @@ -2979,7 +2979,7 @@ func deleteOldFiles(conf *Config, ttl time.Duration) { } func scheduleThumbnailGeneration(ctx context.Context) { - if (!conf.Thumbnails.Enabled) { + if !conf.Thumbnails.Enabled { log.Info("Thumbnail generation is disabled.") return } @@ -3007,7 +3007,7 @@ func scheduleThumbnailGeneration(ctx context.Context) { return err } if !info.IsDir() && isImageFile(path) { - generateThumbnail(path, conf.Thumbnails.Directory, conf.Thumbnails.Size) + generateThumbnail(path, conf.Thumbnails.Size) } return nil }) @@ -3035,143 +3035,143 @@ func isImageFile(path string) bool { // Add or replace the function to authenticate requests func authenticateRequest(r *http.Request) bool { - // Placeholder logic; replace with your own authentication method - apiKey := r.Header.Get("X-API-Key") - expectedAPIKey := "your-secure-api-key" - return hmac.Equal([]byte(apiKey), []byte(expectedAPIKey)) + // Placeholder logic; replace with your own authentication method + apiKey := r.Header.Get("X-API-Key") + expectedAPIKey := "your-secure-api-key" + return hmac.Equal([]byte(apiKey), []byte(expectedAPIKey)) } // Add or replace the handler for /thumbnails func handleThumbnails(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - userID := r.URL.Query().Get("user_id") - if userID == "" { - http.Error(w, "Missing user_id parameter", http.StatusBadRequest) - return - } - - // Authenticate the request - if !authenticateRequest(r) { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - // Construct the thumbnail file path (assuming JPEG) - thumbnailPath := filepath.Join(conf.Thumbnails.Directory, fmt.Sprintf("%s.jpg", userID)) - - fileInfo, err := os.Stat(thumbnailPath) - if os.IsNotExist(err) { - http.Error(w, "Thumbnail not found", http.StatusNotFound) - return - } else if err != nil { - log.WithError(err).Errorf("Error accessing thumbnail for user_id: %s", userID) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - file, err := os.Open(thumbnailPath) - if err != nil { - log.WithError(err).Errorf("Failed to open thumbnail for user_id: %s", userID) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - defer file.Close() - - // Determine the Content-Type based on file extension - ext := strings.ToLower(filepath.Ext(thumbnailPath)) - contentType := mime.TypeByExtension(ext) - if contentType == "" { - contentType = "application/octet-stream" - } - - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10)) - - if _, err := io.Copy(w, file); err != nil { - log.WithError(err).Errorf("Failed to serve thumbnail for user_id: %s", userID) - } + if r.Method != http.MethodGet { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + userID := r.URL.Query().Get("user_id") + if userID == "" { + http.Error(w, "Missing user_id parameter", http.StatusBadRequest) + return + } + + // Authenticate the request + if !authenticateRequest(r) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Construct the thumbnail file path (assuming JPEG) + thumbnailPath := filepath.Join(conf.Thumbnails.Directory, fmt.Sprintf("%s.jpg", userID)) + + fileInfo, err := os.Stat(thumbnailPath) + if os.IsNotExist(err) { + http.Error(w, "Thumbnail not found", http.StatusNotFound) + return + } else if err != nil { + log.WithError(err).Errorf("Error accessing thumbnail for user_id: %s", userID) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + file, err := os.Open(thumbnailPath) + if err != nil { + log.WithError(err).Errorf("Failed to open thumbnail for user_id: %s", userID) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + defer file.Close() + + // Determine the Content-Type based on file extension + ext := strings.ToLower(filepath.Ext(thumbnailPath)) + contentType := mime.TypeByExtension(ext) + if contentType == "" { + contentType = "application/octet-stream" + } + + w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10)) + + if _, err := io.Copy(w, file); err != nil { + log.WithError(err).Errorf("Failed to serve thumbnail for user_id: %s", userID) + } } // verifyAndRepairThumbnails verifies the integrity of thumbnail files and repairs them if necessary func verifyAndRepairThumbnails(thumbnailPaths []string, redisClient *redis.Client, originalDir string) { - // Check if redisClient is nil - if redisClient == nil { - log.Error("Redis client is nil. Cannot verify and repair thumbnails.") - return - } + // Check if redisClient is nil + if redisClient == nil { + log.Error("Redis client is nil. Cannot verify and repair thumbnails.") + return + } - // Check if thumbnailPaths is nil or empty + // Check if thumbnailPaths is nil or empty if len(thumbnailPaths) == 0 { - log.Error("Thumbnail paths are nil or empty. Nothing to verify or repair.") - return - } - - for _, thumbPath := range thumbnailPaths { - // Compute SHA-256 hash of the thumbnail file - file, err := os.Open(thumbPath) - if err != nil { - log.Warnf("Error opening thumbnail %s: %v", thumbPath, err) - continue - } - hasher := sha256.New() - if _, err := io.Copy(hasher, file); err != nil { - log.Warnf("Error hashing thumbnail %s: %v", thumbPath, err) - file.Close() - continue - } - file.Close() - computedHash := hex.EncodeToString(hasher.Sum(nil)) - - // Get stored hash from Redis - storedHash, err := redisClient.Get(context.Background(), thumbPath).Result() - if err == redis.Nil || storedHash != computedHash { - log.Warnf("Thumbnail %s is corrupted or missing. Regenerating...", thumbPath) - - // Assume original image is in originalDir with the same base name - originalPath := filepath.Join(originalDir, filepath.Base(thumbPath)) - origImage, err := imaging.Open(originalPath) - if err != nil { - log.Warnf("Error opening original image %s: %v", originalPath, err) - continue - } - - // Generate thumbnail (e.g., 200x200 pixels) - thumbnail := imaging.Thumbnail(origImage, 200, 200, imaging.Lanczos) - - // Save the regenerated thumbnail - err = imaging.Save(thumbnail, thumbPath) - if err != nil { - log.Warnf("Error saving regenerated thumbnail %s: %v", thumbPath, err) - continue - } - - // Compute new hash - file, err := os.Open(thumbPath) - if err != nil { - log.Warnf("Error opening regenerated thumbnail %s: %v", thumbPath, err) - continue - } - hasher.Reset() - if _, err := io.Copy(hasher, file); err != nil { - log.Warnf("Error hashing regenerated thumbnail %s: %v", thumbPath, err) - file.Close() - continue - } - file.Close() - newHash := hex.EncodeToString(hasher.Sum(nil)) - - // Store new hash in Redis - err = redisClient.Set(context.Background(), thumbPath, newHash, 0).Err() - if err != nil { - log.Warnf("Error storing new hash for thumbnail %s in Redis: %v", thumbPath, err) - continue - } - - log.Infof("Successfully regenerated and updated thumbnail %s", thumbPath) - } - } -} \ No newline at end of file + log.Error("Thumbnail paths are nil or empty. Nothing to verify or repair.") + return + } + + for _, thumbPath := range thumbnailPaths { + // Compute SHA-256 hash of the thumbnail file + file, err := os.Open(thumbPath) + if err != nil { + log.Warnf("Error opening thumbnail %s: %v", thumbPath, err) + continue + } + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + log.Warnf("Error hashing thumbnail %s: %v", thumbPath, err) + file.Close() + continue + } + file.Close() + computedHash := hex.EncodeToString(hasher.Sum(nil)) + + // Get stored hash from Redis + storedHash, err := redisClient.Get(context.Background(), thumbPath).Result() + if err == redis.Nil || storedHash != computedHash { + log.Warnf("Thumbnail %s is corrupted or missing. Regenerating...", thumbPath) + + // Assume original image is in originalDir with the same base name + originalPath := filepath.Join(originalDir, filepath.Base(thumbPath)) + origImage, err := imaging.Open(originalPath) + if err != nil { + log.Warnf("Error opening original image %s: %v", originalPath, err) + continue + } + + // Generate thumbnail (e.g., 200x200 pixels) + thumbnail := imaging.Thumbnail(origImage, 200, 200, imaging.Lanczos) + + // Save the regenerated thumbnail + err = imaging.Save(thumbnail, thumbPath) + if err != nil { + log.Warnf("Error saving regenerated thumbnail %s: %v", thumbPath, err) + continue + } + + // Compute new hash + file, err := os.Open(thumbPath) + if err != nil { + log.Warnf("Error opening regenerated thumbnail %s: %v", thumbPath, err) + continue + } + hasher.Reset() + if _, err := io.Copy(hasher, file); err != nil { + log.Warnf("Error hashing regenerated thumbnail %s: %v", thumbPath, err) + file.Close() + continue + } + file.Close() + newHash := hex.EncodeToString(hasher.Sum(nil)) + + // Store new hash in Redis + err = redisClient.Set(context.Background(), thumbPath, newHash, 0).Err() + if err != nil { + log.Warnf("Error storing new hash for thumbnail %s in Redis: %v", thumbPath, err) + continue + } + + log.Infof("Successfully regenerated and updated thumbnail %s", thumbPath) + } + } +}