copyparty 1.12.2__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.
Files changed (117) hide show
  1. {copyparty-1.12.2 → copyparty-1.13.0}/PKG-INFO +17 -4
  2. {copyparty-1.12.2 → copyparty-1.13.0}/README.md +16 -3
  3. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/__main__.py +4 -2
  4. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/__version__.py +3 -3
  5. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/cfg.py +1 -0
  6. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/httpcli.py +174 -3
  7. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/httpconn.py +1 -0
  8. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/httpsrv.py +2 -0
  9. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/up2k.py +115 -52
  10. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/util.py +37 -3
  11. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/a/u2c.py +8 -4
  12. copyparty-1.13.0/copyparty/web/browser.css.gz +0 -0
  13. copyparty-1.13.0/copyparty/web/browser.js.gz +0 -0
  14. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/up2k.js.gz +0 -0
  15. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/PKG-INFO +17 -4
  16. copyparty-1.12.2/copyparty/web/browser.css.gz +0 -0
  17. copyparty-1.12.2/copyparty/web/browser.js.gz +0 -0
  18. {copyparty-1.12.2 → copyparty-1.13.0}/LICENSE +0 -0
  19. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/__init__.py +0 -0
  20. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/authsrv.py +0 -0
  21. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/bos/__init__.py +0 -0
  22. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/bos/bos.py +0 -0
  23. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/bos/path.py +0 -0
  24. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/broker_mp.py +0 -0
  25. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/broker_mpw.py +0 -0
  26. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/broker_thr.py +0 -0
  27. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/broker_util.py +0 -0
  28. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/cert.py +0 -0
  29. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/dxml.py +0 -0
  30. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/fsutil.py +0 -0
  31. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/ftpd.py +0 -0
  32. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/ico.py +0 -0
  33. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/mdns.py +0 -0
  34. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/metrics.py +0 -0
  35. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/mtag.py +0 -0
  36. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/multicast.py +0 -0
  37. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/pwhash.py +0 -0
  38. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/res/COPYING.txt +0 -0
  39. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/res/__init__.py +0 -0
  40. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/res/insecure.pem +0 -0
  41. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/smbd.py +0 -0
  42. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/ssdp.py +0 -0
  43. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/star.py +0 -0
  44. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/__init__.py +0 -0
  45. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/__init__.py +0 -0
  46. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/bimap.py +0 -0
  47. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/bit.py +0 -0
  48. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/buffer.py +0 -0
  49. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/dns.py +0 -0
  50. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/label.py +0 -0
  51. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/lex.py +0 -0
  52. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/dnslib/ranges.py +0 -0
  53. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/ifaddr/__init__.py +0 -0
  54. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_posix.py +0 -0
  55. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_shared.py +0 -0
  56. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/ifaddr/_win32.py +0 -0
  57. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/qrcodegen.py +0 -0
  58. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/stolen/surrogateescape.py +0 -0
  59. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/sutil.py +0 -0
  60. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/svchub.py +0 -0
  61. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/szip.py +0 -0
  62. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/tcpsrv.py +0 -0
  63. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/tftpd.py +0 -0
  64. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/th_cli.py +0 -0
  65. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/th_srv.py +0 -0
  66. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/u2idx.py +0 -0
  67. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/a/__init__.py +0 -0
  68. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/a/partyfuse.py +0 -0
  69. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/a/webdav-cfg.bat +0 -0
  70. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/baguettebox.js.gz +0 -0
  71. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/browser.html +0 -0
  72. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/browser2.html +0 -0
  73. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/cf.html +0 -0
  74. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dbg-audio.js.gz +0 -0
  75. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dd/2.png +0 -0
  76. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dd/3.png +0 -0
  77. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dd/4.png +0 -0
  78. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dd/5.png +0 -0
  79. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/dd/__init__.py +0 -0
  80. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/__init__.py +0 -0
  81. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/busy.mp3.gz +0 -0
  82. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/easymde.css.gz +0 -0
  83. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/easymde.js.gz +0 -0
  84. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/marked.js.gz +0 -0
  85. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/mini-fa.css.gz +0 -0
  86. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/mini-fa.woff +0 -0
  87. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/prism.css.gz +0 -0
  88. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/prism.js.gz +0 -0
  89. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/prismd.css.gz +0 -0
  90. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/scp.woff2 +0 -0
  91. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  92. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  93. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/md.css.gz +0 -0
  94. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/md.html +0 -0
  95. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/md.js.gz +0 -0
  96. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/md2.css.gz +0 -0
  97. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/md2.js.gz +0 -0
  98. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/mde.css.gz +0 -0
  99. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/mde.html +0 -0
  100. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/mde.js.gz +0 -0
  101. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/msg.css.gz +0 -0
  102. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/msg.html +0 -0
  103. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/splash.css.gz +0 -0
  104. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/splash.html +0 -0
  105. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/splash.js.gz +0 -0
  106. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/svcs.html +0 -0
  107. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/svcs.js.gz +0 -0
  108. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/ui.css.gz +0 -0
  109. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/util.js.gz +0 -0
  110. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty/web/w.hash.js.gz +0 -0
  111. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/SOURCES.txt +0 -0
  112. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/dependency_links.txt +0 -0
  113. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/entry_points.txt +0 -0
  114. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/requires.txt +0 -0
  115. {copyparty-1.12.2 → copyparty-1.13.0}/copyparty.egg-info/top_level.txt +0 -0
  116. {copyparty-1.12.2 → copyparty-1.13.0}/pyproject.toml +0 -0
  117. {copyparty-1.12.2 → 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.12.2
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,6 +64,8 @@ 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
 
@@ -71,7 +73,7 @@ turn almost any device into a file server with resumable uploads/downloads using
71
73
  * [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
72
74
  * [at home](#at-home) - make it accessible over the internet
73
75
  * [on servers](#on-servers) - you may also want these, especially on servers
74
- * [features](#features)
76
+ * [features](#features) - also see [comparison to similar software](./docs/versus.md)
75
77
  * [testimonials](#testimonials) - small collection of user feedback
76
78
  * [motivations](#motivations) - project goals / philosophy
77
79
  * [notes](#notes) - general notes
@@ -92,6 +94,7 @@ turn almost any device into a file server with resumable uploads/downloads using
92
94
  * [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server
93
95
  * [unpost](#unpost) - undo/delete accidental uploads
94
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))
95
98
  * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
96
99
  * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
97
100
  * [media player](#media-player) - plays almost every audio format there is
@@ -181,7 +184,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
181
184
 
182
185
  * **Alpine:** `apk add py3-pillow ffmpeg`
183
186
  * **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
184
- * **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg`
187
+ * **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
185
188
  * **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg`
186
189
  * **MacOS:** `port install py-Pillow ffmpeg`
187
190
  * **MacOS** (alternative): `brew install pillow ffmpeg`
@@ -236,6 +239,8 @@ firewall-cmd --reload
236
239
 
237
240
  ## features
238
241
 
242
+ also see [comparison to similar software](./docs/versus.md)
243
+
239
244
  * backend stuff
240
245
  * ☑ IPv6
241
246
  * ☑ [multiprocessing](#performance) (actual multithreading)
@@ -258,6 +263,7 @@ firewall-cmd --reload
258
263
  * ☑ write-only folders
259
264
  * ☑ [unpost](#unpost): undo/delete accidental uploads
260
265
  * ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
266
+ * ☑ [race the beam](#race-the-beam) (almost like peer-to-peer)
261
267
  * ☑ symlink/discard duplicates (content-matching)
262
268
  * download
263
269
  * ☑ single files in browser
@@ -683,7 +689,7 @@ up2k has several advantages:
683
689
  > it is perfectly safe to restart / upgrade copyparty while someone is uploading to it!
684
690
  > all known up2k clients will resume just fine 💪
685
691
 
686
- 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)
687
693
 
688
694
  ![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/129635371-48fc54ca-fa91-48e3-9b1d-ba413e4b68cb.png)
689
695
 
@@ -749,6 +755,13 @@ clients can specify a shorter expiration time using the [up2k ui](#uploading) --
749
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
750
756
 
751
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
+
752
765
  ## file manager
753
766
 
754
767
  cut/paste, rename, and delete files/folders (if you have permission)
@@ -10,6 +10,8 @@ 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
 
@@ -17,7 +19,7 @@ turn almost any device into a file server with resumable uploads/downloads using
17
19
  * [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
18
20
  * [at home](#at-home) - make it accessible over the internet
19
21
  * [on servers](#on-servers) - you may also want these, especially on servers
20
- * [features](#features)
22
+ * [features](#features) - also see [comparison to similar software](./docs/versus.md)
21
23
  * [testimonials](#testimonials) - small collection of user feedback
22
24
  * [motivations](#motivations) - project goals / philosophy
23
25
  * [notes](#notes) - general notes
@@ -38,6 +40,7 @@ turn almost any device into a file server with resumable uploads/downloads using
38
40
  * [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server
39
41
  * [unpost](#unpost) - undo/delete accidental uploads
40
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))
41
44
  * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
42
45
  * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
43
46
  * [media player](#media-player) - plays almost every audio format there is
@@ -127,7 +130,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
127
130
 
128
131
  * **Alpine:** `apk add py3-pillow ffmpeg`
129
132
  * **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
130
- * **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg`
133
+ * **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
131
134
  * **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg`
132
135
  * **MacOS:** `port install py-Pillow ffmpeg`
133
136
  * **MacOS** (alternative): `brew install pillow ffmpeg`
@@ -182,6 +185,8 @@ firewall-cmd --reload
182
185
 
183
186
  ## features
184
187
 
188
+ also see [comparison to similar software](./docs/versus.md)
189
+
185
190
  * backend stuff
186
191
  * ☑ IPv6
187
192
  * ☑ [multiprocessing](#performance) (actual multithreading)
@@ -204,6 +209,7 @@ firewall-cmd --reload
204
209
  * ☑ write-only folders
205
210
  * ☑ [unpost](#unpost): undo/delete accidental uploads
206
211
  * ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
212
+ * ☑ [race the beam](#race-the-beam) (almost like peer-to-peer)
207
213
  * ☑ symlink/discard duplicates (content-matching)
208
214
  * download
209
215
  * ☑ single files in browser
@@ -629,7 +635,7 @@ up2k has several advantages:
629
635
  > it is perfectly safe to restart / upgrade copyparty while someone is uploading to it!
630
636
  > all known up2k clients will resume just fine 💪
631
637
 
632
- 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)
633
639
 
634
640
  ![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/129635371-48fc54ca-fa91-48e3-9b1d-ba413e4b68cb.png)
635
641
 
@@ -695,6 +701,13 @@ clients can specify a shorter expiration time using the [up2k ui](#uploading) --
695
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
696
702
 
697
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
+
698
711
  ## file manager
699
712
 
700
713
  cut/paste, rename, and delete files/folders (if you have permission)
@@ -850,7 +850,7 @@ 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 = "5/0.1" if ANYWIN else "0/0"
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
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)")
856
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)")
@@ -1085,6 +1085,8 @@ def add_optouts(ap):
1085
1085
  ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
1086
1086
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1087
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")
1088
1090
 
1089
1091
 
1090
1092
  def add_safety(ap):
@@ -1210,7 +1212,7 @@ def add_db_general(ap, hcores):
1210
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)")
1211
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)")
1212
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")
1213
- ap2.add_argument("--re-dhash", action="store_true", help="rebuild the cache if it gets out of sync (for example crash on startup during metadata scanning)")
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)")
1214
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)")
1215
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)")
1216
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, 12, 2)
4
- CODENAME = "locksmith"
5
- BUILD_DT = (2024, 4, 12)
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)
@@ -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",
@@ -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,
@@ -123,6 +124,7 @@ class HttpCli(object):
123
124
  self.ico = conn.ico # mypy404
124
125
  self.thumbcli = conn.thumbcli # mypy404
125
126
  self.u2fh = conn.u2fh # mypy404
127
+ self.pipes = conn.pipes # mypy404
126
128
  self.log_func = conn.log_func # mypy404
127
129
  self.log_src = conn.log_src # mypy404
128
130
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
@@ -2925,17 +2927,42 @@ class HttpCli(object):
2925
2927
 
2926
2928
  return txt
2927
2929
 
2928
- def tx_file(self, req_path ) :
2930
+ def tx_file(self, req_path , ptop = None) :
2929
2931
  status = 200
2930
2932
  logmsg = "{:4} {} ".format("", self.req)
2931
2933
  logtail = ""
2932
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
+
2933
2954
  #
2934
2955
  # if request is for foo.js, check if we have foo.js.gz
2935
2956
 
2936
2957
  file_ts = 0.0
2937
2958
  editions = {}
2938
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
+
2939
2966
  try:
2940
2967
  fs_path = req_path + ext
2941
2968
  st = bos.stat(fs_path)
@@ -3092,6 +3119,11 @@ class HttpCli(object):
3092
3119
  self.send_headers(length=upper - lower, status=status, mime=mime)
3093
3120
  return True
3094
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
+
3095
3127
  ret = True
3096
3128
  with open_func(*open_args) as f:
3097
3129
  self.send_headers(length=upper - lower, status=status, mime=mime)
@@ -3111,6 +3143,143 @@ class HttpCli(object):
3111
3143
 
3112
3144
  return ret
3113
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
+
3114
3283
  def tx_zip(
3115
3284
  self,
3116
3285
  fmt ,
@@ -3748,7 +3917,7 @@ class HttpCli(object):
3748
3917
  if not allvols:
3749
3918
  ret = [{"kinshi": 1}]
3750
3919
 
3751
- jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, indent=0))
3920
+ jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
3752
3921
  zi = len(uret.split('\n"pd":')) - 1
3753
3922
  self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
3754
3923
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
@@ -4027,7 +4196,9 @@ class HttpCli(object):
4027
4196
  ):
4028
4197
  return self.tx_md(vn, abspath)
4029
4198
 
4030
- return self.tx_file(abspath)
4199
+ return self.tx_file(
4200
+ abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
4201
+ )
4031
4202
 
4032
4203
  elif is_dir and not self.can_read:
4033
4204
  if self._use_dirkey(abspath):
@@ -52,6 +52,7 @@ class HttpConn(object):
52
52
  self.E = self.args.E
53
53
  self.asrv = hsrv.asrv # mypy404
54
54
  self.u2fh = hsrv.u2fh # mypy404
55
+ self.pipes = hsrv.pipes # mypy404
55
56
  self.ipa_nm = hsrv.ipa_nm
56
57
  self.xff_nm = hsrv.xff_nm
57
58
  self.xff_lan = hsrv.xff_lan # type: ignore
@@ -61,6 +61,7 @@ from .u2idx import U2idx
61
61
  from .util import (
62
62
  E_SCK,
63
63
  FHC,
64
+ CachedDict,
64
65
  Daemon,
65
66
  Garda,
66
67
  Magician,
@@ -126,6 +127,7 @@ class HttpSrv(object):
126
127
  self.t_periodic = None
127
128
 
128
129
  self.u2fh = FHC()
130
+ self.pipes = CachedDict(0.2)
129
131
  self.metrics = Metrics(self)
130
132
  self.nreq = 0
131
133
  self.nsus = 0