preserve mtimes when juggling symlinks

This commit is contained in:
ed 2021-12-04 01:58:04 +01:00
parent f39f575a9c
commit 241ef5b99d
6 changed files with 45 additions and 20 deletions

View file

@ -2,7 +2,7 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os import os
from ..util import fsenc, fsdec from ..util import fsenc, fsdec, SYMTIME
from . import path from . import path
@ -55,5 +55,8 @@ def unlink(p):
return os.unlink(fsenc(p)) return os.unlink(fsenc(p))
def utime(p, times=None): def utime(p, times=None, follow_symlinks=True):
return os.utime(fsenc(p), times) if SYMTIME:
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
else:
return os.utime(fsenc(p), times)

View file

@ -2,7 +2,7 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os import os
from ..util import fsenc, fsdec from ..util import fsenc, fsdec, SYMTIME
def abspath(p): def abspath(p):
@ -13,8 +13,11 @@ def exists(p):
return os.path.exists(fsenc(p)) return os.path.exists(fsenc(p))
def getmtime(p): def getmtime(p, follow_symlinks=True):
return os.path.getmtime(fsenc(p)) if not follow_symlinks and SYMTIME:
return os.lstat(fsenc(p)).st_mtime
else:
return os.path.getmtime(fsenc(p))
def getsize(p): def getsize(p):

View file

@ -21,6 +21,7 @@ from .util import (
Pebkac, Pebkac,
Queue, Queue,
ProgressPrinter, ProgressPrinter,
SYMTIME,
fsdec, fsdec,
fsenc, fsenc,
absreal, absreal,
@ -1307,7 +1308,7 @@ class Up2k(object):
err = "partial upload exists at a different location; please resume uploading here instead:\n" err = "partial upload exists at a different location; please resume uploading here instead:\n"
err += "/" + quotep(vsrc) + " " err += "/" + quotep(vsrc) + " "
dupe = [cj["prel"], cj["name"]] dupe = [cj["prel"], cj["name"], cj["lmod"]]
try: try:
self.dupesched[src].append(dupe) self.dupesched[src].append(dupe)
except: except:
@ -1332,7 +1333,7 @@ class Up2k(object):
dst = os.path.join(job["ptop"], job["prel"], job["name"]) dst = os.path.join(job["ptop"], job["prel"], job["name"])
if not self.args.nw: if not self.args.nw:
bos.unlink(dst) # TODO ed pls bos.unlink(dst) # TODO ed pls
self._symlink(src, dst) self._symlink(src, dst, lmod=cj["lmod"])
if cur: if cur:
a = [cj[x] for x in "prel name lmod size addr".split()] 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: with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f:
return f["orz"][1] return f["orz"][1]
def _symlink(self, src, dst, verbose=True): def _symlink(self, src, dst, verbose=True, lmod=None):
if verbose: if verbose:
self.log("linking dupe:\n {0}\n {1}".format(src, dst)) self.log("linking dupe:\n {0}\n {1}".format(src, dst))
if self.args.nw: if self.args.nw:
return return
linked = False
try: try:
if self.args.no_symlink: if self.args.no_symlink:
raise Exception("disabled in config") raise Exception("disabled in config")
@ -1441,10 +1443,18 @@ class Up2k(object):
hops = len(ndst[nc:]) - 1 hops = len(ndst[nc:]) - 1
lsrc = "../" * hops + "/".join(lsrc) lsrc = "../" * hops + "/".join(lsrc)
os.symlink(fsenc(lsrc), fsenc(ldst)) os.symlink(fsenc(lsrc), fsenc(ldst))
linked = True
except Exception as ex: except Exception as ex:
self.log("cannot symlink; creating copy: " + repr(ex)) self.log("cannot symlink; creating copy: " + repr(ex))
shutil.copy2(fsenc(src), fsenc(dst)) 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): def handle_chunk(self, ptop, wark, chash):
with self.mutex: with self.mutex:
job = self.registry[ptop].get(wark) job = self.registry[ptop].get(wark)
@ -1551,12 +1561,12 @@ class Up2k(object):
return return
cur = self.cur.get(ptop) cur = self.cur.get(ptop)
for rd, fn in dupes: for rd, fn, lmod in dupes:
d2 = os.path.join(ptop, rd, fn) d2 = os.path.join(ptop, rd, fn)
if os.path.exists(d2): if os.path.exists(d2):
continue continue
self._symlink(dst, d2) self._symlink(dst, d2, lmod=lmod)
if cur: if cur:
self.db_rm(cur, rd, fn) self.db_rm(cur, rd, fn)
self.db_add(cur, wark, rd, fn, *a[-4:]) self.db_add(cur, wark, rd, fn, *a[-4:])
@ -1773,8 +1783,9 @@ class Up2k(object):
dlabs = absreal(sabs) dlabs = absreal(sabs)
m = "moving symlink from [{}] to [{}], target [{}]" m = "moving symlink from [{}] to [{}], target [{}]"
self.log(m.format(sabs, dabs, dlabs)) self.log(m.format(sabs, dabs, dlabs))
os.unlink(sabs) mt = bos.path.getmtime(sabs, False)
self._symlink(dlabs, dabs, False) bos.unlink(sabs)
self._symlink(dlabs, dabs, False, lmod=mt)
# folders are too scary, schedule rescan of both vols # folders are too scary, schedule rescan of both vols
self.need_rescan[svn.vpath] = 1 self.need_rescan[svn.vpath] = 1
@ -1904,25 +1915,30 @@ class Up2k(object):
slabs = list(sorted(links.keys()))[0] slabs = list(sorted(links.keys()))[0]
ptop, rem = links.pop(slabs) ptop, rem = links.pop(slabs)
self.log("linkswap [{}] and [{}]".format(sabs, slabs)) self.log("linkswap [{}] and [{}]".format(sabs, slabs))
mt = bos.path.getmtime(slabs, False)
bos.unlink(slabs) bos.unlink(slabs)
bos.rename(sabs, slabs) bos.rename(sabs, slabs)
bos.utime(slabs, (int(time.time()), int(mt)), False)
self._symlink(slabs, sabs, False) self._symlink(slabs, sabs, False)
full[slabs] = [ptop, rem] full[slabs] = [ptop, rem]
sabs = slabs
if not dabs: if not dabs:
dabs = list(sorted(full.keys()))[0] dabs = list(sorted(full.keys()))[0]
for alink in links.keys(): for alink in links.keys():
lmod = None
try: try:
if alink != sabs and absreal(alink) != sabs: if alink != sabs and absreal(alink) != sabs:
continue continue
self.log("relinking [{}] to [{}]".format(alink, dabs)) self.log("relinking [{}] to [{}]".format(alink, dabs))
lmod = bos.path.getmtime(alink, False)
bos.unlink(alink) bos.unlink(alink)
except: except:
pass pass
self._symlink(dabs, alink, False) self._symlink(dabs, alink, False, lmod=lmod)
return len(full) + len(links) return len(full) + len(links)
@ -2028,7 +2044,7 @@ class Up2k(object):
for path, sz, times in ready: for path, sz, times in ready:
self.log("lmod: setting times {} on {}".format(times, path)) self.log("lmod: setting times {} on {}".format(times, path))
try: try:
bos.utime(path, times) bos.utime(path, times, False)
except: except:
self.log("lmod: failed to utime ({}, {})".format(path, times)) self.log("lmod: failed to utime ({}, {})".format(path, times))

View file

@ -67,8 +67,9 @@ if WINDOWS and PY2:
FS_ENCODING = "utf-8" 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 = { HTTPCODE = {
200: "OK", 200: "OK",

View file

@ -82,10 +82,12 @@ a, #files tbody div a:last-child {
.s0:after, .s0:after,
.s1:after { .s1:after {
content: '⌄'; content: '⌄';
margin-left: -.1em;
} }
.s0r:after, .s0r:after,
.s1r:after { .s1r:after {
content: '⌃'; content: '⌃';
margin-left: -.1em;
} }
.s0:after, .s0:after,
.s0r:after { .s0r:after {
@ -96,7 +98,7 @@ a, #files tbody div a:last-child {
color: #d09; color: #d09;
} }
#files thead th:after { #files thead th:after {
margin-right: -.8em; margin-right: -.7em;
} }
#files tbody tr:hover td { #files tbody tr:hover td {
background: #1c1c1c; background: #1c1c1c;

View file

@ -2562,9 +2562,9 @@ var thegrid = (function () {
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' + '<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' +
'<a href="#" class="btn" l="-1" tt="truncate filenames more (show less)">&ndash;</a> ' + '<a href="#" class="btn" l="-1" tt="truncate filenames more (show less)">&ndash;</a> ' +
'<a href="#" class="btn" l="1" tt="truncate filenames less (show more)">+</a></span> <span>sort by: ' + '<a href="#" class="btn" l="1" tt="truncate filenames less (show more)">+</a></span> <span>sort by: ' +
'<a href="#" s="href">name</a>, ' + '<a href="#" s="href">name</a> ' +
'<a href="#" s="sz">size</a>, ' + '<a href="#" s="sz">size</a> ' +
'<a href="#" s="ts">date</a>, ' + '<a href="#" s="ts">date</a> ' +
'<a href="#" s="ext">type</a>' + '<a href="#" s="ext">type</a>' +
'</span></div>' + '</span></div>' +
'<div id="ggrid"></div>' '<div id="ggrid"></div>'