Files
agent-helm/helmd/shares.go
claude f9c79ac84e
Some checks failed
build-and-push / build (push) Successful in 42s
release-client / build-release (push) Has been cancelled
helmd-release / build-release (push) Successful in 1m6s
helmd v2: Go-server som styr agent i tmux — REST+SSE, frågedetektor, fildelning, ntfy, CI-release
- tmux som sanningskälla (agentoberoende, överlever omstart via adoption)
- API: sessions, prompt, question/answer, keys, mode, config, shares, notify, events
- frågedetektor testad mot riktiga agy 1.1.9-dumpar + mock
- integrationstest: 26 tester i isolerad debian-container (scripts/run-integration.sh)
- e2e-verifierad mot riktig agy (trust-fråga -> svar -> prompt)
- .gitea/workflows/helmd-release.yaml -> rullande helmd-latest (x64+arm64)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 20:54:54 +02:00

124 lines
2.9 KiB
Go

package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"mime"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// Share is a file the agent published for the clients to view
// (reports, screenshots, generated pages...).
type Share struct {
ID string `json:"id"`
Name string `json:"name"`
Note string `json:"note,omitempty"`
Session string `json:"session,omitempty"`
Size int64 `json:"size"`
Mime string `json:"mime"`
Created time.Time `json:"created"`
}
type ShareStore struct {
dir string
cfg *Config
}
func NewShareStore(cfg *Config) (*ShareStore, error) {
if err := os.MkdirAll(cfg.ShareDir, 0o755); err != nil {
return nil, err
}
return &ShareStore{dir: cfg.ShareDir, cfg: cfg}, nil
}
func (s *ShareStore) metaPath(id string) string { return filepath.Join(s.dir, id+".meta.json") }
func (s *ShareStore) filePath(id, name string) string {
return filepath.Join(s.dir, id+"_"+filepath.Base(name))
}
// Add stores content under a fresh id and writes a metadata sidecar.
func (s *ShareStore) Add(name, note, session string, r io.Reader) (*Share, error) {
name = filepath.Base(name)
if !s.cfg.extAllowed(name) {
return nil, fmt.Errorf("filtyp tillåts inte: %s (tillåtna: %s)",
filepath.Ext(name), strings.Join(s.cfg.ShareExts, " "))
}
idb := make([]byte, 6)
if _, err := rand.Read(idb); err != nil {
return nil, err
}
id := hex.EncodeToString(idb)
f, err := os.Create(s.filePath(id, name))
if err != nil {
return nil, err
}
size, err := io.Copy(f, r)
f.Close()
if err != nil {
os.Remove(s.filePath(id, name))
return nil, err
}
sh := &Share{
ID: id,
Name: name,
Note: note,
Session: session,
Size: size,
Mime: mime.TypeByExtension(strings.ToLower(filepath.Ext(name))),
Created: time.Now(),
}
meta, _ := json.MarshalIndent(sh, "", " ")
if err := os.WriteFile(s.metaPath(id), meta, 0o644); err != nil {
return nil, err
}
return sh, nil
}
func (s *ShareStore) Get(id string) (*Share, error) {
data, err := os.ReadFile(s.metaPath(id))
if err != nil {
return nil, err
}
var sh Share
if err := json.Unmarshal(data, &sh); err != nil {
return nil, err
}
return &sh, nil
}
func (s *ShareStore) List() []*Share {
matches, _ := filepath.Glob(filepath.Join(s.dir, "*.meta.json"))
var out []*Share
for _, m := range matches {
data, err := os.ReadFile(m)
if err != nil {
continue
}
var sh Share
if json.Unmarshal(data, &sh) == nil {
out = append(out, &sh)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Created.After(out[j].Created) })
return out
}
func (s *ShareStore) Delete(id string) error {
sh, err := s.Get(id)
if err != nil {
return err
}
os.Remove(s.filePath(sh.ID, sh.Name))
return os.Remove(s.metaPath(id))
}
// Path returns the on-disk path for serving.
func (s *ShareStore) Path(sh *Share) string { return s.filePath(sh.ID, sh.Name) }