copyparty 1.13.1__tar.gz → 1.13.3__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 (121) hide show
  1. {copyparty-1.13.1 → copyparty-1.13.3}/PKG-INFO +7 -4
  2. {copyparty-1.13.1 → copyparty-1.13.3}/README.md +6 -3
  3. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/__main__.py +52 -8
  4. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/__version__.py +2 -2
  5. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/authsrv.py +16 -4
  6. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/broker_mp.py +2 -5
  7. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/cfg.py +1 -0
  8. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/httpcli.py +12 -8
  9. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/httpsrv.py +1 -4
  10. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/mdns.py +23 -2
  11. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/mtag.py +74 -3
  12. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/smbd.py +1 -1
  13. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/ssdp.py +22 -2
  14. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/star.py +4 -4
  15. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/sutil.py +12 -6
  16. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/svchub.py +9 -2
  17. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/szip.py +4 -4
  18. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/th_cli.py +5 -0
  19. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/th_srv.py +51 -13
  20. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/up2k.py +16 -10
  21. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/util.py +54 -14
  22. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/a/u2c.py +26 -9
  23. copyparty-1.13.3/copyparty/web/baguettebox.js.gz +0 -0
  24. copyparty-1.13.3/copyparty/web/browser.css.gz +0 -0
  25. copyparty-1.13.3/copyparty/web/browser.js.gz +0 -0
  26. copyparty-1.13.3/copyparty/web/deps/marked.js.gz +0 -0
  27. copyparty-1.13.3/copyparty/web/md2.js.gz +0 -0
  28. copyparty-1.13.3/copyparty/web/splash.js.gz +0 -0
  29. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/PKG-INFO +7 -4
  30. copyparty-1.13.1/copyparty/web/baguettebox.js.gz +0 -0
  31. copyparty-1.13.1/copyparty/web/browser.css.gz +0 -0
  32. copyparty-1.13.1/copyparty/web/browser.js.gz +0 -0
  33. copyparty-1.13.1/copyparty/web/deps/marked.js.gz +0 -0
  34. copyparty-1.13.1/copyparty/web/md2.js.gz +0 -0
  35. copyparty-1.13.1/copyparty/web/splash.js.gz +0 -0
  36. {copyparty-1.13.1 → copyparty-1.13.3}/LICENSE +0 -0
  37. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/__init__.py +0 -0
  38. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/bos/__init__.py +0 -0
  39. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/bos/bos.py +0 -0
  40. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/bos/path.py +0 -0
  41. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/broker_mpw.py +0 -0
  42. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/broker_thr.py +0 -0
  43. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/broker_util.py +0 -0
  44. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/cert.py +0 -0
  45. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/dxml.py +0 -0
  46. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/fsutil.py +0 -0
  47. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/ftpd.py +0 -0
  48. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/httpconn.py +0 -0
  49. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/ico.py +0 -0
  50. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/metrics.py +0 -0
  51. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/multicast.py +0 -0
  52. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/pwhash.py +0 -0
  53. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/res/COPYING.txt +0 -0
  54. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/res/__init__.py +0 -0
  55. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/res/insecure.pem +0 -0
  56. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/__init__.py +0 -0
  57. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/__init__.py +0 -0
  58. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/bimap.py +0 -0
  59. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/bit.py +0 -0
  60. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/buffer.py +0 -0
  61. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/dns.py +0 -0
  62. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/label.py +0 -0
  63. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/lex.py +0 -0
  64. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/dnslib/ranges.py +0 -0
  65. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/ifaddr/__init__.py +0 -0
  66. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/ifaddr/_posix.py +0 -0
  67. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/ifaddr/_shared.py +0 -0
  68. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/ifaddr/_win32.py +0 -0
  69. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/qrcodegen.py +0 -0
  70. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/stolen/surrogateescape.py +0 -0
  71. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/tcpsrv.py +0 -0
  72. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/tftpd.py +0 -0
  73. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/u2idx.py +0 -0
  74. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/a/__init__.py +0 -0
  75. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/a/partyfuse.py +0 -0
  76. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/a/webdav-cfg.bat +0 -0
  77. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/browser.html +0 -0
  78. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/browser2.html +0 -0
  79. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/cf.html +0 -0
  80. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dbg-audio.js.gz +0 -0
  81. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dd/2.png +0 -0
  82. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dd/3.png +0 -0
  83. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dd/4.png +0 -0
  84. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dd/5.png +0 -0
  85. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/dd/__init__.py +0 -0
  86. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/__init__.py +0 -0
  87. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/busy.mp3.gz +0 -0
  88. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/easymde.css.gz +0 -0
  89. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/easymde.js.gz +0 -0
  90. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/mini-fa.css.gz +0 -0
  91. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/mini-fa.woff +0 -0
  92. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/prism.css.gz +0 -0
  93. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/prism.js.gz +0 -0
  94. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/prismd.css.gz +0 -0
  95. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/scp.woff2 +0 -0
  96. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  97. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  98. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/md.css.gz +0 -0
  99. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/md.html +0 -0
  100. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/md.js.gz +0 -0
  101. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/md2.css.gz +0 -0
  102. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/mde.css.gz +0 -0
  103. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/mde.html +0 -0
  104. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/mde.js.gz +0 -0
  105. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/msg.css.gz +0 -0
  106. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/msg.html +0 -0
  107. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/splash.css.gz +0 -0
  108. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/splash.html +0 -0
  109. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/svcs.html +0 -0
  110. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/svcs.js.gz +0 -0
  111. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/ui.css.gz +0 -0
  112. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/up2k.js.gz +0 -0
  113. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/util.js.gz +0 -0
  114. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty/web/w.hash.js.gz +0 -0
  115. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/SOURCES.txt +0 -0
  116. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/dependency_links.txt +0 -0
  117. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/entry_points.txt +0 -0
  118. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/requires.txt +0 -0
  119. {copyparty-1.13.1 → copyparty-1.13.3}/copyparty.egg-info/top_level.txt +0 -0
  120. {copyparty-1.13.1 → copyparty-1.13.3}/pyproject.toml +0 -0
  121. {copyparty-1.13.1 → copyparty-1.13.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.13.1
3
+ Version: 1.13.3
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
@@ -284,6 +284,7 @@ also see [comparison to similar software](./docs/versus.md)
284
284
  * ☑ ...of videos using FFmpeg
285
285
  * ☑ ...of audio (spectrograms) using FFmpeg
286
286
  * ☑ cache eviction (max-age; maybe max-size eventually)
287
+ * ☑ multilingual UI (english, norwegian, [add your own](./docs/rice/#translations)))
287
288
  * ☑ SPA (browse while uploading)
288
289
  * server indexing
289
290
  * ☑ [locate files by contents](#file-search)
@@ -466,7 +467,7 @@ configuring accounts/volumes with arguments:
466
467
  `-v .::r,usr1,usr2:rw,usr3,usr4` = usr1/2 read-only, 3/4 read-write
467
468
 
468
469
  permissions:
469
- * `r` (read): browse folder contents, download files, download as zip/tar
470
+ * `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
470
471
  * `w` (write): upload files, move files *into* this folder
471
472
  * `m` (move): move files/folders *from* this folder
472
473
  * `d` (delete): delete files/folders
@@ -668,7 +669,7 @@ you can also zip a selection of files or folders by clicking them in the browser
668
669
 
669
670
  cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
670
671
  * super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
671
- * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
672
+ * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
672
673
  * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
673
674
 
674
675
 
@@ -964,6 +965,8 @@ using arguments or config files, or a mix of both:
964
965
 
965
966
  **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
966
967
  * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
968
+ * or see this (probably outdated): https://ocv.me/copyparty/helptext.html
969
+ * or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
967
970
 
968
971
 
969
972
  ## zeroconf
@@ -1140,7 +1143,7 @@ tweaking the ui
1140
1143
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
1141
1144
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1142
1145
 
1143
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag
1146
+ see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1144
1147
 
1145
1148
 
1146
1149
  ## opengraph
@@ -230,6 +230,7 @@ also see [comparison to similar software](./docs/versus.md)
230
230
  * ☑ ...of videos using FFmpeg
231
231
  * ☑ ...of audio (spectrograms) using FFmpeg
232
232
  * ☑ cache eviction (max-age; maybe max-size eventually)
233
+ * ☑ multilingual UI (english, norwegian, [add your own](./docs/rice/#translations)))
233
234
  * ☑ SPA (browse while uploading)
234
235
  * server indexing
235
236
  * ☑ [locate files by contents](#file-search)
@@ -412,7 +413,7 @@ configuring accounts/volumes with arguments:
412
413
  `-v .::r,usr1,usr2:rw,usr3,usr4` = usr1/2 read-only, 3/4 read-write
413
414
 
414
415
  permissions:
415
- * `r` (read): browse folder contents, download files, download as zip/tar
416
+ * `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
416
417
  * `w` (write): upload files, move files *into* this folder
417
418
  * `m` (move): move files/folders *from* this folder
418
419
  * `d` (delete): delete files/folders
@@ -614,7 +615,7 @@ you can also zip a selection of files or folders by clicking them in the browser
614
615
 
615
616
  cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
616
617
  * super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
617
- * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
618
+ * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
618
619
  * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
619
620
 
620
621
 
@@ -910,6 +911,8 @@ using arguments or config files, or a mix of both:
910
911
 
911
912
  **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
912
913
  * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
914
+ * or see this (probably outdated): https://ocv.me/copyparty/helptext.html
915
+ * or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
913
916
 
914
917
 
915
918
  ## zeroconf
@@ -1086,7 +1089,7 @@ tweaking the ui
1086
1089
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
1087
1090
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1088
1091
 
1089
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag
1092
+ see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1090
1093
 
1091
1094
 
1092
1095
  ## opengraph
@@ -13,6 +13,7 @@ import base64
13
13
  import locale
14
14
  import os
15
15
  import re
16
+ import select
16
17
  import socket
17
18
  import sys
18
19
  import threading
@@ -43,11 +44,13 @@ from .util import (
43
44
  DEF_MTH,
44
45
  IMPLICATIONS,
45
46
  JINJA_VER,
47
+ MIMES,
46
48
  PARTFTPY_VER,
47
49
  PY_DESC,
48
50
  PYFTPD_VER,
49
51
  SQLITE_VER,
50
52
  UNPLICATIONS,
53
+ Daemon,
51
54
  align_tab,
52
55
  ansi_re,
53
56
  dedent,
@@ -165,8 +168,10 @@ def init_E(EE ) :
165
168
  (os.environ.get, "TMP"),
166
169
  (unicode, "/tmp"),
167
170
  ]
171
+ errs = []
168
172
  for chk in [os.listdir, os.mkdir]:
169
- for pf, pa in paths:
173
+ for npath, (pf, pa) in enumerate(paths):
174
+ p = ""
170
175
  try:
171
176
  p = pf(pa)
172
177
  # print(chk.__name__, p, pa)
@@ -179,9 +184,20 @@ def init_E(EE ) :
179
184
  if not os.path.isdir(p):
180
185
  os.mkdir(p)
181
186
 
187
+ if npath > 1:
188
+ t = "Using [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
189
+ errs.append(t % (p,))
190
+ elif errs:
191
+ errs.append("Using [%s] instead" % (p,))
192
+
193
+ if errs:
194
+ print("WARNING: " + ". ".join(errs))
195
+
182
196
  return p # type: ignore
183
- except:
184
- pass
197
+ except Exception as ex:
198
+ if p and npath < 2:
199
+ t = "Unable to store config in [%s] due to %r"
200
+ errs.append(t % (p, ex))
185
201
 
186
202
  raise Exception("could not find a writable path for config")
187
203
 
@@ -464,6 +480,16 @@ def disable_quickedit() :
464
480
  cmode(True, mode | 4)
465
481
 
466
482
 
483
+ def sfx_tpoke(top ):
484
+ files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
485
+ while True:
486
+ t = int(time.time())
487
+ for f in [top] + files:
488
+ os.utime(f, (t, t))
489
+
490
+ time.sleep(78123)
491
+
492
+
467
493
  def showlic() :
468
494
  p = os.path.join(E.mod, "res", "COPYING.txt")
469
495
  if not os.path.exists(p):
@@ -814,7 +840,7 @@ def build_flags_desc():
814
840
  v = v.replace("\n", "\n ")
815
841
  ret += "\n \033[36m{}\033[35m {}".format(k, v)
816
842
 
817
- return ret + "\033[0m"
843
+ return ret
818
844
 
819
845
 
820
846
  # fmt: off
@@ -832,6 +858,8 @@ def add_general(ap, nc, srvname):
832
858
  ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
833
859
  ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
834
860
  ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
861
+ ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
862
+ ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
835
863
  ap2.add_argument("--license", action="store_true", help="show licenses and exit")
836
864
  ap2.add_argument("--version", action="store_true", help="show versions and exit")
837
865
 
@@ -878,7 +906,7 @@ def add_upload(ap):
878
906
  ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
879
907
  ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
880
908
  ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
881
- ap2.add_argument("--df", metavar="GiB", type=float, default=0, help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests")
909
+ ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
882
910
  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")
883
911
  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")
884
912
  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)")
@@ -1188,7 +1216,8 @@ def add_thumbnail(ap):
1188
1216
  ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1189
1217
  ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1190
1218
  ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
1191
- ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,m4a,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,tak,tta,ulaw,wav,wma,wv,xm,xpk", help="audio formats to decode using ffmpeg")
1219
+ ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1220
+ ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz", help="audio formats to decompress before passing to ffmpeg")
1192
1221
 
1193
1222
 
1194
1223
  def add_transcoding(ap):
@@ -1301,6 +1330,8 @@ def add_debug(ap):
1301
1330
  ap2 = ap.add_argument_group('debug options')
1302
1331
  ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
1303
1332
  ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
1333
+ if hasattr(select, "poll"):
1334
+ ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)")
1304
1335
  ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
1305
1336
  ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
1306
1337
  ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
@@ -1399,7 +1430,7 @@ def run_argparse(
1399
1430
  k2 = "help_" + k.replace("-", "_")
1400
1431
  if vars(ret)[k2]:
1401
1432
  lprint("# %s help page (%s)" % (k, h))
1402
- lprint(t + "\033[0m")
1433
+ lprint(t.rstrip() + "\033[0m")
1403
1434
  sys.exit(0)
1404
1435
 
1405
1436
  return ret
@@ -1438,9 +1469,19 @@ def main(argv = None, rsrc = None) :
1438
1469
  showlic()
1439
1470
  sys.exit(0)
1440
1471
 
1472
+ if "--mimes" in argv:
1473
+ print("\n".join("%8s %s" % (k, v) for k, v in sorted(MIMES.items())))
1474
+ sys.exit(0)
1475
+
1441
1476
  if EXE:
1442
1477
  print("pybin: {}\n".format(pybin), end="")
1443
1478
 
1479
+ for n, zs in enumerate(argv):
1480
+ if zs.startswith("--sfx-tpoke="):
1481
+ Daemon(sfx_tpoke, "sfx-tpoke", (zs.split("=", 1)[1],))
1482
+ argv.pop(n)
1483
+ break
1484
+
1444
1485
  ensure_locale()
1445
1486
 
1446
1487
  ensure_webdeps()
@@ -1501,7 +1542,7 @@ def main(argv = None, rsrc = None) :
1501
1542
  if hard > 0: # -1 == infinite
1502
1543
  nc = min(nc, int(hard / 4))
1503
1544
  except:
1504
- nc = 512
1545
+ nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
1505
1546
 
1506
1547
  retry = False
1507
1548
  for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
@@ -1594,6 +1635,9 @@ def main(argv = None, rsrc = None) :
1594
1635
  if not hasattr(os, "sendfile"):
1595
1636
  al.no_sendfile = True
1596
1637
 
1638
+ if not hasattr(select, "poll"):
1639
+ al.no_poll = True
1640
+
1597
1641
  # signal.signal(signal.SIGINT, sighandler)
1598
1642
 
1599
1643
  SvcHub(al, dal, argv, "".join(printed)).run()
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 13, 1)
3
+ VERSION = (1, 13, 3)
4
4
  CODENAME = "race the beam"
5
- BUILD_DT = (2024, 5, 6)
5
+ BUILD_DT = (2024, 6, 1)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -17,7 +17,9 @@ from .bos import bos
17
17
  from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
18
18
  from .pwhash import PWHash
19
19
  from .util import (
20
+ EXTS,
20
21
  IMPLICATIONS,
22
+ MIMES,
21
23
  SQLITE_VER,
22
24
  UNPLICATIONS,
23
25
  UTC,
@@ -1608,11 +1610,14 @@ class AuthSrv(object):
1608
1610
  use = True
1609
1611
  lim.nosub = True
1610
1612
 
1611
- zs = vol.flags.get("df") or (
1612
- "{}g".format(self.args.df) if self.args.df else ""
1613
- )
1614
- if zs:
1613
+ zs = vol.flags.get("df") or self.args.df or ""
1614
+ if zs not in ("", "0"):
1615
1615
  use = True
1616
+ try:
1617
+ _ = float(zs)
1618
+ zs = "%sg" % (zs)
1619
+ except:
1620
+ pass
1616
1621
  lim.dfl = unhumanize(zs)
1617
1622
 
1618
1623
  zs = vol.flags.get("sz")
@@ -2058,6 +2063,13 @@ class AuthSrv(object):
2058
2063
 
2059
2064
  self.re_pwd = re.compile(zs)
2060
2065
 
2066
+ # to ensure it propagates into tcpsrv with mp on
2067
+ if self.args.mime:
2068
+ for zs in self.args.mime:
2069
+ ext, mime = zs.split("=", 1)
2070
+ MIMES[ext] = mime
2071
+ EXTS.update({v: k for k, v in MIMES.items()})
2072
+
2061
2073
  def setup_pwhash(self, acct ) :
2062
2074
  self.ah = PWHash(self.args)
2063
2075
  if not self.ah.on:
@@ -53,11 +53,8 @@ class BrokerMp(object):
53
53
  def shutdown(self) :
54
54
  self.log("broker", "shutting down")
55
55
  for n, proc in enumerate(self.procs):
56
- thr = threading.Thread(
57
- target=proc.q_pend.put((0, "shutdown", [])),
58
- name="mp-shutdown-{}-{}".format(n, len(self.procs)),
59
- )
60
- thr.start()
56
+ name = "mp-shut-%d-%d" % (n, len(self.procs))
57
+ Daemon(proc.q_pend.put, name, ((0, "shutdown", []),))
61
58
 
62
59
  with self.mutex:
63
60
  procs = self.procs
@@ -190,6 +190,7 @@ flagcats = {
190
190
  "dvthumb": "disables video thumbnails",
191
191
  "dathumb": "disables audio thumbnails (spectrograms)",
192
192
  "dithumb": "disables image thumbnails",
193
+ "pngquant": "compress audio waveforms 33% better",
193
194
  "thsize": "thumbnail res; WxH",
194
195
  "crop": "center-cropping (y/n/fy/fn)",
195
196
  "th3x": "3x resolution (y/n/fy/fn)",
@@ -755,7 +755,6 @@ class HttpCli(object):
755
755
  is_jinja = True
756
756
 
757
757
  if is_jinja:
758
- print("applying jinja")
759
758
  with self.conn.hsrv.mutex:
760
759
  if html not in self.conn.hsrv.j2:
761
760
  j2env = jinja2.Environment()
@@ -3190,7 +3189,14 @@ class HttpCli(object):
3190
3189
 
3191
3190
  sendfun = sendfile_kern if use_sendfile else sendfile_py
3192
3191
  remains = sendfun(
3193
- self.log, lower, upper, f, self.s, self.args.s_wr_sz, self.args.s_wr_slp
3192
+ self.log,
3193
+ lower,
3194
+ upper,
3195
+ f,
3196
+ self.s,
3197
+ self.args.s_wr_sz,
3198
+ self.args.s_wr_slp,
3199
+ not self.args.no_poll,
3194
3200
  )
3195
3201
 
3196
3202
  if remains > 0:
@@ -3409,7 +3415,7 @@ class HttpCli(object):
3409
3415
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
3410
3416
  cfmt = ""
3411
3417
  if self.thumbcli and not self.args.no_bacode:
3412
- for zs in ("opus", "mp3", "w", "j"):
3418
+ for zs in ("opus", "mp3", "w", "j", "p"):
3413
3419
  if zs in self.ouparam or uarg == zs:
3414
3420
  cfmt = zs
3415
3421
 
@@ -3419,7 +3425,7 @@ class HttpCli(object):
3419
3425
 
3420
3426
  bgen = packer(
3421
3427
  self.log,
3422
- self.args,
3428
+ self.asrv,
3423
3429
  fgen,
3424
3430
  utf8="utf" in uarg,
3425
3431
  pre_crc="crc" in uarg,
@@ -4800,7 +4806,7 @@ class HttpCli(object):
4800
4806
  query = "th=%s&cache" % (fmt,)
4801
4807
  query = ub64enc(query.encode("utf-8")).decode("utf-8")
4802
4808
  # discord looks at file extension, not content-type...
4803
- query += "/a.jpg" if "j" in fmt else "/a.webp"
4809
+ query += "/th.jpg" if "j" in fmt else "/th.webp"
4804
4810
  j2a["og_thumb"] = "%s/.uqe/%s" % (th_base, query)
4805
4811
 
4806
4812
  j2a["og_fn"] = og_fn
@@ -4808,9 +4814,7 @@ class HttpCli(object):
4808
4814
  if og_fn:
4809
4815
  og_fn_q = quotep(og_fn)
4810
4816
  query = ub64enc(b"raw").decode("utf-8")
4811
- if "." in og_fn:
4812
- query += "/a.%s" % (og_fn.split(".")[-1])
4813
-
4817
+ query += "/%s" % (og_fn_q,)
4814
4818
  j2a["og_url"] = ujoin(url_base, og_fn_q)
4815
4819
  j2a["og_raw"] = j2a["og_url"] + "/.uqe/" + query
4816
4820
  else:
@@ -262,10 +262,7 @@ class HttpSrv(object):
262
262
  msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
263
263
  self.log(self.name, msg)
264
264
 
265
- def fun() :
266
- self.broker.say("cb_httpsrv_up")
267
-
268
- threading.Thread(target=fun, name="sig-hsrv-up1").start()
265
+ Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
269
266
 
270
267
  while not self.stopping:
271
268
  if self.args.log_conn:
@@ -288,6 +288,22 @@ class MDNS(MCast):
288
288
  def run2(self) :
289
289
  last_hop = time.time()
290
290
  ihop = self.args.mc_hop
291
+
292
+ try:
293
+ if self.args.no_poll:
294
+ raise Exception()
295
+ fd2sck = {}
296
+ srvpoll = select.poll()
297
+ for sck in self.srv:
298
+ fd = sck.fileno()
299
+ fd2sck[fd] = sck
300
+ srvpoll.register(fd, select.POLLIN)
301
+ except Exception as ex:
302
+ srvpoll = None
303
+ if not self.args.no_poll:
304
+ t = "WARNING: failed to poll(), will use select() instead: %r"
305
+ self.log(t % (ex,), 3)
306
+
291
307
  while self.running:
292
308
  timeout = (
293
309
  0.02 + random.random() * 0.07
@@ -296,8 +312,13 @@ class MDNS(MCast):
296
312
  if self.unsolicited
297
313
  else (last_hop + ihop if ihop else 180)
298
314
  )
299
- rdy = select.select(self.srv, [], [], timeout)
300
- rx = rdy[0] # type: ignore
315
+ if srvpoll:
316
+ pr = srvpoll.poll(timeout * 1000)
317
+ rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
318
+ else:
319
+ rdy = select.select(self.srv, [], [], timeout)
320
+ rx = rdy[0] # type: ignore
321
+
301
322
  self.rx4.cln()
302
323
  self.rx6.cln()
303
324
  buf = b""
@@ -7,12 +7,15 @@ import os
7
7
  import shutil
8
8
  import subprocess as sp
9
9
  import sys
10
+ import tempfile
10
11
 
11
12
  from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
13
+ from .authsrv import VFS
12
14
  from .bos import bos
13
15
  from .util import (
14
16
  FFMPEG_URL,
15
17
  REKOBO_LKEY,
18
+ VF_CAREFUL,
16
19
  fsenc,
17
20
  min_ex,
18
21
  pybin,
@@ -20,6 +23,7 @@ from .util import (
20
23
  runcmd,
21
24
  sfsenc,
22
25
  uncyg,
26
+ wunlink,
23
27
  )
24
28
 
25
29
  def have_ff(scmd ) :
@@ -101,6 +105,53 @@ class MParser(object):
101
105
  raise Exception()
102
106
 
103
107
 
108
+ def au_unpk(
109
+ log , fmt_map , abspath , vn = None
110
+ ) :
111
+ ret = ""
112
+ try:
113
+ ext = abspath.split(".")[-1].lower()
114
+ au, pk = fmt_map[ext].split(".")
115
+
116
+ fd, ret = tempfile.mkstemp("." + au)
117
+
118
+ if pk == "gz":
119
+ import gzip
120
+
121
+ fi = gzip.GzipFile(abspath, mode="rb")
122
+
123
+ elif pk == "xz":
124
+ import lzma
125
+
126
+ fi = lzma.open(abspath, "rb")
127
+
128
+ elif pk == "zip":
129
+ import zipfile
130
+
131
+ zf = zipfile.ZipFile(abspath, "r")
132
+ zil = zf.infolist()
133
+ zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
134
+ fi = zf.open(zil[0])
135
+
136
+ with os.fdopen(fd, "wb") as fo:
137
+ while True:
138
+ buf = fi.read(32768)
139
+ if not buf:
140
+ break
141
+
142
+ fo.write(buf)
143
+
144
+ return ret
145
+
146
+ except Exception as ex:
147
+ if ret:
148
+ t = "failed to decompress audio file [%s]: %r"
149
+ log(t % (abspath, ex))
150
+ wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
151
+
152
+ return abspath
153
+
154
+
104
155
  def ffprobe(
105
156
  abspath , timeout = 60
106
157
  ) :
@@ -275,7 +326,7 @@ class MTag(object):
275
326
  or_ffprobe = " or FFprobe"
276
327
 
277
328
  if self.backend == "mutagen":
278
- self.get = self.get_mutagen
329
+ self._get = self.get_mutagen
279
330
  try:
280
331
  from mutagen import version # noqa: F401
281
332
  except:
@@ -284,7 +335,7 @@ class MTag(object):
284
335
 
285
336
  if self.backend == "ffprobe":
286
337
  self.usable = self.can_ffprobe
287
- self.get = self.get_ffprobe
338
+ self._get = self.get_ffprobe
288
339
  self.prefer_mt = True
289
340
 
290
341
  if not HAVE_FFPROBE:
@@ -454,6 +505,17 @@ class MTag(object):
454
505
 
455
506
  return r1
456
507
 
508
+ def get(self, abspath ) :
509
+ ext = abspath.split(".")[-1].lower()
510
+ if ext not in self.args.au_unpk:
511
+ return self._get(abspath)
512
+
513
+ ap = au_unpk(self.log, self.args.au_unpk, abspath)
514
+ ret = self._get(ap)
515
+ if ap != abspath:
516
+ wunlink(self.log, ap, VF_CAREFUL)
517
+ return ret
518
+
457
519
  def get_mutagen(self, abspath ) :
458
520
  ret = {}
459
521
 
@@ -547,10 +609,16 @@ class MTag(object):
547
609
  except:
548
610
  raise # might be expected outside cpython
549
611
 
612
+ ext = abspath.split(".")[-1].lower()
613
+ if ext in self.args.au_unpk:
614
+ ap = au_unpk(self.log, self.args.au_unpk, abspath)
615
+ else:
616
+ ap = abspath
617
+
550
618
  ret = {}
551
619
  for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
552
620
  try:
553
- cmd = [parser.bin, abspath]
621
+ cmd = [parser.bin, ap]
554
622
  if parser.bin.endswith(".py"):
555
623
  cmd = [pybin] + cmd
556
624
 
@@ -587,4 +655,7 @@ class MTag(object):
587
655
  t = "mtag error: tagname {}, parser {}, file {} => {}"
588
656
  self.log(t.format(tagname, parser.bin, abspath, min_ex()))
589
657
 
658
+ if ap != abspath:
659
+ wunlink(self.log, ap, VF_CAREFUL)
660
+
590
661
  return ret
@@ -124,7 +124,7 @@ class SMB(object):
124
124
  self.log("smb", msg, c)
125
125
 
126
126
  def start(self) :
127
- Daemon(self.srv.start)
127
+ Daemon(self.srv.start, "smbd")
128
128
 
129
129
  def _auth_cb(self, *a, **ka):
130
130
  debug("auth-result: %s %s", a, ka)
@@ -137,9 +137,29 @@ class SSDPd(MCast):
137
137
  self.log("stopped", 2)
138
138
 
139
139
  def run2(self) :
140
+ try:
141
+ if self.args.no_poll:
142
+ raise Exception()
143
+ fd2sck = {}
144
+ srvpoll = select.poll()
145
+ for sck in self.srv:
146
+ fd = sck.fileno()
147
+ fd2sck[fd] = sck
148
+ srvpoll.register(fd, select.POLLIN)
149
+ except Exception as ex:
150
+ srvpoll = None
151
+ if not self.args.no_poll:
152
+ t = "WARNING: failed to poll(), will use select() instead: %r"
153
+ self.log(t % (ex,), 3)
154
+
140
155
  while self.running:
141
- rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
142
- rx = rdy[0] # type: ignore
156
+ if srvpoll:
157
+ pr = srvpoll.poll((self.args.z_chk or 180) * 1000)
158
+ rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
159
+ else:
160
+ rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
161
+ rx = rdy[0] # type: ignore
162
+
143
163
  self.rxc.cln()
144
164
  buf = b""
145
165
  addr = ("0", 0)
@@ -1,13 +1,13 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- import argparse
5
4
  import re
6
5
  import stat
7
6
  import tarfile
8
7
 
9
8
  from queue import Queue
10
9
 
10
+ from .authsrv import AuthSrv
11
11
  from .bos import bos
12
12
  from .sutil import StreamArc, errdesc
13
13
  from .util import Daemon, fsenc, min_ex
@@ -39,12 +39,12 @@ class StreamTar(StreamArc):
39
39
  def __init__(
40
40
  self,
41
41
  log ,
42
- args ,
42
+ asrv ,
43
43
  fgen ,
44
44
  cmp = "",
45
45
  **kwargs
46
46
  ):
47
- super(StreamTar, self).__init__(log, args, fgen)
47
+ super(StreamTar, self).__init__(log, asrv, fgen)
48
48
 
49
49
  self.ci = 0
50
50
  self.co = 0
@@ -142,7 +142,7 @@ class StreamTar(StreamArc):
142
142
  errors.append((f["vp"], ex))
143
143
 
144
144
  if errors:
145
- self.errf, txt = errdesc(errors)
145
+ self.errf, txt = errdesc(self.asrv.vfs, errors)
146
146
  self.log("\n".join(([repr(self.errf)] + txt[1:])))
147
147
  self.ser(self.errf)
148
148