Files
waste-go/internal/shares/shares.go
Fredrik Johansson e0704f210c feat: persistent multi-share configuration (shares.json + localStorage)
Daemon side:
- internal/shares: new package reads/writes shares.json next to identity
- proto: add ShareEntry type, add_share/remove_share/list_shares IPC commands,
  shares_list event, path field on FileEntry
- netmgr: load shares.json on startup; ScanAllShares combines legacy ShareDir
  with shares.json entries (recursive walk with relative paths)
- mesh: ScanFiles callback lets manager inject multi-share scanning without
  mesh knowing about shares.json
- ipc: handle add_share, remove_share, list_shares commands

Browser side:
- ShareManager component replaces FolderPicker: shows list of named shares
  with remove/re-pick buttons; persists share records to waste_shares in
  localStorage (name, global flag, networkId)
- FolderPicker retained for internal use; Sidebar now uses ShareManager

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 20:47:11 +02:00

100 lines
2.2 KiB
Go

// Package shares manages the persistent multi-share configuration stored in shares.json.
// Shares are additive to the existing single-dir ShareDir mechanism.
package shares
import (
"encoding/json"
"os"
"path/filepath"
"sync"
)
// Share describes one shared directory entry.
type Share struct {
Path string `json:"path"`
Networks []string `json:"networks"` // ["*"] = global (all networks); otherwise list of network IDs
}
// Store manages the shares.json file.
type Store struct {
mu sync.RWMutex
path string
shares []Share
}
// Load reads shares.json from dataDir. Returns an empty store if the file doesn't exist.
func Load(dataDir string) (*Store, error) {
s := &Store{path: filepath.Join(dataDir, "shares.json")}
data, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return s, nil
}
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &s.shares); err != nil {
return nil, err
}
return s, nil
}
func (s *Store) save() error {
data, err := json.MarshalIndent(s.shares, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.path, data, 0600)
}
// All returns a copy of all shares.
func (s *Store) All() []Share {
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]Share, len(s.shares))
copy(out, s.shares)
return out
}
// ForNetwork returns all shares visible on networkID (global + network-specific).
func (s *Store) ForNetwork(networkID string) []Share {
s.mu.RLock()
defer s.mu.RUnlock()
var out []Share
for _, sh := range s.shares {
for _, n := range sh.Networks {
if n == "*" || n == networkID {
out = append(out, sh)
break
}
}
}
return out
}
// Add adds a share (no-op if path already present). Persists immediately.
func (s *Store) Add(sh Share) error {
s.mu.Lock()
defer s.mu.Unlock()
for i, existing := range s.shares {
if existing.Path == sh.Path {
s.shares[i].Networks = sh.Networks
return s.save()
}
}
s.shares = append(s.shares, sh)
return s.save()
}
// Remove removes the share with the given path. Persists immediately.
func (s *Store) Remove(path string) error {
s.mu.Lock()
defer s.mu.Unlock()
for i, sh := range s.shares {
if sh.Path == path {
s.shares = append(s.shares[:i], s.shares[i+1:]...)
return s.save()
}
}
return nil
}