Add file listing: share-dir flag, file_list_req/resp DataChannel messages

- proto: FileEntry, FileListResp types; MsgFileListReq/Resp msg types;
  CmdGetFileList + EvtFileList IPC types; Files field on IpcMessage
- mesh: ShareDir field + ScanShareDir(); on DataChannel open, auto-send
  MsgFileListReq to new peer; handle MsgFileListReq (scan + reply) and
  MsgFileListResp (emit EvtFileList to IPC subscribers)
- ipc: get_file_list command — own list returned immediately; remote peer
  list requested via DataChannel (response arrives as EvtFileList event)
- daemon: -share-dir flag wired to mesh.ShareDir
- test scripts: pass -share-dir /home/frejoh/Downloads/{alice,bob,charlie};
  test-network.sh verifies each peer's own file list via get_file_list
- FUTURE.md: document per-network share directories and multi-network design

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-21 19:07:11 +02:00
parent 1a5f416ee4
commit 8d3ca9d331
9 changed files with 160 additions and 20 deletions

View File

@@ -34,13 +34,15 @@ type PeerInfo struct {
type MsgType string
const (
MsgChat MsgType = "chat"
MsgPeerGossip MsgType = "peer_gossip"
MsgFileOffer MsgType = "file_offer"
MsgFileResp MsgType = "file_response"
MsgFileDone MsgType = "file_done"
MsgPing MsgType = "ping"
MsgPong MsgType = "pong"
MsgChat MsgType = "chat"
MsgPeerGossip MsgType = "peer_gossip"
MsgFileListReq MsgType = "file_list_req"
MsgFileListResp MsgType = "file_list_resp"
MsgFileOffer MsgType = "file_offer"
MsgFileResp MsgType = "file_response"
MsgFileDone MsgType = "file_done"
MsgPing MsgType = "ping"
MsgPong MsgType = "pong"
)
// PeerMessage is the top-level container sent over the "yaw" DataChannel.
@@ -49,12 +51,13 @@ type PeerMessage struct {
Type MsgType `json:"type"`
// Only one of these will be set, depending on Type.
Chat *ChatMessage `json:"chat,omitempty"`
Gossip *PeerGossip `json:"gossip,omitempty"`
FileOffer *FileOffer `json:"file_offer,omitempty"`
FileResp *FileResponse `json:"file_response,omitempty"`
FileDone *FileDone `json:"file_done,omitempty"`
Seq *uint64 `json:"seq,omitempty"` // for ping/pong
Chat *ChatMessage `json:"chat,omitempty"`
Gossip *PeerGossip `json:"gossip,omitempty"`
FileListResp *FileListResp `json:"file_list_resp,omitempty"`
FileOffer *FileOffer `json:"file_offer,omitempty"`
FileResp *FileResponse `json:"file_response,omitempty"`
FileDone *FileDone `json:"file_done,omitempty"`
Seq *uint64 `json:"seq,omitempty"` // for ping/pong
}
// ChatMessage is a message to a room or a DM.
@@ -80,6 +83,18 @@ type GossipEntry struct {
LastSeen time.Time `json:"last_seen"`
}
// FileEntry describes a single file in a peer's shared directory.
type FileEntry struct {
Name string `json:"name"`
SizeBytes int64 `json:"size_bytes"`
}
// FileListResp is the payload for MsgFileListResp.
// MsgFileListReq carries no payload — it is a zero-field request.
type FileListResp struct {
Files []FileEntry `json:"files"`
}
// FileOffer initiates a file transfer.
type FileOffer struct {
Mid string `json:"mid"` // dedup id
@@ -187,6 +202,7 @@ const (
CmdGetState IpcMsgType = "get_state"
CmdSendFile IpcMsgType = "send_file"
CmdGenerateInvite IpcMsgType = "generate_invite"
CmdGetFileList IpcMsgType = "get_file_list"
// Events (daemon → UI)
EvtMessageReceived IpcMsgType = "message_received"
@@ -198,6 +214,7 @@ const (
EvtStateSnapshot IpcMsgType = "state_snapshot"
EvtError IpcMsgType = "error"
EvtInviteGenerated IpcMsgType = "invite_generated"
EvtFileList IpcMsgType = "file_list"
)
// IpcMessage covers both commands and events.
@@ -229,4 +246,5 @@ type IpcMessage struct {
Rooms []string `json:"rooms,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
InviteString string `json:"invite,omitempty"`
Files []FileEntry `json:"files,omitempty"`
}