2020 AclEntryType ,
2121 AclPermissions ,
2222 EntryType ,
23+ ETag ,
2324 FCShare ,
2425 FCShareGroup ,
2526 FCShareUser ,
@@ -51,6 +52,8 @@ def str_to_bool(value):
5152
5253log = logging .getLogger (__name__ )
5354
55+ SEND_ETAG_HEADER = "X-FC-Send-ETag"
56+
5457
5558class 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