photoprism/internal/workers/share.go
Michael Mayer 232ec78c56 Sharing: Verify that record exists before proceeding with upload #3379
Signed-off-by: Michael Mayer <michael@photoprism.app>
2023-05-06 11:27:52 +02:00

206 lines
4.5 KiB
Go

package workers
import (
"fmt"
"path"
"runtime/debug"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/remote/webdav"
"github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
)
// Share represents a share worker.
type Share struct {
conf *config.Config
}
// NewShare returns a new share worker.
func NewShare(conf *config.Config) *Share {
return &Share{conf: conf}
}
// logError logs an error message if err is not nil.
func (w *Share) logError(err error) {
if err != nil {
log.Errorf("share: %s", err.Error())
}
}
// Start starts the share worker.
func (w *Share) Start() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("share: %s (worker panic)\nstack: %s", r, debug.Stack())
log.Error(err)
}
}()
if err := mutex.ShareWorker.Start(); err != nil {
return err
}
defer mutex.ShareWorker.Stop()
f := form.SearchServices{
Share: true,
}
// Find accounts for which sharing is enabled
accounts, err := search.Accounts(f)
// Upload newly shared files
for _, a := range accounts {
if mutex.ShareWorker.Canceled() {
return nil
}
if a.AccType != remote.ServiceWebDAV {
continue
}
files, err := query.FileShares(a.ID, entity.FileShareNew)
if err != nil {
w.logError(err)
continue
}
if len(files) == 0 {
// No files to upload for this account
continue
}
size := thumb.Size{}
if a.ShareSize != "" {
if s, ok := thumb.Sizes[thumb.Name(a.ShareSize)]; ok {
size = s
} else {
size = thumb.Sizes[thumb.Fit2048]
}
}
client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
if err != nil {
return err
}
for _, file := range files {
if mutex.ShareWorker.Canceled() {
return nil
}
// Skip deleted files.
if file.File == nil || file.FileID <= 0 {
log.Warnf("share: %s cannot be uploaded because it has been deleted", clean.Log(file.RemoteName))
file.Status = entity.FileShareError
file.Error = "file not found"
file.Errors++
w.logError(entity.Db().Save(&file).Error)
continue
}
dir := path.Dir(file.RemoteName)
// Ensure remote folder exists.
if err := client.MkdirAll(dir); err != nil {
log.Debugf("share: %s", err)
}
srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName)
if fs.ImageJPEG.Equal(file.File.FileType) && size.Width > 0 && size.Height > 0 {
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, w.conf.ThumbCachePath(), size.Width, size.Height, file.File.FileOrientation, size.Options...)
if err != nil {
w.logError(err)
continue
}
}
if err := client.Upload(srcFileName, file.RemoteName); err != nil {
w.logError(err)
file.Errors++
file.Error = err.Error()
} else {
log.Infof("share: uploaded %s to %s", file.RemoteName, a.AccName)
file.Errors = 0
file.Error = ""
file.Status = entity.FileShareShared
}
// Failed too often?
if a.RetryLimit > 0 && file.Errors > a.RetryLimit {
file.Status = entity.FileShareError
}
if mutex.ShareWorker.Canceled() {
return nil
}
w.logError(entity.Db().Save(&file).Error)
}
}
// Remove previously shared files if expired
for _, a := range accounts {
if mutex.ShareWorker.Canceled() {
return nil
}
if a.AccType != remote.ServiceWebDAV {
continue
}
files, err := query.ExpiredFileShares(a)
if err != nil {
w.logError(err)
continue
}
if len(files) == 0 {
// No files to remove for this account
continue
}
client, err := webdav.NewClient(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
if err != nil {
return err
}
for _, file := range files {
if mutex.ShareWorker.Canceled() {
return nil
}
if err := client.Delete(file.RemoteName); err != nil {
file.Errors++
file.Error = err.Error()
} else {
log.Infof("share: removed %s from %s", file.RemoteName, a.AccName)
file.Errors = 0
file.Error = ""
file.Status = entity.FileShareRemoved
}
if err := entity.Db().Save(&file).Error; err != nil {
w.logError(err)
}
}
}
return err
}