mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app> Co-authored-by: Michael Mayer <michael@photoprism.app> Co-authored-by: graciousgrey <theresagresch@gmail.com>
This commit is contained in:
@@ -7,9 +7,10 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/entity/query"
|
||||
"github.com/photoprism/photoprism/internal/entity/search"
|
||||
"github.com/photoprism/photoprism/internal/form/batch"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/batch"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
)
|
||||
@@ -27,16 +28,16 @@ import (
|
||||
// @Router /api/v1/batch/photos/edit [post]
|
||||
func BatchPhotosEdit(router *gin.RouterGroup) {
|
||||
router.Match(MethodsPutPost, "/batch/photos/edit", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
// Require access to all photos.
|
||||
s := Auth(c, acl.ResourcePhotos, acl.AccessAll)
|
||||
|
||||
if s.Abort(c) {
|
||||
return
|
||||
}
|
||||
|
||||
conf := get.Config()
|
||||
|
||||
if !conf.Develop() && !conf.Experimental() {
|
||||
AbortNotImplemented(c)
|
||||
// Require update permissions for photos.
|
||||
if acl.Rules.Deny(acl.ResourcePhotos, s.GetUserRole(), acl.ActionUpdate) {
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -60,21 +61,74 @@ func BatchPhotosEdit(router *gin.RouterGroup) {
|
||||
|
||||
// Abort if no photos were found.
|
||||
if err != nil {
|
||||
log.Errorf("batch: %s", clean.Error(err))
|
||||
log.Errorf("batch: %s (load selection)", clean.Error(err))
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement photo metadata update based on submitted form values.
|
||||
if frm.Values != nil {
|
||||
log.Debugf("batch: updating photo metadata %#v (not yet implemented)", frm.Values)
|
||||
for _, photo := range photos {
|
||||
log.Debugf("batch: updating metadata of photo %s (not yet implemented)", photo.PhotoUID)
|
||||
}
|
||||
preloadedPhotos := map[string]*entity.Photo{}
|
||||
|
||||
if hydrated, err := query.PhotoPreloadByUIDs(photos.UIDs()); err != nil {
|
||||
log.Errorf("batch: failed to preload photo selection: %s", err)
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
} else {
|
||||
preloadedPhotos = mapPhotosByUID(hydrated)
|
||||
}
|
||||
|
||||
// Create batch edit form values form from photo metadata.
|
||||
batchFrm := batch.NewPhotosForm(photos)
|
||||
var (
|
||||
saveRequests []*batch.PhotoSaveRequest
|
||||
saveResults []bool
|
||||
savedAny bool
|
||||
)
|
||||
|
||||
if frm.Values != nil {
|
||||
outcome, saveErr := batch.PrepareAndSavePhotos(photos, preloadedPhotos, frm.Values)
|
||||
|
||||
if saveErr != nil {
|
||||
log.Errorf("batch: failed to persist photo updates: %s", saveErr)
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
|
||||
saveRequests = outcome.Requests
|
||||
saveResults = outcome.Results
|
||||
preloadedPhotos = outcome.Preloaded
|
||||
savedAny = outcome.SavedAny
|
||||
}
|
||||
|
||||
// Refresh selected photos from database?
|
||||
if !savedAny {
|
||||
// Don't refresh.
|
||||
} else if photos, count, err = search.BatchPhotos(frm.Photos, s); err != nil {
|
||||
log.Errorf("batch: %s (refresh selection)", clean.Error(err))
|
||||
}
|
||||
|
||||
// Create batch edit form values form from photo metadata using the refreshed entities so
|
||||
// the response reflects persisted album/label edits without issuing per-photo queries.
|
||||
batchFrm := batch.NewPhotosFormWithEntities(photos, preloadedPhotos)
|
||||
|
||||
if len(saveResults) > 0 {
|
||||
for i, saved := range saveResults {
|
||||
if !saved {
|
||||
continue
|
||||
}
|
||||
|
||||
photo := preloadedPhotos[saveRequests[i].Photo.PhotoUID]
|
||||
|
||||
if photo == nil {
|
||||
photo = saveRequests[i].Photo
|
||||
}
|
||||
|
||||
// PublishPhotoEvent(StatusUpdated, photo.PhotoUID, c)
|
||||
SaveSidecarYaml(photo)
|
||||
}
|
||||
|
||||
if savedAny {
|
||||
UpdateClientConfig()
|
||||
FlushCoverCache()
|
||||
}
|
||||
}
|
||||
|
||||
// Return models and form values.
|
||||
data := batch.PhotosResponse{
|
||||
@@ -85,3 +139,18 @@ func BatchPhotosEdit(router *gin.RouterGroup) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
})
|
||||
}
|
||||
|
||||
// mapPhotosByUID converts the provided list into a UID keyed lookup map so repeated
|
||||
// selections can reuse already preloaded entities instead of querying again.
|
||||
func mapPhotosByUID(photos entity.Photos) map[string]*entity.Photo {
|
||||
result := make(map[string]*entity.Photo, len(photos))
|
||||
|
||||
for _, e := range photos {
|
||||
if e == nil || e.PhotoUID == "" {
|
||||
continue
|
||||
}
|
||||
result[e.PhotoUID] = e
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user