mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-08 07:23:10 -06:00
mediaapi/thumbnailer: Limit number of parallel generators
Fall back to selecting from already-/pre-generated thumbnails or serving the original.
This commit is contained in:
parent
4afc4bd3eb
commit
d40f54dc9d
|
|
@ -70,15 +70,16 @@ func main() {
|
|||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"BIND_ADDRESS": bindAddr,
|
||||
"LOG_DIR": logDir,
|
||||
"CONFIG_PATH": configPath,
|
||||
"ServerName": cfg.ServerName,
|
||||
"AbsBasePath": cfg.AbsBasePath,
|
||||
"MaxFileSizeBytes": *cfg.MaxFileSizeBytes,
|
||||
"DataSource": cfg.DataSource,
|
||||
"DynamicThumbnails": cfg.DynamicThumbnails,
|
||||
"ThumbnailSizes": cfg.ThumbnailSizes,
|
||||
"BIND_ADDRESS": bindAddr,
|
||||
"LOG_DIR": logDir,
|
||||
"CONFIG_PATH": configPath,
|
||||
"ServerName": cfg.ServerName,
|
||||
"AbsBasePath": cfg.AbsBasePath,
|
||||
"MaxFileSizeBytes": *cfg.MaxFileSizeBytes,
|
||||
"DataSource": cfg.DataSource,
|
||||
"DynamicThumbnails": cfg.DynamicThumbnails,
|
||||
"MaxThumbnailGenerators": cfg.MaxThumbnailGenerators,
|
||||
"ThumbnailSizes": cfg.ThumbnailSizes,
|
||||
}).Info("Starting mediaapi server with configuration")
|
||||
|
||||
routing.Setup(http.DefaultServeMux, http.DefaultClient, cfg, db)
|
||||
|
|
@ -167,6 +168,13 @@ func applyOverrides(cfg *config.MediaAPI) {
|
|||
}
|
||||
cfg.DataSource = dataSource
|
||||
}
|
||||
|
||||
if cfg.MaxThumbnailGenerators == 0 {
|
||||
log.WithField(
|
||||
"max_thumbnail_generators", cfg.MaxThumbnailGenerators,
|
||||
).Info("Using default max_thumbnail_generators")
|
||||
cfg.MaxThumbnailGenerators = 10
|
||||
}
|
||||
}
|
||||
|
||||
func validateConfig(cfg *config.MediaAPI) error {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ type MediaAPI struct {
|
|||
DataSource string `yaml:"database"`
|
||||
// Whether to dynamically generate thumbnails on-the-fly if the requested resolution is not already generated
|
||||
DynamicThumbnails bool `yaml:"dynamic_thumbnails"`
|
||||
// The maximum number of simultaneous thumbnail generators. default: 10
|
||||
MaxThumbnailGenerators int `yaml:"max_thumbnail_generators"`
|
||||
// A list of thumbnail sizes to be pre-generated for downloaded remote / uploaded content
|
||||
ThumbnailSizes []types.ThumbnailSize `yaml:"thumbnail_sizes"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,39 +40,47 @@ type thumbnailFitness struct {
|
|||
const thumbnailTemplate = "thumbnail-%vx%v-%v"
|
||||
|
||||
// GenerateThumbnails generates the configured thumbnail sizes for the source file
|
||||
func GenerateThumbnails(src types.Path, configs []types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database, logger *log.Entry) error {
|
||||
func GenerateThumbnails(src types.Path, configs []types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database, logger *log.Entry) (busy bool, errorReturn error) {
|
||||
buffer, err := bimg.Read(string(src))
|
||||
if err != nil {
|
||||
logger.WithError(err).WithField("src", src).Error("Failed to read src file")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
for _, config := range configs {
|
||||
// Note: createThumbnail does locking based on activeThumbnailGeneration
|
||||
if err = createThumbnail(src, buffer, config, mediaMetadata, activeThumbnailGeneration, db, logger); err != nil {
|
||||
busy, err = createThumbnail(src, buffer, config, mediaMetadata, activeThumbnailGeneration, maxThumbnailGenerators, db, logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).WithField("src", src).Error("Failed to generate thumbnails")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if busy {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GenerateThumbnail generates the configured thumbnail size for the source file
|
||||
func GenerateThumbnail(src types.Path, config types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database, logger *log.Entry) error {
|
||||
func GenerateThumbnail(src types.Path, config types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database, logger *log.Entry) (busy bool, errorReturn error) {
|
||||
buffer, err := bimg.Read(string(src))
|
||||
if err != nil {
|
||||
logger.WithError(err).WithFields(log.Fields{
|
||||
"src": src,
|
||||
}).Error("Failed to read src file")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
// Note: createThumbnail does locking based on activeThumbnailGeneration
|
||||
if err = createThumbnail(src, buffer, config, mediaMetadata, activeThumbnailGeneration, db, logger); err != nil {
|
||||
busy, err = createThumbnail(src, buffer, config, mediaMetadata, activeThumbnailGeneration, maxThumbnailGenerators, db, logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).WithFields(log.Fields{
|
||||
"src": src,
|
||||
}).Error("Failed to generate thumbnails")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
return nil
|
||||
if busy {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetThumbnailPath returns the path to a thumbnail given the absolute src path and thumbnail size configuration
|
||||
|
|
@ -130,7 +138,7 @@ func SelectThumbnail(desired types.ThumbnailSize, thumbnails []*types.ThumbnailM
|
|||
|
||||
// createThumbnail checks if the thumbnail exists, and if not, generates it
|
||||
// Thumbnail generation is only done once for each non-existing thumbnail.
|
||||
func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database, logger *log.Entry) (errorReturn error) {
|
||||
func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize, mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database, logger *log.Entry) (busy bool, errorReturn error) {
|
||||
logger = logger.WithFields(log.Fields{
|
||||
"Width": config.Width,
|
||||
"Height": config.Height,
|
||||
|
|
@ -140,9 +148,12 @@ func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize,
|
|||
dst := GetThumbnailPath(src, config)
|
||||
|
||||
// Note: getActiveThumbnailGeneration uses mutexes and conditions from activeThumbnailGeneration
|
||||
isActive, err := getActiveThumbnailGeneration(dst, config, activeThumbnailGeneration, logger)
|
||||
isActive, busy, err := getActiveThumbnailGeneration(dst, config, activeThumbnailGeneration, maxThumbnailGenerators, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if busy {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if isActive {
|
||||
|
|
@ -162,28 +173,28 @@ func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize,
|
|||
thumbnailMetadata, err := db.GetThumbnail(mediaMetadata.MediaID, mediaMetadata.Origin, config.Width, config.Height, config.ResizeMethod)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query database for thumbnail.")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if thumbnailMetadata != nil {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
// Note: The double-negative is intentional as os.IsExist(err) != !os.IsNotExist(err).
|
||||
// The functions are error checkers to be used in different cases.
|
||||
if _, err = os.Stat(string(dst)); !os.IsNotExist(err) {
|
||||
// Thumbnail exists
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if isActive == false {
|
||||
// Note: This should not happen, but we check just in case.
|
||||
logger.Error("Failed to stat file but this is not the active thumbnail generator. This should not happen.")
|
||||
return fmt.Errorf("Not active thumbnail generator. Stat error: %q", err)
|
||||
return false, fmt.Errorf("Not active thumbnail generator. Stat error: %q", err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
width, height, err := resize(dst, buffer, config.Width, config.Height, config.ResizeMethod == "crop", logger)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
logger.WithFields(log.Fields{
|
||||
"ActualWidth": width,
|
||||
|
|
@ -193,7 +204,7 @@ func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize,
|
|||
|
||||
stat, err := os.Stat(string(dst))
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
thumbnailMetadata = &types.ThumbnailMetadata{
|
||||
|
|
@ -217,14 +228,14 @@ func createThumbnail(src types.Path, buffer []byte, config types.ThumbnailSize,
|
|||
"ActualWidth": width,
|
||||
"ActualHeight": height,
|
||||
}).Error("Failed to store thumbnail metadata in database.")
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// getActiveThumbnailGeneration checks for active thumbnail generation
|
||||
func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, logger *log.Entry) (bool, error) {
|
||||
func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, logger *log.Entry) (isActive bool, busy bool, errorReturn error) {
|
||||
// Check if there is active thumbnail generation.
|
||||
activeThumbnailGeneration.Lock()
|
||||
defer activeThumbnailGeneration.Unlock()
|
||||
|
|
@ -234,7 +245,14 @@ func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, ac
|
|||
// NOTE: Wait unlocks and locks again internally. There is still a deferred Unlock() that will unlock this.
|
||||
activeThumbnailGenerationResult.Cond.Wait()
|
||||
// Note: either there is an error or it is nil, either way returning it is correct
|
||||
return false, activeThumbnailGenerationResult.Err
|
||||
return false, false, activeThumbnailGenerationResult.Err
|
||||
}
|
||||
|
||||
// Only allow thumbnail generation up to a maximum configured number. Above this we fall back to serving the
|
||||
// original. Or in the case of pre-generation, they maybe get generated on the first request for a thumbnail if
|
||||
// load has subsided.
|
||||
if len(activeThumbnailGeneration.PathToResult) >= maxThumbnailGenerators {
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
// No active thumbnail generation so create one
|
||||
|
|
@ -242,7 +260,7 @@ func getActiveThumbnailGeneration(dst types.Path, config types.ThumbnailSize, ac
|
|||
Cond: &sync.Cond{L: activeThumbnailGeneration},
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// broadcastGeneration broadcasts that thumbnail generation completed and the error to all waiting goroutines
|
||||
|
|
@ -252,7 +270,7 @@ func broadcastGeneration(dst types.Path, activeThumbnailGeneration *types.Active
|
|||
defer activeThumbnailGeneration.Unlock()
|
||||
if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok {
|
||||
logger.Info("Signalling other goroutines waiting for this goroutine to generate the thumbnail.")
|
||||
// Note: retErr is a named return value error that is signalled from here to waiting goroutines
|
||||
// Note: errorReturn is a named return value error that is signalled from here to waiting goroutines
|
||||
activeThumbnailGenerationResult.Err = errorReturn
|
||||
activeThumbnailGenerationResult.Cond.Broadcast()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,12 +192,12 @@ func (r *downloadRequest) doDownload(w http.ResponseWriter, cfg *config.MediaAPI
|
|||
// If we have a record, we can respond from the local file
|
||||
r.MediaMetadata = mediaMetadata
|
||||
}
|
||||
return r.respondFromLocalFile(w, cfg.AbsBasePath, activeThumbnailGeneration, db, cfg.DynamicThumbnails, cfg.ThumbnailSizes)
|
||||
return r.respondFromLocalFile(w, cfg.AbsBasePath, activeThumbnailGeneration, cfg.MaxThumbnailGenerators, db, cfg.DynamicThumbnails, cfg.ThumbnailSizes)
|
||||
}
|
||||
|
||||
// respondFromLocalFile reads a file from local storage and writes it to the http.ResponseWriter
|
||||
// Returns a util.JSONResponse error in case of error
|
||||
func (r *downloadRequest) respondFromLocalFile(w http.ResponseWriter, absBasePath types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database, dynamicThumbnails bool, thumbnailSizes []types.ThumbnailSize) *util.JSONResponse {
|
||||
func (r *downloadRequest) respondFromLocalFile(w http.ResponseWriter, absBasePath types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database, dynamicThumbnails bool, thumbnailSizes []types.ThumbnailSize) *util.JSONResponse {
|
||||
filePath, err := fileutils.GetPathFromBase64Hash(r.MediaMetadata.Base64Hash, absBasePath)
|
||||
if err != nil {
|
||||
r.Logger.WithError(err).Error("Failed to get file path from metadata")
|
||||
|
|
@ -230,7 +230,7 @@ func (r *downloadRequest) respondFromLocalFile(w http.ResponseWriter, absBasePat
|
|||
var responseFile *os.File
|
||||
var responseMetadata *types.MediaMetadata
|
||||
if r.IsThumbnailRequest {
|
||||
thumbFile, thumbMetadata, resErr := r.getThumbnailFile(types.Path(filePath), activeThumbnailGeneration, db, dynamicThumbnails, thumbnailSizes)
|
||||
thumbFile, thumbMetadata, resErr := r.getThumbnailFile(types.Path(filePath), activeThumbnailGeneration, maxThumbnailGenerators, db, dynamicThumbnails, thumbnailSizes)
|
||||
if thumbFile != nil {
|
||||
defer thumbFile.Close()
|
||||
}
|
||||
|
|
@ -284,16 +284,19 @@ func (r *downloadRequest) respondFromLocalFile(w http.ResponseWriter, absBasePat
|
|||
}
|
||||
|
||||
// Note: Thumbnail generation may be ongoing asynchronously.
|
||||
func (r *downloadRequest) getThumbnailFile(filePath types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database, dynamicThumbnails bool, thumbnailSizes []types.ThumbnailSize) (*os.File, *types.ThumbnailMetadata, *util.JSONResponse) {
|
||||
func (r *downloadRequest) getThumbnailFile(filePath types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database, dynamicThumbnails bool, thumbnailSizes []types.ThumbnailSize) (*os.File, *types.ThumbnailMetadata, *util.JSONResponse) {
|
||||
var thumbnail *types.ThumbnailMetadata
|
||||
var resErr *util.JSONResponse
|
||||
|
||||
if dynamicThumbnails {
|
||||
thumbnail, resErr = r.generateThumbnail(filePath, r.ThumbnailSize, activeThumbnailGeneration, db)
|
||||
thumbnail, resErr = r.generateThumbnail(filePath, r.ThumbnailSize, activeThumbnailGeneration, maxThumbnailGenerators, db)
|
||||
if resErr != nil {
|
||||
return nil, nil, resErr
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// If dynamicThumbnails is true but there are too many thumbnails being actively generated, we can fall back
|
||||
// to trying to use a pre-generated thumbnail
|
||||
if thumbnail == nil {
|
||||
thumbnails, err := db.GetThumbnails(r.MediaMetadata.MediaID, r.MediaMetadata.Origin)
|
||||
if err != nil {
|
||||
r.Logger.WithError(err).Error("Error looking up thumbnails")
|
||||
|
|
@ -305,13 +308,15 @@ func (r *downloadRequest) getThumbnailFile(filePath types.Path, activeThumbnailG
|
|||
// If we get a thumbnail, we're done.
|
||||
var thumbnailSize *types.ThumbnailSize
|
||||
thumbnail, thumbnailSize = thumbnailer.SelectThumbnail(r.ThumbnailSize, thumbnails, thumbnailSizes)
|
||||
if thumbnailSize != nil {
|
||||
// If dynamicThumbnails is true and we are not over-loaded then we would have generated what was requested above.
|
||||
// So we don't try to generate a pre-generated thumbnail here.
|
||||
if thumbnailSize != nil && dynamicThumbnails == false {
|
||||
r.Logger.WithFields(log.Fields{
|
||||
"Width": thumbnailSize.Width,
|
||||
"Height": thumbnailSize.Height,
|
||||
"ResizeMethod": thumbnailSize.ResizeMethod,
|
||||
}).Info("Pre-generating thumbnail for immediate response.")
|
||||
thumbnail, resErr = r.generateThumbnail(filePath, *thumbnailSize, activeThumbnailGeneration, db)
|
||||
thumbnail, resErr = r.generateThumbnail(filePath, *thumbnailSize, activeThumbnailGeneration, maxThumbnailGenerators, db)
|
||||
if resErr != nil {
|
||||
return nil, nil, resErr
|
||||
}
|
||||
|
|
@ -348,18 +353,21 @@ func (r *downloadRequest) getThumbnailFile(filePath types.Path, activeThumbnailG
|
|||
return thumbFile, thumbnail, nil
|
||||
}
|
||||
|
||||
func (r *downloadRequest) generateThumbnail(filePath types.Path, thumbnailSize types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, db *storage.Database) (*types.ThumbnailMetadata, *util.JSONResponse) {
|
||||
func (r *downloadRequest) generateThumbnail(filePath types.Path, thumbnailSize types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, db *storage.Database) (*types.ThumbnailMetadata, *util.JSONResponse) {
|
||||
logger := r.Logger.WithFields(log.Fields{
|
||||
"Width": thumbnailSize.Width,
|
||||
"Height": thumbnailSize.Height,
|
||||
"ResizeMethod": thumbnailSize.ResizeMethod,
|
||||
})
|
||||
var err error
|
||||
if err = thumbnailer.GenerateThumbnail(filePath, thumbnailSize, r.MediaMetadata, activeThumbnailGeneration, db, logger); err != nil {
|
||||
busy, err := thumbnailer.GenerateThumbnail(filePath, thumbnailSize, r.MediaMetadata, activeThumbnailGeneration, maxThumbnailGenerators, db, r.Logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error creating thumbnail")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
}
|
||||
if busy {
|
||||
return nil, nil
|
||||
}
|
||||
var thumbnail *types.ThumbnailMetadata
|
||||
thumbnail, err = db.GetThumbnail(r.MediaMetadata.MediaID, r.MediaMetadata.Origin, thumbnailSize.Width, thumbnailSize.Height, thumbnailSize.ResizeMethod)
|
||||
if err != nil {
|
||||
|
|
@ -406,7 +414,7 @@ func (r *downloadRequest) getRemoteFile(cfg *config.MediaAPI, db *storage.Databa
|
|||
|
||||
if mediaMetadata == nil {
|
||||
// If we do not have a record, we need to fetch the remote file first and then respond from the local file
|
||||
resErr := r.fetchRemoteFileAndStoreMetadata(cfg.AbsBasePath, *cfg.MaxFileSizeBytes, db, cfg.ThumbnailSizes, activeThumbnailGeneration)
|
||||
resErr := r.fetchRemoteFileAndStoreMetadata(cfg.AbsBasePath, *cfg.MaxFileSizeBytes, db, cfg.ThumbnailSizes, activeThumbnailGeneration, cfg.MaxThumbnailGenerators)
|
||||
if resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
|
|
@ -468,7 +476,7 @@ func (r *downloadRequest) broadcastMediaMetadata(activeRemoteRequests *types.Act
|
|||
}
|
||||
|
||||
// fetchRemoteFileAndStoreMetadata fetches the file from the remote server and stores its metadata in the database
|
||||
func (r *downloadRequest) fetchRemoteFileAndStoreMetadata(absBasePath types.Path, maxFileSizeBytes types.FileSizeBytes, db *storage.Database, thumbnailSizes []types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration) *util.JSONResponse {
|
||||
func (r *downloadRequest) fetchRemoteFileAndStoreMetadata(absBasePath types.Path, maxFileSizeBytes types.FileSizeBytes, db *storage.Database, thumbnailSizes []types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int) *util.JSONResponse {
|
||||
finalPath, duplicate, resErr := r.fetchRemoteFile(absBasePath, maxFileSizeBytes)
|
||||
if resErr != nil {
|
||||
return resErr
|
||||
|
|
@ -497,10 +505,13 @@ func (r *downloadRequest) fetchRemoteFileAndStoreMetadata(absBasePath types.Path
|
|||
}
|
||||
|
||||
go func() {
|
||||
err := thumbnailer.GenerateThumbnails(finalPath, thumbnailSizes, r.MediaMetadata, activeThumbnailGeneration, db, r.Logger)
|
||||
busy, err := thumbnailer.GenerateThumbnails(finalPath, thumbnailSizes, r.MediaMetadata, activeThumbnailGeneration, maxThumbnailGenerators, db, r.Logger)
|
||||
if err != nil {
|
||||
r.Logger.WithError(err).Warn("Error generating thumbnails")
|
||||
}
|
||||
if busy {
|
||||
r.Logger.Warn("Maximum number of active thumbnail generators reached. Skipping pre-generation.")
|
||||
}
|
||||
}()
|
||||
|
||||
r.Logger.WithFields(log.Fields{
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ func (r *uploadRequest) doUpload(reqReader io.Reader, cfg *config.MediaAPI, db *
|
|||
}
|
||||
}
|
||||
|
||||
if resErr := r.storeFileAndMetadata(tmpDir, cfg.AbsBasePath, db, cfg.ThumbnailSizes, activeThumbnailGeneration); resErr != nil {
|
||||
if resErr := r.storeFileAndMetadata(tmpDir, cfg.AbsBasePath, db, cfg.ThumbnailSizes, activeThumbnailGeneration, cfg.MaxThumbnailGenerators); resErr != nil {
|
||||
return resErr
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ func (r *uploadRequest) Validate(maxFileSizeBytes types.FileSizeBytes) *util.JSO
|
|||
// The order of operations is important as it avoids metadata entering the database before the file
|
||||
// is ready, and if we fail to move the file, it never gets added to the database.
|
||||
// Returns a util.JSONResponse error and cleans up directories in case of error.
|
||||
func (r *uploadRequest) storeFileAndMetadata(tmpDir types.Path, absBasePath types.Path, db *storage.Database, thumbnailSizes []types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration) *util.JSONResponse {
|
||||
func (r *uploadRequest) storeFileAndMetadata(tmpDir types.Path, absBasePath types.Path, db *storage.Database, thumbnailSizes []types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int) *util.JSONResponse {
|
||||
finalPath, duplicate, err := fileutils.MoveFileWithHashCheck(tmpDir, r.MediaMetadata, absBasePath, r.Logger)
|
||||
if err != nil {
|
||||
r.Logger.WithError(err).Error("Failed to move file.")
|
||||
|
|
@ -243,10 +243,13 @@ func (r *uploadRequest) storeFileAndMetadata(tmpDir types.Path, absBasePath type
|
|||
}
|
||||
|
||||
go func() {
|
||||
err := thumbnailer.GenerateThumbnails(finalPath, thumbnailSizes, r.MediaMetadata, activeThumbnailGeneration, db, r.Logger)
|
||||
busy, err := thumbnailer.GenerateThumbnails(finalPath, thumbnailSizes, r.MediaMetadata, activeThumbnailGeneration, maxThumbnailGenerators, db, r.Logger)
|
||||
if err != nil {
|
||||
r.Logger.WithError(err).Warn("Error generating thumbnails")
|
||||
}
|
||||
if busy {
|
||||
r.Logger.Warn("Maximum number of active thumbnail generators reached. Skipping pre-generation.")
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue