diff --git a/src/app/command/shrink-cmd.go b/src/app/command/shrink-cmd.go index b754b27..c18fe63 100644 --- a/src/app/command/shrink-cmd.go +++ b/src/app/command/shrink-cmd.go @@ -9,7 +9,6 @@ import ( "github.com/snivilised/cobrass/src/assistant" "github.com/snivilised/cobrass/src/store" xi18n "github.com/snivilised/extendio/i18n" - "github.com/snivilised/extendio/xfs/utils" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -166,7 +165,7 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob defaultOutputPath, ), ¶mSet.Native.OutputPath, func(s string, f *pflag.Flag) error { - if f.Changed && !utils.FolderExists(s) { + if f.Changed && !b.Vfs.DirectoryExists(s) { return i18n.NewOutputPathDoesNotExistError(s) } @@ -186,7 +185,7 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob defaultTrashPath, ), ¶mSet.Native.TrashPath, func(s string, f *pflag.Flag) error { - if f.Changed && !utils.FolderExists(s) { + if f.Changed && !b.Vfs.DirectoryExists(s) { return i18n.NewOutputPathDoesNotExistError(s) } diff --git a/src/app/proxy/controller-registry.go b/src/app/proxy/controller-registry.go index 52d9e15..451b09b 100644 --- a/src/app/proxy/controller-registry.go +++ b/src/app/proxy/controller-registry.go @@ -21,7 +21,8 @@ func NewControllerRegistry(shared *SharedControllerInfo) *ControllerRegistry { case ControllerTypeSamplerEn: return &SamplerController{ controller: controller{ - shared: shared, + shared: shared, + private: &privateControllerInfo{}, }, } } diff --git a/src/app/proxy/controller-sampler.go b/src/app/proxy/controller-sampler.go index 9128647..3aed905 100644 --- a/src/app/proxy/controller-sampler.go +++ b/src/app/proxy/controller-sampler.go @@ -13,20 +13,27 @@ func (c *SamplerController) OnNewShrinkItem(item *nav.TraverseItem, ) error { _ = positional - profileName := c.shared.Inputs.Root.ProfileFam.Native.Profile - schemeName := c.shared.Inputs.Root.ProfileFam.Native.Scheme + // create a master path info here and pass into the sequences + // to replace the individual properties on the step + // + pi := &pathInfo{ + item: item, + scheme: c.shared.Inputs.Root.ProfileFam.Native.Scheme, + profile: c.shared.Inputs.Root.ProfileFam.Native.Profile, + origin: item.Extension.Parent, + } var sequence Sequence switch { - case profileName != "": - sequence = c.profileSequence(profileName, item.Path) + case pi.profile != "": + sequence = c.profileSequence(pi) - case schemeName != "": - sequence = c.schemeSequence(schemeName, item.Path) + case pi.scheme != "": + sequence = c.schemeSequence(pi) default: - sequence = c.adhocSequence(item.Path) + sequence = c.adhocSequence(pi) } return c.Run(item, sequence) diff --git a/src/app/proxy/controller-sampler_test.go b/src/app/proxy/controller-sampler_test.go index b6d2f72..349eaf2 100644 --- a/src/app/proxy/controller-sampler_test.go +++ b/src/app/proxy/controller-sampler_test.go @@ -100,6 +100,9 @@ func init() { "adaptive-blur": proxy.MsSchemeConfig{ Profiles: []string{"adaptive", "blur"}, }, + "singleton": proxy.MsSchemeConfig{ + Profiles: []string{"adaptive"}, + }, }, } } @@ -163,6 +166,8 @@ type controllerTE struct { given string should string args []string + outputFlag string + trashFlag string profile string relative string expected []string @@ -246,6 +251,16 @@ var _ = Describe("SamplerController", Ordered, func() { } args := options args = append(args, entry.args...) + if entry.outputFlag != "" { + output := helpers.Path(root, entry.outputFlag) + args = append(args, "--output") + args = append(args, output) + } + if entry.trashFlag != "" { + trash := helpers.Path(root, entry.trashFlag) + args = append(args, "--trash") + args = append(args, trash) + } bootstrap := command.Bootstrap{ Vfs: vfs, @@ -335,6 +350,88 @@ var _ = Describe("SamplerController", Ordered, func() { }, }), + Entry(nil, &samplerTE{ + controllerTE: controllerTE{ + given: "run transparent with scheme with single profile", + should: "sample(first) with glob filter, result file takes place of input", + relative: backyardWorldsPlanet9Scan01, + args: []string{ + "--sample", + "--no-files", "4", + "--files-gb", "*Backyard Worlds*", + "--scheme", "singleton", + "--gaussian-blur", "0.51", + "--interlace", "line", + }, + expected: backyardWorldsPlanet9Scan01First4, + intermediate: "nasa/exo/Backyard Worlds - Planet 9/sessions/scan-01", + supplement: "singleton/TRASH", + inputs: backyardWorldsPlanet9Scan01First4, + }, + }), + + Entry(nil, &samplerTE{ + controllerTE: controllerTE{ + given: "run non transparent adhoc", + should: "sample(first) with glob filter, input moved to alternative location", + relative: backyardWorldsPlanet9Scan01, + args: []string{ + "--sample", + "--no-files", "4", + "--files-gb", "*Backyard Worlds*", + "--gaussian-blur", "0.51", + "--interlace", "line", + }, + trashFlag: "discard", + expected: backyardWorldsPlanet9Scan01First4, + intermediate: "discard", + supplement: "ADHOC/TRASH", + inputs: backyardWorldsPlanet9Scan01First4, + }, + }), + + Entry(nil, &samplerTE{ + controllerTE: controllerTE{ + given: "run non transparent with profile", + should: "sample(first) with glob filter, input moved to alternative location", + relative: backyardWorldsPlanet9Scan01, + args: []string{ + "--sample", + "--no-files", "4", + "--files-gb", "*Backyard Worlds*", + "--profile", "adaptive", + "--gaussian-blur", "0.51", + "--interlace", "line", + }, + trashFlag: "discard", + expected: backyardWorldsPlanet9Scan01First4, + intermediate: "discard", + supplement: "adaptive/TRASH", + inputs: backyardWorldsPlanet9Scan01First4, + }, + }), + + Entry(nil, &samplerTE{ + controllerTE: controllerTE{ + given: "run non transparent scheme single with profile", + should: "sample(first) with glob filter, input moved to alternative location", + relative: backyardWorldsPlanet9Scan01, + args: []string{ + "--sample", + "--no-files", "4", + "--files-gb", "*Backyard Worlds*", + "--scheme", "singleton", + "--gaussian-blur", "0.51", + "--interlace", "line", + }, + trashFlag: "discard", + expected: backyardWorldsPlanet9Scan01First4, + intermediate: "discard", + supplement: "singleton/TRASH", + inputs: backyardWorldsPlanet9Scan01First4, + }, + }), + XEntry(nil, &samplerTE{ controllerTE: controllerTE{ given: "profile", diff --git a/src/app/proxy/controller.go b/src/app/proxy/controller.go index 351ffb2..c5dfdcb 100644 --- a/src/app/proxy/controller.go +++ b/src/app/proxy/controller.go @@ -14,21 +14,21 @@ import ( // type controller struct { - shared *SharedControllerInfo - local localControllerInfo + shared *SharedControllerInfo + private *privateControllerInfo } func (c *controller) profileSequence( - name, itemPath string, + pi *pathInfo, ) Sequence { changed := c.shared.Inputs.ParamSet.Native.ThirdPartySet.LongChangedCL - cl := c.composeProfileCL(name, changed) + cl := c.composeProfileCL(pi.profile, changed) step := &magickStep{ shared: c.shared, thirdPartyCL: cl, - sourcePath: itemPath, - profile: name, - // outputPath: , + sourcePath: pi.item.Path, + profile: pi.profile, + outputPath: c.shared.Inputs.ParamSet.Native.OutputPath, // journalPath: , } @@ -36,10 +36,10 @@ func (c *controller) profileSequence( } func (c *controller) schemeSequence( - name, itemPath string, + pi *pathInfo, ) Sequence { changed := c.shared.Inputs.ParamSet.Native.ThirdPartySet.LongChangedCL - schemeCfg, _ := c.shared.sampler.Scheme(name) // scheme already validated + schemeCfg, _ := c.shared.sampler.Scheme(pi.scheme) // scheme already validated sequence := make(Sequence, 0, len(schemeCfg.Profiles)) for _, current := range schemeCfg.Profiles { @@ -47,10 +47,9 @@ func (c *controller) schemeSequence( step := &magickStep{ shared: c.shared, thirdPartyCL: cl, - sourcePath: itemPath, - scheme: name, + sourcePath: pi.item.Path, profile: current, - // outputPath: , + outputPath: c.shared.Inputs.ParamSet.Native.OutputPath, // journalPath: , } @@ -61,14 +60,14 @@ func (c *controller) schemeSequence( } func (c *controller) adhocSequence( - itemPath string, + pi *pathInfo, ) Sequence { changed := c.shared.Inputs.ParamSet.Native.ThirdPartySet.LongChangedCL step := &magickStep{ shared: c.shared, thirdPartyCL: changed, - sourcePath: itemPath, - // outputPath: , + sourcePath: pi.item.Path, + outputPath: c.shared.Inputs.ParamSet.Native.OutputPath, // journalPath: , } @@ -90,26 +89,29 @@ func (c *controller) composeProfileCL( func (c *controller) Run(item *nav.TraverseItem, sequence Sequence) error { var ( - zero Step - resultErr error + zero Step + err error ) iterator := collections.ForwardRunIt[Step, error](sequence, zero) each := func(step Step) error { - return step.Run(&RunStepInfo{ - Item: item, - Source: c.local.destination, - }) + return step.Run(&c.private.pi) } - while := func(_ Step, err error) bool { - if resultErr == nil { - resultErr = err + while := func(_ Step, e error) bool { + if err == nil { + err = e } // TODO: this needs to change according to a new, not yet defined // setting, 'ContinueOnError' // - return err == nil + return e == nil + } + + c.private.pi = pathInfo{ + item: item, + origin: item.Parent.Path, + scheme: c.shared.finder.Scheme, } // TODO: need to decide a proper policy for cleaning up @@ -119,8 +121,10 @@ func (c *controller) Run(item *nav.TraverseItem, sequence Sequence) error { // Perhaps we have an error policy including one that implements // a retry. // - if c.local.destination, resultErr = c.shared.fileManager.Setup(item); resultErr != nil { - return resultErr + if c.private.pi.runStep.Source, err = c.shared.fileManager.Setup( + &c.private.pi, + ); err != nil { + return err } iterator.RunAll(each, while) @@ -128,6 +132,4 @@ func (c *controller) Run(item *nav.TraverseItem, sequence Sequence) error { return c.shared.fileManager.Tidy() } -func (c *controller) Reset() { - c.local.destination = "" -} +func (c *controller) Reset() {} diff --git a/src/app/proxy/enter-shrink.go b/src/app/proxy/enter-shrink.go index 56d76b8..13b713c 100644 --- a/src/app/proxy/enter-shrink.go +++ b/src/app/proxy/enter-shrink.go @@ -102,8 +102,8 @@ func (e *ShrinkEntry) PrincipalOptionsFn(o *nav.TraverseOptions) { func (e *ShrinkEntry) createFinder() *PathFinder { finder := &PathFinder{ - Scheme: e.Inputs.Root.ProfileFam.Native.Scheme, - Profile: e.Inputs.Root.ProfileFam.Native.Profile, + Scheme: e.Inputs.Root.ProfileFam.Native.Scheme, + ExplicitProfile: e.Inputs.Root.ProfileFam.Native.Profile, behaviours: strategies{ output: &inlineOutputStrategy{}, deletion: &inlineDeletionStrategy{}, diff --git a/src/app/proxy/execution-step.go b/src/app/proxy/execution-step.go index 65d05ad..140a7b8 100644 --- a/src/app/proxy/execution-step.go +++ b/src/app/proxy/execution-step.go @@ -4,18 +4,16 @@ import ( "path/filepath" "github.com/snivilised/cobrass/src/clif" - "github.com/snivilised/extendio/xfs/nav" ) // Step type ( RunStepInfo struct { - Item *nav.TraverseItem Source string } Step interface { - Run(rsi *RunStepInfo) error + Run(pi *pathInfo) error } // Sequence @@ -29,7 +27,6 @@ type ( type magickStep struct { shared *SharedControllerInfo thirdPartyCL clif.ThirdPartyCommandLine - scheme string profile string sourcePath string outputPath string @@ -37,17 +34,10 @@ type magickStep struct { } // Run -func (s *magickStep) Run(rsi *RunStepInfo) error { - folder, file := s.shared.finder.Result(&resultInfo{ - pathInfo: pathInfo{ - item: rsi.Item, - origin: rsi.Item.Extension.Parent, - }, - scheme: s.scheme, - profile: s.profile, - }) +func (s *magickStep) Run(pi *pathInfo) error { + folder, file := s.shared.finder.Result(pi) result := filepath.Join(folder, file) - input := []string{rsi.Source} + input := []string{pi.runStep.Source} // if transparent, then we need to ask the fm to move the // existing file out of the way. But shouldn't that already have happened diff --git a/src/app/proxy/file-manager.go b/src/app/proxy/file-manager.go index 5f28d94..8c78e22 100644 --- a/src/app/proxy/file-manager.go +++ b/src/app/proxy/file-manager.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/pkg/errors" - "github.com/snivilised/extendio/xfs/nav" "github.com/snivilised/extendio/xfs/storage" ) @@ -23,12 +22,12 @@ type FileManager struct { } // Setup prepares for operation by moving existing file out of the way, -// if applicable. -func (fm *FileManager) Setup(item *nav.TraverseItem) (destination string, err error) { +// if applicable. Return the path denoting where the input will be moved to. +func (fm *FileManager) Setup(pi *pathInfo) (destination string, err error) { if !fm.finder.transparentInput { // Any result file must not clash with the input file, so the input // file must stay in place - return item.Path, nil + return pi.item.Path, nil } // https://pkg.go.dev/os#Rename LinkError may result @@ -37,38 +36,31 @@ func (fm *FileManager) Setup(item *nav.TraverseItem) (destination string, err er // original alone and create other outputs; in this scenario // we don't want to rename/move the source... // - from := &pathInfo{ - item: item, - origin: item.Parent.Path, - } - - if folder, file := fm.finder.Destination(from); folder != "" { + if folder, file := fm.finder.Destination(pi); folder != "" { if err = fm.vfs.MkdirAll(folder, beezledub); err != nil { return errorDestination, errors.Wrapf( - err, "could not create parent setup for '%v'", item.Path, + err, "could not create parent setup for '%v'", pi.item.Path, ) } - // THIS DESTINATION IS NOT REPORTED BACK - // TO BE USED AS THE INPUT destination = filepath.Join(folder, file) - if !fm.vfs.FileExists(item.Path) { + if !fm.vfs.FileExists(pi.item.Path) { return errorDestination, fmt.Errorf( - "source file: '%v' does not exist", item.Path, + "source file: '%v' does not exist", pi.item.Path, ) } - if item.Path != destination { + if pi.item.Path != destination { if fm.vfs.FileExists(destination) { return errorDestination, fmt.Errorf( "destination file: '%v' already exists", destination, ) } - if err := fm.vfs.Rename(item.Path, destination); err != nil { + if err := fm.vfs.Rename(pi.item.Path, destination); err != nil { return errorDestination, errors.Wrapf( - err, "could not complete setup for '%v'", item.Path, + err, "could not complete setup for '%v'", pi.item.Path, ) } } diff --git a/src/app/proxy/path-finder.go b/src/app/proxy/path-finder.go index 08f6aa3..ae73d52 100644 --- a/src/app/proxy/path-finder.go +++ b/src/app/proxy/path-finder.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/samber/lo" - "github.com/snivilised/extendio/xfs/nav" ) type pfPath uint @@ -197,8 +196,8 @@ type strategies struct { } type PathFinder struct { - Scheme string - Profile string + Scheme string + ExplicitProfile string // Origin is the parent of the item (item.Parent) // Origin string @@ -233,43 +232,6 @@ type staticInfo struct { legacyLabel string } -type pathInfo struct { - item *nav.TraverseItem - origin string // in:item.Parent.Path, ej:eject-path(output???) - // statics *staticInfo - - // - // transparent=true should be the default scenario. This means - // that any changes that occur leave the file system in a state - // where nothing appears to have changed except that files have - // been modified, without name changes. This of course doesn't - // include items that end up in TRASH and can be manually deleted - // by the user. The purpose of this is to by default require - // the least amount of post-processing clean-up from the user. - // - // In sampling mode, transparent may mean something different - // because multiple files could be created for each input file. - // So, in this scenario, the original file should stay in-tact - // and the result(s) should be created into the supplementary - // location. - // - // In full mode, transparent means the input file is moved - // to a trash location. The output takes the name of the original - // file, so that by the end of processing, the resultant file - // takes the place of the source file, leaving the file system - // in a state that was the same before processing occurred. - // - // So what happens in non transparent scenario? The source file - // remains unchanged, so the user has to look at another location - // to get the result. It uses the SHRINK label to create the - // output filename; but note, we only use the SHRINK label in - // scenarios where there is a potential for a filename clash if - // the output file is in the same location as the input file - // because we want to create the least amount of friction as - // possible. This only occurs when in adhoc mode (no profile - // or scheme) -} - // Destination returns the location of what should be used // for the specified source path; ie when the program runs, it uses // a source file and requires the destination location. The source @@ -280,7 +242,8 @@ type pathInfo struct { // Destination creates a path for the input; should return empty // string for the folder, if no move is required (ie transparent) // The PathFinder will only call this function when the input -// is not transparent +// is not transparent. When the --Trash option is present, it will +// determine the destination path for the input. func (f *PathFinder) Destination(info *pathInfo) (folder, file string) { // TODO: we still need to get the rest of the mirror sub-path // legacyLabel := "LEGACY" @@ -320,15 +283,9 @@ func (f *PathFinder) Destination(info *pathInfo) (folder, file string) { return folder, file } -type resultInfo struct { - pathInfo - scheme string - profile string -} - // Result creates a path for each result so should be called by the // execution step -func (f *PathFinder) Result(info *resultInfo) (folder, file string) { +func (f *PathFinder) Result(info *pathInfo) (folder, file string) { to := lo.TernaryF(f.Output != "", func() string { return f.Output // eject @@ -383,13 +340,13 @@ func (f *PathFinder) Result(info *resultInfo) (folder, file string) { } func (f *PathFinder) supplement() string { - return lo.TernaryF(f.Scheme == "" && f.Profile == "", + return lo.TernaryF(f.Scheme == "" && f.ExplicitProfile == "", func() string { adhocLabel := "ADHOC" return adhocLabel }, func() string { - return filepath.Join(f.Scheme, f.Profile) + return filepath.Join(f.Scheme, f.ExplicitProfile) }, ) } diff --git a/src/app/proxy/proxy-defs.go b/src/app/proxy/proxy-defs.go index d76f4c7..0b86653 100644 --- a/src/app/proxy/proxy-defs.go +++ b/src/app/proxy/proxy-defs.go @@ -23,8 +23,9 @@ type SharedControllerInfo struct { fileManager *FileManager } -type localControllerInfo struct { +type privateControllerInfo struct { destination string + pi pathInfo } // ItemController @@ -34,3 +35,45 @@ type ItemController interface { ) error Reset() } + +type pathInfo struct { + item *nav.TraverseItem + origin string // in:item.Parent.Path, ej:eject-path(output???) + // statics *staticInfo + + // + // transparent=true should be the default scenario. This means + // that any changes that occur leave the file system in a state + // where nothing appears to have changed except that files have + // been modified, without name changes. This of course doesn't + // include items that end up in TRASH and can be manually deleted + // by the user. The purpose of this is to by default require + // the least amount of post-processing clean-up from the user. + // + // In sampling mode, transparent may mean something different + // because multiple files could be created for each input file. + // So, in this scenario, the original file should stay in-tact + // and the result(s) should be created into the supplementary + // location. + // + // In full mode, transparent means the input file is moved + // to a trash location. The output takes the name of the original + // file, so that by the end of processing, the resultant file + // takes the place of the source file, leaving the file system + // in a state that was the same before processing occurred. + // + // So what happens in non transparent scenario? The source file + // remains unchanged, so the user has to look at another location + // to get the result. It uses the SHRINK label to create the + // output filename; but note, we only use the SHRINK label in + // scenarios where there is a potential for a filename clash if + // the output file is in the same location as the input file + // because we want to create the least amount of friction as + // possible. This only occurs when in adhoc mode (no profile + // or scheme) + + scheme string + profile string + + runStep RunStepInfo +} diff --git a/test/data/configuration/pixa-test.yml b/test/data/configuration/pixa-test.yml index ed9487b..603d0e3 100644 --- a/test/data/configuration/pixa-test.yml +++ b/test/data/configuration/pixa-test.yml @@ -23,6 +23,7 @@ sampler: profiles: ["adaptive", "sf"] adaptive-blur: profiles: ["adaptive", "blur"] + singleton: ["adaptive"] advanced: abort-on-error: false external-program-execution-timeout: "20s" diff --git a/test/data/research/nasa-scientist-index.xml b/test/data/research/nasa-scientist-index.xml index 50bd931..ba9d0a4 100644 --- a/test/data/research/nasa-scientist-index.xml +++ b/test/data/research/nasa-scientist-index.xml @@ -2,6 +2,7 @@ +