Files
pocket-id/backend/internal/utils/file_util.go
2025-11-10 09:02:25 +00:00

133 lines
2.8 KiB
Go

package utils
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"mime"
"os"
"path/filepath"
"strings"
"syscall"
)
func GetFileExtension(filename string) string {
ext := filepath.Ext(filename)
if len(ext) > 0 && ext[0] == '.' {
return ext[1:]
}
return filename
}
// SplitFileName splits a full file name into name and extension.
func SplitFileName(fullName string) (name, ext string) {
dot := strings.LastIndex(fullName, ".")
if dot == -1 || dot == 0 {
return fullName, "" // no extension or hidden file like .gitignore
}
return fullName[:dot], fullName[dot+1:]
}
func GetImageMimeType(ext string) string {
switch ext {
case "jpg", "jpeg":
return "image/jpeg"
case "png":
return "image/png"
case "svg":
return "image/svg+xml"
case "ico":
return "image/x-icon"
case "gif":
return "image/gif"
case "webp":
return "image/webp"
case "avif":
return "image/avif"
case "heic":
return "image/heic"
default:
return ""
}
}
func GetImageExtensionFromMimeType(mimeType string) string {
// Normalize and strip parameters like `; charset=utf-8`
mt := strings.TrimSpace(strings.ToLower(mimeType))
if v, _, err := mime.ParseMediaType(mt); err == nil {
mt = v
}
switch mt {
case "image/jpeg", "image/jpg":
return "jpg"
case "image/png":
return "png"
case "image/svg+xml":
return "svg"
case "image/x-icon", "image/vnd.microsoft.icon":
return "ico"
case "image/gif":
return "gif"
case "image/webp":
return "webp"
case "image/avif":
return "avif"
case "image/heic", "image/heif":
return "heic"
default:
return ""
}
}
// FileExists returns true if a file exists on disk and is a regular file
func FileExists(path string) (bool, error) {
s, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return false, err
}
return !s.IsDir(), nil
}
// IsWritableDir checks if a directory exists and is writable
func IsWritableDir(dir string) (bool, error) {
// Check if directory exists and it's actually a directory
info, err := os.Stat(dir)
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, fmt.Errorf("failed to stat '%s': %w", dir, err)
}
if !info.IsDir() {
return false, nil
}
// Generate a random suffix for the test file to avoid conflicts
randomBytes := make([]byte, 8)
_, err = io.ReadFull(rand.Reader, randomBytes)
if err != nil {
return false, fmt.Errorf("failed to generate random bytes: %w", err)
}
// Check if directory is writable by trying to create a temporary file
testFile := filepath.Join(dir, ".pocketid_test_write_"+hex.EncodeToString(randomBytes))
defer os.Remove(testFile)
file, err := os.Create(testFile)
if err != nil {
if os.IsPermission(err) || errors.Is(err, syscall.EROFS) {
return false, nil
}
return false, fmt.Errorf("failed to create test file: %w", err)
}
_ = file.Close()
return true, nil
}