Skip to content

Commit

Permalink
feat(proxy): add basic path-finder transparent mode (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
plastikfan committed Dec 19, 2023
1 parent ae51059 commit e98432c
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 98 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
"--fast"
],
"cSpell.words": [
"beezledub",
"bodyclose",
"chardata",
"clif",
"cmds",
"cmock",
"cmocks",
"cobrass",
"cubiest",
"deadcode",
Expand All @@ -19,6 +22,7 @@
"errcheck",
"exportloopref",
"extendio",
"faydeaudeau",
"fieldalignment",
"GOARCH",
"goconst",
Expand Down
3 changes: 3 additions & 0 deletions src/app/command/shrink-cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob
shrinkPS.Native.ThirdPartySet.KnownBy,
)

// changed is incorrect; it only contains the third party args,
// all the native args are being omitted

shrinkPS.Native.ThirdPartySet.LongChangedCL = changed

fmt.Printf("%v %v Running shrink, with options: '%v', args: '%v'\n",
Expand Down
6 changes: 4 additions & 2 deletions src/app/proxy/enter-shrink.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ 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,
behaviours: strategies{
output: inlineOutputStrategy{},
deletion: inlineDeletionStrategy{},
output: &inlineOutputStrategy{},
deletion: &inlineDeletionStrategy{},
},
}

Expand Down
10 changes: 5 additions & 5 deletions src/app/proxy/execution-step.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

// Step
type Step interface {
Run() error
Run(*SharedRunnerInfo) error
}

// Sequence
Expand All @@ -19,16 +19,16 @@ type Sequence []Step
type magickStep struct {
shared *SharedRunnerInfo
thirdPartyCL clif.ThirdPartyCommandLine
scheme string
profile string
sourcePath string
outputPath string
journalPath string
}

// Run
func (s *magickStep) Run() error {
func (s *magickStep) Run(*SharedRunnerInfo) error {
positional := []string{s.sourcePath}

err := s.shared.program.Execute(clif.Expand(positional, s.thirdPartyCL)...)

return err
return s.shared.program.Execute(clif.Expand(positional, s.thirdPartyCL, s.outputPath)...)
}
45 changes: 38 additions & 7 deletions src/app/proxy/file-manager.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
package proxy

import (
"fmt"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/snivilised/extendio/xfs/nav"
"github.com/snivilised/extendio/xfs/storage"
)

const (
beezledub = os.FileMode(0o666)
)

// FileManager knows how to translate requests into invocations on the file
// system and nothing else.
type FileManager struct {
vfs storage.VirtualFS
finder *PathFinder
}

func (fm *FileManager) setup(source string) error {
// prepare: move existing file out of the way

// Setup prepares for operation by moving existing file out of the way,
// if applicable.
func (fm *FileManager) Setup(item *nav.TraverseItem) error {
// https://pkg.go.dev/os#Rename LinkError may result
//
// this might not be right. it may be that we want to leave the
// original alone and create other outputs; in this scenario
// we don't want to rename/move the source...
//
destination := fm.finder.Destination(source)
from := &destinationInfo{
item: item,
origin: item.Parent.Path,
transparent: true, // might come from a flag
}

if folder, file := fm.finder.Destination(from); folder != "" {
if err := fm.vfs.MkdirAll(folder, beezledub); err != nil {
return errors.Wrapf(err, "could not create parent setup for '%v'", item.Path)
}

destination := filepath.Join(folder, file)

if !fm.vfs.FileExists(item.Path) {
return fmt.Errorf("source file: '%v' does not exist", item.Path)
}

if fm.vfs.FileExists(destination) {
return fmt.Errorf("destination file: '%v' already exists", destination)
}

if err := fm.vfs.Rename(source, destination); err != nil {
return errors.Wrapf(err, "could not complete setup for '%v'", source)
if err := fm.vfs.Rename(item.Path, destination); err != nil {
return errors.Wrapf(err, "could not complete setup for '%v'", item.Path)
}
}

return nil
Expand All @@ -40,7 +71,7 @@ func (fm *FileManager) delete(target string) error {
return nil
}

func (fm *FileManager) tidy() error {
func (fm *FileManager) Tidy() error {
// invoke deletions
// delete journal file
//
Expand Down
171 changes: 161 additions & 10 deletions src/app/proxy/path-finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,65 @@ package proxy

import (
"path/filepath"
"strings"

"github.com/samber/lo"
"github.com/snivilised/extendio/xfs/nav"
)

const (
inlineDestinationTempl = ""
)

type (
templateSegments []string
pfTemplatesCollection map[string]templateSegments
pfFieldValues map[string]string
)

var (
pfTemplates pfTemplatesCollection
)

func init() {
pfTemplates = pfTemplatesCollection{
// we probably have to come up with better key names...
//
"setup-inline-dest-folder": templateSegments{
"${{OUTPUT-ROOT}}",
"${{ITEM-SUB-PATH}}",
"${{TRASH-LABEL}}",
},

"setup-inline-dest-file-original-ext": templateSegments{
"${{ITEM-NAME-ORIG-EXT}}",
},
}
}

// expand returns a string as a result of joining the segments
func (tc pfTemplatesCollection) expand(segments ...string) string {
return filepath.Join(segments...)
}

// evaluate returns a string representing a file system path from a
// template string containing place-holders and field values
func (tc pfTemplatesCollection) evaluate(
sourceTemplate string,
placeHolders templateSegments,
values pfFieldValues,
) string {
const (
quantity = 1
)

return lo.Reduce(placeHolders, func(acc, field string, _ int) string {
return strings.Replace(acc, field, values[field], quantity)
},
sourceTemplate,
)
}

// INLINE-MODE: EJECT | INLINE (should we call this a strategy?
// they do the same thing but create a different output structure => OutputStrategy)
//
Expand Down Expand Up @@ -39,7 +96,7 @@ then we revert to the default which is eject(transparent)
-- then other flags could adjust the transparent mode
if --eject not specified, then ous=inline; des=inline
but if can be adjusted by --output <path>, --trash <path>
but it can be adjusted by --output <path>, --trash <path>
-- perhaps we have a transparency mode, ie perform renames such that the new generated
Expand All @@ -66,9 +123,12 @@ type strategies struct {
}

type PathFinder struct {
// is this item.Path or item.Path's parent folder?
Scheme string
Profile string
// Origin is the parent of the item (item.Parent)
//
Origin string

// only the step knows this, so this should be the parent of the output
// for scheme, this would include scheme/profile
// for profile, this should include profile
Expand All @@ -78,8 +138,11 @@ type PathFinder struct {
// perhaps represented as a slice so it can be joined with filepath.Join
//
// if Output Path is set, then use this as the output, but also
// create the intermediate paths in order to implement mirroring
//
// create the intermediate paths in order to implement mirroring.
// It is the output as indicated by --output. If not set, then it is
// derived:
// - sampling: (inline) -- item.parent; => item.parent/SHRINK/<supplement>
// - full: (inline) -- item.parent
Output string

// I think this depends on the mode (tidy/preserve)
Expand All @@ -88,12 +151,100 @@ type PathFinder struct {
behaviours strategies
}

func (f *PathFinder) Destination(source string) string {
// may be we also return a bool that indicates weather a rename
// should be implemented or not. this depends on the appropriate
// strategy. Or if we dont need to rename, we return an empty string;
// this is the preferred solution.
return filepath.Join(f.Output, source)
type staticInfo struct {
trashLabel string
legacyLabel string
}

type destinationInfo struct {
item *nav.TraverseItem
origin string // in:item.Parent.Path, ej:eject-path(output???)
// statics *staticInfo
transparent bool
//
// 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
// and destination may not be n the same folder, so the source's name
// is extracted from the source path and attached to the output
// folder.
//
// should return empty string if no move is required
func (f *PathFinder) Destination(info *destinationInfo) (destinationFolder, destinationFile string) {
// TODO: we still need to get the rest of the mirror sub-path
// ./<item.Parent>/<MIRROR-SUB-PATH>/TRASH/<scheme>/<profile>/<.item.Name>.<LEGACY>.ext
// legacyLabel := "LEGACY"
trashLabel := "TRASH"

// this does not take into account transparent, without modification;
// ie what happens if we don;t want any supplemented paths?

to := lo.TernaryF(f.Output != "",
func() string {
return f.Output // eject
},
func() string {
return info.origin // inline
},
)

destinationFolder = func() string {
templateFolderSegments := pfTemplates["setup-inline-dest-folder"]
templateFolderPath := pfTemplates.expand(filepath.Join(templateFolderSegments...))
folder := pfTemplates.evaluate(templateFolderPath, templateFolderSegments, pfFieldValues{
"${{OUTPUT-ROOT}}": to,
"${{ITEM-SUB-PATH}}": info.item.Extension.SubPath,
"${{TRASH-LABEL}}": trashLabel,
})
folder = filepath.Clean(folder)

return folder
}()

destinationFile = func() string {
templateFileSegments := pfTemplates["setup-inline-dest-file-original-ext"]
templateFilePath := pfTemplates.expand(filepath.Join(templateFileSegments...))

file := pfTemplates.evaluate(templateFilePath, templateFileSegments, pfFieldValues{
"${{ITEM-NAME-ORIG-EXT}}": info.item.Extension.Name,
})
file = filepath.Clean(file)

return file
}()

return destinationFolder, destinationFile
}

/*
Expand Down
Loading

0 comments on commit e98432c

Please sign in to comment.