diff --git a/copyparty/bos/bos.py b/copyparty/bos/bos.py index 04c4f7bc..37672f85 100644 --- a/copyparty/bos/bos.py +++ b/copyparty/bos/bos.py @@ -2,7 +2,7 @@ from __future__ import print_function, unicode_literals import os -from ..util import fsenc, fsdec +from ..util import fsenc, fsdec, SYMTIME from . import path @@ -55,5 +55,8 @@ def unlink(p): return os.unlink(fsenc(p)) -def utime(p, times=None): - return os.utime(fsenc(p), times) +def utime(p, times=None, follow_symlinks=True): + if SYMTIME: + return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks) + else: + return os.utime(fsenc(p), times) diff --git a/copyparty/bos/path.py b/copyparty/bos/path.py index 6a77cb6e..0ea543f6 100644 --- a/copyparty/bos/path.py +++ b/copyparty/bos/path.py @@ -2,7 +2,7 @@ from __future__ import print_function, unicode_literals import os -from ..util import fsenc, fsdec +from ..util import fsenc, fsdec, SYMTIME def abspath(p): @@ -13,8 +13,11 @@ def exists(p): return os.path.exists(fsenc(p)) -def getmtime(p): - return os.path.getmtime(fsenc(p)) +def getmtime(p, follow_symlinks=True): + if not follow_symlinks and SYMTIME: + return os.lstat(fsenc(p)).st_mtime + else: + return os.path.getmtime(fsenc(p)) def getsize(p): diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 58dea1bf..ee834935 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -21,6 +21,7 @@ from .util import ( Pebkac, Queue, ProgressPrinter, + SYMTIME, fsdec, fsenc, absreal, @@ -1307,7 +1308,7 @@ class Up2k(object): err = "partial upload exists at a different location; please resume uploading here instead:\n" err += "/" + quotep(vsrc) + " " - dupe = [cj["prel"], cj["name"]] + dupe = [cj["prel"], cj["name"], cj["lmod"]] try: self.dupesched[src].append(dupe) except: @@ -1332,7 +1333,7 @@ class Up2k(object): dst = os.path.join(job["ptop"], job["prel"], job["name"]) if not self.args.nw: bos.unlink(dst) # TODO ed pls - self._symlink(src, dst) + self._symlink(src, dst, lmod=cj["lmod"]) if cur: a = [cj[x] for x in "prel name lmod size addr".split()] @@ -1404,13 +1405,14 @@ class Up2k(object): with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f: return f["orz"][1] - def _symlink(self, src, dst, verbose=True): + def _symlink(self, src, dst, verbose=True, lmod=None): if verbose: self.log("linking dupe:\n {0}\n {1}".format(src, dst)) if self.args.nw: return + linked = False try: if self.args.no_symlink: raise Exception("disabled in config") @@ -1441,10 +1443,18 @@ class Up2k(object): hops = len(ndst[nc:]) - 1 lsrc = "../" * hops + "/".join(lsrc) os.symlink(fsenc(lsrc), fsenc(ldst)) + linked = True except Exception as ex: self.log("cannot symlink; creating copy: " + repr(ex)) shutil.copy2(fsenc(src), fsenc(dst)) + if lmod and (not linked or SYMTIME): + times = (int(time.time()), int(lmod)) + if ANYWIN: + self.lastmod_q.put([dst, 0, times]) + else: + bos.utime(dst, times, False) + def handle_chunk(self, ptop, wark, chash): with self.mutex: job = self.registry[ptop].get(wark) @@ -1551,12 +1561,12 @@ class Up2k(object): return cur = self.cur.get(ptop) - for rd, fn in dupes: + for rd, fn, lmod in dupes: d2 = os.path.join(ptop, rd, fn) if os.path.exists(d2): continue - self._symlink(dst, d2) + self._symlink(dst, d2, lmod=lmod) if cur: self.db_rm(cur, rd, fn) self.db_add(cur, wark, rd, fn, *a[-4:]) @@ -1773,8 +1783,9 @@ class Up2k(object): dlabs = absreal(sabs) m = "moving symlink from [{}] to [{}], target [{}]" self.log(m.format(sabs, dabs, dlabs)) - os.unlink(sabs) - self._symlink(dlabs, dabs, False) + mt = bos.path.getmtime(sabs, False) + bos.unlink(sabs) + self._symlink(dlabs, dabs, False, lmod=mt) # folders are too scary, schedule rescan of both vols self.need_rescan[svn.vpath] = 1 @@ -1904,25 +1915,30 @@ class Up2k(object): slabs = list(sorted(links.keys()))[0] ptop, rem = links.pop(slabs) self.log("linkswap [{}] and [{}]".format(sabs, slabs)) + mt = bos.path.getmtime(slabs, False) bos.unlink(slabs) bos.rename(sabs, slabs) + bos.utime(slabs, (int(time.time()), int(mt)), False) self._symlink(slabs, sabs, False) full[slabs] = [ptop, rem] + sabs = slabs if not dabs: dabs = list(sorted(full.keys()))[0] for alink in links.keys(): + lmod = None try: if alink != sabs and absreal(alink) != sabs: continue self.log("relinking [{}] to [{}]".format(alink, dabs)) + lmod = bos.path.getmtime(alink, False) bos.unlink(alink) except: pass - self._symlink(dabs, alink, False) + self._symlink(dabs, alink, False, lmod=lmod) return len(full) + len(links) @@ -2028,7 +2044,7 @@ class Up2k(object): for path, sz, times in ready: self.log("lmod: setting times {} on {}".format(times, path)) try: - bos.utime(path, times) + bos.utime(path, times, False) except: self.log("lmod: failed to utime ({}, {})".format(path, times)) diff --git a/copyparty/util.py b/copyparty/util.py index 8d9a2951..95c98bca 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -67,8 +67,9 @@ if WINDOWS and PY2: FS_ENCODING = "utf-8" -HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT" +SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks +HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT" HTTPCODE = { 200: "OK", diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 3072ee0c..fbae23bc 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -82,10 +82,12 @@ a, #files tbody div a:last-child { .s0:after, .s1:after { content: '⌄'; + margin-left: -.1em; } .s0r:after, .s1r:after { content: '⌃'; + margin-left: -.1em; } .s0:after, .s0r:after { @@ -96,7 +98,7 @@ a, #files tbody div a:last-child { color: #d09; } #files thead th:after { - margin-right: -.8em; + margin-right: -.7em; } #files tbody tr:hover td { background: #1c1c1c; diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index d7cddb09..5fd4be45 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -2562,9 +2562,9 @@ var thegrid = (function () { '+ chop: ' + ' ' + '+ sort by: ' + - 'name, ' + - 'size, ' + - 'date, ' + + 'name ' + + 'size ' + + 'date ' + 'type' + '' + '
'