mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-20 09:34:27 +00:00
staticweb package for easier static web server hosting
This commit is contained in:
119
pkg/server/staticweb/providers/embed.go
Normal file
119
pkg/server/staticweb/providers/embed.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"sync"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/server/zipfs"
|
||||
)
|
||||
|
||||
// EmbedFSProvider serves files from an embedded filesystem.
|
||||
// It supports both direct embedded directories and embedded zip files.
|
||||
type EmbedFSProvider struct {
|
||||
embedFS *embed.FS
|
||||
zipFile string // Optional: path within embedded FS to zip file
|
||||
zipReader *zip.Reader
|
||||
fs fs.FS
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewEmbedFSProvider creates a new EmbedFSProvider.
|
||||
// If zipFile is empty, the embedded FS is used directly.
|
||||
// If zipFile is specified, it's treated as a path to a zip file within the embedded FS.
|
||||
func NewEmbedFSProvider(embedFS fs.FS, zipFile string) (*EmbedFSProvider, error) {
|
||||
if embedFS == nil {
|
||||
return nil, fmt.Errorf("embedded filesystem cannot be nil")
|
||||
}
|
||||
|
||||
// Try to cast to *embed.FS for tracking purposes
|
||||
var embedFSPtr *embed.FS
|
||||
if efs, ok := embedFS.(*embed.FS); ok {
|
||||
embedFSPtr = efs
|
||||
}
|
||||
|
||||
provider := &EmbedFSProvider{
|
||||
embedFS: embedFSPtr,
|
||||
zipFile: zipFile,
|
||||
}
|
||||
|
||||
// If zipFile is specified, open it as a zip archive
|
||||
if zipFile != "" {
|
||||
// Read the zip file from the embedded FS
|
||||
// We need to check if the FS supports ReadFile
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if readFileFS, ok := embedFS.(interface{ ReadFile(string) ([]byte, error) }); ok {
|
||||
data, err = readFileFS.ReadFile(zipFile)
|
||||
} else {
|
||||
// Fall back to Open and reading
|
||||
file, openErr := embedFS.Open(zipFile)
|
||||
if openErr != nil {
|
||||
return nil, fmt.Errorf("failed to open embedded zip file: %w", openErr)
|
||||
}
|
||||
defer file.Close()
|
||||
data, err = io.ReadAll(file)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read embedded zip file: %w", err)
|
||||
}
|
||||
|
||||
// Create a zip reader from the data
|
||||
reader := bytes.NewReader(data)
|
||||
zipReader, err := zip.NewReader(reader, int64(len(data)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create zip reader: %w", err)
|
||||
}
|
||||
|
||||
provider.zipReader = zipReader
|
||||
provider.fs = zipfs.NewZipFS(zipReader)
|
||||
} else {
|
||||
// Use the embedded FS directly
|
||||
provider.fs = embedFS
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// Open opens the named file from the embedded filesystem.
|
||||
func (p *EmbedFSProvider) Open(name string) (fs.File, error) {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
if p.fs == nil {
|
||||
return nil, fmt.Errorf("embedded filesystem is closed")
|
||||
}
|
||||
|
||||
return p.fs.Open(name)
|
||||
}
|
||||
|
||||
// Close releases any resources held by the provider.
|
||||
// For embedded filesystems, this is mostly a no-op since Go manages the lifecycle.
|
||||
func (p *EmbedFSProvider) Close() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Clear references to allow garbage collection
|
||||
p.fs = nil
|
||||
p.zipReader = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns "embed" or "embed-zip" depending on the configuration.
|
||||
func (p *EmbedFSProvider) Type() string {
|
||||
if p.zipFile != "" {
|
||||
return "embed-zip"
|
||||
}
|
||||
return "embed"
|
||||
}
|
||||
|
||||
// ZipFile returns the path to the zip file within the embedded FS, if any.
|
||||
func (p *EmbedFSProvider) ZipFile() string {
|
||||
return p.zipFile
|
||||
}
|
||||
80
pkg/server/staticweb/providers/local.go
Normal file
80
pkg/server/staticweb/providers/local.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// LocalFSProvider serves files from a local directory.
|
||||
type LocalFSProvider struct {
|
||||
path string
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
// NewLocalFSProvider creates a new LocalFSProvider for the given directory path.
|
||||
// The path must be an absolute path to an existing directory.
|
||||
func NewLocalFSProvider(path string) (*LocalFSProvider, error) {
|
||||
// Validate that the path exists and is a directory
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat directory: %w", err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil, fmt.Errorf("path is not a directory: %s", path)
|
||||
}
|
||||
|
||||
// Convert to absolute path
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get absolute path: %w", err)
|
||||
}
|
||||
|
||||
return &LocalFSProvider{
|
||||
path: absPath,
|
||||
fs: os.DirFS(absPath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open opens the named file from the local directory.
|
||||
func (p *LocalFSProvider) Open(name string) (fs.File, error) {
|
||||
return p.fs.Open(name)
|
||||
}
|
||||
|
||||
// Close releases any resources held by the provider.
|
||||
// For local filesystem, this is a no-op since os.DirFS doesn't hold resources.
|
||||
func (p *LocalFSProvider) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns "local".
|
||||
func (p *LocalFSProvider) Type() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
// Path returns the absolute path to the directory being served.
|
||||
func (p *LocalFSProvider) Path() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
// Reload refreshes the filesystem view.
|
||||
// For local directories, os.DirFS automatically picks up changes,
|
||||
// so this recreates the DirFS to ensure a fresh view.
|
||||
func (p *LocalFSProvider) Reload() error {
|
||||
// Verify the directory still exists
|
||||
info, err := os.Stat(p.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat directory: %w", err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("path is no longer a directory: %s", p.path)
|
||||
}
|
||||
|
||||
// Recreate the DirFS
|
||||
p.fs = os.DirFS(p.path)
|
||||
|
||||
return nil
|
||||
}
|
||||
393
pkg/server/staticweb/providers/providers_test.go
Normal file
393
pkg/server/staticweb/providers/providers_test.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLocalFSProvider(t *testing.T) {
|
||||
// Create a temporary directory with test files
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.txt")
|
||||
if err := os.WriteFile(testFile, []byte("test content"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider, err := NewLocalFSProvider(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Test opening a file
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
if string(data) != "test content" {
|
||||
t.Errorf("Expected 'test content', got %q", string(data))
|
||||
}
|
||||
|
||||
// Test type
|
||||
if provider.Type() != "local" {
|
||||
t.Errorf("Expected type 'local', got %q", provider.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipFSProvider(t *testing.T) {
|
||||
// Create a temporary zip file
|
||||
tmpDir := t.TempDir()
|
||||
zipPath := filepath.Join(tmpDir, "test.zip")
|
||||
|
||||
// Create zip file with test content
|
||||
zipFile, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
fileWriter, err := zipWriter.Create("test.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = fileWriter.Write([]byte("zip content"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := zipWriter.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := zipFile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test the provider
|
||||
provider, err := NewZipFSProvider(zipPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Test opening a file
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
if string(data) != "zip content" {
|
||||
t.Errorf("Expected 'zip content', got %q", string(data))
|
||||
}
|
||||
|
||||
// Test type
|
||||
if provider.Type() != "zip" {
|
||||
t.Errorf("Expected type 'zip', got %q", provider.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipFSProviderReload(t *testing.T) {
|
||||
// Create a temporary zip file
|
||||
tmpDir := t.TempDir()
|
||||
zipPath := filepath.Join(tmpDir, "test.zip")
|
||||
|
||||
// Helper to create zip with content
|
||||
createZip := func(content string) {
|
||||
zipFile, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
fileWriter, err := zipWriter.Create("test.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = fileWriter.Write([]byte(content))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := zipWriter.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create initial zip
|
||||
createZip("original content")
|
||||
|
||||
// Test the provider
|
||||
provider, err := NewZipFSProvider(zipPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Read initial content
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
data, _ := io.ReadAll(file)
|
||||
file.Close()
|
||||
|
||||
if string(data) != "original content" {
|
||||
t.Errorf("Expected 'original content', got %q", string(data))
|
||||
}
|
||||
|
||||
// Update the zip file
|
||||
createZip("updated content")
|
||||
|
||||
// Reload the provider
|
||||
if err := provider.Reload(); err != nil {
|
||||
t.Fatalf("Failed to reload: %v", err)
|
||||
}
|
||||
|
||||
// Read updated content
|
||||
file, err = provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file after reload: %v", err)
|
||||
}
|
||||
data, _ = io.ReadAll(file)
|
||||
file.Close()
|
||||
|
||||
if string(data) != "updated content" {
|
||||
t.Errorf("Expected 'updated content', got %q", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFSProviderReload(t *testing.T) {
|
||||
// Create a temporary directory with test files
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.txt")
|
||||
if err := os.WriteFile(testFile, []byte("original"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider, err := NewLocalFSProvider(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Read initial content
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
data, _ := io.ReadAll(file)
|
||||
file.Close()
|
||||
|
||||
if string(data) != "original" {
|
||||
t.Errorf("Expected 'original', got %q", string(data))
|
||||
}
|
||||
|
||||
// Update the file
|
||||
if err := os.WriteFile(testFile, []byte("updated"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Reload the provider
|
||||
if err := provider.Reload(); err != nil {
|
||||
t.Fatalf("Failed to reload: %v", err)
|
||||
}
|
||||
|
||||
// Read updated content
|
||||
file, err = provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file after reload: %v", err)
|
||||
}
|
||||
data, _ = io.ReadAll(file)
|
||||
file.Close()
|
||||
|
||||
if string(data) != "updated" {
|
||||
t.Errorf("Expected 'updated', got %q", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedFSProvider(t *testing.T) {
|
||||
// Test with a mock embed.FS
|
||||
mockFS := &mockEmbedFS{
|
||||
files: map[string][]byte{
|
||||
"test.txt": []byte("test content"),
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewEmbedFSProvider(mockFS, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Test type
|
||||
if provider.Type() != "embed" {
|
||||
t.Errorf("Expected type 'embed', got %q", provider.Type())
|
||||
}
|
||||
|
||||
// Test opening a file
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
if string(data) != "test content" {
|
||||
t.Errorf("Expected 'test content', got %q", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedFSProviderWithZip(t *testing.T) {
|
||||
// Create an embedded-like FS with a zip file
|
||||
// For simplicity, we'll use a mock embed.FS
|
||||
tmpDir := t.TempDir()
|
||||
zipPath := filepath.Join(tmpDir, "test.zip")
|
||||
|
||||
// Create zip file
|
||||
zipFile, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
fileWriter, err := zipWriter.Create("test.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = fileWriter.Write([]byte("embedded zip content"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
zipWriter.Close()
|
||||
zipFile.Close()
|
||||
|
||||
// Read the zip file
|
||||
zipData, err := os.ReadFile(zipPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a mock embed.FS
|
||||
mockFS := &mockEmbedFS{
|
||||
files: map[string][]byte{
|
||||
"test.zip": zipData,
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewEmbedFSProvider(mockFS, "test.zip")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider: %v", err)
|
||||
}
|
||||
defer provider.Close()
|
||||
|
||||
// Test opening a file
|
||||
file, err := provider.Open("test.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
if string(data) != "embedded zip content" {
|
||||
t.Errorf("Expected 'embedded zip content', got %q", string(data))
|
||||
}
|
||||
|
||||
// Test type
|
||||
if provider.Type() != "embed-zip" {
|
||||
t.Errorf("Expected type 'embed-zip', got %q", provider.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// mockEmbedFS is a mock embed.FS for testing
|
||||
type mockEmbedFS struct {
|
||||
files map[string][]byte
|
||||
}
|
||||
|
||||
func (m *mockEmbedFS) Open(name string) (fs.File, error) {
|
||||
data, ok := m.files[name]
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return &mockFile{
|
||||
name: name,
|
||||
reader: bytes.NewReader(data),
|
||||
size: int64(len(data)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockEmbedFS) ReadFile(name string) ([]byte, error) {
|
||||
data, ok := m.files[name]
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type mockFile struct {
|
||||
name string
|
||||
reader *bytes.Reader
|
||||
size int64
|
||||
}
|
||||
|
||||
func (f *mockFile) Stat() (fs.FileInfo, error) {
|
||||
return &mockFileInfo{name: f.name, size: f.size}, nil
|
||||
}
|
||||
|
||||
func (f *mockFile) Read(p []byte) (int, error) {
|
||||
return f.reader.Read(p)
|
||||
}
|
||||
|
||||
func (f *mockFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
}
|
||||
|
||||
func (fi *mockFileInfo) Name() string { return fi.name }
|
||||
func (fi *mockFileInfo) Size() int64 { return fi.size }
|
||||
func (fi *mockFileInfo) Mode() fs.FileMode { return 0644 }
|
||||
func (fi *mockFileInfo) ModTime() time.Time { return time.Now() }
|
||||
func (fi *mockFileInfo) IsDir() bool { return false }
|
||||
func (fi *mockFileInfo) Sys() interface{} { return nil }
|
||||
102
pkg/server/staticweb/providers/zip.go
Normal file
102
pkg/server/staticweb/providers/zip.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/server/zipfs"
|
||||
)
|
||||
|
||||
// ZipFSProvider serves files from a zip file.
|
||||
type ZipFSProvider struct {
|
||||
zipPath 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.
|
||||
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")
|
||||
}
|
||||
|
||||
return p.zipFS.Open(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
|
||||
}
|
||||
Reference in New Issue
Block a user