diff --git a/SiaDrive.Api/SiaApi.cpp b/SiaDrive.Api/SiaApi.cpp index 98a14ba..6b44a8a 100644 --- a/SiaDrive.Api/SiaApi.cpp +++ b/SiaDrive.Api/SiaApi.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "SiaApi.h" +#include using namespace Sia::Api; @@ -16,6 +17,24 @@ CSiaApi::~CSiaApi() _wallet->Lock(); } +String CSiaApi::FormatToSiaPath(String path) +{ + if (path.length()) + { + std::replace(path.begin(), path.end(), '\\', '/'); + std::wregex r(L"/+"); + path = std::regex_replace(path, r, L"/"); + + while (path[0] == '/') + { + path = path.substr(1); + } + } + + + return path; +} + String CSiaApi::GetServerVersion() const { return _siaCurl.GetServerVersion(); diff --git a/SiaDrive.Api/SiaApi.h b/SiaDrive.Api/SiaApi.h index 1fa39ca..b3861b5 100644 --- a/SiaDrive.Api/SiaApi.h +++ b/SiaDrive.Api/SiaApi.h @@ -40,6 +40,8 @@ public: public: void BuildTree(const json& result); + + bool FileExists(const String& siaPath) const; }; class AFX_EXT_CLASS _CSiaWallet @@ -100,6 +102,9 @@ private: std::shared_ptr<_CSiaWallet> _wallet; std::shared_ptr<_CSiaRenter> _renter; +public: + static String FormatToSiaPath(String path); + public: std::shared_ptr<_CSiaWallet> GetWallet() const; std::shared_ptr<_CSiaRenter> GetRenter() const; diff --git a/SiaDrive.Api/SiaFileTree.cpp b/SiaDrive.Api/SiaFileTree.cpp index 8d0db67..7727ff4 100644 --- a/SiaDrive.Api/SiaFileTree.cpp +++ b/SiaDrive.Api/SiaFileTree.cpp @@ -14,6 +14,11 @@ CSiaApi::_CSiaFileTree::~_CSiaFileTree() } +bool CSiaApi::_CSiaFileTree::FileExists(const String& siaPath) const +{ + return false; +} + void CSiaApi::_CSiaFileTree::BuildTree(const json& result) { diff --git a/SiaDrive.Api/SiaRenter.cpp b/SiaDrive.Api/SiaRenter.cpp index e25642b..057fe12 100644 --- a/SiaDrive.Api/SiaRenter.cpp +++ b/SiaDrive.Api/SiaRenter.cpp @@ -15,7 +15,13 @@ CSiaApi::_CSiaRenter::~_CSiaRenter() SiaApiError CSiaApi::_CSiaRenter::FileExists(const String& siaPath, bool& exists) const { - return SiaApiError::NotImplemented; + CSiaFileTreePtr siaFileTree; + SiaApiError ret = GetFileTree(siaFileTree); + if (API_SUCCESS(SiaApiError, ret)) + { + exists = siaFileTree->FileExists(siaPath); + } + return ret; } SiaApiError CSiaApi::_CSiaRenter::DeleteFile(const String& siaPath) diff --git a/SiaDrive.Dokan.Api/SiaDokanDrive.cpp b/SiaDrive.Dokan.Api/SiaDokanDrive.cpp index 627d6e3..e46e9af 100644 --- a/SiaDrive.Dokan.Api/SiaDokanDrive.cpp +++ b/SiaDrive.Dokan.Api/SiaDokanDrive.cpp @@ -128,170 +128,185 @@ private: ACCESS_MASK genericDesiredAccess = DokanMapStandardToGenericAccess(DesiredAccess); NTSTATUS ret = STATUS_SUCCESS; - bool isFile = (FileAttributes & FILE_NON_DIRECTORY_FILE); - DokanFileInfo->IsDirectory = !isFile; - if (isFile) + // Probably not going to happen, but just in case + if (PathIsUNC(FileName)) { - // Formulate Sia path and cache path - String siaPath = PathSkipRoot(FileName); // Strip drive letter to get Sia path - String cacheFilePath; - cacheFilePath.resize(MAX_PATH + 1); - PathCombine(&cacheFilePath[0], _cacheLocation.c_str(), siaPath.c_str()); - - // If cache file already exists and is a directory, requested file operation doesn't make sense - if (GetFileAttributes(cacheFilePath.c_str()) & FILE_ATTRIBUTE_DIRECTORY) + ret = STATUS_ILLEGAL_ELEMENT_ADDRESS; + } + else + { + bool isFile = (FileAttributes & FILE_NON_DIRECTORY_FILE); + DokanFileInfo->IsDirectory = !isFile; + if (isFile) { - ret = STATUS_OBJECT_NAME_COLLISION; - } - else - { - bool exists; - if (API_SUCCESS(SiaApiError, _siaApi->GetRenter()->FileExists(siaPath, exists))) + // Formulate Sia path and cache path + String siaPath = CSiaApi::FormatToSiaPath(PathSkipRoot(FileName)); // Strip drive letter to get Sia path + if (siaPath.length()) { - // Operations on existing files that are requested to be truncated, overwritten or re-created - // will first be deleted and then replaced if, after the file operation is done, the resulting file - // size is > 0. Sia doesn't support random access to files (upload/download/rename/delete). - bool isCreateOp = false; - bool isReplaceOp = false; - switch (creationDisposition) - { - case CREATE_ALWAYS: - { - isCreateOp = true; - isReplaceOp = exists; - } - break; + String cacheFilePath; + cacheFilePath.resize(MAX_PATH + 1); + PathCombine(&cacheFilePath[0], _cacheLocation.c_str(), siaPath.c_str()); - case CREATE_NEW: + // If cache file already exists and is a directory, requested file operation isn't valid + if (GetFileAttributes(cacheFilePath.c_str()) & FILE_ATTRIBUTE_DIRECTORY) { - if (exists) - { - ret = STATUS_OBJECT_NAME_EXISTS; - } - else - { - isCreateOp = true; - } + ret = STATUS_OBJECT_NAME_COLLISION; } - break; - - case OPEN_ALWAYS: + else { - if (!exists) + bool exists; + if (API_SUCCESS(SiaApiError, _siaApi->GetRenter()->FileExists(siaPath, exists))) { - isCreateOp = true; - } - } - break; - - case OPEN_EXISTING: - { - if (!exists) - { - ret = STATUS_NOT_FOUND; - } - } - break; - - case TRUNCATE_EXISTING: - { - if (exists) - { - isCreateOp = isReplaceOp = true; - } - else - { - ret = STATUS_NOT_FOUND; - } - } - break; - } - - if (ret == STATUS_SUCCESS) - { - if (isReplaceOp) - { - // Since this is a request to replace existing file, so make sure cache is deleted first. - // If file isn't cached, delete from Sia only - if (!PathFileExists(cacheFilePath.c_str()) || ::DeleteFile(cacheFilePath.c_str())) + // Operations on existing files that are requested to be truncated, overwritten or re-created + // will first be deleted and then replaced if, after the file operation is done, the resulting file + // size is > 0. Sia doesn't support random access to files (upload/download/rename/delete). + bool isCreateOp = false; + bool isReplaceOp = false; + switch (creationDisposition) { - // Delete from Sia since this file will be replaced - if (!API_SUCCESS(SiaApiError, _siaApi->GetRenter()->DeleteFile(siaPath))) + case CREATE_ALWAYS: + { + isCreateOp = true; + isReplaceOp = exists; + } + break; + + case CREATE_NEW: + { + if (exists) { - ret = STATUS_INVALID_SERVER_STATE; + ret = STATUS_OBJECT_NAME_EXISTS; + } + else + { + isCreateOp = true; } } - else - { - ret = DokanNtStatusFromWin32(GetLastError()); - } - } + break; - if (ret == STATUS_SUCCESS) - { - // If file must exist, then check for it in cache location. If not found, - // it must be downloaded first and placed in cache - if (!isCreateOp && !PathFileExists(cacheFilePath.c_str())) + case OPEN_ALWAYS: { - if (!AddFileToCache(siaPath, cacheFilePath)) + if (!exists) { - ret = STATUS_INVALID_SERVER_STATE; + isCreateOp = true; } } + break; + + case OPEN_EXISTING: + { + if (!exists) + { + ret = STATUS_NOT_FOUND; + } + } + break; + + case TRUNCATE_EXISTING: + { + if (exists) + { + isCreateOp = isReplaceOp = true; + } + else + { + ret = STATUS_NOT_FOUND; + } + } + break; + } if (ret == STATUS_SUCCESS) { - // Create file as specified - HANDLE handle = CreateFile( - cacheFilePath.c_str(), - genericDesiredAccess, - ShareAccess, - &securityAttrib, - creationDisposition, - fileAttributesAndFlags, - nullptr); - if (handle == INVALID_HANDLE_VALUE) + if (isReplaceOp) { - ret = DokanNtStatusFromWin32(GetLastError()); - } - else - { - DokanFileInfo->Context = reinterpret_cast(handle); // save the file handle in Context - if (isFile) + // Since this is a request to replace existing file, so make sure cache is deleted first. + // If file isn't cached, delete from Sia only + if (!PathFileExists(cacheFilePath.c_str()) || ::DeleteFile(cacheFilePath.c_str())) { - OpenFileInfo ofi; - ofi.SiaPath = siaPath; - ofi.CacheFilePath = cacheFilePath; - // TODO Detect if file is read-only - // TODO Quick hash to detect changes - ofi.ReadOnly = false; - _openFileMap.insert({ DokanFileInfo->Context, ofi }); + // Delete from Sia since this file will be replaced + if (!API_SUCCESS(SiaApiError, _siaApi->GetRenter()->DeleteFile(siaPath))) + { + ret = STATUS_INVALID_SERVER_STATE; + } + } + else + { + ret = DokanNtStatusFromWin32(GetLastError()); + } + } + + if (ret == STATUS_SUCCESS) + { + // If file must exist, then check for it in cache location. If not found, + // it must be downloaded first and placed in cache + if (!isCreateOp && !PathFileExists(cacheFilePath.c_str())) + { + if (!AddFileToCache(siaPath, cacheFilePath)) + { + ret = STATUS_INVALID_SERVER_STATE; + } } - /*if (creationDisposition == OPEN_ALWAYS || - creationDisposition == CREATE_ALWAYS) { - error = GetLastError(); - if (error == ERROR_ALREADY_EXISTS) { - DbgPrint(L"\tOpen an already existing file\n"); - // Open succeed but we need to inform the driver - // that the file open and not created by returning STATUS_OBJECT_NAME_COLLISION - return STATUS_OBJECT_NAME_COLLISION; + if (ret == STATUS_SUCCESS) + { + // Create file as specified + HANDLE handle = CreateFile( + cacheFilePath.c_str(), + genericDesiredAccess, + ShareAccess, + &securityAttrib, + creationDisposition, + fileAttributesAndFlags, + nullptr); + if (handle == INVALID_HANDLE_VALUE) + { + ret = DokanNtStatusFromWin32(GetLastError()); } - }*/ + else + { + DokanFileInfo->Context = reinterpret_cast(handle); // save the file handle in Context + if (isFile) + { + OpenFileInfo ofi; + ofi.SiaPath = siaPath; + ofi.CacheFilePath = cacheFilePath; + // TODO Detect if file is read-only + // TODO Quick hash to detect changes + ofi.ReadOnly = false; + _openFileMap.insert({ DokanFileInfo->Context, ofi }); + } + + /*if (creationDisposition == OPEN_ALWAYS || + creationDisposition == CREATE_ALWAYS) { + error = GetLastError(); + if (error == ERROR_ALREADY_EXISTS) { + DbgPrint(L"\tOpen an already existing file\n"); + // Open succeed but we need to inform the driver + // that the file open and not created by returning STATUS_OBJECT_NAME_COLLISION + return STATUS_OBJECT_NAME_COLLISION; + } + }*/ + } + } } } } + else + { + ret = STATUS_INVALID_SERVER_STATE; + } } } else { - ret = STATUS_INVALID_SERVER_STATE; + ret = STATUS_OBJECT_NAME_INVALID; } } - } - else // Folder Operation (cache operation only) - { - ret = STATUS_NOT_IMPLEMENTED; + else // Folder Operation (cache operation only) + { + ret = STATUS_NOT_IMPLEMENTED; + } } return ret; diff --git a/UnitTests/SiaApiTests.cpp b/UnitTests/SiaApiTests.cpp index 2e910b8..45ddd0c 100644 --- a/UnitTests/SiaApiTests.cpp +++ b/UnitTests/SiaApiTests.cpp @@ -20,6 +20,46 @@ namespace UnitTests CSiaApi api(_hostConfig); Assert::IsTrue(api.GetServerVersion() == TEST_SERVER_VERSION); } + + TEST_METHOD(RelativePathToSiaPath) + { + String relPath = L"test\\moose\\cow.txt"; + String siaPath = CSiaApi::FormatToSiaPath(relPath); + + Assert::AreEqual(L"test/moose/cow.txt", siaPath.c_str()); + } + + TEST_METHOD(RelativePathWithBeginningBackslashToSiaPath) + { + String relPath = L"\\test\\moose\\cow.txt"; + String siaPath = CSiaApi::FormatToSiaPath(relPath); + + Assert::AreEqual(L"test/moose/cow.txt", siaPath.c_str()); + } + + TEST_METHOD(RelativePathWithBeginningBackslashOnlyToSiaPath) + { + String relPath = L"\\"; + String siaPath = CSiaApi::FormatToSiaPath(relPath); + + Assert::AreEqual(L"", siaPath.c_str()); + } + + TEST_METHOD(FilenameOnlyToSiaPath) + { + String relPath = L"moose.txt"; + String siaPath = CSiaApi::FormatToSiaPath(relPath); + + Assert::AreEqual(L"moose.txt", siaPath.c_str()); + } + + TEST_METHOD(RemoveExtraBackslashes) + { + String relPath = L"\\\\\\\\test\\\\\\\\\\\\\\\\\\moose\\\\\\\\\\\\cow.txt"; + String siaPath = CSiaApi::FormatToSiaPath(relPath); + + Assert::AreEqual(L"test/moose/cow.txt", siaPath.c_str()); + } }; DEFINE_DAEMON(SiaApi);