Flamenco/internal/worker/command_file_mgmt.go
Sybren A. Stüvel fb89658530 Refactor: replace os.IsNotExist() with errors.Is(err, fs.ErrNotExist()
`os.IsNotExist()` is from before `errors.Is()` existed. The latter is the
recommended approach, as it also recognised wrapped errors.

No functional changes, except for recognising more cases of "does not
exist" errors as such.
2022-06-28 10:24:46 +02:00

154 lines
4.3 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"
"errors"
"fmt"
"io/fs"
"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 !errors.Is(err, fs.ErrNotExist)
}
// 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
}