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:
parent
7b1c3cfc28
commit
a5d98c2eb7
1
go.mod
1
go.mod
@ -24,6 +24,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -429,6 +429,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
11
main.go
11
main.go
@ -28,6 +28,9 @@ func main() {
|
||||
minZoomArg := flag.Int("min-zoom", 1, "Minimum zoom that will be sent to clients.")
|
||||
defaultCenterLatArg := flag.Float64("default-center-lat", 0, "Latitude of the default map center.")
|
||||
defaultCenterLngArg := flag.Float64("default-center-lng", 0, "Longitude of the default map center.")
|
||||
maxPhotoXArg := flag.Int("max-photo-width", 0, "Maximum width of photos. 0 means no limit.")
|
||||
maxPhotoYArg := flag.Int("max-photo-height", 0, "Maximum height of photos. 0 means no limit.")
|
||||
photoQualityArg := flag.Int("photo-quality", 90, "Photo JPEG quality.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -42,6 +45,11 @@ func main() {
|
||||
t = time.Now()
|
||||
}
|
||||
|
||||
if *maxPhotoXArg < 0 || *maxPhotoYArg < 0 {
|
||||
fmt.Fprintln(os.Stderr, "Max photo width and height cannot be less than 0.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
s := server.New(server.ServerConfig{
|
||||
VersionHash: sha1ver,
|
||||
BuildTime: &t,
|
||||
@ -55,6 +63,9 @@ func main() {
|
||||
Lat: *defaultCenterLatArg,
|
||||
Lng: *defaultCenterLngArg,
|
||||
},
|
||||
MaxPhotoX: *maxPhotoXArg,
|
||||
MaxPhotoY: *maxPhotoYArg,
|
||||
PhotoQuality: *photoQualityArg,
|
||||
})
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user