diff --git a/go.mod b/go.mod index 7a232908d..22c1d55ab 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/go.sum b/go.sum index ec8eaf9ac..dedf57d81 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/tui/show_file.go b/tui/show_file.go index e5f15949d..ed2f97c10 100644 --- a/tui/show_file.go +++ b/tui/show_file.go @@ -2,11 +2,18 @@ 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" @@ -23,18 +30,23 @@ func (ui *UI) showFile() *tview.TextView { return nil } - f, err := os.Open(selectedFile.GetPath()) + 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 + } totalLines := 0 - scanner := bufio.NewScanner(f) file := tview.NewTextView() ui.currentDirLabel.SetText("[::b] --- " + - strings.TrimPrefix(selectedFile.GetPath(), build.RootPathPrefix) + + strings.TrimPrefix(path, build.RootPathPrefix) + " ---").SetDynamicColors(true) readNextPart := func(linesCount int) int { @@ -94,3 +106,41 @@ func (ui *UI) showFile() *tview.TextView { 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") + } + scanner = bufio.NewScanner(f) + + typ, err := filetype.Match(head) + if err != nil { + return nil, errors.Wrap(err, "error matching file type") + } + + 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") + } + 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") + } + scanner = bufio.NewScanner(r) + } + + return scanner, nil +} diff --git a/tui/show_file_test.go b/tui/show_file_test.go new file mode 100644 index 000000000..2958202ea --- /dev/null +++ b/tui/show_file_test.go @@ -0,0 +1,83 @@ +package tui + +import ( + "bytes" + "compress/gzip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ulikunitz/xz" +) + +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()) +}