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

@@ -3,6 +3,7 @@ package mesh
import (
"log"
"os"
"sync"
"github.com/waste-go/internal/crypto"
@@ -22,6 +23,7 @@ type PeerConn struct {
type Mesh struct {
Identity *crypto.Identity
Store *store.Store // may be nil if persistence is disabled
ShareDir string // directory whose contents are shared with peers; "" = no sharing
mu sync.RWMutex
peers map[proto.PeerID]*PeerConn
@@ -41,6 +43,31 @@ func New(id *crypto.Identity, st *store.Store) *Mesh {
}
}
// ScanShareDir returns the list of files in the local share directory.
// Returns an empty slice if ShareDir is unset or the directory is empty.
func (m *Mesh) ScanShareDir() []proto.FileEntry {
if m.ShareDir == "" {
return nil
}
entries, err := os.ReadDir(m.ShareDir)
if err != nil {
log.Printf("mesh: scan share dir %s: %v", m.ShareDir, err)
return nil
}
var files []proto.FileEntry
for _, e := range entries {
if e.IsDir() {
continue
}
info, err := e.Info()
if err != nil {
continue
}
files = append(files, proto.FileEntry{Name: e.Name(), SizeBytes: info.Size()})
}
return files
}
// ── Peer management ───────────────────────────────────────────────────────────
// AddPeer registers a connected peer and notifies subscribers.

View File

@@ -56,6 +56,11 @@ func WireDataChannel(
}
m.AddPeer(peerConn)
// Request the peer's file list immediately after connect.
if req, err := json.Marshal(proto.PeerMessage{Type: proto.MsgFileListReq}); err == nil {
sendCh <- req
}
go func() {
for payload := range sendCh {
if err := dc.SendText(string(payload)); err != nil {
@@ -147,6 +152,28 @@ func handleDCMessage(data []byte, from proto.PeerID, localID *crypto.Identity, m
func dispatchPeerMessage(msg proto.PeerMessage, from proto.PeerID, m *Mesh) {
switch msg.Type {
case proto.MsgFileListReq:
// Peer wants our file list — reply directly on their send channel.
files := m.ScanShareDir()
resp, err := json.Marshal(proto.PeerMessage{
Type: proto.MsgFileListResp,
FileListResp: &proto.FileListResp{Files: files},
})
if err != nil {
return
}
m.SendTo(from, resp)
case proto.MsgFileListResp:
// Received a remote peer's file list — forward to IPC subscribers.
if msg.FileListResp != nil {
m.Emit(proto.IpcMessage{
Type: proto.EvtFileList,
PeerID: peerIDPtr(from),
Files: msg.FileListResp.Files,
})
}
case proto.MsgChat:
if msg.Chat != nil {
m.SaveMessage(msg.Chat)