From ab4e3f3c5916d58064003228ca25db58c6210b68 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Tue, 30 Jan 2024 17:41:58 -0800 Subject: [PATCH] add file modes --- .golangci.yml | 20 ++++++++++++++++++-- MANUAL.md | 2 ++ main.go | 10 +++++----- pkg/xt/filemode.go | 40 ++++++++++++++++++++++++++++++++++++++++ pkg/xt/job.go | 40 ++++++++++++++++++++++++++++++++++++---- pkg/xt/xt.go | 16 +++++++++------- 6 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 pkg/xt/filemode.go diff --git a/.golangci.yml b/.golangci.yml index 7db28d3..12324ba 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,9 @@ +run: + timeout: 3m + +output: + sort-results: true + linters: enable-all: true disable: @@ -16,6 +22,16 @@ linters: - nlreturn - exhaustruct - depguard -run: - timeout: 3m + - tagalign +issues: + # disable the default limit so we see everything + max-same-issues: 0 + max-issues-per-linter: 0 + exclude-rules: + # Exclude some linters from testing files. + - linters: + - goconst + - wsl + - funlen + path: '.+_test.go' \ No newline at end of file diff --git a/MANUAL.md b/MANUAL.md index d5a0c5c..f2df3a2 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -59,6 +59,8 @@ Example TOML job file: exclude_suffix = ['.iso', '.gz'] max_depth = 0 min_depth = 1 + file_mode = 644 + dir_mode = 755 AUTHOR --- diff --git a/main.go b/main.go index c3bdab3..ecaa593 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( "golift.io/version" ) -func parseFlags(pwd string) (xt.Job, *flags) { +func parseFlags(pwd string) (*xt.Job, *flags) { flag.Usage = func() { // XXX: Write more "help" info here. fmt.Println("If you pass a directory, this app will extract every archive in it.") @@ -19,7 +19,7 @@ func parseFlags(pwd string) (xt.Job, *flags) { flag.PrintDefaults() os.Exit(0) } - job := xt.Job{} + job := &xt.Job{} flags := &flags{} flag.BoolVarP(&flags.PrintVer, "version", "v", false, "Print application version and exit") @@ -27,7 +27,7 @@ func parseFlags(pwd string) (xt.Job, *flags) { flag.StringVarP(&job.Output, "output", "o", pwd, "Output directory, default is current directory") flag.UintVarP(&job.MaxDepth, "max-depth", "d", 0, "Maximum folder depth to recursively search for archives.") flag.UintVarP(&job.MinDepth, "min-depth", "m", 0, "Minimum folder depth to recursively search for archives.") - //flag.UintVarP(&job.Recurse, "recurse", "r", 0, "Extract archives inside archives, up to this depth.") + // flag.UintVarP(&job.Recurse, "recurse", "r", 0, "Extract archives inside archives, up to this depth.") flag.StringSliceVarP(&job.Passwords, "password", "P", nil, "Attempt these passwords for rar and 7zip archives.") flag.StringSliceVarP(&flags.JobFiles, "job-file", "j", nil, "Read additional extraction jobs from these files.") // Preserve paths? @@ -77,7 +77,7 @@ func main() { // Extract the jobs. for i, job := range jobs { - log.Printf("Starting Job %d of %d with %d paths, output: %s", i+1, len(jobs), len(job.Paths), job.Output) - xt.Extract(&job) + log.Printf("Starting Job %d of %d with %s", i+1, len(jobs), job) + xt.Extract(job) } } diff --git a/pkg/xt/filemode.go b/pkg/xt/filemode.go new file mode 100644 index 0000000..78ee585 --- /dev/null +++ b/pkg/xt/filemode.go @@ -0,0 +1,40 @@ +package xt + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// FileMode is used to unmarshal a unix file mode from the config file. +type FileMode os.FileMode + +// UnmarshalText turns a unix file mode, wrapped in quotes or not, into a usable os.FileMode. +func (f *FileMode) UnmarshalText(text []byte) error { + str := strings.TrimSpace(strings.Trim(string(text), `"'`)) + + fm, err := strconv.ParseUint(str, 8, 32) + if err != nil { + return fmt.Errorf("file_mode (%s) is invalid: %w", str, err) + } + + *f = FileMode(os.FileMode(fm)) + + return nil +} + +// MarshalText satisfies an encoder.TextMarshaler interface. +func (f FileMode) MarshalText() ([]byte, error) { + return []byte(f.String()), nil +} + +// String creates a unix-octal version of a file mode. +func (f FileMode) String() string { + return fmt.Sprintf("%04o", f) +} + +// Mode returns the compatible os.FileMode. +func (f FileMode) Mode() os.FileMode { + return os.FileMode(f) +} diff --git a/pkg/xt/job.go b/pkg/xt/job.go index 5fe3cc9..2127142 100644 --- a/pkg/xt/job.go +++ b/pkg/xt/job.go @@ -1,6 +1,8 @@ package xt import ( + "fmt" + "golift.io/cnfgfile" ) @@ -12,20 +14,50 @@ type Job struct { Exclude []string `json:"excludeSuffix" yaml:"excludeSuffix" xml:"exclude_suffix" toml:"exclude_suffix"` MaxDepth uint `json:"maxDepth" yaml:"maxDepth" xml:"max_depth" toml:"max_depth"` MinDepth uint `json:"minDepth" yaml:"minDepth" xml:"min_depth" toml:"min_depth"` + DirMode FileMode `json:"dirMode" yaml:"dirMode" xml:"dir_mode" toml:"dir_mode"` + FileMode FileMode `json:"fileMode" yaml:"fileMode" xml:"file_mode" toml:"file_mode"` } // ParseJobs checks for and reads more jobs in from 0 or more job files. -func ParseJobs(jobFiles []string) ([]Job, error) { - jobs := make([]Job, len(jobFiles)) +func ParseJobs(jobFiles []string) ([]*Job, error) { + jobs := make([]*Job, len(jobFiles)) for idx, jobFile := range jobFiles { + jobs[idx] = &Job{} // This library simply parses xml, json, toml, and yaml into a data struct. // It parses based on file name extension, toml is default. Supports compression. - err := cnfgfile.Unmarshal(&jobs[idx], jobFile) + err := cnfgfile.Unmarshal(jobs[idx], jobFile) if err != nil { - return nil, err + return nil, fmt.Errorf("bad job file: %w", err) } } return jobs, nil } + +func (j *Job) fixModes() { + const ( + defaultFileMode = 0o644 + defaultDirMode = 0o755 + ) + + if j.DirMode == 0 { + j.DirMode = defaultDirMode + } + + if j.FileMode == 0 { + j.FileMode = defaultFileMode + } +} + +func (j *Job) String() string { + j.fixModes() + + sSfx := "" + if len(j.Paths) > 1 { + sSfx = "s" + } + + return fmt.Sprintf("%d path%s, f/d-mode:%s/%s, min/max-depth: %d/%d output: %s", + len(j.Paths), sSfx, j.FileMode, j.DirMode, j.MinDepth, j.MaxDepth, j.Output) +} diff --git a/pkg/xt/xt.go b/pkg/xt/xt.go index 069c6d7..24e8b6f 100644 --- a/pkg/xt/xt.go +++ b/pkg/xt/xt.go @@ -15,6 +15,8 @@ func Extract(job *Job) { log.Println("==> No archives found in:", job.Paths) } + job.fixModes() + total := 0 count := 0 @@ -30,11 +32,11 @@ func Extract(job *Job) { start := time.Now() size, files, _, err := xtractr.ExtractFile(&xtractr.XFile{ - FilePath: fileName, // Path to archive being extracted. - OutputDir: job.Output, // Folder to extract archive into. - FileMode: 0o644, //nolint:gomnd // Write files with this mode. - DirMode: 0o755, //nolint:gomnd // Write folders with this mode. - Passwords: job.Passwords, // (RAR/7zip) Archive password(s). + FilePath: fileName, // Path to archive being extracted. + OutputDir: job.Output, // Folder to extract archive into. + FileMode: job.FileMode.Mode(), // Write files with this mode. + DirMode: job.DirMode.Mode(), // Write folders with this mode. + Passwords: job.Passwords, // (RAR/7zip) Archive password(s). }) if err != nil { log.Printf("[ERROR] Archive: %s: %v", fileName, err) @@ -63,13 +65,13 @@ func (j *Job) getArchives() map[string][]string { continue } - for k, v := range xtractr.FindCompressedFiles(xtractr.Filter{ + for folder, fileList := range xtractr.FindCompressedFiles(xtractr.Filter{ Path: fileName, ExcludeSuffix: j.Exclude, MaxDepth: int(j.MaxDepth), MinDepth: int(j.MinDepth), }) { - archives[k] = v + archives[folder] = fileList } }