-
Notifications
You must be signed in to change notification settings - Fork 8
/
Data.Cloud.MinIO.pas
218 lines (192 loc) · 8.06 KB
/
Data.Cloud.MinIO.pas
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
(**************************************************************************)
(* *)
(* Module: Unit 'Data.Cloud.MinIO' Copyright (c) 2023 *)
(* *)
(* Lucas Moura Belo - lmbelo *)
(* [email protected] *)
(* Brazil *)
(* *)
(* Project page: https://github.com/lmbelo/MinIO4Delphi *)
(**************************************************************************)
(* Functionality: MinIO Integration. *)
(* *)
(* *)
(**************************************************************************)
(* This source code is distributed with no WARRANTY, for no reason or use.*)
(* Everyone is allowed to use and change this code free for his own tasks *)
(* and projects, as long as this header and its copyright text is intact. *)
(* For changed versions of this code, which are public distributed the *)
(* following additional conditions have to be fullfilled: *)
(* 1) The header has to contain a comment on the change and the author of *)
(* it. *)
(* 2) A copy of the changed source has to be sent to the above E-Mail *)
(* address or my then valid address, if this is possible to the *)
(* author. *)
(* The second condition has the target to maintain an up to date central *)
(* version of the component. If this condition is not acceptable for *)
(* confidential or legal reasons, everyone is free to derive a component *)
(* or to generate a diff file to my or other original sources. *)
(**************************************************************************)
unit Data.Cloud.MinIO;
interface
uses
System.IOUtils,
System.SysUtils,
System.Classes,
System.Generics.Collections,
Data.Cloud.CloudAPI,
Data.Cloud.AmazonAPI;
type
TMinIOConnectionInfo = class(TAmazonConnectionInfo)
private
function GetServiceURL(const Host: string): string;
function StorageURL(const BucketName: string = ''): string;
end;
TMinIOStorageService = class(TAmazonStorageService)
private
const DEFAULT_DATA_CHUNK_SIZE = 10485760; //5242880 - 5MB / 10485760 - 10MB
private
procedure ValidateFile(const AFileName: string); inline;
function FormatResponseInfo(const AResponseInfo: TCloudResponseInfo): string;
procedure TestMultipartUpload(const ASuccess: boolean; const AResponseInfo: TCloudResponseInfo);
protected
function PrepareRequest(const HTTPVerb: string; Headers, QueryParameters: TStringList;
const QueryPrefix: string; var URL: string; var Content: TStream): TCloudHTTP; overload; override;
public
procedure UploadSmallFile(const ABucketName, AFileName: string; ARemoteFileName: string = '');
procedure UploadLargeFile(const ABucketName, AFileName: string; ARemoteFileName: string = '';
ADataChunkSize: integer = 0);
procedure UploadFile(const ABucketName, AFileName: string; ARemoteFileName: string = '');
end;
EMinIO = class(Exception);
EInvalidFile = class(EMinIO);
ERegularUpload = class(EMinIO);
EMultipartUpload = class(EMinIO);
implementation
{ TMinIOConnectionInfo }
function TMinIOConnectionInfo.GetServiceURL(const Host: string): string;
begin
//View all available endpoints here: http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3912
Result := Format('%s://%s', [Protocol, Host]); //sqs.us-east-1.amazonaws.com
end;
function TMinIOConnectionInfo.StorageURL(const BucketName: string): string;
begin
if BucketName = EmptyStr then
Result := GetServiceURL(StorageEndpoint)
else
Result := GetServiceURL(Format('%s/%s', [StorageEndpoint, BucketName]));
end;
function TMinIOStorageService.PrepareRequest(const HTTPVerb: string; Headers,
QueryParameters: TStringList; const QueryPrefix: string; var URL: string;
var Content: TStream): TCloudHTTP;
begin
URL := TMinIOConnectionInfo(GetConnectionInfo).StorageURL(QueryPrefix.Replace('/', '', []));
if URL.Contains('?') and (not URL.EndsWith('?') and not URL.Contains('=')) then //?uploads
URL := URL + '=';
if Assigned(QueryParameters) then
URL := BuildQueryParameterString(url, QueryParameters, False, True);
Headers.Values['host'] := GetConnectionInfo.StorageEndpoint;
Result := inherited;
end;
procedure TMinIOStorageService.ValidateFile(const AFileName: string);
begin
if not TFile.Exists(AFileName) then
raise EInvalidFile.CreateFmt('Invalid file %s.', [AFileName]);
end;
function TMinIOStorageService.FormatResponseInfo(const AResponseInfo: TCloudResponseInfo): string;
begin
Result := AResponseInfo.StatusCode.ToString + ' - ' + AResponseInfo.StatusMessage;
end;
procedure TMinIOStorageService.TestMultipartUpload(const ASuccess: boolean;
const AResponseInfo: TCloudResponseInfo);
begin
if not ASuccess then
raise EMultipartUpload.CreateFmt('An error occurred in the upload. %s', [
FormatResponseInfo(AResponseInfo)]);
end;
procedure TMinIOStorageService.UploadSmallFile(const ABucketName, AFileName: string;
ARemoteFileName: string);
var
LResponseInfo: TCloudResponseInfo;
begin
ValidateFile(AFileName);
if ARemoteFileName.IsEmpty then
ARemoteFileName := TPath.GetFileName(AFileName);
LResponseInfo := TCloudResponseInfo.Create;
try
if not Self.UploadObject(ABucketName, ARemoteFileName, TFile.ReadAllBytes(AFileName), false,
nil, nil, amzbaPrivate, LResponseInfo) then
raise ERegularUpload.CreateFmt('An error occurred in the upload. %s', [
FormatResponseInfo(LResponseInfo)]);
finally
LResponseInfo.Free;
end;
end;
procedure TMinIOStorageService.UploadLargeFile(const ABucketName, AFileName: string;
ARemoteFileName: string; ADataChunkSize: integer);
var
LResponseInfo: TCloudResponseInfo;
LUploadId: string;
LChunk: TArray<byte>;
LPart: TAmazonMultipartPart;
LParts: TList<TAmazonMultipartPart>;
LBinaryReader: TBinaryReader;
begin
ValidateFile(AFileName);
if ARemoteFileName.IsEmpty then
ARemoteFileName := TPath.GetFileName(AFileName);
if (ADataChunkSize = 0) then
ADataChunkSize := DEFAULT_DATA_CHUNK_SIZE;
LParts := TList<TAmazonMultipartPart>.Create;
try
LResponseInfo := TCloudResponseInfo.Create;
try
LUploadId := InitiateMultipartUpload(ABucketName, ARemoteFileName, nil, nil, amzbaPrivate, LResponseInfo);
TestMultipartUpload(not LUploadId.IsEmpty(), LResponseInfo);
try
LBinaryReader := TBinaryReader.Create(AFileName);
try
LChunk := LBinaryReader.ReadBytes(ADataChunkSize);
while Assigned(LChunk) do
begin
TestMultipartUpload(
UploadPart(
ABucketName,
ARemoteFileName,
LUploadId,
Succ(LParts.Count),
LChunk,
LPart),
LResponseInfo);
LParts.Add(LPart);
LChunk := LBinaryReader.ReadBytes(ADataChunkSize);
end;
finally
LBinaryReader.Free;
end;
finally
TestMultipartUpload(
CompleteMultipartUpload(ABucketName, ARemoteFileName, LUploadId, LParts, LResponseInfo),
LResponseInfo);
end;
finally
LResponseInfo.Free;
end;
finally
LParts.Free;
end;
end;
procedure TMinIOStorageService.UploadFile(const ABucketName, AFileName: string;
ARemoteFileName: string);
begin
with TFile.OpenRead(AFileName) do
try
if (Size > DEFAULT_DATA_CHUNK_SIZE) then
UploadLargeFile(ABucketName, AFileName, ARemoteFileName)
else
UploadSmallFile(ABucketName, AFileName, ARemoteFileName);
finally
Free;
end;
end;
end.