Skip to content

Commit eded582

Browse files
Return etag on createfile and upload calls
1 parent bd4dae8 commit eded582

File tree

2 files changed

+43
-14
lines changed

2 files changed

+43
-14
lines changed

filecloudapi/datastructures.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class AclEntryType(Enum):
2020
user = "user"
2121

2222

23+
ETag = str
24+
25+
2326
@dataclass
2427
class FileListEntry:
2528
path: str
@@ -43,6 +46,7 @@ class FileListEntry:
4346
isshareable: bool
4447
issyncable: bool
4548
isdatasyncable: bool
49+
etag: ETag
4650

4751

4852
@dataclass

filecloudapi/fcserver.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
AclEntryType,
2121
AclPermissions,
2222
EntryType,
23+
ETag,
2324
FCShare,
2425
FCShareGroup,
2526
FCShareUser,
@@ -51,6 +52,8 @@ def str_to_bool(value):
5152

5253
log = logging.getLogger(__name__)
5354

55+
SEND_ETAG_HEADER = "X-FC-Send-ETag"
56+
5457

5558
class Progress:
5659
"""
@@ -129,11 +132,13 @@ def __init__(
129132
else:
130133
self.login()
131134

132-
def _api_call(self, method: str, params: Dict) -> ET.Element:
135+
def _api_call(
136+
self, method: str, params: Dict, headers: Optional[Dict] = None
137+
) -> ET.Element:
133138
"""
134139
Perform a FC API call (post)
135140
"""
136-
resp = self.session.post(self.url + method, data=params)
141+
resp = self.session.post(self.url + method, data=params, headers=headers)
137142
resp.raise_for_status()
138143
self.last_headers = resp.headers
139144
return ET.fromstring(resp.content)
@@ -183,6 +188,15 @@ def _raise_exception_from_command(self, resp: ET.Element):
183188
resp.findtext("./command/message", "")
184189
)
185190

191+
def _extract_etag(self, etag: str):
192+
"""
193+
Extract ETag value from header
194+
"""
195+
if etag.startswith('"') and etag.endswith('"'):
196+
return etag[1:-1]
197+
else:
198+
return etag
199+
186200
def login(self) -> None:
187201
"""
188202
Try to login to FC server with the credentials
@@ -305,6 +319,7 @@ def shared_opt(txt: Optional[str]) -> SharedType:
305319
entry.findtext("./isshareable", "1") == "1",
306320
entry.findtext("./issyncable", "1") == "1",
307321
entry.findtext("./isdatasyncable", "1") == "1",
322+
entry.findtext("./etag", ""),
308323
)
309324

310325
def getfilelist(
@@ -368,7 +383,7 @@ def fileinfo_no_retry(self, path: str) -> FileListEntry:
368383
"""
369384
Returns information about file/directory 'path'
370385
"""
371-
resp = self._api_call("/core/fileinfo", {"file": path})
386+
resp = self._api_call("/core/fileinfo", {"file": path, "includeextrafields": 1})
372387

373388
entry = resp.find("./entry")
374389

@@ -598,7 +613,7 @@ def downloadfolder(self, path: str, dstPath: Union[pathlib.Path, str]) -> None:
598613
else:
599614
self.downloadfile(path + "/" + file.name, dstFn)
600615

601-
def deletefile(self, path: str, adminproxyuserid: Optional[str] = None):
616+
def deletefile(self, path: str, adminproxyuserid: Optional[str] = None) -> None:
602617
"""
603618
Delete file at 'path'
604619
"""
@@ -619,11 +634,11 @@ def upload_bytes(
619634
nofileoverwrite: Optional[bool] = False,
620635
iflastmodified: Optional[datetime.datetime] = None,
621636
progress: Optional[Progress] = None,
622-
) -> None:
637+
) -> ETag:
623638
"""
624639
Upload bytes 'data' to server at 'serverpath'.
625640
"""
626-
self.upload(BufferedReader(BytesIO(data)), serverpath, datemodified, nofileoverwrite=nofileoverwrite, iflastmodified=iflastmodified, progress=progress) # type: ignore
641+
return self.upload(BufferedReader(BytesIO(data)), serverpath, datemodified, nofileoverwrite=nofileoverwrite, iflastmodified=iflastmodified, progress=progress) # type: ignore
627642

628643
def upload_str(
629644
self,
@@ -633,11 +648,11 @@ def upload_str(
633648
nofileoverwrite: Optional[bool] = False,
634649
iflastmodified: Optional[datetime.datetime] = None,
635650
progress: Optional[Progress] = None,
636-
) -> None:
651+
) -> ETag:
637652
"""
638653
Upload str 'data' UTF-8 encoded to server at 'serverpath'.
639654
"""
640-
self.upload_bytes(
655+
return self.upload_bytes(
641656
data.encode("utf-8"),
642657
serverpath,
643658
datemodified,
@@ -655,12 +670,12 @@ def upload_file(
655670
iflastmodified: Optional[datetime.datetime] = None,
656671
adminproxyuserid: Optional[str] = None,
657672
progress: Optional[Progress] = None,
658-
) -> None:
673+
) -> ETag:
659674
"""
660675
Upload file at 'localpath' to server at 'serverpath'.
661676
"""
662677
with open(localpath, "rb") as uploadf:
663-
self.upload(
678+
return self.upload(
664679
uploadf,
665680
serverpath,
666681
datemodified,
@@ -689,7 +704,7 @@ def upload(
689704
iflastmodified: Optional[datetime.datetime] = None,
690705
adminproxyuserid: Optional[str] = None,
691706
progress: Optional[Progress] = None,
692-
) -> None:
707+
) -> ETag:
693708
"""
694709
Upload seekable stream at uploadf to server at 'serverpath'
695710
"""
@@ -833,6 +848,7 @@ def close(self):
833848
resp = self.session.post(
834849
self.url + "/core/upload?" + params_str,
835850
files={"file_contents": (name, b"")},
851+
headers={SEND_ETAG_HEADER: "1"},
836852
)
837853

838854
resp.raise_for_status()
@@ -841,7 +857,7 @@ def close(self):
841857
log.warning(f"Upload error. Response: {resp.text}")
842858
raise ServerError("", resp.text)
843859

844-
return
860+
return self._extract_etag(resp.headers["ETag"])
845861

846862
rf = RequestField(name="file_contents", data=data_marker, filename=name)
847863
rf.make_multipart()
@@ -850,7 +866,7 @@ def close(self):
850866

851867
headers = {"Content-type": content_type}
852868

853-
while pos < data_size or (data_size == 0 and pos == 0):
869+
while True:
854870

855871
curr_slice_size = min(slice_size, data_size - pos)
856872
complete = 0 if pos + curr_slice_size < data_size else 1
@@ -881,6 +897,9 @@ def close(self):
881897
"%%2FSHARED%2F%21", "%2FSHARED%2F!"
882898
) # WEBUI DOES NOT ENCODE THE !
883899

900+
if complete == 1:
901+
headers[SEND_ETAG_HEADER] = "1"
902+
884903
resp = self.session.post(
885904
self.url + "/core/upload?" + params_str,
886905
data=FileSlice(uploadf, pos, curr_slice_size, envelope),
@@ -899,6 +918,10 @@ def close(self):
899918
if progress is not None:
900919
progress.update(pos, data_size, True)
901920

921+
if complete == 1:
922+
assert pos == data_size
923+
return self._extract_etag(resp.headers["ETag"])
924+
902925
def share(self, path: str, adminproxyuserid: str = "") -> FCShare:
903926
"""
904927
Share 'path'
@@ -1048,7 +1071,7 @@ def createfolder(
10481071
path: str,
10491072
subpath: Optional[str] = None,
10501073
adminproxyuserid: Optional[str] = None,
1051-
) -> None:
1074+
) -> ETag:
10521075
"""
10531076
Create folder at 'path'
10541077
"""
@@ -1066,9 +1089,11 @@ def createfolder(
10661089
resp = self._api_call(
10671090
"/core/createfolder",
10681091
payload,
1092+
headers={SEND_ETAG_HEADER: "1"},
10691093
)
10701094

10711095
self._raise_exception_from_command(resp)
1096+
return self._extract_etag(self.last_headers["ETag"])
10721097

10731098
def renamefile(self, path: str, name: str, newname) -> None:
10741099
"""

0 commit comments

Comments
 (0)