copyparty 1.19.15__py3-none-any.whl → 1.19.17__py3-none-any.whl

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 (52) hide show
  1. copyparty/__init__.py +19 -0
  2. copyparty/__main__.py +47 -11
  3. copyparty/__version__.py +2 -2
  4. copyparty/authsrv.py +45 -9
  5. copyparty/bos/bos.py +5 -1
  6. copyparty/cfg.py +20 -0
  7. copyparty/ftpd.py +5 -3
  8. copyparty/httpcli.py +114 -27
  9. copyparty/mdns.py +53 -18
  10. copyparty/mtag.py +18 -4
  11. copyparty/qrkode.py +107 -0
  12. copyparty/res/COPYING.txt +76 -5
  13. copyparty/smbd.py +1 -1
  14. copyparty/stolen/qrcodegen.py +1 -64
  15. copyparty/svchub.py +13 -2
  16. copyparty/tcpsrv.py +6 -7
  17. copyparty/tftpd.py +1 -1
  18. copyparty/th_srv.py +11 -5
  19. copyparty/up2k.py +40 -16
  20. copyparty/util.py +63 -16
  21. copyparty/web/baguettebox.js.gz +0 -0
  22. copyparty/web/browser.css.gz +0 -0
  23. copyparty/web/browser.html +3 -1
  24. copyparty/web/browser.js.gz +0 -0
  25. copyparty/web/splash.html +3 -0
  26. copyparty/web/splash.js.gz +0 -0
  27. copyparty/web/tl/chi.js.gz +0 -0
  28. copyparty/web/tl/cze.js.gz +0 -0
  29. copyparty/web/tl/deu.js.gz +0 -0
  30. copyparty/web/tl/epo.js.gz +0 -0
  31. copyparty/web/tl/fin.js.gz +0 -0
  32. copyparty/web/tl/fra.js.gz +0 -0
  33. copyparty/web/tl/grc.js.gz +0 -0
  34. copyparty/web/tl/ita.js.gz +0 -0
  35. copyparty/web/tl/kor.js.gz +0 -0
  36. copyparty/web/tl/nld.js.gz +0 -0
  37. copyparty/web/tl/nno.js.gz +0 -0
  38. copyparty/web/tl/nor.js.gz +0 -0
  39. copyparty/web/tl/pol.js.gz +0 -0
  40. copyparty/web/tl/por.js.gz +0 -0
  41. copyparty/web/tl/rus.js.gz +0 -0
  42. copyparty/web/tl/spa.js.gz +0 -0
  43. copyparty/web/tl/swe.js.gz +0 -0
  44. copyparty/web/tl/tur.js.gz +0 -0
  45. copyparty/web/tl/ukr.js.gz +0 -0
  46. copyparty/web/util.js.gz +0 -0
  47. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/METADATA +47 -2
  48. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/RECORD +52 -32
  49. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/WHEEL +0 -0
  50. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/entry_points.txt +0 -0
  51. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/licenses/LICENSE +0 -0
  52. {copyparty-1.19.15.dist-info → copyparty-1.19.17.dist-info}/top_level.txt +0 -0
copyparty/res/COPYING.txt CHANGED
@@ -6,15 +6,15 @@ License: MIT
6
6
 
7
7
  https://github.com/pallets/jinja/
8
8
  Copyright (c) 2007 Pallets
9
- License: BSD 3-Clause
9
+ License: BSD-3-Clause
10
10
 
11
11
  https://github.com/pallets/markupsafe/
12
12
  Copyright (c) 2010 Pallets
13
- License: BSD 3-Clause
13
+ License: BSD-3-Clause
14
14
 
15
15
  https://github.com/paulc/dnslib/
16
16
  Copyright (c) 2010-2017 Paul Chakravarti
17
- License: BSD 2-Clause
17
+ License: BSD-2-Clause
18
18
 
19
19
  https://github.com/pydron/ifaddr/
20
20
  Copyright (c) 2014 Stefan C. Mueller
@@ -36,6 +36,10 @@ https://github.com/ahupp/python-magic/
36
36
  Copyright (c) 2001-2014 Adam Hupp
37
37
  License: MIT
38
38
 
39
+ https://github.com/fusepy/fusepy
40
+ Copyright (c) 2012 Giorgos Verigakis
41
+ License: ISC
42
+
39
43
  --- client-side --- software ---
40
44
 
41
45
  https://github.com/Daninet/hash-wasm/
@@ -50,6 +54,10 @@ https://github.com/feimosi/baguetteBox.js/
50
54
  Copyright (c) 2017 Marek Grzybek
51
55
  License: MIT
52
56
 
57
+ https://github.com/cure53/DOMPurify/
58
+ Copyright (c) 2025 Cure53 / Mario Heiderich
59
+ License: Apache-2.0 or MPL-2.0
60
+
53
61
  https://github.com/markedjs/marked/
54
62
  Copyright (c) 2018+, MarkedJS
55
63
  Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
@@ -68,11 +76,11 @@ License: MIT
68
76
 
69
77
  https://github.com/adobe-fonts/source-code-pro/
70
78
  Copyright (c) 2010-2019 Adobe
71
- License: SIL OFL 1.1
79
+ License: OFL-1.1
72
80
 
73
81
  https://github.com/FortAwesome/Font-Awesome/
74
82
  Copyright (c) 2022 Fonticons, Inc.
75
- License: SIL OFL 1.1
83
+ License: OFL-1.1
76
84
 
77
85
 
78
86
 
@@ -108,6 +116,69 @@ Redistribution and use in source and binary forms, with or without modification,
108
116
 
109
117
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
110
118
 
119
+ --- ISC License ---
120
+
121
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
122
+
123
+ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
124
+
125
+ --- Apache License v2.0 ---
126
+
127
+ Apache License
128
+ Version 2.0, January 2004
129
+ http://www.apache.org/licenses/
130
+
131
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
132
+
133
+ 1. Definitions.
134
+
135
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
136
+
137
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
138
+
139
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
140
+
141
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
142
+
143
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
144
+
145
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
146
+
147
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
148
+
149
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
150
+
151
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
152
+
153
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
154
+
155
+ 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
156
+
157
+ 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
158
+
159
+ 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
160
+
161
+ (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
162
+
163
+ (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
164
+
165
+ (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
166
+
167
+ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
168
+
169
+ You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
170
+
171
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
172
+
173
+ 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
174
+
175
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
176
+
177
+ 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
178
+
179
+ 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
180
+
181
+
111
182
  --- SIL Open Font License v1.1 ---
112
183
 
113
184
  PREAMBLE
copyparty/smbd.py CHANGED
@@ -259,7 +259,7 @@ class SMB(object):
259
259
  0,
260
260
  "1.7.6.2",
261
261
  time.time(),
262
- "",
262
+ None,
263
263
  )
264
264
  t = hr.get("rejectmsg") or ""
265
265
  if t or not hr:
@@ -4,7 +4,7 @@
4
4
  # https://github.com/nayuki/QR-Code-generator/blob/daa3114/python/qrcodegen.py
5
5
  # the original ^ is extremely well commented so refer to that for explanations
6
6
 
7
- # hacks: binary-only, auto-ecc, render, py2-compat
7
+ # hacks: binary-only, auto-ecc, py2-compat
8
8
 
9
9
  from __future__ import print_function, unicode_literals
10
10
 
@@ -168,52 +168,6 @@ class QrCode(object):
168
168
  self._apply_mask(msk) # Apply the final choice of mask
169
169
  self._draw_format_bits(msk) # Overwrite old format bits
170
170
 
171
- def render(self, zoom=1, pad=4) :
172
- tab = self.modules
173
- sz = self.size
174
- if sz % 2 and zoom == 1:
175
- tab.append([False] * sz)
176
-
177
- tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
178
- tab = [[False] * pad + x + [False] * pad for x in tab]
179
-
180
- rows = []
181
- if zoom == 1:
182
- for y in range(0, len(tab), 2):
183
- row = ""
184
- for x in range(len(tab[y])):
185
- v = 2 if tab[y][x] else 0
186
- v += 1 if tab[y + 1][x] else 0
187
- row += " ▄▀█"[v]
188
- rows.append(row)
189
- else:
190
- for tr in tab:
191
- row = ""
192
- for zb in tr:
193
- row += " █"[int(zb)] * 2
194
- rows.append(row)
195
-
196
- return "\n".join(rows)
197
-
198
- def to_png(self, zoom, pad, bg, fg, ap) :
199
- from PIL import Image
200
-
201
- tab = self.modules
202
- sz = self.size
203
- psz = sz + pad * 2
204
- if bg:
205
- img = Image.new("RGB", (psz, psz), bg)
206
- else:
207
- img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
208
- fg = (fg[0], fg[1], fg[2], 255)
209
- for y in range(sz):
210
- for x in range(sz):
211
- if tab[y][x]:
212
- img.putpixel((x + pad, y + pad), fg)
213
- if zoom != 1:
214
- img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
215
- img.save(ap)
216
-
217
171
  def _draw_function_patterns(self) :
218
172
  # Draw horizontal and vertical timing patterns
219
173
  for i in range(self.size):
@@ -608,20 +562,3 @@ def _get_bit(x , i ) :
608
562
 
609
563
  class DataTooLongError(ValueError):
610
564
  pass
611
-
612
-
613
- def qr2svg(qr , border ) :
614
- parts = []
615
- for y in range(qr.size):
616
- sy = border + y
617
- for x in range(qr.size):
618
- if qr.modules[y][x]:
619
- parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
620
- t = """\
621
- <?xml version="1.0" encoding="UTF-8"?>
622
- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {0} {0}" stroke="none">
623
- <rect width="100%" height="100%" fill="#F7F7F7"/>
624
- <path d="{1}" fill="#111111"/>
625
- </svg>
626
- """
627
- return t.format(qr.size + border * 2, " ".join(parts))
copyparty/svchub.py CHANGED
@@ -61,6 +61,7 @@ from .util import (
61
61
  build_netmap,
62
62
  expat_ver,
63
63
  gzip,
64
+ html_escape,
64
65
  load_ipr,
65
66
  load_ipu,
66
67
  lock_file,
@@ -241,8 +242,8 @@ class SvcHub(object):
241
242
  t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
242
243
  self.log("root", t % (args.th_ram_max, zs), 3)
243
244
 
244
- if args.chpw and args.have_idp_hdrs:
245
- t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
245
+ if args.chpw and args.have_idp_hdrs and "pw" not in args.auth_ord.split(","):
246
+ t = "ERROR: user-changeable passwords is not compatible with your current configuration. Choose one of these options to fix it:\n option1: disable --chpw\n option2: remove all use of IdP features; --idp-*\n option3: change --auth-ord to something like pw,idp,ipu"
246
247
  self.log("root", t, 1)
247
248
  raise Exception(t)
248
249
 
@@ -284,6 +285,9 @@ class SvcHub(object):
284
285
  ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
285
286
  args.theme = "{0}{1} {0} {1}".format(ch, bri)
286
287
 
288
+ if args.no_stack:
289
+ args.stack_who = "no"
290
+
287
291
  if args.nid:
288
292
  args.du_who = "no"
289
293
  args.du_iwho = n_du_who(args.du_who)
@@ -1161,6 +1165,13 @@ class SvcHub(object):
1161
1165
  if len(al.tcolor) == 3: # fc5 => ffcc55
1162
1166
  al.tcolor = "".join([x * 2 for x in al.tcolor])
1163
1167
 
1168
+ if self.args.name_url:
1169
+ zs = html_escape(self.args.name_url, True, True)
1170
+ zs = '<a href="%s">%s</a>' % (zs, self.args.name)
1171
+ else:
1172
+ zs = self.args.name
1173
+ self.args.name_html = zs
1174
+
1164
1175
  zs = al.u2sz
1165
1176
  zsl = [x.strip() for x in zs.split(",")]
1166
1177
  if len(zsl) not in (1, 3):
copyparty/tcpsrv.py CHANGED
@@ -9,7 +9,7 @@ import time
9
9
 
10
10
  from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
11
11
  from .cert import gencert
12
- from .stolen.qrcodegen import QrCode, qr2svg
12
+ from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
13
13
  from .util import (
14
14
  E_ACCESS,
15
15
  E_ADDR_IN_USE,
@@ -21,6 +21,7 @@ from .util import (
21
21
  VF_CAREFUL,
22
22
  Netdev,
23
23
  atomic_move,
24
+ get_adapters,
24
25
  min_ex,
25
26
  sunpack,
26
27
  termsize,
@@ -453,8 +454,6 @@ class TcpSrv(object):
453
454
  self._distribute_netdevs()
454
455
 
455
456
  def detect_interfaces(self, listen_ips ) :
456
- from .stolen.ifaddr import get_adapters
457
-
458
457
  listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
459
458
 
460
459
  nics = get_adapters(True)
@@ -627,7 +626,7 @@ class TcpSrv(object):
627
626
 
628
627
  pad = self.args.qrp
629
628
  zoom = self.args.qrz
630
- qrc = QrCode.encode_binary(btxt)
629
+ qrc = qrgen(btxt)
631
630
 
632
631
  for zs in self.args.qr_file or []:
633
632
  self._qr2file(qrc, zs)
@@ -640,7 +639,7 @@ class TcpSrv(object):
640
639
  except:
641
640
  zoom = 1
642
641
 
643
- qr = qrc.render(zoom, pad)
642
+ qr = qr2txt(qrc, zoom, pad)
644
643
  if self.args.no_ansi:
645
644
  return "{}\n{}".format(txt, qr)
646
645
 
@@ -681,12 +680,12 @@ class TcpSrv(object):
681
680
  if zoom not in (1, 2):
682
681
  raise Exception("invalid zoom for qr.txt; must be 1 or 2")
683
682
  with open(ap, "wb") as f:
684
- f.write(qrc.render(zoom, pad).encode("utf-8"))
683
+ f.write(qr2txt(qrc, zoom, pad).encode("utf-8"))
685
684
  elif ap.endswith(".svg"):
686
685
  with open(ap, "wb") as f:
687
686
  f.write(qr2svg(qrc, pad).encode("utf-8"))
688
687
  else:
689
- qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
688
+ qr2png(qrc, zoom, pad, self._h2i(bg), self._h2i(fg), ap)
690
689
 
691
690
  def _h2i(self, hs):
692
691
  try:
copyparty/tftpd.py CHANGED
@@ -376,7 +376,7 @@ class Tftpd(object):
376
376
  0,
377
377
  "8.3.8.7",
378
378
  time.time(),
379
- "",
379
+ None,
380
380
  )
381
381
  t = hr.get("rejectmsg") or ""
382
382
  if t or not hr:
copyparty/th_srv.py CHANGED
@@ -378,7 +378,7 @@ class ThumbSrv(object):
378
378
  else:
379
379
  ap_unpk = abspath
380
380
 
381
- if not bos.path.exists(tpath):
381
+ if ap_unpk and not bos.path.exists(tpath):
382
382
  tex = tpath.rsplit(".", 1)[-1]
383
383
  want_mp3 = tex == "mp3"
384
384
  want_opus = tex in ("opus", "owa", "caf")
@@ -421,12 +421,14 @@ class ThumbSrv(object):
421
421
  except:
422
422
  pass
423
423
 
424
+ conv_ok = False
424
425
  for fun in funs:
425
426
  try:
426
427
  if not png_ok and tpath.endswith(".png"):
427
428
  raise Exception("png only allowed for waveforms")
428
429
 
429
430
  fun(ap_unpk, ttpath, fmt, vn)
431
+ conv_ok = True
430
432
  break
431
433
  except Exception as ex:
432
434
  msg = "%s could not create thumbnail of %r\n%s"
@@ -448,16 +450,20 @@ class ThumbSrv(object):
448
450
  except:
449
451
  pass
450
452
 
451
- if abspath != ap_unpk:
453
+ if abspath != ap_unpk and ap_unpk:
452
454
  wunlink(self.log, ap_unpk, vn.flags)
453
455
 
454
456
  try:
455
457
  atomic_move(self.log, ttpath, tpath, vn.flags)
456
458
  except Exception as ex:
457
- if not os.path.exists(tpath):
459
+ if conv_ok and not os.path.exists(tpath):
458
460
  t = "failed to move [%s] to [%s]: %r"
459
461
  self.log(t % (ttpath, tpath, ex), 3)
460
- pass
462
+ elif not conv_ok:
463
+ try:
464
+ open(tpath, "ab").close()
465
+ except:
466
+ pass
461
467
 
462
468
  untemp = []
463
469
  with self.mutex:
@@ -677,7 +683,7 @@ class ThumbSrv(object):
677
683
  return
678
684
 
679
685
  c = "90"
680
- t = "FFmpeg failed (probably a corrupt video file):\n"
686
+ t = "FFmpeg failed (probably a corrupt file):\n"
681
687
  if (
682
688
  (not self.args.th_ff_jpg or time.time() - int(self.args.th_ff_jpg) < 60)
683
689
  and cmd[-1].lower().endswith(b".webp")
copyparty/up2k.py CHANGED
@@ -10,6 +10,7 @@ import re
10
10
  import shutil
11
11
  import stat
12
12
  import subprocess as sp
13
+ import sys
13
14
  import tempfile
14
15
  import threading
15
16
  import time
@@ -27,6 +28,7 @@ from .mtag import MParser, MTag
27
28
  from .util import (
28
29
  E_FS_CRIT,
29
30
  E_FS_MEH,
31
+ HAVE_FICLONE,
30
32
  HAVE_SQLITE3,
31
33
  SYMTIME,
32
34
  VF_CAREFUL,
@@ -85,13 +87,17 @@ DB_VER = 6
85
87
  if TYPE_CHECKING:
86
88
  from .svchub import SvcHub
87
89
 
90
+ USE_FICLONE = HAVE_FICLONE and sys.version_info < (3, 14)
91
+ if USE_FICLONE:
92
+ import fcntl
93
+
88
94
  zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
89
95
  ICV_EXTS = set(zsg.split(","))
90
96
 
91
97
  zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
92
98
  VCV_EXTS = set(zsg.split(","))
93
99
 
94
- zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv"
100
+ zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv,cbz,epub"
95
101
  ACV_EXTS = set(zsg.split(","))
96
102
 
97
103
  zsg = "nohash noidx xdev xvol"
@@ -3282,7 +3288,7 @@ class Up2k(object):
3282
3288
  job["size"],
3283
3289
  job["addr"],
3284
3290
  job["at"],
3285
- "",
3291
+ None,
3286
3292
  )
3287
3293
  t = hr.get("rejectmsg") or ""
3288
3294
  if t or not hr:
@@ -3514,11 +3520,26 @@ class Up2k(object):
3514
3520
 
3515
3521
  linked = False
3516
3522
  try:
3517
- if "reflink" in flags:
3518
- raise Exception("reflink")
3523
+ if rm and bos.path.exists(dst):
3524
+ wunlink(self.log, dst, flags)
3525
+
3519
3526
  if not is_mv and not flags.get("dedup"):
3520
3527
  raise Exception("dedup is disabled in config")
3521
3528
 
3529
+ if "reflink" in flags:
3530
+ if not USE_FICLONE:
3531
+ raise Exception("reflink") # python 3.14 or newer; no need
3532
+ try:
3533
+ with open(fsenc(src), "rb") as fi, open(fsenc(dst), "wb") as fo:
3534
+ fcntl.ioctl(fo.fileno(), fcntl.FICLONE, fi.fileno())
3535
+ except:
3536
+ if bos.path.exists(dst):
3537
+ wunlink(self.log, dst, flags)
3538
+ raise
3539
+ if lmod:
3540
+ bos.utime_c(self.log, dst, int(lmod), False)
3541
+ return
3542
+
3522
3543
  lsrc = src
3523
3544
  ldst = dst
3524
3545
  fs1 = bos.stat(os.path.dirname(src)).st_dev
@@ -3545,9 +3566,6 @@ class Up2k(object):
3545
3566
  lsrc = lsrc.replace("/", "\\")
3546
3567
  ldst = ldst.replace("/", "\\")
3547
3568
 
3548
- if rm and bos.path.exists(dst):
3549
- wunlink(self.log, dst, flags)
3550
-
3551
3569
  try:
3552
3570
  if "hardlink" in flags:
3553
3571
  os.link(fsenc(absreal(src)), fsenc(dst))
@@ -3962,7 +3980,7 @@ class Up2k(object):
3962
3980
  sz,
3963
3981
  ip,
3964
3982
  at or time.time(),
3965
- "",
3983
+ None,
3966
3984
  )
3967
3985
  t = hr.get("rejectmsg") or ""
3968
3986
  if t or not hr:
@@ -4197,7 +4215,7 @@ class Up2k(object):
4197
4215
  st.st_size,
4198
4216
  ip,
4199
4217
  time.time(),
4200
- "",
4218
+ None,
4201
4219
  ):
4202
4220
  t = "delete blocked by xbd server config: %r"
4203
4221
  self.log(t % (abspath,), 1)
@@ -4237,7 +4255,7 @@ class Up2k(object):
4237
4255
  st.st_size,
4238
4256
  ip,
4239
4257
  time.time(),
4240
- "",
4258
+ None,
4241
4259
  )
4242
4260
 
4243
4261
  if is_dir:
@@ -4365,7 +4383,7 @@ class Up2k(object):
4365
4383
  fsize,
4366
4384
  ip,
4367
4385
  time.time(),
4368
- "",
4386
+ None,
4369
4387
  ):
4370
4388
  t = "copy blocked by xbr server config: %r" % (svp,)
4371
4389
  self.log(t, 1)
@@ -4465,7 +4483,7 @@ class Up2k(object):
4465
4483
  fsize,
4466
4484
  ip,
4467
4485
  time.time(),
4468
- "",
4486
+ None,
4469
4487
  )
4470
4488
 
4471
4489
  return "k"
@@ -4616,7 +4634,7 @@ class Up2k(object):
4616
4634
  fsize,
4617
4635
  ip,
4618
4636
  time.time(),
4619
- "",
4637
+ None,
4620
4638
  ):
4621
4639
  t = "move blocked by xbr server config: %r" % (svp,)
4622
4640
  self.log(t, 1)
@@ -4656,7 +4674,7 @@ class Up2k(object):
4656
4674
  fsize,
4657
4675
  ip,
4658
4676
  time.time(),
4659
- "",
4677
+ None,
4660
4678
  )
4661
4679
 
4662
4680
  return "k"
@@ -4667,6 +4685,12 @@ class Up2k(object):
4667
4685
  has_dupes = False
4668
4686
  if w:
4669
4687
  if c2 and c2 != c1:
4688
+ if "nodupem" in dvn.flags:
4689
+ q = "select w from up where substr(w,1,16) = ?"
4690
+ for (w2,) in c2.execute(q, (w[:16],)):
4691
+ if w == w2:
4692
+ t = "file exists in target volume, and dupes are forbidden in config"
4693
+ raise Pebkac(400, t)
4670
4694
  self._copy_tags(c1, c2, w)
4671
4695
 
4672
4696
  xlink = bool(svn.flags.get("xlink"))
@@ -4775,7 +4799,7 @@ class Up2k(object):
4775
4799
  fsize,
4776
4800
  ip,
4777
4801
  time.time(),
4778
- "",
4802
+ None,
4779
4803
  )
4780
4804
 
4781
4805
  return "k"
@@ -5112,7 +5136,7 @@ class Up2k(object):
5112
5136
  job["size"],
5113
5137
  job["addr"],
5114
5138
  job["t0"],
5115
- "",
5139
+ None,
5116
5140
  )
5117
5141
  t = hr.get("rejectmsg") or ""
5118
5142
  if t or not hr: