mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-01 13:42:20 -04:00
* feat: add distributed mode (experimental) Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix data races, mutexes, transactions Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fixups Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix events and tool stream in agent chat Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * use ginkgo Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(cron): compute correctly time boundaries avoiding re-triggering Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * enhancements, refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * do not flood of healthy checks Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * do not list obvious backends as text backends Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * tests fixups Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Drop redundant healthcheck Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * enhancements, refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// validateKey ensures the resolved path stays inside the store root.
|
|
func (fs *FilesystemStore) validateKey(key string) error {
|
|
p := filepath.Join(fs.root, filepath.FromSlash(key))
|
|
absRoot, err := filepath.Abs(fs.root)
|
|
if err != nil {
|
|
return fmt.Errorf("resolving store root: %w", err)
|
|
}
|
|
absPath, err := filepath.Abs(p)
|
|
if err != nil {
|
|
return fmt.Errorf("resolving key path: %w", err)
|
|
}
|
|
if !strings.HasPrefix(absPath, absRoot+string(filepath.Separator)) && absPath != absRoot {
|
|
return fmt.Errorf("key %q resolves outside storage root", key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FilesystemStore implements ObjectStore backed by a local directory.
|
|
type FilesystemStore struct {
|
|
root string
|
|
}
|
|
|
|
// NewFilesystemStore creates a new filesystem-backed ObjectStore rooted at the given directory.
|
|
func NewFilesystemStore(root string) (*FilesystemStore, error) {
|
|
if err := os.MkdirAll(root, 0750); err != nil {
|
|
return nil, fmt.Errorf("creating storage root %s: %w", root, err)
|
|
}
|
|
return &FilesystemStore{root: root}, nil
|
|
}
|
|
|
|
func (fs *FilesystemStore) path(key string) string {
|
|
return filepath.Join(fs.root, filepath.FromSlash(key))
|
|
}
|
|
|
|
func (fs *FilesystemStore) Put(_ context.Context, key string, r io.Reader) error {
|
|
if err := fs.validateKey(key); err != nil {
|
|
return err
|
|
}
|
|
p := fs.path(key)
|
|
if err := os.MkdirAll(filepath.Dir(p), 0750); err != nil {
|
|
return fmt.Errorf("creating directories for %s: %w", key, err)
|
|
}
|
|
f, err := os.Create(p)
|
|
if err != nil {
|
|
return fmt.Errorf("creating file %s: %w", key, err)
|
|
}
|
|
defer f.Close()
|
|
if _, err := io.Copy(f, r); err != nil {
|
|
return fmt.Errorf("writing %s: %w", key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fs *FilesystemStore) Get(_ context.Context, key string) (io.ReadCloser, error) {
|
|
if err := fs.validateKey(key); err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := os.Open(fs.path(key))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening %s: %w", key, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func (fs *FilesystemStore) Head(_ context.Context, key string) (*ObjectMeta, error) {
|
|
if err := fs.validateKey(key); err != nil {
|
|
return nil, err
|
|
}
|
|
info, err := os.Stat(fs.path(key))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("stat %s: %w", key, err)
|
|
}
|
|
return &ObjectMeta{
|
|
Key: key,
|
|
Size: info.Size(),
|
|
LastModified: info.ModTime(),
|
|
}, nil
|
|
}
|
|
|
|
func (fs *FilesystemStore) Exists(_ context.Context, key string) (bool, error) {
|
|
if err := fs.validateKey(key); err != nil {
|
|
return false, err
|
|
}
|
|
_, err := os.Stat(fs.path(key))
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func (fs *FilesystemStore) Delete(_ context.Context, key string) error {
|
|
if err := fs.validateKey(key); err != nil {
|
|
return err
|
|
}
|
|
err := os.Remove(fs.path(key))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("deleting %s: %w", key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close implements io.Closer. FilesystemStore holds no resources that need
|
|
// explicit cleanup, so this is a no-op.
|
|
func (fs *FilesystemStore) Close() error { return nil }
|
|
|
|
func (fs *FilesystemStore) List(_ context.Context, prefix string) ([]string, error) {
|
|
if err := fs.validateKey(prefix); err != nil {
|
|
return nil, err
|
|
}
|
|
var keys []string
|
|
base := fs.path(prefix)
|
|
|
|
// If the prefix path doesn't exist, return empty list
|
|
if _, err := os.Stat(base); os.IsNotExist(err) {
|
|
return keys, nil
|
|
}
|
|
|
|
err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
rel, err := filepath.Rel(fs.root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keys = append(keys, strings.ReplaceAll(rel, string(filepath.Separator), "/"))
|
|
}
|
|
return nil
|
|
})
|
|
return keys, err
|
|
}
|