diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/format/files.go | 71 | ||||
| -rw-r--r-- | utils/format/format.go | 32 | ||||
| -rw-r--r-- | utils/format/numbers.go | 20 | ||||
| -rw-r--r-- | utils/format/time.go | 7 | ||||
| -rw-r--r-- | utils/transformers/image.go | 73 | ||||
| -rw-r--r-- | utils/transformers/tokens.go | 7 | ||||
| -rw-r--r-- | utils/validators/url.go | 26 |
7 files changed, 203 insertions, 33 deletions
diff --git a/utils/format/files.go b/utils/format/files.go new file mode 100644 index 0000000..82f1546 --- /dev/null +++ b/utils/format/files.go @@ -0,0 +1,71 @@ +package format + +import ( + "bytes" + "fmt" + "image" + _ "image/gif" + "image/jpeg" + "image/png" + "strings" + + "golang.org/x/image/webp" +) + +func init() { + image.RegisterFormat("webp", "RIFF????WEBP", webp.Decode, webp.DecodeConfig) +} + +func FileSize(size int64) string { + const unit = 1024 + if size < unit { + return fmt.Sprintf("%d B", size) + } + div, exp := int64(unit), 0 + for n := size / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + + val := float64(size) / float64(div) + unitStr := "KMGTPE"[exp : exp+1] + if val == float64(int64(val)) { + return fmt.Sprintf("%d %sB", int64(val), unitStr) + } + return fmt.Sprintf("%.2f %sB", val, unitStr) +} + +func RemoveExtension(fileName string) string { + if fileName == "" { + return fileName + } + parts := strings.Split(fileName, ".") + if len(parts) <= 1 { + return fileName + } + return strings.Join(parts[:len(parts)-1], ".") +} + +func DecodeImage(imgData []byte) (image.Image, string, error) { + img, formatName, err := image.Decode(bytes.NewReader(imgData)) + return img, formatName, err +} + +func GetImageFileSize(img image.Image) int64 { + var buf bytes.Buffer + switch img.(type) { + case *image.NRGBA, *image.RGBA, *image.YCbCr: + // Use PNG encoding for lossless compression + err := png.Encode(&buf, img) + if err != nil { + return 0 + } + default: + // Fallback to JPEG encoding for other formats + err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 85}) + if err != nil { + return 0 + } + } + return int64(buf.Len()) +} diff --git a/utils/format/format.go b/utils/format/format.go deleted file mode 100644 index e57bc1f..0000000 --- a/utils/format/format.go +++ /dev/null @@ -1,32 +0,0 @@ -package format - -import "fmt" - -func FileSize(size int64) string { - const unit = 1024 - if size < unit { - return fmt.Sprintf("%d B", size) - } - div, exp := int64(unit), 0 - for n := size / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - - val := float64(size) / float64(div) - unitStr := "KMGTPE"[exp : exp+1] - if val == float64(int64(val)) { - return fmt.Sprintf("%d %sB", int64(val), unitStr) - } - return fmt.Sprintf("%.2f %sB", val, unitStr) -} - -func Count(count int64) string { - if count < 1000 { - return fmt.Sprintf("%d", count) - } else if count < 1000000 { - return fmt.Sprintf("%.1fK", float64(count)/1000) - } else { - return fmt.Sprintf("%.1fM", float64(count)/1000000) - } -} diff --git a/utils/format/numbers.go b/utils/format/numbers.go new file mode 100644 index 0000000..8f546f1 --- /dev/null +++ b/utils/format/numbers.go @@ -0,0 +1,20 @@ +package format + +import "fmt" + +func Count(count int64) string { + if count < 1000 { + return fmt.Sprintf("%d", count) + } else if count < 1000000 { + return fmt.Sprintf("%.1fK", float64(count)/1000) + } else { + return fmt.Sprintf("%.1fM", float64(count)/1000000) + } +} + +func Int64ToString(value int64) string { + if value < 0 { + return fmt.Sprintf("-%d", -value) + } + return fmt.Sprintf("%d", value) +} diff --git a/utils/format/time.go b/utils/format/time.go new file mode 100644 index 0000000..3e65337 --- /dev/null +++ b/utils/format/time.go @@ -0,0 +1,7 @@ +package format + +import "time" + +func GetCurrentTimeAsTimestamp() int64 { + return time.Now().Unix() +} diff --git a/utils/transformers/image.go b/utils/transformers/image.go index a88d8bb..e392f7f 100644 --- a/utils/transformers/image.go +++ b/utils/transformers/image.go @@ -1,6 +1,77 @@ package transformers -import "imageboard/config" +import ( + "image" + "imageboard/config" + "imageboard/models" + "imageboard/utils/format" + "imageboard/utils/validators" + "strings" +) + +func TransformImageToVariant(img image.Image, variant config.ImageSizeType) (models.ImageSize, image.Image, error) { + variantSizeMap := map[config.ImageSizeType]int{ + config.ImageSizeTypeIcon: 64, + config.ImageSizeTypeThumbnail: 256, + config.ImageSizeTypeSmall: 512, + config.ImageSizeTypeMedium: 1024, + config.ImageSizeTypeLarge: 2048, + config.ImageSizeTypeOriginal: 0, // Original size, no resizing + } + + maxWidth := variantSizeMap[variant] + if maxWidth > 0 { + img = ResizeImage(img, maxWidth) + } + + fileSize := format.GetImageFileSize(img) + + return models.ImageSize{ + SizeType: variant, + Width: img.Bounds().Dx(), + Height: img.Bounds().Dy(), + FileSize: fileSize, + }, img, nil +} + +func ResizeImage(img image.Image, maxWidth int) image.Image { + if maxWidth <= 0 || img.Bounds().Dx() <= maxWidth { + return img + } + + ratio := float64(maxWidth) / float64(img.Bounds().Dx()) + newWidth := int(float64(img.Bounds().Dx()) * ratio) + newHeight := int(float64(img.Bounds().Dy()) * ratio) + newImg := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) + for y := 0; y < newHeight; y++ { + for x := 0; x < newWidth; x++ { + srcX := int(float64(x) / ratio) + srcY := int(float64(y) / ratio) + if srcX < img.Bounds().Dx() && srcY < img.Bounds().Dy() { + newImg.Set(x, y, img.At(srcX, srcY)) + } + } + } + return newImg +} + +func CreateUniqueFileName(sourceURLOrOriginalName, imageFormat string) string { + fileName := sourceURLOrOriginalName + if validators.IsValidURL(sourceURLOrOriginalName) { + parts := strings.Split(sourceURLOrOriginalName, "/") + fileName = parts[len(parts)-1] + } + + currentTime := format.GetCurrentTimeAsTimestamp() + fileNameWithoutExtension := format.RemoveExtension(fileName) + fileName = GenerateTokenFromString(fileNameWithoutExtension + "_" + format.Int64ToString(currentTime)) + + if len(fileName) > 32 { + mid := len(fileName) / 2 + fileName = fileName[mid-16 : mid+16] + } + return fileName + "." + imageFormat +} func ConvertStringRatingToType(rating string) (config.Rating, error) { switch rating { diff --git a/utils/transformers/tokens.go b/utils/transformers/tokens.go index 7ad36ed..f2f2e0b 100644 --- a/utils/transformers/tokens.go +++ b/utils/transformers/tokens.go @@ -1,6 +1,7 @@ package transformers import ( + "crypto" "crypto/rand" "encoding/hex" ) @@ -12,3 +13,9 @@ func GenerateRandomToken() (string, error) { } return hex.EncodeToString(bytes), nil } + +func GenerateTokenFromString(input string) string { + hasher := crypto.SHA256.New() + hasher.Write([]byte(input)) + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/utils/validators/url.go b/utils/validators/url.go new file mode 100644 index 0000000..b85eca7 --- /dev/null +++ b/utils/validators/url.go @@ -0,0 +1,26 @@ +package validators + +import ( + "regexp" + "strings" +) + +func IsValidURL(url string) bool { + if url == "" { + return false + } + if len(url) > 2048 { // Common max URL length + return false + } + if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { + return false + } + + pattern := `^(http|https)://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=]+$` + matched, err := regexp.MatchString(pattern, url) + if err != nil { + return false + } + + return matched +} |
