copyparty 1.12.1__py3-none-any.whl → 1.13.0__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.
copyparty/__main__.py CHANGED
@@ -850,8 +850,9 @@ def add_qr(ap, tty):
850
850
 
851
851
  def add_fs(ap):
852
852
  ap2 = ap.add_argument_group("filesystem options")
853
- rm_re_def = "5/0.1" if ANYWIN else "0/0"
853
+ rm_re_def = "15/0.1" if ANYWIN else "0/0"
854
854
  ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
855
+ ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
855
856
  ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
856
857
 
857
858
 
@@ -943,6 +944,8 @@ def add_auth(ap):
943
944
  ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
944
945
  ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
945
946
  ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
947
+ ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
948
+ ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
946
949
 
947
950
 
948
951
  def add_zeroconf(ap):
@@ -1082,6 +1085,8 @@ def add_optouts(ap):
1082
1085
  ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
1083
1086
  ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
1084
1087
  ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
1088
+ ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
1089
+ ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
1085
1090
 
1086
1091
 
1087
1092
  def add_safety(ap):
@@ -1207,7 +1212,7 @@ def add_db_general(ap, hcores):
1207
1212
  ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
1208
1213
  ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
1209
1214
  ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
1210
- ap2.add_argument("--re-dhash", action="store_true", help="rebuild the cache if it gets out of sync (for example crash on startup during metadata scanning)")
1215
+ ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
1211
1216
  ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
1212
1217
  ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
1213
1218
  ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 12, 1)
4
- CODENAME = "locksmith"
5
- BUILD_DT = (2024, 4, 9)
3
+ VERSION = (1, 13, 0)
4
+ CODENAME = "race the beam"
5
+ BUILD_DT = (2024, 4, 20)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -1757,13 +1757,14 @@ class AuthSrv(object):
1757
1757
  if k in vol.flags:
1758
1758
  vol.flags[k] = float(vol.flags[k])
1759
1759
 
1760
- try:
1761
- zs1, zs2 = vol.flags["rm_retry"].split("/")
1762
- vol.flags["rm_re_t"] = float(zs1)
1763
- vol.flags["rm_re_r"] = float(zs2)
1764
- except:
1765
- t = 'volume "/%s" has invalid rm_retry [%s]'
1766
- raise Exception(t % (vol.vpath, vol.flags.get("rm_retry")))
1760
+ for k in ("mv_re", "rm_re"):
1761
+ try:
1762
+ zs1, zs2 = vol.flags[k + "try"].split("/")
1763
+ vol.flags[k + "_t"] = float(zs1)
1764
+ vol.flags[k + "_r"] = float(zs2)
1765
+ except:
1766
+ t = 'volume "/%s" has invalid %stry [%s]'
1767
+ raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
1767
1768
 
1768
1769
  for k1, k2 in IMPLICATIONS:
1769
1770
  if k1 in vol.flags:
copyparty/cert.py CHANGED
@@ -6,10 +6,17 @@ import os
6
6
  import shutil
7
7
  import time
8
8
 
9
- from .util import Netdev, runcmd
9
+ from .__init__ import ANYWIN
10
+ from .util import Netdev, runcmd, wrename, wunlink
10
11
 
11
12
  HAVE_CFSSL = True
12
13
 
14
+ if ANYWIN:
15
+ VF = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
16
+ else:
17
+ VF = {"mv_re_t": 0, "rm_re_t": 0}
18
+
19
+
13
20
  def ensure_cert(log , args) :
14
21
  """
15
22
  the default cert (and the entire TLS support) is only here to enable the
@@ -101,8 +108,12 @@ def _gen_ca(log , args):
101
108
  raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3)
102
109
 
103
110
  bname = os.path.join(args.crt_dir, "ca")
104
- os.rename(bname + "-key.pem", bname + ".key")
105
- os.unlink(bname + ".csr")
111
+ try:
112
+ wunlink(log, bname + ".key", VF)
113
+ except:
114
+ pass
115
+ wrename(log, bname + "-key.pem", bname + ".key", VF)
116
+ wunlink(log, bname + ".csr", VF)
106
117
 
107
118
  log("cert", "new ca OK", 2)
108
119
 
@@ -181,11 +192,11 @@ def _gen_srv(log , args, netdevs ):
181
192
 
182
193
  bname = os.path.join(args.crt_dir, "srv")
183
194
  try:
184
- os.unlink(bname + ".key")
195
+ wunlink(log, bname + ".key", VF)
185
196
  except:
186
197
  pass
187
- os.rename(bname + "-key.pem", bname + ".key")
188
- os.unlink(bname + ".csr")
198
+ wrename(log, bname + "-key.pem", bname + ".key", VF)
199
+ wunlink(log, bname + ".csr", VF)
189
200
 
190
201
  with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
191
202
  ca = f.read()
copyparty/cfg.py CHANGED
@@ -16,6 +16,7 @@ def vf_bmap() :
16
16
  "no_dedup": "copydupes",
17
17
  "no_dupe": "nodupe",
18
18
  "no_forget": "noforget",
19
+ "no_pipe": "nopipe",
19
20
  "no_robots": "norobots",
20
21
  "no_thumb": "dthumb",
21
22
  "no_vthumb": "dvthumb",
@@ -63,6 +64,7 @@ def vf_vmap() :
63
64
  "lg_sbf",
64
65
  "md_sbf",
65
66
  "nrand",
67
+ "mv_retry",
66
68
  "rm_retry",
67
69
  "sort",
68
70
  "unlist",
@@ -214,6 +216,7 @@ flagcats = {
214
216
  "dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
215
217
  "fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
216
218
  "fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
219
+ "mv_retry": "ms-windows: timeout for renaming busy files",
217
220
  "rm_retry": "ms-windows: timeout for deleting busy files",
218
221
  "davauth": "ask webdav clients to login for all folders",
219
222
  "davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
copyparty/httpcli.py CHANGED
@@ -36,6 +36,7 @@ from .bos import bos
36
36
  from .star import StreamTar
37
37
  from .sutil import StreamArc, gfilter
38
38
  from .szip import StreamZip
39
+ from .up2k import up2k_chunksize
39
40
  from .util import unquote # type: ignore
40
41
  from .util import (
41
42
  APPLESAN_RE,
@@ -89,6 +90,7 @@ from .util import (
89
90
  vjoin,
90
91
  vol_san,
91
92
  vsplit,
93
+ wrename,
92
94
  wunlink,
93
95
  yieldfile,
94
96
  )
@@ -122,6 +124,7 @@ class HttpCli(object):
122
124
  self.ico = conn.ico # mypy404
123
125
  self.thumbcli = conn.thumbcli # mypy404
124
126
  self.u2fh = conn.u2fh # mypy404
127
+ self.pipes = conn.pipes # mypy404
125
128
  self.log_func = conn.log_func # mypy404
126
129
  self.log_src = conn.log_src # mypy404
127
130
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
@@ -439,7 +442,11 @@ class HttpCli(object):
439
442
 
440
443
  zso = self.headers.get("authorization")
441
444
  bauth = ""
442
- if zso:
445
+ if (
446
+ zso
447
+ and not self.args.no_bauth
448
+ and (not cookie_pw or not self.args.bauth_last)
449
+ ):
443
450
  try:
444
451
  zb = zso.split(" ")[1].encode("ascii")
445
452
  zs = base64.b64decode(zb).decode("utf-8")
@@ -1796,7 +1803,7 @@ class HttpCli(object):
1796
1803
  f, fn = zfw["orz"]
1797
1804
 
1798
1805
  path2 = os.path.join(fdir, fn2)
1799
- atomic_move(path, path2)
1806
+ atomic_move(self.log, path, path2, vfs.flags)
1800
1807
  fn = fn2
1801
1808
  path = path2
1802
1809
 
@@ -1877,7 +1884,9 @@ class HttpCli(object):
1877
1884
  self.reply(t.encode("utf-8"), 201, headers=h)
1878
1885
  return True
1879
1886
 
1880
- def bakflip(self, f , ofs , sz , sha ) :
1887
+ def bakflip(
1888
+ self, f , ofs , sz , sha , flags
1889
+ ) :
1881
1890
  if not self.args.bak_flips or self.args.nw:
1882
1891
  return
1883
1892
 
@@ -1905,7 +1914,7 @@ class HttpCli(object):
1905
1914
 
1906
1915
  if nrem:
1907
1916
  self.log("bakflip truncated; {} remains".format(nrem), 1)
1908
- atomic_move(fp, fp + ".trunc")
1917
+ atomic_move(self.log, fp, fp + ".trunc", flags)
1909
1918
  else:
1910
1919
  self.log("bakflip ok", 2)
1911
1920
 
@@ -2171,7 +2180,7 @@ class HttpCli(object):
2171
2180
 
2172
2181
  if sha_b64 != chash:
2173
2182
  try:
2174
- self.bakflip(f, cstart[0], post_sz, sha_b64)
2183
+ self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
2175
2184
  except:
2176
2185
  self.log("bakflip failed: " + min_ex())
2177
2186
 
@@ -2523,7 +2532,7 @@ class HttpCli(object):
2523
2532
  raise
2524
2533
 
2525
2534
  if not nullwrite:
2526
- atomic_move(tabspath, abspath)
2535
+ atomic_move(self.log, tabspath, abspath, vfs.flags)
2527
2536
 
2528
2537
  tabspath = ""
2529
2538
 
@@ -2763,7 +2772,7 @@ class HttpCli(object):
2763
2772
  hidedir(dp)
2764
2773
  except:
2765
2774
  pass
2766
- bos.rename(fp, os.path.join(mdir, ".hist", mfile2))
2775
+ wrename(self.log, fp, os.path.join(mdir, ".hist", mfile2), vfs.flags)
2767
2776
 
2768
2777
  assert self.parser.gen
2769
2778
  p_field, _, p_data = next(self.parser.gen)
@@ -2918,17 +2927,42 @@ class HttpCli(object):
2918
2927
 
2919
2928
  return txt
2920
2929
 
2921
- def tx_file(self, req_path ) :
2930
+ def tx_file(self, req_path , ptop = None) :
2922
2931
  status = 200
2923
2932
  logmsg = "{:4} {} ".format("", self.req)
2924
2933
  logtail = ""
2925
2934
 
2935
+ if ptop is not None:
2936
+ try:
2937
+ dp, fn = os.path.split(req_path)
2938
+ tnam = fn + ".PARTIAL"
2939
+ if self.args.dotpart:
2940
+ tnam = "." + tnam
2941
+ ap_data = os.path.join(dp, tnam)
2942
+ st_data = bos.stat(ap_data)
2943
+ if not st_data.st_size:
2944
+ raise Exception("partial is empty")
2945
+ x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
2946
+ job = json.loads(x.get())
2947
+ if not job:
2948
+ raise Exception("not found in registry")
2949
+ self.pipes.set(req_path, job)
2950
+ except Exception as ex:
2951
+ self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
2952
+ ptop = None
2953
+
2926
2954
  #
2927
2955
  # if request is for foo.js, check if we have foo.js.gz
2928
2956
 
2929
2957
  file_ts = 0.0
2930
2958
  editions = {}
2931
2959
  for ext in ("", ".gz"):
2960
+ if ptop is not None:
2961
+ sz = job["size"]
2962
+ file_ts = job["lmod"]
2963
+ editions["plain"] = (ap_data, sz)
2964
+ break
2965
+
2932
2966
  try:
2933
2967
  fs_path = req_path + ext
2934
2968
  st = bos.stat(fs_path)
@@ -3085,6 +3119,11 @@ class HttpCli(object):
3085
3119
  self.send_headers(length=upper - lower, status=status, mime=mime)
3086
3120
  return True
3087
3121
 
3122
+ if ptop is not None:
3123
+ return self.tx_pipe(
3124
+ ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
3125
+ )
3126
+
3088
3127
  ret = True
3089
3128
  with open_func(*open_args) as f:
3090
3129
  self.send_headers(length=upper - lower, status=status, mime=mime)
@@ -3104,6 +3143,143 @@ class HttpCli(object):
3104
3143
 
3105
3144
  return ret
3106
3145
 
3146
+ def tx_pipe(
3147
+ self,
3148
+ ptop ,
3149
+ req_path ,
3150
+ ap_data ,
3151
+ job ,
3152
+ lower ,
3153
+ upper ,
3154
+ status ,
3155
+ mime ,
3156
+ logmsg ,
3157
+ ) :
3158
+ M = 1048576
3159
+ self.send_headers(length=upper - lower, status=status, mime=mime)
3160
+ wr_slp = self.args.s_wr_slp
3161
+ wr_sz = self.args.s_wr_sz
3162
+ file_size = job["size"]
3163
+ chunk_size = up2k_chunksize(file_size)
3164
+ num_need = -1
3165
+ data_end = 0
3166
+ remains = upper - lower
3167
+ broken = False
3168
+ spins = 0
3169
+ tier = 0
3170
+ tiers = ["uncapped", "reduced speed", "one byte per sec"]
3171
+
3172
+ while lower < upper and not broken:
3173
+ with self.u2mutex:
3174
+ job = self.pipes.get(req_path)
3175
+ if not job:
3176
+ x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
3177
+ job = json.loads(x.get())
3178
+ if job:
3179
+ self.pipes.set(req_path, job)
3180
+
3181
+ if not job:
3182
+ t = "pipe: OK, upload has finished; yeeting remainder"
3183
+ self.log(t, 2)
3184
+ data_end = file_size
3185
+ break
3186
+
3187
+ if num_need != len(job["need"]):
3188
+ num_need = len(job["need"])
3189
+ data_end = 0
3190
+ for cid in job["hash"]:
3191
+ if cid in job["need"]:
3192
+ break
3193
+ data_end += chunk_size
3194
+ t = "pipe: can stream %.2f MiB; requested range is %.2f to %.2f"
3195
+ self.log(t % (data_end / M, lower / M, upper / M), 6)
3196
+ with self.u2mutex:
3197
+ if data_end > self.u2fh.aps.get(ap_data, data_end):
3198
+ try:
3199
+ fhs = self.u2fh.cache[ap_data].all_fhs
3200
+ for fh in fhs:
3201
+ fh.flush()
3202
+ self.u2fh.aps[ap_data] = data_end
3203
+ self.log("pipe: flushed %d up2k-FDs" % (len(fhs),))
3204
+ except Exception as ex:
3205
+ self.log("pipe: u2fh flush failed: %r" % (ex,))
3206
+
3207
+ if lower >= data_end:
3208
+ if data_end:
3209
+ t = "pipe: uploader is too slow; aborting download at %.2f MiB"
3210
+ self.log(t % (data_end / M))
3211
+ raise Pebkac(416, "uploader is too slow")
3212
+
3213
+ raise Pebkac(416, "no data available yet; please retry in a bit")
3214
+
3215
+ slack = data_end - lower
3216
+ if slack >= 8 * M:
3217
+ ntier = 0
3218
+ winsz = M
3219
+ bufsz = wr_sz
3220
+ slp = wr_slp
3221
+ else:
3222
+ winsz = max(40, int(M * (slack / (12 * M))))
3223
+ base_rate = M if not wr_slp else wr_sz / wr_slp
3224
+ if winsz > base_rate:
3225
+ ntier = 0
3226
+ bufsz = wr_sz
3227
+ slp = wr_slp
3228
+ elif winsz > 300:
3229
+ ntier = 1
3230
+ bufsz = winsz // 5
3231
+ slp = 0.2
3232
+ else:
3233
+ ntier = 2
3234
+ bufsz = winsz = slp = 1
3235
+
3236
+ if tier != ntier:
3237
+ tier = ntier
3238
+ self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
3239
+
3240
+ try:
3241
+ with open(ap_data, "rb", self.args.iobuf) as f:
3242
+ f.seek(lower)
3243
+ page = f.read(min(winsz, data_end - lower, upper - lower))
3244
+ if not page:
3245
+ raise Exception("got 0 bytes (EOF?)")
3246
+ except Exception as ex:
3247
+ self.log("pipe: read failed at %.2f MiB: %s" % (lower / M, ex), 3)
3248
+ with self.u2mutex:
3249
+ self.pipes.c.pop(req_path, None)
3250
+ spins += 1
3251
+ if spins > 3:
3252
+ raise Pebkac(500, "file became unreadable")
3253
+ time.sleep(2)
3254
+ continue
3255
+
3256
+ spins = 0
3257
+ pofs = 0
3258
+ while pofs < len(page):
3259
+ if slp:
3260
+ time.sleep(slp)
3261
+
3262
+ try:
3263
+ buf = page[pofs : pofs + bufsz]
3264
+ self.s.sendall(buf)
3265
+ zi = len(buf)
3266
+ remains -= zi
3267
+ lower += zi
3268
+ pofs += zi
3269
+ except:
3270
+ broken = True
3271
+ break
3272
+
3273
+ if lower < upper and not broken:
3274
+ with open(req_path, "rb") as f:
3275
+ remains = sendfile_py(self.log, lower, upper, f, self.s, wr_sz, wr_slp)
3276
+
3277
+ spd = self._spd((upper - lower) - remains)
3278
+ if self.do_log:
3279
+ self.log("{}, {}".format(logmsg, spd))
3280
+
3281
+ return not broken
3282
+
3107
3283
  def tx_zip(
3108
3284
  self,
3109
3285
  fmt ,
@@ -3741,7 +3917,7 @@ class HttpCli(object):
3741
3917
  if not allvols:
3742
3918
  ret = [{"kinshi": 1}]
3743
3919
 
3744
- jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, indent=0))
3920
+ jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
3745
3921
  zi = len(uret.split('\n"pd":')) - 1
3746
3922
  self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
3747
3923
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
@@ -4020,7 +4196,9 @@ class HttpCli(object):
4020
4196
  ):
4021
4197
  return self.tx_md(vn, abspath)
4022
4198
 
4023
- return self.tx_file(abspath)
4199
+ return self.tx_file(
4200
+ abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
4201
+ )
4024
4202
 
4025
4203
  elif is_dir and not self.can_read:
4026
4204
  if self._use_dirkey(abspath):
copyparty/httpconn.py CHANGED
@@ -52,6 +52,7 @@ class HttpConn(object):
52
52
  self.E = self.args.E
53
53
  self.asrv = hsrv.asrv # mypy404
54
54
  self.u2fh = hsrv.u2fh # mypy404
55
+ self.pipes = hsrv.pipes # mypy404
55
56
  self.ipa_nm = hsrv.ipa_nm
56
57
  self.xff_nm = hsrv.xff_nm
57
58
  self.xff_lan = hsrv.xff_lan # type: ignore
copyparty/httpsrv.py CHANGED
@@ -61,6 +61,7 @@ from .u2idx import U2idx
61
61
  from .util import (
62
62
  E_SCK,
63
63
  FHC,
64
+ CachedDict,
64
65
  Daemon,
65
66
  Garda,
66
67
  Magician,
@@ -126,6 +127,7 @@ class HttpSrv(object):
126
127
  self.t_periodic = None
127
128
 
128
129
  self.u2fh = FHC()
130
+ self.pipes = CachedDict(0.2)
129
131
  self.metrics = Metrics(self)
130
132
  self.nreq = 0
131
133
  self.nsus = 0
copyparty/svchub.py CHANGED
@@ -418,6 +418,12 @@ class SvcHub(object):
418
418
  t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
419
419
  self.log("root", t % (E.cfg, ", ".join(hits)), 3)
420
420
 
421
+ if self.args.no_bauth:
422
+ t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead"
423
+ self.log("root", t, 3)
424
+ if self.args.bauth_last:
425
+ self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
426
+
421
427
  def _process_config(self) :
422
428
  al = self.args
423
429
 
@@ -538,6 +544,13 @@ class SvcHub(object):
538
544
  except:
539
545
  raise Exception("invalid --rm-retry [%s]" % (self.args.rm_retry,))
540
546
 
547
+ try:
548
+ zf1, zf2 = self.args.mv_retry.split("/")
549
+ self.args.mv_re_t = float(zf1)
550
+ self.args.mv_re_r = float(zf2)
551
+ except:
552
+ raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
553
+
541
554
  return True
542
555
 
543
556
  def _ipa2re(self, txt) :
copyparty/th_srv.py CHANGED
@@ -28,6 +28,7 @@ from .util import (
28
28
  runcmd,
29
29
  statdir,
30
30
  vsplit,
31
+ wrename,
31
32
  wunlink,
32
33
  )
33
34
 
@@ -343,7 +344,7 @@ class ThumbSrv(object):
343
344
  pass
344
345
 
345
346
  try:
346
- bos.rename(ttpath, tpath)
347
+ wrename(self.log, ttpath, tpath, vn.flags)
347
348
  except:
348
349
  pass
349
350