preserve empty folders (closes #23):

* when deleting files, do not cascade upwards through empty folders
* when moving folders, also move any empty folders inside

the only remaining action which autoremoves empty folders is
files getting deleted as they expire volume lifetimes

also prevents accidentally moving parent folders into subfolders
(even though that actually worked surprisingly well)
This commit is contained in:
ed 2023-04-29 11:30:43 +00:00
parent c44f5f5701
commit 83178d0836
5 changed files with 39 additions and 25 deletions

View file

@ -271,7 +271,7 @@ class FtpFs(AbstractedFS):
vp = join(self.cwd, path).lstrip("/")
try:
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [])
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False)
except Exception as ex:
raise FSE(str(ex))

View file

@ -1152,18 +1152,9 @@ class HttpCli(object):
dst = self.headers["destination"]
dst = re.sub("^https?://[^/]+", "", dst).lstrip()
dst = unquotep(dst)
if not self._mv(self.vpath, dst):
if not self._mv(self.vpath, dst.lstrip("/")):
return False
# up2k only cares about files and removes all empty folders;
# clients naturally expect empty folders to survive a rename
vn, rem = self.asrv.vfs.get(dst, self.uname, False, False)
dabs = vn.canonical(rem)
try:
bos.makedirs(dabs)
except:
pass
return True
def _applesan(self) -> bool:
@ -3054,7 +3045,7 @@ class HttpCli(object):
ret = self.gen_tree(top, dst)
if self.is_vproxied:
parents = self.args.R.split("/")
for parent in parents[::-1]:
for parent in reversed(parents):
ret = {"k%s" % (parent,): ret, "a": []}
zs = json.dumps(ret)
@ -3193,7 +3184,9 @@ class HttpCli(object):
nlim = int(self.uparam.get("lim") or 0)
lim = [nlim, nlim] if nlim else []
x = self.conn.hsrv.broker.ask("up2k.handle_rm", self.uname, self.ip, req, lim)
x = self.conn.hsrv.broker.ask(
"up2k.handle_rm", self.uname, self.ip, req, lim, False
)
self.loud_reply(x.get())
return True
@ -3210,7 +3203,7 @@ class HttpCli(object):
# x-www-form-urlencoded (url query part) uses
# either + or %20 for 0x20 so handle both
dst = unquotep(dst.replace("+", " "))
return self._mv(self.vpath, dst)
return self._mv(self.vpath, dst.lstrip("/"))
def _mv(self, vsrc: str, vdst: str) -> bool:
if not self.can_move:

View file

@ -261,7 +261,7 @@ class SMB(object):
yeet("blocked delete (no-del-acc): " + vpath)
vpath = vpath.replace("\\", "/").lstrip("/")
self.hub.up2k.handle_rm(LEELOO_DALLAS, "1.7.6.2", [vpath], [])
self.hub.up2k.handle_rm(LEELOO_DALLAS, "1.7.6.2", [vpath], [], False)
def _utime(self, vpath: str, times: tuple[float, float]) -> None:
if not self.args.smbw:

View file

@ -384,7 +384,7 @@ class Up2k(object):
if vp:
fvp = "%s/%s" % (vp, fvp)
self._handle_rm(LEELOO_DALLAS, "", fvp, [])
self._handle_rm(LEELOO_DALLAS, "", fvp, [], True)
nrm += 1
if nrm:
@ -2897,7 +2897,9 @@ class Up2k(object):
except:
pass
def handle_rm(self, uname: str, ip: str, vpaths: list[str], lim: list[int]) -> str:
def handle_rm(
self, uname: str, ip: str, vpaths: list[str], lim: list[int], rm_up: bool
) -> str:
n_files = 0
ok = {}
ng = {}
@ -2906,7 +2908,7 @@ class Up2k(object):
self.log("hit delete limit of {} files".format(lim[1]), 3)
break
a, b, c = self._handle_rm(uname, ip, vp, lim)
a, b, c = self._handle_rm(uname, ip, vp, lim, rm_up)
n_files += a
for k in b:
ok[k] = 1
@ -2920,7 +2922,7 @@ class Up2k(object):
return "deleted {} files (and {}/{} folders)".format(n_files, iok, iok + ing)
def _handle_rm(
self, uname: str, ip: str, vpath: str, lim: list[int]
self, uname: str, ip: str, vpath: str, lim: list[int], rm_up: bool
) -> tuple[int, list[str], list[str]]:
self.db_act = time.time()
try:
@ -3027,16 +3029,22 @@ class Up2k(object):
if xad:
runhook(self.log, xad, abspath, vpath, "", uname, 0, 0, ip, 0, "")
ok: list[str] = []
ng: list[str] = []
if is_dir:
ok, ng = rmdirs(self.log_func, scandir, True, atop, 1)
else:
ok = ng = []
if rm_up:
ok2, ng2 = rmdirs_up(os.path.dirname(atop), ptop)
else:
ok2 = ng2 = []
return n_files, ok + ok2, ng + ng2
def handle_mv(self, uname: str, svp: str, dvp: str) -> str:
if svp == dvp or dvp.startswith(svp + "/"):
raise Pebkac(400, "mv: cannot move parent into subfolder")
svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
svn, srem = svn.get_dbv(srem)
sabs = svn.canonical(srem, False)
@ -3090,8 +3098,21 @@ class Up2k(object):
curs.clear()
rmdirs(self.log_func, scandir, True, sabs, 1)
rmdirs_up(os.path.dirname(sabs), svn.realpath)
rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1)
for zsl in (rm_ok, rm_ng):
for ap in reversed(zsl):
if not ap.startswith(sabs):
raise Pebkac(500, "mv_d: bug at {}, top {}".format(ap, sabs))
rem = ap[len(sabs) :].replace(os.sep, "/").lstrip("/")
vp = vjoin(dvp, rem)
try:
dvn, drem = self.asrv.vfs.get(vp, uname, False, True)
bos.mkdir(dvn.canonical(drem))
except:
pass
return "k"
def _mv_file(

View file

@ -2270,7 +2270,7 @@ def rmdirs(
dirs = [os.path.join(top, x) for x in dirs]
ok = []
ng = []
for d in dirs[::-1]:
for d in reversed(dirs):
a, b = rmdirs(logger, scandir, lstat, d, depth + 1)
ok += a
ng += b