1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
"""
What an upload will do, decided before anything is sent — shared by `put` and
the interactive mode, so both refuse the same names and the same collisions.
A name already in the destination folder, compared without case as the node and
the Files tab compare it, or a name the node does not accept (`names.py`), is
found here; what to do about it (refuse the run, skip it, ask) is the caller's.
"""
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from meshbay_common.paths import fold
from ..errors import USAGE, CliError
from ..output import safe
from .names import same_name, upload_name_problem
from .tree import Tree
@dataclass
class UploadFile:
local: Path
rel: list[str] # remote parts below the destination folder
size: int
def plan_upload(sources: list[str], recursive: bool):
"""Folders to create (parents first) and files to send, as remote parts."""
dirs: list[list[str]] = []
files: list[UploadFile] = []
problems: list[str] = []
seen: dict[str, str] = {}
for src in sources:
path = Path(src).expanduser()
try:
st = path.stat()
except OSError as e:
problems.append(f"{src}: {e.strerror}")
continue
top = path.resolve().name if src.rstrip("/\\") in (".", "..") else path.name
key = fold(top)
if key in seen:
problems.append(f"{src} and {seen[key]} would have the same name there")
continue
seen[key] = src
if stat.S_ISREG(st.st_mode):
files.append(UploadFile(path, [top], st.st_size))
elif stat.S_ISDIR(st.st_mode):
if not recursive:
raise CliError(USAGE, f"{safe(src)} is a folder; use -R.")
for here, subdirs, names in os.walk(path):
rel = [top] + list(Path(here).relative_to(path).parts)
dirs.append(rel)
for d in list(subdirs):
if (Path(here) / d).is_symlink():
subdirs.remove(d)
problems.append(f"{Path(here) / d} is a link to a folder; not "
"followed")
for n in names:
p = Path(here) / n
try:
st_n = p.stat()
except OSError as e:
problems.append(f"{p}: {e.strerror}")
continue
if stat.S_ISREG(st_n.st_mode):
files.append(UploadFile(p, rel + [n], st_n.st_size))
subdirs.sort()
else:
problems.append(f"{src} is neither a file nor a folder")
return dirs, files, problems
def taken_names(tree: Tree, folder: str, names: list[str]) -> set[str]:
"""The names among `names` already in `folder`, compared without case."""
there = [i.name for i in tree.children(folder, include_unavailable=True)]
return {n for n in names if any(same_name(n, t) for t in there)}
def refused_names(file_names: list[str], dirs: list[list[str]]) -> list[str]:
"""Names the node would refuse — the client's copy of its rule."""
out = []
for name in file_names:
if (why := upload_name_problem(name)):
out.append(f"{name}: {why}")
for d in dirs:
if (why := upload_name_problem(d[-1], is_dir=True)):
out.append(f"{'/'.join(d)}/: {why}")
return out
|