Sybren A. Stüvel 27a6dde708 Manager: add local_storage package for managing storage locations
Add a `local_storage` package that finds a suitable place to put files.
Currently it just looks at the location of the currently running
executable; it can later do other things. It can be queried for directory
to put job-specific files.

It is intended to be used by the under-development "last rendered output"
processing system, to store an image file per job. Later we should also
refactor the task log handling system to use this.
2022-06-23 16:45:38 +02:00

89 lines
2.4 KiB
Go

package local_storage
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"fmt"
"os"
"path/filepath"
"git.blender.org/flamenco/pkg/crosspath"
"github.com/rs/zerolog/log"
)
type StorageInfo struct {
rootPath string
}
// NewNextToExe returns a storage representation that sits next to the
// currently-running executable. If that directory cannot be determined, falls
// back to the current working directory.
func NewNextToExe(subdir string) StorageInfo {
exeDir := getSuitableStorageRoot()
storagePath := filepath.Join(exeDir, subdir)
return StorageInfo{
rootPath: storagePath,
}
}
// ForJob returns the directory path for storing job-related files.
func (si StorageInfo) ForJob(jobUUID string) string {
return filepath.Join(si.rootPath, pathForJob(jobUUID))
}
// Erase removes the entire storage directory from disk.
func (si StorageInfo) Erase() error {
// A few safety measures before erasing the planet.
if si.rootPath == "" {
return fmt.Errorf("%+v.Erase(): refusing to erase empty directory", si)
}
if crosspath.IsRoot(si.rootPath) {
return fmt.Errorf("%+v.Erase(): refusing to erase root directory", si)
}
if home, found := os.LookupEnv("HOME"); found && home == si.rootPath {
return fmt.Errorf("%+v.Erase(): refusing to erase home directory %s", si, home)
}
log.Debug().Str("path", si.rootPath).Msg("erasing storage directory")
return os.RemoveAll(si.rootPath)
}
// MustErase removes the entire storage directory from disk, and panics if it
// cannot do that. This is primarily aimed at cleaning up at the end of unit
// tests.
func (si StorageInfo) MustErase() {
err := si.Erase()
if err != nil {
panic(err)
}
}
// Returns a sub-directory suitable for files of this job.
// Note that this is intentionally in sync with the `filepath()` function in
// `internal/manager/task_logs/task_logs.go`.
func pathForJob(jobUUID string) string {
if jobUUID == "" {
return "jobless"
}
return filepath.Join("job-"+jobUUID[:4], jobUUID)
}
func getSuitableStorageRoot() string {
exename, err := os.Executable()
if err == nil {
return filepath.Dir(exename)
}
log.Error().Err(err).Msg("unable to determine the path of the currently running executable")
// Fall back to current working directory.
cwd, err := os.Getwd()
if err == nil {
return cwd
}
log.Error().Err(err).Msg("unable to determine the current working directory")
// Fall back to "." if all else fails.
return "."
}