tempspace-cli 1.2.2__tar.gz → 1.2.4__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.
- tempspace_cli-1.2.4/PKG-INFO +189 -0
- tempspace_cli-1.2.4/README.md +174 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/cli/tempspace.py +157 -66
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/pyproject.toml +3 -3
- tempspace_cli-1.2.4/tempspace_cli.egg-info/PKG-INFO +189 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tempspace_cli.egg-info/requires.txt +2 -2
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tests/test_main.py +306 -5
- tempspace_cli-1.2.2/PKG-INFO +0 -108
- tempspace_cli-1.2.2/README.md +0 -93
- tempspace_cli-1.2.2/tempspace_cli.egg-info/PKG-INFO +0 -108
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/cli/__init__.py +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/setup.cfg +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tempspace_cli.egg-info/SOURCES.txt +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tempspace_cli.egg-info/dependency_links.txt +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tempspace_cli.egg-info/entry_points.txt +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tempspace_cli.egg-info/top_level.txt +0 -0
- {tempspace_cli-1.2.2 → tempspace_cli-1.2.4}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tempspace-cli
|
|
3
|
+
Version: 1.2.4
|
|
4
|
+
Summary: A command-line tool for uploading files to Tempspace.
|
|
5
|
+
Author-email: Tempspace <mcbplay1@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests==2.32.5
|
|
12
|
+
Requires-Dist: requests-toolbelt==1.0.0
|
|
13
|
+
Requires-Dist: rich==14.2.0
|
|
14
|
+
Requires-Dist: qrcode==8.2
|
|
15
|
+
|
|
16
|
+
# 🚀 Tempspace
|
|
17
|
+
|
|
18
|
+
Tempspace is a self-hostable, terminal-themed file sharing service. It allows you to quickly upload files and share them via a link, with features like password protection, one-time downloads, and automatic expiration.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
## ✨ Core Features
|
|
23
|
+
|
|
24
|
+
- **Seamless Uploading:** Drag-and-drop web interface, cURL, or a dedicated CLI tool.
|
|
25
|
+
- **Chunked Uploads:** Robust support for large files (default 4GB, configurable) via chunking.
|
|
26
|
+
- **Security Focused:**
|
|
27
|
+
- **Password Protection:** Secure individual files with a password.
|
|
28
|
+
- **One-Time Downloads:** Links can be configured to expire after the first download.
|
|
29
|
+
- **Rate Limiting:** Built-in protection against abuse.
|
|
30
|
+
- **Efficient Storage:** Files with identical content are de-duplicated to save space.
|
|
31
|
+
- **Flexible Expiration:** Set custom expiration times from hours to days.
|
|
32
|
+
- **Modern UI:**
|
|
33
|
+
- **Terminal-Inspired Design:** A clean, developer-focused interface.
|
|
34
|
+
- **QR Code Generation:** Instantly generate QR codes for mobile sharing.
|
|
35
|
+
- **Local Upload History:** Your browser remembers your recent uploads.
|
|
36
|
+
- **Automatic Cleanup:** The server periodically cleans up expired files automatically.
|
|
37
|
+
|
|
38
|
+
## 🚀 Getting Started
|
|
39
|
+
|
|
40
|
+
There are three primary ways to use Tempspace.
|
|
41
|
+
|
|
42
|
+
### 1. Web Interface (The Easy Way)
|
|
43
|
+
|
|
44
|
+
1. Open the service URL (e.g., `https://tempspace.fly.dev/` or your self-hosted instance) in your browser.
|
|
45
|
+
2. Drag and drop your file(s) or click to browse.
|
|
46
|
+
3. Configure options like expiration time, password, or one-time download.
|
|
47
|
+
4. Click "Upload" and share the generated link.
|
|
48
|
+
|
|
49
|
+
### 2. cURL (From the Terminal)
|
|
50
|
+
|
|
51
|
+
Use `cURL` to upload files directly from your command line.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Basic upload (expires in 24 hours)
|
|
55
|
+
curl -F "file=@/path/to/your/file.txt" https://tempspace.fly.dev/upload
|
|
56
|
+
|
|
57
|
+
# Set a custom expiration time (e.g., 7 days) and password
|
|
58
|
+
curl -F "file=@/path/to/secret.txt" \
|
|
59
|
+
-F "hours=168" \
|
|
60
|
+
-F "password=secret123" \
|
|
61
|
+
https://tempspace.fly.dev/upload
|
|
62
|
+
|
|
63
|
+
# Create a one-time download link
|
|
64
|
+
curl -F "file=@/path/to/onetime.zip" \
|
|
65
|
+
-F "one_time=true" \
|
|
66
|
+
https://tempspace.fly.dev/upload
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Official CLI Tool
|
|
70
|
+
|
|
71
|
+
For the best terminal experience, install the official CLI tool from PyPI.
|
|
72
|
+
|
|
73
|
+
#### Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install tempspace-cli
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Usage
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Interactive mode (prompts for all options)
|
|
83
|
+
tempspace --it
|
|
84
|
+
|
|
85
|
+
# Basic upload (expires in 24 hours)
|
|
86
|
+
tempspace /path/to/document.pdf
|
|
87
|
+
|
|
88
|
+
# Set expiration to 7 days and add a password
|
|
89
|
+
tempspace /path/to/archive.zip -t 7d -p "secure_password"
|
|
90
|
+
|
|
91
|
+
# Display a QR code in the terminal after upload
|
|
92
|
+
tempspace /path/to/image.png --qr
|
|
93
|
+
|
|
94
|
+
# Point to a self-hosted server
|
|
95
|
+
tempspace /path/to/file.txt --url http://localhost:8000
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 🔧 Self-Hosting
|
|
99
|
+
|
|
100
|
+
You can easily run your own instance of Tempspace using Docker or by running the source code directly.
|
|
101
|
+
|
|
102
|
+
### Method 1: Using Docker (Recommended)
|
|
103
|
+
|
|
104
|
+
1. **Clone the repository:**
|
|
105
|
+
```bash
|
|
106
|
+
git clone https://github.com/Bartixxx32/tempspace.git
|
|
107
|
+
cd tempspace
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
2. **Build the Docker image:**
|
|
111
|
+
```bash
|
|
112
|
+
docker build -t tempspace-app .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
3. **Run the container:**
|
|
116
|
+
Create a `.env` file to configure your admin password.
|
|
117
|
+
```bash
|
|
118
|
+
cp .env.example .env
|
|
119
|
+
nano .env # Set your ADMIN_PASS
|
|
120
|
+
```
|
|
121
|
+
Then, run the container, mounting the environment file and a volume for persistent storage.
|
|
122
|
+
```bash
|
|
123
|
+
docker run -d \
|
|
124
|
+
-p 8000:8000 \
|
|
125
|
+
--name tempspace \
|
|
126
|
+
--env-file .env \
|
|
127
|
+
-v $(pwd)/uploads:/app/uploads \
|
|
128
|
+
tempspace-app
|
|
129
|
+
```
|
|
130
|
+
Your Tempspace instance will be available at `http://localhost:8000`.
|
|
131
|
+
|
|
132
|
+
### Method 2: Running from Source
|
|
133
|
+
|
|
134
|
+
1. **Clone the repository:**
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/Bartixxx32/tempspace.git
|
|
137
|
+
cd tempspace
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
2. **Install dependencies:**
|
|
141
|
+
It's recommended to use a virtual environment.
|
|
142
|
+
```bash
|
|
143
|
+
python -m venv venv
|
|
144
|
+
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
|
|
145
|
+
pip install -r requirements.txt
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
3. **Configure:**
|
|
149
|
+
Copy the example environment file and set your admin password.
|
|
150
|
+
```bash
|
|
151
|
+
cp .env.example .env
|
|
152
|
+
nano .env
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
4. **Run the server:**
|
|
156
|
+
```bash
|
|
157
|
+
python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## ⚙️ Configuration (Environment Variables)
|
|
161
|
+
|
|
162
|
+
Configure your Tempspace instance by creating a `.env` file in the project root.
|
|
163
|
+
|
|
164
|
+
| Variable | Description | Default |
|
|
165
|
+
| -------------------- | ------------------------------------------------------------------------ | ------------------------ |
|
|
166
|
+
| `ADMIN_USER` | Username for accessing debug/admin endpoints. | `admin` |
|
|
167
|
+
| `ADMIN_PASS` | **Required.** Password for the admin user. The app will not start without this. | `changeme123` |
|
|
168
|
+
| `MAX_FILE_SIZE` | Maximum file size in bytes. | `4294967296` (4GB) |
|
|
169
|
+
| `RATE_LIMIT_UPLOADS` | Max uploads per IP per hour. | `10` |
|
|
170
|
+
| `RATE_LIMIT_DOWNLOADS` | Max downloads per IP per hour. | `100` |
|
|
171
|
+
| `RATE_LIMIT_WINDOW` | The rate limit time window in seconds. | `3600` (1 hour) |
|
|
172
|
+
|
|
173
|
+
## 🛠️ API & Admin Endpoints
|
|
174
|
+
|
|
175
|
+
- `/upload`: The primary endpoint for `curl` and simple POST request uploads.
|
|
176
|
+
- `/upload/initiate`, `/upload/chunk`, `/upload/finalize`: Endpoints for the chunked upload flow used by the web UI and CLI.
|
|
177
|
+
- `/health`: A public health check endpoint.
|
|
178
|
+
- `/debug/stats`: (Admin-only) View detailed statistics of all stored files.
|
|
179
|
+
- `/debug/wipe`: (Admin-only) **Destructive.** Deletes all files and metadata.
|
|
180
|
+
|
|
181
|
+
Access admin endpoints by providing the admin credentials via HTTP Basic Authentication.
|
|
182
|
+
|
|
183
|
+
## 🤝 Contributing
|
|
184
|
+
|
|
185
|
+
Contributions are welcome! Please fork the repository, make your changes, and submit a pull request.
|
|
186
|
+
|
|
187
|
+
## 📝 License
|
|
188
|
+
|
|
189
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# 🚀 Tempspace
|
|
2
|
+
|
|
3
|
+
Tempspace is a self-hostable, terminal-themed file sharing service. It allows you to quickly upload files and share them via a link, with features like password protection, one-time downloads, and automatic expiration.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## ✨ Core Features
|
|
8
|
+
|
|
9
|
+
- **Seamless Uploading:** Drag-and-drop web interface, cURL, or a dedicated CLI tool.
|
|
10
|
+
- **Chunked Uploads:** Robust support for large files (default 4GB, configurable) via chunking.
|
|
11
|
+
- **Security Focused:**
|
|
12
|
+
- **Password Protection:** Secure individual files with a password.
|
|
13
|
+
- **One-Time Downloads:** Links can be configured to expire after the first download.
|
|
14
|
+
- **Rate Limiting:** Built-in protection against abuse.
|
|
15
|
+
- **Efficient Storage:** Files with identical content are de-duplicated to save space.
|
|
16
|
+
- **Flexible Expiration:** Set custom expiration times from hours to days.
|
|
17
|
+
- **Modern UI:**
|
|
18
|
+
- **Terminal-Inspired Design:** A clean, developer-focused interface.
|
|
19
|
+
- **QR Code Generation:** Instantly generate QR codes for mobile sharing.
|
|
20
|
+
- **Local Upload History:** Your browser remembers your recent uploads.
|
|
21
|
+
- **Automatic Cleanup:** The server periodically cleans up expired files automatically.
|
|
22
|
+
|
|
23
|
+
## 🚀 Getting Started
|
|
24
|
+
|
|
25
|
+
There are three primary ways to use Tempspace.
|
|
26
|
+
|
|
27
|
+
### 1. Web Interface (The Easy Way)
|
|
28
|
+
|
|
29
|
+
1. Open the service URL (e.g., `https://tempspace.fly.dev/` or your self-hosted instance) in your browser.
|
|
30
|
+
2. Drag and drop your file(s) or click to browse.
|
|
31
|
+
3. Configure options like expiration time, password, or one-time download.
|
|
32
|
+
4. Click "Upload" and share the generated link.
|
|
33
|
+
|
|
34
|
+
### 2. cURL (From the Terminal)
|
|
35
|
+
|
|
36
|
+
Use `cURL` to upload files directly from your command line.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Basic upload (expires in 24 hours)
|
|
40
|
+
curl -F "file=@/path/to/your/file.txt" https://tempspace.fly.dev/upload
|
|
41
|
+
|
|
42
|
+
# Set a custom expiration time (e.g., 7 days) and password
|
|
43
|
+
curl -F "file=@/path/to/secret.txt" \
|
|
44
|
+
-F "hours=168" \
|
|
45
|
+
-F "password=secret123" \
|
|
46
|
+
https://tempspace.fly.dev/upload
|
|
47
|
+
|
|
48
|
+
# Create a one-time download link
|
|
49
|
+
curl -F "file=@/path/to/onetime.zip" \
|
|
50
|
+
-F "one_time=true" \
|
|
51
|
+
https://tempspace.fly.dev/upload
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Official CLI Tool
|
|
55
|
+
|
|
56
|
+
For the best terminal experience, install the official CLI tool from PyPI.
|
|
57
|
+
|
|
58
|
+
#### Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install tempspace-cli
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Usage
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Interactive mode (prompts for all options)
|
|
68
|
+
tempspace --it
|
|
69
|
+
|
|
70
|
+
# Basic upload (expires in 24 hours)
|
|
71
|
+
tempspace /path/to/document.pdf
|
|
72
|
+
|
|
73
|
+
# Set expiration to 7 days and add a password
|
|
74
|
+
tempspace /path/to/archive.zip -t 7d -p "secure_password"
|
|
75
|
+
|
|
76
|
+
# Display a QR code in the terminal after upload
|
|
77
|
+
tempspace /path/to/image.png --qr
|
|
78
|
+
|
|
79
|
+
# Point to a self-hosted server
|
|
80
|
+
tempspace /path/to/file.txt --url http://localhost:8000
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 🔧 Self-Hosting
|
|
84
|
+
|
|
85
|
+
You can easily run your own instance of Tempspace using Docker or by running the source code directly.
|
|
86
|
+
|
|
87
|
+
### Method 1: Using Docker (Recommended)
|
|
88
|
+
|
|
89
|
+
1. **Clone the repository:**
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/Bartixxx32/tempspace.git
|
|
92
|
+
cd tempspace
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
2. **Build the Docker image:**
|
|
96
|
+
```bash
|
|
97
|
+
docker build -t tempspace-app .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
3. **Run the container:**
|
|
101
|
+
Create a `.env` file to configure your admin password.
|
|
102
|
+
```bash
|
|
103
|
+
cp .env.example .env
|
|
104
|
+
nano .env # Set your ADMIN_PASS
|
|
105
|
+
```
|
|
106
|
+
Then, run the container, mounting the environment file and a volume for persistent storage.
|
|
107
|
+
```bash
|
|
108
|
+
docker run -d \
|
|
109
|
+
-p 8000:8000 \
|
|
110
|
+
--name tempspace \
|
|
111
|
+
--env-file .env \
|
|
112
|
+
-v $(pwd)/uploads:/app/uploads \
|
|
113
|
+
tempspace-app
|
|
114
|
+
```
|
|
115
|
+
Your Tempspace instance will be available at `http://localhost:8000`.
|
|
116
|
+
|
|
117
|
+
### Method 2: Running from Source
|
|
118
|
+
|
|
119
|
+
1. **Clone the repository:**
|
|
120
|
+
```bash
|
|
121
|
+
git clone https://github.com/Bartixxx32/tempspace.git
|
|
122
|
+
cd tempspace
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
2. **Install dependencies:**
|
|
126
|
+
It's recommended to use a virtual environment.
|
|
127
|
+
```bash
|
|
128
|
+
python -m venv venv
|
|
129
|
+
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
|
|
130
|
+
pip install -r requirements.txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. **Configure:**
|
|
134
|
+
Copy the example environment file and set your admin password.
|
|
135
|
+
```bash
|
|
136
|
+
cp .env.example .env
|
|
137
|
+
nano .env
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
4. **Run the server:**
|
|
141
|
+
```bash
|
|
142
|
+
python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## ⚙️ Configuration (Environment Variables)
|
|
146
|
+
|
|
147
|
+
Configure your Tempspace instance by creating a `.env` file in the project root.
|
|
148
|
+
|
|
149
|
+
| Variable | Description | Default |
|
|
150
|
+
| -------------------- | ------------------------------------------------------------------------ | ------------------------ |
|
|
151
|
+
| `ADMIN_USER` | Username for accessing debug/admin endpoints. | `admin` |
|
|
152
|
+
| `ADMIN_PASS` | **Required.** Password for the admin user. The app will not start without this. | `changeme123` |
|
|
153
|
+
| `MAX_FILE_SIZE` | Maximum file size in bytes. | `4294967296` (4GB) |
|
|
154
|
+
| `RATE_LIMIT_UPLOADS` | Max uploads per IP per hour. | `10` |
|
|
155
|
+
| `RATE_LIMIT_DOWNLOADS` | Max downloads per IP per hour. | `100` |
|
|
156
|
+
| `RATE_LIMIT_WINDOW` | The rate limit time window in seconds. | `3600` (1 hour) |
|
|
157
|
+
|
|
158
|
+
## 🛠️ API & Admin Endpoints
|
|
159
|
+
|
|
160
|
+
- `/upload`: The primary endpoint for `curl` and simple POST request uploads.
|
|
161
|
+
- `/upload/initiate`, `/upload/chunk`, `/upload/finalize`: Endpoints for the chunked upload flow used by the web UI and CLI.
|
|
162
|
+
- `/health`: A public health check endpoint.
|
|
163
|
+
- `/debug/stats`: (Admin-only) View detailed statistics of all stored files.
|
|
164
|
+
- `/debug/wipe`: (Admin-only) **Destructive.** Deletes all files and metadata.
|
|
165
|
+
|
|
166
|
+
Access admin endpoints by providing the admin credentials via HTTP Basic Authentication.
|
|
167
|
+
|
|
168
|
+
## 🤝 Contributing
|
|
169
|
+
|
|
170
|
+
Contributions are welcome! Please fork the repository, make your changes, and submit a pull request.
|
|
171
|
+
|
|
172
|
+
## 📝 License
|
|
173
|
+
|
|
174
|
+
This project is licensed under the MIT License.
|
|
@@ -21,9 +21,16 @@ DEFAULT_SERVER_URL = "https://tempspace.fly.dev/"
|
|
|
21
21
|
CHUNK_SIZE = 1024 * 1024 # 1MB
|
|
22
22
|
|
|
23
23
|
def parse_time(time_str: str) -> int:
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"""Parses a user-provided time string into a total number of hours.
|
|
25
|
+
|
|
26
|
+
Supports strings ending in 'd' for days, 'h' for hours, or a plain
|
|
27
|
+
number for hours.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
time_str: The time string to parse (e.g., '7d', '24h', '360').
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The total number of hours as an integer, or None if parsing fails.
|
|
27
34
|
"""
|
|
28
35
|
time_str = time_str.lower().strip()
|
|
29
36
|
if time_str.endswith('d'):
|
|
@@ -44,7 +51,14 @@ def parse_time(time_str: str) -> int:
|
|
|
44
51
|
return None
|
|
45
52
|
|
|
46
53
|
def format_size(size_bytes: int) -> str:
|
|
47
|
-
"""Converts a size in bytes
|
|
54
|
+
"""Converts a file size in bytes into a human-readable string.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
size_bytes: The size of the file in bytes.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A formatted string representing the size (e.g., "1.23 MB").
|
|
61
|
+
"""
|
|
48
62
|
if size_bytes == 0:
|
|
49
63
|
return "0B"
|
|
50
64
|
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
@@ -53,64 +67,61 @@ def format_size(size_bytes: int) -> str:
|
|
|
53
67
|
s = round(size_bytes / p, 2)
|
|
54
68
|
return f"{s} {size_name[i]}"
|
|
55
69
|
|
|
56
|
-
def main():
|
|
57
|
-
"""Main function to handle argument parsing and file upload."""
|
|
58
|
-
console = Console()
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
def calculate_file_hash(filepath: str) -> str:
|
|
72
|
+
"""Calculates the SHA256 hash of a file.
|
|
62
73
|
|
|
74
|
+
Args:
|
|
75
|
+
filepath: The path to the file.
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
77
|
+
Returns:
|
|
78
|
+
The hex digest of the SHA256 hash.
|
|
79
|
+
"""
|
|
80
|
+
sha256_hash = hashlib.sha256()
|
|
81
|
+
with open(filepath, "rb") as f:
|
|
82
|
+
# Read and update hash in chunks of 4K
|
|
83
|
+
for byte_block in iter(lambda: f.read(4096), b""):
|
|
84
|
+
sha256_hash.update(byte_block)
|
|
85
|
+
return sha256_hash.hexdigest()
|
|
68
86
|
|
|
69
|
-
parser.add_argument("filepath", nargs='?', default=None, help="The path to the file you want to upload.")
|
|
70
|
-
parser.add_argument("-t", "--time", type=str, default='24', help="Set the file's expiration time. Examples: '24h', '7d', '360' (hours).\nDefault: '24' (24 hours).")
|
|
71
|
-
parser.add_argument("-p", "--password", type=str, help="Protect the file with a password.")
|
|
72
|
-
parser.add_argument("--one-time", action="store_true", help="The file will be deleted after the first download.")
|
|
73
|
-
parser.add_argument("--url", type=str, default=os.environ.get("TEMPSPACE_URL", DEFAULT_SERVER_URL), help=f"The URL of the Tempspace server.\nCan also be set with the TEMPSPACE_URL environment variable.\nDefault: {DEFAULT_SERVER_URL}")
|
|
74
|
-
parser.add_argument("--qr", action="store_true", help="Display a QR code of the download link.")
|
|
75
|
-
parser.add_argument("--it", action="store_true", help="Enable interactive mode.")
|
|
76
|
-
|
|
77
|
-
args = parser.parse_args()
|
|
78
|
-
|
|
79
|
-
# --- Interactive Mode ---
|
|
80
|
-
if args.it:
|
|
81
|
-
args.filepath = Prompt.ask("Enter the path to the file you want to upload")
|
|
82
|
-
args.time = Prompt.ask("Set the file's expiration time (e.g., '24h', '7d')", default='24')
|
|
83
|
-
args.password = Prompt.ask("Protect the file with a password?", default=None, password=True)
|
|
84
|
-
args.one_time = Confirm.ask("Delete the file after the first download?", default=False)
|
|
85
|
-
args.qr = Confirm.ask("Display a QR code of the download link?", default=False)
|
|
86
87
|
|
|
88
|
+
def upload_file(console, filepath, hours, password, one_time, qr, url):
|
|
89
|
+
"""Handles the upload of a single file.
|
|
87
90
|
|
|
91
|
+
Args:
|
|
92
|
+
console: The Rich console object for output.
|
|
93
|
+
filepath (str): The path to the file to upload.
|
|
94
|
+
hours (int): The number of hours until the file expires.
|
|
95
|
+
password (str): The password for the file.
|
|
96
|
+
one_time (bool): Whether the file should be a one-time download.
|
|
97
|
+
qr (bool): Whether to display a QR code for the download link.
|
|
98
|
+
url (str): The base URL of the Tempspace server.
|
|
99
|
+
"""
|
|
88
100
|
# --- Validate Inputs ---
|
|
89
|
-
if not
|
|
90
|
-
console.print(Panel(f"[bold red]Error:[/] File not found at '{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
hours = parse_time(args.time)
|
|
94
|
-
if hours is None:
|
|
95
|
-
console.print(Panel(f"[bold red]Error:[/] Invalid time format '{args.time}'. Use formats like '24h', '7d', or '360'.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
96
|
-
sys.exit(1)
|
|
101
|
+
if not os.path.isfile(filepath):
|
|
102
|
+
console.print(Panel(f"[bold red]Error:[/] File not found at '{filepath}'", title="[bold red]Error[/bold red]", border_style="red"))
|
|
103
|
+
return # Return instead of exiting to allow other files to be processed
|
|
97
104
|
|
|
98
105
|
# --- Display File Details ---
|
|
99
106
|
table = Table(title="File Details", show_header=False, box=box.ROUNDED, border_style="cyan")
|
|
100
107
|
table.add_column("Field", style="bold")
|
|
101
108
|
table.add_column("Value")
|
|
102
|
-
table.add_row("File Name", os.path.basename(
|
|
103
|
-
table.add_row("File Size", format_size(os.path.getsize(
|
|
109
|
+
table.add_row("File Name", os.path.basename(filepath))
|
|
110
|
+
table.add_row("File Size", format_size(os.path.getsize(filepath)))
|
|
104
111
|
table.add_row("Expiration", f"{hours} hours")
|
|
105
|
-
table.add_row("Password", "[green]Yes[/green]" if
|
|
106
|
-
table.add_row("One-Time Download", "[green]Yes[/green]" if
|
|
107
|
-
console.print(table)
|
|
108
|
-
|
|
112
|
+
table.add_row("Password", "[green]Yes[/green]" if password else "[red]No[/red]")
|
|
113
|
+
table.add_row("One-Time Download", "[green]Yes[/green]" if one_time else "[red]No[/red]")
|
|
109
114
|
|
|
110
115
|
# --- Prepare Upload ---
|
|
111
|
-
upload_url = f"{
|
|
112
|
-
filename = os.path.basename(
|
|
113
|
-
file_size = os.path.getsize(
|
|
116
|
+
upload_url = f"{url.rstrip('/')}"
|
|
117
|
+
filename = os.path.basename(filepath)
|
|
118
|
+
file_size = os.path.getsize(filepath)
|
|
119
|
+
|
|
120
|
+
# --- Calculate Hash ---
|
|
121
|
+
client_hash = calculate_file_hash(filepath)
|
|
122
|
+
table.add_row("File Hash", f"[cyan]{client_hash}[/cyan]")
|
|
123
|
+
console.print(table)
|
|
124
|
+
|
|
114
125
|
|
|
115
126
|
# --- Chunked Upload ---
|
|
116
127
|
response = None
|
|
@@ -131,7 +142,7 @@ def main():
|
|
|
131
142
|
|
|
132
143
|
with Live(Panel(progress, title="[cyan]Uploading[/cyan]", border_style="cyan", title_align="left")) as live:
|
|
133
144
|
task_id = progress.add_task(filename, total=file_size)
|
|
134
|
-
with open(
|
|
145
|
+
with open(filepath, 'rb') as f:
|
|
135
146
|
chunk_number = 0
|
|
136
147
|
while chunk := f.read(CHUNK_SIZE):
|
|
137
148
|
chunk_number += 1
|
|
@@ -155,17 +166,18 @@ def main():
|
|
|
155
166
|
'upload_id': upload_id,
|
|
156
167
|
'filename': filename,
|
|
157
168
|
'hours': str(hours),
|
|
158
|
-
'one_time': str(
|
|
169
|
+
'one_time': str(one_time).lower(),
|
|
170
|
+
'client_hash': client_hash, # Send the client-side hash for verification
|
|
159
171
|
}
|
|
160
|
-
if
|
|
161
|
-
finalize_data['password'] =
|
|
172
|
+
if password:
|
|
173
|
+
finalize_data['password'] = password
|
|
162
174
|
|
|
163
175
|
response = requests.post(f"{upload_url}/upload/finalize", data=finalize_data)
|
|
164
176
|
response.raise_for_status()
|
|
165
177
|
|
|
166
178
|
except FileNotFoundError:
|
|
167
|
-
console.print(Panel(f"[bold red]Error:[/] The file '{
|
|
168
|
-
|
|
179
|
+
console.print(Panel(f"[bold red]Error:[/] The file '{filepath}' was not found.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
180
|
+
return
|
|
169
181
|
except requests.exceptions.RequestException as e:
|
|
170
182
|
error_message = str(e)
|
|
171
183
|
if e.response:
|
|
@@ -174,24 +186,47 @@ def main():
|
|
|
174
186
|
except:
|
|
175
187
|
error_message = e.response.text
|
|
176
188
|
console.print(Panel(f"[bold red]An error occurred:[/] {error_message}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
177
|
-
|
|
189
|
+
return
|
|
178
190
|
except Exception as e:
|
|
179
191
|
console.print(Panel(f"[bold red]An unexpected error occurred:[/] {e}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
180
|
-
|
|
192
|
+
return
|
|
181
193
|
|
|
182
194
|
# --- Handle Response ---
|
|
183
195
|
if response is not None:
|
|
184
196
|
if response.status_code == 200:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
try:
|
|
198
|
+
data = response.json()
|
|
199
|
+
download_link = data.get('url')
|
|
200
|
+
file_hash = data.get('hash')
|
|
201
|
+
hash_verified = data.get('hash_verified', False)
|
|
202
|
+
|
|
203
|
+
success_message = f"[bold green]Upload successful![/bold green]\n\nDownload Link: {download_link}"
|
|
204
|
+
if file_hash:
|
|
205
|
+
verified_status = "[bold green]Yes[/bold green]" if hash_verified else "[bold red]No[/bold red]"
|
|
206
|
+
success_message += f"\nFile Hash (SHA256): {file_hash}"
|
|
207
|
+
success_message += f"\nHash Verified: {verified_status}"
|
|
208
|
+
|
|
209
|
+
success_panel = Panel(success_message, title="[bold cyan]Success[/bold cyan]", border_style="green")
|
|
210
|
+
console.print(success_panel)
|
|
211
|
+
|
|
212
|
+
if qr:
|
|
213
|
+
qr_code = qrcode.QRCode()
|
|
214
|
+
qr_code.add_data(download_link)
|
|
215
|
+
qr_code.make(fit=True)
|
|
216
|
+
qr_code.print_ascii()
|
|
217
|
+
|
|
218
|
+
except requests.exceptions.JSONDecodeError:
|
|
219
|
+
# Fallback for older servers or unexpected plain text responses
|
|
220
|
+
download_link = response.text.strip()
|
|
221
|
+
success_panel = Panel(f"[bold green]Upload successful![/bold green]\n\nDownload Link: {download_link}",
|
|
222
|
+
title="[bold cyan]Success[/bold cyan]", border_style="green")
|
|
223
|
+
console.print(success_panel)
|
|
224
|
+
|
|
225
|
+
if qr:
|
|
226
|
+
qr_code = qrcode.QRCode()
|
|
227
|
+
qr_code.add_data(download_link)
|
|
228
|
+
qr_code.make(fit=True)
|
|
229
|
+
qr_code.print_ascii()
|
|
195
230
|
else:
|
|
196
231
|
try:
|
|
197
232
|
error_details = response.json()
|
|
@@ -199,7 +234,63 @@ def main():
|
|
|
199
234
|
except requests.exceptions.JSONDecodeError:
|
|
200
235
|
error_message = response.text
|
|
201
236
|
console.print(Panel(f"[bold red]Error:[/] Upload failed with status code {response.status_code}\n[red]Server message:[/] {error_message}", title="[bold red]Error[/bold red]", border_style="red"))
|
|
202
|
-
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def main():
|
|
240
|
+
"""The main entry point for the Tempspace CLI tool.
|
|
241
|
+
|
|
242
|
+
Handles argument parsing, interactive mode, file validation,
|
|
243
|
+
and orchestrates the chunked file upload process for one or more files.
|
|
244
|
+
"""
|
|
245
|
+
console = Console()
|
|
246
|
+
|
|
247
|
+
# --- Header ---
|
|
248
|
+
console.print(Panel("[bold cyan]Tempspace File Uploader[/bold cyan]", expand=False, border_style="blue"))
|
|
249
|
+
|
|
250
|
+
parser = argparse.ArgumentParser(
|
|
251
|
+
description="Upload one or more files to Tempspace.",
|
|
252
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
parser.add_argument("filepaths", nargs='*', default=[], help="The path(s) to the file(s) you want to upload.")
|
|
256
|
+
parser.add_argument("-t", "--time", type=str, default='24', help="Set the file's expiration time for all files. Examples: '24h', '7d', '360' (hours).\nDefault: '24' (24 hours).")
|
|
257
|
+
parser.add_argument("-p", "--password", type=str, help="Protect all files with a password.")
|
|
258
|
+
parser.add_argument("--one-time", action="store_true", help="All files will be deleted after the first download.")
|
|
259
|
+
parser.add_argument("--url", type=str, default=os.environ.get("TEMPSPACE_URL", DEFAULT_SERVER_URL), help=f"The URL of the Tempspace server.\nCan also be set with the TEMPSPACE_URL environment variable.\nDefault: {DEFAULT_SERVER_URL}")
|
|
260
|
+
parser.add_argument("--qr", action="store_true", help="Display a QR code of the download link for each file.")
|
|
261
|
+
parser.add_argument("--it", action="store_true", help="Enable interactive mode for a single file upload.")
|
|
262
|
+
|
|
263
|
+
args = parser.parse_args()
|
|
264
|
+
|
|
265
|
+
filepaths = args.filepaths
|
|
266
|
+
|
|
267
|
+
# --- Interactive Mode ---
|
|
268
|
+
if args.it:
|
|
269
|
+
# Interactive mode only supports a single file, so we overwrite the filepaths list
|
|
270
|
+
filepath = Prompt.ask("Enter the path to the file you want to upload")
|
|
271
|
+
filepaths = [filepath]
|
|
272
|
+
args.time = Prompt.ask("Set the file's expiration time (e.g., '24h', '7d')", default='24')
|
|
273
|
+
args.password = Prompt.ask("Protect the file with a password?", default=None, password=True)
|
|
274
|
+
args.one_time = Confirm.ask("Delete the file after the first download?", default=False)
|
|
275
|
+
args.qr = Confirm.ask("Display a QR code of the download link?", default=False)
|
|
276
|
+
|
|
277
|
+
# --- Validate Inputs ---
|
|
278
|
+
if not filepaths:
|
|
279
|
+
console.print(Panel("[bold red]Error:[/] No file path(s) provided.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
280
|
+
parser.print_help()
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
|
|
283
|
+
hours = parse_time(args.time)
|
|
284
|
+
if hours is None:
|
|
285
|
+
console.print(Panel(f"[bold red]Error:[/] Invalid time format '{args.time}'. Use formats like '24h', '7d', or '360'.", title="[bold red]Error[/bold red]", border_style="red"))
|
|
286
|
+
sys.exit(1)
|
|
287
|
+
|
|
288
|
+
# --- Process each file ---
|
|
289
|
+
for i, filepath in enumerate(filepaths):
|
|
290
|
+
if len(filepaths) > 1:
|
|
291
|
+
console.print(f"\n[bold yellow]Uploading file {i+1} of {len(filepaths)}: {os.path.basename(filepath)}[/bold yellow]\n")
|
|
292
|
+
upload_file(console, filepath, hours, args.password, args.one_time, args.qr, args.url)
|
|
293
|
+
|
|
203
294
|
|
|
204
295
|
if __name__ == "__main__":
|
|
205
296
|
multiprocessing.freeze_support()
|
|
@@ -7,7 +7,7 @@ packages = ["cli"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "tempspace-cli"
|
|
10
|
-
version = "1.2.
|
|
10
|
+
version = "1.2.4"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name="Tempspace", email="mcbplay1@gmail.com" },
|
|
13
13
|
]
|
|
@@ -20,9 +20,9 @@ classifiers = [
|
|
|
20
20
|
"Operating System :: OS Independent",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"requests==2.
|
|
23
|
+
"requests==2.32.5",
|
|
24
24
|
"requests-toolbelt==1.0.0",
|
|
25
|
-
"rich==
|
|
25
|
+
"rich==14.2.0",
|
|
26
26
|
"qrcode==8.2",
|
|
27
27
|
]
|
|
28
28
|
|