copyparty 1.9.12__tar.gz → 1.9.14__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 (114) hide show
  1. {copyparty-1.9.12 → copyparty-1.9.14}/PKG-INFO +33 -18
  2. {copyparty-1.9.12 → copyparty-1.9.14}/README.md +32 -17
  3. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/__main__.py +13 -8
  4. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/__version__.py +2 -2
  5. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/authsrv.py +21 -13
  6. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/cfg.py +29 -2
  7. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/httpcli.py +31 -12
  8. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/sutil.py +2 -2
  9. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/svchub.py +28 -8
  10. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/u2idx.py +5 -0
  11. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/up2k.py +24 -17
  12. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/util.py +50 -2
  13. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/a/partyfuse.py +3 -2
  14. copyparty-1.9.14/copyparty/web/browser.css.gz +0 -0
  15. copyparty-1.9.14/copyparty/web/browser.js.gz +0 -0
  16. copyparty-1.9.14/copyparty/web/up2k.js.gz +0 -0
  17. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/PKG-INFO +33 -18
  18. copyparty-1.9.12/copyparty/web/browser.css.gz +0 -0
  19. copyparty-1.9.12/copyparty/web/browser.js.gz +0 -0
  20. copyparty-1.9.12/copyparty/web/up2k.js.gz +0 -0
  21. {copyparty-1.9.12 → copyparty-1.9.14}/LICENSE +0 -0
  22. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/__init__.py +0 -0
  23. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/bos/__init__.py +0 -0
  24. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/bos/bos.py +0 -0
  25. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/bos/path.py +0 -0
  26. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/broker_mp.py +0 -0
  27. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/broker_mpw.py +0 -0
  28. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/broker_thr.py +0 -0
  29. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/broker_util.py +0 -0
  30. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/cert.py +0 -0
  31. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/dxml.py +0 -0
  32. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/fsutil.py +0 -0
  33. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/ftpd.py +0 -0
  34. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/httpconn.py +0 -0
  35. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/httpsrv.py +0 -0
  36. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/ico.py +0 -0
  37. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/mdns.py +0 -0
  38. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/metrics.py +0 -0
  39. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/mtag.py +0 -0
  40. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/multicast.py +0 -0
  41. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/pwhash.py +0 -0
  42. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/res/COPYING.txt +0 -0
  43. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/res/__init__.py +0 -0
  44. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/res/insecure.pem +0 -0
  45. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/smbd.py +0 -0
  46. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/ssdp.py +0 -0
  47. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/star.py +0 -0
  48. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/__init__.py +0 -0
  49. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/__init__.py +0 -0
  50. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/bimap.py +0 -0
  51. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/bit.py +0 -0
  52. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/buffer.py +0 -0
  53. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/dns.py +0 -0
  54. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/label.py +0 -0
  55. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/lex.py +0 -0
  56. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/dnslib/ranges.py +0 -0
  57. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/ifaddr/__init__.py +0 -0
  58. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/ifaddr/_posix.py +0 -0
  59. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/ifaddr/_shared.py +0 -0
  60. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/ifaddr/_win32.py +0 -0
  61. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/qrcodegen.py +0 -0
  62. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/stolen/surrogateescape.py +0 -0
  63. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/szip.py +0 -0
  64. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/tcpsrv.py +0 -0
  65. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/th_cli.py +0 -0
  66. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/th_srv.py +0 -0
  67. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/a/__init__.py +0 -0
  68. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/a/u2c.py +0 -0
  69. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/a/webdav-cfg.bat +0 -0
  70. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/baguettebox.js.gz +0 -0
  71. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/browser.html +0 -0
  72. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/browser2.html +0 -0
  73. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/cf.html +0 -0
  74. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dbg-audio.js.gz +0 -0
  75. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dd/2.png +0 -0
  76. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dd/3.png +0 -0
  77. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dd/4.png +0 -0
  78. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dd/5.png +0 -0
  79. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/dd/__init__.py +0 -0
  80. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/__init__.py +0 -0
  81. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/marked.js.gz +0 -0
  82. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/mini-fa.css.gz +0 -0
  83. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/mini-fa.woff +0 -0
  84. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/prism.css.gz +0 -0
  85. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/prism.js.gz +0 -0
  86. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/prismd.css.gz +0 -0
  87. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/scp.woff2 +0 -0
  88. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  89. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  90. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/md.css.gz +0 -0
  91. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/md.html +0 -0
  92. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/md.js.gz +0 -0
  93. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/md2.css.gz +0 -0
  94. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/md2.js.gz +0 -0
  95. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/mde.css.gz +0 -0
  96. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/mde.html +0 -0
  97. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/mde.js.gz +0 -0
  98. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/msg.css.gz +0 -0
  99. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/msg.html +0 -0
  100. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/splash.css.gz +0 -0
  101. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/splash.html +0 -0
  102. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/splash.js.gz +0 -0
  103. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/svcs.html +0 -0
  104. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/svcs.js.gz +0 -0
  105. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/ui.css.gz +0 -0
  106. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/util.js.gz +0 -0
  107. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty/web/w.hash.js.gz +0 -0
  108. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/SOURCES.txt +0 -0
  109. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/dependency_links.txt +0 -0
  110. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/entry_points.txt +0 -0
  111. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/requires.txt +0 -0
  112. {copyparty-1.9.12 → copyparty-1.9.14}/copyparty.egg-info/top_level.txt +0 -0
  113. {copyparty-1.9.12 → copyparty-1.9.14}/pyproject.toml +0 -0
  114. {copyparty-1.9.12 → copyparty-1.9.14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.9.12
3
+ Version: 1.9.14
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
@@ -71,9 +71,8 @@ turn almost any device into a file server with resumable uploads/downloads using
71
71
  * [testimonials](#testimonials) - small collection of user feedback
72
72
  * [motivations](#motivations) - project goals / philosophy
73
73
  * [notes](#notes) - general notes
74
- * [bugs](#bugs)
75
- * [general bugs](#general-bugs)
76
- * [not my bugs](#not-my-bugs)
74
+ * [bugs](#bugs) - roughly sorted by chance of encounter
75
+ * [not my bugs](#not-my-bugs) - same order here too
77
76
  * [breaking changes](#breaking-changes) - upgrade notes
78
77
  * [FAQ](#FAQ) - "frequently" asked questions
79
78
  * [accounts and volumes](#accounts-and-volumes) - per-folder, per-user permissions
@@ -91,7 +90,7 @@ turn almost any device into a file server with resumable uploads/downloads using
91
90
  * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
92
91
  * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
93
92
  * [media player](#media-player) - plays almost every audio format there is
94
- * [audio equalizer](#audio-equalizer) - bass boosted
93
+ * [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
95
94
  * [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
96
95
  * [markdown viewer](#markdown-viewer) - and there are *two* editors
97
96
  * [other tricks](#other-tricks)
@@ -314,20 +313,28 @@ server notes:
314
313
 
315
314
  # bugs
316
315
 
317
- * Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
318
- * Windows: python 2.7 cannot handle filenames with mojibake
319
- * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
320
- * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
316
+ roughly sorted by chance of encounter
321
317
 
322
- ## general bugs
318
+ * general:
319
+ * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
320
+ * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
321
+ * if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
322
+ * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
323
+ * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
324
+ * probably more, pls let me know
323
325
 
324
- * Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
325
- * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
326
- * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
327
- * probably more, pls let me know
326
+ * python 3.4 and older (including 2.7):
327
+ * many rare and exciting edge-cases because [python didn't handle EINTR yet](https://peps.python.org/pep-0475/)
328
+ * downloads from copyparty may suddenly fail, but uploads *should* be fine
329
+
330
+ * python 2.7 on Windows:
331
+ * cannot index non-ascii filenames with `-e2d`
332
+ * cannot handle filenames with mojibake
328
333
 
329
334
  ## not my bugs
330
335
 
336
+ same order here too
337
+
331
338
  * [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
332
339
 
333
340
  * [Chrome issue 1352210](https://bugs.chromium.org/p/chromium/issues/detail?id=1352210) -- plaintext http may be faster at filehashing than https (but also extremely CPU-intensive)
@@ -410,7 +417,7 @@ permissions:
410
417
  * `g` (get): only download files, cannot see folder contents or zip/tar
411
418
  * `G` (upget): same as `g` except uploaders get to see their own [filekeys](#filekeys) (see `fk` in examples below)
412
419
  * `h` (html): same as `g` except folders return their index.html, and filekeys are not necessary for index.html
413
- * `a` (admin): can see uploader IPs, config-reload
420
+ * `a` (admin): can see upload time, uploader IPs, config-reload
414
421
 
415
422
  examples:
416
423
  * add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
@@ -787,7 +794,7 @@ open the `[🎺]` media-player-settings tab to configure it,
787
794
 
788
795
  ### audio equalizer
789
796
 
790
- bass boosted
797
+ and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
791
798
 
792
799
  can also boost the volume in general, or increase/decrease stereo width (like [crossfeed](https://www.foobar2000.org/components/view/foo_dsp_meiercf) just worse)
793
800
 
@@ -998,6 +1005,15 @@ authenticate with one of the following:
998
1005
  * username `$password`, password `k`
999
1006
 
1000
1007
 
1008
+ ## browser ux
1009
+
1010
+ tweaking the ui
1011
+
1012
+ * set default sort order globally with `--sort` or per-volume with the `sort` volflag; specify one or more comma-separated columns to sort by, and prefix the column name with `-` for reverse sort
1013
+ * column names are visible as tooltips when hovering over the column headers in the directory listing, for example `href ext sz ts tags/.up_at tags/Cirle tags/.tn tags/Artist tags/Title`
1014
+ * 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`
1015
+
1016
+
1001
1017
  ## file indexing
1002
1018
 
1003
1019
  enables dedup and music search ++
@@ -1024,6 +1040,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
1024
1040
  * `-v ~/music::r:c,d2ts` same except only affecting tags
1025
1041
 
1026
1042
  note:
1043
+ * upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
1027
1044
  * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
1028
1045
  * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
1029
1046
  * deduplication is possible on windows if you run copyparty as administrator (not saying you should!)
@@ -1687,8 +1704,6 @@ safety profiles:
1687
1704
  * `--hardlink` creates hardlinks instead of symlinks when deduplicating uploads, which is less maintenance
1688
1705
  * however note if you edit one file it will also affect the other copies
1689
1706
  * `--vague-403` returns a "404 not found" instead of "401 unauthorized" which is a common enterprise meme
1690
- * `--ban-404=50,60,1440` ban client for 1440min (24h) if they hit 50 404's in 60min
1691
- * `--turbo=-1` to force-disable turbo-mode in the uploader which could otherwise hit the 404-ban
1692
1707
  * `--nih` removes the server hostname from directory listings
1693
1708
 
1694
1709
  * option `-sss` is a shortcut for the above plus:
@@ -20,9 +20,8 @@ turn almost any device into a file server with resumable uploads/downloads using
20
20
  * [testimonials](#testimonials) - small collection of user feedback
21
21
  * [motivations](#motivations) - project goals / philosophy
22
22
  * [notes](#notes) - general notes
23
- * [bugs](#bugs)
24
- * [general bugs](#general-bugs)
25
- * [not my bugs](#not-my-bugs)
23
+ * [bugs](#bugs) - roughly sorted by chance of encounter
24
+ * [not my bugs](#not-my-bugs) - same order here too
26
25
  * [breaking changes](#breaking-changes) - upgrade notes
27
26
  * [FAQ](#FAQ) - "frequently" asked questions
28
27
  * [accounts and volumes](#accounts-and-volumes) - per-folder, per-user permissions
@@ -40,7 +39,7 @@ turn almost any device into a file server with resumable uploads/downloads using
40
39
  * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
41
40
  * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
42
41
  * [media player](#media-player) - plays almost every audio format there is
43
- * [audio equalizer](#audio-equalizer) - bass boosted
42
+ * [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
44
43
  * [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
45
44
  * [markdown viewer](#markdown-viewer) - and there are *two* editors
46
45
  * [other tricks](#other-tricks)
@@ -263,20 +262,28 @@ server notes:
263
262
 
264
263
  # bugs
265
264
 
266
- * Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
267
- * Windows: python 2.7 cannot handle filenames with mojibake
268
- * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
269
- * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
265
+ roughly sorted by chance of encounter
270
266
 
271
- ## general bugs
267
+ * general:
268
+ * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
269
+ * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
270
+ * if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
271
+ * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
272
+ * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
273
+ * probably more, pls let me know
272
274
 
273
- * Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
274
- * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
275
- * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
276
- * probably more, pls let me know
275
+ * python 3.4 and older (including 2.7):
276
+ * many rare and exciting edge-cases because [python didn't handle EINTR yet](https://peps.python.org/pep-0475/)
277
+ * downloads from copyparty may suddenly fail, but uploads *should* be fine
278
+
279
+ * python 2.7 on Windows:
280
+ * cannot index non-ascii filenames with `-e2d`
281
+ * cannot handle filenames with mojibake
277
282
 
278
283
  ## not my bugs
279
284
 
285
+ same order here too
286
+
280
287
  * [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
281
288
 
282
289
  * [Chrome issue 1352210](https://bugs.chromium.org/p/chromium/issues/detail?id=1352210) -- plaintext http may be faster at filehashing than https (but also extremely CPU-intensive)
@@ -359,7 +366,7 @@ permissions:
359
366
  * `g` (get): only download files, cannot see folder contents or zip/tar
360
367
  * `G` (upget): same as `g` except uploaders get to see their own [filekeys](#filekeys) (see `fk` in examples below)
361
368
  * `h` (html): same as `g` except folders return their index.html, and filekeys are not necessary for index.html
362
- * `a` (admin): can see uploader IPs, config-reload
369
+ * `a` (admin): can see upload time, uploader IPs, config-reload
363
370
 
364
371
  examples:
365
372
  * add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
@@ -736,7 +743,7 @@ open the `[🎺]` media-player-settings tab to configure it,
736
743
 
737
744
  ### audio equalizer
738
745
 
739
- bass boosted
746
+ and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
740
747
 
741
748
  can also boost the volume in general, or increase/decrease stereo width (like [crossfeed](https://www.foobar2000.org/components/view/foo_dsp_meiercf) just worse)
742
749
 
@@ -947,6 +954,15 @@ authenticate with one of the following:
947
954
  * username `$password`, password `k`
948
955
 
949
956
 
957
+ ## browser ux
958
+
959
+ tweaking the ui
960
+
961
+ * set default sort order globally with `--sort` or per-volume with the `sort` volflag; specify one or more comma-separated columns to sort by, and prefix the column name with `-` for reverse sort
962
+ * column names are visible as tooltips when hovering over the column headers in the directory listing, for example `href ext sz ts tags/.up_at tags/Cirle tags/.tn tags/Artist tags/Title`
963
+ * 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`
964
+
965
+
950
966
  ## file indexing
951
967
 
952
968
  enables dedup and music search ++
@@ -973,6 +989,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
973
989
  * `-v ~/music::r:c,d2ts` same except only affecting tags
974
990
 
975
991
  note:
992
+ * upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
976
993
  * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
977
994
  * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
978
995
  * deduplication is possible on windows if you run copyparty as administrator (not saying you should!)
@@ -1636,8 +1653,6 @@ safety profiles:
1636
1653
  * `--hardlink` creates hardlinks instead of symlinks when deduplicating uploads, which is less maintenance
1637
1654
  * however note if you edit one file it will also affect the other copies
1638
1655
  * `--vague-403` returns a "404 not found" instead of "401 unauthorized" which is a common enterprise meme
1639
- * `--ban-404=50,60,1440` ban client for 1440min (24h) if they hit 50 404's in 60min
1640
- * `--turbo=-1` to force-disable turbo-mode in the uploader which could otherwise hit the 404-ban
1641
1656
  * `--nih` removes the server hostname from directory listings
1642
1657
 
1643
1658
  * option `-sss` is a shortcut for the above plus:
@@ -27,6 +27,8 @@ from .authsrv import expand_config_file, re_vol, split_cfg_ln, upgrade_cfg_fmt
27
27
  from .cfg import flagcats, onedash
28
28
  from .svchub import SvcHub
29
29
  from .util import (
30
+ DEF_MTE,
31
+ DEF_MTH,
30
32
  IMPLICATIONS,
31
33
  JINJA_VER,
32
34
  PYFTPD_VER,
@@ -178,7 +180,10 @@ def init_E(E ) :
178
180
 
179
181
  with open_binary("copyparty", "z.tar") as tgz:
180
182
  with tarfile.open(fileobj=tgz) as tf:
181
- tf.extractall(tdn) # nosec (archive is safe)
183
+ try:
184
+ tf.extractall(tdn, filter="tar")
185
+ except TypeError:
186
+ tf.extractall(tdn) # nosec (archive is safe)
182
187
 
183
188
  return tdn
184
189
 
@@ -800,6 +805,8 @@ def add_upload(ap):
800
805
  ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes)")
801
806
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
802
807
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
808
+ ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every SEC seconds; allows resuming incomplete uploads after a server crash")
809
+ ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440, help="forget unfinished uploads after MIN minutes; impossible to resume them after that (360=6h, 1440=24h)")
803
810
  ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
804
811
  ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)")
805
812
  ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
@@ -990,7 +997,7 @@ def add_optouts(ap):
990
997
  def add_safety(ap):
991
998
  ap2 = ap.add_argument_group('safety options')
992
999
  ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
993
- ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 --ban-404=50,60,1440 --turbo=-1 -nih")
1000
+ ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih")
994
1001
  ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
995
1002
  ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m; example [\033[32m**,*,ln,p,r\033[0m]")
996
1003
  ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
@@ -1004,10 +1011,10 @@ def add_safety(ap):
1004
1011
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1005
1012
  ap2.add_argument("--logout", metavar="H", type=float, default="8086", help="logout clients after H hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
1006
1013
  ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1007
- ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="no", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (disabled by default since turbo-up2k counts as 404s)")
1014
+ ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
1008
1015
  ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
1009
1016
  ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (422 is server fuzzing, invalid POSTs and so)")
1010
- ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (decent replacement for --ban-404 if that can't be used)")
1017
+ ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to access g/G/h (decent replacement for --ban-404 if that can't be used)")
1011
1018
  ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
1012
1019
  ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
1013
1020
  ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0")
@@ -1126,10 +1133,8 @@ def add_db_metadata(ap):
1126
1133
  ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such")
1127
1134
  ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
1128
1135
  ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
1129
- ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
1130
- default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash,up_ip,.up_at")
1131
- ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
1132
- default=".vq,.aq,vc,ac,fmt,res,.fps")
1136
+ ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
1137
+ ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as -mte", default=DEF_MTH)
1133
1138
  ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
1134
1139
 
1135
1140
 
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 9, 12)
3
+ VERSION = (1, 9, 14)
4
4
  CODENAME = "prometheable"
5
- BUILD_DT = (2023, 10, 15)
5
+ BUILD_DT = (2023, 10, 21)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -21,11 +21,14 @@ from .util import (
21
21
  META_NOBOTS,
22
22
  SQLITE_VER,
23
23
  UNPLICATIONS,
24
+ ODict,
24
25
  Pebkac,
26
+ UTC,
25
27
  absreal,
26
28
  afsenc,
27
29
  get_df,
28
30
  humansize,
31
+ odfusion,
29
32
  relchk,
30
33
  statdir,
31
34
  uncyg,
@@ -206,7 +209,7 @@ class Lim(object):
206
209
  if self.rot_re.search(path.replace("\\", "/")):
207
210
  return path, ""
208
211
 
209
- suf = datetime.utcnow().strftime(self.rotf)
212
+ suf = datetime.now(UTC).strftime(self.rotf)
210
213
  if path:
211
214
  path += "/"
212
215
 
@@ -1419,12 +1422,12 @@ class AuthSrv(object):
1419
1422
 
1420
1423
  for ga, vf in [["no_hash", "nohash"], ["no_idx", "noidx"]]:
1421
1424
  if vf in vol.flags:
1422
- ptn = vol.flags.pop(vf)
1425
+ ptn = re.compile(vol.flags.pop(vf))
1423
1426
  else:
1424
1427
  ptn = getattr(self.args, ga)
1425
1428
 
1426
1429
  if ptn:
1427
- vol.flags[vf] = re.compile(ptn)
1430
+ vol.flags[vf] = ptn
1428
1431
 
1429
1432
  for ga, vf in vf_bmap().items():
1430
1433
  if getattr(self.args, ga):
@@ -1470,13 +1473,14 @@ class AuthSrv(object):
1470
1473
 
1471
1474
  # default tag cfgs if unset
1472
1475
  if "mte" not in vol.flags:
1473
- vol.flags["mte"] = self.args.mte
1474
- elif vol.flags["mte"].startswith("+"):
1475
- vol.flags["mte"] = ",".join(
1476
- x for x in [self.args.mte, vol.flags["mte"][1:]] if x
1477
- )
1476
+ vol.flags["mte"] = self.args.mte.copy()
1477
+ else:
1478
+ vol.flags["mte"] = odfusion(self.args.mte, vol.flags["mte"])
1479
+
1478
1480
  if "mth" not in vol.flags:
1479
- vol.flags["mth"] = self.args.mth
1481
+ vol.flags["mth"] = self.args.mth.copy()
1482
+ else:
1483
+ vol.flags["mth"] = odfusion(self.args.mth, vol.flags["mth"])
1480
1484
 
1481
1485
  # append additive args from argv to volflags
1482
1486
  hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
@@ -1530,7 +1534,11 @@ class AuthSrv(object):
1530
1534
  if vol.flags.get(grp, False):
1531
1535
  continue
1532
1536
 
1533
- vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
1537
+ vol.flags = {
1538
+ k: v
1539
+ for k, v in vol.flags.items()
1540
+ if not k.startswith(rm) or k == "mte"
1541
+ }
1534
1542
 
1535
1543
  for grp, rm in [["d2v", "e2v"]]:
1536
1544
  if not vol.flags.get(grp, False):
@@ -1577,12 +1585,12 @@ class AuthSrv(object):
1577
1585
  if local:
1578
1586
  local_only_mtp[a] = True
1579
1587
 
1580
- local_mte = {}
1581
- for a in vol.flags.get("mte", "").split(","):
1588
+ local_mte = ODict()
1589
+ for a in vol.flags.get("mte", {}).keys():
1582
1590
  local = True
1583
1591
  all_mte[a] = True
1584
1592
  local_mte[a] = True
1585
- for b in self.args.mte.split(","):
1593
+ for b in self.args.mte.keys():
1586
1594
  if not a or not b:
1587
1595
  continue
1588
1596
 
@@ -13,12 +13,20 @@ def vf_bmap() :
13
13
  "no_dedup": "copydupes",
14
14
  "no_dupe": "nodupe",
15
15
  "no_forget": "noforget",
16
+ "no_robots": "norobots",
17
+ "no_thumb": "dthumb",
18
+ "no_vthumb": "dvthumb",
19
+ "no_athumb": "dathumb",
20
+ "re_maxage": "scan",
16
21
  "th_no_crop": "nocrop",
17
22
  "dav_auth": "davauth",
18
23
  "dav_rt": "davrt",
19
24
  }
20
25
  for k in (
21
26
  "dotsrch",
27
+ "e2d",
28
+ "e2ds",
29
+ "e2dsa",
22
30
  "e2t",
23
31
  "e2ts",
24
32
  "e2tsr",
@@ -41,7 +49,12 @@ def vf_bmap() :
41
49
 
42
50
  def vf_vmap() :
43
51
  """argv-to-volflag: simple values"""
44
- ret = {"th_convt": "convt", "th_size": "thsize"}
52
+ ret = {
53
+ "no_hash": "nohash",
54
+ "no_idx": "noidx",
55
+ "th_convt": "convt",
56
+ "th_size": "thsize",
57
+ }
45
58
  for k in ("dbd", "lg_sbf", "md_sbf", "nrand", "sort", "unlist", "u2ts"):
46
59
  ret[k] = k
47
60
  return ret
@@ -50,7 +63,21 @@ def vf_vmap() :
50
63
  def vf_cmap() :
51
64
  """argv-to-volflag: complex/lists"""
52
65
  ret = {}
53
- for k in ("html_head", "mte", "mth"):
66
+ for k in (
67
+ "html_head",
68
+ "mte",
69
+ "mth",
70
+ "mtp",
71
+ "xad",
72
+ "xar",
73
+ "xau",
74
+ "xban",
75
+ "xbd",
76
+ "xbr",
77
+ "xbu",
78
+ "xiu",
79
+ "xm",
80
+ ):
54
81
  ret[k] = k
55
82
  return ret
56
83
 
@@ -40,7 +40,9 @@ from .util import (
40
40
  HTTPCODE,
41
41
  META_NOBOTS,
42
42
  MultipartParser,
43
+ ODict,
43
44
  Pebkac,
45
+ UTC,
44
46
  UnrecvEOF,
45
47
  absreal,
46
48
  alltrace,
@@ -650,6 +652,7 @@ class HttpCli(object):
650
652
  and not body.startswith(b"<pre>source file busy")
651
653
  )
652
654
  )
655
+ and (status != 404 or (self.can_get and not self.can_read))
653
656
  ):
654
657
  if status == 404:
655
658
  g = self.conn.hsrv.g404
@@ -1967,8 +1970,7 @@ class HttpCli(object):
1967
1970
  self.log("q#: {} ({:.2f}s)".format(msg, idx.p_dur))
1968
1971
 
1969
1972
  order = []
1970
- cfg = self.args.mte.split(",")
1971
- for t in cfg:
1973
+ for t in self.args.mte:
1972
1974
  if t in taglist:
1973
1975
  order.append(t)
1974
1976
  for t in taglist:
@@ -3987,7 +3989,7 @@ class HttpCli(object):
3987
3989
  margin = "-"
3988
3990
 
3989
3991
  sz = inf.st_size
3990
- zd = datetime.utcfromtimestamp(linf.st_mtime)
3992
+ zd = datetime.fromtimestamp(linf.st_mtime, UTC)
3991
3993
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
3992
3994
  zd.year,
3993
3995
  zd.month,
@@ -4047,6 +4049,9 @@ class HttpCli(object):
4047
4049
  ap = vn.canonical(rem)
4048
4050
  return self.tx_file(ap) # is no-cache
4049
4051
 
4052
+ mte = vn.flags.get("mte", {})
4053
+ add_up_at = ".up_at" in mte
4054
+ is_admin = self.can_admin
4050
4055
  tagset = set()
4051
4056
  for fe in files:
4052
4057
  fn = fe["name"]
@@ -4074,24 +4079,38 @@ class HttpCli(object):
4074
4079
  self.log(t.format(rd, fn, min_ex()))
4075
4080
  break
4076
4081
 
4077
- fe["tags"] = {k: v for k, v in r}
4082
+ tags = {k: v for k, v in r}
4078
4083
 
4079
- if self.can_admin:
4084
+ if is_admin:
4080
4085
  q = "select ip, at from up where rd=? and fn=?"
4081
4086
  try:
4082
4087
  zs1, zs2 = icur.execute(q, erd_efn).fetchone()
4083
- fe["tags"]["up_ip"] = zs1
4084
- fe["tags"][".up_at"] = zs2
4088
+ if zs1:
4089
+ tags["up_ip"] = zs1
4090
+ if zs2:
4091
+ tags[".up_at"] = zs2
4092
+ except:
4093
+ pass
4094
+ elif add_up_at:
4095
+ q = "select at from up where rd=? and fn=?"
4096
+ try:
4097
+ (zs1,) = icur.execute(q, erd_efn).fetchone()
4098
+ if zs1:
4099
+ tags[".up_at"] = zs1
4085
4100
  except:
4086
4101
  pass
4087
4102
 
4088
- _ = [tagset.add(k) for k in fe["tags"]]
4103
+ _ = [tagset.add(k) for k in tags]
4104
+ fe["tags"] = tags
4089
4105
 
4090
4106
  if icur:
4091
- mte = vn.flags.get("mte") or "up_ip,.up_at"
4092
- taglist = [k for k in mte.split(",") if k in tagset]
4107
+ lmte = list(mte)
4108
+ if self.can_admin:
4109
+ lmte += ["up_ip", ".up_at"]
4110
+
4111
+ taglist = [k for k in lmte if k in tagset]
4093
4112
  for fe in dirs:
4094
- fe["tags"] = {}
4113
+ fe["tags"] = ODict()
4095
4114
  else:
4096
4115
  taglist = list(tagset)
4097
4116
 
@@ -4138,7 +4157,7 @@ class HttpCli(object):
4138
4157
  j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
4139
4158
 
4140
4159
  if "mth" in vn.flags:
4141
- j2a["def_hcols"] = vn.flags["mth"].split(",")
4160
+ j2a["def_hcols"] = list(vn.flags["mth"])
4142
4161
 
4143
4162
  html = self.j2s(tpl, **j2a)
4144
4163
  self.reply(html.encode("utf-8", "replace"))
@@ -8,7 +8,7 @@ from datetime import datetime
8
8
  from .__init__ import CORES
9
9
  from .bos import bos
10
10
  from .th_cli import ThumbCli
11
- from .util import vjoin
11
+ from .util import UTC, vjoin
12
12
 
13
13
  class StreamArc(object):
14
14
  def __init__(
@@ -102,7 +102,7 @@ def errdesc(errors ) :
102
102
  tf_path = tf.name
103
103
  tf.write("\r\n".join(report).encode("utf-8", "replace"))
104
104
 
105
- dt = datetime.utcnow().strftime("%Y-%m%d-%H%M%S")
105
+ dt = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S")
106
106
 
107
107
  bos.chmod(tf_path, 0o444)
108
108
  return {