diff --git a/sdk/storage/inc/common/constants.hpp b/sdk/storage/inc/common/constants.hpp index 704d02f94..ba760ab69 100644 --- a/sdk/storage/inc/common/constants.hpp +++ b/sdk/storage/inc/common/constants.hpp @@ -16,4 +16,5 @@ namespace Azure { namespace Storage { namespace Details { constexpr static const char* c_HttpHeaderXMsVersion = "x-ms-version"; constexpr static const char* c_HttpHeaderRequestId = "x-ms-request-id"; constexpr static const char* c_HttpHeaderClientRequestId = "x-ms-client-request-id"; + constexpr static const char* c_HttpHeaderContentType = "content-type"; }}} // namespace Azure::Storage::Details diff --git a/sdk/storage/inc/common/storage_error.hpp b/sdk/storage/inc/common/storage_error.hpp index c9ff50a87..b21fc788f 100644 --- a/sdk/storage/inc/common/storage_error.hpp +++ b/sdk/storage/inc/common/storage_error.hpp @@ -24,6 +24,6 @@ namespace Azure { namespace Storage { std::unique_ptr RawResponse; static StorageError CreateFromResponse( - /* const */ std::unique_ptr response); + std::unique_ptr response); }; }} // namespace Azure::Storage diff --git a/sdk/storage/inc/datalake/directory_client.hpp b/sdk/storage/inc/datalake/directory_client.hpp index 6296c2f46..2c69920a4 100644 --- a/sdk/storage/inc/datalake/directory_client.hpp +++ b/sdk/storage/inc/datalake/directory_client.hpp @@ -65,6 +65,13 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { const std::string& directoryUri, const DirectoryClientOptions& options = DirectoryClientOptions()); + /** + * @brief Create a FileClient from current DirectoryClient + * @param path Path of the file under the directory. + * @return FileClient + */ + FileClient GetFileClient(const std::string& path) const; + /** * @brief Gets the directory's primary uri endpoint. This is the endpoint used for blob * storage available features in DataLake. diff --git a/sdk/storage/inc/datalake/file_client.hpp b/sdk/storage/inc/datalake/file_client.hpp index 9c41fc2ef..b97a93541 100644 --- a/sdk/storage/inc/datalake/file_client.hpp +++ b/sdk/storage/inc/datalake/file_client.hpp @@ -231,5 +231,6 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { { } friend class FileSystemClient; + friend class DirectoryClient; }; }}}} // namespace Azure::Storage::Files::DataLake diff --git a/sdk/storage/sample/datalake_getting_started.cpp b/sdk/storage/sample/datalake_getting_started.cpp index 9402c82e1..2954d5ab9 100644 --- a/sdk/storage/sample/datalake_getting_started.cpp +++ b/sdk/storage/sample/datalake_getting_started.cpp @@ -3,11 +3,113 @@ #include "datalake/datalake.hpp" #include "samples_common.hpp" +#include SAMPLE(DataLakeGettingStarted, DataLakeGettingStarted) void DataLakeGettingStarted() { + // Please note that you can always reference test cases for advanced usages. + using namespace Azure::Storage::Files::DataLake; - auto client = ServiceClient::CreateFromConnectionString(GetConnectionString()); - auto response = client.ListFileSystems(); + + std::string fileSystemName = "sample-file-system"; + std::string directoryName = "sample-directory"; + std::string fileName = "sample-file"; + + // Initializing a ServiceClient that can then initialize the FileSystemClient or list file + // systems. + auto serviceClient = ServiceClient::CreateFromConnectionString(GetConnectionString()); + // Initializing a FileSystemClient that can then initialize the PathClient, FileClient, + // DirectoryClient. + auto fileSystemClient + = FileSystemClient::CreateFromConnectionString(GetConnectionString(), fileSystemName); + + try + { + // Create file systems and ignore the already exist error. + try + { + fileSystemClient.Create(); + } + catch (Azure::Storage::StorageError& e) + { + if (e.ErrorCode != "ContainerAlreadyExists") + { + throw; + } + else + { + std::cout << "ErrorCode: " + e.ErrorCode << std::endl; + std::cout << "ReasonPhrase: " + e.ReasonPhrase << std::endl; + } + } + + // Create a directory. + auto directoryClient = fileSystemClient.GetDirectoryClient(directoryName); + directoryClient.Create(); + + // Creates a file under the directory. + auto fileClient = directoryClient.GetFileClient(fileName); + fileClient.Create(); + + // Append/flush/read data from the client. + // Append data + // Initialize the string that contains the first piece of data to be appended to the file. + std::string str1 = "Hello "; + // Initialize the buffer that represents what contains your data to be appended, please ignore + // how it is constructed here, since the memory copy is not efficient. + std::string str2 = "World!"; + std::vector buffer(str1.begin(), str1.end()); + + // One way of passing in the buffer, note that the buffer is not copied. + auto bufferStream = Azure::Core::Http::MemoryBodyStream(buffer); + + fileClient.AppendData(&bufferStream, 0 /* Offset of the position to be appended.*/); + + // Another way of passing in the buffer, note that buffer is also not copied. + bufferStream = Azure::Core::Http::MemoryBodyStream( + reinterpret_cast(str2.data()), str2.size()); + + fileClient.AppendData(&bufferStream, str1.size()); + + // Flush + fileClient.FlushData(str1.size() + str2.size()); + + // Read + auto result = fileClient.Read(); + Azure::Core::Context context; + std::vector downloaded + = Azure::Core::Http::BodyStream::ReadToEnd(context, *(result->Body)); + // downloaded contains your downloaded data. + std::cout << "Downloaded data was:\n" + std::string(downloaded.begin(), downloaded.end()) + << std::endl; + + // List all file systems. + std::string continuation; + std::vector fileSystems; + do + { + auto response = serviceClient.ListFileSystems(); + if (response->Continuation.HasValue()) + { + continuation = response->Continuation.GetValue(); + } + fileSystems.insert( + fileSystems.end(), response->Filesystems.begin(), response->Filesystems.end()); + } while (!continuation.empty()); + + // Delete file system. + fileSystemClient.Delete(); + + std::cout << "Successfully finished sample." << std::endl; + } + catch (const Azure::Storage::StorageError& e) + { + // Deal with the information when storage error is met. + std::cout << "Error encountered when sending the request." << std::endl; + std::cout << "ErrorCode: " + e.ErrorCode << std::endl; + std::cout << "Message: " + e.Message << std::endl; + std::cout << "ReasonPhrase: " + e.ReasonPhrase << std::endl; + std::cout << "RequestId: " + e.RequestId << std::endl; + } } diff --git a/sdk/storage/src/common/storage_error.cpp b/sdk/storage/src/common/storage_error.cpp index c870ad436..daa167e6a 100644 --- a/sdk/storage/src/common/storage_error.cpp +++ b/sdk/storage/src/common/storage_error.cpp @@ -3,6 +3,7 @@ #include "common/storage_error.hpp" +#include "common/constants.hpp" #include "common/xml_wrapper.hpp" #include "json.hpp" @@ -38,9 +39,11 @@ namespace Azure { namespace Storage { std::string errorCode; std::string message; - if (response->GetHeaders().find("Content-Type") != response->GetHeaders().end()) + if (response->GetHeaders().find(Details::c_HttpHeaderContentType) + != response->GetHeaders().end()) { - if (response->GetHeaders().at("Content-Type").find("xml") != std::string::npos) + if (response->GetHeaders().at(Details::c_HttpHeaderContentType).find("xml") + != std::string::npos) { auto xmlReader = XmlReader(reinterpret_cast(bodyBuffer.data()), bodyBuffer.size()); @@ -101,12 +104,16 @@ namespace Azure { namespace Storage { } } } - else if (response->GetHeaders().at("Content-Type").find("html") != std::string::npos) + else if ( + response->GetHeaders().at(Details::c_HttpHeaderContentType).find("html") + != std::string::npos) { // TODO: add a refined message parsed from result. message = std::string(bodyBuffer.begin(), bodyBuffer.end()); } - else if (response->GetHeaders().at("Content-Type").find("json") != std::string::npos) + else if ( + response->GetHeaders().at(Details::c_HttpHeaderContentType).find("json") + != std::string::npos) { auto jsonParser = nlohmann::json::parse(bodyBuffer); errorCode = jsonParser["error"]["code"].get(); diff --git a/sdk/storage/src/datalake/directory_client.cpp b/sdk/storage/src/datalake/directory_client.cpp index de986d0a9..688c5b283 100644 --- a/sdk/storage/src/datalake/directory_client.cpp +++ b/sdk/storage/src/datalake/directory_client.cpp @@ -11,6 +11,7 @@ #include "common/storage_version.hpp" #include "credentials/policy/policies.hpp" #include "datalake/datalake_utilities.hpp" +#include "datalake/file_client.hpp" #include "http/curl/curl.hpp" #include @@ -118,6 +119,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { m_pipeline = std::make_shared(policies); } + FileClient DirectoryClient::GetFileClient(const std::string& path) const + { + auto builder = m_dfsUri; + builder.AppendPath(path, true); + auto blobClient = m_blobClient; + blobClient.m_blobUrl.AppendPath(path, true); + return FileClient(std::move(builder), std::move(blobClient), m_pipeline); + } + Azure::Core::Response DirectoryClient::Rename( const std::string& destinationPath, const DirectoryRenameOptions& options) const diff --git a/sdk/storage/test/datalake/directory_client_test.cpp b/sdk/storage/test/datalake/directory_client_test.cpp index d6d90fa93..6af4122b6 100644 --- a/sdk/storage/test/datalake/directory_client_test.cpp +++ b/sdk/storage/test/datalake/directory_client_test.cpp @@ -104,25 +104,27 @@ namespace Azure { namespace Storage { namespace Test { { { // Normal create/rename/delete. - std::vector directoryClient; + std::vector directoryClients; for (int32_t i = 0; i < 5; ++i) { auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); EXPECT_NO_THROW(client.Create()); - directoryClient.emplace_back(std::move(client)); + directoryClients.emplace_back(std::move(client)); } - auto directoryClientClone = directoryClient; - for (auto& client : directoryClient) + std::vector newPaths; + for (auto& client : directoryClients) { - EXPECT_NO_THROW(client.Rename(LowercaseRandomString())); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath)); + newPaths.push_back(newPath); } - for (const auto& client : directoryClientClone) + for (const auto& client : directoryClients) { EXPECT_THROW(client.Delete(false), StorageError); } - for (const auto& client : directoryClient) + for (const auto& newPath : newPaths) { - EXPECT_NO_THROW(client.Delete(false)); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -142,8 +144,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageError); Files::DataLake::DirectoryRenameOptions options2; options2.SourceAccessConditions.IfUnmodifiedSince = response->LastModified; - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options2)); - EXPECT_NO_THROW(client.Delete(false)); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options2)); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -163,8 +166,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageError); Files::DataLake::DirectoryRenameOptions options2; options2.SourceAccessConditions.IfMatch = response->ETag; - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options2)); - EXPECT_NO_THROW(client.Delete(false)); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options2)); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -177,7 +181,7 @@ namespace Azure { namespace Storage { namespace Test { directoryClient.emplace_back(std::move(client)); } { - // Rename to a non-existing file system will fail but will not change URI. + // Rename to a non-existing file system will fail and source is not changed. Files::DataLake::DirectoryRenameOptions options; options.DestinationFileSystem = LowercaseRandomString(); for (auto& client : directoryClient) @@ -187,7 +191,7 @@ namespace Azure { namespace Storage { namespace Test { } } { - // Rename to a non-existing file system will succeed and changes URI. + // Rename to an existing file system will succeed and changes URI. auto newfileSystemName = LowercaseRandomString(10); auto newfileSystemClient = std::make_shared( Files::DataLake::FileSystemClient::CreateFromConnectionString( @@ -197,9 +201,9 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = newfileSystemName; for (auto& client : directoryClient) { - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options)); - EXPECT_NO_THROW(client.GetProperties()); - EXPECT_NO_THROW(client.Delete(false)); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options)); + EXPECT_NO_THROW(newfileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } } diff --git a/sdk/storage/test/datalake/file_client_test.cpp b/sdk/storage/test/datalake/file_client_test.cpp index b480e08bb..c8bedd332 100644 --- a/sdk/storage/test/datalake/file_client_test.cpp +++ b/sdk/storage/test/datalake/file_client_test.cpp @@ -87,25 +87,27 @@ namespace Azure { namespace Storage { namespace Test { { { // Normal create/rename/delete. - std::vector fileClient; + std::vector fileClients; for (int32_t i = 0; i < 5; ++i) { auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); EXPECT_NO_THROW(client.Create()); - fileClient.emplace_back(std::move(client)); + fileClients.emplace_back(std::move(client)); } - auto fileClientClone = fileClient; - for (auto& client : fileClient) + std::vector newPaths; + for (auto& client : fileClients) { - EXPECT_NO_THROW(client.Rename(LowercaseRandomString())); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath)); + newPaths.push_back(newPath); } - for (const auto& client : fileClientClone) + for (const auto& client : fileClients) { EXPECT_THROW(client.Delete(), StorageError); } - for (const auto& client : fileClient) + for (const auto& newPath : newPaths) { - EXPECT_NO_THROW(client.Delete()); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -125,8 +127,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageError); Files::DataLake::FileRenameOptions options2; options2.SourceAccessConditions.IfUnmodifiedSince = response->LastModified; - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options2)); - EXPECT_NO_THROW(client.Delete()); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options2)); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -146,8 +149,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageError); Files::DataLake::FileRenameOptions options2; options2.SourceAccessConditions.IfMatch = response->ETag; - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options2)); - EXPECT_NO_THROW(client.Delete()); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options2)); + EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } { @@ -170,7 +174,7 @@ namespace Azure { namespace Storage { namespace Test { } } { - // Rename to a non-existing file system will succeed and changes URI. + // Rename to an existing file system will succeed and changes URI. auto newfileSystemName = LowercaseRandomString(10); auto newfileSystemClient = std::make_shared( Files::DataLake::FileSystemClient::CreateFromConnectionString( @@ -180,9 +184,9 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = newfileSystemName; for (auto& client : fileClient) { - EXPECT_NO_THROW(client.Rename(LowercaseRandomString(), options)); - EXPECT_NO_THROW(client.GetProperties()); - EXPECT_NO_THROW(client.Delete()); + auto newPath = LowercaseRandomString(); + EXPECT_NO_THROW(client.Rename(newPath, options)); + EXPECT_NO_THROW(newfileSystemClient->GetDirectoryClient(newPath).Delete(false)); } } }