Skip to content

Commit dc929cd

Browse files
authored
Merge pull request #28 from Unpackerr/dn2_paths
Make preserve-paths use output dir.
2 parents 59f219b + 94c4319 commit dc929cd

File tree

6 files changed

+70
-44
lines changed

6 files changed

+70
-44
lines changed

MANUAL.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ DESCRIPTION
1212

1313
* This application recursively extracts compressed archives.
1414
* Provide directories to search or provide files to extract.
15-
* Supports ZIP, RAR, GZIP, BZIP2, TAR, TGZ, TBZ2, 7ZIP, ISO9660
16-
* ie. *.zip *.rar *.r00 *.gz *.bz2 *.tar *.tgz *.tbz2 *.7z *.iso
15+
* Supports: ZIP, RAR, GZIP, BZIP2, TAR, TGZ, TBZ2, 7ZIP, ISO9660
16+
* Supports: Z, AR, BR, CPIO, DEB, LZ/4, LZIP, LZMA2, S2, SNAPPY
17+
* Supports: RPM, SZ, TLZ, TXZ, ZLIB, ZSTD, BROTLI, ZZ
18+
* ie: *.zip *.rar *.r00 *.gz *.bz2 *.tar *.tgz *.tbz2 *.7z *.iso (and others)
1719

1820
OPTIONS
1921
---
@@ -58,6 +60,9 @@ OPTIONS
5860
This option determines if the archives will be extracted to their
5961
parent folder. Using this flag will override the --output option.
6062

63+
-V, --verbose
64+
Verbose logging prints the extracted file paths.
65+
6166
-D, --debug
6267
Enable debug output.
6368

@@ -83,6 +88,8 @@ Example TOML job file:
8388
min_depth = 1
8489
file_mode = 644
8590
dir_mode = 755
91+
verbose = false
92+
debug = false
8693
preserve_paths = false
8794

8895
AUTHOR

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ toolchain go1.24.2
77
require (
88
github.com/spf13/pflag v1.0.6
99
golift.io/version v0.0.2
10-
golift.io/xtractr v0.2.3-0.20250419070747-b391d40d7453
10+
golift.io/xtractr v0.2.3-0.20250419170021-53bfe05970fe
1111
)
1212

1313
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ golift.io/cnfgfile v0.0.0-20240713024420-a5436d84eb48 h1:c7cJWRr0cUnFHKtq072esKz
255255
golift.io/cnfgfile v0.0.0-20240713024420-a5436d84eb48/go.mod h1:zHm9o8SkZ6Mm5DfGahsrEJPsogyR0qItP59s5lJ98/I=
256256
golift.io/version v0.0.2 h1:i0gXRuSDHKs4O0sVDUg4+vNIuOxYoXhaxspftu2FRTE=
257257
golift.io/version v0.0.2/go.mod h1:76aHNz8/Pm7CbuxIsDi97jABL5Zui3f2uZxDm4vB6hU=
258-
golift.io/xtractr v0.2.3-0.20250419070747-b391d40d7453 h1:JnozjdGe8GNAgcLDxd6WjWxVpktsmqJzP2PMT+DulhE=
259-
golift.io/xtractr v0.2.3-0.20250419070747-b391d40d7453/go.mod h1:invEOYfyBnFtegY2V2n+9K5bUEHB8pGZng1BK0U2r38=
258+
golift.io/xtractr v0.2.3-0.20250419170021-53bfe05970fe h1:fCqAf/BYLNy5BdX6IeeGIGutHqOANjIfKeYANd5Cktg=
259+
golift.io/xtractr v0.2.3-0.20250419170021-53bfe05970fe/go.mod h1:invEOYfyBnFtegY2V2n+9K5bUEHB8pGZng1BK0U2r38=
260260
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
261261
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
262262
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func parseFlags(pwd string) (*xt.Job, *flags) {
3232
flag.StringSliceVarP(&job.Passwords, "password", "P", nil, "Attempt these passwords for rar and 7zip archives.")
3333
flag.BoolVarP(&job.SquashRoot, "squash-root", "S", false,
3434
"If archive contains only 1 folder at in the root, its contents are moved into output folder.")
35+
flag.BoolVarP(&job.Verbose, "verbose", "V", false, "Verbose output prints the file list that was extracted.")
3536
flag.BoolVarP(&job.DebugLog, "debug", "D", false, "Enable debug output.")
3637
flag.StringSliceVarP(&flags.JobFiles, "job-file", "j", nil, "Read additional extraction jobs from these files.")
3738
flag.BoolVarP(&job.Preserve, "preserve-paths", "p", false, "Recreate directory hierarchy while extracting.")

pkg/xt/job.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Job struct {
2121
SquashRoot bool `json:"squashRoot" toml:"squash_root" xml:"squash_root" yaml:"squashRoot"`
2222
DebugLog bool `json:"debugLog" toml:"debug_log" xml:"debug_log" yaml:"debugLog"`
2323
Preserve bool `json:"preservePaths" toml:"preserve_paths" xml:"preserve_paths" yaml:"preservePaths"`
24+
Verbose bool `json:"verbose" toml:"verbose" xml:"verbose" yaml:"verbose"`
2425
}
2526

2627
// ParseJobs checks for and reads more jobs in from 0 or more job files.
@@ -48,8 +49,8 @@ func (j *Job) String() string {
4849
sSfx = "s"
4950
}
5051

51-
return fmt.Sprintf("%d path%s, f/d-mode:%s/%s, min/max-depth: %d/%d output: %s",
52-
len(j.Paths), sSfx, j.FileMode, j.DirMode, j.MinDepth, j.MaxDepth, j.Output)
52+
return fmt.Sprintf("%d path%s, f/d-mode:%s/%s, min/max-depth: %d/%d, preserve/squash: %v/%v output: %s",
53+
len(j.Paths), sSfx, j.FileMode, j.DirMode, j.MinDepth, j.MaxDepth, j.Preserve, j.SquashRoot, j.Output)
5354
}
5455

5556
// Debugf prints a log message if debug is enabled.

pkg/xt/xt.go

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package xt
33

44
import (
5+
"fmt"
56
"log"
67
"os"
78
"path/filepath"
@@ -20,54 +21,69 @@ func Extract(job *Job) {
2021

2122
job.fixModes()
2223

23-
total := 0
24+
total := archives.Count()
2425
count := 0
26+
size := int64(0)
27+
fCount := 0
28+
start := time.Now()
2529

26-
for _, files := range archives {
27-
total += len(files)
28-
}
29-
30-
for _, files := range archives {
31-
for _, fileName := range files {
30+
for folder, files := range archives {
31+
for _, archive := range files {
3232
count++
33-
log.Printf("==> Extracting Archive (%d/%d): %s", count, total, fileName)
34-
35-
file := &xtractr.XFile{
36-
FilePath: fileName, // Path to archive being extracted.
37-
OutputDir: job.Output, // Folder to extract archive into.
38-
FileMode: job.FileMode.Mode(), // Write files with this mode.
39-
DirMode: job.DirMode.Mode(), // Write folders with this mode.
40-
Passwords: job.Passwords, // (RAR/7zip) Archive password(s).
41-
SquashRoot: job.SquashRoot, // Remove single root folder?
42-
}
43-
44-
file.SetLogger(job)
45-
46-
start := time.Now()
33+
log.Printf("==> Extracting Archive (%d/%d): %s", count, total, archive)
4734

48-
// If preserving the file hierarchy, set the output directory to the
49-
// folder of the archive being extracted.
50-
if job.Preserve {
51-
file.OutputDir = filepath.Dir(fileName)
52-
}
53-
54-
size, files, _, err := xtractr.ExtractFile(file)
35+
output, fSize, files, duration, err := job.processArchive(folder, archive)
5536
if err != nil {
56-
log.Printf("[ERROR] Archive: %s: %v", fileName, err)
57-
continue
37+
log.Printf("[ERROR] Extracting: %v", err)
38+
} else {
39+
log.Printf("==> Extracted Archive %s to %s in %v: bytes: %d, files: %d",
40+
archive, output, duration.Round(time.Millisecond), fSize, len(files))
5841
}
5942

60-
log.Printf("==> Extracted Archive %s in %v: bytes: %d, files: %d",
61-
fileName, time.Since(start).Round(time.Millisecond), size, len(files))
62-
63-
if len(files) > 0 {
43+
if len(files) > 0 && job.Verbose {
6444
log.Printf("==> Files:\n - %s", strings.Join(files, "\n - "))
6545
}
46+
47+
size += fSize
48+
fCount += len(files)
6649
}
6750
}
51+
52+
log.Printf("==> Done.")
53+
log.Printf("==> Extracted %d archives; wrote %d bytes into %d files in %v",
54+
total, size, fCount, time.Since(start).Round(time.Millisecond))
55+
}
56+
57+
func (j *Job) processArchive(folder, archive string) (string, int64, []string, time.Duration, error) {
58+
file := &xtractr.XFile{
59+
FilePath: archive, // Path to archive being extracted.
60+
OutputDir: j.Output, // Folder to extract archive into.
61+
FileMode: j.FileMode.Mode(), // Write files with this mode.
62+
DirMode: j.DirMode.Mode(), // Write folders with this mode.
63+
Passwords: j.Passwords, // (RAR/7zip) Archive password(s).
64+
SquashRoot: j.SquashRoot, // Remove single root folder?
65+
}
66+
file.SetLogger(j)
67+
68+
// If preserving the file hierarchy: set the output directory to the same path as the input file.
69+
if j.Preserve {
70+
// Remove input path prefix from fileName,
71+
// append fileName.Dir to job.Output,
72+
// extract file into job.Output/file(sub)Folder(s).
73+
file.OutputDir = filepath.Join(j.Output, filepath.Dir(strings.TrimPrefix(archive, folder)))
74+
}
75+
76+
start := time.Now()
77+
78+
size, files, _, err := xtractr.ExtractFile(file)
79+
if err != nil {
80+
err = fmt.Errorf("archive: %s: %w", archive, err)
81+
}
82+
83+
return file.OutputDir, size, files, time.Since(start), err
6884
}
6985

70-
func (j *Job) getArchives() map[string][]string {
86+
func (j *Job) getArchives() xtractr.ArchiveList {
7187
archives := map[string][]string{}
7288

7389
for _, fileName := range j.Paths {
@@ -87,13 +103,14 @@ func (j *Job) getArchives() map[string][]string {
87103
exclude = xtractr.AllExcept(j.Include...)
88104
}
89105

90-
for folder, fileList := range xtractr.FindCompressedFiles(xtractr.Filter{
106+
for _, fileList := range xtractr.FindCompressedFiles(xtractr.Filter{
91107
Path: fileName,
92108
ExcludeSuffix: exclude,
93109
MaxDepth: int(j.MaxDepth),
94110
MinDepth: int(j.MinDepth),
95111
}) {
96-
archives[folder] = fileList
112+
// Group archive lists by the parent search folder that found them.
113+
archives[fileName] = append(archives[fileName], fileList...)
97114
}
98115
}
99116

0 commit comments

Comments
 (0)