copyparty 1.12.1__tar.gz → 1.13.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.
- {copyparty-1.12.1 → copyparty-1.13.0}/PKG-INFO +29 -4
- {copyparty-1.12.1 → copyparty-1.13.0}/README.md +28 -3
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/__main__.py +7 -2
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/__version__.py +3 -3
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/authsrv.py +8 -7
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/cert.py +17 -6
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/cfg.py +3 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/httpcli.py +188 -10
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/httpconn.py +1 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/httpsrv.py +2 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/svchub.py +13 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/th_srv.py +2 -1
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/up2k.py +124 -60
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/util.py +101 -29
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/a/u2c.py +8 -4
- copyparty-1.13.0/copyparty/web/browser.css.gz +0 -0
- copyparty-1.13.0/copyparty/web/browser.js.gz +0 -0
- copyparty-1.13.0/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/splash.html +1 -1
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/up2k.js.gz +0 -0
- copyparty-1.13.0/copyparty/web/util.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/PKG-INFO +29 -4
- copyparty-1.12.1/copyparty/web/browser.css.gz +0 -0
- copyparty-1.12.1/copyparty/web/browser.js.gz +0 -0
- copyparty-1.12.1/copyparty/web/splash.css.gz +0 -0
- copyparty-1.12.1/copyparty/web/util.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/LICENSE +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/bos/bos.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/bos/path.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/broker_mp.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/broker_thr.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/broker_util.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/dxml.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/fsutil.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/ftpd.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/ico.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/mdns.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/metrics.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/mtag.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/multicast.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/pwhash.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/res/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/smbd.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/ssdp.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/star.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/sutil.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/szip.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/tcpsrv.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/tftpd.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/th_cli.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/u2idx.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/a/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/a/partyfuse.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/baguettebox.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/browser.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/browser2.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/cf.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dbg-audio.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dd/2.png +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dd/3.png +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dd/4.png +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dd/5.png +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/__init__.py +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/busy.mp3.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/easymde.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/easymde.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/prism.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/prismd.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/md.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/md.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/md.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/md2.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/md2.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/mde.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/mde.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/msg.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/msg.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/splash.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/svcs.html +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/svcs.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/SOURCES.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/entry_points.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/requires.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/copyparty.egg-info/top_level.txt +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/pyproject.toml +0 -0
- {copyparty-1.12.1 → copyparty-1.13.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.13.0
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
6
6
|
License: MIT
|
@@ -64,13 +64,16 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
64
64
|
|
65
65
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
66
66
|
|
67
|
+
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
|
68
|
+
|
67
69
|
|
68
70
|
## readme toc
|
69
71
|
|
70
72
|
* top
|
71
73
|
* [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
74
|
+
* [at home](#at-home) - make it accessible over the internet
|
72
75
|
* [on servers](#on-servers) - you may also want these, especially on servers
|
73
|
-
* [features](#features)
|
76
|
+
* [features](#features) - also see [comparison to similar software](./docs/versus.md)
|
74
77
|
* [testimonials](#testimonials) - small collection of user feedback
|
75
78
|
* [motivations](#motivations) - project goals / philosophy
|
76
79
|
* [notes](#notes) - general notes
|
@@ -91,6 +94,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
91
94
|
* [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server
|
92
95
|
* [unpost](#unpost) - undo/delete accidental uploads
|
93
96
|
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
97
|
+
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
94
98
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
95
99
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
96
100
|
* [media player](#media-player) - plays almost every audio format there is
|
@@ -180,7 +184,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
|
|
180
184
|
|
181
185
|
* **Alpine:** `apk add py3-pillow ffmpeg`
|
182
186
|
* **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
|
183
|
-
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg`
|
187
|
+
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
|
184
188
|
* **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg`
|
185
189
|
* **MacOS:** `port install py-Pillow ffmpeg`
|
186
190
|
* **MacOS** (alternative): `brew install pillow ffmpeg`
|
@@ -201,6 +205,17 @@ some recommended options:
|
|
201
205
|
* see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
|
202
206
|
|
203
207
|
|
208
|
+
### at home
|
209
|
+
|
210
|
+
make it accessible over the internet by starting a [cloudflare quicktunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) like so:
|
211
|
+
|
212
|
+
first download [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) and then start the tunnel with `cloudflared tunnel --url http://127.0.0.1:3923`
|
213
|
+
|
214
|
+
as the tunnel starts, it will show a URL which you can share to let anyone browse your stash or upload files to you
|
215
|
+
|
216
|
+
since people will be connecting through cloudflare, run copyparty with `--xff-hdr cf-connecting-ip` to detect client IPs correctly
|
217
|
+
|
218
|
+
|
204
219
|
### on servers
|
205
220
|
|
206
221
|
you may also want these, especially on servers:
|
@@ -224,6 +239,8 @@ firewall-cmd --reload
|
|
224
239
|
|
225
240
|
## features
|
226
241
|
|
242
|
+
also see [comparison to similar software](./docs/versus.md)
|
243
|
+
|
227
244
|
* backend stuff
|
228
245
|
* ☑ IPv6
|
229
246
|
* ☑ [multiprocessing](#performance) (actual multithreading)
|
@@ -246,6 +263,7 @@ firewall-cmd --reload
|
|
246
263
|
* ☑ write-only folders
|
247
264
|
* ☑ [unpost](#unpost): undo/delete accidental uploads
|
248
265
|
* ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
|
266
|
+
* ☑ [race the beam](#race-the-beam) (almost like peer-to-peer)
|
249
267
|
* ☑ symlink/discard duplicates (content-matching)
|
250
268
|
* download
|
251
269
|
* ☑ single files in browser
|
@@ -671,7 +689,7 @@ up2k has several advantages:
|
|
671
689
|
> it is perfectly safe to restart / upgrade copyparty while someone is uploading to it!
|
672
690
|
> all known up2k clients will resume just fine 💪
|
673
691
|
|
674
|
-
see [up2k](#up2k) for details on how it works, or watch a [demo video](https://a.ocv.me/pub/demo/pics-vids/#gf-0f6f5c0d)
|
692
|
+
see [up2k](./docs/devnotes.md#up2k) for details on how it works, or watch a [demo video](https://a.ocv.me/pub/demo/pics-vids/#gf-0f6f5c0d)
|
675
693
|
|
676
694
|

|
677
695
|
|
@@ -737,6 +755,13 @@ clients can specify a shorter expiration time using the [up2k ui](#uploading) --
|
|
737
755
|
specifying a custom expiration time client-side will affect the timespan in which unposts are permitted, so keep an eye on the estimates in the up2k ui
|
738
756
|
|
739
757
|
|
758
|
+
### race the beam
|
759
|
+
|
760
|
+
download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)) -- it's almost like peer-to-peer
|
761
|
+
|
762
|
+
requires the file to be uploaded using up2k (which is the default drag-and-drop uploader), alternatively the command-line program
|
763
|
+
|
764
|
+
|
740
765
|
## file manager
|
741
766
|
|
742
767
|
cut/paste, rename, and delete files/folders (if you have permission)
|
@@ -10,13 +10,16 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
10
10
|
|
11
11
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
12
12
|
|
13
|
+
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
|
14
|
+
|
13
15
|
|
14
16
|
## readme toc
|
15
17
|
|
16
18
|
* top
|
17
19
|
* [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
20
|
+
* [at home](#at-home) - make it accessible over the internet
|
18
21
|
* [on servers](#on-servers) - you may also want these, especially on servers
|
19
|
-
* [features](#features)
|
22
|
+
* [features](#features) - also see [comparison to similar software](./docs/versus.md)
|
20
23
|
* [testimonials](#testimonials) - small collection of user feedback
|
21
24
|
* [motivations](#motivations) - project goals / philosophy
|
22
25
|
* [notes](#notes) - general notes
|
@@ -37,6 +40,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
37
40
|
* [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server
|
38
41
|
* [unpost](#unpost) - undo/delete accidental uploads
|
39
42
|
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
43
|
+
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
40
44
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
41
45
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
42
46
|
* [media player](#media-player) - plays almost every audio format there is
|
@@ -126,7 +130,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
|
|
126
130
|
|
127
131
|
* **Alpine:** `apk add py3-pillow ffmpeg`
|
128
132
|
* **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
|
129
|
-
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg`
|
133
|
+
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
|
130
134
|
* **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg`
|
131
135
|
* **MacOS:** `port install py-Pillow ffmpeg`
|
132
136
|
* **MacOS** (alternative): `brew install pillow ffmpeg`
|
@@ -147,6 +151,17 @@ some recommended options:
|
|
147
151
|
* see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
|
148
152
|
|
149
153
|
|
154
|
+
### at home
|
155
|
+
|
156
|
+
make it accessible over the internet by starting a [cloudflare quicktunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) like so:
|
157
|
+
|
158
|
+
first download [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) and then start the tunnel with `cloudflared tunnel --url http://127.0.0.1:3923`
|
159
|
+
|
160
|
+
as the tunnel starts, it will show a URL which you can share to let anyone browse your stash or upload files to you
|
161
|
+
|
162
|
+
since people will be connecting through cloudflare, run copyparty with `--xff-hdr cf-connecting-ip` to detect client IPs correctly
|
163
|
+
|
164
|
+
|
150
165
|
### on servers
|
151
166
|
|
152
167
|
you may also want these, especially on servers:
|
@@ -170,6 +185,8 @@ firewall-cmd --reload
|
|
170
185
|
|
171
186
|
## features
|
172
187
|
|
188
|
+
also see [comparison to similar software](./docs/versus.md)
|
189
|
+
|
173
190
|
* backend stuff
|
174
191
|
* ☑ IPv6
|
175
192
|
* ☑ [multiprocessing](#performance) (actual multithreading)
|
@@ -192,6 +209,7 @@ firewall-cmd --reload
|
|
192
209
|
* ☑ write-only folders
|
193
210
|
* ☑ [unpost](#unpost): undo/delete accidental uploads
|
194
211
|
* ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
|
212
|
+
* ☑ [race the beam](#race-the-beam) (almost like peer-to-peer)
|
195
213
|
* ☑ symlink/discard duplicates (content-matching)
|
196
214
|
* download
|
197
215
|
* ☑ single files in browser
|
@@ -617,7 +635,7 @@ up2k has several advantages:
|
|
617
635
|
> it is perfectly safe to restart / upgrade copyparty while someone is uploading to it!
|
618
636
|
> all known up2k clients will resume just fine 💪
|
619
637
|
|
620
|
-
see [up2k](#up2k) for details on how it works, or watch a [demo video](https://a.ocv.me/pub/demo/pics-vids/#gf-0f6f5c0d)
|
638
|
+
see [up2k](./docs/devnotes.md#up2k) for details on how it works, or watch a [demo video](https://a.ocv.me/pub/demo/pics-vids/#gf-0f6f5c0d)
|
621
639
|
|
622
640
|

|
623
641
|
|
@@ -683,6 +701,13 @@ clients can specify a shorter expiration time using the [up2k ui](#uploading) --
|
|
683
701
|
specifying a custom expiration time client-side will affect the timespan in which unposts are permitted, so keep an eye on the estimates in the up2k ui
|
684
702
|
|
685
703
|
|
704
|
+
### race the beam
|
705
|
+
|
706
|
+
download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)) -- it's almost like peer-to-peer
|
707
|
+
|
708
|
+
requires the file to be uploaded using up2k (which is the default drag-and-drop uploader), alternatively the command-line program
|
709
|
+
|
710
|
+
|
686
711
|
## file manager
|
687
712
|
|
688
713
|
cut/paste, rename, and delete files/folders (if you have permission)
|
@@ -850,8 +850,9 @@ def add_qr(ap, tty):
|
|
850
850
|
|
851
851
|
def add_fs(ap):
|
852
852
|
ap2 = ap.add_argument_group("filesystem options")
|
853
|
-
rm_re_def = "
|
853
|
+
rm_re_def = "15/0.1" if ANYWIN else "0/0"
|
854
854
|
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
|
855
|
+
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
|
855
856
|
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
|
856
857
|
|
857
858
|
|
@@ -943,6 +944,8 @@ def add_auth(ap):
|
|
943
944
|
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
|
944
945
|
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
|
945
946
|
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
|
947
|
+
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
|
948
|
+
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
946
949
|
|
947
950
|
|
948
951
|
def add_zeroconf(ap):
|
@@ -1082,6 +1085,8 @@ def add_optouts(ap):
|
|
1082
1085
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
1083
1086
|
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
1084
1087
|
ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
|
1088
|
+
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
|
1089
|
+
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
|
1085
1090
|
|
1086
1091
|
|
1087
1092
|
def add_safety(ap):
|
@@ -1207,7 +1212,7 @@ def add_db_general(ap, hcores):
|
|
1207
1212
|
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
|
1208
1213
|
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
|
1209
1214
|
ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
|
1210
|
-
ap2.add_argument("--re-dhash", action="store_true", help="
|
1215
|
+
ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
|
1211
1216
|
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
|
1212
1217
|
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
|
1213
1218
|
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2024, 4,
|
3
|
+
VERSION = (1, 13, 0)
|
4
|
+
CODENAME = "race the beam"
|
5
|
+
BUILD_DT = (2024, 4, 20)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
@@ -1757,13 +1757,14 @@ class AuthSrv(object):
|
|
1757
1757
|
if k in vol.flags:
|
1758
1758
|
vol.flags[k] = float(vol.flags[k])
|
1759
1759
|
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1760
|
+
for k in ("mv_re", "rm_re"):
|
1761
|
+
try:
|
1762
|
+
zs1, zs2 = vol.flags[k + "try"].split("/")
|
1763
|
+
vol.flags[k + "_t"] = float(zs1)
|
1764
|
+
vol.flags[k + "_r"] = float(zs2)
|
1765
|
+
except:
|
1766
|
+
t = 'volume "/%s" has invalid %stry [%s]'
|
1767
|
+
raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
|
1767
1768
|
|
1768
1769
|
for k1, k2 in IMPLICATIONS:
|
1769
1770
|
if k1 in vol.flags:
|
@@ -6,10 +6,17 @@ import os
|
|
6
6
|
import shutil
|
7
7
|
import time
|
8
8
|
|
9
|
-
from .
|
9
|
+
from .__init__ import ANYWIN
|
10
|
+
from .util import Netdev, runcmd, wrename, wunlink
|
10
11
|
|
11
12
|
HAVE_CFSSL = True
|
12
13
|
|
14
|
+
if ANYWIN:
|
15
|
+
VF = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
|
16
|
+
else:
|
17
|
+
VF = {"mv_re_t": 0, "rm_re_t": 0}
|
18
|
+
|
19
|
+
|
13
20
|
def ensure_cert(log , args) :
|
14
21
|
"""
|
15
22
|
the default cert (and the entire TLS support) is only here to enable the
|
@@ -101,8 +108,12 @@ def _gen_ca(log , args):
|
|
101
108
|
raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3)
|
102
109
|
|
103
110
|
bname = os.path.join(args.crt_dir, "ca")
|
104
|
-
|
105
|
-
|
111
|
+
try:
|
112
|
+
wunlink(log, bname + ".key", VF)
|
113
|
+
except:
|
114
|
+
pass
|
115
|
+
wrename(log, bname + "-key.pem", bname + ".key", VF)
|
116
|
+
wunlink(log, bname + ".csr", VF)
|
106
117
|
|
107
118
|
log("cert", "new ca OK", 2)
|
108
119
|
|
@@ -181,11 +192,11 @@ def _gen_srv(log , args, netdevs ):
|
|
181
192
|
|
182
193
|
bname = os.path.join(args.crt_dir, "srv")
|
183
194
|
try:
|
184
|
-
|
195
|
+
wunlink(log, bname + ".key", VF)
|
185
196
|
except:
|
186
197
|
pass
|
187
|
-
|
188
|
-
|
198
|
+
wrename(log, bname + "-key.pem", bname + ".key", VF)
|
199
|
+
wunlink(log, bname + ".csr", VF)
|
189
200
|
|
190
201
|
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
191
202
|
ca = f.read()
|
@@ -16,6 +16,7 @@ def vf_bmap() :
|
|
16
16
|
"no_dedup": "copydupes",
|
17
17
|
"no_dupe": "nodupe",
|
18
18
|
"no_forget": "noforget",
|
19
|
+
"no_pipe": "nopipe",
|
19
20
|
"no_robots": "norobots",
|
20
21
|
"no_thumb": "dthumb",
|
21
22
|
"no_vthumb": "dvthumb",
|
@@ -63,6 +64,7 @@ def vf_vmap() :
|
|
63
64
|
"lg_sbf",
|
64
65
|
"md_sbf",
|
65
66
|
"nrand",
|
67
|
+
"mv_retry",
|
66
68
|
"rm_retry",
|
67
69
|
"sort",
|
68
70
|
"unlist",
|
@@ -214,6 +216,7 @@ flagcats = {
|
|
214
216
|
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
|
215
217
|
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
216
218
|
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
|
219
|
+
"mv_retry": "ms-windows: timeout for renaming busy files",
|
217
220
|
"rm_retry": "ms-windows: timeout for deleting busy files",
|
218
221
|
"davauth": "ask webdav clients to login for all folders",
|
219
222
|
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
|
@@ -36,6 +36,7 @@ from .bos import bos
|
|
36
36
|
from .star import StreamTar
|
37
37
|
from .sutil import StreamArc, gfilter
|
38
38
|
from .szip import StreamZip
|
39
|
+
from .up2k import up2k_chunksize
|
39
40
|
from .util import unquote # type: ignore
|
40
41
|
from .util import (
|
41
42
|
APPLESAN_RE,
|
@@ -89,6 +90,7 @@ from .util import (
|
|
89
90
|
vjoin,
|
90
91
|
vol_san,
|
91
92
|
vsplit,
|
93
|
+
wrename,
|
92
94
|
wunlink,
|
93
95
|
yieldfile,
|
94
96
|
)
|
@@ -122,6 +124,7 @@ class HttpCli(object):
|
|
122
124
|
self.ico = conn.ico # mypy404
|
123
125
|
self.thumbcli = conn.thumbcli # mypy404
|
124
126
|
self.u2fh = conn.u2fh # mypy404
|
127
|
+
self.pipes = conn.pipes # mypy404
|
125
128
|
self.log_func = conn.log_func # mypy404
|
126
129
|
self.log_src = conn.log_src # mypy404
|
127
130
|
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
|
@@ -439,7 +442,11 @@ class HttpCli(object):
|
|
439
442
|
|
440
443
|
zso = self.headers.get("authorization")
|
441
444
|
bauth = ""
|
442
|
-
if
|
445
|
+
if (
|
446
|
+
zso
|
447
|
+
and not self.args.no_bauth
|
448
|
+
and (not cookie_pw or not self.args.bauth_last)
|
449
|
+
):
|
443
450
|
try:
|
444
451
|
zb = zso.split(" ")[1].encode("ascii")
|
445
452
|
zs = base64.b64decode(zb).decode("utf-8")
|
@@ -1796,7 +1803,7 @@ class HttpCli(object):
|
|
1796
1803
|
f, fn = zfw["orz"]
|
1797
1804
|
|
1798
1805
|
path2 = os.path.join(fdir, fn2)
|
1799
|
-
atomic_move(path, path2)
|
1806
|
+
atomic_move(self.log, path, path2, vfs.flags)
|
1800
1807
|
fn = fn2
|
1801
1808
|
path = path2
|
1802
1809
|
|
@@ -1877,7 +1884,9 @@ class HttpCli(object):
|
|
1877
1884
|
self.reply(t.encode("utf-8"), 201, headers=h)
|
1878
1885
|
return True
|
1879
1886
|
|
1880
|
-
def bakflip(
|
1887
|
+
def bakflip(
|
1888
|
+
self, f , ofs , sz , sha , flags
|
1889
|
+
) :
|
1881
1890
|
if not self.args.bak_flips or self.args.nw:
|
1882
1891
|
return
|
1883
1892
|
|
@@ -1905,7 +1914,7 @@ class HttpCli(object):
|
|
1905
1914
|
|
1906
1915
|
if nrem:
|
1907
1916
|
self.log("bakflip truncated; {} remains".format(nrem), 1)
|
1908
|
-
atomic_move(fp, fp + ".trunc")
|
1917
|
+
atomic_move(self.log, fp, fp + ".trunc", flags)
|
1909
1918
|
else:
|
1910
1919
|
self.log("bakflip ok", 2)
|
1911
1920
|
|
@@ -2171,7 +2180,7 @@ class HttpCli(object):
|
|
2171
2180
|
|
2172
2181
|
if sha_b64 != chash:
|
2173
2182
|
try:
|
2174
|
-
self.bakflip(f, cstart[0], post_sz, sha_b64)
|
2183
|
+
self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
|
2175
2184
|
except:
|
2176
2185
|
self.log("bakflip failed: " + min_ex())
|
2177
2186
|
|
@@ -2523,7 +2532,7 @@ class HttpCli(object):
|
|
2523
2532
|
raise
|
2524
2533
|
|
2525
2534
|
if not nullwrite:
|
2526
|
-
atomic_move(tabspath, abspath)
|
2535
|
+
atomic_move(self.log, tabspath, abspath, vfs.flags)
|
2527
2536
|
|
2528
2537
|
tabspath = ""
|
2529
2538
|
|
@@ -2763,7 +2772,7 @@ class HttpCli(object):
|
|
2763
2772
|
hidedir(dp)
|
2764
2773
|
except:
|
2765
2774
|
pass
|
2766
|
-
|
2775
|
+
wrename(self.log, fp, os.path.join(mdir, ".hist", mfile2), vfs.flags)
|
2767
2776
|
|
2768
2777
|
assert self.parser.gen
|
2769
2778
|
p_field, _, p_data = next(self.parser.gen)
|
@@ -2918,17 +2927,42 @@ class HttpCli(object):
|
|
2918
2927
|
|
2919
2928
|
return txt
|
2920
2929
|
|
2921
|
-
def tx_file(self, req_path ) :
|
2930
|
+
def tx_file(self, req_path , ptop = None) :
|
2922
2931
|
status = 200
|
2923
2932
|
logmsg = "{:4} {} ".format("", self.req)
|
2924
2933
|
logtail = ""
|
2925
2934
|
|
2935
|
+
if ptop is not None:
|
2936
|
+
try:
|
2937
|
+
dp, fn = os.path.split(req_path)
|
2938
|
+
tnam = fn + ".PARTIAL"
|
2939
|
+
if self.args.dotpart:
|
2940
|
+
tnam = "." + tnam
|
2941
|
+
ap_data = os.path.join(dp, tnam)
|
2942
|
+
st_data = bos.stat(ap_data)
|
2943
|
+
if not st_data.st_size:
|
2944
|
+
raise Exception("partial is empty")
|
2945
|
+
x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
|
2946
|
+
job = json.loads(x.get())
|
2947
|
+
if not job:
|
2948
|
+
raise Exception("not found in registry")
|
2949
|
+
self.pipes.set(req_path, job)
|
2950
|
+
except Exception as ex:
|
2951
|
+
self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
|
2952
|
+
ptop = None
|
2953
|
+
|
2926
2954
|
#
|
2927
2955
|
# if request is for foo.js, check if we have foo.js.gz
|
2928
2956
|
|
2929
2957
|
file_ts = 0.0
|
2930
2958
|
editions = {}
|
2931
2959
|
for ext in ("", ".gz"):
|
2960
|
+
if ptop is not None:
|
2961
|
+
sz = job["size"]
|
2962
|
+
file_ts = job["lmod"]
|
2963
|
+
editions["plain"] = (ap_data, sz)
|
2964
|
+
break
|
2965
|
+
|
2932
2966
|
try:
|
2933
2967
|
fs_path = req_path + ext
|
2934
2968
|
st = bos.stat(fs_path)
|
@@ -3085,6 +3119,11 @@ class HttpCli(object):
|
|
3085
3119
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
3086
3120
|
return True
|
3087
3121
|
|
3122
|
+
if ptop is not None:
|
3123
|
+
return self.tx_pipe(
|
3124
|
+
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
3125
|
+
)
|
3126
|
+
|
3088
3127
|
ret = True
|
3089
3128
|
with open_func(*open_args) as f:
|
3090
3129
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
@@ -3104,6 +3143,143 @@ class HttpCli(object):
|
|
3104
3143
|
|
3105
3144
|
return ret
|
3106
3145
|
|
3146
|
+
def tx_pipe(
|
3147
|
+
self,
|
3148
|
+
ptop ,
|
3149
|
+
req_path ,
|
3150
|
+
ap_data ,
|
3151
|
+
job ,
|
3152
|
+
lower ,
|
3153
|
+
upper ,
|
3154
|
+
status ,
|
3155
|
+
mime ,
|
3156
|
+
logmsg ,
|
3157
|
+
) :
|
3158
|
+
M = 1048576
|
3159
|
+
self.send_headers(length=upper - lower, status=status, mime=mime)
|
3160
|
+
wr_slp = self.args.s_wr_slp
|
3161
|
+
wr_sz = self.args.s_wr_sz
|
3162
|
+
file_size = job["size"]
|
3163
|
+
chunk_size = up2k_chunksize(file_size)
|
3164
|
+
num_need = -1
|
3165
|
+
data_end = 0
|
3166
|
+
remains = upper - lower
|
3167
|
+
broken = False
|
3168
|
+
spins = 0
|
3169
|
+
tier = 0
|
3170
|
+
tiers = ["uncapped", "reduced speed", "one byte per sec"]
|
3171
|
+
|
3172
|
+
while lower < upper and not broken:
|
3173
|
+
with self.u2mutex:
|
3174
|
+
job = self.pipes.get(req_path)
|
3175
|
+
if not job:
|
3176
|
+
x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
|
3177
|
+
job = json.loads(x.get())
|
3178
|
+
if job:
|
3179
|
+
self.pipes.set(req_path, job)
|
3180
|
+
|
3181
|
+
if not job:
|
3182
|
+
t = "pipe: OK, upload has finished; yeeting remainder"
|
3183
|
+
self.log(t, 2)
|
3184
|
+
data_end = file_size
|
3185
|
+
break
|
3186
|
+
|
3187
|
+
if num_need != len(job["need"]):
|
3188
|
+
num_need = len(job["need"])
|
3189
|
+
data_end = 0
|
3190
|
+
for cid in job["hash"]:
|
3191
|
+
if cid in job["need"]:
|
3192
|
+
break
|
3193
|
+
data_end += chunk_size
|
3194
|
+
t = "pipe: can stream %.2f MiB; requested range is %.2f to %.2f"
|
3195
|
+
self.log(t % (data_end / M, lower / M, upper / M), 6)
|
3196
|
+
with self.u2mutex:
|
3197
|
+
if data_end > self.u2fh.aps.get(ap_data, data_end):
|
3198
|
+
try:
|
3199
|
+
fhs = self.u2fh.cache[ap_data].all_fhs
|
3200
|
+
for fh in fhs:
|
3201
|
+
fh.flush()
|
3202
|
+
self.u2fh.aps[ap_data] = data_end
|
3203
|
+
self.log("pipe: flushed %d up2k-FDs" % (len(fhs),))
|
3204
|
+
except Exception as ex:
|
3205
|
+
self.log("pipe: u2fh flush failed: %r" % (ex,))
|
3206
|
+
|
3207
|
+
if lower >= data_end:
|
3208
|
+
if data_end:
|
3209
|
+
t = "pipe: uploader is too slow; aborting download at %.2f MiB"
|
3210
|
+
self.log(t % (data_end / M))
|
3211
|
+
raise Pebkac(416, "uploader is too slow")
|
3212
|
+
|
3213
|
+
raise Pebkac(416, "no data available yet; please retry in a bit")
|
3214
|
+
|
3215
|
+
slack = data_end - lower
|
3216
|
+
if slack >= 8 * M:
|
3217
|
+
ntier = 0
|
3218
|
+
winsz = M
|
3219
|
+
bufsz = wr_sz
|
3220
|
+
slp = wr_slp
|
3221
|
+
else:
|
3222
|
+
winsz = max(40, int(M * (slack / (12 * M))))
|
3223
|
+
base_rate = M if not wr_slp else wr_sz / wr_slp
|
3224
|
+
if winsz > base_rate:
|
3225
|
+
ntier = 0
|
3226
|
+
bufsz = wr_sz
|
3227
|
+
slp = wr_slp
|
3228
|
+
elif winsz > 300:
|
3229
|
+
ntier = 1
|
3230
|
+
bufsz = winsz // 5
|
3231
|
+
slp = 0.2
|
3232
|
+
else:
|
3233
|
+
ntier = 2
|
3234
|
+
bufsz = winsz = slp = 1
|
3235
|
+
|
3236
|
+
if tier != ntier:
|
3237
|
+
tier = ntier
|
3238
|
+
self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
|
3239
|
+
|
3240
|
+
try:
|
3241
|
+
with open(ap_data, "rb", self.args.iobuf) as f:
|
3242
|
+
f.seek(lower)
|
3243
|
+
page = f.read(min(winsz, data_end - lower, upper - lower))
|
3244
|
+
if not page:
|
3245
|
+
raise Exception("got 0 bytes (EOF?)")
|
3246
|
+
except Exception as ex:
|
3247
|
+
self.log("pipe: read failed at %.2f MiB: %s" % (lower / M, ex), 3)
|
3248
|
+
with self.u2mutex:
|
3249
|
+
self.pipes.c.pop(req_path, None)
|
3250
|
+
spins += 1
|
3251
|
+
if spins > 3:
|
3252
|
+
raise Pebkac(500, "file became unreadable")
|
3253
|
+
time.sleep(2)
|
3254
|
+
continue
|
3255
|
+
|
3256
|
+
spins = 0
|
3257
|
+
pofs = 0
|
3258
|
+
while pofs < len(page):
|
3259
|
+
if slp:
|
3260
|
+
time.sleep(slp)
|
3261
|
+
|
3262
|
+
try:
|
3263
|
+
buf = page[pofs : pofs + bufsz]
|
3264
|
+
self.s.sendall(buf)
|
3265
|
+
zi = len(buf)
|
3266
|
+
remains -= zi
|
3267
|
+
lower += zi
|
3268
|
+
pofs += zi
|
3269
|
+
except:
|
3270
|
+
broken = True
|
3271
|
+
break
|
3272
|
+
|
3273
|
+
if lower < upper and not broken:
|
3274
|
+
with open(req_path, "rb") as f:
|
3275
|
+
remains = sendfile_py(self.log, lower, upper, f, self.s, wr_sz, wr_slp)
|
3276
|
+
|
3277
|
+
spd = self._spd((upper - lower) - remains)
|
3278
|
+
if self.do_log:
|
3279
|
+
self.log("{}, {}".format(logmsg, spd))
|
3280
|
+
|
3281
|
+
return not broken
|
3282
|
+
|
3107
3283
|
def tx_zip(
|
3108
3284
|
self,
|
3109
3285
|
fmt ,
|
@@ -3741,7 +3917,7 @@ class HttpCli(object):
|
|
3741
3917
|
if not allvols:
|
3742
3918
|
ret = [{"kinshi": 1}]
|
3743
3919
|
|
3744
|
-
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret,
|
3920
|
+
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
|
3745
3921
|
zi = len(uret.split('\n"pd":')) - 1
|
3746
3922
|
self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
|
3747
3923
|
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
@@ -4020,7 +4196,9 @@ class HttpCli(object):
|
|
4020
4196
|
):
|
4021
4197
|
return self.tx_md(vn, abspath)
|
4022
4198
|
|
4023
|
-
return self.tx_file(
|
4199
|
+
return self.tx_file(
|
4200
|
+
abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
|
4201
|
+
)
|
4024
4202
|
|
4025
4203
|
elif is_dir and not self.can_read:
|
4026
4204
|
if self._use_dirkey(abspath):
|