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>
This commit is contained in:
Fredrik Johansson
2026-06-26 20:47:11 +02:00
parent 0f54f3bbad
commit e0704f210c
10 changed files with 366 additions and 4 deletions

View File

@@ -111,6 +111,13 @@ type GossipEntry struct {
type FileEntry struct {
Name string `json:"name"`
SizeBytes int64 `json:"size_bytes"`
Path string `json:"path,omitempty"` // relative path including filename
}
// ShareEntry describes one persistent share root.
type ShareEntry struct {
Path string `json:"path"`
Networks []string `json:"networks"` // ["*"] = global
}
// FileListResp is the payload for MsgFileListResp.
@@ -222,6 +229,9 @@ const (
CmdGetFileList IpcMsgType = "get_file_list"
CmdExportIdentity IpcMsgType = "export_identity" // returns encrypted backup blob
CmdImportIdentity IpcMsgType = "import_identity" // replaces identity from backup blob
CmdAddShare IpcMsgType = "add_share" // add a share root; fields: path, networks
CmdRemoveShare IpcMsgType = "remove_share" // remove a share root; field: path
CmdListShares IpcMsgType = "list_shares" // returns shares_list event
// Events (daemon → UI)
EvtMessageReceived IpcMsgType = "message_received"
@@ -240,6 +250,7 @@ const (
EvtNetworkLeft IpcMsgType = "network_left"
EvtIdentityExported IpcMsgType = "identity_exported"
EvtIdentityImported IpcMsgType = "identity_imported"
EvtSharesList IpcMsgType = "shares_list"
)
// NetworkInfo summarises one joined network for state_snapshot and network_joined events.
@@ -292,6 +303,8 @@ type IpcMessage struct {
ErrorMessage string `json:"error_message,omitempty"`
InviteString string `json:"invite,omitempty"`
Files []FileEntry `json:"files,omitempty"`
Shares []ShareEntry `json:"shares,omitempty"`
ShareNetworks []string `json:"networks,omitempty"` // for add_share command
// export_identity / import_identity
Passphrase string `json:"passphrase,omitempty"` // import only; never echoed back
Backup string `json:"backup,omitempty"` // JSON backup blob