diff --git a/archive/archive.go b/archive/archive.go index 5f25896..3ba8d4d 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -64,7 +64,9 @@ func NewArchive(path string, pageCache *pagecache.PageCache, httpReferer string) switch ext { case "zip", "cbz": return NewZip(path) - case "7z", "rar", "tar", "tgz", "gz", "tbz2", "cb7", "cbr", "cbt", "lha": + case "rar": + return NewRar(path) + case "7z", "tar", "tgz", "gz", "tbz2", "cb7", "cbr", "cbt", "lha": return nil, errors.New("Archive type not supported, please unpack it first") } diff --git a/archive/rar.go b/archive/rar.go new file mode 100644 index 0000000..76b9044 --- /dev/null +++ b/archive/rar.go @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013-2021 Utkan Güngördü + * Copyright (c) 2021-2023 Piotr Grabowski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package archive + +import ( + "errors" + "fmt" + "io" + "log" + "path/filepath" + "sort" + + "github.com/fauu/gomicsv/pixbuf" + "github.com/gotk3/gotk3/gdk" + "github.com/nwaples/rardecode/v2" +) + +type Rar struct { + files RarMembers // Sorted by name + reader *rardecode.ReadCloser + name string +} + +type RarMember struct { + Header *rardecode.FileHeader + Offset int // in terms of files +} + +type RarMembers []RarMember + +func (p RarMembers) Len() int { return len(p) } +func (p RarMembers) Less(i, j int) bool { return strcmp(p[i].Header.Name, p[j].Header.Name, true) } +func (p RarMembers) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// NewRar reads supported image filenames from a given rar archive and sorts them +func NewRar(name string) (*Rar, error) { + var err error + + ar := new(Rar) + + ar.name = filepath.Base(name) + ar.files = make([]RarMember, 0, MaxArchiveEntries) + ar.reader, err = rardecode.OpenReader(name) + if err != nil { + return nil, err + } + + offsetAcc := -1 + for { + offsetAcc += 1 + + header, err := ar.reader.Next() + if err != nil { + if err == io.EOF { + break + } + log.Printf("error reading a file inside the rar archive: %v", err) + } + if header.IsDir { + continue + } + if !extensionMatches(header.Name, imageExtensions) { + continue + } + ar.files = append(ar.files, RarMember{ + Header: header, + Offset: offsetAcc, + }) + } + + if len(ar.files) == 0 { + return nil, errors.New(ar.name + ": no supported images in the rar file") + } + + sort.Sort(RarMembers(ar.files)) + + ar.Close() + + return ar, nil +} + +func (ar *Rar) checkbounds(i int) error { + if i < 0 || i >= len(ar.files) { + return ErrBounds + } + return nil +} + +func (ar *Rar) Load(i int, autorotate bool, _nPreload int) (*gdk.Pixbuf, error) { + var err error + if err = ar.checkbounds(i); err != nil { + return nil, err + } + + ar.reader, err = rardecode.OpenReader(ar.name) + if err != nil { + return nil, err + } + + targetOffset := ar.files[i].Offset + offsetAcc := -1 + for { + offsetAcc += 1 + + header, err := ar.reader.Next() + if err != nil { + if err == io.EOF { + break + } + log.Printf("error reading a file inside the rar archive: %v", err) + } + if header.IsDir { + continue + } + if offsetAcc == targetOffset { + defer ar.Close() + return pixbuf.Load(ar.reader, autorotate) + } + } + + return nil, fmt.Errorf(ar.name + ": could not find a file inside the rar archive") +} + +func (ar *Rar) Kind() Kind { + return Packed +} + +func (ar *Rar) ArchiveName() string { + return ar.name +} + +func (ar *Rar) Name(i int) (string, error) { + if err := ar.checkbounds(i); err != nil { + return "", err + } + return ar.files[i].Header.Name, nil +} + +func (ar *Rar) Len() *int { + l := len(ar.files) + return &l +} + +func (ar *Rar) Close() error { + return ar.reader.Close() +} diff --git a/archive/util.go b/archive/util.go index a322015..4022dec 100644 --- a/archive/util.go +++ b/archive/util.go @@ -37,7 +37,7 @@ type Loader interface { Len() int } -var archiveExtensions = []string{".zip", ".cbz"} +var archiveExtensions = []string{".zip", ".cbz", ".rar"} var imageExtensions []string func init() { diff --git a/go.mod b/go.mod index c50ac96..02c33d4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ go 1.20 require ( github.com/flytam/filenamify v1.2.0 github.com/gotk3/gotk3 v0.6.2 + github.com/nwaples/rardecode/v2 v2.0.0-beta.2 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.14.0 ) diff --git a/go.sum b/go.sum index ff8e1cb..e204261 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/gotk3/gotk3 v0.6.1 h1:GJ400a0ecEEWrzjBvzBzH+pB/esEMIGdB9zPSmBdoeo= github.com/gotk3/gotk3 v0.6.1/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= github.com/gotk3/gotk3 v0.6.2 h1:sx/PjaKfKULJPTPq8p2kn2ZbcNFxpOJqi4VLzMbEOO8= github.com/gotk3/gotk3 v0.6.2/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= +github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= +github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= diff --git a/gomicsv.ui b/gomicsv.ui index b4a7a3a..9244b02 100644 --- a/gomicsv.ui +++ b/gomicsv.ui @@ -23,6 +23,7 @@ application/zip application/x-cbz + application/vnd.rar