From ef1343185e4568072b74e226c07dc89ea045afdf Mon Sep 17 00:00:00 2001 From: Jack Del Vecchio Date: Wed, 1 Apr 2026 16:26:55 -0400 Subject: [PATCH 1/4] HPCC-36100 Implement directoryFiles for Azure Blob and Files classes --- common/remote/hooks/azure/azureapiutils.hpp | 151 ++++++++++++++++++++ common/remote/hooks/azure/azureblob.cpp | 118 ++++++++++++++- common/remote/hooks/azure/azurefile.cpp | 116 +++++++++++++-- dali/sasha/saserver.cpp | 1 + dali/sasha/saxref.cpp | 5 + 5 files changed, 376 insertions(+), 15 deletions(-) diff --git a/common/remote/hooks/azure/azureapiutils.hpp b/common/remote/hooks/azure/azureapiutils.hpp index bc6f2c4c33a..fa204e21896 100644 --- a/common/remote/hooks/azure/azureapiutils.hpp +++ b/common/remote/hooks/azure/azureapiutils.hpp @@ -22,6 +22,8 @@ #include "jlog.hpp" #include "jmutex.hpp" #include "jstring.hpp" +#include "jfile.hpp" +#include "jregexp.hpp" #include #include @@ -31,6 +33,8 @@ #include #include +#include +#include /* * Common utility functions and constants shared by Azure Blob and File implementations @@ -52,4 +56,151 @@ void handleRequestException(const Azure::Core::RequestFailedException& e, const void handleRequestException(const std::exception& e, const char * op, unsigned attempt, unsigned maxRetries, const char * filename); void handleRequestException(const std::exception& e, const char * op, unsigned attempt, unsigned maxRetries, const char * filename, offset_t pos, offset_t len); +//--------------------------------------------------------------------------------------------------------------------- +// Base class for Azure directory iterators (Blob and File). +// Subclasses implement fetchPage() to populate the items vector using the +// appropriate Azure SDK listing API (with MoveToNextPage() for pagination), +// resetPaging() to discard stored paged-response state, and createFile() to +// construct the correct IFile type for each entry. + +class AzureDirectoryIteratorBase : implements IDirectoryIterator, public CInterface +{ +public: + IMPLEMENT_IINTERFACE; + + struct DirEntry + { + std::string name; + bool isDir; + int64_t size; + time_t modifiedTime; + }; + + AzureDirectoryIteratorBase(const char *_fullPrefix, const char *_containerOrShare, const char *_mask, bool _includeDirs) + : fullPrefix(_fullPrefix), containerOrShare(_containerOrShare), mask(_mask), includeDirs(_includeDirs) {} + + virtual bool first() override + { + items.clear(); + itemIndex = 0; + hasMorePages = true; + resetPaging(); + return loadNextPage() && advance(); + } + virtual bool next() override { itemIndex++; return advance(); } + virtual bool isValid() override { return curValid; } + virtual IFile &query() override + { + if (!curFile) + { + StringBuffer fullPath(fullPrefix); + fullPath.append(curName); + curFile.setown(createFile(fullPath.str(), items[itemIndex])); + } + return *curFile; + } + virtual StringBuffer &getName(StringBuffer &buf) override { if (isValid()) buf.append(curName); return buf; } + virtual bool isDir() override { return curIsDir; } + virtual __int64 getFileSize() override { return curIsDir ? -1 : curSize; } + virtual bool getModifiedTime(CDateTime &ret) override + { + if (curIsDir || curModifiedTime == 0) + { + ret.clear(); + return false; + } + ret.set(curModifiedTime); + return true; + } + +protected: + // Subclass populates 'items' and sets 'hasMorePages' via the SDK's PagedResponse + virtual void fetchPage() = 0; + // Subclass discards any stored paged-response state so iteration can restart + virtual void resetPaging() = 0; + // Subclass creates the appropriate IFile (AzureBlob or AzureFile) for the given path + virtual IFile *createFile(const char *fullPath, const DirEntry &entry) = 0; + + bool matchesMask(const char *name) const { return !mask.length() || WildMatch(name, mask, false); } + + static time_t toTimeT(const Azure::DateTime &dt) + { + return std::chrono::system_clock::to_time_t(std::chrono::system_clock::time_point(dt)); + } + + StringAttr fullPrefix; + StringAttr containerOrShare; + StringAttr mask; + bool includeDirs; + std::vector items; + unsigned itemIndex = 0; + bool hasMorePages = false; + +private: + bool loadNextPage() + { + if (!hasMorePages) + return !items.empty(); + constexpr unsigned maxRetries = 4; + unsigned attempt = 0; + for (;;) + { + try + { + fetchPage(); + break; + } + catch (const Azure::Core::RequestFailedException &e) + { + attempt++; + VStringBuffer msg("%s (container: %s)", fullPrefix.str(), containerOrShare.str()); + handleRequestException(e, "Azure::directoryFiles", attempt, maxRetries, msg.str()); + } + catch (const std::exception &e) + { + attempt++; + VStringBuffer msg("%s (container: %s)", fullPrefix.str(), containerOrShare.str()); + handleRequestException(e, "Azure::directoryFiles", attempt, maxRetries, msg.str()); + } + } + return !items.empty() || hasMorePages; + } + + bool advance() + { + while (itemIndex >= items.size()) + { + if (!hasMorePages) + { + curFile.clear(); + curValid = false; + return false; + } + items.clear(); + itemIndex = 0; + if (!loadNextPage()) + { + curFile.clear(); + curValid = false; + return false; + } + } + const DirEntry &entry = items[itemIndex]; + curName.set(entry.name.c_str()); + curIsDir = entry.isDir; + curSize = entry.size; + curModifiedTime = entry.modifiedTime; + curFile.clear(); // Created lazily in query() - scanDirectory never calls it + curValid = true; + return true; + } + + Owned curFile; + StringAttr curName; + bool curValid = false; + bool curIsDir = false; + int64_t curSize = -1; + time_t curModifiedTime = 0; +}; + #endif diff --git a/common/remote/hooks/azure/azureblob.cpp b/common/remote/hooks/azure/azureblob.cpp index 3d44412db3d..912f45c2695 100644 --- a/common/remote/hooks/azure/azureblob.cpp +++ b/common/remote/hooks/azure/azureblob.cpp @@ -153,6 +153,77 @@ class AzureBlobBlockBlobWriteIO final : implements AzureBlobWriteIO }; +//--------------------------------------------------------------------------------------------------------------------- +// Directory iterator for Azure Blob Storage using ListBlobsByHierarchy API. +// Uses "/" as a delimiter to simulate directory listing, returning both blobs +// (files) and blob prefixes (virtual directories) at the current level. + +class AzureBlobDirectoryIterator final : public AzureDirectoryIteratorBase +{ +public: + AzureBlobDirectoryIterator(std::shared_ptr _containerClient, + const char *_prefix, const char *_fullPrefix, + const char *_containerName, + const char *_mask, bool _includeDirs) + : AzureDirectoryIteratorBase(_fullPrefix, _containerName, _mask, _includeDirs), + containerClient(std::move(_containerClient)), prefix(_prefix) {} + +protected: + virtual IFile *createFile(const char *fullPath, const DirEntry &entry) override; + virtual void resetPaging() override { pagedResponse.reset(); } + + virtual void fetchPage() override + { + if (!pagedResponse) + { + ListBlobsOptions options; + if (!prefix.empty()) + options.Prefix = prefix; + pagedResponse.emplace(containerClient->ListBlobsByHierarchy("/", options)); + } + else + { + pagedResponse->MoveToNextPage(); + } + extractItems(*pagedResponse); + hasMorePages = pagedResponse->NextPageToken.HasValue() && !pagedResponse->NextPageToken.Value().empty(); + } + +private: + void extractItems(const ListBlobsByHierarchyPagedResponse &response) + { + if (includeDirs) + { + for (const auto &dp : response.BlobPrefixes) + { + std::string tail = stripPrefix(dp); + if (!tail.empty() && tail.back() == '/') + tail.pop_back(); + if (!tail.empty() && matchesMask(tail.c_str())) + items.push_back({std::move(tail), true, -1, 0}); + } + } + for (const auto &blob : response.Blobs) + { + std::string tail = stripPrefix(blob.Name); + if (!tail.empty() && tail.find('/') == std::string::npos && matchesMask(tail.c_str())) + items.push_back({std::move(tail), false, blob.BlobSize, toTimeT(blob.Details.LastModified)}); + } + } + + std::string stripPrefix(const std::string &name) const + { + if (!prefix.empty() && name.compare(0, prefix.size(), prefix) == 0) + return name.substr(prefix.size()); + return name; + } + + std::shared_ptr containerClient; + std::string prefix; + std::optional pagedResponse; +}; + + class AzureBlob final : implements CInterfaceOf { public: @@ -211,10 +282,7 @@ class AzureBlob final : implements CInterfaceOf } // Directory functions - virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs) override - { - UNIMPLEMENTED_X("AzureBlob::directoryFiles"); - } + virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs) override; virtual bool getInfo(bool &isdir,offset_t &size,CDateTime &modtime) override { ensureMetaData(); @@ -259,6 +327,15 @@ class AzureBlob final : implements CInterfaceOf public: SharedBlobClient getBlobClient() const; void invalidateMeta() { haveMeta = false; } + void setListedInfo(bool _isDir, offset_t _fileSize, time_t _lastModified) + { + haveMeta = true; + isDir = _isDir; + fileExists = true; + fileSize = _fileSize; + lastModified = _lastModified; + createdOn = 0; + } const AzureAPIConfig & queryConfig() const { return config; } protected: @@ -624,10 +701,10 @@ AzureBlob::AzureBlob(const char *_azureFileName, AzureAPIConfig &&_config) : ful if ((device == 0) || (device > numDevices)) throw makeStringExceptionV(99, "Device %d out of range for plane %s", device, planeName.str()); - if (!endDevice || (*endDevice != '/')) + if (!endDevice || (*endDevice != '/' && *endDevice != '\0')) throw makeStringExceptionV(99, "Unexpected end of device partition %s", fullName.str()); - filename = endDevice+1; + filename = (*endDevice == '/') ? endDevice+1 : endDevice; } VStringBuffer childPath("containers[%u]", device); @@ -660,10 +737,16 @@ AzureBlob::AzureBlob(const char *_azureFileName, AzureAPIConfig &&_config) : ful } else throw makeStringExceptionV(99, "Unexpected prefix on azure filename %s", fullName.str()); - blobUrl = ::getBlobUrl(accountName, containerName, blobName); } +IFile * AzureBlobDirectoryIterator::createFile(const char *fullPath, const DirEntry &entry) +{ + AzureBlob *file = static_cast(createAzureBlob(fullPath)); + file->setListedInfo(entry.isDir, entry.size, entry.modifiedTime); + return file; +} + std::shared_ptr AzureBlob::getSharedKeyCredentials() const { return getAzureSharedKeyCredential(accountName.str(), secretName.str()); @@ -786,6 +869,27 @@ IFileIO * AzureBlob::createFileWriteIO() return new AzureBlobBlockBlobWriteIO(this); } +IDirectoryIterator * AzureBlob::directoryFiles(const char *mask, bool sub, bool includeDirs) +{ + if (sub) + UNIMPLEMENTED_X("AzureBlob::directoryFiles recursive"); + + // Build the blob prefix for listing. The blobName is the path within the container. + // For directory listing, we want to list blobs under this prefix with "/" delimiter. + std::string listPrefix(blobName.str()); + if (!listPrefix.empty() && listPrefix.back() != '/') + listPrefix += '/'; + + // Build the full azure path prefix for constructing child IFile paths + // fullName is e.g. "azureblob:plane/path" or "azureblob:plane/d1/path" + StringBuffer fullPathPrefix(fullName); + if (fullPathPrefix.length() > 0 && fullPathPrefix.charAt(fullPathPrefix.length()-1) != '/') + fullPathPrefix.append('/'); + + auto containerClient = getBlobContainerClient(); + return new AzureBlobDirectoryIterator(containerClient, listPrefix.c_str(), fullPathPrefix.str(), containerName.str(), mask, includeDirs); +} + void AzureBlob::ensureMetaData() { CriticalBlock block(cs); diff --git a/common/remote/hooks/azure/azurefile.cpp b/common/remote/hooks/azure/azurefile.cpp index 4d326c1c647..05aef513002 100644 --- a/common/remote/hooks/azure/azurefile.cpp +++ b/common/remote/hooks/azure/azurefile.cpp @@ -129,6 +129,57 @@ class AzureFileShareWriteIO final : implements AzureFileWriteIO }; +//--------------------------------------------------------------------------------------------------------------------- +// Directory iterator for Azure File Shares using ShareDirectoryClient::ListFilesAndDirectories API. + +class AzureFileDirectoryIterator final : public AzureDirectoryIteratorBase +{ +public: + AzureFileDirectoryIterator(Azure::Storage::Files::Shares::ShareDirectoryClient _dirClient, + const char *_fullPrefix, const char *_shareName, + const char *_mask, bool _includeDirs) + : AzureDirectoryIteratorBase(_fullPrefix, _shareName, _mask, _includeDirs), + dirClient(std::move(_dirClient)) {} + +protected: + virtual IFile *createFile(const char *fullPath, const DirEntry &entry) override; + virtual void resetPaging() override { pagedResponse.reset(); } + + virtual void fetchPage() override + { + if (!pagedResponse) + { + ListFilesAndDirectoriesOptions options; + options.Include = Azure::Storage::Files::Shares::Models::ListFilesIncludeFlags::Timestamps; + pagedResponse.emplace(dirClient.ListFilesAndDirectories(options)); + } + else + { + pagedResponse->MoveToNextPage(); + } + extractItems(*pagedResponse); + hasMorePages = pagedResponse->NextPageToken.HasValue() && !pagedResponse->NextPageToken.Value().empty(); + } + +private: + void extractItems(const ListFilesAndDirectoriesPagedResponse &response) + { + if (includeDirs) + { + for (const auto &dir : response.Directories) + if (matchesMask(dir.Name.c_str())) + items.push_back({dir.Name, true, -1, toTimeT(dir.Details.LastModified)}); + } + for (const auto &file : response.Files) + if (matchesMask(file.Name.c_str())) + items.push_back({file.Name, false, file.Details.FileSize, toTimeT(file.Details.LastModified)}); + } + + Azure::Storage::Files::Shares::ShareDirectoryClient dirClient; + std::optional pagedResponse; +}; + + class AzureFile final : implements CInterfaceOf { friend class AzureFileIO; @@ -189,10 +240,7 @@ class AzureFile final : implements CInterfaceOf } // Directory functions - virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs) override - { - UNIMPLEMENTED_X("AzureFile::directoryFiles"); - } + virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs) override; virtual bool getInfo(bool &isdir,offset_t &size,CDateTime &modtime) override { ensureMetaData(); @@ -237,11 +285,21 @@ class AzureFile final : implements CInterfaceOf public: SharedFileClient getFileClient() const; void invalidateMeta() { haveMeta = false; } + void setListedInfo(bool _isDir, offset_t _fileSize, time_t _lastModified) + { + haveMeta = true; + isDir = _isDir; + fileExists = true; + fileSize = _fileSize; + lastModified = _lastModified; + createdOn = 0; + } protected: std::shared_ptr getSharedKeyCredentials() const; std::string getFileUrl() const; std::shared_ptr getShareClient() const; + Azure::Storage::Files::Shares::ShareDirectoryClient getDirectoryClient() const; void ensureMetaData(); void gatherMetaData(); @@ -331,10 +389,10 @@ AzureFile::AzureFile(const char *_azureFileName) : fullName(_azureFileName) if ((device == 0) || (device > numDevices)) throw makeStringExceptionV(99, "Device %d out of range for plane %s", device, planeName.str()); - if (!endDevice || (*endDevice != '/')) + if (!endDevice || (*endDevice != '/' && *endDevice != '\0')) throw makeStringExceptionV(99, "Unexpected end of device partition %s", fullName.str()); - filename = endDevice+1; + filename = (*endDevice == '/') ? endDevice+1 : endDevice; } VStringBuffer childPath("containers[%u]", device); @@ -363,14 +421,20 @@ AzureFile::AzureFile(const char *_azureFileName) : fullName(_azureFileName) if (!useManagedIdentity && isEmptyString(secretName)) throw makeStringExceptionV(99, "Missing secret name for plane %s", planeName.str()); - fileName.set(slash+1); + fileName.set(filename); } else throw makeStringExceptionV(99, "Unexpected prefix on azure filename %s", fullName.str()); - fileUrl = ::getFileUrl(accountName, shareName, fileName); } +IFile * AzureFileDirectoryIterator::createFile(const char *fullPath, const DirEntry &entry) +{ + AzureFile *file = new AzureFile(fullPath); + file->setListedInfo(entry.isDir, entry.size, entry.modifiedTime); + return file; +} + SharedFileClient AzureFile::getFileClient() const { if (useManagedIdentity) @@ -405,6 +469,42 @@ std::shared_ptr AzureFile::getShareClient() const } } +Azure::Storage::Files::Shares::ShareDirectoryClient AzureFile::getDirectoryClient() const +{ + auto shareClient = getShareClient(); + auto dirClient = shareClient->GetRootDirectoryClient(); + + // Navigate to the subdirectory specified by fileName + // fileName is the path within the share, e.g. "path/to/dir" + const char *path = fileName.str(); + if (!isEmptyString(path)) + { + StringArray components; + components.appendList(path, "/"); + ForEachItemIn(i, components) + { + const char *component = components.item(i); + if (!isEmptyString(component)) + dirClient = dirClient.GetSubdirectoryClient(component); + } + } + return dirClient; +} + +IDirectoryIterator * AzureFile::directoryFiles(const char *mask, bool sub, bool includeDirs) +{ + if (sub) + UNIMPLEMENTED_X("AzureFile::directoryFiles recursive"); + + // Build the full azure path prefix for constructing child IFile paths + StringBuffer fullPathPrefix(fullName); + if (fullPathPrefix.length() > 0 && fullPathPrefix.charAt(fullPathPrefix.length()-1) != '/') + fullPathPrefix.append('/'); + + auto dirClient = getDirectoryClient(); + return new AzureFileDirectoryIterator(std::move(dirClient), fullPathPrefix.str(), shareName.str(), mask, includeDirs); +} + void AzureFile::ensureMetaData() { if (haveMeta) diff --git a/dali/sasha/saserver.cpp b/dali/sasha/saserver.cpp index eb02c536d80..3a7b089bc15 100644 --- a/dali/sasha/saserver.cpp +++ b/dali/sasha/saserver.cpp @@ -430,6 +430,7 @@ int main(int argc, const char* argv[]) { addAbortHandler(actionOnAbort); initializeStoragePlanes(true, true); + installDefaultFileHooks(serverConfig); #ifdef _CONTAINERIZED service = serverConfig->queryProp("@service"); if (isEmptyString(service)) diff --git a/dali/sasha/saxref.cpp b/dali/sasha/saxref.cpp index aa106cf26a6..02ee60a8a61 100644 --- a/dali/sasha/saxref.cpp +++ b/dali/sasha/saxref.cpp @@ -2899,6 +2899,11 @@ class CSashaXRefServer: public ISashaServer, public Thread OERRLOG("Could not connect to /Environment/Software"); return; } + + // Bare-metal only: resolve cluster names to node groups by matching against + // ThorCluster/RoxieCluster entries under /Environment/Software. + // Clusters that don't match a Thor or Roxie entry (e.g. standalone data + // planes) will not be added to 'groups' and will be skipped by the XREF scan. clustersToGroups(conn->queryRoot(),list,cnames,groups,NULL); } IArrayOf groupsdone; From d7ea353414fb761f1a5b1897c2d88bc85cdd9e6b Mon Sep 17 00:00:00 2001 From: Jack Del Vecchio Date: Thu, 23 Apr 2026 11:23:03 -0400 Subject: [PATCH 2/4] Use isEmpty() rather than length() --- common/remote/hooks/azure/azureapiutils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/remote/hooks/azure/azureapiutils.hpp b/common/remote/hooks/azure/azureapiutils.hpp index fa204e21896..880c3bc68d7 100644 --- a/common/remote/hooks/azure/azureapiutils.hpp +++ b/common/remote/hooks/azure/azureapiutils.hpp @@ -121,7 +121,7 @@ class AzureDirectoryIteratorBase : implements IDirectoryIterator, public CInterf // Subclass creates the appropriate IFile (AzureBlob or AzureFile) for the given path virtual IFile *createFile(const char *fullPath, const DirEntry &entry) = 0; - bool matchesMask(const char *name) const { return !mask.length() || WildMatch(name, mask, false); } + bool matchesMask(const char *name) const { return mask.isEmpty() || WildMatch(name, mask, false); } static time_t toTimeT(const Azure::DateTime &dt) { From c50f8a76e5e7ba500a735075d0213090670c4a65 Mon Sep 17 00:00:00 2001 From: Jack Del Vecchio Date: Thu, 23 Apr 2026 11:24:54 -0400 Subject: [PATCH 3/4] Avoid cloning DirEntry contents --- common/remote/hooks/azure/azureapiutils.hpp | 37 +++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/common/remote/hooks/azure/azureapiutils.hpp b/common/remote/hooks/azure/azureapiutils.hpp index 880c3bc68d7..ac5315eb9d2 100644 --- a/common/remote/hooks/azure/azureapiutils.hpp +++ b/common/remote/hooks/azure/azureapiutils.hpp @@ -87,29 +87,39 @@ class AzureDirectoryIteratorBase : implements IDirectoryIterator, public CInterf resetPaging(); return loadNextPage() && advance(); } - virtual bool next() override { itemIndex++; return advance(); } - virtual bool isValid() override { return curValid; } + virtual bool next() override { itemIndex++; return advance(); } + virtual bool isValid() override { return curValid; } virtual IFile &query() override { if (!curFile) { StringBuffer fullPath(fullPrefix); - fullPath.append(curName); + fullPath.append(queryCurEntry().name); curFile.setown(createFile(fullPath.str(), items[itemIndex])); } return *curFile; } - virtual StringBuffer &getName(StringBuffer &buf) override { if (isValid()) buf.append(curName); return buf; } - virtual bool isDir() override { return curIsDir; } - virtual __int64 getFileSize() override { return curIsDir ? -1 : curSize; } + virtual StringBuffer &getName(StringBuffer &buf) override + { + if (isValid()) + buf.append(queryCurEntry().name); + return buf; + } + virtual bool isDir() override { return queryCurEntry().isDir; } + virtual __int64 getFileSize() override + { + const DirEntry &entry = queryCurEntry(); + return entry.isDir ? -1 : entry.size; + } virtual bool getModifiedTime(CDateTime &ret) override { - if (curIsDir || curModifiedTime == 0) + const DirEntry &entry = queryCurEntry(); + if (entry.isDir) { ret.clear(); return false; } - ret.set(curModifiedTime); + ret.set(entry.modifiedTime); return true; } @@ -185,22 +195,15 @@ class AzureDirectoryIteratorBase : implements IDirectoryIterator, public CInterf return false; } } - const DirEntry &entry = items[itemIndex]; - curName.set(entry.name.c_str()); - curIsDir = entry.isDir; - curSize = entry.size; - curModifiedTime = entry.modifiedTime; curFile.clear(); // Created lazily in query() - scanDirectory never calls it curValid = true; return true; } + inline const DirEntry & queryCurEntry() const { return items[itemIndex]; } + Owned curFile; - StringAttr curName; bool curValid = false; - bool curIsDir = false; - int64_t curSize = -1; - time_t curModifiedTime = 0; }; #endif From a7fc448678c3ce0c481007091ae78ead54e8b86b Mon Sep 17 00:00:00 2001 From: Jack Del Vecchio Date: Thu, 23 Apr 2026 11:25:41 -0400 Subject: [PATCH 4/4] Create overloaded constructor that takes DirEntry and calls setListedInfo --- common/remote/hooks/azure/azureapiutils.hpp | 16 ++++++++-------- common/remote/hooks/azure/azureblob.cpp | 11 ++++++++--- common/remote/hooks/azure/azureblob.hpp | 2 ++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/common/remote/hooks/azure/azureapiutils.hpp b/common/remote/hooks/azure/azureapiutils.hpp index ac5315eb9d2..bea9efd5b2a 100644 --- a/common/remote/hooks/azure/azureapiutils.hpp +++ b/common/remote/hooks/azure/azureapiutils.hpp @@ -56,6 +56,14 @@ void handleRequestException(const Azure::Core::RequestFailedException& e, const void handleRequestException(const std::exception& e, const char * op, unsigned attempt, unsigned maxRetries, const char * filename); void handleRequestException(const std::exception& e, const char * op, unsigned attempt, unsigned maxRetries, const char * filename, offset_t pos, offset_t len); +struct DirEntry +{ + std::string name; + bool isDir; + int64_t size; + time_t modifiedTime; +}; + //--------------------------------------------------------------------------------------------------------------------- // Base class for Azure directory iterators (Blob and File). // Subclasses implement fetchPage() to populate the items vector using the @@ -68,14 +76,6 @@ class AzureDirectoryIteratorBase : implements IDirectoryIterator, public CInterf public: IMPLEMENT_IINTERFACE; - struct DirEntry - { - std::string name; - bool isDir; - int64_t size; - time_t modifiedTime; - }; - AzureDirectoryIteratorBase(const char *_fullPrefix, const char *_containerOrShare, const char *_mask, bool _includeDirs) : fullPrefix(_fullPrefix), containerOrShare(_containerOrShare), mask(_mask), includeDirs(_includeDirs) {} diff --git a/common/remote/hooks/azure/azureblob.cpp b/common/remote/hooks/azure/azureblob.cpp index 912f45c2695..1a43cf4d890 100644 --- a/common/remote/hooks/azure/azureblob.cpp +++ b/common/remote/hooks/azure/azureblob.cpp @@ -228,6 +228,8 @@ class AzureBlob final : implements CInterfaceOf { public: AzureBlob(const char *_azureFileName, AzureAPIConfig &&_config); + AzureBlob(const char *_azureFileName, AzureAPIConfig &&_config, const DirEntry &entry) + : AzureBlob(_azureFileName, std::move(_config)) { setListedInfo(entry.isDir, entry.size, entry.modifiedTime); } virtual bool exists() override { ensureMetaData(); @@ -742,9 +744,7 @@ AzureBlob::AzureBlob(const char *_azureFileName, AzureAPIConfig &&_config) : ful IFile * AzureBlobDirectoryIterator::createFile(const char *fullPath, const DirEntry &entry) { - AzureBlob *file = static_cast(createAzureBlob(fullPath)); - file->setListedInfo(entry.isDir, entry.size, entry.modifiedTime); - return file; + return createAzureBlob(fullPath, entry); } std::shared_ptr AzureBlob::getSharedKeyCredentials() const @@ -1007,3 +1007,8 @@ IFile *createAzureBlob(const char *azureFileName) { return new AzureBlob(azureFileName, getAzureConfig()); } + +IFile *createAzureBlob(const char *azureFileName, const DirEntry &entry) +{ + return new AzureBlob(azureFileName, getAzureConfig(), entry); +} diff --git a/common/remote/hooks/azure/azureblob.hpp b/common/remote/hooks/azure/azureblob.hpp index 23b38bf35ff..cf67ea12c24 100644 --- a/common/remote/hooks/azure/azureblob.hpp +++ b/common/remote/hooks/azure/azureblob.hpp @@ -19,6 +19,7 @@ #define AZURE_BLOB_HPP #include "jfile.hpp" +#include "azureapiutils.hpp" /* * Direct access to files in Azure blobs @@ -27,5 +28,6 @@ */ IFile * createAzureBlob(const char * filename); +IFile * createAzureBlob(const char * filename, const DirEntry &entry); #endif