mirror of
https://github.com/navidrome/navidrome.git
synced 2026-01-03 06:15:22 +00:00
114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
package plugins
|
|
|
|
import (
|
|
"archive/zip"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
// PackageExtension is the file extension for Navidrome plugin packages.
|
|
PackageExtension = ".ndp"
|
|
|
|
// manifestFileName is the name of the manifest file inside the package.
|
|
manifestFileName = "manifest.json"
|
|
|
|
// wasmFileName is the name of the WebAssembly module inside the package.
|
|
wasmFileName = "plugin.wasm"
|
|
)
|
|
|
|
// ndpPackage represents a loaded .ndp plugin package.
|
|
// It contains the manifest and wasm bytes read from the archive.
|
|
type ndpPackage struct {
|
|
Manifest *Manifest
|
|
WasmBytes []byte
|
|
}
|
|
|
|
// openPackage opens an .ndp file and extracts the manifest and wasm bytes.
|
|
// The caller does not need to call Close() - all resources are read into memory.
|
|
func openPackage(ndpPath string) (*ndpPackage, error) {
|
|
// Open the zip archive
|
|
zr, err := zip.OpenReader(ndpPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening package: %w", err)
|
|
}
|
|
defer zr.Close()
|
|
|
|
var manifestBytes []byte
|
|
var wasmBytes []byte
|
|
|
|
for _, f := range zr.File {
|
|
switch f.Name {
|
|
case manifestFileName:
|
|
manifestBytes, err = readZipFile(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading manifest: %w", err)
|
|
}
|
|
case wasmFileName:
|
|
wasmBytes, err = readZipFile(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading wasm: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if manifestBytes == nil {
|
|
return nil, errors.New("package missing manifest.json")
|
|
}
|
|
if wasmBytes == nil {
|
|
return nil, errors.New("package missing plugin.wasm")
|
|
}
|
|
|
|
// Parse and validate manifest
|
|
var manifest Manifest
|
|
if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
|
|
return nil, fmt.Errorf("parsing manifest: %w", err)
|
|
}
|
|
|
|
return &ndpPackage{
|
|
Manifest: &manifest,
|
|
WasmBytes: wasmBytes,
|
|
}, nil
|
|
}
|
|
|
|
// readManifest reads only the manifest from an .ndp file without loading the wasm bytes.
|
|
// This is useful for quick plugin discovery.
|
|
func readManifest(ndpPath string) (*Manifest, error) {
|
|
// Open the zip archive
|
|
zr, err := zip.OpenReader(ndpPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening package: %w", err)
|
|
}
|
|
defer zr.Close()
|
|
|
|
for _, f := range zr.File {
|
|
if f.Name == manifestFileName {
|
|
manifestBytes, err := readZipFile(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading manifest: %w", err)
|
|
}
|
|
|
|
var manifest Manifest
|
|
if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
|
|
return nil, fmt.Errorf("parsing manifest: %w", err)
|
|
}
|
|
|
|
return &manifest, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("package missing manifest.json")
|
|
}
|
|
|
|
// readZipFile reads the contents of a file from a zip archive.
|
|
func readZipFile(f *zip.File) ([]byte, error) {
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rc.Close()
|
|
return io.ReadAll(rc)
|
|
}
|