
Introduce `ParameterMissingError` and `ParameterInvalidError` structs, to be returned from command executors. These replace free-form `fmt.Errorf()` style errors.
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
package worker
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* This file contains the commands in the "file-management" type group. */
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"git.blender.org/flamenco/pkg/api"
|
|
)
|
|
|
|
// cmdMoveDirectory executes the "move-directory" command.
|
|
// It moves directory 'src' to 'dest'; if 'dest' already exists, it's moved to 'dest-{timestamp}'.
|
|
func (ce *CommandExecutor) cmdMoveDirectory(ctx context.Context, logger zerolog.Logger, taskID string, cmd api.Command) error {
|
|
var src, dest string
|
|
var ok bool
|
|
|
|
if src, ok = cmdParameter[string](cmd, "src"); !ok || src == "" {
|
|
logger.Warn().Interface("command", cmd).Msg("missing 'src' parameter")
|
|
return NewParameterMissingError("src", cmd)
|
|
}
|
|
if dest, ok = cmdParameter[string](cmd, "dest"); !ok || dest == "" {
|
|
logger.Warn().Interface("command", cmd).Msg("missing 'dest' parameter")
|
|
return NewParameterMissingError("dest", cmd)
|
|
}
|
|
|
|
logger = logger.With().
|
|
Str("src", src).
|
|
Str("dest", dest).
|
|
Logger()
|
|
if !fileExists(src) {
|
|
logger.Warn().Msg("source path does not exist, not moving anything")
|
|
msg := fmt.Sprintf("%s: source path %q does not exist, not moving anything", cmd.Name, src)
|
|
if err := ce.listener.LogProduced(ctx, taskID, msg); err != nil {
|
|
return err
|
|
}
|
|
return NewParameterInvalidError("src", cmd, "path does not exist")
|
|
}
|
|
|
|
if fileExists(dest) {
|
|
backup, err := timestampedPath(dest)
|
|
if err != nil {
|
|
logger.Error().Err(err).Str("path", dest).Msg("unable to determine timestamp of directory")
|
|
return err
|
|
}
|
|
|
|
if fileExists(backup) {
|
|
logger.Debug().Str("backup", backup).Msg("backup destination also exists, finding one that does not")
|
|
backup, err = uniquePath(backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
logger.Info().
|
|
Str("toBackup", backup).
|
|
Msg("dest directory exists, moving to backup")
|
|
if err := ce.moveAndLog(ctx, taskID, cmd.Name, dest, backup); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// self._log.info("Moving %s to %s", src, dest)
|
|
// await self.worker.register_log(
|
|
// "%s: Moving %s to %s", self.command_name, src, dest
|
|
// )
|
|
// src.rename(dest)
|
|
logger.Info().Msg("moving directory")
|
|
return ce.moveAndLog(ctx, taskID, cmd.Name, src, dest)
|
|
}
|
|
|
|
// moveAndLog renames a file/directory from `src` to `dest`, and logs the moveAndLog.
|
|
// The other parameters are just for logging.
|
|
func (ce *CommandExecutor) moveAndLog(ctx context.Context, taskID, cmdName, src, dest string) error {
|
|
msg := fmt.Sprintf("%s: moving %q to %q", cmdName, src, dest)
|
|
if err := ce.listener.LogProduced(ctx, taskID, msg); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Rename(src, dest); err != nil {
|
|
msg := fmt.Sprintf("%s: could not move %q to %q: %v", cmdName, src, dest, err)
|
|
if err := ce.listener.LogProduced(ctx, taskID, msg); err != nil {
|
|
return err
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func fileExists(filename string) bool {
|
|
_, err := os.Stat(filename)
|
|
return !os.IsNotExist(err)
|
|
}
|
|
|
|
// timestampedPath returns the path with its modification time appended to the name.
|
|
func timestampedPath(filepath string) (string, error) {
|
|
stat, err := os.Stat(filepath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting mtime of %s: %w", filepath, err)
|
|
}
|
|
|
|
// Round away the milliseconds, as those aren't all that interesting.
|
|
// Uniqueness can ensured by calling unique_path() later.
|
|
mtime := stat.ModTime().Round(time.Second)
|
|
|
|
iso := mtime.Local().Format("2006-01-02_150405") // YYYY-MM-DD_HHMMSS
|
|
return fmt.Sprintf("%s-%s", filepath, iso), nil
|
|
}
|
|
|
|
// uniquePath returns the path, or if it exists, the path with a unique suffix.
|
|
func uniquePath(path string) (string, error) {
|
|
matches, err := filepath.Glob(path + "-*")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
suffixRe, err := regexp.Compile("-([0-9]+)$")
|
|
if err != nil {
|
|
return "", fmt.Errorf("compiling regular expression: %w", err)
|
|
}
|
|
|
|
var maxSuffix int64
|
|
for _, path := range matches {
|
|
matches := suffixRe.FindStringSubmatch(path)
|
|
if len(matches) < 2 {
|
|
continue
|
|
}
|
|
suffix := matches[1]
|
|
value, err := strconv.ParseInt(suffix, 10, 64)
|
|
if err != nil {
|
|
// Non-numeric suffixes are fine; they just don't count for this function.
|
|
continue
|
|
}
|
|
|
|
if value > maxSuffix {
|
|
maxSuffix = value
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("%s-%03d", path, maxSuffix+1), nil
|
|
}
|