package vfs import ( "archive/zip" "io" "io/fs" "os" "path" "path/filepath" "strings" "git.kmsign.ru/royalcat/tstor/src/iio" "github.com/bodgit/sevenzip" "github.com/nwaples/rardecode/v2" ) var ArchiveFactories = map[string]FsFactory{ ".zip": func(f File) (Filesystem, error) { stat, err := f.Stat() if err != nil { return nil, err } return NewArchive(stat.Name(), f, stat.Size(), ZipLoader), nil }, ".rar": func(f File) (Filesystem, error) { stat, err := f.Stat() if err != nil { return nil, err } return NewArchive(stat.Name(), f, stat.Size(), RarLoader), nil }, ".7z": func(f File) (Filesystem, error) { stat, err := f.Stat() if err != nil { return nil, err } return NewArchive(stat.Name(), f, stat.Size(), SevenZipLoader), nil }, } type archiveLoader func(r iio.Reader, size int64) (map[string]*archiveFile, error) var _ Filesystem = &ArchiveFS{} type ArchiveFS struct { name string r iio.Reader size int64 files func() (map[string]File, error) } func NewArchive(name string, r iio.Reader, size int64, loader archiveLoader) *ArchiveFS { return &ArchiveFS{ name: name, r: r, size: size, 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 }), } } // Unlink implements Filesystem. func (a *ArchiveFS) Unlink(filename string) error { return ErrNotImplemented } func (a *ArchiveFS) Open(filename string) (File, error) { files, err := a.files() if err != nil { return nil, err } return getFile(files, filename) } func (fs *ArchiveFS) ReadDir(path string) ([]fs.DirEntry, error) { files, err := fs.files() if err != nil { return nil, err } return listDirFromFiles(files, path) } // Stat implements Filesystem. func (afs *ArchiveFS) Stat(filename string) (fs.FileInfo, error) { files, err := afs.files() if err != nil { return nil, err } if file, ok := files[filename]; ok { return file.Stat() } for p, _ := range files { if strings.HasPrefix(p, filename) { return newDirInfo(path.Base(filename)), nil } } return nil, ErrNotExist } var _ File = &archiveFile{} func NewArchiveFile(name string, readerFunc func() (iio.Reader, error), size int64) *archiveFile { return &archiveFile{ name: name, readerFunc: readerFunc, size: size, } } type archiveFile struct { name string readerFunc func() (iio.Reader, error) reader iio.Reader size int64 } func (d *archiveFile) Stat() (fs.FileInfo, error) { return newFileInfo(d.name, d.size), nil } 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 { return d.size } 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) } var _ archiveLoader = ZipLoader func ZipLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) { zr, err := zip.NewReader(reader, size) if err != nil { return nil, err } out := make(map[string]*archiveFile) for i := range zr.File { zipFile := zr.File[i] if zipFile.FileInfo().IsDir() { continue } rf := func() (iio.Reader, error) { zr, err := zipFile.Open() if err != nil { return nil, err } return iio.NewDiskTeeReader(zr) } out[AbsPath(zipFile.Name)] = NewArchiveFile(zipFile.Name, rf, zipFile.FileInfo().Size()) } return out, nil } var _ archiveLoader = SevenZipLoader func SevenZipLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) { r, err := sevenzip.NewReader(reader, size) if err != nil { return nil, err } out := make(map[string]*archiveFile) 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) } af := NewArchiveFile(f.Name, rf, f.FileInfo().Size()) n := filepath.Join(string(os.PathSeparator), f.Name) out[n] = af } return out, nil } var _ archiveLoader = RarLoader func RarLoader(reader iio.Reader, size int64) (map[string]*archiveFile, error) { r, err := rardecode.NewReader(iio.NewSeekerWrapper(reader, size)) if err != nil { return nil, err } out := make(map[string]*archiveFile) 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) af := NewArchiveFile(header.Name, rf, header.UnPackedSize) out[n] = af } return out, nil }