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/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
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/dbus v0.0.0-20250519011319-e811c41c0bc1
|
||||||
github.com/kovidgoyal/go-parallel v1.1.1
|
github.com/kovidgoyal/go-parallel v1.1.1
|
||||||
github.com/kovidgoyal/go-shm v1.0.0
|
github.com/kovidgoyal/go-shm v1.0.0
|
||||||
github.com/kovidgoyal/imaging v1.8.17
|
github.com/kovidgoyal/imaging v1.8.17
|
||||||
github.com/seancfoley/ipaddress-go v1.7.1
|
github.com/seancfoley/ipaddress-go v1.7.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.10
|
github.com/shirou/gopsutil/v4 v4.25.10
|
||||||
|
github.com/ulikunitz/xz v0.5.15
|
||||||
github.com/zeebo/xxh3 v1.0.2
|
github.com/zeebo/xxh3 v1.0.2
|
||||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||||
golang.org/x/image v0.33.0
|
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 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
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/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 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
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=
|
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/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 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
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/"):
|
case strings.HasPrefix(mt, "video/"):
|
||||||
return NewFFMpegPreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
return NewFFMpegPreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||||
|
|
||||||
|
case IsSupportedArchiveFile(abspath):
|
||||||
|
return NewArchivePeview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||||
|
|
||||||
case IsSupportedByCalibre(abspath):
|
case IsSupportedByCalibre(abspath):
|
||||||
return NewCalibrePreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
return NewCalibrePreview(abspath, s, pm.settings, pm.WakeupMainThread)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue