|
20 | 20 |
|
21 | 21 | from box_sdk_gen.internal.utils import Iterator |
22 | 22 |
|
| 23 | +from box_sdk_gen.schemas.upload_part_plan_hit import UploadPartPlanHit |
| 24 | + |
23 | 25 | from box_sdk_gen.schemas.upload_session import UploadSession |
24 | 26 |
|
25 | 27 | from box_sdk_gen.schemas.client_error import ClientError |
@@ -81,12 +83,15 @@ def __init__( |
81 | 83 | file_size: int, |
82 | 84 | upload_part_url: str, |
83 | 85 | file_hash: Hash, |
| 86 | + *, |
| 87 | + plan_url: str = '' |
84 | 88 | ): |
85 | 89 | self.last_index = last_index |
86 | 90 | self.parts = parts |
87 | 91 | self.file_size = file_size |
88 | 92 | self.upload_part_url = upload_part_url |
89 | 93 | self.file_hash = file_hash |
| 94 | + self.plan_url = plan_url |
90 | 95 |
|
91 | 96 |
|
92 | 97 | class ChunkedUploadsManager: |
@@ -932,6 +937,7 @@ def _reducer(self, acc: _PartAccumulator, chunk: ByteStream) -> _PartAccumulator |
932 | 937 | file_size=acc.file_size, |
933 | 938 | upload_part_url=acc.upload_part_url, |
934 | 939 | file_hash=acc.file_hash, |
| 940 | + plan_url=acc.plan_url, |
935 | 941 | ) |
936 | 942 |
|
937 | 943 | def upload_big_file( |
@@ -982,3 +988,129 @@ def upload_big_file( |
982 | 988 | self.create_file_upload_session_commit_by_url(commit_url, parts, digest) |
983 | 989 | ) |
984 | 990 | return committed_session.entries[0] |
| 991 | + |
| 992 | + def _get_cached_upload_part( |
| 993 | + self, plan_url: str, offset: int, size: int, sha_512: str |
| 994 | + ) -> Optional[UploadPart]: |
| 995 | + plan: UploadSessionPlanResponse = self.create_file_upload_session_plan_by_url( |
| 996 | + plan_url, [UploadPartPlan(offset=offset, size=size, sha_512=sha_512)] |
| 997 | + ) |
| 998 | + if len(plan.hits) > 0: |
| 999 | + hit: UploadPartPlanHit = plan.hits[0] |
| 1000 | + return UploadPart(part_id=hit.part_id, offset=hit.offset, size=hit.size) |
| 1001 | + return None |
| 1002 | + |
| 1003 | + def _reducer_for_file_version( |
| 1004 | + self, acc: _PartAccumulator, chunk: ByteStream |
| 1005 | + ) -> _PartAccumulator: |
| 1006 | + last_index: int = acc.last_index |
| 1007 | + parts: List[UploadPart] = acc.parts |
| 1008 | + chunk_buffer: Buffer = read_byte_stream(chunk) |
| 1009 | + hash: Hash = Hash(algorithm=HashName.SHA1) |
| 1010 | + hash.update_hash(chunk_buffer) |
| 1011 | + sha_1: str = hash.digest_hash('base64') |
| 1012 | + digest: str = ''.join(['sha=', sha_1]) |
| 1013 | + chunk_size: int = buffer_length(chunk_buffer) |
| 1014 | + bytes_start: int = last_index + 1 |
| 1015 | + bytes_end: int = last_index + chunk_size |
| 1016 | + content_range: str = ''.join( |
| 1017 | + [ |
| 1018 | + 'bytes ', |
| 1019 | + to_string(bytes_start), |
| 1020 | + '-', |
| 1021 | + to_string(bytes_end), |
| 1022 | + '/', |
| 1023 | + to_string(acc.file_size), |
| 1024 | + ] |
| 1025 | + ) |
| 1026 | + sha_512_hash: Hash = Hash(algorithm=HashName.SHA512) |
| 1027 | + sha_512_hash.update_hash(chunk_buffer) |
| 1028 | + sha_512: str = sha_512_hash.digest_hash('hex') |
| 1029 | + cached_part: Optional[UploadPart] = self._get_cached_upload_part( |
| 1030 | + acc.plan_url, bytes_start, chunk_size, sha_512 |
| 1031 | + ) |
| 1032 | + if not cached_part == None: |
| 1033 | + acc.file_hash.update_hash(chunk_buffer) |
| 1034 | + return _PartAccumulator( |
| 1035 | + last_index=bytes_end, |
| 1036 | + parts=parts + [cached_part], |
| 1037 | + file_size=acc.file_size, |
| 1038 | + upload_part_url=acc.upload_part_url, |
| 1039 | + file_hash=acc.file_hash, |
| 1040 | + plan_url=acc.plan_url, |
| 1041 | + ) |
| 1042 | + uploaded_part: UploadedPart = self.upload_file_part_by_url( |
| 1043 | + acc.upload_part_url, |
| 1044 | + generate_byte_stream_from_buffer(chunk_buffer), |
| 1045 | + digest, |
| 1046 | + content_range, |
| 1047 | + ) |
| 1048 | + part: UploadPart = uploaded_part.part |
| 1049 | + part_sha_1: str = hex_to_base_64(part.sha_1) |
| 1050 | + assert part_sha_1 == sha_1 |
| 1051 | + assert part.size == chunk_size |
| 1052 | + assert part.offset == bytes_start |
| 1053 | + acc.file_hash.update_hash(chunk_buffer) |
| 1054 | + return _PartAccumulator( |
| 1055 | + last_index=bytes_end, |
| 1056 | + parts=parts + [part], |
| 1057 | + file_size=acc.file_size, |
| 1058 | + upload_part_url=acc.upload_part_url, |
| 1059 | + file_hash=acc.file_hash, |
| 1060 | + plan_url=acc.plan_url, |
| 1061 | + ) |
| 1062 | + |
| 1063 | + def upload_big_file_version( |
| 1064 | + self, |
| 1065 | + file_id: str, |
| 1066 | + file: ByteStream, |
| 1067 | + file_size: int, |
| 1068 | + *, |
| 1069 | + file_name: Optional[str] = None |
| 1070 | + ) -> Optional[FileFull]: |
| 1071 | + """ |
| 1072 | + Starts the process of chunk uploading a new version of a big file. Should return a File object representing the uploaded file version. Returns nothing when commit responds with 202 because the file did not change. |
| 1073 | + :param file_id: The ID of the file to upload a new version of. |
| 1074 | + :type file_id: str |
| 1075 | + :param file: The stream of the file to upload. |
| 1076 | + :type file: ByteStream |
| 1077 | + :param file_size: The total size of the file for the chunked upload in bytes. |
| 1078 | + :type file_size: int |
| 1079 | + :param file_name: The optional new name of the file., defaults to None |
| 1080 | + :type file_name: Optional[str], optional |
| 1081 | + """ |
| 1082 | + upload_session: UploadSession = ( |
| 1083 | + self.create_file_upload_session_for_existing_file( |
| 1084 | + file_id, file_size, file_name=file_name |
| 1085 | + ) |
| 1086 | + ) |
| 1087 | + upload_part_url: str = upload_session.session_endpoints.upload_part |
| 1088 | + commit_url: str = upload_session.session_endpoints.commit |
| 1089 | + plan_url: str = upload_session.session_endpoints.plan |
| 1090 | + part_size: int = upload_session.part_size |
| 1091 | + total_parts: int = upload_session.total_parts |
| 1092 | + assert part_size * total_parts >= file_size |
| 1093 | + assert upload_session.num_parts_processed == 0 |
| 1094 | + file_hash: Hash = Hash(algorithm=HashName.SHA1) |
| 1095 | + chunks_iterator: Iterator = iterate_chunks(file, part_size, file_size) |
| 1096 | + results: _PartAccumulator = reduce_iterator( |
| 1097 | + chunks_iterator, |
| 1098 | + self._reducer_for_file_version, |
| 1099 | + _PartAccumulator( |
| 1100 | + last_index=-1, |
| 1101 | + parts=[], |
| 1102 | + file_size=file_size, |
| 1103 | + upload_part_url=upload_part_url, |
| 1104 | + file_hash=file_hash, |
| 1105 | + plan_url=plan_url, |
| 1106 | + ), |
| 1107 | + ) |
| 1108 | + parts: List[UploadPart] = results.parts |
| 1109 | + sha_1: str = file_hash.digest_hash('base64') |
| 1110 | + digest: str = ''.join(['sha=', sha_1]) |
| 1111 | + committed_session: Optional[Files] = ( |
| 1112 | + self.create_file_upload_session_commit_by_url(commit_url, parts, digest) |
| 1113 | + ) |
| 1114 | + if committed_session == None: |
| 1115 | + return None |
| 1116 | + return committed_session.entries[0] |
0 commit comments