API: Add action and user context to indexing events #98
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
a5f4cce181
commit
dcffa2848a
|
@ -105,7 +105,7 @@ func StartImport(router *gin.RouterGroup) {
|
|||
|
||||
// Set user UID if known.
|
||||
if s.UserUID != "" {
|
||||
opt.UserUID = s.UserUID
|
||||
opt.UID = s.UserUID
|
||||
}
|
||||
|
||||
// Start import.
|
||||
|
@ -138,8 +138,16 @@ func StartImport(router *gin.RouterGroup) {
|
|||
msg := i18n.Msg(i18n.MsgImportCompletedIn, elapsed)
|
||||
|
||||
event.Success(msg)
|
||||
event.Publish("import.completed", event.Data{"path": importPath, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"path": importPath, "seconds": elapsed})
|
||||
|
||||
eventData := event.Data{
|
||||
"uid": opt.UID,
|
||||
"action": opt.Action,
|
||||
"path": importPath,
|
||||
"seconds": elapsed,
|
||||
}
|
||||
|
||||
event.Publish("import.completed", eventData)
|
||||
event.Publish("index.completed", eventData)
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
|
|
|
@ -53,6 +53,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||
skipArchived := settings.Index.SkipArchived
|
||||
|
||||
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false, skipArchived)
|
||||
indOpt.SetUser(s.User())
|
||||
|
||||
if len(indOpt.Path) > 1 {
|
||||
event.InfoMsg(i18n.MsgIndexingFiles, clean.Log(indOpt.Path))
|
||||
|
@ -76,13 +77,17 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||
// Update index?
|
||||
if updateIndex {
|
||||
event.Publish("index.updating", event.Data{
|
||||
"step": "folders",
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"step": "folders",
|
||||
})
|
||||
|
||||
RemoveFromFolderCache(entity.RootOriginals)
|
||||
|
||||
event.Publish("index.updating", event.Data{
|
||||
"step": "purge",
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"step": "purge",
|
||||
})
|
||||
|
||||
// Configure purge options.
|
||||
|
@ -107,7 +112,9 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||
// Update moments?
|
||||
if forceUpdate {
|
||||
event.Publish("index.updating", event.Data{
|
||||
"step": "moments",
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"step": "moments",
|
||||
})
|
||||
|
||||
moments := get.Moments()
|
||||
|
@ -122,7 +129,12 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||
msg := i18n.Msg(i18n.MsgIndexingCompletedIn, elapsed)
|
||||
|
||||
event.Success(msg)
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"path": path,
|
||||
"seconds": elapsed,
|
||||
})
|
||||
|
||||
UpdateClientConfig()
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
|
@ -145,8 +144,6 @@ func AddService(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
event.SuccessMsg(i18n.MsgAccountCreated)
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
}
|
||||
|
@ -201,8 +198,6 @@ func UpdateService(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
event.SuccessMsg(i18n.MsgAccountSaved)
|
||||
|
||||
m, err = query.AccountByID(id)
|
||||
|
||||
if err != nil {
|
||||
|
@ -250,8 +245,6 @@ func DeleteService(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
event.SuccessMsg(i18n.MsgAccountDeleted)
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func UploadUserAvatar(router *gin.RouterGroup) {
|
|||
|
||||
// Users may only change their own avatar.
|
||||
if !isPrivileged && s.User().UserUID != uid {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "user uid does not match"}, s.RefID)
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "user does not match"}, s.RefID)
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
|
@ -75,8 +74,6 @@ func UpdateUser(router *gin.RouterGroup) {
|
|||
// Clear the session cache, as it contains user information.
|
||||
s.ClearCache()
|
||||
|
||||
event.SuccessMsg(i18n.MsgChangesSaved)
|
||||
|
||||
m = entity.FindUserByUID(uid)
|
||||
|
||||
if m == nil {
|
||||
|
|
|
@ -29,11 +29,13 @@ func UploadUserFiles(router *gin.RouterGroup) {
|
|||
router.POST("/users/:uid/upload/:token", func(c *gin.Context) {
|
||||
conf := get.Config()
|
||||
|
||||
// Abort in public mode or when the upload feature is disabled.
|
||||
if conf.ReadOnly() || !conf.Settings().Features.Upload {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
|
||||
return
|
||||
}
|
||||
|
||||
// Check permission.
|
||||
s := AuthAny(c, acl.ResourceFiles, acl.Permissions{acl.ActionManage, acl.ActionUpload})
|
||||
|
||||
if s.Abort(c) {
|
||||
|
@ -44,7 +46,7 @@ func UploadUserFiles(router *gin.RouterGroup) {
|
|||
|
||||
// Users may only upload their own files.
|
||||
if s.User().UserUID != uid {
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "upload files", "user uid does not match"}, s.RefID)
|
||||
event.AuditErr([]string{ClientIP(c), "session %s", "upload files", "user does not match"}, s.RefID)
|
||||
AbortForbidden(c)
|
||||
return
|
||||
}
|
||||
|
@ -60,13 +62,15 @@ func UploadUserFiles(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
event.Publish("upload.start", event.Data{"time": start})
|
||||
// Publish upload start event.
|
||||
event.Publish("upload.start", event.Data{"uid": s.UserUID, "time": start})
|
||||
|
||||
files := f.File["files"]
|
||||
uploaded := len(files)
|
||||
|
||||
var uploads []string
|
||||
|
||||
// Compose upload path.
|
||||
uploadDir, err := conf.UserUploadPath(s.UserUID, s.RefID+token)
|
||||
|
||||
if err != nil {
|
||||
|
@ -75,20 +79,24 @@ func UploadUserFiles(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
// Save uploaded files.
|
||||
for _, file := range files {
|
||||
filename := path.Join(uploadDir, filepath.Base(file.Filename))
|
||||
fileName := filepath.Base(file.Filename)
|
||||
filePath := path.Join(uploadDir, fileName)
|
||||
|
||||
log.Debugf("upload: saving file %s", clean.Log(file.Filename))
|
||||
|
||||
if err := c.SaveUploadedFile(file, filename); err != nil {
|
||||
log.Errorf("upload: failed saving file %s", clean.Log(filepath.Base(file.Filename)))
|
||||
if err = c.SaveUploadedFile(file, filePath); err != nil {
|
||||
log.Errorf("upload: failed saving file %s", clean.Log(fileName))
|
||||
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||
return
|
||||
} else {
|
||||
log.Debugf("upload: saved file %s", clean.Log(fileName))
|
||||
event.Publish("upload.saved", event.Data{"uid": s.UserUID, "file": fileName})
|
||||
}
|
||||
|
||||
uploads = append(uploads, filename)
|
||||
uploads = append(uploads, filePath)
|
||||
}
|
||||
|
||||
// Check if uploaded file is safe.
|
||||
if !conf.UploadNSFW() {
|
||||
nd := get.NsfwDetector()
|
||||
|
||||
|
@ -195,7 +203,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
|||
|
||||
// Set user UID if known.
|
||||
if s.UserUID != "" {
|
||||
opt.UserUID = s.UserUID
|
||||
opt.UID = s.UserUID
|
||||
}
|
||||
|
||||
// Start import.
|
||||
|
@ -228,8 +236,9 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
|||
msg := i18n.Msg(i18n.MsgUploadProcessed)
|
||||
|
||||
event.Success(msg)
|
||||
event.Publish("import.completed", event.Data{"path": uploadPath, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"path": uploadPath, "seconds": elapsed})
|
||||
event.Publish("import.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
event.Publish("upload.completed", event.Data{"uid": opt.UID, "path": uploadPath, "seconds": elapsed})
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
|
|
|
@ -69,6 +69,7 @@ func Import() error {
|
|||
event.InfoMsg(i18n.MsgCopyingFilesFrom, clean.Log(filepath.Base(path)))
|
||||
|
||||
var opt photoprism.ImportOptions
|
||||
opt.Action = photoprism.ActionAutoImport
|
||||
|
||||
if conf.Settings().Import.Move {
|
||||
opt = photoprism.ImportOptionsMove(path, conf.ImportDest())
|
||||
|
@ -93,8 +94,16 @@ func Import() error {
|
|||
msg := i18n.Msg(i18n.MsgImportCompletedIn, elapsed)
|
||||
|
||||
event.Success(msg)
|
||||
event.Publish("import.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
|
||||
eventData := event.Data{
|
||||
"uid": opt.UID,
|
||||
"action": opt.Action,
|
||||
"path": path,
|
||||
"seconds": elapsed,
|
||||
}
|
||||
|
||||
event.Publish("import.completed", eventData)
|
||||
event.Publish("index.completed", eventData)
|
||||
|
||||
api.UpdateClientConfig()
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ func Index() error {
|
|||
|
||||
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false, true)
|
||||
indOpt.Action = photoprism.ActionAutoIndex
|
||||
|
||||
lastRun, lastFound := ind.LastRun()
|
||||
found, indexed := ind.Start(indOpt)
|
||||
|
@ -87,7 +88,9 @@ func Index() error {
|
|||
}
|
||||
|
||||
event.Publish("index.updating", event.Data{
|
||||
"step": "moments",
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"step": "moments",
|
||||
})
|
||||
|
||||
moments := get.Moments()
|
||||
|
@ -101,7 +104,15 @@ func Index() error {
|
|||
msg := i18n.Msg(i18n.MsgIndexingCompletedIn, elapsed)
|
||||
|
||||
event.Success(msg)
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
|
||||
eventData := event.Data{
|
||||
"uid": indOpt.UID,
|
||||
"action": indOpt.Action,
|
||||
"path": path,
|
||||
"seconds": elapsed,
|
||||
}
|
||||
|
||||
event.Publish("index.completed", eventData)
|
||||
|
||||
api.UpdateClientConfig()
|
||||
|
||||
|
|
10
internal/photoprism/actions.go
Normal file
10
internal/photoprism/actions.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package photoprism
|
||||
|
||||
const (
|
||||
ActionIndex = "index"
|
||||
ActionAutoIndex = "autoindex"
|
||||
ActionImport = "import"
|
||||
ActionAutoImport = "autoimport"
|
||||
ActionUpload = "upload"
|
||||
ActionUnknown = ""
|
||||
)
|
|
@ -107,6 +107,8 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
|
|||
settings := imp.conf.Settings()
|
||||
convert := settings.Index.Convert && imp.conf.SidecarWritable()
|
||||
indexOpt := NewIndexOptions("/", true, convert, true, false, false)
|
||||
indexOpt.UID = opt.UID
|
||||
indexOpt.Action = opt.Action
|
||||
skipRaw := imp.conf.DisableRaw()
|
||||
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
|
||||
|
||||
|
|
|
@ -1,21 +1,35 @@
|
|||
package photoprism
|
||||
|
||||
import "github.com/photoprism/photoprism/internal/entity"
|
||||
|
||||
// ImportOptions represents file import options.
|
||||
type ImportOptions struct {
|
||||
UID string
|
||||
Action string
|
||||
Albums []string
|
||||
Path string
|
||||
Move bool
|
||||
NonBlocking bool
|
||||
UserUID string
|
||||
DestFolder string
|
||||
RemoveDotFiles bool
|
||||
RemoveExistingFiles bool
|
||||
RemoveEmptyDirectories bool
|
||||
}
|
||||
|
||||
// SetUser sets the user who performs the import operation.
|
||||
func (o *ImportOptions) SetUser(user *entity.User) *ImportOptions {
|
||||
if o != nil && user != nil {
|
||||
o.UID = user.UID()
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// ImportOptionsCopy returns import options for copying files to originals (read-only).
|
||||
func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
Action: ActionImport,
|
||||
Path: importPath,
|
||||
Move: false,
|
||||
NonBlocking: false,
|
||||
|
@ -31,6 +45,8 @@ func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
|||
// ImportOptionsMove returns import options for moving files to originals (modifies import directory).
|
||||
func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
Action: ActionImport,
|
||||
Path: importPath,
|
||||
Move: true,
|
||||
NonBlocking: false,
|
||||
|
@ -46,6 +62,8 @@ func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
|||
// ImportOptionsUpload returns options for importing user uploads.
|
||||
func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
|
||||
result := ImportOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
Action: ActionUpload,
|
||||
Path: uploadPath,
|
||||
Move: true,
|
||||
NonBlocking: true,
|
||||
|
|
|
@ -110,7 +110,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
// Do nothing.
|
||||
} else if file, err := entity.FirstFileByHash(fileHash); err != nil {
|
||||
// Do nothing.
|
||||
} else if err := entity.AddPhotoToUserAlbums(file.PhotoUID, opt.Albums, opt.UserUID); err != nil {
|
||||
} else if err := entity.AddPhotoToUserAlbums(file.PhotoUID, opt.Albums, opt.UID); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
|
||||
// Index main MediaFile.
|
||||
res := ind.UserMediaFile(f, o, originalName, "", opt.UserUID)
|
||||
res := ind.UserMediaFile(f, o, originalName, "", opt.UID)
|
||||
|
||||
// Log result.
|
||||
log.Infof("import: %s main %s file %s", res, f.FileType(), clean.Log(f.RootRelName()))
|
||||
|
@ -203,7 +203,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
photoUID = res.PhotoUID
|
||||
|
||||
// Add photo to album if a list of albums was provided when importing.
|
||||
if err := entity.AddPhotoToUserAlbums(photoUID, opt.Albums, opt.UserUID); err != nil {
|
||||
if err := entity.AddPhotoToUserAlbums(photoUID, opt.Albums, opt.UID); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
|
||||
// Index related media file including its original filename.
|
||||
res := ind.UserMediaFile(f, o, relatedOriginalNames[f.FileName()], photoUID, opt.UserUID)
|
||||
res := ind.UserMediaFile(f, o, relatedOriginalNames[f.FileName()], photoUID, opt.UID)
|
||||
|
||||
// Save file error.
|
||||
if fileUid, err := res.FileError(); err != nil {
|
||||
|
|
|
@ -177,6 +177,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) {
|
|||
}
|
||||
|
||||
event.Publish("index.folder", event.Data{
|
||||
"uid": o.UID,
|
||||
"filePath": relName,
|
||||
})
|
||||
|
||||
|
@ -291,6 +292,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) {
|
|||
|
||||
if updated > 0 {
|
||||
event.Publish("index.updating", event.Data{
|
||||
"uid": o.UID,
|
||||
"step": "faces",
|
||||
})
|
||||
|
||||
|
@ -302,6 +304,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) {
|
|||
}
|
||||
|
||||
event.Publish("index.updating", event.Data{
|
||||
"uid": o.UID,
|
||||
"step": "counts",
|
||||
})
|
||||
|
||||
|
|
|
@ -76,6 +76,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
|
|||
photoExists := false
|
||||
|
||||
event.Publish("index.indexing", event.Data{
|
||||
"uid": o.UID,
|
||||
"action": o.Action,
|
||||
"fileHash": fileHash,
|
||||
"fileSize": fileSize,
|
||||
"fileName": fileName,
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package photoprism
|
||||
|
||||
import "github.com/photoprism/photoprism/internal/entity"
|
||||
|
||||
// IndexOptions represents file indexing options.
|
||||
type IndexOptions struct {
|
||||
UID string
|
||||
Action string
|
||||
Path string
|
||||
Rescan bool
|
||||
Convert bool
|
||||
|
@ -15,6 +19,8 @@ type IndexOptions struct {
|
|||
// NewIndexOptions returns new index options instance.
|
||||
func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchived bool) IndexOptions {
|
||||
result := IndexOptions{
|
||||
UID: entity.Admin.UID(),
|
||||
Action: ActionIndex,
|
||||
Path: path,
|
||||
Rescan: rescan,
|
||||
Convert: convert,
|
||||
|
@ -33,6 +39,15 @@ func (o *IndexOptions) SkipUnchanged() bool {
|
|||
return !o.Rescan
|
||||
}
|
||||
|
||||
// SetUser sets the user who performs the index operation.
|
||||
func (o *IndexOptions) SetUser(user *entity.User) *IndexOptions {
|
||||
if o != nil && user != nil {
|
||||
o.UID = user.UID()
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// IndexOptionsAll returns new index options with all options set to true.
|
||||
func IndexOptionsAll() IndexOptions {
|
||||
return NewIndexOptions("/", true, true, true, false, true)
|
||||
|
|
Loading…
Reference in a new issue