package main import ( "fmt" "io" "io/fs" "os" "path/filepath" "strings" "g.activething.com/go/DateKeys/capsule" ) // collect returns the files named by the -in arguments: a file by its name, // and a folder by its name followed by the path of each file below it, as a // browser names the files of a folder (webkitRelativePath). Folders are // walked with Lstat, following no link, and only regular files are // accepted. The files that systems create on their own are left out of // folders, and listed in skipped (spec §62.1 rule 15). With mtime, each // file keeps its modification time (rule 16). func collect(ins []string, mtime bool) (sources []capsule.Source, skipped []string, err error) { for _, in := range ins { abs, err := filepath.Abs(in) if err != nil { return nil, nil, err } base := filepath.Base(abs) info, err := os.Lstat(abs) if err != nil { return nil, nil, err } switch { case info.Mode().IsRegular(): sources = append(sources, fileSource(abs, base, info, mtime)) continue case !info.IsDir(): return nil, nil, notRegular(in, info) } err = filepath.WalkDir(abs, func(p string, d fs.DirEntry, err error) error { if err != nil { return err } if p == abs { return nil } if systemFile(d.Name(), d.IsDir()) { skipped = append(skipped, p) if d.IsDir() { return filepath.SkipDir } return nil } if d.IsDir() { return nil } info, err := d.Info() if err != nil { return err } if !info.Mode().IsRegular() { return notRegular(p, info) } rel, err := filepath.Rel(abs, p) if err != nil { return err } sources = append(sources, fileSource(p, base+"/"+filepath.ToSlash(rel), info, mtime)) return nil }) if err != nil { return nil, nil, err } } return sources, skipped, nil } func notRegular(path string, info fs.FileInfo) error { return fmt.Errorf("%s is not a regular file (%s): only regular files are encrypted, and links are not followed", path, info.Mode().Type()) } // fileSource is the Source of the local file at path, stored as name. func fileSource(path, name string, info fs.FileInfo, mtime bool) capsule.Source { s := capsule.Source{Path: name, Size: info.Size(), Open: func() (io.ReadCloser, error) { return os.Open(path) }} if mtime { s.ModTime = info.ModTime() } return s } // systemFile reports the files and folders that systems create on their // own, which the official SDK leaves out by default: .DS_Store, Thumbs.db, // desktop.ini, ._* and __MACOSX (spec §62.1 rule 15). func systemFile(name string, dir bool) bool { if dir { return name == "__MACOSX" } return strings.EqualFold(name, ".DS_Store") || strings.EqualFold(name, "Thumbs.db") || strings.EqualFold(name, "desktop.ini") || strings.HasPrefix(name, "._") }