mirror of
https://github.com/Cernobor/oko-server.git
synced 2025-02-24 08:27:17 +00:00
Resizing photos, db vacuuming.
* Incoming feature photos are resized and compressed to JPEG to the given dimensions and quality. * Database is vacuumed during cleanup.
This commit is contained in:
+2
-2
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -257,7 +257,7 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
|
||||
return
|
||||
}
|
||||
dataBytes, err := ioutil.ReadAll(df)
|
||||
dataBytes, err := io.ReadAll(df)
|
||||
if err != nil {
|
||||
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
|
||||
return
|
||||
|
||||
+27
-4
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -48,6 +49,9 @@ type ServerConfig struct {
|
||||
ReinitDB bool
|
||||
MinZoom int
|
||||
DefaultCenter models.Coords
|
||||
MaxPhotoX int
|
||||
MaxPhotoY int
|
||||
PhotoQuality int
|
||||
}
|
||||
|
||||
func New(config ServerConfig) *Server {
|
||||
@@ -62,6 +66,8 @@ func (s *Server) Run(ctx context.Context) {
|
||||
|
||||
s.ctx = ctx
|
||||
s.setupDB()
|
||||
defer s.cleanupDb()
|
||||
|
||||
s.setupTiles()
|
||||
|
||||
router := s.setupRouter()
|
||||
@@ -86,6 +92,9 @@ func (s *Server) Run(ctx context.Context) {
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
s.log.WithError(err).Fatal("Server forced to shutdown.")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) cleanupDb() {
|
||||
close(s.checkpointNotice)
|
||||
s.log.Info("Closing db connection pool...")
|
||||
s.dbpool.Close()
|
||||
@@ -96,6 +105,10 @@ func (s *Server) Run(ctx context.Context) {
|
||||
s.log.WithError(err).Error("Failed to open connection for final checkpoint.")
|
||||
return
|
||||
}
|
||||
err = sqlitex.Exec(conn, "vacuum", nil)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to vacuum db.")
|
||||
}
|
||||
s.checkpointDb(conn, true)
|
||||
conn.Close()
|
||||
}
|
||||
@@ -710,11 +723,22 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
|
||||
return fmt.Errorf("failed to clear bindings of prepared statement: %w", err)
|
||||
}
|
||||
|
||||
resizedPhoto, err := func() ([]byte, error) {
|
||||
defer photo.File.Close()
|
||||
|
||||
resized, err := resizePhoto(s.config.MaxPhotoX, s.config.MaxPhotoY, s.config.PhotoQuality, photo.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resized, nil
|
||||
}()
|
||||
|
||||
stmt.BindInt64(1, int64(featureID))
|
||||
stmt.BindText(2, thumbnail.ContentType)
|
||||
stmt.BindText(3, photo.ContentType)
|
||||
stmt.BindText(3, "image/jpeg")
|
||||
stmt.BindZeroBlob(4, thumbnail.Size)
|
||||
stmt.BindZeroBlob(5, photo.Size)
|
||||
stmt.BindZeroBlob(5, int64(len(resizedPhoto)))
|
||||
|
||||
_, err = stmt.Step()
|
||||
if err != nil {
|
||||
@@ -742,9 +766,8 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
|
||||
}
|
||||
err = func() error {
|
||||
defer blob.Close()
|
||||
defer photo.File.Close()
|
||||
|
||||
_, err := io.Copy(blob, photo.File)
|
||||
_, err = io.Copy(blob, bytes.NewBuffer(resizedPhoto))
|
||||
return err
|
||||
}()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
|
||||
"cernobor.cz/oko-server/models"
|
||||
)
|
||||
|
||||
@@ -32,3 +41,57 @@ func isUniqueFeatureID(features []models.Feature) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func resizePhoto(maxX, maxY, quality int, data io.Reader) ([]byte, error) {
|
||||
src, _, err := image.Decode(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode image: %w", err)
|
||||
}
|
||||
|
||||
if maxX == 0 && maxY == 0 {
|
||||
output := &bytes.Buffer{}
|
||||
jpeg.Encode(output, src, &jpeg.Options{
|
||||
Quality: 90,
|
||||
})
|
||||
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
var dst draw.Image
|
||||
srcX := src.Bounds().Max.X
|
||||
srcY := src.Bounds().Max.Y
|
||||
srcRatio := float64(srcX) / float64(srcY)
|
||||
if maxX == 0 && srcY > maxY {
|
||||
newX := int(math.Round(float64(maxY) * srcRatio))
|
||||
newY := maxY
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else if maxY == 0 && srcX > maxX {
|
||||
newX := maxX
|
||||
newY := int(math.Round(float64(maxX) / srcRatio))
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else if srcX > maxX || srcY > maxY {
|
||||
tgtRatio := float64(maxX) / float64(maxY)
|
||||
var newX, newY int
|
||||
if srcRatio > tgtRatio {
|
||||
newX = maxX
|
||||
newY = int(math.Round(float64(maxX) / srcRatio))
|
||||
} else {
|
||||
newX = int(math.Round(float64(maxY) * srcRatio))
|
||||
newY = maxY
|
||||
}
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else {
|
||||
dst = image.NewRGBA(image.Rect(0, 0, srcX, srcY))
|
||||
draw.Copy(dst, image.Point{X: 0, Y: 0}, src, src.Bounds(), draw.Over, nil)
|
||||
}
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
jpeg.Encode(output, dst, &jpeg.Options{
|
||||
Quality: quality,
|
||||
})
|
||||
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user