226 lines
5.5 KiB
Go
Raw Normal View History

// internal/utils/files.go
package utils
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"path/filepath"
"strings"
"time"
2025-03-01 22:53:36 -08:00
"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) {
2025-03-01 22:53:36 -08:00
// 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()...)
}