Restore original bundle file names
This commit is contained in:
@@ -50,6 +50,13 @@ OutputRoot/
|
|||||||
restore.log
|
restore.log
|
||||||
```
|
```
|
||||||
|
|
||||||
|
恢复时会尽量去掉去重打包时增加的名称前缀:
|
||||||
|
|
||||||
|
- `data/scene_<hash>_<原始数据名>/` 只作为 bundle 内部存储目录,恢复后不会在 `master/` 或 `slave/` 下额外保留这一层目录。
|
||||||
|
- `master/` 和 `slave/` 下直接放数据文件;如果数据文件名为 `scene_<hash>_<原始文件名>`,会恢复为 `<原始文件名>`。
|
||||||
|
- `orbit/orbit_<hash>_<原始轨道文件名>` 会恢复为 `Task_*/orbit/<原始轨道文件名>`。
|
||||||
|
- 如果 `pairs.json` 中提供了 `master_orbit_source_path` / `slave_orbit_source_path`,轨道文件会优先使用这些原始路径的文件名部分。
|
||||||
|
|
||||||
Task 目录名优先使用:
|
Task 目录名优先使用:
|
||||||
|
|
||||||
1. `task_alias`
|
1. `task_alias`
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type pair struct {
|
|||||||
SlaveData string `json:"slave_data"`
|
SlaveData string `json:"slave_data"`
|
||||||
MasterOrbit string `json:"master_orbit"`
|
MasterOrbit string `json:"master_orbit"`
|
||||||
SlaveOrbit string `json:"slave_orbit"`
|
SlaveOrbit string `json:"slave_orbit"`
|
||||||
|
MasterSourcePath string `json:"master_source_path"`
|
||||||
|
SlaveSourcePath string `json:"slave_source_path"`
|
||||||
|
MasterOrbitSource string `json:"master_orbit_source_path"`
|
||||||
|
SlaveOrbitSource string `json:"slave_orbit_source_path"`
|
||||||
MasterImagingDate string `json:"master_imaging_date"`
|
MasterImagingDate string `json:"master_imaging_date"`
|
||||||
SlaveImagingDate string `json:"slave_imaging_date"`
|
SlaveImagingDate string `json:"slave_imaging_date"`
|
||||||
TimeBaselineDays int `json:"time_baseline_days"`
|
TimeBaselineDays int `json:"time_baseline_days"`
|
||||||
@@ -60,6 +64,10 @@ type pairMetadata struct {
|
|||||||
SlaveData string `json:"slave_data"`
|
SlaveData string `json:"slave_data"`
|
||||||
MasterOrbit string `json:"master_orbit"`
|
MasterOrbit string `json:"master_orbit"`
|
||||||
SlaveOrbit string `json:"slave_orbit"`
|
SlaveOrbit string `json:"slave_orbit"`
|
||||||
|
MasterSourcePath string `json:"master_source_path,omitempty"`
|
||||||
|
SlaveSourcePath string `json:"slave_source_path,omitempty"`
|
||||||
|
MasterOrbitSource string `json:"master_orbit_source_path,omitempty"`
|
||||||
|
SlaveOrbitSource string `json:"slave_orbit_source_path,omitempty"`
|
||||||
MasterImagingDate string `json:"master_imaging_date"`
|
MasterImagingDate string `json:"master_imaging_date"`
|
||||||
SlaveImagingDate string `json:"slave_imaging_date"`
|
SlaveImagingDate string `json:"slave_imaging_date"`
|
||||||
TimeBaselineDays int `json:"time_baseline_days"`
|
TimeBaselineDays int `json:"time_baseline_days"`
|
||||||
@@ -522,10 +530,10 @@ func restorePair(cfg config, inputRoot string, outputRoot string, p pair, logger
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := copyDirContents(masterSource, filepath.Join(tempDir, "master")); err != nil {
|
if err := copyDataTree(masterSource, filepath.Join(tempDir, "master"), p.MasterData, p.MasterSourcePath); err != nil {
|
||||||
return nil, fmt.Errorf("copy master data: %w", err)
|
return nil, fmt.Errorf("copy master data: %w", err)
|
||||||
}
|
}
|
||||||
if err := copyDirContents(slaveSource, filepath.Join(tempDir, "slave")); err != nil {
|
if err := copyDataTree(slaveSource, filepath.Join(tempDir, "slave"), p.SlaveData, p.SlaveSourcePath); err != nil {
|
||||||
return nil, fmt.Errorf("copy slave data: %w", err)
|
return nil, fmt.Errorf("copy slave data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,6 +547,10 @@ func restorePair(cfg config, inputRoot string, outputRoot string, p pair, logger
|
|||||||
SlaveData: p.SlaveData,
|
SlaveData: p.SlaveData,
|
||||||
MasterOrbit: p.MasterOrbit,
|
MasterOrbit: p.MasterOrbit,
|
||||||
SlaveOrbit: p.SlaveOrbit,
|
SlaveOrbit: p.SlaveOrbit,
|
||||||
|
MasterSourcePath: p.MasterSourcePath,
|
||||||
|
SlaveSourcePath: p.SlaveSourcePath,
|
||||||
|
MasterOrbitSource: p.MasterOrbitSource,
|
||||||
|
SlaveOrbitSource: p.SlaveOrbitSource,
|
||||||
MasterImagingDate: p.MasterImagingDate,
|
MasterImagingDate: p.MasterImagingDate,
|
||||||
SlaveImagingDate: p.SlaveImagingDate,
|
SlaveImagingDate: p.SlaveImagingDate,
|
||||||
TimeBaselineDays: p.TimeBaselineDays,
|
TimeBaselineDays: p.TimeBaselineDays,
|
||||||
@@ -574,15 +586,23 @@ func taskDirectoryName(p pair) (string, error) {
|
|||||||
func copyOrbitFiles(inputRoot string, tempDir string, taskDirName string, p pair, logger *log.Logger) []string {
|
func copyOrbitFiles(inputRoot string, tempDir string, taskDirName string, p pair, logger *log.Logger) []string {
|
||||||
var warnings []string
|
var warnings []string
|
||||||
for _, entry := range []struct {
|
for _, entry := range []struct {
|
||||||
label string
|
label string
|
||||||
value string
|
value string
|
||||||
|
sourcePath string
|
||||||
}{
|
}{
|
||||||
{label: "master_orbit", value: p.MasterOrbit},
|
{label: "master_orbit", value: p.MasterOrbit, sourcePath: p.MasterOrbitSource},
|
||||||
{label: "slave_orbit", value: p.SlaveOrbit},
|
{label: "slave_orbit", value: p.SlaveOrbit, sourcePath: p.SlaveOrbitSource},
|
||||||
} {
|
} {
|
||||||
if strings.TrimSpace(entry.value) == "" {
|
if strings.TrimSpace(entry.value) == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
destName, err := restoredOrbitFileName(entry.value, entry.sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
warning := fmt.Sprintf("task %s %s cannot determine restored file name: %v", taskDirName, entry.label, err)
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
logger.Printf("warning %s", warning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
source, err := safeJoin(inputRoot, entry.value)
|
source, err := safeJoin(inputRoot, entry.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warning := fmt.Sprintf("task %s %s invalid: %v", taskDirName, entry.label, err)
|
warning := fmt.Sprintf("task %s %s invalid: %v", taskDirName, entry.label, err)
|
||||||
@@ -610,7 +630,7 @@ func copyOrbitFiles(inputRoot string, tempDir string, taskDirName string, p pair
|
|||||||
logger.Printf("warning %s", warning)
|
logger.Printf("warning %s", warning)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := copyFile(source, filepath.Join(orbitDir, filepath.Base(source)), stat.Mode()); err != nil {
|
if err := copyFile(source, filepath.Join(orbitDir, destName), stat.Mode()); err != nil {
|
||||||
warning := fmt.Sprintf("task %s cannot copy %s %s: %v", taskDirName, entry.label, entry.value, err)
|
warning := fmt.Sprintf("task %s cannot copy %s %s: %v", taskDirName, entry.label, entry.value, err)
|
||||||
warnings = append(warnings, warning)
|
warnings = append(warnings, warning)
|
||||||
logger.Printf("warning %s", warning)
|
logger.Printf("warning %s", warning)
|
||||||
@@ -623,15 +643,20 @@ func copyOrbitFiles(inputRoot string, tempDir string, taskDirName string, p pair
|
|||||||
func orbitWarnings(inputRoot string, p pair, taskDirName string) []string {
|
func orbitWarnings(inputRoot string, p pair, taskDirName string) []string {
|
||||||
var warnings []string
|
var warnings []string
|
||||||
for _, entry := range []struct {
|
for _, entry := range []struct {
|
||||||
label string
|
label string
|
||||||
value string
|
value string
|
||||||
|
sourcePath string
|
||||||
}{
|
}{
|
||||||
{label: "master_orbit", value: p.MasterOrbit},
|
{label: "master_orbit", value: p.MasterOrbit, sourcePath: p.MasterOrbitSource},
|
||||||
{label: "slave_orbit", value: p.SlaveOrbit},
|
{label: "slave_orbit", value: p.SlaveOrbit, sourcePath: p.SlaveOrbitSource},
|
||||||
} {
|
} {
|
||||||
if strings.TrimSpace(entry.value) == "" {
|
if strings.TrimSpace(entry.value) == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, err := restoredOrbitFileName(entry.value, entry.sourcePath); err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("task %s %s cannot determine restored file name: %v", taskDirName, entry.label, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
source, err := safeJoin(inputRoot, entry.value)
|
source, err := safeJoin(inputRoot, entry.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, fmt.Sprintf("task %s %s invalid: %v", taskDirName, entry.label, err))
|
warnings = append(warnings, fmt.Sprintf("task %s %s invalid: %v", taskDirName, entry.label, err))
|
||||||
@@ -646,7 +671,158 @@ func orbitWarnings(inputRoot string, p pair, taskDirName string) []string {
|
|||||||
return warnings
|
return warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyDataTree(sourceDir string, destRoleDir string, bundlePath string, sourcePath string) error {
|
||||||
|
rootName, hasRootName, err := restoredDataRootName(bundlePath, sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasRootName {
|
||||||
|
if nestedSource, ok, err := singleNestedDataRoot(sourceDir, rootName); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
sourceDir = nestedSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copyDirContentsRenamed(sourceDir, destRoleDir, restoredDataEntryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func singleNestedDataRoot(sourceDir string, rootName string) (string, bool, error) {
|
||||||
|
entries, err := os.ReadDir(sourceDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
if len(entries) != 1 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
entry := entries[0]
|
||||||
|
if !entry.IsDir() {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
if restoredDataEntryName(entry.Name()) != rootName {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
return filepath.Join(sourceDir, entry.Name()), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoredDataRootName(bundlePath string, sourcePath string) (string, bool, error) {
|
||||||
|
if name, ok, err := originalNameFromSourcePath(sourcePath); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
} else if ok {
|
||||||
|
return name, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := pathLeafName(bundlePath)
|
||||||
|
if stripped := stripBundlePrefix(name, "scene"); stripped != name {
|
||||||
|
if err := validateRestoredLeafName(stripped); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return stripped, true, nil
|
||||||
|
}
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoredDataEntryName(name string) string {
|
||||||
|
return stripBundlePrefix(name, "scene")
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoredOrbitFileName(bundlePath string, sourcePath string) (string, error) {
|
||||||
|
if name, ok, err := originalNameFromSourcePath(sourcePath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := stripBundlePrefix(pathLeafName(bundlePath), "orbit")
|
||||||
|
if err := validateRestoredLeafName(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func originalNameFromSourcePath(sourcePath string) (string, bool, error) {
|
||||||
|
name := pathLeafName(sourcePath)
|
||||||
|
if name == "" {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
switch strings.ToLower(name) {
|
||||||
|
case "master", "slave", "data", "orbit":
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
if err := validateRestoredLeafName(name); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return name, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripBundlePrefix(name string, prefix string) string {
|
||||||
|
rest, ok := strings.CutPrefix(name, prefix+"_")
|
||||||
|
if !ok {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
hashEnd := strings.IndexByte(rest, '_')
|
||||||
|
if hashEnd <= 0 || hashEnd == len(rest)-1 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
if !looksLikeHashToken(rest[:hashEnd]) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return rest[hashEnd+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func looksLikeHashToken(value string) bool {
|
||||||
|
if len(value) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range value {
|
||||||
|
switch {
|
||||||
|
case r >= '0' && r <= '9':
|
||||||
|
case r >= 'a' && r <= 'f':
|
||||||
|
case r >= 'A' && r <= 'F':
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathLeafName(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
value = strings.TrimRight(value, `/\`)
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
index := strings.LastIndexAny(value, `/\`)
|
||||||
|
if index >= 0 {
|
||||||
|
return value[index+1:]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRestoredLeafName(name string) error {
|
||||||
|
if name == "" || name == "." || name == ".." {
|
||||||
|
return fmt.Errorf("invalid empty file name")
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(name, `<>:"/\|?*`) {
|
||||||
|
return fmt.Errorf("invalid file name %q", name)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(name, " ") || strings.HasSuffix(name, ".") {
|
||||||
|
return fmt.Errorf("invalid file name %q", name)
|
||||||
|
}
|
||||||
|
for _, r := range name {
|
||||||
|
if r < 32 {
|
||||||
|
return fmt.Errorf("invalid file name %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func copyDirContents(sourceDir string, destDir string) error {
|
func copyDirContents(sourceDir string, destDir string) error {
|
||||||
|
return copyDirContentsRenamed(sourceDir, destDir, func(name string) string {
|
||||||
|
return name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirContentsRenamed(sourceDir string, destDir string, renameEntry func(string) string) error {
|
||||||
sourceEntries, err := os.ReadDir(sourceDir)
|
sourceEntries, err := os.ReadDir(sourceDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -656,7 +832,11 @@ func copyDirContents(sourceDir string, destDir string) error {
|
|||||||
}
|
}
|
||||||
for _, entry := range sourceEntries {
|
for _, entry := range sourceEntries {
|
||||||
sourcePath := filepath.Join(sourceDir, entry.Name())
|
sourcePath := filepath.Join(sourceDir, entry.Name())
|
||||||
destPath := filepath.Join(destDir, entry.Name())
|
destName := renameEntry(entry.Name())
|
||||||
|
if err := validateRestoredLeafName(destName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
destPath := filepath.Join(destDir, destName)
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -666,7 +846,7 @@ func copyDirContents(sourceDir string, destDir string) error {
|
|||||||
case mode&os.ModeSymlink != 0:
|
case mode&os.ModeSymlink != 0:
|
||||||
return fmt.Errorf("symlinks are not supported: %s", sourcePath)
|
return fmt.Errorf("symlinks are not supported: %s", sourcePath)
|
||||||
case info.IsDir():
|
case info.IsDir():
|
||||||
if err := copyDirContents(sourcePath, destPath); err != nil {
|
if err := copyDirContentsRenamed(sourcePath, destPath, renameEntry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case mode.IsRegular():
|
case mode.IsRegular():
|
||||||
|
|||||||
@@ -59,6 +59,95 @@ func TestRunRestoresPair(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunRestoresOriginalDataAndOrbitNames(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
input := filepath.Join(root, "bundle")
|
||||||
|
output := filepath.Join(root, "out")
|
||||||
|
|
||||||
|
masterName := "LT1B_MONO_SYC_STRIP1_019663_E131.0_N44.8_20251010_SLC_HH_S2A_0000898998"
|
||||||
|
slaveName := "LT1A_MONO_SYC_STRIP1_019664_E131.0_N44.8_20251022_SLC_HH_S2A_0000899001"
|
||||||
|
masterBundleName := "scene_e1e6ef658c_" + masterName
|
||||||
|
slaveBundleName := "scene_abc123def4_" + slaveName
|
||||||
|
|
||||||
|
writeTestFile(t, filepath.Join(input, "data", masterBundleName, "scene_e1e6ef658c_"+masterName+".tiff"), "master")
|
||||||
|
writeTestFile(t, filepath.Join(input, "data", slaveBundleName, "scene_abc123def4_"+slaveName+".tiff"), "slave")
|
||||||
|
writeTestFile(t, filepath.Join(input, "orbit", "orbit_0123abcd_LT1A_GpsData_GAS_C_20230508.txt"), "master orbit")
|
||||||
|
writeTestFile(t, filepath.Join(input, "orbit", "orbit_abcd1234_LT1B_GpsData_GAS_C_20230508.txt"), "slave orbit")
|
||||||
|
writeTestJSON(t, filepath.Join(input, pairsFileName), pairsDocument{
|
||||||
|
Pairs: []pair{{
|
||||||
|
PairID: "pair_0001",
|
||||||
|
TaskName: "Task_20251010_20251022",
|
||||||
|
MasterData: filepath.ToSlash(filepath.Join("data", masterBundleName)),
|
||||||
|
SlaveData: filepath.ToSlash(filepath.Join("data", slaveBundleName)),
|
||||||
|
MasterOrbit: "orbit/orbit_0123abcd_LT1A_GpsData_GAS_C_20230508.txt",
|
||||||
|
SlaveOrbit: "orbit/orbit_abcd1234_LT1B_GpsData_GAS_C_20230508.txt",
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := run(config{inputRoot: input, outputRoot: output, skipExisting: true, logWriter: io.Discard})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run failed: %v", err)
|
||||||
|
}
|
||||||
|
if result.report.Restored != 1 {
|
||||||
|
t.Fatalf("restored = %d, want 1", result.report.Restored)
|
||||||
|
}
|
||||||
|
|
||||||
|
task := filepath.Join(output, "Task_20251010_20251022")
|
||||||
|
for _, path := range []string{
|
||||||
|
filepath.Join(task, "master", masterName+".tiff"),
|
||||||
|
filepath.Join(task, "slave", slaveName+".tiff"),
|
||||||
|
filepath.Join(task, "orbit", "LT1A_GpsData_GAS_C_20230508.txt"),
|
||||||
|
filepath.Join(task, "orbit", "LT1B_GpsData_GAS_C_20230508.txt"),
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
t.Fatalf("expected %s to exist: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, path := range []string{
|
||||||
|
filepath.Join(task, "master", masterBundleName),
|
||||||
|
filepath.Join(task, "slave", slaveBundleName),
|
||||||
|
filepath.Join(task, "master", "scene_e1e6ef658c_"+masterName+".tiff"),
|
||||||
|
filepath.Join(task, "slave", "scene_abc123def4_"+slaveName+".tiff"),
|
||||||
|
filepath.Join(task, "orbit", "orbit_0123abcd_LT1A_GpsData_GAS_C_20230508.txt"),
|
||||||
|
filepath.Join(task, "orbit", "orbit_abcd1234_LT1B_GpsData_GAS_C_20230508.txt"),
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("expected %s to be absent, stat err=%v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCollapsesNestedPrefixedDataRoot(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
input := filepath.Join(root, "bundle")
|
||||||
|
output := filepath.Join(root, "out")
|
||||||
|
|
||||||
|
originalName := "LT1B_MONO_SYC_STRIP1_019663_E131.0_N44.8_20251010_SLC_HH_S2A_0000898998"
|
||||||
|
bundleName := "scene_e1e6ef658c_" + originalName
|
||||||
|
writeTestFile(t, filepath.Join(input, "data", bundleName, bundleName, "m.txt"), "master")
|
||||||
|
writeTestFile(t, filepath.Join(input, "data", "scene_abc123def4_slave", "s.txt"), "slave")
|
||||||
|
writeTestJSON(t, filepath.Join(input, pairsFileName), pairsDocument{
|
||||||
|
Pairs: []pair{{
|
||||||
|
PairID: "pair_0001",
|
||||||
|
TaskName: "Task_20251010_20251022",
|
||||||
|
MasterData: filepath.ToSlash(filepath.Join("data", bundleName)),
|
||||||
|
SlaveData: "data/scene_abc123def4_slave",
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := run(config{inputRoot: input, outputRoot: output, skipExisting: true, logWriter: io.Discard}); err != nil {
|
||||||
|
t.Fatalf("run failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
task := filepath.Join(output, "Task_20251010_20251022")
|
||||||
|
if _, err := os.Stat(filepath.Join(task, "master", "m.txt")); err != nil {
|
||||||
|
t.Fatalf("expected nested data root to be collapsed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(task, "master", originalName)); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("expected duplicated nested data root to be absent, stat err=%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunSkipsExistingCompleteTask(t *testing.T) {
|
func TestRunSkipsExistingCompleteTask(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
input := filepath.Join(root, "bundle")
|
input := filepath.Join(root, "bundle")
|
||||||
|
|||||||
Reference in New Issue
Block a user