mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-04-05 07:32:26 +00:00
* Implemented alternate path handling for file retrieval * Improved error messaging for file not found scenarios
154 lines
3.7 KiB
Go
154 lines
3.7 KiB
Go
package providers
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"io/fs"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/server/zipfs"
|
|
)
|
|
|
|
// ZipFSProvider serves files from a zip file.
|
|
type ZipFSProvider struct {
|
|
zipPath string
|
|
stripPrefix string
|
|
zipReader *zip.ReadCloser
|
|
zipFS *zipfs.ZipFS
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewZipFSProvider creates a new ZipFSProvider for the given zip file path.
|
|
func NewZipFSProvider(zipPath string) (*ZipFSProvider, error) {
|
|
// Convert to absolute path
|
|
absPath, err := filepath.Abs(zipPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get absolute path: %w", err)
|
|
}
|
|
|
|
// Open the zip file
|
|
zipReader, err := zip.OpenReader(absPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open zip file: %w", err)
|
|
}
|
|
|
|
return &ZipFSProvider{
|
|
zipPath: absPath,
|
|
zipReader: zipReader,
|
|
zipFS: zipfs.NewZipFS(&zipReader.Reader),
|
|
}, nil
|
|
}
|
|
|
|
// Open opens the named file from the zip archive.
|
|
// If a strip prefix is configured, it prepends the prefix to the requested path.
|
|
// For example, with stripPrefix="/dist", requesting "/assets/style.css" will
|
|
// open "/dist/assets/style.css" from the zip filesystem.
|
|
func (p *ZipFSProvider) Open(name string) (fs.File, error) {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if p.zipFS == nil {
|
|
return nil, fmt.Errorf("zip filesystem is closed")
|
|
}
|
|
|
|
// Apply prefix stripping by prepending the prefix to the requested path
|
|
actualPath := name
|
|
alternatePath := ""
|
|
if p.stripPrefix != "" {
|
|
// Clean the paths to handle leading/trailing slashes
|
|
prefix := strings.Trim(p.stripPrefix, "/")
|
|
cleanName := strings.TrimPrefix(name, "/")
|
|
|
|
if prefix != "" {
|
|
actualPath = path.Join(prefix, cleanName)
|
|
alternatePath = cleanName
|
|
} else {
|
|
actualPath = cleanName
|
|
}
|
|
}
|
|
|
|
// First try the actual path with prefix
|
|
if file, err := p.zipFS.Open(actualPath); err == nil {
|
|
return file, nil
|
|
}
|
|
|
|
// If alternate path is different, try it as well
|
|
if alternatePath != "" && alternatePath != actualPath {
|
|
if file, err := p.zipFS.Open(alternatePath); err == nil {
|
|
return file, nil
|
|
}
|
|
}
|
|
|
|
// If both attempts fail, return the error from the first attempt
|
|
return nil, fmt.Errorf("file not found: %s", name)
|
|
}
|
|
|
|
// Close releases resources held by the zip reader.
|
|
func (p *ZipFSProvider) Close() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.zipReader != nil {
|
|
err := p.zipReader.Close()
|
|
p.zipReader = nil
|
|
p.zipFS = nil
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Type returns "zip".
|
|
func (p *ZipFSProvider) Type() string {
|
|
return "zip"
|
|
}
|
|
|
|
// Path returns the absolute path to the zip file being served.
|
|
func (p *ZipFSProvider) Path() string {
|
|
return p.zipPath
|
|
}
|
|
|
|
// Reload reopens the zip file to pick up any changes.
|
|
// This is useful in development when the zip file is updated.
|
|
func (p *ZipFSProvider) Reload() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
// Close the existing zip reader if open
|
|
if p.zipReader != nil {
|
|
if err := p.zipReader.Close(); err != nil {
|
|
return fmt.Errorf("failed to close old zip reader: %w", err)
|
|
}
|
|
}
|
|
|
|
// Reopen the zip file
|
|
zipReader, err := zip.OpenReader(p.zipPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to reopen zip file: %w", err)
|
|
}
|
|
|
|
p.zipReader = zipReader
|
|
p.zipFS = zipfs.NewZipFS(&zipReader.Reader)
|
|
|
|
return nil
|
|
}
|
|
|
|
// WithStripPrefix sets the prefix to strip from requested paths.
|
|
// For example, WithStripPrefix("/dist") will make files at "/dist/assets"
|
|
// accessible via "/assets".
|
|
func (p *ZipFSProvider) WithStripPrefix(prefix string) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.stripPrefix = prefix
|
|
}
|
|
|
|
// StripPrefix returns the configured strip prefix.
|
|
func (p *ZipFSProvider) StripPrefix() string {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return p.stripPrefix
|
|
}
|