Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for reading gzip, bzip2 and xz files #363

Merged
merged 3 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/fatih/color v1.16.0
github.com/gdamore/tcell/v2 v2.7.1
github.com/h2non/filetype v1.1.3
github.com/maruel/natural v1.1.0
github.com/mattn/go-isatty v0.0.20
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
Expand All @@ -14,6 +15,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
github.com/ulikunitz/xz v0.5.12
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
golang.org/x/sys v0.20.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down Expand Up @@ -116,6 +118,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
89 changes: 3 additions & 86 deletions tui/actions.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tui

import (
"bufio"
"bytes"
"fmt"
"io"
Expand All @@ -13,13 +12,14 @@ import (
"strings"
"time"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"

"github.com/dundee/gdu/v5/build"
"github.com/dundee/gdu/v5/pkg/analyze"
"github.com/dundee/gdu/v5/pkg/device"
"github.com/dundee/gdu/v5/pkg/fs"
"github.com/dundee/gdu/v5/report"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

const (
Expand Down Expand Up @@ -246,89 +246,6 @@ func (ui *UI) deleteSelected(shouldEmpty bool) {
}()
}

func (ui *UI) showFile() *tview.TextView {
if ui.currentDir == nil {
return nil
}

row, column := ui.table.GetSelection()
selectedFile := ui.table.GetCell(row, column).GetReference().(fs.Item)
if selectedFile.IsDir() {
return nil
}

f, err := os.Open(selectedFile.GetPath())
if err != nil {
ui.showErr("Error opening file", err)
return nil
}

totalLines := 0
scanner := bufio.NewScanner(f)

file := tview.NewTextView()
ui.currentDirLabel.SetText("[::b] --- " +
strings.TrimPrefix(selectedFile.GetPath(), build.RootPathPrefix) +
" ---").SetDynamicColors(true)

readNextPart := func(linesCount int) int {
var err error
readLines := 0
for scanner.Scan() && readLines <= linesCount {
_, err = file.Write(scanner.Bytes())
if err != nil {
ui.showErr("Error reading file", err)
return 0
}
_, err = file.Write([]byte("\n"))
if err != nil {
ui.showErr("Error reading file", err)
return 0
}
readLines++
}
return readLines
}
totalLines += readNextPart(defaultLinesCount)

file.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Rune() == 'q' || event.Key() == tcell.KeyESC {
err = f.Close()
if err != nil {
ui.showErr("Error closing file", err)
return event
}
ui.currentDirLabel.SetText("[::b] --- " +
strings.TrimPrefix(ui.currentDirPath, build.RootPathPrefix) +
" ---").SetDynamicColors(true)
ui.pages.RemovePage("file")
ui.app.SetFocus(ui.table)
return event
}

if event.Rune() == 'j' || event.Rune() == 'G' ||
event.Key() == tcell.KeyDown || event.Key() == tcell.KeyPgDn {
_, _, _, height := file.GetInnerRect()
row, _ := file.GetScrollOffset()
if height+row > totalLines-linesTreshold {
totalLines += readNextPart(defaultLinesCount)
}
}
return event
})

grid := tview.NewGrid().SetRows(1, 1, 0, 1).SetColumns(0)
grid.AddItem(ui.header, 0, 0, 1, 1, 0, 0, false).
AddItem(ui.currentDirLabel, 1, 0, 1, 1, 0, 0, false).
AddItem(file, 2, 0, 1, 1, 0, 0, true).
AddItem(ui.footerLabel, 3, 0, 1, 1, 0, 0, false)

ui.pages.HidePage("background")
ui.pages.AddPage("file", grid, true, true)

return file
}

func (ui *UI) showInfo() {
if ui.currentDir == nil {
return
Expand Down
146 changes: 146 additions & 0 deletions tui/show_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package tui

import (
"bufio"
"compress/bzip2"
"compress/gzip"
"io"
"os"
"strings"

"github.com/gdamore/tcell/v2"
"github.com/h2non/filetype"
"github.com/h2non/filetype/matchers"
"github.com/pkg/errors"
"github.com/rivo/tview"
"github.com/ulikunitz/xz"

"github.com/dundee/gdu/v5/build"
"github.com/dundee/gdu/v5/pkg/fs"
)

func (ui *UI) showFile() *tview.TextView {
if ui.currentDir == nil {
return nil
}

row, column := ui.table.GetSelection()
selectedFile := ui.table.GetCell(row, column).GetReference().(fs.Item)
if selectedFile.IsDir() {
return nil
}

path := selectedFile.GetPath()
f, err := os.Open(path)
if err != nil {
ui.showErr("Error opening file", err)
return nil
}
scanner, err := getScanner(f)
if err != nil {
ui.showErr("Error reading file", err)
return nil

Check warning on line 42 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L41-L42

Added lines #L41 - L42 were not covered by tests
}

totalLines := 0

file := tview.NewTextView()
ui.currentDirLabel.SetText("[::b] --- " +
strings.TrimPrefix(path, build.RootPathPrefix) +
" ---").SetDynamicColors(true)

readNextPart := func(linesCount int) int {
var err error
readLines := 0
for scanner.Scan() && readLines <= linesCount {
_, err = file.Write(scanner.Bytes())
if err != nil {
ui.showErr("Error reading file", err)
return 0

Check warning on line 59 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L58-L59

Added lines #L58 - L59 were not covered by tests
}
_, err = file.Write([]byte("\n"))
if err != nil {
ui.showErr("Error reading file", err)
return 0

Check warning on line 64 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L63-L64

Added lines #L63 - L64 were not covered by tests
}
readLines++
}
return readLines
}
totalLines += readNextPart(defaultLinesCount)

file.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Rune() == 'q' || event.Key() == tcell.KeyESC {
err = f.Close()
if err != nil {
ui.showErr("Error closing file", err)
return event

Check warning on line 77 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L76-L77

Added lines #L76 - L77 were not covered by tests
}
ui.currentDirLabel.SetText("[::b] --- " +
strings.TrimPrefix(ui.currentDirPath, build.RootPathPrefix) +
" ---").SetDynamicColors(true)
ui.pages.RemovePage("file")
ui.app.SetFocus(ui.table)
return event
}

if event.Rune() == 'j' || event.Rune() == 'G' ||
event.Key() == tcell.KeyDown || event.Key() == tcell.KeyPgDn {
_, _, _, height := file.GetInnerRect()
row, _ := file.GetScrollOffset()
if height+row > totalLines-linesTreshold {
totalLines += readNextPart(defaultLinesCount)
}
}
return event
})

grid := tview.NewGrid().SetRows(1, 1, 0, 1).SetColumns(0)
grid.AddItem(ui.header, 0, 0, 1, 1, 0, 0, false).
AddItem(ui.currentDirLabel, 1, 0, 1, 1, 0, 0, false).
AddItem(file, 2, 0, 1, 1, 0, 0, true).
AddItem(ui.footerLabel, 3, 0, 1, 1, 0, 0, false)

ui.pages.HidePage("background")
ui.pages.AddPage("file", grid, true, true)

return file
}

func getScanner(f io.ReadSeeker) (scanner *bufio.Scanner, err error) {
// We only have to pass the file header = first 261 bytes
head := make([]byte, 261)
if _, err = f.Read(head); err != nil {
return nil, errors.Wrap(err, "error reading file header")
}

if pos, err := f.Seek(0, 0); pos != 0 || err != nil {
return nil, errors.Wrap(err, "error seeking file")

Check warning on line 118 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L118

Added line #L118 was not covered by tests
}
scanner = bufio.NewScanner(f)

typ, err := filetype.Match(head)
if err != nil {
return nil, errors.Wrap(err, "error matching file type")

Check warning on line 124 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L124

Added line #L124 was not covered by tests
}

switch typ.MIME.Value {
case matchers.TypeGz.MIME.Value:
r, err := gzip.NewReader(f)
if err != nil {
return nil, errors.Wrap(err, "error creating gzip reader")

Check warning on line 131 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L131

Added line #L131 was not covered by tests
}
scanner = bufio.NewScanner(r)
case matchers.TypeBz2.MIME.Value:
r := bzip2.NewReader(f)
scanner = bufio.NewScanner(r)
case matchers.TypeXz.MIME.Value:
r, err := xz.NewReader(f)
if err != nil {
return nil, errors.Wrap(err, "error creating xz reader")

Check warning on line 140 in tui/show_file.go

View check run for this annotation

Codecov / codecov/patch

tui/show_file.go#L140

Added line #L140 was not covered by tests
}
scanner = bufio.NewScanner(r)
}

return scanner, nil
}
89 changes: 89 additions & 0 deletions tui/show_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package tui

import (
"bytes"
"compress/gzip"
"testing"

"github.com/stretchr/testify/assert"
"github.com/ulikunitz/xz"
)

func TestGetScannerForEmptyString(t *testing.T) {
r := bytes.NewReader([]byte{})
_, err := getScanner(r)
assert.ErrorContains(t, err, "EOF")
}

func TestGetScannerForPlainString(t *testing.T) {
r := bytes.NewReader([]byte("hello"))
s, err := getScanner(r)
assert.Nil(t, err)

assert.Equal(t, true, s.Scan())
assert.Equal(t, "hello", s.Text())
assert.Equal(t, nil, s.Err())
}

func TestGetScannerForGzipped(t *testing.T) {
b := bytes.NewBuffer([]byte{})
w := gzip.NewWriter(b)

_, err := w.Write([]byte("hello world"))
assert.Nil(t, err)

err = w.Close()
assert.Nil(t, err)

r := bytes.NewReader(b.Bytes())
s, err := getScanner(r)
assert.Nil(t, err)

assert.Equal(t, true, s.Scan())
assert.Equal(t, "hello world", s.Text())
assert.Equal(t, nil, s.Err())
}

func TestGetScannerForBzipped(t *testing.T) {
r := bytes.NewReader([]byte{
// bzip2 header
0x42, 0x5A, 0x68, 0x39,
// bzip2 compressed data: "hello"
0x31, 0x41, 0x59, 0x26,
0x53, 0x59, 0xC1, 0xC0,
0x80, 0xE2, 0x00, 0x00,
0x01, 0x41, 0x00, 0x00,
0x10, 0x02, 0x44, 0xA0,
0x00, 0x30, 0xCD, 0x00,
0xC3, 0x46, 0x29, 0x97,
0x17, 0x72, 0x45, 0x38,
0x50, 0x90, 0xC1, 0xC0,
0x80, 0xE2,
})
s, err := getScanner(r)
assert.Nil(t, err)

assert.Equal(t, true, s.Scan())
assert.Equal(t, "hello", s.Text())
assert.Equal(t, nil, s.Err())
}

func TestGetScannerForXzipped(t *testing.T) {
b := bytes.NewBuffer([]byte{})
w, err := xz.NewWriter(b)
assert.Nil(t, err)

_, err = w.Write([]byte("hello world"))
assert.Nil(t, err)

err = w.Close()
assert.Nil(t, err)

r := bytes.NewReader(b.Bytes())
s, err := getScanner(r)
assert.Nil(t, err)

assert.Equal(t, true, s.Scan())
assert.Equal(t, "hello world", s.Text())
assert.Equal(t, nil, s.Err())
}
Loading