226 lines
5.5 KiB
Go
226 lines
5.5 KiB
Go
// internal/utils/files.go
|
|
package utils
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
"bytes"
|
|
)
|
|
|
|
func init() {
|
|
// Seed the random number generator
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
// EnsureDir makes sure the directory exists
|
|
func EnsureDir(dir string) error {
|
|
return os.MkdirAll(dir, 0755)
|
|
}
|
|
|
|
// FileExists checks if a file exists and is not a directory
|
|
func FileExists(filename string) bool {
|
|
info, err := os.Stat(filename)
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return !info.IsDir()
|
|
}
|
|
|
|
// GetRandomFile returns a random file from the specified directory
|
|
// It only includes files with the given extensions (no dot, e.g. "jpg", "png")
|
|
// If extensions is empty, it includes all files
|
|
func GetRandomFile(dir string, extensions []string) (string, error) {
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read directory: %w", err)
|
|
}
|
|
|
|
// Filter files based on extensions
|
|
var validFiles []string
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
if len(extensions) == 0 {
|
|
validFiles = append(validFiles, file.Name())
|
|
continue
|
|
}
|
|
|
|
ext := strings.TrimPrefix(filepath.Ext(file.Name()), ".")
|
|
for _, validExt := range extensions {
|
|
if strings.EqualFold(ext, validExt) {
|
|
validFiles = append(validFiles, file.Name())
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(validFiles) == 0 {
|
|
return "", fmt.Errorf("no valid files found in directory")
|
|
}
|
|
|
|
// Pick a random file
|
|
randomIndex := rand.Intn(len(validFiles))
|
|
return filepath.Join(dir, validFiles[randomIndex]), nil
|
|
}
|
|
|
|
// MoveFile moves a file from src to dst
|
|
func MoveFile(src, dst string) error {
|
|
// Ensure the destination directory exists
|
|
if err := EnsureDir(filepath.Dir(dst)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// First try to rename (which works within the same filesystem)
|
|
err := os.Rename(src, dst)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// If rename fails (e.g., across filesystems), copy and delete the original
|
|
if err := CopyFile(src, dst); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Remove(src)
|
|
}
|
|
|
|
// CopyFile copies a file from src to dst
|
|
func CopyFile(src, dst string) error {
|
|
// Ensure the destination directory exists
|
|
if err := EnsureDir(filepath.Dir(dst)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Open the source file
|
|
sourceFile, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sourceFile.Close()
|
|
|
|
// Create the destination file
|
|
destFile, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer destFile.Close()
|
|
|
|
// Copy the content
|
|
_, err = io.Copy(destFile, sourceFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Flush the write buffer to disk
|
|
return destFile.Sync()
|
|
}
|
|
|
|
// CalculateFileHash returns the SHA-256 hash of a file
|
|
func CalculateFileHash(filePath string) (string, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
hasher := sha256.New()
|
|
if _, err := io.Copy(hasher, file); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(hasher.Sum(nil)), nil
|
|
}
|
|
|
|
// GetFileContentType tries to determine the content type of a file
|
|
func GetFileContentType(filePath string) (string, error) {
|
|
// Open the file
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read the first 512 bytes to determine the content type
|
|
buffer := make([]byte, 512)
|
|
bytesRead, err := file.Read(buffer)
|
|
if err != nil && err != io.EOF {
|
|
return "", err
|
|
}
|
|
|
|
// If we read less than 512 bytes, resize the buffer
|
|
if bytesRead < 512 {
|
|
buffer = buffer[:bytesRead]
|
|
}
|
|
|
|
// Reset the file pointer
|
|
_, err = file.Seek(0, 0)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Detect the content type
|
|
contentType := http.DetectContentType(buffer)
|
|
|
|
// If the detection gave us a generic type, try to be more specific using the extension
|
|
if contentType == "application/octet-stream" || contentType == "text/plain" {
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
switch ext {
|
|
case ".jpg", ".jpeg":
|
|
return "image/jpeg", nil
|
|
case ".png":
|
|
return "image/png", nil
|
|
case ".gif":
|
|
return "image/gif", nil
|
|
case ".webp":
|
|
return "image/webp", nil
|
|
case ".mp4":
|
|
return "video/mp4", nil
|
|
case ".mov":
|
|
return "video/quicktime", nil
|
|
case ".webm":
|
|
return "video/webm", nil
|
|
case ".mp3":
|
|
return "audio/mpeg", nil
|
|
case ".wav":
|
|
return "audio/wav", nil
|
|
case ".pdf":
|
|
return "application/pdf", nil
|
|
}
|
|
}
|
|
|
|
// Check for WebP signature, which is sometimes not detected correctly
|
|
if contentType == "application/octet-stream" || contentType == "image/webp" {
|
|
if bytesRead > 12 {
|
|
if bytes.HasPrefix(buffer, []byte("RIFF")) && bytes.Contains(buffer[8:12], []byte("WEBP")) {
|
|
return "image/webp", nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return contentType, nil
|
|
}
|
|
|
|
// GetSupportedImageExtensions returns a list of supported image extensions
|
|
func GetSupportedImageExtensions() []string {
|
|
return []string{"jpg", "jpeg", "png", "gif", "webp", "avif"}
|
|
}
|
|
|
|
// GetSupportedVideoExtensions returns a list of supported video extensions
|
|
func GetSupportedVideoExtensions() []string {
|
|
return []string{"mp4", "webm", "mov", "avi"}
|
|
}
|
|
|
|
// GetAllSupportedMediaExtensions returns all supported media extensions
|
|
func GetAllSupportedMediaExtensions() []string {
|
|
return append(GetSupportedImageExtensions(), GetSupportedVideoExtensions()...)
|
|
} |