mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
support copying files/folders; closes #115
behaves according to the target volume's deduplication config; will create symlinks / hardlinks instead if dedup is enabled
This commit is contained in:
parent
44ee07f0b2
commit
cacec9c1f3
|
@ -428,7 +428,7 @@ configuring accounts/volumes with arguments:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
* `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
|
* `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
|
||||||
* `w` (write): upload files, move files *into* this folder
|
* `w` (write): upload files, move/copy files *into* this folder
|
||||||
* `m` (move): move files/folders *from* this folder
|
* `m` (move): move files/folders *from* this folder
|
||||||
* `d` (delete): delete files/folders
|
* `d` (delete): delete files/folders
|
||||||
* `.` (dots): user can ask to show dotfiles in directory listings
|
* `.` (dots): user can ask to show dotfiles in directory listings
|
||||||
|
@ -508,7 +508,8 @@ the browser has the following hotkeys (always qwerty)
|
||||||
* `ESC` close various things
|
* `ESC` close various things
|
||||||
* `ctrl-K` delete selected files/folders
|
* `ctrl-K` delete selected files/folders
|
||||||
* `ctrl-X` cut selected files/folders
|
* `ctrl-X` cut selected files/folders
|
||||||
* `ctrl-V` paste
|
* `ctrl-C` copy selected files/folders to clipboard
|
||||||
|
* `ctrl-V` paste (move/copy)
|
||||||
* `Y` download selected files
|
* `Y` download selected files
|
||||||
* `F2` [rename](#batch-rename) selected file/folder
|
* `F2` [rename](#batch-rename) selected file/folder
|
||||||
* when a file/folder is selected (in not-grid-view):
|
* when a file/folder is selected (in not-grid-view):
|
||||||
|
@ -757,10 +758,11 @@ file selection: click somewhere on the line (not the link itself), then:
|
||||||
* shift-click another line for range-select
|
* shift-click another line for range-select
|
||||||
|
|
||||||
* cut: select some files and `ctrl-x`
|
* cut: select some files and `ctrl-x`
|
||||||
|
* copy: select some files and `ctrl-c`
|
||||||
* paste: `ctrl-v` in another folder
|
* paste: `ctrl-v` in another folder
|
||||||
* rename: `F2`
|
* rename: `F2`
|
||||||
|
|
||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can copy/move files across browser tabs (cut/copy in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
## shares
|
## shares
|
||||||
|
|
|
@ -2,7 +2,7 @@ standalone programs which are executed by copyparty when an event happens (uploa
|
||||||
|
|
||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
||||||
|
|
||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbr/xar/xbd/xad/xban)
|
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbc/xac/xbr/xar/xbd/xad/xban)
|
||||||
|
|
||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||||
|
|
||||||
|
|
|
@ -684,6 +684,8 @@ def get_sects():
|
||||||
\033[36mxbu\033[35m executes CMD before a file upload starts
|
\033[36mxbu\033[35m executes CMD before a file upload starts
|
||||||
\033[36mxau\033[35m executes CMD after a file upload finishes
|
\033[36mxau\033[35m executes CMD after a file upload finishes
|
||||||
\033[36mxiu\033[35m executes CMD after all uploads finish and volume is idle
|
\033[36mxiu\033[35m executes CMD after all uploads finish and volume is idle
|
||||||
|
\033[36mxbc\033[35m executes CMD before a file copy
|
||||||
|
\033[36mxac\033[35m executes CMD after a file copy
|
||||||
\033[36mxbr\033[35m executes CMD before a file rename/move
|
\033[36mxbr\033[35m executes CMD before a file rename/move
|
||||||
\033[36mxar\033[35m executes CMD after a file rename/move
|
\033[36mxar\033[35m executes CMD after a file rename/move
|
||||||
\033[36mxbd\033[35m executes CMD before a file delete
|
\033[36mxbd\033[35m executes CMD before a file delete
|
||||||
|
@ -1201,6 +1203,8 @@ def add_hooks(ap):
|
||||||
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts")
|
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts")
|
||||||
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes")
|
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes")
|
||||||
ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
|
ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
|
||||||
|
ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy")
|
||||||
|
ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy")
|
||||||
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename")
|
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename")
|
||||||
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename")
|
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename")
|
||||||
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete")
|
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete")
|
||||||
|
@ -1233,6 +1237,7 @@ def add_optouts(ap):
|
||||||
ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
|
ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
|
||||||
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
||||||
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
||||||
|
ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
|
||||||
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
||||||
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
||||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
||||||
|
|
|
@ -673,6 +673,10 @@ class VFS(object):
|
||||||
"""
|
"""
|
||||||
recursively yields from ./rem;
|
recursively yields from ./rem;
|
||||||
rel is a unix-style user-defined vpath (not vfs-related)
|
rel is a unix-style user-defined vpath (not vfs-related)
|
||||||
|
|
||||||
|
NOTE: don't invoke this function from a dbv; subvols are only
|
||||||
|
descended into if rem is blank due to the _ls `if not rem:`
|
||||||
|
which intention is to prevent unintended access to subvols
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, permsets, lstat=lstat)
|
fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, permsets, lstat=lstat)
|
||||||
|
@ -1383,7 +1387,7 @@ class AuthSrv(object):
|
||||||
flags[name] = True
|
flags[name] = True
|
||||||
return
|
return
|
||||||
|
|
||||||
zs = "mtp on403 on404 xbu xau xiu xbr xar xbd xad xm xban"
|
zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
|
||||||
if name not in zs.split():
|
if name not in zs.split():
|
||||||
if value is True:
|
if value is True:
|
||||||
t = "└─add volflag [{}] = {} ({})"
|
t = "└─add volflag [{}] = {} ({})"
|
||||||
|
@ -1938,7 +1942,7 @@ class AuthSrv(object):
|
||||||
vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
|
vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
|
||||||
|
|
||||||
# append additive args from argv to volflags
|
# append additive args from argv to volflags
|
||||||
hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
|
hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
|
||||||
for name in "mtp on404 on403".split() + hooks:
|
for name in "mtp on404 on403".split() + hooks:
|
||||||
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
||||||
|
|
||||||
|
@ -2641,7 +2645,7 @@ class AuthSrv(object):
|
||||||
]
|
]
|
||||||
|
|
||||||
csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
|
csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
|
||||||
zs = "c ihead ohead mtm mtp on403 on404 xad xar xau xiu xban xbd xbr xbu xm"
|
zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
|
||||||
lst = set(zs.split())
|
lst = set(zs.split())
|
||||||
askip = set("a v c vc cgen exp_lg exp_md theme".split())
|
askip = set("a v c vc cgen exp_lg exp_md theme".split())
|
||||||
fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
|
fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
|
||||||
|
|
|
@ -103,10 +103,12 @@ def vf_cmap() -> dict[str, str]:
|
||||||
"mte",
|
"mte",
|
||||||
"mth",
|
"mth",
|
||||||
"mtp",
|
"mtp",
|
||||||
|
"xac",
|
||||||
"xad",
|
"xad",
|
||||||
"xar",
|
"xar",
|
||||||
"xau",
|
"xau",
|
||||||
"xban",
|
"xban",
|
||||||
|
"xbc",
|
||||||
"xbd",
|
"xbd",
|
||||||
"xbr",
|
"xbr",
|
||||||
"xbu",
|
"xbu",
|
||||||
|
@ -212,6 +214,8 @@ flagcats = {
|
||||||
"xbu=CMD": "execute CMD before a file upload starts",
|
"xbu=CMD": "execute CMD before a file upload starts",
|
||||||
"xau=CMD": "execute CMD after a file upload finishes",
|
"xau=CMD": "execute CMD after a file upload finishes",
|
||||||
"xiu=CMD": "execute CMD after all uploads finish and volume is idle",
|
"xiu=CMD": "execute CMD after all uploads finish and volume is idle",
|
||||||
|
"xbc=CMD": "execute CMD before a file copy",
|
||||||
|
"xac=CMD": "execute CMD after a file copy",
|
||||||
"xbr=CMD": "execute CMD before a file rename/move",
|
"xbr=CMD": "execute CMD before a file rename/move",
|
||||||
"xar=CMD": "execute CMD after a file rename/move",
|
"xar=CMD": "execute CMD after a file rename/move",
|
||||||
"xbd=CMD": "execute CMD before a file delete",
|
"xbd=CMD": "execute CMD before a file delete",
|
||||||
|
|
|
@ -637,7 +637,7 @@ class HttpCli(object):
|
||||||
avn.can_access("", self.uname) if avn else [False] * 8
|
avn.can_access("", self.uname) if avn else [False] * 8
|
||||||
)
|
)
|
||||||
self.avn = avn
|
self.avn = avn
|
||||||
self.vn = vn
|
self.vn = vn # note: do not dbv due to walk/zipgen
|
||||||
self.rem = rem
|
self.rem = rem
|
||||||
|
|
||||||
self.s.settimeout(self.args.s_tbody or None)
|
self.s.settimeout(self.args.s_tbody or None)
|
||||||
|
@ -1196,6 +1196,9 @@ class HttpCli(object):
|
||||||
if "move" in self.uparam:
|
if "move" in self.uparam:
|
||||||
return self.handle_mv()
|
return self.handle_mv()
|
||||||
|
|
||||||
|
if "copy" in self.uparam:
|
||||||
|
return self.handle_cp()
|
||||||
|
|
||||||
if not self.vpath and self.ouparam:
|
if not self.vpath and self.ouparam:
|
||||||
if "reload" in self.uparam:
|
if "reload" in self.uparam:
|
||||||
return self.handle_reload()
|
return self.handle_reload()
|
||||||
|
@ -1791,6 +1794,9 @@ class HttpCli(object):
|
||||||
if "move" in self.uparam:
|
if "move" in self.uparam:
|
||||||
return self.handle_mv()
|
return self.handle_mv()
|
||||||
|
|
||||||
|
if "copy" in self.uparam:
|
||||||
|
return self.handle_cp()
|
||||||
|
|
||||||
if "delete" in self.uparam:
|
if "delete" in self.uparam:
|
||||||
return self.handle_rm([])
|
return self.handle_rm([])
|
||||||
|
|
||||||
|
@ -5021,16 +5027,39 @@ class HttpCli(object):
|
||||||
return self._mv(self.vpath, dst.lstrip("/"))
|
return self._mv(self.vpath, dst.lstrip("/"))
|
||||||
|
|
||||||
def _mv(self, vsrc: str, vdst: str) -> bool:
|
def _mv(self, vsrc: str, vdst: str) -> bool:
|
||||||
if not self.can_move:
|
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
|
||||||
|
|
||||||
if self.args.no_mv:
|
if self.args.no_mv:
|
||||||
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
||||||
|
|
||||||
|
self.asrv.vfs.get(vsrc, self.uname, True, False, True)
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
||||||
self.loud_reply(x.get(), status=201)
|
self.loud_reply(x.get(), status=201)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def handle_cp(self) -> bool:
|
||||||
|
# full path of new loc (incl filename)
|
||||||
|
dst = self.uparam.get("copy")
|
||||||
|
|
||||||
|
if self.is_vproxied and dst and dst.startswith(self.args.SR):
|
||||||
|
dst = dst[len(self.args.RS) :]
|
||||||
|
|
||||||
|
if not dst:
|
||||||
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
|
return self._cp(self.vpath, dst.lstrip("/"))
|
||||||
|
|
||||||
|
def _cp(self, vsrc: str, vdst: str) -> bool:
|
||||||
|
if self.args.no_cp:
|
||||||
|
raise Pebkac(403, "the copy feature is disabled in server config")
|
||||||
|
|
||||||
|
self.asrv.vfs.get(vsrc, self.uname, True, False)
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
|
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
|
||||||
|
self.loud_reply(x.get(), status=201)
|
||||||
|
return True
|
||||||
|
|
||||||
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
||||||
dirs = ls["dirs"]
|
dirs = ls["dirs"]
|
||||||
files = ls["files"]
|
files = ls["files"]
|
||||||
|
|
|
@ -1464,7 +1464,7 @@ class Up2k(object):
|
||||||
t = "failed to index subdir [{}]:\n{}"
|
t = "failed to index subdir [{}]:\n{}"
|
||||||
self.log(t.format(abspath, min_ex()), c=1)
|
self.log(t.format(abspath, min_ex()), c=1)
|
||||||
elif not stat.S_ISREG(inf.st_mode):
|
elif not stat.S_ISREG(inf.st_mode):
|
||||||
self.log("skip type-{:x} file [{}]".format(inf.st_mode, abspath))
|
self.log("skip type-0%o file [%s]" % (inf.st_mode, abspath))
|
||||||
else:
|
else:
|
||||||
# self.log("file: {}".format(abspath))
|
# self.log("file: {}".format(abspath))
|
||||||
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
|
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
|
||||||
|
@ -3896,13 +3896,13 @@ class Up2k(object):
|
||||||
partial = ""
|
partial = ""
|
||||||
if not unpost:
|
if not unpost:
|
||||||
permsets = [[True, False, False, True]]
|
permsets = [[True, False, False, True]]
|
||||||
vn, rem = self.vfs.get(vpath, uname, *permsets[0])
|
vn0, rem0 = self.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn0.get_dbv(rem0)
|
||||||
else:
|
else:
|
||||||
# unpost with missing permissions? verify with db
|
# unpost with missing permissions? verify with db
|
||||||
permsets = [[False, True]]
|
permsets = [[False, True]]
|
||||||
vn, rem = self.vfs.get(vpath, uname, *permsets[0])
|
vn0, rem0 = self.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn0.get_dbv(rem0)
|
||||||
ptop = vn.realpath
|
ptop = vn.realpath
|
||||||
with self.mutex, self.reg_mutex:
|
with self.mutex, self.reg_mutex:
|
||||||
abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1)
|
abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1)
|
||||||
|
@ -3958,7 +3958,9 @@ class Up2k(object):
|
||||||
|
|
||||||
scandir = not self.args.no_scandir
|
scandir = not self.args.no_scandir
|
||||||
if is_dir:
|
if is_dir:
|
||||||
g = vn.walk("", rem, [], uname, permsets, True, scandir, True)
|
# note: deletion inside shares would require a rewrite here;
|
||||||
|
# shares necessitate get_dbv which is incompatible with walk
|
||||||
|
g = vn0.walk("", rem0, [], uname, permsets, True, scandir, True)
|
||||||
if unpost:
|
if unpost:
|
||||||
raise Pebkac(400, "cannot unpost folders")
|
raise Pebkac(400, "cannot unpost folders")
|
||||||
elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
|
elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
|
||||||
|
@ -3966,7 +3968,7 @@ class Up2k(object):
|
||||||
vpath_dir = vsplit(vpath)[0]
|
vpath_dir = vsplit(vpath)[0]
|
||||||
g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore
|
g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore
|
||||||
else:
|
else:
|
||||||
self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop))
|
self.log("rm: skip type-0%o file [%s]" % (st.st_mode, atop))
|
||||||
return 0, [], []
|
return 0, [], []
|
||||||
|
|
||||||
xbd = vn.flags.get("xbd")
|
xbd = vn.flags.get("xbd")
|
||||||
|
@ -4066,17 +4068,226 @@ class Up2k(object):
|
||||||
|
|
||||||
return n_files, ok + ok2, ng + ng2
|
return n_files, ok + ok2, ng + ng2
|
||||||
|
|
||||||
|
def handle_cp(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||||
|
if svp == dvp or dvp.startswith(svp + "/"):
|
||||||
|
raise Pebkac(400, "cp: cannot copy parent into subfolder")
|
||||||
|
|
||||||
|
svn, srem = self.vfs.get(svp, uname, True, False)
|
||||||
|
svn_dbv, _ = svn.get_dbv(srem)
|
||||||
|
sabs = svn.canonical(srem, False)
|
||||||
|
curs: set["sqlite3.Cursor"] = set()
|
||||||
|
self.db_act = self.vol_act[svn_dbv.realpath] = time.time()
|
||||||
|
|
||||||
|
st = bos.stat(sabs)
|
||||||
|
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
|
||||||
|
with self.mutex:
|
||||||
|
try:
|
||||||
|
ret = self._cp_file(uname, ip, svp, dvp, curs)
|
||||||
|
finally:
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
|
raise Pebkac(400, "cannot copy type-0%o file" % (st.st_mode,))
|
||||||
|
|
||||||
|
permsets = [[True, False]]
|
||||||
|
scandir = not self.args.no_scandir
|
||||||
|
|
||||||
|
# don't use svn_dbv; would skip subvols due to _ls `if not rem:`
|
||||||
|
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
||||||
|
with self.mutex:
|
||||||
|
try:
|
||||||
|
for dbv, vrem, _, atop, files, rd, vd in g:
|
||||||
|
for fn in files:
|
||||||
|
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
||||||
|
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
||||||
|
if not svpf.startswith(svp + "/"): # assert
|
||||||
|
self.log(min_ex(), 1)
|
||||||
|
t = "cp: bug at %s, top %s%s"
|
||||||
|
raise Pebkac(500, t % (svpf, svp, SEESLOG))
|
||||||
|
|
||||||
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
|
self._cp_file(uname, ip, svpf, dvpf, curs)
|
||||||
|
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
curs.clear()
|
||||||
|
finally:
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
|
||||||
|
return "k"
|
||||||
|
|
||||||
|
def _cp_file(
|
||||||
|
self, uname: str, ip: str, svp: str, dvp: str, curs: set["sqlite3.Cursor"]
|
||||||
|
) -> str:
|
||||||
|
"""mutex(main) me; will mutex(reg)"""
|
||||||
|
svn, srem = self.vfs.get(svp, uname, True, False)
|
||||||
|
svn_dbv, srem_dbv = svn.get_dbv(srem)
|
||||||
|
|
||||||
|
dvn, drem = self.vfs.get(dvp, uname, False, True)
|
||||||
|
dvn, drem = dvn.get_dbv(drem)
|
||||||
|
|
||||||
|
sabs = svn.canonical(srem, False)
|
||||||
|
dabs = dvn.canonical(drem)
|
||||||
|
drd, dfn = vsplit(drem)
|
||||||
|
|
||||||
|
if bos.path.exists(dabs):
|
||||||
|
raise Pebkac(400, "cp2: target file exists")
|
||||||
|
|
||||||
|
st = stl = bos.lstat(sabs)
|
||||||
|
if stat.S_ISLNK(stl.st_mode):
|
||||||
|
is_link = True
|
||||||
|
try:
|
||||||
|
st = bos.stat(sabs)
|
||||||
|
except:
|
||||||
|
pass # broken symlink; keep as-is
|
||||||
|
elif not stat.S_ISREG(st.st_mode):
|
||||||
|
self.log("skipping type-0%o file [%s]" % (st.st_mode, sabs))
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
is_link = False
|
||||||
|
|
||||||
|
ftime = stl.st_mtime
|
||||||
|
fsize = st.st_size
|
||||||
|
|
||||||
|
xbc = svn.flags.get("xbc")
|
||||||
|
xac = dvn.flags.get("xac")
|
||||||
|
if xbc:
|
||||||
|
if not runhook(
|
||||||
|
self.log,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
"xbc",
|
||||||
|
xbc,
|
||||||
|
sabs,
|
||||||
|
svp,
|
||||||
|
"",
|
||||||
|
uname,
|
||||||
|
self.vfs.get_perms(svp, uname),
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
ip,
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "copy blocked by xbr server config: {}".format(svp)
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(405, t)
|
||||||
|
|
||||||
|
bos.makedirs(os.path.dirname(dabs))
|
||||||
|
|
||||||
|
c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
|
||||||
|
svn_dbv.realpath, srem_dbv
|
||||||
|
)
|
||||||
|
c2 = self.cur.get(dvn.realpath)
|
||||||
|
|
||||||
|
if w:
|
||||||
|
assert c1 # !rm
|
||||||
|
if c2 and c2 != c1:
|
||||||
|
self._copy_tags(c1, c2, w)
|
||||||
|
|
||||||
|
curs.add(c1)
|
||||||
|
|
||||||
|
if c2:
|
||||||
|
self.db_add(
|
||||||
|
c2,
|
||||||
|
{}, # skip upload hooks
|
||||||
|
drd,
|
||||||
|
dfn,
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
dvn.realpath,
|
||||||
|
dvn.vpath,
|
||||||
|
w,
|
||||||
|
w,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
ip or "",
|
||||||
|
at or 0,
|
||||||
|
)
|
||||||
|
curs.add(c2)
|
||||||
|
else:
|
||||||
|
self.log("not found in src db: [{}]".format(svp))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if is_link and st != stl:
|
||||||
|
# relink non-broken symlinks to still work after the move,
|
||||||
|
# but only resolve 1st level to maintain relativity
|
||||||
|
dlink = bos.readlink(sabs)
|
||||||
|
dlink = os.path.join(os.path.dirname(sabs), dlink)
|
||||||
|
dlink = bos.path.abspath(dlink)
|
||||||
|
self._symlink(dlink, dabs, dvn.flags, lmod=ftime)
|
||||||
|
else:
|
||||||
|
self._symlink(sabs, dabs, dvn.flags, lmod=ftime)
|
||||||
|
|
||||||
|
except OSError as ex:
|
||||||
|
if ex.errno != errno.EXDEV:
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.log("using plain copy (%s):\n %s\n %s" % (ex.strerror, sabs, dabs))
|
||||||
|
b1, b2 = fsenc(sabs), fsenc(dabs)
|
||||||
|
is_link = os.path.islink(b1) # due to _relink
|
||||||
|
try:
|
||||||
|
shutil.copy2(b1, b2)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not is_link:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# broken symlink? keep it as-is
|
||||||
|
try:
|
||||||
|
zb = os.readlink(b1)
|
||||||
|
os.symlink(zb, b2)
|
||||||
|
except:
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
raise
|
||||||
|
|
||||||
|
if is_link:
|
||||||
|
try:
|
||||||
|
times = (int(time.time()), int(ftime))
|
||||||
|
bos.utime(dabs, times, False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if xac:
|
||||||
|
runhook(
|
||||||
|
self.log,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
"xac",
|
||||||
|
xac,
|
||||||
|
dabs,
|
||||||
|
dvp,
|
||||||
|
"",
|
||||||
|
uname,
|
||||||
|
self.vfs.get_perms(dvp, uname),
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
ip,
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
return "k"
|
||||||
|
|
||||||
def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||||
if svp == dvp or dvp.startswith(svp + "/"):
|
if svp == dvp or dvp.startswith(svp + "/"):
|
||||||
raise Pebkac(400, "mv: cannot move parent into subfolder")
|
raise Pebkac(400, "mv: cannot move parent into subfolder")
|
||||||
|
|
||||||
svn, srem = self.vfs.get(svp, uname, True, False, True)
|
svn, srem = self.vfs.get(svp, uname, True, False, True)
|
||||||
svn, srem = svn.get_dbv(srem)
|
jail, jail_rem = svn.get_dbv(srem)
|
||||||
sabs = svn.canonical(srem, False)
|
sabs = svn.canonical(srem, False)
|
||||||
curs: set["sqlite3.Cursor"] = set()
|
curs: set["sqlite3.Cursor"] = set()
|
||||||
self.db_act = self.vol_act[svn.realpath] = time.time()
|
self.db_act = self.vol_act[jail.realpath] = time.time()
|
||||||
|
|
||||||
if not srem:
|
if not jail_rem:
|
||||||
raise Pebkac(400, "mv: cannot move a mountpoint")
|
raise Pebkac(400, "mv: cannot move a mountpoint")
|
||||||
|
|
||||||
st = bos.lstat(sabs)
|
st = bos.lstat(sabs)
|
||||||
|
@ -4090,7 +4301,9 @@ class Up2k(object):
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
jail = svn.get_dbv(srem)[0]
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
|
raise Pebkac(400, "cannot move type-0%o file" % (st.st_mode,))
|
||||||
|
|
||||||
permsets = [[True, False, True]]
|
permsets = [[True, False, True]]
|
||||||
scandir = not self.args.no_scandir
|
scandir = not self.args.no_scandir
|
||||||
|
|
||||||
|
@ -4102,13 +4315,13 @@ class Up2k(object):
|
||||||
raise Pebkac(400, "mv: source folder contains other volumes")
|
raise Pebkac(400, "mv: source folder contains other volumes")
|
||||||
|
|
||||||
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
||||||
for dbv, vrem, _, atop, files, rd, vd in g:
|
with self.mutex:
|
||||||
if dbv != jail:
|
try:
|
||||||
# the actual check (avoid toctou)
|
for dbv, vrem, _, atop, files, rd, vd in g:
|
||||||
raise Pebkac(400, "mv: source folder contains other volumes")
|
if dbv != jail:
|
||||||
|
# the actual check (avoid toctou)
|
||||||
|
raise Pebkac(400, "mv: source folder contains other volumes")
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
try:
|
|
||||||
for fn in files:
|
for fn in files:
|
||||||
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
||||||
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
||||||
|
@ -4119,11 +4332,13 @@ class Up2k(object):
|
||||||
|
|
||||||
dvpf = dvp + svpf[len(svp) :]
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
self._mv_file(uname, ip, svpf, dvpf, curs)
|
self._mv_file(uname, ip, svpf, dvpf, curs)
|
||||||
finally:
|
|
||||||
for v in curs:
|
for v in curs:
|
||||||
v.connection.commit()
|
v.connection.commit()
|
||||||
|
curs.clear()
|
||||||
curs.clear()
|
finally:
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
|
||||||
rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1)
|
rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1)
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,9 @@ var Ls = {
|
||||||
["T", "toggle thumbnails / icons"],
|
["T", "toggle thumbnails / icons"],
|
||||||
["🡅 A/D", "thumbnail size"],
|
["🡅 A/D", "thumbnail size"],
|
||||||
["ctrl-K", "delete selected"],
|
["ctrl-K", "delete selected"],
|
||||||
["ctrl-X", "cut selected"],
|
["ctrl-X", "cut selection to clipboard"],
|
||||||
["ctrl-V", "paste into folder"],
|
["ctrl-C", "copy selection to clipboard"],
|
||||||
|
["ctrl-V", "paste (move/copy) here"],
|
||||||
["Y", "download selected"],
|
["Y", "download selected"],
|
||||||
["F2", "rename selected"],
|
["F2", "rename selected"],
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ var Ls = {
|
||||||
["I/K", "prev/next file"],
|
["I/K", "prev/next file"],
|
||||||
["M", "close textfile"],
|
["M", "close textfile"],
|
||||||
["E", "edit textfile"],
|
["E", "edit textfile"],
|
||||||
["S", "select file (for cut/rename)"],
|
["S", "select file (for cut/copy/rename)"],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -133,6 +134,7 @@ var Ls = {
|
||||||
"wt_ren": "rename selected items$NHotkey: F2",
|
"wt_ren": "rename selected items$NHotkey: F2",
|
||||||
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
||||||
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
||||||
|
"wt_cpy": "copy selected items to clipboard$N(to paste them somewhere else)$NHotkey: ctrl-C",
|
||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
|
@ -327,6 +329,7 @@ var Ls = {
|
||||||
"fr_emore": "select at least one item to rename",
|
"fr_emore": "select at least one item to rename",
|
||||||
"fd_emore": "select at least one item to delete",
|
"fd_emore": "select at least one item to delete",
|
||||||
"fc_emore": "select at least one item to cut",
|
"fc_emore": "select at least one item to cut",
|
||||||
|
"fcp_emore": "select at least one item to copy to clipboard",
|
||||||
|
|
||||||
"fs_sc": "share the folder you're in",
|
"fs_sc": "share the folder you're in",
|
||||||
"fs_ss": "share the selected files",
|
"fs_ss": "share the selected files",
|
||||||
|
@ -379,16 +382,26 @@ var Ls = {
|
||||||
"fc_ok": "cut {0} items",
|
"fc_ok": "cut {0} items",
|
||||||
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
"fp_ecut": "first cut some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
"fcc_ok": "copied {0} items to clipboard",
|
||||||
|
"fcc_warn": 'copied {0} items to clipboard\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
|
"fp_ecut": "first cut or copy some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
||||||
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
||||||
|
"fcp_ename": "these {0} items cannot be copied here (names already exist):",
|
||||||
"fp_ok": "move OK",
|
"fp_ok": "move OK",
|
||||||
|
"fcp_ok": "copy OK",
|
||||||
"fp_busy": "moving {0} items...\n\n{1}",
|
"fp_busy": "moving {0} items...\n\n{1}",
|
||||||
|
"fcp_busy": "copying {0} items...\n\n{1}",
|
||||||
"fp_err": "move failed:\n",
|
"fp_err": "move failed:\n",
|
||||||
|
"fcp_err": "copy failed:\n",
|
||||||
"fp_confirm": "move these {0} items here?",
|
"fp_confirm": "move these {0} items here?",
|
||||||
|
"fcp_confirm": "copy these {0} items here?",
|
||||||
"fp_etab": 'failed to read clipboard from other browser tab',
|
"fp_etab": 'failed to read clipboard from other browser tab',
|
||||||
"fp_name": "uploading a file from your device. Give it a name:",
|
"fp_name": "uploading a file from your device. Give it a name:",
|
||||||
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
|
"fcp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Copy {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Copy</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
|
||||||
"mk_noname": "type a name into the text field on the left before you do that :p",
|
"mk_noname": "type a name into the text field on the left before you do that :p",
|
||||||
|
|
||||||
|
@ -400,7 +413,7 @@ var Ls = {
|
||||||
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
||||||
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
||||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||||
"tvt_sel": "select file ( for cut / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
|
@ -605,8 +618,9 @@ var Ls = {
|
||||||
["T", "miniatyrbilder på/av"],
|
["T", "miniatyrbilder på/av"],
|
||||||
["🡅 A/D", "ikonstørrelse"],
|
["🡅 A/D", "ikonstørrelse"],
|
||||||
["ctrl-K", "slett valgte"],
|
["ctrl-K", "slett valgte"],
|
||||||
["ctrl-X", "klipp ut"],
|
["ctrl-X", "klipp ut valgte"],
|
||||||
["ctrl-V", "lim inn"],
|
["ctrl-C", "kopiér til utklippstavle"],
|
||||||
|
["ctrl-V", "lim inn (flytt/kopiér)"],
|
||||||
["Y", "last ned valgte"],
|
["Y", "last ned valgte"],
|
||||||
["F2", "endre navn på valgte"],
|
["F2", "endre navn på valgte"],
|
||||||
|
|
||||||
|
@ -702,7 +716,8 @@ var Ls = {
|
||||||
"wt_ren": "gi nye navn til de valgte filene$NSnarvei: F2",
|
"wt_ren": "gi nye navn til de valgte filene$NSnarvei: F2",
|
||||||
"wt_del": "slett de valgte filene$NSnarvei: ctrl-K",
|
"wt_del": "slett de valgte filene$NSnarvei: ctrl-K",
|
||||||
"wt_cut": "klipp ut de valgte filene <small>(for å lime inn et annet sted)</small>$NSnarvei: ctrl-X",
|
"wt_cut": "klipp ut de valgte filene <small>(for å lime inn et annet sted)</small>$NSnarvei: ctrl-X",
|
||||||
"wt_pst": "lim inn filer (som tidligere ble klippet ut et annet sted)$NSnarvei: ctrl-V",
|
"wt_cpy": "kopiér de valgte filene til utklippstavlen$N(for å lime inn et annet sted)$NSnarvei: ctrl-C",
|
||||||
|
"wt_pst": "lim inn filer (som tidligere ble klippet ut / kopiert et annet sted)$NSnarvei: ctrl-V",
|
||||||
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
||||||
"wt_selinv": "inverter utvalg",
|
"wt_selinv": "inverter utvalg",
|
||||||
"wt_selzip": "last ned de valgte filene som et arkiv",
|
"wt_selzip": "last ned de valgte filene som et arkiv",
|
||||||
|
@ -845,7 +860,7 @@ var Ls = {
|
||||||
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
||||||
"mt_follow": "bla slik at sangen som spilles alltid er synlig\">🎯",
|
"mt_follow": "bla slik at sangen som spilles alltid er synlig\">🎯",
|
||||||
"mt_compact": "tettpakket avspillerpanel\">⟎",
|
"mt_compact": "tettpakket avspillerpanel\">⟎",
|
||||||
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">uncache",
|
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">oppfrisk",
|
||||||
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
||||||
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
||||||
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
||||||
|
@ -896,6 +911,7 @@ var Ls = {
|
||||||
"fr_emore": "velg minst én fil som skal få nytt navn",
|
"fr_emore": "velg minst én fil som skal få nytt navn",
|
||||||
"fd_emore": "velg minst én fil som skal slettes",
|
"fd_emore": "velg minst én fil som skal slettes",
|
||||||
"fc_emore": "velg minst én fil som skal klippes ut",
|
"fc_emore": "velg minst én fil som skal klippes ut",
|
||||||
|
"fcp_emore": "velg minst én fil som skal kopieres til utklippstavlen",
|
||||||
|
|
||||||
"fs_sc": "del mappen du er i nå",
|
"fs_sc": "del mappen du er i nå",
|
||||||
"fs_ss": "del de valgte filene",
|
"fs_ss": "del de valgte filene",
|
||||||
|
@ -948,16 +964,26 @@ var Ls = {
|
||||||
"fc_ok": "klippet ut {0} filer",
|
"fc_ok": "klippet ut {0} filer",
|
||||||
"fc_warn": 'klippet ut {0} filer\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
"fc_warn": 'klippet ut {0} filer\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
||||||
|
|
||||||
"fp_ecut": "du må klippe ut noen filer / mapper først\n\nmerk: du kan gjerne jobbe på kryss av nettleserfaner; klippe ut i én fane, lime inn i en annen",
|
"fcc_ok": "kopierte {0} filer til utklippstavlen",
|
||||||
|
"fcc_warn": 'kopierte {0} filer til utklippstavlen\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
||||||
|
|
||||||
|
"fp_ecut": "du må klippe ut eller kopiere noen filer / mapper først\n\nmerk: du kan gjerne jobbe på kryss av nettleserfaner; klippe ut i én fane, lime inn i en annen",
|
||||||
"fp_ename": "disse {0} filene kan ikke flyttes til målmappen fordi det allerede finnes filer med samme navn:",
|
"fp_ename": "disse {0} filene kan ikke flyttes til målmappen fordi det allerede finnes filer med samme navn:",
|
||||||
|
"fcp_ename": "disse {0} filene kan ikke kopieres til målmappen fordi det allerede finnes filer med samme navn:",
|
||||||
"fp_ok": "flytting OK",
|
"fp_ok": "flytting OK",
|
||||||
|
"fcp_ok": "kopiering OK",
|
||||||
"fp_busy": "flytter {0} filer...\n\n{1}",
|
"fp_busy": "flytter {0} filer...\n\n{1}",
|
||||||
|
"fcp_busy": "kopierer {0} filer...\n\n{1}",
|
||||||
"fp_err": "flytting feilet:\n",
|
"fp_err": "flytting feilet:\n",
|
||||||
|
"fcp_err": "kopiering feilet:\n",
|
||||||
"fp_confirm": "flytt disse {0} filene hit?",
|
"fp_confirm": "flytt disse {0} filene hit?",
|
||||||
|
"fcp_confirm": "kopiér disse {0} filene hit?",
|
||||||
"fp_etab": 'kunne ikke lese listen med filer ifra den andre nettleserfanen',
|
"fp_etab": 'kunne ikke lese listen med filer ifra den andre nettleserfanen',
|
||||||
"fp_name": "Laster opp én fil fra enheten din. Velg filnavn:",
|
"fp_name": "Laster opp én fil fra enheten din. Velg filnavn:",
|
||||||
"fp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Flytt {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
"fp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Flytt {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
||||||
|
"fcp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Kopiér {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Flytt</a><a href="#" id="modal-ng">Last opp</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Flytt</a><a href="#" id="modal-ng">Last opp</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Kopiér</a><a href="#" id="modal-ng">Last opp</a>',
|
||||||
|
|
||||||
"mk_noname": "skriv inn et navn i tekstboksen til venstre først :p",
|
"mk_noname": "skriv inn et navn i tekstboksen til venstre først :p",
|
||||||
|
|
||||||
|
@ -1176,6 +1202,7 @@ var Ls = {
|
||||||
["🡅 A/D", "缩略图大小"],
|
["🡅 A/D", "缩略图大小"],
|
||||||
["ctrl-K", "删除选中项"],
|
["ctrl-K", "删除选中项"],
|
||||||
["ctrl-X", "剪切选中项"],
|
["ctrl-X", "剪切选中项"],
|
||||||
|
["ctrl-C", "复制选中项"], //m
|
||||||
["ctrl-V", "粘贴到文件夹"],
|
["ctrl-V", "粘贴到文件夹"],
|
||||||
["Y", "下载选中项"],
|
["Y", "下载选中项"],
|
||||||
["F2", "重命名选中项"],
|
["F2", "重命名选中项"],
|
||||||
|
@ -1271,6 +1298,7 @@ var Ls = {
|
||||||
"wt_ren": "重命名选中的项目$N快捷键: F2",
|
"wt_ren": "重命名选中的项目$N快捷键: F2",
|
||||||
"wt_del": "删除选中的项目$N快捷键: ctrl-K",
|
"wt_del": "删除选中的项目$N快捷键: ctrl-K",
|
||||||
"wt_cut": "剪切选中的项目<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-X",
|
"wt_cut": "剪切选中的项目<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-X",
|
||||||
|
"wt_cpy": "将选中的项目复制到剪贴板<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-C", //m
|
||||||
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
||||||
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
||||||
"wt_selinv": "反转选择",
|
"wt_selinv": "反转选择",
|
||||||
|
@ -1465,6 +1493,7 @@ var Ls = {
|
||||||
"fr_emore": "选择至少一个项目以重命名",
|
"fr_emore": "选择至少一个项目以重命名",
|
||||||
"fd_emore": "选择至少一个项目以删除",
|
"fd_emore": "选择至少一个项目以删除",
|
||||||
"fc_emore": "选择至少一个项目以剪切",
|
"fc_emore": "选择至少一个项目以剪切",
|
||||||
|
"fcp_emore": "选择至少一个要复制到剪贴板的项目", //m
|
||||||
|
|
||||||
"fs_sc": "分享你所在的文件夹",
|
"fs_sc": "分享你所在的文件夹",
|
||||||
"fs_ss": "分享选定的文件",
|
"fs_ss": "分享选定的文件",
|
||||||
|
@ -1517,16 +1546,26 @@ var Ls = {
|
||||||
"fc_ok": "剪切 {0} 项",
|
"fc_ok": "剪切 {0} 项",
|
||||||
"fc_warn": '剪切 {0} 项\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)',
|
"fc_warn": '剪切 {0} 项\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)',
|
||||||
|
|
||||||
"fp_ecut": "首先剪切一些文件/文件夹以粘贴/移动\n\n注意:你可以在不同的浏览器标签页之间剪切/粘贴",
|
"fcc_ok": "已将 {0} 项复制到剪贴板", //m
|
||||||
|
"fcc_warn": '已将 {0} 项复制到剪贴板\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)', //m
|
||||||
|
|
||||||
|
"fp_ecut": "首先剪切或复制一些文件/文件夹以粘贴/移动\n\n注意:你可以在不同的浏览器标签页之间剪切/粘贴", //m
|
||||||
"fp_ename": "这些 {0} 项不能移动到这里(名称已存在):",
|
"fp_ename": "这些 {0} 项不能移动到这里(名称已存在):",
|
||||||
|
"fcp_ename": "这些 {0} 项不能复制到这里(名称已存在):", //m
|
||||||
"fp_ok": "移动成功",
|
"fp_ok": "移动成功",
|
||||||
|
"fcp_ok": "复制成功", //m
|
||||||
"fp_busy": "正在移动 {0} 项...\n\n{1}",
|
"fp_busy": "正在移动 {0} 项...\n\n{1}",
|
||||||
|
"fcp_busy": "正在复制 {0} 项...\n\n{1}", //m
|
||||||
"fp_err": "移动失败:\n",
|
"fp_err": "移动失败:\n",
|
||||||
|
"fcp_err": "复制失败:\n", //m
|
||||||
"fp_confirm": "将这些 {0} 项移动到这里?",
|
"fp_confirm": "将这些 {0} 项移动到这里?",
|
||||||
|
"fcp_confirm": "将这些 {0} 项复制到这里?", //m
|
||||||
"fp_etab": '无法从其他浏览器标签页读取剪贴板',
|
"fp_etab": '无法从其他浏览器标签页读取剪贴板',
|
||||||
"fp_name": "从你的设备上传一个文件。给它一个名字:",
|
"fp_name": "从你的设备上传一个文件。给它一个名字:",
|
||||||
"fp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 移动 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件',
|
"fp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 移动 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件',
|
||||||
|
"fcp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 复制 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件', //m
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">移动</a><a href="#" id="modal-ng">上传</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">移动</a><a href="#" id="modal-ng">上传</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">复制</a><a href="#" id="modal-ng">上传</a>', //m
|
||||||
|
|
||||||
"mk_noname": "在左侧文本框中输入名称,然后再执行此操作 :p",
|
"mk_noname": "在左侧文本框中输入名称,然后再执行此操作 :p",
|
||||||
|
|
||||||
|
@ -1771,6 +1810,7 @@ ebi('widget').innerHTML = (
|
||||||
' href="#" id="fren" tt="' + L.wt_ren + '">✎<span>name</span></a><a' +
|
' href="#" id="fren" tt="' + L.wt_ren + '">✎<span>name</span></a><a' +
|
||||||
' href="#" id="fdel" tt="' + L.wt_del + '">⌫<span>del.</span></a><a' +
|
' href="#" id="fdel" tt="' + L.wt_del + '">⌫<span>del.</span></a><a' +
|
||||||
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
||||||
|
' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
|
||||||
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
||||||
'</span><span id="wzip"><a' +
|
'</span><span id="wzip"><a' +
|
||||||
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
||||||
|
@ -4377,6 +4417,7 @@ var fileman = (function () {
|
||||||
var bren = ebi('fren'),
|
var bren = ebi('fren'),
|
||||||
bdel = ebi('fdel'),
|
bdel = ebi('fdel'),
|
||||||
bcut = ebi('fcut'),
|
bcut = ebi('fcut'),
|
||||||
|
bcpy = ebi('fcpy'),
|
||||||
bpst = ebi('fpst'),
|
bpst = ebi('fpst'),
|
||||||
bshr = ebi('fshr'),
|
bshr = ebi('fshr'),
|
||||||
t_paste,
|
t_paste,
|
||||||
|
@ -4389,14 +4430,19 @@ var fileman = (function () {
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
r.render = function () {
|
r.render = function () {
|
||||||
if (r.clip === null)
|
if (r.clip === null) {
|
||||||
r.clip = jread('fman_clip', []).slice(1);
|
r.clip = jread('fman_clip', []).slice(1);
|
||||||
|
r.ccp = r.clip.length && r.clip[0] == '//c';
|
||||||
|
if (r.ccp)
|
||||||
|
r.clip.shift();
|
||||||
|
}
|
||||||
|
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
nsel = sel.length,
|
nsel = sel.length,
|
||||||
enren = nsel,
|
enren = nsel,
|
||||||
endel = nsel,
|
endel = nsel,
|
||||||
encut = nsel,
|
encut = nsel,
|
||||||
|
encpy = nsel,
|
||||||
enpst = r.clip && r.clip.length,
|
enpst = r.clip && r.clip.length,
|
||||||
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
||||||
hdel = !(have_del && has(perms, 'delete')),
|
hdel = !(have_del && has(perms, 'delete')),
|
||||||
|
@ -4410,6 +4456,7 @@ var fileman = (function () {
|
||||||
clmod(bren, 'en', enren);
|
clmod(bren, 'en', enren);
|
||||||
clmod(bdel, 'en', endel);
|
clmod(bdel, 'en', endel);
|
||||||
clmod(bcut, 'en', encut);
|
clmod(bcut, 'en', encut);
|
||||||
|
clmod(bcpy, 'en', encpy);
|
||||||
clmod(bpst, 'en', enpst);
|
clmod(bpst, 'en', enpst);
|
||||||
clmod(bshr, 'en', 1);
|
clmod(bshr, 'en', 1);
|
||||||
|
|
||||||
|
@ -5015,7 +5062,8 @@ var fileman = (function () {
|
||||||
r.cut = function (e) {
|
r.cut = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
vps = [];
|
stamp = Date.now(),
|
||||||
|
vps = [stamp];
|
||||||
|
|
||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
return toast.err(3, L.fc_emore);
|
return toast.err(3, L.fc_emore);
|
||||||
|
@ -5046,9 +5094,11 @@ var fileman = (function () {
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
|
r.ccp = false;
|
||||||
|
r.clip = vps.slice(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var stamp = Date.now();
|
vps = JSON.stringify(vps);
|
||||||
vps = JSON.stringify([stamp].concat(vps));
|
|
||||||
if (vps.length > 1024 * 1024)
|
if (vps.length > 1024 * 1024)
|
||||||
throw 'a';
|
throw 'a';
|
||||||
|
|
||||||
|
@ -5062,6 +5112,59 @@ var fileman = (function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
r.cpy = function (e) {
|
||||||
|
ev(e);
|
||||||
|
var sel = msel.getsel(),
|
||||||
|
stamp = Date.now(),
|
||||||
|
vps = [stamp, '//c'];
|
||||||
|
|
||||||
|
if (!sel.length)
|
||||||
|
return toast.err(3, L.fcp_emore);
|
||||||
|
|
||||||
|
var els = [], griden = thegrid.en;
|
||||||
|
for (var a = 0; a < sel.length; a++) {
|
||||||
|
vps.push(sel[a].vp);
|
||||||
|
if (sel.length < 100)
|
||||||
|
try {
|
||||||
|
if (griden)
|
||||||
|
els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
|
||||||
|
else
|
||||||
|
els.push(ebi(sel[a].id).closest('tr'));
|
||||||
|
|
||||||
|
clmod(els[a], 'fcut');
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
|
for (var a = 0; a < els.length; a++)
|
||||||
|
clmod(els[a], 'fcut', 1);
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
if (vps.length < 3)
|
||||||
|
vps.pop();
|
||||||
|
|
||||||
|
r.ccp = true;
|
||||||
|
r.clip = vps.slice(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
vps = JSON.stringify(vps);
|
||||||
|
if (vps.length > 1024 * 1024)
|
||||||
|
throw 'a';
|
||||||
|
|
||||||
|
swrite('fman_clip', vps);
|
||||||
|
r.tx(stamp);
|
||||||
|
if (sel.length)
|
||||||
|
toast.inf(1.5, L.fcc_ok.format(sel.length));
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
toast.warn(30, L.fcc_warn.format(sel.length));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
document.onpaste = function (e) {
|
document.onpaste = function (e) {
|
||||||
var xfer = e.clipboardData || window.clipboardData;
|
var xfer = e.clipboardData || window.clipboardData;
|
||||||
if (!xfer || !xfer.files || !xfer.files.length)
|
if (!xfer || !xfer.files || !xfer.files.length)
|
||||||
|
@ -5077,9 +5180,9 @@ var fileman = (function () {
|
||||||
return r.clip_up(files);
|
return r.clip_up(files);
|
||||||
|
|
||||||
var src = r.clip.length == 1 ? r.clip[0] : vsplit(r.clip[0])[0],
|
var src = r.clip.length == 1 ? r.clip[0] : vsplit(r.clip[0])[0],
|
||||||
msg = L.fp_both_m.format(r.clip.length, src, files.length);
|
msg = (r.ccp ? L.fcp_both_m : L.fp_both_m).format(r.clip.length, src, files.length);
|
||||||
|
|
||||||
modal.confirm(msg, r.paste, function () { r.clip_up(files); }, null, L.fp_both_b);
|
modal.confirm(msg, r.paste, function () { r.clip_up(files); }, null, (r.ccp ? L.fcp_both_b : L.fp_both_b));
|
||||||
};
|
};
|
||||||
|
|
||||||
r.clip_up = function (files) {
|
r.clip_up = function (files) {
|
||||||
|
@ -5157,7 +5260,7 @@ var fileman = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists.length)
|
if (exists.length)
|
||||||
toast.warn(30, L.fp_ename.format(exists.length) + '<ul>' + uricom_adec(exists, true).join('') + '</ul>');
|
toast.warn(30, (r.ccp ? L.fcp_ename : L.fp_ename).format(exists.length) + '<ul>' + uricom_adec(exists, true).join('') + '</ul>');
|
||||||
|
|
||||||
if (!req.length)
|
if (!req.length)
|
||||||
return;
|
return;
|
||||||
|
@ -5167,29 +5270,30 @@ var fileman = (function () {
|
||||||
vp = req.shift();
|
vp = req.shift();
|
||||||
|
|
||||||
if (!vp) {
|
if (!vp) {
|
||||||
toast.ok(2, L.fp_ok);
|
toast.ok(2, r.ccp ? L.fcp_ok : L.fp_ok);
|
||||||
treectl.goto();
|
treectl.goto();
|
||||||
r.tx(srcdir);
|
r.tx(srcdir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.show('inf r', 0, esc(L.fp_busy.format(req.length + 1, uricom_dec(vp))));
|
toast.show('inf r', 0, esc((r.ccp ? L.fcp_busy : L.fp_busy).format(req.length + 1, uricom_dec(vp))));
|
||||||
|
|
||||||
var dst = get_evpath() + vp.split('/').pop();
|
var act = r.ccp ? '?copy=' : '?move=',
|
||||||
|
dst = get_evpath() + vp.split('/').pop();
|
||||||
|
|
||||||
xhr.open('POST', vp + '?move=' + dst, true);
|
xhr.open('POST', vp + act + dst, true);
|
||||||
xhr.onload = xhr.onerror = paste_cb;
|
xhr.onload = xhr.onerror = paste_cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
function paste_cb() {
|
function paste_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = unpre(this.responseText);
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fp_err + msg);
|
toast.err(9, (r.ccp ? L.fcp_err : L.fp_err) + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
paster();
|
paster();
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.confirm(L.fp_confirm.format(req.length) + '<ul>' + uricom_adec(req, true).join('') + '</ul>', function () {
|
modal.confirm((r.ccp ? L.fcp_confirm : L.fp_confirm).format(req.length) + '<ul>' + uricom_adec(req, true).join('') + '</ul>', function () {
|
||||||
paster();
|
paster();
|
||||||
jwrite('fman_clip', [Date.now()]);
|
jwrite('fman_clip', [Date.now()]);
|
||||||
}, null);
|
}, null);
|
||||||
|
@ -5231,6 +5335,7 @@ var fileman = (function () {
|
||||||
bren.onclick = r.rename;
|
bren.onclick = r.rename;
|
||||||
bdel.onclick = r.delete;
|
bdel.onclick = r.delete;
|
||||||
bcut.onclick = r.cut;
|
bcut.onclick = r.cut;
|
||||||
|
bcpy.onclick = r.cpy;
|
||||||
bpst.onclick = r.paste;
|
bpst.onclick = r.paste;
|
||||||
bshr.onclick = r.share;
|
bshr.onclick = r.share;
|
||||||
|
|
||||||
|
@ -6326,9 +6431,15 @@ var ahotkeys = function (e) {
|
||||||
return hkhelp();
|
return hkhelp();
|
||||||
|
|
||||||
if (ctrl(e)) {
|
if (ctrl(e)) {
|
||||||
|
var sel = window.getSelection && window.getSelection() || {};
|
||||||
|
sel = sel && !sel.isCollapsed && sel.direction != 'none';
|
||||||
|
|
||||||
if (k == 'KeyX' || k == 'x')
|
if (k == 'KeyX' || k == 'x')
|
||||||
return fileman.cut();
|
return fileman.cut();
|
||||||
|
|
||||||
|
if ((k == 'KeyC' || k == 'c') && !sel)
|
||||||
|
return fileman.cpy();
|
||||||
|
|
||||||
if (k == 'KeyV' || k == 'v')
|
if (k == 'KeyV' || k == 'v')
|
||||||
return fileman.d_paste();
|
return fileman.d_paste();
|
||||||
|
|
||||||
|
|
|
@ -163,6 +163,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
||||||
|
|
||||||
| method | params | result |
|
| method | params | result |
|
||||||
|--|--|--|
|
|--|--|--|
|
||||||
|
| POST | `?copy=/foo/bar` | copy the file/folder at URL to /foo/bar |
|
||||||
| POST | `?move=/foo/bar` | move/rename the file/folder at URL to /foo/bar |
|
| POST | `?move=/foo/bar` | move/rename the file/folder at URL to /foo/bar |
|
||||||
|
|
||||||
| method | params | body | result |
|
| method | params | body | result |
|
||||||
|
|
|
@ -121,8 +121,9 @@ var tl_browser = {
|
||||||
["T", "toggle thumbnails / icons"],
|
["T", "toggle thumbnails / icons"],
|
||||||
["🡅 A/D", "thumbnail size"],
|
["🡅 A/D", "thumbnail size"],
|
||||||
["ctrl-K", "delete selected"],
|
["ctrl-K", "delete selected"],
|
||||||
["ctrl-X", "cut selected"],
|
["ctrl-X", "cut selection to clipboard"],
|
||||||
["ctrl-V", "paste into folder"],
|
["ctrl-C", "copy selection to clipboard"],
|
||||||
|
["ctrl-V", "paste (move/copy) here"],
|
||||||
["Y", "download selected"],
|
["Y", "download selected"],
|
||||||
["F2", "rename selected"],
|
["F2", "rename selected"],
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ var tl_browser = {
|
||||||
["I/K", "prev/next file"],
|
["I/K", "prev/next file"],
|
||||||
["M", "close textfile"],
|
["M", "close textfile"],
|
||||||
["E", "edit textfile"],
|
["E", "edit textfile"],
|
||||||
["S", "select file (for cut/rename)"],
|
["S", "select file (for cut/copy/rename)"],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -217,6 +218,7 @@ var tl_browser = {
|
||||||
"wt_ren": "rename selected items$NHotkey: F2",
|
"wt_ren": "rename selected items$NHotkey: F2",
|
||||||
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
||||||
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
||||||
|
"wt_cpy": "copy selected items to clipboard$N(to paste them somewhere else)$NHotkey: ctrl-C",
|
||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
|
@ -411,6 +413,7 @@ var tl_browser = {
|
||||||
"fr_emore": "select at least one item to rename",
|
"fr_emore": "select at least one item to rename",
|
||||||
"fd_emore": "select at least one item to delete",
|
"fd_emore": "select at least one item to delete",
|
||||||
"fc_emore": "select at least one item to cut",
|
"fc_emore": "select at least one item to cut",
|
||||||
|
"fcp_emore": "select at least one item to copy",
|
||||||
|
|
||||||
"fs_sc": "share the folder you're in",
|
"fs_sc": "share the folder you're in",
|
||||||
"fs_ss": "share the selected files",
|
"fs_ss": "share the selected files",
|
||||||
|
@ -463,16 +466,26 @@ var tl_browser = {
|
||||||
"fc_ok": "cut {0} items",
|
"fc_ok": "cut {0} items",
|
||||||
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
"fp_ecut": "first cut some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
"fcc_ok": "copied {0} items to clipboard",
|
||||||
|
"fcc_warn": 'copied {0} items to clipboard\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
|
"fp_ecut": "first cut or copy some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
||||||
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
||||||
|
"fcp_ename": "these {0} items cannot be copied here (names already exist):",
|
||||||
"fp_ok": "move OK",
|
"fp_ok": "move OK",
|
||||||
|
"fcp_ok": "copy OK",
|
||||||
"fp_busy": "moving {0} items...\n\n{1}",
|
"fp_busy": "moving {0} items...\n\n{1}",
|
||||||
|
"fcp_busy": "copying {0} items...\n\n{1}",
|
||||||
"fp_err": "move failed:\n",
|
"fp_err": "move failed:\n",
|
||||||
|
"fcp_err": "copy failed:\n",
|
||||||
"fp_confirm": "move these {0} items here?",
|
"fp_confirm": "move these {0} items here?",
|
||||||
|
"fcp_confirm": "copy these {0} items here?",
|
||||||
"fp_etab": 'failed to read clipboard from other browser tab',
|
"fp_etab": 'failed to read clipboard from other browser tab',
|
||||||
"fp_name": "uploading a file from your device. Give it a name:",
|
"fp_name": "uploading a file from your device. Give it a name:",
|
||||||
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
|
"fcp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Copy {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Copy</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
|
||||||
"mk_noname": "type a name into the text field on the left before you do that :p",
|
"mk_noname": "type a name into the text field on the left before you do that :p",
|
||||||
|
|
||||||
|
@ -484,7 +497,7 @@ var tl_browser = {
|
||||||
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
||||||
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
||||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||||
"tvt_sel": "select file ( for cut / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
|
|
109
tests/test_cp.py
Normal file
109
tests/test_cp.py
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
||||||
|
class TestDedup(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
if os.path.exists(td):
|
||||||
|
shutil.rmtree(td)
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
for a in "abc":
|
||||||
|
os.mkdir(a)
|
||||||
|
for b in "fg":
|
||||||
|
d = "%s/%s%s" % (a, a, b)
|
||||||
|
os.mkdir(d)
|
||||||
|
for fn in "x":
|
||||||
|
fp = "%s/%s%s%s" % (d, a, b, fn)
|
||||||
|
with open(fp, "wb") as f:
|
||||||
|
f.write(fp.encode("utf-8"))
|
||||||
|
return td
|
||||||
|
|
||||||
|
def cinit(self):
|
||||||
|
if self.conn:
|
||||||
|
self.fstab = self.conn.hsrv.hub.up2k.fstab
|
||||||
|
self.conn.hsrv.hub.up2k.shutdown()
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"", True)
|
||||||
|
if self.fstab:
|
||||||
|
self.conn.hsrv.hub.up2k.fstab = self.fstab
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
tc_dedup = ["sym", "no"]
|
||||||
|
vols = [".::A", "a/af:a/af:r", "b:a/b:r"]
|
||||||
|
tcs = [
|
||||||
|
"/a?copy=/c/a /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/a/af/afx /c/a/ag/agx /c/a/b/bf/bfx /c/a/b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
"/b?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/bf/bfx /d/bg/bgx",
|
||||||
|
"/b/bf?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/bfx",
|
||||||
|
"/a/af?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/afx",
|
||||||
|
"/a/af?copy=/ /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /afx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
"/a/af/afx?copy=/afx /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /afx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.conn = None
|
||||||
|
self.fstab = None
|
||||||
|
self.ctr = 0 # 2304
|
||||||
|
for dedup, act_exp in product(tc_dedup, tcs):
|
||||||
|
action, expect = act_exp.split(" ", 1)
|
||||||
|
t = "dedup:%s action:%s" % (dedup, action)
|
||||||
|
print("\n\n\033[0;7m# ", t, "\033[0m")
|
||||||
|
|
||||||
|
ka = {"dav_inf": True}
|
||||||
|
if dedup == "hard":
|
||||||
|
ka["hardlink"] = True
|
||||||
|
elif dedup == "no":
|
||||||
|
ka["no_dedup"] = True
|
||||||
|
|
||||||
|
self.args = Cfg(v=vols, a=[], **ka)
|
||||||
|
self.reset()
|
||||||
|
self.cinit()
|
||||||
|
|
||||||
|
self.do_cp(action)
|
||||||
|
zs = self.propfind()
|
||||||
|
|
||||||
|
fns = " ".join(zs[1])
|
||||||
|
self.assertEqual(expect, fns)
|
||||||
|
|
||||||
|
def do_cp(self, action):
|
||||||
|
hdr = "POST %s HTTP/1.1\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
buf = (hdr % (action,)).encode("utf-8")
|
||||||
|
print("CP [%s]" % (action,))
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("CP <-- ", ret)
|
||||||
|
self.assertIn(" 201 Created", ret[0])
|
||||||
|
self.assertEqual("k\r\n", ret[1])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def propfind(self):
|
||||||
|
h = "PROPFIND / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||||
|
HttpCli(self.conn.setbuf(h.encode("utf-8"))).run()
|
||||||
|
h, t = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
fns = t.split("<D:response><D:href>")[1:]
|
||||||
|
fns = [x.split("</D", 1)[0] for x in fns]
|
||||||
|
fns = [x for x in fns if not x.endswith("/")]
|
||||||
|
fns.sort()
|
||||||
|
return h, fns
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
|
@ -122,7 +122,7 @@ class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_clone no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
||||||
|
@ -146,7 +146,7 @@ class Cfg(Namespace):
|
||||||
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
||||||
ka.update(**{k: "no" for k in ex.split()})
|
ka.update(**{k: "no" for k in ex.split()})
|
||||||
|
|
||||||
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
ex = "grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||||
ka.update(**{k: [] for k in ex.split()})
|
ka.update(**{k: [] for k in ex.split()})
|
||||||
|
|
||||||
ex = "exp_lg exp_md"
|
ex = "exp_lg exp_md"
|
||||||
|
|
Loading…
Reference in a new issue