mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2026-11-13 14:10:48 +08:00
Beta Release builds / Beta Release Changelog (push) Failing after 56s
Beta Release builds / Beta Release (, , md5, !(*musl*|*windows-arm64*|*windows7-*|*android*|*freebsd*)) (push) Skipped
Beta Release builds / Beta Release (, , md5-android, false, android-*) (push) Skipped
Beta Release builds / Beta Release (, , md5-freebsd, false, freebsd-*) (push) Skipped
Beta Release builds / Beta Release (, , md5-windows-arm64, false, windows-arm64) (push) Skipped
Beta Release builds / Beta Release (, -tags=sqlite_cgo_compat, md5-windows7, false, windows7-*) (push) Skipped
Beta Release builds / Beta Release (-ldflags=-linkmode external -extldflags '-static -fpic', , md5-linux-musl, true, linux-!(arm*|mips|mips64|mipsle|mips64le|loong64)-musl*) (push) Skipped
Beta Release builds / Beta Release (-ldflags=-linkmode external -extldflags '-static -fpic', , md5-linux-musl-arm, true, linux-arm*-musl*) (push) Skipped
Beta Release builds / Beta Release (-ldflags=-linkmode external -extldflags '-static -fpic', , md5-linux-musl-mips, true, linux-(mips|mips64|mipsle|mips64le|loong64)-musl*) (push) Skipped
Sync to Gitee / Sync GitHub to Gitee (push) Failing after 36s
Beta Release (Docker) / Build Binaries for Docker Release (Beta) (push) Failing after 57m4s
Beta Release (Docker) / Release Docker image (Beta) (aio, INSTALL_FFMPEG=true
INSTALL_ARIA2=true
, aio, suffix=-aio,onlatest=true) (push) Skipped
Beta Release (Docker) / Release Docker image (Beta) (aria2, INSTALL_ARIA2=true, aria2, suffix=-aria2,onlatest=true) (push) Skipped
Beta Release (Docker) / Release Docker image (Beta) (base, , latest, ) (push) Skipped
Beta Release (Docker) / Release Docker image (Beta) (ffmpeg, INSTALL_FFMPEG=true, ffmpeg, suffix=-ffmpeg,onlatest=true) (push) Skipped
- Fall back from cross-device rename to copy-then-remove while preserving relative symlinks, read-only directory support, file and directory modification times, and existing permissions. - Keep the source and incomplete recycle-bin copy when a named-pipe copy does not create a destination entry. - Preserve the complete recycle-bin copy when source removal fails. - Skip the recycle bin when transfer tasks remove sources after a successful cross-storage move, avoiding duplicate move and recycle-bin copies. - Add targeted coverage for relative symlinks, read-only directories, modification times, and permanent removal with a recycle bin configured. Fixes #1856 Co-authored-by: LunelleEverdawn <lunelle+suyunmeng@oplist.org>
123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package task_group
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
type SrcPathToRemove string
|
|
|
|
// ActualPath
|
|
type DstPathToHook string
|
|
|
|
func HookAndRemove(ctx context.Context, dstPath string, payloads ...any) {
|
|
dstStorage, dstActualPath, err := op.GetStorageAndActualPath(dstPath)
|
|
if err != nil {
|
|
log.Error(errors.WithMessage(err, "failed get dst storage"))
|
|
return
|
|
}
|
|
dstNeedHandleHook := setting.GetBool(conf.HandleHookAfterWriting)
|
|
dstHandleHookLimit := setting.GetFloat(conf.HandleHookRateLimit, .0)
|
|
var listLimiter *rate.Limiter
|
|
if dstNeedHandleHook && dstHandleHookLimit > .0 {
|
|
listLimiter = rate.NewLimiter(rate.Limit(dstHandleHookLimit), 1)
|
|
}
|
|
hookedPaths := make(map[string]struct{})
|
|
handleHook := func(actualPath string) {
|
|
if _, ok := hookedPaths[actualPath]; ok {
|
|
return
|
|
}
|
|
if listLimiter != nil {
|
|
_ = listLimiter.Wait(ctx)
|
|
}
|
|
files, e := op.List(ctx, dstStorage, actualPath, model.ListArgs{SkipHook: true})
|
|
if e != nil {
|
|
log.Errorf("failed handle objs update hook: %v", e)
|
|
} else {
|
|
op.HandleObjsUpdateHook(ctx, utils.GetFullPath(dstStorage.GetStorage().MountPath, actualPath), files)
|
|
hookedPaths[actualPath] = struct{}{}
|
|
}
|
|
}
|
|
if dstNeedHandleHook {
|
|
handleHook(dstActualPath)
|
|
}
|
|
for _, payload := range payloads {
|
|
switch p := payload.(type) {
|
|
case DstPathToHook:
|
|
if dstNeedHandleHook {
|
|
handleHook(string(p))
|
|
}
|
|
case SrcPathToRemove:
|
|
srcStorage, srcActualPath, err := op.GetStorageAndActualPath(string(p))
|
|
if err != nil {
|
|
log.Error(errors.WithMessage(err, "failed get src storage"))
|
|
continue
|
|
}
|
|
// The destination already holds the moved objects, so skip recycle bins.
|
|
removeCtx := context.WithValue(ctx, conf.RemovePermanentlyKey, struct{}{})
|
|
err = verifyAndRemove(removeCtx, srcStorage, dstStorage, srcActualPath, dstActualPath)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyAndRemove(ctx context.Context, srcStorage, dstStorage driver.Driver, srcPath, dstPath string) error {
|
|
srcObj, err := op.GetUnwrap(ctx, srcStorage, srcPath)
|
|
if err != nil {
|
|
return errors.WithMessagef(err, "failed get src [%s] file", path.Join(srcStorage.GetStorage().MountPath, srcPath))
|
|
}
|
|
|
|
dstObjPath := path.Join(dstPath, srcObj.GetName())
|
|
dstObj, err := op.GetUnwrap(ctx, dstStorage, dstObjPath)
|
|
if err != nil {
|
|
return errors.WithMessagef(err, "failed get dst [%s] file", path.Join(dstStorage.GetStorage().MountPath, dstObjPath))
|
|
}
|
|
|
|
if !dstObj.IsDir() {
|
|
err = op.Remove(ctx, srcStorage, srcPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed remove %s: %+v", path.Join(srcStorage.GetStorage().MountPath, srcPath), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Verify directory
|
|
srcObjs, err := op.List(ctx, srcStorage, srcPath, model.ListArgs{})
|
|
if err != nil {
|
|
return errors.WithMessagef(err, "failed list src [%s] objs", path.Join(srcStorage.GetStorage().MountPath, srcPath))
|
|
}
|
|
|
|
hasErr := false
|
|
for _, obj := range srcObjs {
|
|
srcSubPath := path.Join(srcPath, obj.GetName())
|
|
err := verifyAndRemove(ctx, srcStorage, dstStorage, srcSubPath, dstObjPath)
|
|
if err != nil {
|
|
log.Error(err)
|
|
hasErr = true
|
|
}
|
|
}
|
|
if hasErr {
|
|
return errors.Errorf("some subitems of [%s] failed to verify and remove", path.Join(srcStorage.GetStorage().MountPath, srcPath))
|
|
}
|
|
err = op.Remove(ctx, srcStorage, srcPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed remove %s: %+v", path.Join(srcStorage.GetStorage().MountPath, srcPath), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var TransferCoordinator *TaskGroupCoordinator = NewTaskGroupCoordinator("HookAndRemove", HookAndRemove)
|