# PeerDrop — Architecture Document

## 1. Core Principle

**One daemon process holds the engine. All clients communicate with it via IPC. Single source of truth for all state.**

```
PeerDrop Engine (py-ipfs-lite Peer)
        │
        ├── REST API client
        ├── CLI client
        ├── GUI client (PySide6)
        ├── MCP Server
        └── SDK client
```

Every client is a thin adapter over the same daemon. No business logic lives in any client.

---

## 2. Dependency Decision

| Layer | Technology | Rationale |
|---|---|---|
| Networking | **py-ipfs-lite only** | Wraps py-libp2p. Provides host, mDNS, bitswap, file chunking, block storage. No need to touch py-libp2p directly. |
| Async | **trio** | Native async framework. Zero-dependency. Good cancellation semantics. |
| Data models | **dataclasses** | Built-in, no extra dependency. Sufficient for PeerDrop's needs. |
| GUI | **PySide6** | Mature, full-featured desktop GUI framework. |
| Daemon IPC | **Unix sockets (macOS/Linux) / named pipes (Windows)** with **msgpack** serialization | Fast, simple, no HTTP overhead for internal communication. |
| Protocol handshake | **libp2p streams** | py-ipfs-lite's host exposes `new_stream()` and `set_stream_handler()`. Standard IPFS pattern. |
| Data transfer | **Bitswap** via py-ipfs-lite | Content-addressed, deduplication, resume support, integrity verification — all built in. |

### Why NOT py-libp2p directly

py-ipfs-lite already wraps py-libp2p and provides:

- Host creation with mDNS auto-enabled
- BitswapClient auto-configured with provider query manager
- File chunking via UnixFS (MerkleDag)
- File reassembly from CID
- Block storage (memory or filesystem)
- Noise encryption, Yamux muxing — all defaults

PeerDrop adds **only** the transfer protocol (stream handshake) and application-level concerns (transfer tracking, device identity, event bus).

---

## 3. Transfer Protocol

### Stream + Bitswap Pattern

The stream is only for the handshake. Actual data transfer happens via bitswap.

```
SENDER                                      RECEIVER
  │                                            │
  │  1. open_stream(receiver,                  │
  │     ["/peerdrop/transfer/1.0.0"])          │
  │───────────────────────────────────────────>│
  │                                            │
  │  2. TransferRequest {                      │
  │       file_name, file_size, root_cid       │
  │     }                                      │
  │───────────────────────────────────────────>│
  │                                            │
  │  3. ACK / REJECT                           │
  │<───────────────────────────────────────────│
  │                                            │
  │  (receiver fetches blocks via bitswap      │
  │   using root_cid — py-ipfs-lite handles)   │
  │                                            │
  │  4. TransferComplete                       │
  │───────────────────────────────────────────>│
```

**Why this pattern:**
- Bitswap handles block-level reliability (retransmission, deduplication)
- Content addressing provides integrity verification
- Resume support is automatic (re-fetch missing CIDs)
- No manual block pushing required

---

## 4. Directory Structure

```
peerdrop/
├── peerdrop/
│   ├── __init__.py
│   ├── constants.py       # Shared paths (DEFAULT_SOCK_PATH, etc.)
│   │
│   ├── core/
│   │   ├── __init__.py
│   │   ├── engine.py          # PeerEngine — orchestrates everything
│   │   ├── service.py         # ServiceInterface — typed API layer
│   │   ├── models.py          # dataclasses: Peer, Transfer, Device, etc.
│   │   ├── discovery.py       # Wraps py-ipfs-lite mDNS peer discovery
│   │   ├── transfer.py        # File transfer via stream handshake + bitswap
│   │   └── events.py          # Event definitions + trio-based event bus
│   │
│   ├── daemon/
│   │   ├── __init__.py
│   │   ├── server.py          # Unix socket IPC server
│   │   ├── protocol.py        # Request/response message types
│   │   ├── handler.py         # Routes IPC messages to ServiceInterface
│   │   ├── lifecycle.py       # Daemon start/stop/health
│   │   └── start.py           # Standalone entry point
│   │
│   ├── interfaces/
│   │   ├── __init__.py
│   │   ├── client.py          # Unified IPC client (sync + async)
│   │   ├── cli/
│   │   │   ├── __init__.py
│   │   │   ├── app.py         # Click entry point + CLI commands
│   │   │   └── client.py      # Re-exports AsyncPeerDropClient as DaemonClient
│   │   ├── rest/
│   │   │   ├── __init__.py
│   │   │   └── app.py         # HTTP server + routes
│   │   ├── mcp/
│   │   │   ├── __init__.py
│   │   │   └── server.py      # MCP server (uses unified client)
│   │   └── gui/
│   │       ├── __init__.py
│   │       └── app.py         # PySide6 desktop GUI
│   │
│   └── utils/
│       ├── __init__.py
│       └── framing.py         # Length-prefix IPC message framing
│
├── tests/
├── packaging/             # PyInstaller + Docker build configs
├── pyproject.toml
└── LICENSE
```

---

## 5. Data Flow

### Sending a File (CLI example)

```
peerdrop send file.txt --to device-2
        │
        ▼
   CLI app.py
   Serializes: {"action": "send", "file": "file.txt", "target": "device-2"}
        │
        ▼ (Unix socket)
   Daemon handler.py
   Routes to TransferManager.send_file()
        │
        ▼
   TransferManager
   1. peer.add_file("file.py") → root_cid (py-ipfs-lite)
   2. open_stream to receiver, send TransferRequest
   3. Wait for ACK
        │
        ▼
   py-ipfs-lite Peer
   - Chunks file via UnixFS
   - Stores blocks in blockstore
   - Bitswap provides blocks to receiver
        │
        ▼
   Event Bus
   TransferStarted → daemon → CLI displays progress
   TransferCompleted → daemon → CLI displays done
```

---

## 6. Core Module Responsibilities

### `core/models.py`

| Dataclass | Fields | Purpose |
|---|---|---|
| `Peer` | peer_id, addrs, name, last_seen, is_online | Discovered peer on network |
| `Device` | peer_id, name, listen_port, created_at | This device's identity |
| `Transfer` | transfer_id, file_path, file_name, file_size, root_cid, sender/receiver_peer_id, status, progress, bytes_sent, timestamps, error | Transfer state |
| `TransferRequest` | file_name, file_size, root_cid, sender_peer_id | Stream handshake message |
| `TransferStatus` | PENDING, CONNECTING, TRANSFERRING, COMPLETED, FAILED, CANCELLED | Enum |

### `core/discovery.py` — ~40 lines

| Method | What it does |
|---|---|
| `start()` | Subscribe to py-libp2p's `peerDiscovery` singleton events |
| `_on_peer_found(peer_info)` | Convert `PeerInfo` → `Peer` model, store in map, emit `PeerDiscovered` |
| `get_peers()` | Return discovered peers |
| `get_peer(peer_id)` | Lookup by ID |

**Wraps:** `peerDiscovery.register_peer_discovered_handler()` from py-libp2p (accessible via py-ipfs-lite's host).

### `core/transfer.py` — ~100 lines

| Method | What it does |
|---|---|
| `send_file(path, target_peer_id)` | Add file via py-ipfs-lite, open stream, send TransferRequest, wait for ACK |
| `handle_incoming_transfer(stream)` | Read TransferRequest, send ACK, fetch via `peer.get_file(root_cid)` |
| `get_transfer(id)` | Lookup transfer |
| `list_transfers()` | Return all transfers |
| `cancel_transfer(id)` | Cancel via `bitswap.cancel_want()` + `stream.reset()` |

**Uses from py-ipfs-lite:**
- `peer.add_file(path)` — chunks file, stores blocks
- `peer.get_file(root_cid)` — fetches all blocks via bitswap, reassembles
- `peer._host.new_stream()` — opens protocol stream
- `peer._host.set_stream_handler()` — registers inbound handler

### `core/events.py` — ~50 lines

| Event | Data | Trigger |
|---|---|---|
| `PeerDiscovered` | peer, timestamp | mDNS finds new peer |
| `PeerLost` | peer_id, timestamp | mDNS peer goes offline |
| `TransferStarted` | transfer, timestamp | Transfer begins |
| `TransferProgress` | transfer_id, progress, bytes_sent, timestamp | Blocks transferred |
| `TransferCompleted` | transfer, timestamp | Transfer finishes |
| `TransferFailed` | transfer_id, error, timestamp | Transfer fails |

**Implementation:** trio memory channels. `EventBus.subscribe(type)` returns a `trio.MemoryReceiveChannel`. `EventBus.publish(event)` sends to all subscribers.

### `core/engine.py` — ~60 lines

| Method | What it does |
|---|---|
| `start()` | Create py-ipfs-lite Peer, register stream handler, start discovery |
| `stop()` | Close py-ipfs-lite Peer |
| `discover_peers()` | Delegate to DiscoveryManager |
| `send_file(path, target)` | Delegate to TransferManager |
| `list_transfers()` | Delegate to TransferManager |
| `on_event(type, handler)` | Subscribe to EventBus |

**The engine IS the py-ipfs-lite Peer plus application-level managers.**

---

## 7. Daemon IPC Protocol

### Message Format (msgpack)

```python
# Request (client → daemon)
{"id": "uuid", "action": "send_file", "params": {"file": "path", "target": "peer_id"}}

# Response (daemon → client)
{"id": "uuid", "ok": true, "data": {"transfer_id": "..."}}

# Event (daemon → client, no id)
{"event": "transfer_progress", "data": {"transfer_id": "...", "progress": 0.45}}
```

### IPC Server

- Listens on Unix socket (`~/.peerdrop/peerdrop.sock`)
- One connection per client
- Multiplexes requests and events over same socket
- Falls back to TCP on Windows

---

## 8. Unified IPC Client

All interfaces (CLI, REST, MCP, GUI) communicate with the daemon through a single unified client module (`interfaces/client.py`):

```python
# Synchronous client (for CLI, REST, GUI)
with PeerDropClient(sock_path) as client:
    client.connect()
    identity = client.get_identity()
    peers = client.discover_peers()
    transfers = client.list_transfers()

# Async client (for trio-based code)
async with AsyncPeerDropClient(sock_path) as client:
    await client.connect()
    identity = await client.get_identity()
```

### Why Unified Client

- **No duplicated parsing code** — all response parsing happens in one place
- **Consistent API** — all interfaces use the same methods and return formats
- **Easier maintenance** — changes to IPC protocol only need to be made once
- **Type safety** — returns typed dataclasses (IdentityInfo, PeerInfo, etc.)

### Interface Mapping

| Interface | Client Used | Notes |
|-----------|-------------|-------|
| CLI | `AsyncPeerDropClient` | Re-exported as `DaemonClient` in `cli/client.py` |
| REST API | `PeerDropClient` (sync) | Thread-safe, one instance per request |
| MCP Server | `PeerDropClient` (sync) | Handles stdio-based MCP protocol |
| GUI | `PeerDropClient` (sync) | Wrapped in `SyncDaemonClient` for Qt compatibility |

---

## 9. MVP Scope

| Milestone | What | Proves |
|---|---|---|
| 1 | py-ipfs-lite Peer initializes, mDNS discovers one peer | Core integration works |
| 2 | Daemon starts, CLI connects via IPC | IPC protocol works |
| 3 | CLI sends small file (< 1MB) to another peer | Full transfer chain works |
| 4 | CLI receives file, shows progress | Event bus works |
| 5 | Transfer failure/error handling | Error mapping works |

### What to Skip in MVP

- REST API, WebSocket, MCP, SDK, GUI — add after CLI validates the engine
- Folder sync, resume support, plugin system — add after basic transfer works
- Persistent block storage — start with MemoryBlockStore

---

## 10. Future Extensions

| Feature | How it fits |
|---|---|
| REST API | Thin HTTP adapter over PeerEngine |
| WebSocket | Subscribes to EventBus, streams events to browser |
| MCP Server | Exposes `send_file`, `list_peers`, `list_transfers` as MCP tools |
| GUI | PySide6 app, connects to daemon via IPC |
| SDK | Python client library, same IPC protocol as CLI |
| Folder sync | New `SyncManager` using py-ipfs-lite's watch + add_file |
| Resume | Track sent CIDs per transfer, re-fetch missing on retry |
| Plugin system | Register custom protocols via `host.set_stream_handler()` |

---

## 11. Key Design Decisions Summary

| Decision | Choice | Rationale |
|---|---|---|
| Single dependency for networking | py-ipfs-lite | Wraps py-libp2p. Provides host, mDNS, bitswap, file chunking. No reason to go lower. |
| Async framework | trio | Native, zero-dependency, good cancellation. |
| Transfer protocol | Stream handshake + bitswap data | Standard IPFS pattern. Bitswap handles reliability, dedup, resume. |
| Daemon architecture | Single process, IPC to clients | All clients share same engine state (same peers, same transfers). |
| IPC transport | Unix sockets + msgpack | Fast, simple, no HTTP overhead. |
| Data models | dataclasses | Built-in, sufficient, no Pydantic dependency. |
| GUI framework | PySide6 | Mature, full-featured, cross-platform. |
