1+ #include < charconv>
2+ #include < print>
13#include < s3cpp/auth.h>
24#include < s3cpp/xml.hpp>
5+ #include < stdexcept>
6+ #include < unordered_set>
37
48class S3Client {
59public:
6- // TODO(cristian): We should accept and define the endpoint url here
10+ // TODO(cristian): We should accept and define the endpoint url here
711 S3Client (const std::string& access, const std::string& secret)
812 : Client(HttpClient())
913 , Signer(AWSSigV4Signer(access, secret))
@@ -13,24 +17,91 @@ class S3Client {
1317 , Signer(AWSSigV4Signer(access, secret, region))
1418 , Parser(XMLParser()) { }
1519
16- void list_objects (const std::string& bucket) {
17- return list_objects (bucket, " /" );
18- }
19-
20- void list_objects (const std::string& bucket, const std::string& prefix) {
21- // TODO(cristian): Decide what to do with the Host header
22-
23- HttpRequest req = Client.get (std::format (" http://127.0.0.1:9000/{}?prefix={}" , bucket, prefix))
24- .header (" Host" , " 127.0.0.1" );
20+ ListBucketResult list_objects (const std::string& bucket) { return list_objects (bucket, " /" , 1000 ); }
21+ ListBucketResult list_objects (const std::string& bucket, const std::string& prefix) { return list_objects (bucket, prefix, 1000 ); }
22+ ListBucketResult list_objects (const std::string& bucket, const std::string& prefix, int maxKeys) {
23+ HttpRequest req = Client.get (std::format (" http://127.0.0.1:9000/{}?prefix={}&max-keys={}" , bucket, prefix, maxKeys)).header (" Host" , " 127.0.0.1" );
2524 Signer.sign (req);
25+ HttpResponse res = req.execute ();
26+ return deserializeListBucketResult (Parser.parse (res.body ()));
27+ }
28+
29+ ListBucketResult deserializeListBucketResult (const std::vector<XMLNode>& nodes) {
30+ // TODO(cristian): Detect and parse errors
31+ ListBucketResult response;
32+ response.Contents .push_back (Contents{});
33+ response.CommonPrefixes .push_back (CommonPrefix{});
34+ int contentsIdx = 0 ;
35+ int commonPrefixesIdx = 0 ;
36+
37+ // To keep track when we need to append an element
38+ std::unordered_set<std::string> contentsKeySet;
39+ std::unordered_set<std::string> commonPrefixKeySet;
2640
27- HttpResponse res = req.execute ();
41+ for (const auto & node : nodes) {
42+ /* Sigh... no reflection */
43+
44+ // Check if we've seen this tag before in the current object
45+ if (contentsKeySet.contains (node.tag )) {
46+ response.Contents .push_back (Contents{});
47+ contentsKeySet.clear ();
48+ contentsIdx++;
49+ } else if (commonPrefixKeySet.contains (node.tag )) {
50+ response.CommonPrefixes .push_back (CommonPrefix{});
51+ commonPrefixKeySet.clear ();
52+ commonPrefixesIdx++;
53+ }
2854
29- auto xml_response = Parser.parse (res.body ());
30- for (const auto & xml_node : xml_response) {
31- std::println (" {}: {}" , xml_node.tag , xml_node.value );
32- }
55+ if (node.tag == " ListBucketResult.IsTruncated" ) {
56+ response.IsTruncated = Parser.parseBool (std::move (node.value ));
57+ } else if (node.tag == " ListBucketResult.Marker" ) {
58+ response.Marker = std::move (node.value );
59+ } else if (node.tag == " ListBucketResult.NextMarker" ) {
60+ response.NextMarker = std::move (node.value );
61+ } else if (node.tag == " ListBucketResult.Name" ) {
62+ response.Name = std::move (node.value );
63+ } else if (node.tag == " ListBucketResult.Prefix" ) {
64+ response.Prefix = std::move (node.value );
65+ } else if (node.tag == " ListBucketResult.Delimiter" ) {
66+ response.Delimiter = std::move (node.value );
67+ } else if (node.tag == " ListBucketResult.MaxKeys" ) {
68+ response.MaxKeys = Parser.parseNumber <int >(std::move (node.value ));
69+ } else if (node.tag == " ListBucketResult.EncodingType" ) {
70+ response.EncodingType = std::move (node.value );
71+ } else if (node.tag == " ListBucketResult.Contents.ChecksumAlgorithm" ) {
72+ response.Contents [contentsIdx].ChecksumAlgorithm = std::move (node.value );
73+ } else if (node.tag == " ListBucketResult.Contents.ChecksumType" ) {
74+ response.Contents [contentsIdx].ChecksumType = std::move (node.value );
75+ } else if (node.tag == " ListBucketResult.Contents.ETag" ) {
76+ response.Contents [contentsIdx].ETag = std::move (node.value );
77+ } else if (node.tag == " ListBucketResult.Contents.Key" ) {
78+ response.Contents [contentsIdx].Key = std::move (node.value );
79+ } else if (node.tag == " ListBucketResult.Contents.LastModified" ) {
80+ response.Contents [contentsIdx].LastModified = std::move (node.value );
81+ } else if (node.tag == " ListBucketResult.Contents.Owner.DisplayName" ) {
82+ response.Contents [contentsIdx].Owner .DisplayName = std::move (node.value );
83+ } else if (node.tag == " ListBucketResult.Contents.Owner.ID" ) {
84+ response.Contents [contentsIdx].Owner .ID = std::move (node.value );
85+ } else if (node.tag == " ListBucketResult.Contents.RestoreStatus.IsRestoreInProgress" ) {
86+ response.Contents [contentsIdx].RestoreStatus .IsRestoreInProgress = Parser.parseBool (node.value );
87+ } else if (node.tag == " ListBucketResult.Contents.RestoreStatus.RestoreExpiryDate" ) {
88+ response.Contents [contentsIdx].RestoreStatus .RestoreExpiryDate = std::move (node.value );
89+ } else if (node.tag == " ListBucketResult.Contents.Size" ) {
90+ response.Contents [contentsIdx].Size = Parser.parseNumber <long >(node.value );
91+ } else if (node.tag == " ListBucketResult.Contents.StorageClass" ) {
92+ response.Contents [contentsIdx].StorageClass = std::move (node.value );
93+ } else {
94+ throw std::runtime_error (std::format (" No case for ListBucketResult response found for: {}" , node.tag ));
95+ }
3396
97+ // Add already seen fields
98+ if (node.tag .contains (" ListBucketResult.Contents" )) {
99+ contentsKeySet.insert (node.tag );
100+ } else if (node.tag .contains (" ListBucketResult.CommonPrefix" )) {
101+ commonPrefixKeySet.insert (node.tag );
102+ }
103+ }
104+ return response;
34105 }
35106
36107private:
0 commit comments