copyparty 1.13.4__tar.gz → 1.13.6__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 (120) hide show
  1. {copyparty-1.13.4 → copyparty-1.13.6}/PKG-INFO +9 -4
  2. {copyparty-1.13.4 → copyparty-1.13.6}/README.md +8 -3
  3. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/__main__.py +10 -3
  4. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/__version__.py +2 -2
  5. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/broker_util.py +3 -3
  6. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/httpcli.py +52 -42
  7. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/th_cli.py +3 -2
  8. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/th_srv.py +16 -8
  9. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/up2k.py +44 -28
  10. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/util.py +1 -1
  11. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/a/u2c.py +123 -38
  12. copyparty-1.13.6/copyparty/web/baguettebox.js.gz +0 -0
  13. copyparty-1.13.6/copyparty/web/browser.js.gz +0 -0
  14. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/svcs.html +13 -12
  15. copyparty-1.13.6/copyparty/web/ui.css.gz +0 -0
  16. copyparty-1.13.6/copyparty/web/up2k.js.gz +0 -0
  17. copyparty-1.13.6/copyparty/web/util.js.gz +0 -0
  18. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/w.hash.js.gz +0 -0
  19. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/PKG-INFO +9 -4
  20. copyparty-1.13.4/copyparty/web/baguettebox.js.gz +0 -0
  21. copyparty-1.13.4/copyparty/web/browser.js.gz +0 -0
  22. copyparty-1.13.4/copyparty/web/ui.css.gz +0 -0
  23. copyparty-1.13.4/copyparty/web/up2k.js.gz +0 -0
  24. copyparty-1.13.4/copyparty/web/util.js.gz +0 -0
  25. {copyparty-1.13.4 → copyparty-1.13.6}/LICENSE +0 -0
  26. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/__init__.py +0 -0
  27. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/authsrv.py +0 -0
  28. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/bos/__init__.py +0 -0
  29. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/bos/bos.py +0 -0
  30. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/bos/path.py +0 -0
  31. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/broker_mp.py +0 -0
  32. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/broker_mpw.py +0 -0
  33. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/broker_thr.py +0 -0
  34. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/cert.py +0 -0
  35. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/cfg.py +0 -0
  36. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/dxml.py +0 -0
  37. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/fsutil.py +0 -0
  38. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/ftpd.py +0 -0
  39. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/httpconn.py +0 -0
  40. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/httpsrv.py +0 -0
  41. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/ico.py +0 -0
  42. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/mdns.py +0 -0
  43. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/metrics.py +0 -0
  44. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/mtag.py +0 -0
  45. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/multicast.py +0 -0
  46. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/pwhash.py +0 -0
  47. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/res/COPYING.txt +0 -0
  48. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/res/__init__.py +0 -0
  49. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/res/insecure.pem +0 -0
  50. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/smbd.py +0 -0
  51. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/ssdp.py +0 -0
  52. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/star.py +0 -0
  53. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/__init__.py +0 -0
  54. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/__init__.py +0 -0
  55. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/bimap.py +0 -0
  56. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/bit.py +0 -0
  57. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/buffer.py +0 -0
  58. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/dns.py +0 -0
  59. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/label.py +0 -0
  60. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/lex.py +0 -0
  61. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/dnslib/ranges.py +0 -0
  62. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/ifaddr/__init__.py +0 -0
  63. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/ifaddr/_posix.py +0 -0
  64. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/ifaddr/_shared.py +0 -0
  65. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/ifaddr/_win32.py +0 -0
  66. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/qrcodegen.py +0 -0
  67. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/stolen/surrogateescape.py +0 -0
  68. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/sutil.py +0 -0
  69. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/svchub.py +0 -0
  70. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/szip.py +0 -0
  71. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/tcpsrv.py +0 -0
  72. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/tftpd.py +0 -0
  73. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/u2idx.py +0 -0
  74. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/a/__init__.py +0 -0
  75. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/a/partyfuse.py +0 -0
  76. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/a/webdav-cfg.bat +0 -0
  77. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/browser.css.gz +0 -0
  78. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/browser.html +0 -0
  79. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/browser2.html +0 -0
  80. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/cf.html +0 -0
  81. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dbg-audio.js.gz +0 -0
  82. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dd/2.png +0 -0
  83. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dd/3.png +0 -0
  84. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dd/4.png +0 -0
  85. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dd/5.png +0 -0
  86. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/dd/__init__.py +0 -0
  87. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/__init__.py +0 -0
  88. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/busy.mp3.gz +0 -0
  89. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/easymde.css.gz +0 -0
  90. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/easymde.js.gz +0 -0
  91. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/marked.js.gz +0 -0
  92. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/mini-fa.css.gz +0 -0
  93. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/mini-fa.woff +0 -0
  94. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/prism.css.gz +0 -0
  95. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/prism.js.gz +0 -0
  96. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/prismd.css.gz +0 -0
  97. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/scp.woff2 +0 -0
  98. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  99. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  100. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/md.css.gz +0 -0
  101. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/md.html +0 -0
  102. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/md.js.gz +0 -0
  103. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/md2.css.gz +0 -0
  104. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/md2.js.gz +0 -0
  105. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/mde.css.gz +0 -0
  106. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/mde.html +0 -0
  107. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/mde.js.gz +0 -0
  108. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/msg.css.gz +0 -0
  109. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/msg.html +0 -0
  110. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/splash.css.gz +0 -0
  111. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/splash.html +0 -0
  112. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/splash.js.gz +0 -0
  113. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty/web/svcs.js.gz +0 -0
  114. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/SOURCES.txt +0 -0
  115. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/dependency_links.txt +0 -0
  116. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/entry_points.txt +0 -0
  117. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/requires.txt +0 -0
  118. {copyparty-1.13.4 → copyparty-1.13.6}/copyparty.egg-info/top_level.txt +0 -0
  119. {copyparty-1.13.4 → copyparty-1.13.6}/pyproject.toml +0 -0
  120. {copyparty-1.13.4 → copyparty-1.13.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.13.4
3
+ Version: 1.13.6
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
@@ -263,7 +263,7 @@ also see [comparison to similar software](./docs/versus.md)
263
263
  * upload
264
264
  * ☑ basic: plain multipart, ie6 support
265
265
  * ☑ [up2k](#uploading): js, resumable, multithreaded
266
- * unaffected by cloudflare's max-upload-size (100 MiB)
266
+ * **no filesize limit!** ...unless you use Cloudflare, then it's 383.9 GiB
267
267
  * ☑ stash: simple PUT filedropper
268
268
  * ☑ filename randomizer
269
269
  * ☑ write-only folders
@@ -279,6 +279,7 @@ also see [comparison to similar software](./docs/versus.md)
279
279
  * ☑ [navpane](#navpane) (directory tree sidebar)
280
280
  * ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
281
281
  * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
282
+ * ☑ play video files as audio (converted on server)
282
283
  * ☑ image gallery with webm player
283
284
  * ☑ textfile browser with syntax hilighting
284
285
  * ☑ [thumbnails](#thumbnails)
@@ -700,6 +701,7 @@ up2k has several advantages:
700
701
  * uploads resume if you reboot your browser or pc, just upload the same files again
701
702
  * server detects any corruption; the client reuploads affected chunks
702
703
  * the client doesn't upload anything that already exists on the server
704
+ * no filesize limit unless imposed by a proxy, for example Cloudflare, which blocks uploads over 383.9 GiB
703
705
  * much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
704
706
  * the last-modified timestamp of the file is preserved
705
707
 
@@ -854,6 +856,7 @@ some hilights:
854
856
  * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
855
857
  * shows the audio waveform in the seekbar
856
858
  * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
859
+ * videos can be played as audio, without wasting bandwidth on the video
857
860
 
858
861
  click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)
859
862
 
@@ -1041,7 +1044,7 @@ some recommended FTP / FTPS clients; `wark` = example password:
1041
1044
 
1042
1045
  ## webdav server
1043
1046
 
1044
- with read-write support, supports winXP and later, macos, nautilus/gvfs
1047
+ with read-write support, supports winXP and later, macos, nautilus/gvfs ... a greay way to [access copyparty straight from the file explorer in your OS](#mount-as-drive)
1045
1048
 
1046
1049
  click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to see connection instructions for windows, linux, macos
1047
1050
 
@@ -1365,6 +1368,8 @@ you can set hooks before and/or after an event happens, and currently you can ho
1365
1368
 
1366
1369
  there's a bunch of flags and stuff, see `--help-hooks`
1367
1370
 
1371
+ if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
1372
+
1368
1373
 
1369
1374
  ### upload events
1370
1375
 
@@ -1852,7 +1857,7 @@ alternatively, some alternatives roughly sorted by speed (unreproducible benchma
1852
1857
  * [rclone-http](./docs/rclone.md) (26s), read-only
1853
1858
  * [partyfuse.py](./bin/#partyfusepy) (35s), read-only
1854
1859
  * [rclone-ftp](./docs/rclone.md) (47s), read/WRITE
1855
- * davfs2 (103s), read/WRITE, *very fast* on small files
1860
+ * davfs2 (103s), read/WRITE
1856
1861
  * [win10-webdav](#webdav-server) (138s), read/WRITE
1857
1862
  * [win10-smb2](#smb-server) (387s), read/WRITE
1858
1863
 
@@ -209,7 +209,7 @@ also see [comparison to similar software](./docs/versus.md)
209
209
  * upload
210
210
  * ☑ basic: plain multipart, ie6 support
211
211
  * ☑ [up2k](#uploading): js, resumable, multithreaded
212
- * unaffected by cloudflare's max-upload-size (100 MiB)
212
+ * **no filesize limit!** ...unless you use Cloudflare, then it's 383.9 GiB
213
213
  * ☑ stash: simple PUT filedropper
214
214
  * ☑ filename randomizer
215
215
  * ☑ write-only folders
@@ -225,6 +225,7 @@ also see [comparison to similar software](./docs/versus.md)
225
225
  * ☑ [navpane](#navpane) (directory tree sidebar)
226
226
  * ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
227
227
  * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
228
+ * ☑ play video files as audio (converted on server)
228
229
  * ☑ image gallery with webm player
229
230
  * ☑ textfile browser with syntax hilighting
230
231
  * ☑ [thumbnails](#thumbnails)
@@ -646,6 +647,7 @@ up2k has several advantages:
646
647
  * uploads resume if you reboot your browser or pc, just upload the same files again
647
648
  * server detects any corruption; the client reuploads affected chunks
648
649
  * the client doesn't upload anything that already exists on the server
650
+ * no filesize limit unless imposed by a proxy, for example Cloudflare, which blocks uploads over 383.9 GiB
649
651
  * much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
650
652
  * the last-modified timestamp of the file is preserved
651
653
 
@@ -800,6 +802,7 @@ some hilights:
800
802
  * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
801
803
  * shows the audio waveform in the seekbar
802
804
  * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
805
+ * videos can be played as audio, without wasting bandwidth on the video
803
806
 
804
807
  click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)
805
808
 
@@ -987,7 +990,7 @@ some recommended FTP / FTPS clients; `wark` = example password:
987
990
 
988
991
  ## webdav server
989
992
 
990
- with read-write support, supports winXP and later, macos, nautilus/gvfs
993
+ with read-write support, supports winXP and later, macos, nautilus/gvfs ... a greay way to [access copyparty straight from the file explorer in your OS](#mount-as-drive)
991
994
 
992
995
  click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to see connection instructions for windows, linux, macos
993
996
 
@@ -1311,6 +1314,8 @@ you can set hooks before and/or after an event happens, and currently you can ho
1311
1314
 
1312
1315
  there's a bunch of flags and stuff, see `--help-hooks`
1313
1316
 
1317
+ if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
1318
+
1314
1319
 
1315
1320
  ### upload events
1316
1321
 
@@ -1798,7 +1803,7 @@ alternatively, some alternatives roughly sorted by speed (unreproducible benchma
1798
1803
  * [rclone-http](./docs/rclone.md) (26s), read-only
1799
1804
  * [partyfuse.py](./bin/#partyfusepy) (35s), read-only
1800
1805
  * [rclone-ftp](./docs/rclone.md) (47s), read/WRITE
1801
- * davfs2 (103s), read/WRITE, *very fast* on small files
1806
+ * davfs2 (103s), read/WRITE
1802
1807
  * [win10-webdav](#webdav-server) (138s), read/WRITE
1803
1808
  * [win10-smb2](#smb-server) (387s), read/WRITE
1804
1809
 
@@ -485,11 +485,17 @@ def disable_quickedit() :
485
485
 
486
486
 
487
487
  def sfx_tpoke(top ):
488
- files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
488
+ files = [top] + [
489
+ os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df
490
+ ]
489
491
  while True:
490
492
  t = int(time.time())
491
- for f in [top] + files:
492
- os.utime(f, (t, t))
493
+ for f in list(files):
494
+ try:
495
+ os.utime(f, (t, t))
496
+ except Exception as ex:
497
+ lprint("<TPOKE> [%s] %r" % (f, ex))
498
+ files.remove(f)
493
499
 
494
500
  time.sleep(78123)
495
501
 
@@ -936,6 +942,7 @@ def add_upload(ap):
936
942
  ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
937
943
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
938
944
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
945
+ ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for this size. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
939
946
  ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
940
947
  ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
941
948
 
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 13, 4)
3
+ VERSION = (1, 13, 6)
4
4
  CODENAME = "race the beam"
5
- BUILD_DT = (2024, 7, 16)
5
+ BUILD_DT = (2024, 7, 29)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -23,7 +23,7 @@ class ExceptionalQueue(Queue, object):
23
23
  if rv[1] == "pebkac":
24
24
  raise Pebkac(*rv[2:])
25
25
  else:
26
- raise Exception(rv[2])
26
+ raise rv[2]
27
27
 
28
28
  return rv
29
29
 
@@ -60,8 +60,8 @@ def try_exec(want_retval , func , *args ) :
60
60
 
61
61
  return ["exception", "pebkac", ex.code, str(ex)]
62
62
 
63
- except:
63
+ except Exception as ex:
64
64
  if not want_retval:
65
65
  raise
66
66
 
67
- return ["exception", "stack", traceback.format_exc()]
67
+ return ["exception", "stack", ex]
@@ -642,11 +642,8 @@ class HttpCli(object):
642
642
  if not self._check_nonfatal(pex, post):
643
643
  self.keepalive = False
644
644
 
645
- if pex is ex:
646
- em = msg = str(ex)
647
- else:
648
- em = repr(ex)
649
- msg = min_ex()
645
+ em = str(ex)
646
+ msg = em if pex is ex else min_ex()
650
647
 
651
648
  if pex.code != 404 or self.do_log:
652
649
  self.log(
@@ -2198,33 +2195,39 @@ class HttpCli(object):
2198
2195
 
2199
2196
  def handle_post_binary(self) :
2200
2197
  try:
2201
- remains = int(self.headers["content-length"])
2198
+ postsize = remains = int(self.headers["content-length"])
2202
2199
  except:
2203
2200
  raise Pebkac(400, "you must supply a content-length for binary POST")
2204
2201
 
2205
2202
  try:
2206
- chash = self.headers["x-up2k-hash"]
2203
+ chashes = self.headers["x-up2k-hash"].split(",")
2207
2204
  wark = self.headers["x-up2k-wark"]
2208
2205
  except KeyError:
2209
2206
  raise Pebkac(400, "need hash and wark headers for binary POST")
2210
2207
 
2208
+ chashes = [x.strip() for x in chashes]
2209
+
2211
2210
  vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2212
2211
  ptop = (vfs.dbv or vfs).realpath
2213
2212
 
2214
- x = self.conn.hsrv.broker.ask("up2k.handle_chunk", ptop, wark, chash)
2213
+ x = self.conn.hsrv.broker.ask("up2k.handle_chunks", ptop, wark, chashes)
2215
2214
  response = x.get()
2216
- chunksize, cstart, path, lastmod, sprs = response
2215
+ chunksize, cstarts, path, lastmod, sprs = response
2216
+ maxsize = chunksize * len(chashes)
2217
+ cstart0 = cstarts[0]
2217
2218
 
2218
2219
  try:
2219
2220
  if self.args.nw:
2220
2221
  path = os.devnull
2221
2222
 
2222
- if remains > chunksize:
2223
- raise Pebkac(400, "your chunk is too big to fit")
2224
-
2225
- self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains))
2223
+ if remains > maxsize:
2224
+ t = "your client is sending %d bytes which is too much (server expected %d bytes at most)"
2225
+ raise Pebkac(400, t % (remains, maxsize))
2226
2226
 
2227
- reader = read_socket(self.sr, self.args.s_rd_sz, remains)
2227
+ t = "writing %s %s+%d #%d+%d %s"
2228
+ chunkno = cstart0[0] // chunksize
2229
+ zs = " ".join([chashes[0][:15]] + [x[:9] for x in chashes[1:]])
2230
+ self.log(t % (path, cstart0, remains, chunkno, len(chashes), zs))
2228
2231
 
2229
2232
  f = None
2230
2233
  fpool = not self.args.no_fpool and sprs
@@ -2238,37 +2241,43 @@ class HttpCli(object):
2238
2241
  f = f or open(fsenc(path), "rb+", self.args.iobuf)
2239
2242
 
2240
2243
  try:
2241
- f.seek(cstart[0])
2242
- post_sz, _, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
2244
+ for chash, cstart in zip(chashes, cstarts):
2245
+ f.seek(cstart[0])
2246
+ reader = read_socket(
2247
+ self.sr, self.args.s_rd_sz, min(remains, chunksize)
2248
+ )
2249
+ post_sz, _, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
2243
2250
 
2244
- if sha_b64 != chash:
2245
- try:
2246
- self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
2247
- except:
2248
- self.log("bakflip failed: " + min_ex())
2251
+ if sha_b64 != chash:
2252
+ try:
2253
+ self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
2254
+ except:
2255
+ self.log("bakflip failed: " + min_ex())
2249
2256
 
2250
- t = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
2251
- raise Pebkac(400, t.format(post_sz, chash, sha_b64))
2257
+ t = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
2258
+ raise Pebkac(400, t.format(post_sz, chash, sha_b64))
2252
2259
 
2253
- if len(cstart) > 1 and path != os.devnull:
2254
- self.log(
2255
- "clone {} to {}".format(
2256
- cstart[0], " & ".join(unicode(x) for x in cstart[1:])
2260
+ remains -= chunksize
2261
+
2262
+ if len(cstart) > 1 and path != os.devnull:
2263
+ self.log(
2264
+ "clone {} to {}".format(
2265
+ cstart[0], " & ".join(unicode(x) for x in cstart[1:])
2266
+ )
2257
2267
  )
2258
- )
2259
- ofs = 0
2260
- while ofs < chunksize:
2261
- bufsz = max(4 * 1024 * 1024, self.args.iobuf)
2262
- bufsz = min(chunksize - ofs, bufsz)
2263
- f.seek(cstart[0] + ofs)
2264
- buf = f.read(bufsz)
2265
- for wofs in cstart[1:]:
2266
- f.seek(wofs + ofs)
2267
- f.write(buf)
2268
+ ofs = 0
2269
+ while ofs < chunksize:
2270
+ bufsz = max(4 * 1024 * 1024, self.args.iobuf)
2271
+ bufsz = min(chunksize - ofs, bufsz)
2272
+ f.seek(cstart[0] + ofs)
2273
+ buf = f.read(bufsz)
2274
+ for wofs in cstart[1:]:
2275
+ f.seek(wofs + ofs)
2276
+ f.write(buf)
2268
2277
 
2269
- ofs += len(buf)
2278
+ ofs += len(buf)
2270
2279
 
2271
- self.log("clone {} done".format(cstart[0]))
2280
+ self.log("clone {} done".format(cstart[0]))
2272
2281
 
2273
2282
  if not fpool:
2274
2283
  f.close()
@@ -2280,10 +2289,10 @@ class HttpCli(object):
2280
2289
  f.close()
2281
2290
  raise
2282
2291
  finally:
2283
- x = self.conn.hsrv.broker.ask("up2k.release_chunk", ptop, wark, chash)
2292
+ x = self.conn.hsrv.broker.ask("up2k.release_chunks", ptop, wark, chashes)
2284
2293
  x.get() # block client until released
2285
2294
 
2286
- x = self.conn.hsrv.broker.ask("up2k.confirm_chunk", ptop, wark, chash)
2295
+ x = self.conn.hsrv.broker.ask("up2k.confirm_chunks", ptop, wark, chashes)
2287
2296
  ztis = x.get()
2288
2297
  try:
2289
2298
  num_left, fin_path = ztis
@@ -2302,7 +2311,7 @@ class HttpCli(object):
2302
2311
 
2303
2312
  cinf = self.headers.get("x-up2k-stat", "")
2304
2313
 
2305
- spd = self._spd(post_sz)
2314
+ spd = self._spd(postsize)
2306
2315
  self.log("{:70} thank {}".format(spd, cinf))
2307
2316
  self.reply(b"thank")
2308
2317
  return True
@@ -4499,6 +4508,7 @@ class HttpCli(object):
4499
4508
  "themes": self.args.themes,
4500
4509
  "turbolvl": self.args.turbo,
4501
4510
  "u2j": self.args.u2j,
4511
+ "u2sz": self.args.u2sz,
4502
4512
  "idxh": int(self.args.ih),
4503
4513
  "u2sort": self.args.u2sort,
4504
4514
  }
@@ -56,7 +56,8 @@ class ThumbCli(object):
56
56
 
57
57
  want_opus = fmt in ("opus", "caf", "mp3")
58
58
  is_au = ext in self.fmt_ffa
59
- if is_au:
59
+ is_vau = want_opus and ext in self.fmt_ffv
60
+ if is_au or is_vau:
60
61
  if want_opus:
61
62
  if self.args.no_acode:
62
63
  return None
@@ -104,7 +105,7 @@ class ThumbCli(object):
104
105
 
105
106
  fmt = sfmt
106
107
 
107
- elif fmt[:1] == "p" and not is_au:
108
+ elif fmt[:1] == "p" and not is_au and not is_vid:
108
109
  t = "cannot thumbnail [%s]: png only allowed for waveforms"
109
110
  self.log(t % (rem), 6)
110
111
  return None
@@ -301,23 +301,31 @@ class ThumbSrv(object):
301
301
  ap_unpk = abspath
302
302
 
303
303
  if not bos.path.exists(tpath):
304
+ want_mp3 = tpath.endswith(".mp3")
305
+ want_opus = tpath.endswith(".opus") or tpath.endswith(".caf")
306
+ want_png = tpath.endswith(".png")
307
+ want_au = want_mp3 or want_opus
304
308
  for lib in self.args.th_dec:
309
+ can_au = lib == "ff" and (
310
+ ext in self.fmt_ffa or ext in self.fmt_ffv
311
+ )
312
+
305
313
  if lib == "pil" and ext in self.fmt_pil:
306
314
  funs.append(self.conv_pil)
307
315
  elif lib == "vips" and ext in self.fmt_vips:
308
316
  funs.append(self.conv_vips)
309
- elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
310
- funs.append(self.conv_ffmpeg)
311
- elif lib == "ff" and ext in self.fmt_ffa:
312
- if tpath.endswith(".opus") or tpath.endswith(".caf"):
317
+ elif can_au and (want_png or want_au):
318
+ if want_opus:
313
319
  funs.append(self.conv_opus)
314
- elif tpath.endswith(".mp3"):
320
+ elif want_mp3:
315
321
  funs.append(self.conv_mp3)
316
- elif tpath.endswith(".png"):
322
+ elif want_png:
317
323
  funs.append(self.conv_waves)
318
324
  png_ok = True
319
- else:
320
- funs.append(self.conv_spec)
325
+ elif lib == "ff" and (ext in self.fmt_ffi or ext in self.fmt_ffv):
326
+ funs.append(self.conv_ffmpeg)
327
+ elif lib == "ff" and ext in self.fmt_ffa and not want_au:
328
+ funs.append(self.conv_spec)
321
329
 
322
330
  tdir, tfn = os.path.split(tpath)
323
331
  ttpath = os.path.join(tdir, "w", tfn)
@@ -542,7 +542,7 @@ class Up2k(object):
542
542
  nrm += 1
543
543
 
544
544
  if nrm:
545
- self.log("{} files graduated in {}".format(nrm, vp))
545
+ self.log("%d files graduated in /%s" % (nrm, vp))
546
546
 
547
547
  if timeout < 10:
548
548
  continue
@@ -1293,7 +1293,7 @@ class Up2k(object):
1293
1293
  not cv
1294
1294
  or liname not in th_cvds
1295
1295
  or cv.lower() not in th_cvds
1296
- or th_cvd.index(iname) < th_cvd.index(cv)
1296
+ or th_cvd.index(liname) < th_cvd.index(cv.lower())
1297
1297
  )
1298
1298
  ):
1299
1299
  cv = iname
@@ -3010,8 +3010,8 @@ class Up2k(object):
3010
3010
  times = (int(time.time()), int(lmod))
3011
3011
  bos.utime(dst, times, False)
3012
3012
 
3013
- def handle_chunk(
3014
- self, ptop , wark , chash
3013
+ def handle_chunks(
3014
+ self, ptop , wark , chashes
3015
3015
  ) :
3016
3016
  with self.mutex, self.reg_mutex:
3017
3017
  self.db_act = self.vol_act[ptop] = time.time()
@@ -3021,26 +3021,37 @@ class Up2k(object):
3021
3021
  self.log("unknown wark [{}], known: {}".format(wark, known))
3022
3022
  raise Pebkac(400, "unknown wark" + SSEELOG)
3023
3023
 
3024
- if chash not in job["need"]:
3025
- msg = "chash = {} , need:\n".format(chash)
3026
- msg += "\n".join(job["need"])
3027
- self.log(msg)
3028
- raise Pebkac(400, "already got that but thanks??")
3024
+ for chash in chashes:
3025
+ if chash not in job["need"]:
3026
+ msg = "chash = {} , need:\n".format(chash)
3027
+ msg += "\n".join(job["need"])
3028
+ self.log(msg)
3029
+ raise Pebkac(400, "already got that (%s) but thanks??" % (chash,))
3029
3030
 
3030
- nchunk = [n for n, v in enumerate(job["hash"]) if v == chash]
3031
- if not nchunk:
3032
- raise Pebkac(400, "unknown chunk")
3031
+ if chash in job["busy"]:
3032
+ nh = len(job["hash"])
3033
+ idx = job["hash"].index(chash)
3034
+ t = "that chunk is already being written to:\n {}\n {} {}/{}\n {}"
3035
+ raise Pebkac(400, t.format(wark, chash, idx, nh, job["name"]))
3033
3036
 
3034
- if chash in job["busy"]:
3035
- nh = len(job["hash"])
3036
- idx = job["hash"].index(chash)
3037
- t = "that chunk is already being written to:\n {}\n {} {}/{}\n {}"
3038
- raise Pebkac(400, t.format(wark, chash, idx, nh, job["name"]))
3037
+ chunksize = up2k_chunksize(job["size"])
3039
3038
 
3040
- path = djoin(job["ptop"], job["prel"], job["tnam"])
3039
+ coffsets = []
3040
+ for chash in chashes:
3041
+ nchunk = [n for n, v in enumerate(job["hash"]) if v == chash]
3042
+ if not nchunk:
3043
+ raise Pebkac(400, "unknown chunk %s" % (chash))
3041
3044
 
3042
- chunksize = up2k_chunksize(job["size"])
3043
- ofs = [chunksize * x for x in nchunk]
3045
+ ofs = [chunksize * x for x in nchunk]
3046
+ coffsets.append(ofs)
3047
+
3048
+ for ofs1, ofs2 in zip(coffsets, coffsets[1:]):
3049
+ gap = (ofs2[0] - ofs1[0]) - chunksize
3050
+ if gap:
3051
+ t = "only sibling chunks can be stitched; gap of %d bytes between offsets %d and %d in %s"
3052
+ raise Pebkac(400, t % (gap, ofs1[0], ofs2[0], job["name"]))
3053
+
3054
+ path = djoin(job["ptop"], job["prel"], job["tnam"])
3044
3055
 
3045
3056
  if not job["sprs"]:
3046
3057
  cur_sz = bos.path.getsize(path)
@@ -3053,17 +3064,20 @@ class Up2k(object):
3053
3064
 
3054
3065
  job["poke"] = time.time()
3055
3066
 
3056
- return chunksize, ofs, path, job["lmod"], job["sprs"]
3067
+ return chunksize, coffsets, path, job["lmod"], job["sprs"]
3057
3068
 
3058
- def release_chunk(self, ptop , wark , chash ) :
3069
+ def release_chunks(self, ptop , wark , chashes ) :
3059
3070
  with self.reg_mutex:
3060
3071
  job = self.registry[ptop].get(wark)
3061
3072
  if job:
3062
- job["busy"].pop(chash, None)
3073
+ for chash in chashes:
3074
+ job["busy"].pop(chash, None)
3063
3075
 
3064
3076
  return True
3065
3077
 
3066
- def confirm_chunk(self, ptop , wark , chash ) :
3078
+ def confirm_chunks(
3079
+ self, ptop , wark , chashes
3080
+ ) :
3067
3081
  with self.mutex, self.reg_mutex:
3068
3082
  self.db_act = self.vol_act[ptop] = time.time()
3069
3083
  try:
@@ -3072,14 +3086,16 @@ class Up2k(object):
3072
3086
  src = djoin(pdir, job["tnam"])
3073
3087
  dst = djoin(pdir, job["name"])
3074
3088
  except Exception as ex:
3075
- return "confirm_chunk, wark, " + repr(ex) # type: ignore
3089
+ return "confirm_chunk, wark(%r)" % (ex,) # type: ignore
3076
3090
 
3077
- job["busy"].pop(chash, None)
3091
+ for chash in chashes:
3092
+ job["busy"].pop(chash, None)
3078
3093
 
3079
3094
  try:
3080
- job["need"].remove(chash)
3095
+ for chash in chashes:
3096
+ job["need"].remove(chash)
3081
3097
  except Exception as ex:
3082
- return "confirm_chunk, chash, " + repr(ex) # type: ignore
3098
+ return "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore
3083
3099
 
3084
3100
  ret = len(job["need"])
3085
3101
  if ret > 0:
@@ -1356,7 +1356,7 @@ def vol_san(vols , txt ) :
1356
1356
  def min_ex(max_lines = 8, reverse = False) :
1357
1357
  et, ev, tb = sys.exc_info()
1358
1358
  stb = traceback.extract_tb(tb) if tb else traceback.extract_stack()[:-1]
1359
- fmt = "%s @ %d <%s>: %s"
1359
+ fmt = "%s:%d <%s>: %s"
1360
1360
  ex = [fmt % (fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in stb]
1361
1361
  if et or ev or tb:
1362
1362
  ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))