mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 08:44:04 +01:00
Add thumbnail filename cache and reuse db connections
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -431,9 +431,9 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
typeName := c.Param("type")
|
typeName := c.Param("type")
|
||||||
uid := c.Param("uid")
|
uid := c.Param("uid")
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
thumbType, ok := thumb.Types[typeName]
|
thumbType, ok := thumb.Types[typeName]
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
log.Warnf("album: %s is missing", txt.Quote(f.FileName))
|
log.Warnf("album: %s is missing", txt.Quote(f.FileName))
|
||||||
report("album", f.Update("FileMissing", true))
|
logError("album", f.Update("FileMissing", true))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
var log = event.Log
|
var log = event.Log
|
||||||
|
|
||||||
func report(prefix string, err error) {
|
func logError(prefix string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%s: %s", prefix, err.Error())
|
log.Errorf("%s: %s", prefix, err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func GetDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
c.Data(404, "image/svg+xml", brokenIconSvg)
|
c.Data(404, "image/svg+xml", brokenIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
report("download", f.Update("FileMissing", true))
|
logError("download", f.Update("FileMissing", true))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,9 +167,9 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
typeName := c.Param("type")
|
typeName := c.Param("type")
|
||||||
labelUID := c.Param("uid")
|
labelUID := c.Param("uid")
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
thumbType, ok := thumb.Types[typeName]
|
thumbType, ok := thumb.Types[typeName]
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
report("label", f.Update("FileMissing", true))
|
logError("label", f.Update("FileMissing", true))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
report("photo", f.Update("FileMissing", true))
|
logError("photo", f.Update("FileMissing", true))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/query"
|
"github.com/photoprism/photoprism/internal/query"
|
||||||
|
"github.com/photoprism/photoprism/internal/service"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ThumbCache struct {
|
||||||
|
FileName string
|
||||||
|
ShareName string
|
||||||
|
}
|
||||||
|
|
||||||
// GET /api/v1/t/:hash/:token/:type
|
// GET /api/v1/t/:hash/:token/:type
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
@@ -25,17 +33,41 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
fileHash := c.Param("hash")
|
fileHash := c.Param("hash")
|
||||||
typeName := c.Param("type")
|
typeName := c.Param("type")
|
||||||
|
|
||||||
thumbType, ok := thumb.Types[typeName]
|
thumbType, ok := thumb.Types[typeName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("photo: invalid thumb type %s", txt.Quote(typeName))
|
log.Errorf("thumbnail: invalid type %s", txt.Quote(typeName))
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gc := service.Cache()
|
||||||
|
cacheKey := fmt.Sprintf("thumbnail:%s:%s", fileHash, typeName)
|
||||||
|
|
||||||
|
if cacheData, ok := gc.Get(cacheKey); ok {
|
||||||
|
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||||
|
|
||||||
|
cached := cacheData.(ThumbCache)
|
||||||
|
|
||||||
|
if !fs.FileExists(cached.FileName) {
|
||||||
|
log.Errorf("thumbnail: %s not found", fileHash)
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Query("download") != "" {
|
||||||
|
c.FileAttachment(cached.FileName, cached.ShareName)
|
||||||
|
} else {
|
||||||
|
c.File(cached.FileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
f, err := query.FileByHash(fileHash)
|
f, err := query.FileByHash(fileHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,16 +94,16 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||||
|
|
||||||
if !fs.FileExists(fileName) {
|
if !fs.FileExists(fileName) {
|
||||||
log.Errorf("photo: file %s is missing", txt.Quote(f.FileName))
|
log.Errorf("thumbnail: file %s is missing", txt.Quote(f.FileName))
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
report("photo", f.Update("FileMissing", true))
|
logError("thumbnail", f.Update("FileMissing", true))
|
||||||
|
|
||||||
if f.AllFilesMissing() {
|
if f.AllFilesMissing() {
|
||||||
log.Infof("photo: deleting photo, all files missing for %s", txt.Quote(f.FileName))
|
log.Infof("thumbnail: deleting photo, all files missing for %s", txt.Quote(f.FileName))
|
||||||
|
|
||||||
report("photo", f.RelatedPhoto().Delete(false))
|
logError("thumbnail", f.RelatedPhoto().Delete(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -79,7 +111,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
|
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
|
||||||
if thumbType.ExceedsLimit() && c.Query("download") == "" {
|
if thumbType.ExceedsLimit() && c.Query("download") == "" {
|
||||||
log.Debugf("photo: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
|
log.Debugf("thumbnail: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
|
||||||
|
|
||||||
c.File(fileName)
|
c.File(fileName)
|
||||||
|
|
||||||
@@ -95,15 +127,19 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("photo: %s", err)
|
log.Errorf("thumbnail: %s", err)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||||
return
|
return
|
||||||
} else if thumbnail == "" {
|
} else if thumbnail == "" {
|
||||||
log.Errorf("photo: thumbnail name for %s is empty - bug?", filepath.Base(fileName))
|
log.Errorf("thumbnail: thumbnail name for %s is empty - bug?", filepath.Base(fileName))
|
||||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache thumbnail filename.
|
||||||
|
gc.Set(cacheKey, ThumbCache{thumbnail, f.ShareFileName()}, time.Hour*24)
|
||||||
|
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
|
||||||
|
|
||||||
if c.Query("download") != "" {
|
if c.Query("download") != "" {
|
||||||
c.FileAttachment(thumbnail, f.ShareFileName())
|
c.FileAttachment(thumbnail, f.ShareFileName())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func GetVideo(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||||
report("video", f.Update("FileMissing", true))
|
logError("video", f.Update("FileMissing", true))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
log.Infof("zip: added %s as %s", txt.Quote(f.FileName), txt.Quote(fileAlias))
|
log.Infof("zip: added %s as %s", txt.Quote(f.FileName), txt.Quote(fileAlias))
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("zip: file %s is missing", txt.Quote(f.FileName))
|
log.Warnf("zip: file %s is missing", txt.Quote(f.FileName))
|
||||||
report("zip", f.Update("FileMissing", true))
|
logError("zip", f.Update("FileMissing", true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -141,7 +142,14 @@ func (c *Config) connectToDatabase(ctx context.Context) error {
|
|||||||
|
|
||||||
db.LogMode(false)
|
db.LogMode(false)
|
||||||
db.SetLogger(log)
|
db.SetLogger(log)
|
||||||
db.DB().SetMaxIdleConns(0)
|
|
||||||
|
if runtime.NumCPU() > 4 {
|
||||||
|
db.DB().SetMaxIdleConns(runtime.NumCPU())
|
||||||
|
} else {
|
||||||
|
db.DB().SetMaxIdleConns(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.DB().SetConnMaxLifetime(time.Minute)
|
||||||
db.DB().SetMaxOpenConns(c.DatabaseConns())
|
db.DB().SetMaxOpenConns(c.DatabaseConns())
|
||||||
|
|
||||||
c.db = db
|
c.db = db
|
||||||
|
|||||||
Reference in New Issue
Block a user