Choose files: Preview for common archive types
This commit is contained in:
parent
158ebee92c
commit
22f0292e35
4 changed files with 162 additions and 0 deletions
2
go.mod
2
go.mod
|
|
@ -12,12 +12,14 @@ require (
|
|||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/klauspost/compress v1.18.1
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1
|
||||
github.com/kovidgoyal/go-parallel v1.1.1
|
||||
github.com/kovidgoyal/go-shm v1.0.0
|
||||
github.com/kovidgoyal/imaging v1.8.17
|
||||
github.com/seancfoley/ipaddress-go v1.7.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.10
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.33.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -26,6 +26,8 @@ github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1p
|
|||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 h1:rMY/hWfcVzBm6BLX6YLA+gLJEpuXBed/VP6YEkXt8R4=
|
||||
|
|
@ -56,6 +58,8 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O
|
|||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
|
|
|
|||
153
kittens/choose_files/archive.go
Normal file
153
kittens/choose_files/archive.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package choose_files
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/bzip2"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/ulikunitz/xz"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func IsSupportedArchiveFile(abspath string) bool {
|
||||
name := strings.ToLower(filepath.Base(abspath))
|
||||
ext := filepath.Ext(name)
|
||||
switch ext {
|
||||
case ".zip", ".tgz", ".tbz2", ".tzst", ".txz":
|
||||
return true
|
||||
case ".gz", ".bz2", ".zst", ".xz":
|
||||
name = name[:len(name)-len(ext)]
|
||||
ext = filepath.Ext(name)
|
||||
return ext == ".tar"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type archive_preview struct {
|
||||
path string
|
||||
metadata fs.FileInfo
|
||||
WakeupMainThread func() bool
|
||||
ch chan *MessagePreview
|
||||
mp *MessagePreview
|
||||
}
|
||||
|
||||
func displayFilename(s string) string {
|
||||
if isPrintableUTF8(s) {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("%X", utils.UnsafeStringToBytes(s))
|
||||
}
|
||||
|
||||
func isPrintableUTF8(s string) bool {
|
||||
for _, r := range s {
|
||||
if r == '\uFFFD' { // replacement char
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *archive_preview) render() {
|
||||
name := strings.ToLower(filepath.Base(p.path))
|
||||
ext := filepath.Ext(name)
|
||||
names := []string{""}
|
||||
populate_tar := func(r io.Reader) {
|
||||
tr := tar.NewReader(r)
|
||||
for len(names) < 500 {
|
||||
hdr, err := tr.Next()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
names = append(names, displayFilename(hdr.Name))
|
||||
}
|
||||
}
|
||||
switch ext {
|
||||
case ".zip":
|
||||
r, err := zip.OpenReader(p.path)
|
||||
if err == nil || errors.Is(err, zip.ErrInsecurePath) {
|
||||
defer r.Close()
|
||||
for _, f := range r.File {
|
||||
if f.NonUTF8 {
|
||||
names = append(names, displayFilename(f.Name))
|
||||
} else {
|
||||
names = append(names, f.Name)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
case ".gz", ".tgz":
|
||||
if f, err := os.Open(p.path); err == nil {
|
||||
defer f.Close()
|
||||
if gz, err := gzip.NewReader(f); err == nil {
|
||||
defer gz.Close()
|
||||
populate_tar(gz)
|
||||
}
|
||||
}
|
||||
case ".xz", ".txz":
|
||||
if f, err := os.Open(p.path); err == nil {
|
||||
defer f.Close()
|
||||
if gz, err := xz.NewReader(f); err == nil {
|
||||
populate_tar(gz)
|
||||
}
|
||||
}
|
||||
case ".bz2", ".tbz2":
|
||||
if f, err := os.Open(p.path); err == nil {
|
||||
defer f.Close()
|
||||
populate_tar(bzip2.NewReader(f))
|
||||
}
|
||||
case ".zst", ".tzst":
|
||||
if f, err := os.Open(p.path); err == nil {
|
||||
defer f.Close()
|
||||
if gz, err := zstd.NewReader(f); err == nil {
|
||||
defer gz.Close()
|
||||
populate_tar(gz)
|
||||
}
|
||||
}
|
||||
}
|
||||
mp := *p.mp
|
||||
mp.trailers = append(mp.trailers, names...)
|
||||
p.ch <- &mp
|
||||
p.WakeupMainThread()
|
||||
}
|
||||
|
||||
func (p *archive_preview) IsReady() bool { return true }
|
||||
func (p *archive_preview) Unload() {}
|
||||
func (p *archive_preview) IsValidForColorScheme(light bool) bool { return true }
|
||||
func (p *archive_preview) String() string { return fmt.Sprintf("ArchivePreview{%s}", p.path) }
|
||||
|
||||
func (p *archive_preview) Render(h *Handler, x, y, width, height int) {
|
||||
if p.ch != nil {
|
||||
select {
|
||||
case mp := <-p.ch:
|
||||
p.mp = mp
|
||||
close(p.ch)
|
||||
p.ch = nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
p.mp.Render(h, x, y, width, height)
|
||||
}
|
||||
|
||||
func NewArchivePeview(
|
||||
abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool,
|
||||
) Preview {
|
||||
mp := NewFileMetadataPreview(abspath, metadata)
|
||||
ans := &archive_preview{
|
||||
path: abspath, metadata: metadata, WakeupMainThread: WakeupMainThread, ch: make(chan *MessagePreview, 1), mp: mp,
|
||||
}
|
||||
go ans.render()
|
||||
return ans
|
||||
}
|
||||
|
|
@ -354,6 +354,9 @@ func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Pr
|
|||
case strings.HasPrefix(mt, "video/"):
|
||||
return NewFFMpegPreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||
|
||||
case IsSupportedArchiveFile(abspath):
|
||||
return NewArchivePeview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||
|
||||
case IsSupportedByCalibre(abspath):
|
||||
return NewCalibrePreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue