package mfer import ( "bytes" "context" "errors" "fmt" "io/fs" "path" "path/filepath" "strings" "git.eeqj.de/sneak/mfer/internal/log" "github.com/spf13/afero" ) type manifestFile struct { path string info fs.FileInfo } func (m *manifestFile) String() string { return fmt.Sprintf("", m.path) } type manifest struct { sourceFS []afero.Fs files []*manifestFile scanOptions *ManifestScanOptions totalFileSize int64 pbInner *MFFile pbOuter *MFFileOuter output *bytes.Buffer ctx context.Context errors []*error } func (m *manifest) String() string { return fmt.Sprintf("", len(m.files), m.totalFileSize) } type ManifestScanOptions struct { IgnoreDotfiles bool FollowSymLinks bool } func (m *manifest) HasError() bool { return len(m.errors) > 0 } func (m *manifest) AddError(e error) *manifest { m.errors = append(m.errors, &e) return m } func (m *manifest) WithContext(c context.Context) *manifest { m.ctx = c return m } func (m *manifest) addInputPath(inputPath string) error { abs, err := filepath.Abs(inputPath) if err != nil { return err } // FIXME check to make sure inputPath/abs exists maybe afs := afero.NewReadOnlyFs(afero.NewBasePathFs(afero.NewOsFs(), abs)) return m.addInputFS(afs) } func (m *manifest) addInputFS(f afero.Fs) error { if m.sourceFS == nil { m.sourceFS = make([]afero.Fs, 0) } m.sourceFS = append(m.sourceFS, f) // FIXME do some sort of check on f here? return nil } func New() *manifest { m := &manifest{} return m } func NewFromPaths(options *ManifestScanOptions, inputPaths ...string) (*manifest, error) { log.Dump(inputPaths) m := New() m.scanOptions = options for _, p := range inputPaths { err := m.addInputPath(p) if err != nil { return nil, err } } return m, nil } func NewFromFS(options *ManifestScanOptions, fs afero.Fs) (*manifest, error) { m := New() m.scanOptions = options err := m.addInputFS(fs) if err != nil { return nil, err } return m, nil } func (m *manifest) GetFileCount() int64 { return int64(len(m.files)) } func (m *manifest) GetTotalFileSize() int64 { return m.totalFileSize } func pathIsHidden(p string) bool { tp := path.Clean(p) if strings.HasPrefix(tp, ".") { return true } for { d, f := path.Split(tp) if strings.HasPrefix(f, ".") { return true } if d == "" { return false } tp = d[0 : len(d)-1] // trim trailing slash from dir } } func (m *manifest) addFile(p string, fi fs.FileInfo, sfsIndex int) error { if m.scanOptions.IgnoreDotfiles && pathIsHidden(p) { return nil } if fi != nil && fi.IsDir() { // manifests contain only files, directories are implied. return nil } // FIXME test if 'fi' is already result of stat fileinfo, staterr := m.sourceFS[sfsIndex].Stat(p) if staterr != nil { return staterr } cleanPath := p if cleanPath[0:1] == "/" { cleanPath = cleanPath[1:] } nf := &manifestFile{ path: cleanPath, info: fileinfo, } m.files = append(m.files, nf) m.totalFileSize = m.totalFileSize + fi.Size() return nil } func (m *manifest) Scan() error { // FIXME scan and whatever function does the hashing should take ctx for idx, sfs := range m.sourceFS { if sfs == nil { return errors.New("invalid source fs") } e := afero.Walk(sfs, "/", func(p string, info fs.FileInfo, err error) error { return m.addFile(p, info, idx) }) if e != nil { return e } } return nil }