2023-10-16 09:18:40 +00:00
|
|
|
package vfs
|
2021-11-29 10:07:54 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"io"
|
2023-12-21 23:15:39 +00:00
|
|
|
"io/fs"
|
2021-11-29 10:07:54 +00:00
|
|
|
"os"
|
2023-12-21 23:15:39 +00:00
|
|
|
"path"
|
2021-11-29 10:07:54 +00:00
|
|
|
"path/filepath"
|
2023-12-21 23:15:39 +00:00
|
|
|
"strings"
|
2021-11-29 10:07:54 +00:00
|
|
|
|
2023-10-08 16:46:03 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/iio"
|
2021-11-29 10:07:54 +00:00
|
|
|
"github.com/bodgit/sevenzip"
|
|
|
|
"github.com/nwaples/rardecode/v2"
|
|
|
|
)
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
var ArchiveFactories = map[string]FsFactory{
|
|
|
|
".zip": func(f File) (Filesystem, error) {
|
2024-01-28 20:22:49 +00:00
|
|
|
stat, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewArchive(stat.Name(), f, stat.Size(), ZipLoader), nil
|
2023-10-16 09:18:40 +00:00
|
|
|
},
|
|
|
|
".rar": func(f File) (Filesystem, error) {
|
2024-01-28 20:22:49 +00:00
|
|
|
stat, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewArchive(stat.Name(), f, stat.Size(), RarLoader), nil
|
2023-10-16 09:18:40 +00:00
|
|
|
},
|
|
|
|
".7z": func(f File) (Filesystem, error) {
|
2024-01-28 20:22:49 +00:00
|
|
|
stat, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewArchive(stat.Name(), f, stat.Size(), SevenZipLoader), nil
|
2023-10-16 09:18:40 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
type archiveLoader func(r iio.Reader, size int64) (map[string]*archiveFile, error)
|
2023-10-16 09:18:40 +00:00
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
var _ Filesystem = &ArchiveFS{}
|
2023-10-16 09:18:40 +00:00
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
type ArchiveFS struct {
|
2024-01-28 20:22:49 +00:00
|
|
|
name string
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
r iio.Reader
|
|
|
|
|
|
|
|
size int64
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
files func() (map[string]File, error)
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
func NewArchive(name string, r iio.Reader, size int64, loader archiveLoader) *ArchiveFS {
|
|
|
|
return &ArchiveFS{
|
2024-01-28 20:22:49 +00:00
|
|
|
name: name,
|
2023-10-16 09:18:40 +00:00
|
|
|
r: r,
|
|
|
|
size: size,
|
2024-01-28 20:22:49 +00:00
|
|
|
files: OnceValueWOErr(func() (map[string]File, error) {
|
|
|
|
zipFiles, err := loader(r, size)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// TODO make optional
|
|
|
|
singleDir := true
|
|
|
|
for k := range zipFiles {
|
|
|
|
if !strings.HasPrefix(k, "/"+name+"/") {
|
|
|
|
singleDir = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
files := make(map[string]File, len(zipFiles))
|
|
|
|
for k, v := range zipFiles {
|
|
|
|
// TODO make optional
|
|
|
|
if strings.Contains(k, "/__MACOSX/") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if singleDir {
|
|
|
|
k, _ = strings.CutPrefix(k, "/"+name)
|
|
|
|
}
|
|
|
|
|
|
|
|
files[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME
|
|
|
|
files["/.forcegallery"] = NewMemoryFile(".forcegallery", []byte{})
|
|
|
|
|
|
|
|
return files, nil
|
2023-10-16 09:18:40 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-25 22:11:03 +00:00
|
|
|
// Unlink implements Filesystem.
|
2024-03-19 21:30:37 +00:00
|
|
|
func (a *ArchiveFS) Unlink(filename string) error {
|
2023-12-25 22:11:03 +00:00
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2023-12-21 23:15:39 +00:00
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
func (a *ArchiveFS) Open(filename string) (File, error) {
|
2023-10-16 09:18:40 +00:00
|
|
|
files, err := a.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return getFile(files, filename)
|
|
|
|
}
|
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
func (fs *ArchiveFS) ReadDir(path string) ([]fs.DirEntry, error) {
|
2023-10-16 09:18:40 +00:00
|
|
|
files, err := fs.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
return listDirFromFiles(files, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat implements Filesystem.
|
2024-03-19 21:30:37 +00:00
|
|
|
func (afs *ArchiveFS) Stat(filename string) (fs.FileInfo, error) {
|
2023-12-21 23:15:39 +00:00
|
|
|
files, err := afs.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if file, ok := files[filename]; ok {
|
2024-01-28 20:22:49 +00:00
|
|
|
return file.Stat()
|
2023-12-21 23:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for p, _ := range files {
|
|
|
|
if strings.HasPrefix(p, filename) {
|
|
|
|
return newDirInfo(path.Base(filename)), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, ErrNotExist
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ File = &archiveFile{}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
func NewArchiveFile(name string, readerFunc func() (iio.Reader, error), size int64) *archiveFile {
|
2023-10-16 09:18:40 +00:00
|
|
|
return &archiveFile{
|
2023-12-21 23:15:39 +00:00
|
|
|
name: name,
|
2023-10-16 09:18:40 +00:00
|
|
|
readerFunc: readerFunc,
|
2023-12-21 23:15:39 +00:00
|
|
|
size: size,
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-29 10:07:54 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
type archiveFile struct {
|
2023-12-21 23:15:39 +00:00
|
|
|
name string
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
readerFunc func() (iio.Reader, error)
|
|
|
|
reader iio.Reader
|
2023-12-21 23:15:39 +00:00
|
|
|
size int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) Stat() (fs.FileInfo, error) {
|
|
|
|
return newFileInfo(d.name, d.size), nil
|
2021-11-29 10:07:54 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
func (d *archiveFile) load() error {
|
|
|
|
if d.reader != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
r, err := d.readerFunc()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.reader = r
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) Size() int64 {
|
2023-12-21 23:15:39 +00:00
|
|
|
return d.size
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) IsDir() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) Close() (err error) {
|
|
|
|
if d.reader != nil {
|
|
|
|
err = d.reader.Close()
|
|
|
|
d.reader = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) Read(p []byte) (n int, err error) {
|
|
|
|
if err := d.load(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.reader.Read(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *archiveFile) ReadAt(p []byte, off int64) (n int, err error) {
|
|
|
|
if err := d.load(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.reader.ReadAt(p, off)
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
var _ archiveLoader = ZipLoader
|
2023-10-16 09:18:40 +00:00
|
|
|
|
|
|
|
func ZipLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) {
|
2021-11-29 10:07:54 +00:00
|
|
|
zr, err := zip.NewReader(reader, size)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
out := make(map[string]*archiveFile)
|
2024-01-28 20:22:49 +00:00
|
|
|
for i := range zr.File {
|
|
|
|
zipFile := zr.File[i]
|
|
|
|
if zipFile.FileInfo().IsDir() {
|
2021-11-29 10:07:54 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
rf := func() (iio.Reader, error) {
|
2024-01-28 20:22:49 +00:00
|
|
|
zr, err := zipFile.Open()
|
2021-11-29 10:07:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iio.NewDiskTeeReader(zr)
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
out[AbsPath(zipFile.Name)] = NewArchiveFile(zipFile.Name, rf, zipFile.FileInfo().Size())
|
2021-11-29 10:07:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
var _ archiveLoader = SevenZipLoader
|
2021-11-29 10:07:54 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
func SevenZipLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) {
|
2021-11-29 10:07:54 +00:00
|
|
|
r, err := sevenzip.NewReader(reader, size)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
out := make(map[string]*archiveFile)
|
2021-11-29 10:07:54 +00:00
|
|
|
for _, f := range r.File {
|
|
|
|
f := f
|
|
|
|
if f.FileInfo().IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
rf := func() (iio.Reader, error) {
|
|
|
|
zr, err := f.Open()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return iio.NewDiskTeeReader(zr)
|
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
af := NewArchiveFile(f.Name, rf, f.FileInfo().Size())
|
2021-11-29 10:07:54 +00:00
|
|
|
n := filepath.Join(string(os.PathSeparator), f.Name)
|
|
|
|
|
|
|
|
out[n] = af
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
var _ archiveLoader = RarLoader
|
2021-11-29 10:07:54 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
func RarLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) {
|
2021-11-29 10:07:54 +00:00
|
|
|
r, err := rardecode.NewReader(iio.NewSeekerWrapper(reader, size))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
out := make(map[string]*archiveFile)
|
2021-11-29 10:07:54 +00:00
|
|
|
for {
|
|
|
|
header, err := r.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rf := func() (iio.Reader, error) {
|
|
|
|
return iio.NewDiskTeeReader(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
n := filepath.Join(string(os.PathSeparator), header.Name)
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
af := NewArchiveFile(header.Name, rf, header.UnPackedSize)
|
2021-11-29 10:07:54 +00:00
|
|
|
|
|
|
|
out[n] = af
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|