lantern-p2p 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lantern_p2p-1.0.0/.gitignore +33 -0
- lantern_p2p-1.0.0/PKG-INFO +173 -0
- lantern_p2p-1.0.0/README.md +139 -0
- lantern_p2p-1.0.0/lantern/__init__.py +51 -0
- lantern_p2p-1.0.0/lantern/client.py +212 -0
- lantern_p2p-1.0.0/lantern/config.py +23 -0
- lantern_p2p-1.0.0/lantern/discovery.py +176 -0
- lantern_p2p-1.0.0/lantern/main.py +9 -0
- lantern_p2p-1.0.0/lantern/peer.py +206 -0
- lantern_p2p-1.0.0/lantern/protocol.py +93 -0
- lantern_p2p-1.0.0/lantern/server.py +176 -0
- lantern_p2p-1.0.0/lantern/styles/lantern.css +727 -0
- lantern_p2p-1.0.0/lantern/tui.py +774 -0
- lantern_p2p-1.0.0/pyproject.toml +66 -0
- lantern_p2p-1.0.0/requirements.txt +8 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
venv/
|
|
8
|
+
env/
|
|
9
|
+
ENV/
|
|
10
|
+
|
|
11
|
+
# IDE
|
|
12
|
+
.vscode/
|
|
13
|
+
.idea/
|
|
14
|
+
*.swp
|
|
15
|
+
*.swo
|
|
16
|
+
|
|
17
|
+
# OS
|
|
18
|
+
.DS_Store
|
|
19
|
+
Thumbs.db
|
|
20
|
+
|
|
21
|
+
# Shared files (user data)
|
|
22
|
+
shared_files/
|
|
23
|
+
!shared_files/.gitkeep
|
|
24
|
+
|
|
25
|
+
# Logs
|
|
26
|
+
*.log
|
|
27
|
+
|
|
28
|
+
extras/
|
|
29
|
+
|
|
30
|
+
# Build artifacts
|
|
31
|
+
dist/
|
|
32
|
+
build/
|
|
33
|
+
*.egg-info/
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lantern-p2p
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Lantern - A P2P file sharing system with TUI dashboard
|
|
5
|
+
Project-URL: Homepage, https://github.com/shx-dow/lantern
|
|
6
|
+
Project-URL: Documentation, https://github.com/shx-dow/lantern#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/shx-dow/lantern
|
|
8
|
+
Project-URL: Issues, https://github.com/shx-dow/lantern/issues
|
|
9
|
+
Author-email: shx-dow <83426772+shx-dow@users.noreply.github.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: file-sharing,lan,p2p,transfer,tui
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Communications :: File Sharing
|
|
24
|
+
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
|
|
25
|
+
Classifier: Topic :: System :: Networking
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Requires-Dist: psutil>=5.9.0
|
|
28
|
+
Requires-Dist: textual>=0.50.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: black; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Lantern
|
|
36
|
+
|
|
37
|
+
A peer-to-peer file sharing system with a beautiful terminal UI (TUI) dashboard.
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **P2P File Sharing** - Share files directly between computers on the same network
|
|
45
|
+
- **Auto Discovery** - Automatically discovers peers on the LAN via UDP broadcast
|
|
46
|
+
- **Beautiful TUI** - Modern terminal interface built with Textual
|
|
47
|
+
- **Dual Mode** - Both CLI and TUI modes available
|
|
48
|
+
- **Light/Dark Themes** - Toggle between color schemes
|
|
49
|
+
- **Progress Bars** - Visual feedback for large file transfers
|
|
50
|
+
- **Notifications** - Toast notifications for operation completion
|
|
51
|
+
- **Path Safety** - Built-in protection against path traversal attacks
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
### From PyPI (when published)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install lantern-p2p
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### From Source
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/shx-dow/lantern.git
|
|
65
|
+
cd lantern
|
|
66
|
+
pip install -e .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### TUI Mode (Default)
|
|
72
|
+
|
|
73
|
+
Launch the terminal dashboard (beautiful visual interface):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
lantern
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or explicitly:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
lantern --tui
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### CLI Mode
|
|
86
|
+
|
|
87
|
+
Use command-line interface (text-based commands):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
lantern --cli
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Available CLI commands:
|
|
94
|
+
- `peers` - Show discovered peers
|
|
95
|
+
- `list <host[:port]>` - List files on remote peer
|
|
96
|
+
- `download <host[:port]> <file>` - Download a file
|
|
97
|
+
- `upload <host[:port]> <path>` - Upload a file
|
|
98
|
+
- `delete <host[:port]> <file>` - Delete remote file
|
|
99
|
+
- `myfiles` - List your shared files
|
|
100
|
+
- `help` - Show help
|
|
101
|
+
- `quit` - Exit
|
|
102
|
+
|
|
103
|
+
### Custom Port
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
lantern --port 6000
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Key Bindings (TUI Mode)
|
|
110
|
+
|
|
111
|
+
| Key | Action |
|
|
112
|
+
|-----|--------|
|
|
113
|
+
| `F1` | Show help |
|
|
114
|
+
| `F5` | Refresh files |
|
|
115
|
+
| `t` | Toggle theme |
|
|
116
|
+
| `u` | Upload file |
|
|
117
|
+
| `d` | Download file |
|
|
118
|
+
| `x` | Delete file |
|
|
119
|
+
| `Tab` | Cycle focus |
|
|
120
|
+
| `q` | Quit |
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
Shared files are stored in the `shared_files/` directory by default. This directory is created automatically when you first run Lantern.
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.8 or higher
|
|
129
|
+
- textual >= 0.50.0
|
|
130
|
+
- psutil >= 5.9.0
|
|
131
|
+
|
|
132
|
+
## Architecture
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
lantern/
|
|
136
|
+
├── config.py # Configuration constants
|
|
137
|
+
├── protocol.py # Message framing & file transfer protocol
|
|
138
|
+
├── discovery.py # UDP peer discovery
|
|
139
|
+
├── server.py # TCP file server
|
|
140
|
+
├── client.py # TCP client operations
|
|
141
|
+
├── peer.py # CLI entry point
|
|
142
|
+
├── tui.py # Textual TUI dashboard
|
|
143
|
+
├── main.py # Package entry point
|
|
144
|
+
└── styles/ # CSS stylesheets
|
|
145
|
+
└── lantern.css
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
Install with dev dependencies:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[dev]"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Run linting:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
black lantern/
|
|
160
|
+
ruff check lantern/
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT License - see LICENSE file for details.
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
Contributions welcome! Please open an issue or pull request.
|
|
170
|
+
|
|
171
|
+
## Acknowledgments
|
|
172
|
+
|
|
173
|
+
Built with [Textual](https://textual.textualize.io/) for the TUI.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Lantern
|
|
2
|
+
|
|
3
|
+
A peer-to-peer file sharing system with a beautiful terminal UI (TUI) dashboard.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **P2P File Sharing** - Share files directly between computers on the same network
|
|
11
|
+
- **Auto Discovery** - Automatically discovers peers on the LAN via UDP broadcast
|
|
12
|
+
- **Beautiful TUI** - Modern terminal interface built with Textual
|
|
13
|
+
- **Dual Mode** - Both CLI and TUI modes available
|
|
14
|
+
- **Light/Dark Themes** - Toggle between color schemes
|
|
15
|
+
- **Progress Bars** - Visual feedback for large file transfers
|
|
16
|
+
- **Notifications** - Toast notifications for operation completion
|
|
17
|
+
- **Path Safety** - Built-in protection against path traversal attacks
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### From PyPI (when published)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install lantern-p2p
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### From Source
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/shx-dow/lantern.git
|
|
31
|
+
cd lantern
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### TUI Mode (Default)
|
|
38
|
+
|
|
39
|
+
Launch the terminal dashboard (beautiful visual interface):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
lantern
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or explicitly:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
lantern --tui
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### CLI Mode
|
|
52
|
+
|
|
53
|
+
Use command-line interface (text-based commands):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
lantern --cli
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Available CLI commands:
|
|
60
|
+
- `peers` - Show discovered peers
|
|
61
|
+
- `list <host[:port]>` - List files on remote peer
|
|
62
|
+
- `download <host[:port]> <file>` - Download a file
|
|
63
|
+
- `upload <host[:port]> <path>` - Upload a file
|
|
64
|
+
- `delete <host[:port]> <file>` - Delete remote file
|
|
65
|
+
- `myfiles` - List your shared files
|
|
66
|
+
- `help` - Show help
|
|
67
|
+
- `quit` - Exit
|
|
68
|
+
|
|
69
|
+
### Custom Port
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
lantern --port 6000
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Key Bindings (TUI Mode)
|
|
76
|
+
|
|
77
|
+
| Key | Action |
|
|
78
|
+
|-----|--------|
|
|
79
|
+
| `F1` | Show help |
|
|
80
|
+
| `F5` | Refresh files |
|
|
81
|
+
| `t` | Toggle theme |
|
|
82
|
+
| `u` | Upload file |
|
|
83
|
+
| `d` | Download file |
|
|
84
|
+
| `x` | Delete file |
|
|
85
|
+
| `Tab` | Cycle focus |
|
|
86
|
+
| `q` | Quit |
|
|
87
|
+
|
|
88
|
+
## Configuration
|
|
89
|
+
|
|
90
|
+
Shared files are stored in the `shared_files/` directory by default. This directory is created automatically when you first run Lantern.
|
|
91
|
+
|
|
92
|
+
## Requirements
|
|
93
|
+
|
|
94
|
+
- Python 3.8 or higher
|
|
95
|
+
- textual >= 0.50.0
|
|
96
|
+
- psutil >= 5.9.0
|
|
97
|
+
|
|
98
|
+
## Architecture
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
lantern/
|
|
102
|
+
├── config.py # Configuration constants
|
|
103
|
+
├── protocol.py # Message framing & file transfer protocol
|
|
104
|
+
├── discovery.py # UDP peer discovery
|
|
105
|
+
├── server.py # TCP file server
|
|
106
|
+
├── client.py # TCP client operations
|
|
107
|
+
├── peer.py # CLI entry point
|
|
108
|
+
├── tui.py # Textual TUI dashboard
|
|
109
|
+
├── main.py # Package entry point
|
|
110
|
+
└── styles/ # CSS stylesheets
|
|
111
|
+
└── lantern.css
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
Install with dev dependencies:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install -e ".[dev]"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Run linting:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
black lantern/
|
|
126
|
+
ruff check lantern/
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT License - see LICENSE file for details.
|
|
132
|
+
|
|
133
|
+
## Contributing
|
|
134
|
+
|
|
135
|
+
Contributions welcome! Please open an issue or pull request.
|
|
136
|
+
|
|
137
|
+
## Acknowledgments
|
|
138
|
+
|
|
139
|
+
Built with [Textual](https://textual.textualize.io/) for the TUI.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lantern - P2P File Sharing System
|
|
3
|
+
|
|
4
|
+
A peer-to-peer file sharing application with a beautiful TUI dashboard.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0.0"
|
|
8
|
+
__author__ = "shx-dow"
|
|
9
|
+
|
|
10
|
+
from .config import (
|
|
11
|
+
TCP_PORT,
|
|
12
|
+
UDP_PORT,
|
|
13
|
+
BUFFER_SIZE,
|
|
14
|
+
BROADCAST_INTERVAL,
|
|
15
|
+
PEER_TIMEOUT,
|
|
16
|
+
SEPARATOR,
|
|
17
|
+
SHARED_DIR,
|
|
18
|
+
PEER_ID,
|
|
19
|
+
)
|
|
20
|
+
from .discovery import PeerDiscovery
|
|
21
|
+
from .server import FileServer
|
|
22
|
+
from .client import (
|
|
23
|
+
fetch_file_list,
|
|
24
|
+
do_download,
|
|
25
|
+
do_upload,
|
|
26
|
+
do_delete,
|
|
27
|
+
format_size,
|
|
28
|
+
)
|
|
29
|
+
from .protocol import send_msg, recv_msg, send_file, recv_file
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"TCP_PORT",
|
|
33
|
+
"UDP_PORT",
|
|
34
|
+
"BUFFER_SIZE",
|
|
35
|
+
"BROADCAST_INTERVAL",
|
|
36
|
+
"PEER_TIMEOUT",
|
|
37
|
+
"SEPARATOR",
|
|
38
|
+
"SHARED_DIR",
|
|
39
|
+
"PEER_ID",
|
|
40
|
+
"PeerDiscovery",
|
|
41
|
+
"FileServer",
|
|
42
|
+
"fetch_file_list",
|
|
43
|
+
"do_download",
|
|
44
|
+
"do_upload",
|
|
45
|
+
"do_delete",
|
|
46
|
+
"format_size",
|
|
47
|
+
"send_msg",
|
|
48
|
+
"recv_msg",
|
|
49
|
+
"send_file",
|
|
50
|
+
"recv_file",
|
|
51
|
+
]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TCP client — connects to a remote peer and performs file operations.
|
|
3
|
+
|
|
4
|
+
Each operation opens a fresh TCP connection, sends a command, processes the
|
|
5
|
+
response, and closes the connection. This keeps the protocol stateless and
|
|
6
|
+
simple.
|
|
7
|
+
|
|
8
|
+
Two API layers:
|
|
9
|
+
- Core functions (fetch_*) return structured data for the TUI.
|
|
10
|
+
- CLI wrappers (list_files, download_file, …) print results for the CLI.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import socket
|
|
15
|
+
|
|
16
|
+
from .config import SEPARATOR, BUFFER_SIZE, SHARED_DIR
|
|
17
|
+
from .protocol import send_msg, recv_msg, recv_file
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _connect(host: str, port: int) -> socket.socket:
|
|
21
|
+
"""Open a TCP connection to a remote peer."""
|
|
22
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
23
|
+
sock.settimeout(10)
|
|
24
|
+
sock.connect((host, port))
|
|
25
|
+
return sock
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_size(size_bytes: int | float) -> str:
|
|
29
|
+
"""Human-readable file size."""
|
|
30
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
31
|
+
if size_bytes < 1024:
|
|
32
|
+
return f"{size_bytes:.1f} {unit}"
|
|
33
|
+
size_bytes /= 1024
|
|
34
|
+
return f"{size_bytes:.1f} TB"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ======================================================================
|
|
38
|
+
# Core API — returns structured data (used by TUI and CLI wrappers)
|
|
39
|
+
# ======================================================================
|
|
40
|
+
|
|
41
|
+
def fetch_file_list(host: str, port: int) -> list[dict]:
|
|
42
|
+
"""
|
|
43
|
+
Fetch the file listing from a remote peer.
|
|
44
|
+
|
|
45
|
+
Returns a list of dicts: [{"name": str, "size": int}, ...]
|
|
46
|
+
Raises RuntimeError on protocol errors.
|
|
47
|
+
"""
|
|
48
|
+
sock = _connect(host, port)
|
|
49
|
+
try:
|
|
50
|
+
send_msg(sock, "LIST")
|
|
51
|
+
response = recv_msg(sock)
|
|
52
|
+
if response is None:
|
|
53
|
+
raise RuntimeError("No response from peer")
|
|
54
|
+
|
|
55
|
+
parts = response.split(SEPARATOR, 1)
|
|
56
|
+
if parts[0] != "OK":
|
|
57
|
+
raise RuntimeError(parts[1] if len(parts) > 1 else "Unknown error")
|
|
58
|
+
|
|
59
|
+
listing = parts[1] if len(parts) > 1 else ""
|
|
60
|
+
if not listing.strip():
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
files = []
|
|
64
|
+
for line in listing.split("\n"):
|
|
65
|
+
if SEPARATOR in line:
|
|
66
|
+
name, size_str = line.split(SEPARATOR, 1)
|
|
67
|
+
files.append({"name": name, "size": int(size_str)})
|
|
68
|
+
return files
|
|
69
|
+
finally:
|
|
70
|
+
sock.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def do_download(host: str, port: int, filename: str) -> tuple[str, int]:
|
|
74
|
+
"""
|
|
75
|
+
Download a file from a remote peer.
|
|
76
|
+
|
|
77
|
+
Returns (destination_path, bytes_received).
|
|
78
|
+
Raises RuntimeError on failure.
|
|
79
|
+
"""
|
|
80
|
+
sock = _connect(host, port)
|
|
81
|
+
try:
|
|
82
|
+
send_msg(sock, f"DOWNLOAD{SEPARATOR}{filename}")
|
|
83
|
+
response = recv_msg(sock)
|
|
84
|
+
if response is None:
|
|
85
|
+
raise RuntimeError("No response from peer")
|
|
86
|
+
|
|
87
|
+
if response.startswith("ERROR"):
|
|
88
|
+
parts = response.split(SEPARATOR, 1)
|
|
89
|
+
raise RuntimeError(parts[1] if len(parts) > 1 else "Unknown error")
|
|
90
|
+
|
|
91
|
+
os.makedirs(SHARED_DIR, exist_ok=True)
|
|
92
|
+
dest = os.path.join(SHARED_DIR, filename)
|
|
93
|
+
received = recv_file(sock, dest)
|
|
94
|
+
return dest, received
|
|
95
|
+
finally:
|
|
96
|
+
sock.close()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def do_upload(host: str, port: int, filepath: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Upload a local file to a remote peer.
|
|
102
|
+
|
|
103
|
+
Returns a success message string.
|
|
104
|
+
Raises RuntimeError on failure.
|
|
105
|
+
"""
|
|
106
|
+
if not os.path.isfile(filepath):
|
|
107
|
+
raise RuntimeError(f"Local file not found: {filepath}")
|
|
108
|
+
|
|
109
|
+
filename = os.path.basename(filepath)
|
|
110
|
+
filesize = os.path.getsize(filepath)
|
|
111
|
+
|
|
112
|
+
sock = _connect(host, port)
|
|
113
|
+
try:
|
|
114
|
+
send_msg(sock, f"UPLOAD{SEPARATOR}{filename}{SEPARATOR}{filesize}")
|
|
115
|
+
|
|
116
|
+
response = recv_msg(sock)
|
|
117
|
+
if response is None or not response.startswith("OK"):
|
|
118
|
+
parts = (response or "").split(SEPARATOR, 1)
|
|
119
|
+
raise RuntimeError(f"Peer rejected upload: {parts[1] if len(parts) > 1 else 'unknown'}")
|
|
120
|
+
|
|
121
|
+
sent = 0
|
|
122
|
+
with open(filepath, "rb") as f:
|
|
123
|
+
while sent < filesize:
|
|
124
|
+
chunk = f.read(BUFFER_SIZE)
|
|
125
|
+
if not chunk:
|
|
126
|
+
break
|
|
127
|
+
sock.sendall(chunk)
|
|
128
|
+
sent += len(chunk)
|
|
129
|
+
|
|
130
|
+
confirm = recv_msg(sock)
|
|
131
|
+
if confirm and confirm.startswith("OK"):
|
|
132
|
+
parts = confirm.split(SEPARATOR, 1)
|
|
133
|
+
return parts[1] if len(parts) > 1 else "Upload complete"
|
|
134
|
+
else:
|
|
135
|
+
parts = (confirm or "").split(SEPARATOR, 1)
|
|
136
|
+
raise RuntimeError(f"Upload issue: {parts[1] if len(parts) > 1 else 'unknown'}")
|
|
137
|
+
finally:
|
|
138
|
+
sock.close()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def do_delete(host: str, port: int, filename: str) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Request deletion of a file on a remote peer.
|
|
144
|
+
|
|
145
|
+
Returns a success message string.
|
|
146
|
+
Raises RuntimeError on failure.
|
|
147
|
+
"""
|
|
148
|
+
sock = _connect(host, port)
|
|
149
|
+
try:
|
|
150
|
+
send_msg(sock, f"DELETE{SEPARATOR}{filename}")
|
|
151
|
+
response = recv_msg(sock)
|
|
152
|
+
if response is None:
|
|
153
|
+
raise RuntimeError("No response from peer")
|
|
154
|
+
|
|
155
|
+
parts = response.split(SEPARATOR, 1)
|
|
156
|
+
msg = parts[1] if len(parts) > 1 else ""
|
|
157
|
+
|
|
158
|
+
if parts[0] == "OK":
|
|
159
|
+
return msg
|
|
160
|
+
else:
|
|
161
|
+
raise RuntimeError(msg)
|
|
162
|
+
finally:
|
|
163
|
+
sock.close()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ======================================================================
|
|
167
|
+
# CLI wrappers — print results (used by peer.py CLI mode)
|
|
168
|
+
# ======================================================================
|
|
169
|
+
|
|
170
|
+
def list_files(host: str, port: int) -> None:
|
|
171
|
+
"""Print the file listing from a remote peer."""
|
|
172
|
+
try:
|
|
173
|
+
files = fetch_file_list(host, port)
|
|
174
|
+
except RuntimeError as e:
|
|
175
|
+
print(f" [!] {e}")
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if not files:
|
|
179
|
+
print(" (no files)")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
print(f" {'Filename':<40} {'Size':>12}")
|
|
183
|
+
print(f" {'-'*40} {'-'*12}")
|
|
184
|
+
for f in files:
|
|
185
|
+
print(f" {f['name']:<40} {format_size(f['size']):>12}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def download_file(host: str, port: int, filename: str) -> None:
|
|
189
|
+
"""Download a file and print the result."""
|
|
190
|
+
try:
|
|
191
|
+
dest, received = do_download(host, port, filename)
|
|
192
|
+
print(f" Downloaded {filename} ({format_size(received)}) -> {dest}")
|
|
193
|
+
except RuntimeError as e:
|
|
194
|
+
print(f" [!] {e}")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def upload_file(host: str, port: int, filepath: str) -> None:
|
|
198
|
+
"""Upload a file and print the result."""
|
|
199
|
+
try:
|
|
200
|
+
msg = do_upload(host, port, filepath)
|
|
201
|
+
print(f" {msg}")
|
|
202
|
+
except RuntimeError as e:
|
|
203
|
+
print(f" [!] {e}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def delete_file(host: str, port: int, filename: str) -> None:
|
|
207
|
+
"""Delete a remote file and print the result."""
|
|
208
|
+
try:
|
|
209
|
+
msg = do_delete(host, port, filename)
|
|
210
|
+
print(f" {msg}")
|
|
211
|
+
except RuntimeError as e:
|
|
212
|
+
print(f" [!] {e}")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration constants for the P2P file sharing system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
# --- Networking ---
|
|
9
|
+
TCP_PORT = 5000 # Default TCP port for file operations
|
|
10
|
+
UDP_PORT = 5001 # UDP port for peer discovery broadcasts
|
|
11
|
+
BUFFER_SIZE = 4096 # Chunk size (bytes) for file transfer
|
|
12
|
+
BROADCAST_INTERVAL = 5 # Seconds between UDP discovery beacons
|
|
13
|
+
PEER_TIMEOUT = 15 # Seconds before a peer is considered offline
|
|
14
|
+
|
|
15
|
+
# --- Protocol ---
|
|
16
|
+
SEPARATOR = "<SEP>" # Delimiter used in protocol messages
|
|
17
|
+
|
|
18
|
+
# --- File Storage ---
|
|
19
|
+
SHARED_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "shared_files")
|
|
20
|
+
|
|
21
|
+
# --- Identity ---
|
|
22
|
+
# Each peer gets a unique ID at startup so it can ignore its own broadcasts
|
|
23
|
+
PEER_ID = str(uuid.uuid4())[:8]
|