hmn/src/website/imagefile_helper.go

113 lines
2.9 KiB
Go

package website
import (
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"image"
"io"
"net/http"
"os"
"git.handmade.network/hmn/hmn/src/db"
"git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/oops"
)
type SaveImageFileResult struct {
ImageFile *models.ImageFile
ValidationError string
FatalError error
}
/*
Reads an image file from form data and saves it to the filesystem and the database.
If the file doesn't exist, this does nothing and returns 0 for the image file id.
NOTE(ben): Someday we should replace this with the asset system.
*/
func SaveImageFile(c *RequestContext, dbConn db.ConnOrTx, fileFieldName string, maxSize int64, filepath string) SaveImageFileResult {
img, header, err := c.Req.FormFile(fileFieldName)
filename := ""
width := 0
height := 0
if err != nil && !errors.Is(err, http.ErrMissingFile) {
return SaveImageFileResult{
FatalError: oops.New(err, "failed to read uploaded file"),
}
}
if header != nil {
if header.Size > maxSize {
return SaveImageFileResult{
ValidationError: fmt.Sprintf("Image filesize too big. Max size: %d bytes", maxSize),
}
} else {
c.Perf.StartBlock("IMAGE", "Decoding image")
config, format, err := image.DecodeConfig(img)
c.Perf.EndBlock()
if err != nil {
return SaveImageFileResult{
ValidationError: "Image type not supported",
}
}
width = config.Width
height = config.Height
if width == 0 || height == 0 {
return SaveImageFileResult{
ValidationError: "Image has zero size",
}
}
filename = fmt.Sprintf("%s.%s", filepath, format)
storageFilename := fmt.Sprintf("public/media/%s", filename)
c.Perf.StartBlock("IMAGE", "Writing image file")
file, err := os.Create(storageFilename)
if err != nil {
return SaveImageFileResult{
FatalError: oops.New(err, "Failed to create local image file"),
}
}
img.Seek(0, io.SeekStart)
_, err = io.Copy(file, img)
if err != nil {
return SaveImageFileResult{
FatalError: oops.New(err, "Failed to write image to file"),
}
}
file.Close()
img.Close()
c.Perf.EndBlock()
}
}
c.Perf.EndBlock()
c.Perf.StartBlock("SQL", "Saving image file")
if filename != "" {
hasher := sha1.New()
img.Seek(0, io.SeekStart)
io.Copy(hasher, img) // NOTE(asaf): Writing to hash.Hash never returns an error according to the docs
sha1sum := hasher.Sum(nil)
imageFile, err := db.QueryOne[models.ImageFile](c, dbConn,
`
INSERT INTO image_file (file, size, sha1sum, protected, width, height)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING $columns
`,
filename, header.Size, hex.EncodeToString(sha1sum), false, width, height,
)
if err != nil {
return SaveImageFileResult{
FatalError: oops.New(err, "Failed to insert image file row"),
}
}
return SaveImageFileResult{
ImageFile: imageFile,
}
}
return SaveImageFileResult{}
}