#include "stdafx.h" #include "SiaDokanDrive.h" #include #include "UploadManager.h" using namespace Sia::Api; using namespace Sia::Api::Dokan; // TODO Handle paths greater than MAX_PATH!! static String StdConstructPath(const String& part1, const String& part2) { String path = part1; path.resize(MAX_PATH + 1); ::PathAppend(&path[0], part2.c_str()); return path.c_str(); } // The general idea is that normal file I/O occurs in a local cache folder and once the file is closed, it is scheduled for upload into Sia. // Files requested to be openned that are not cached will be downloaded first. If the file is not found in Sia, it will be treated as new. // Keeping cache and Sia in synch will be a bit of a hastle, so it's strongly suggested to treat the cache folder as if it doesn't exist; // however, simply deleting files in the cache folder should not be an issue as long as the drive is not mounted. class AFX_EXT_CLASS DokanImpl { private: typedef struct { String SiaPath; String CacheFilePath; bool ReadOnly; } OpenFileInfo; private: static std::mutex _dokanMutex; static CSiaApi* _siaApi; static CSiaDriveConfig* _siaDriveConfig; static std::unique_ptr _uploadManager; static DOKAN_OPERATIONS _dokanOps; static DOKAN_OPTIONS _dokanOptions; static String _cacheLocation; static std::unordered_map _openFileMap; static std::unique_ptr _fileListThread; static bool _fileListStopRequested; static CSiaFileTreePtr _siaFileTree; static std::mutex _fileTreeMutex; static std::unique_ptr _mountThread; static NTSTATUS _mountStatus; static String _mountPoint; private: inline static const String& GetCacheLocation() { return _cacheLocation; } static bool AddFileToCache(const String& siaPath, const String& cacheLocation) { bool ret = false; std::wstring tempPath; tempPath.resize(MAX_PATH + 1); if (::GetTempPath(MAX_PATH + 1, &tempPath[0])) { // Check cache size is large enough to hold new file ret = ApiSuccess(_siaApi->GetRenter()->DownloadFile(siaPath, tempPath)); if (ret) { String src = StdConstructPath(tempPath, L""); String dest = StdConstructPath(GetCacheLocation(), siaPath); ret = ::MoveFile(src.c_str(), dest.c_str()) ? true : false; } } return ret; } static void QueueUploadIfChanged(const ULONG64& id, const std::uint64_t& size) { if (!_openFileMap[id].ReadOnly) { if (size > 0) { // TODO Always save for now - need to change to detect modifications // TODO Handle error return _uploadManager->AddOrUpdate(_openFileMap[id].SiaPath, _openFileMap[id].CacheFilePath); } else { // Treat 0 length files as deleted in Sia - cache retains 0-length // TODO Retain 0 length in cache? // TODO Handle error return _uploadManager->Remove(_openFileMap[id].SiaPath); } } } static void StartFileListThread() { if (!_fileListThread) { _fileListStopRequested = false; _fileListThread.reset(new std::thread([]() { while (!_fileListStopRequested) { CSiaFileTreePtr siaFileTree; _siaApi->GetRenter()->GetFileTree(siaFileTree); { std::lock_guard l(_fileTreeMutex); _siaFileTree = siaFileTree; } if (!_fileListStopRequested) { // TODO Change to WaitForSingleObject() for immediate termination Sleep(5000); } } })); } } static void StopFileListThread() { if (_fileListThread) { _fileListStopRequested = true; _fileListThread->join(); _fileListThread.reset(nullptr); } } // Dokan callbacks private: static NTSTATUS DOKAN_CALLBACK Sia_ZwCreateFile( LPCWSTR FileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo) { std::lock_guard l(_dokanMutex); SECURITY_ATTRIBUTES securityAttrib; securityAttrib.nLength = sizeof(securityAttrib); securityAttrib.lpSecurityDescriptor = SecurityContext->AccessState.SecurityDescriptor; securityAttrib.bInheritHandle = FALSE; DWORD fileAttributesAndFlags; DWORD creationDisposition; DokanMapKernelToUserCreateFileFlags(FileAttributes, CreateOptions, CreateDisposition, &fileAttributesAndFlags, &creationDisposition); ACCESS_MASK genericDesiredAccess = DokanMapStandardToGenericAccess(DesiredAccess); NTSTATUS ret = STATUS_SUCCESS; // Probably not going to happen, but just in case if (PathIsUNC(FileName)) { ret = STATUS_ILLEGAL_ELEMENT_ADDRESS; } else { // When filePath is a directory, needs to change the flag so that the file can // be opened. String cacheFilePath = StdConstructPath(GetCacheLocation(), &FileName[1]); DWORD fileAttr = ::GetFileAttributes(cacheFilePath.c_str()); if ((fileAttr != INVALID_FILE_ATTRIBUTES) && (fileAttr & FILE_ATTRIBUTE_DIRECTORY) && !(CreateOptions & FILE_NON_DIRECTORY_FILE)) { DokanFileInfo->IsDirectory = TRUE; if (DesiredAccess & DELETE) { // Needed by FindFirstFile to see if directory is empty or not ShareAccess |= FILE_SHARE_READ; } } // Folder (cache operation only) if (DokanFileInfo->IsDirectory) { if (creationDisposition == CREATE_NEW) { if (!::CreateDirectory(cacheFilePath.c_str(), &securityAttrib)) { DWORD error = GetLastError(); ret = DokanNtStatusFromWin32(error); } } else if (creationDisposition == OPEN_ALWAYS) { if (!CreateDirectory(cacheFilePath.c_str(), &securityAttrib)) { DWORD error = GetLastError(); if (error != ERROR_ALREADY_EXISTS) { ret = DokanNtStatusFromWin32(error); } } } if (ret == STATUS_SUCCESS) { //Check first if we're trying to open a file as a directory. if (fileAttr != INVALID_FILE_ATTRIBUTES && !(fileAttr & FILE_ATTRIBUTE_DIRECTORY) && (CreateOptions & FILE_DIRECTORY_FILE)) { return STATUS_NOT_A_DIRECTORY; } // FILE_FLAG_BACKUP_SEMANTICS is required for opening directory handles HANDLE handle = CreateFile(cacheFilePath.c_str(), genericDesiredAccess, ShareAccess, &securityAttrib, OPEN_EXISTING, fileAttributesAndFlags | FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (handle == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); ret = DokanNtStatusFromWin32(error); } else { DokanFileInfo->Context = reinterpret_cast(handle); // save the file handle in Context } } } else // File (cache and/or Sia operation) { // Formulate Sia path and cache path String siaPath = CSiaApi::FormatToSiaPath(PathSkipRoot(FileName)); // Strip drive letter to get Sia path if (siaPath.length()) { // If cache file already exists and is a directory, requested file operation isn't valid DWORD attribs = ::GetFileAttributes(cacheFilePath.c_str()); if ((attribs != INVALID_FILE_ATTRIBUTES) && (attribs & FILE_ATTRIBUTE_DIRECTORY)) { ret = STATUS_OBJECT_NAME_COLLISION; } else { bool exists; if (ApiSuccess(_siaApi->GetRenter()->FileExists(siaPath, exists))) { // 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; case CREATE_NEW: { if (exists) { ret = STATUS_OBJECT_NAME_EXISTS; } else { isCreateOp = true; } } break; case OPEN_ALWAYS: { if (!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; default: // Nothing to do break; } if (ret == STATUS_SUCCESS) { if (isReplaceOp) { // Since this is a request to replace an existing file, make sure cache is deleted first. // If file isn't cached, delete from Sia only if (!::PathFileExists(cacheFilePath.c_str()) || ::DeleteFile(cacheFilePath.c_str())) { if (!ApiSuccess(_uploadManager->Remove(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 (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 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_OBJECT_NAME_INVALID; } } } return ret; } static NTSTATUS DOKAN_CALLBACK Sia_FindFiles(LPCWSTR FileName, PFillFindData FillFindData, PDOKAN_FILE_INFO DokanFileInfo) { std::lock_guard l(_dokanMutex); auto siaFileTree = _siaFileTree; if (siaFileTree) { String siaQuery = CSiaApi::FormatToSiaPath(::PathSkipRoot(FileName)); String cachePath; String rootPath = siaQuery; if (wcscmp(FileName, L"\\") == 0) { cachePath = GetCacheLocation(); siaQuery += L"/*.*"; } else { cachePath = StdConstructPath(GetCacheLocation(), &FileName[1]); if (::GetFileAttributes(&cachePath[0]) & FILE_ATTRIBUTE_DIRECTORY) { siaQuery += L"/*.*"; } else { rootPath = cachePath; ::PathRemoveFileSpec(&rootPath[0]); rootPath = CSiaApi::FormatToSiaPath(rootPath); } } auto dirList = siaFileTree->QueryDirectories(rootPath); for (auto& dir : dirList) { WIN32_FIND_DATA fd = { 0 }; wcscpy_s(fd.cFileName, dir.c_str()); fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; FillFindData(&fd, DokanFileInfo); // Create cache sub-folder String subCachePath = StdConstructPath(cachePath, dir); if (!::PathIsDirectory(subCachePath.c_str())) { ::CreateDirectory(subCachePath.c_str(), nullptr); } } auto fileList = siaFileTree->Query(siaQuery); for (auto& file : fileList) { WIN32_FIND_DATA fd = { 0 }; wcscpy_s(fd.cFileName, ::PathFindFileName(file->GetSiaPath().c_str())); LARGE_INTEGER li = { 0 }; li.QuadPart = file->GetFileSize(); fd.nFileSizeHigh = li.HighPart; fd.nFileSizeLow = li.LowPart; fd.dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NORMAL; FillFindData(&fd, DokanFileInfo); } } return STATUS_SUCCESS; } static void DOKAN_CALLBACK Sia_CloseFile(LPCWSTR FileName, PDOKAN_FILE_INFO DokanFileInfo) { std::lock_guard l(_dokanMutex); ULONG64 id = DokanFileInfo->Context; if (id) { HANDLE handle = reinterpret_cast(DokanFileInfo->Context); // Ignore directories in Sia - reside in cache only. if (DokanFileInfo->IsDirectory) { ::CloseHandle(handle); } else { LARGE_INTEGER li = { 0 }; BOOL sizeOk = ::GetFileSizeEx(handle, &li); ::CloseHandle(handle); // TODO If it's not ok, why and what to do? if (sizeOk) { QueueUploadIfChanged(id, li.QuadPart); } _openFileMap.erase(id); } DokanFileInfo->Context = 0; } } static NTSTATUS DOKAN_CALLBACK Sia_GetFileInformation(LPCWSTR FileName, LPBY_HANDLE_FILE_INFORMATION HandleFileInformation, PDOKAN_FILE_INFO DokanFileInfo) { HANDLE handle = reinterpret_cast(DokanFileInfo->Context); BOOL opened = FALSE; NTSTATUS ret = STATUS_SUCCESS; String cachePath = GetCacheLocation(); if (wcscmp(FileName, L"\\") != 0) { cachePath.resize(MAX_PATH + 1); ::PathAppend(&cachePath[0], FileName); cachePath = cachePath.c_str(); } if (!handle || (handle == INVALID_HANDLE_VALUE)) { handle = ::CreateFile(&cachePath[0], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); if (handle == INVALID_HANDLE_VALUE) { ret = DokanNtStatusFromWin32(GetLastError()); } else { opened = TRUE; } } if (ret == STATUS_SUCCESS) { if (!::GetFileInformationByHandle(handle, HandleFileInformation)) { // FileName is a root directory // in this case, FindFirstFile can't get directory information if (wcslen(FileName) == 1) { HandleFileInformation->dwFileAttributes = ::GetFileAttributes(&cachePath[0]); } else { WIN32_FIND_DATAW find = { 0 }; HANDLE findHandle = ::FindFirstFile(&cachePath[0], &find); if (findHandle == INVALID_HANDLE_VALUE) { // TODO Not Cached, so manual attributes ret = DokanNtStatusFromWin32(::GetLastError()); } else { HandleFileInformation->dwFileAttributes = find.dwFileAttributes; HandleFileInformation->ftCreationTime = find.ftCreationTime; HandleFileInformation->ftLastAccessTime = find.ftLastAccessTime; HandleFileInformation->ftLastWriteTime = find.ftLastWriteTime; HandleFileInformation->nFileSizeHigh = find.nFileSizeHigh; HandleFileInformation->nFileSizeLow = find.nFileSizeLow; FindClose(findHandle); } } } if (opened) { CloseHandle(handle); } } return ret; } static NTSTATUS DOKAN_CALLBACK Sia_Mounted(PDOKAN_FILE_INFO DokanFileInfo) { std::lock_guard l(_dokanMutex); StartFileListThread(); return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_Unmounted(PDOKAN_FILE_INFO DokanFileInfo) { std::lock_guard l(_dokanMutex); StopFileListThread(); return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_GetDiskFreeSpaceW( PULONGLONG FreeBytesAvailable, PULONGLONG TotalNumberOfBytes, PULONGLONG TotalNumberOfFreeBytes, PDOKAN_FILE_INFO DokanFileInfo) { UNREFERENCED_PARAMETER(DokanFileInfo); // TODO Implement this correctly *FreeBytesAvailable = static_cast(512 * 1024 * 1024); *TotalNumberOfBytes = 9223372036854775807; *TotalNumberOfFreeBytes = 9223372036854775807; return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_GetVolumeInformation( LPWSTR VolumeNameBuffer, DWORD VolumeNameSize, LPDWORD VolumeSerialNumber, LPDWORD MaximumComponentLength, LPDWORD FileSystemFlags, LPWSTR FileSystemNameBuffer, DWORD FileSystemNameSize, PDOKAN_FILE_INFO DokanFileInfo) { UNREFERENCED_PARAMETER(DokanFileInfo); wcscpy_s(VolumeNameBuffer, VolumeNameSize, L"SiaDrive"); *VolumeSerialNumber = 0x19831116; *MaximumComponentLength = 256; *FileSystemFlags = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | FILE_SUPPORTS_REMOTE_STORAGE | FILE_UNICODE_ON_DISK | FILE_PERSISTENT_ACLS; // File system name could be anything up to 10 characters. // But Windows check few feature availability based on file system name. // For this, it is recommended to set NTFS or FAT here. wcscpy_s(FileSystemNameBuffer, FileSystemNameSize, L"FAT"); return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_ReadFile(LPCWSTR FileName, LPVOID Buffer, DWORD BufferLength, LPDWORD ReadLength, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo) { String filePath = StdConstructPath(GetCacheLocation(), FileName); HANDLE handle = reinterpret_cast(DokanFileInfo->Context); ULONG offset = static_cast(Offset); BOOL opened = FALSE; if (!handle || (handle == INVALID_HANDLE_VALUE)) { handle = ::CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); if (handle == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); return DokanNtStatusFromWin32(error); } opened = TRUE; } LARGE_INTEGER distanceToMove; distanceToMove.QuadPart = Offset; if (!::SetFilePointerEx(handle, distanceToMove, nullptr, FILE_BEGIN)) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } if (!ReadFile(handle, Buffer, BufferLength, ReadLength, nullptr)) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } else { } if (opened) CloseHandle(handle); return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_WriteFile(LPCWSTR FileName, LPCVOID Buffer, DWORD NumberOfBytesToWrite, LPDWORD NumberOfBytesWritten, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo) { String filePath = StdConstructPath(GetCacheLocation(), FileName); HANDLE handle = reinterpret_cast(DokanFileInfo->Context); BOOL opened = FALSE; // reopen the file if (!handle || (handle == INVALID_HANDLE_VALUE)) { // TODO Get from cache if not found handle = ::CreateFile(filePath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); if (handle == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); return DokanNtStatusFromWin32(error); } opened = TRUE; } UINT64 fileSize = 0; DWORD fileSizeLow = 0; DWORD fileSizeHigh = 0; fileSizeLow = ::GetFileSize(handle, &fileSizeHigh); if (fileSizeLow == INVALID_FILE_SIZE) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } fileSize = (static_cast(fileSizeHigh) << 32) | fileSizeLow; LARGE_INTEGER distanceToMove; if (DokanFileInfo->WriteToEndOfFile) { LARGE_INTEGER z; z.QuadPart = 0; if (!::SetFilePointerEx(handle, z, nullptr, FILE_END)) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } } else { // Paging IO cannot write after allocate file size. if (DokanFileInfo->PagingIo) { if (static_cast(Offset) >= fileSize) { *NumberOfBytesWritten = 0; if (opened) CloseHandle(handle); return STATUS_SUCCESS; } if ((static_cast(Offset) + NumberOfBytesToWrite) > fileSize) { UINT64 bytes = fileSize - Offset; if (bytes >> 32) { NumberOfBytesToWrite = static_cast(bytes & 0xFFFFFFFFUL); } else { NumberOfBytesToWrite = static_cast(bytes); } } } if (static_cast(Offset) > fileSize) { // In the mirror sample helperZeroFileData is not necessary. NTFS will // zero a hole. // But if user's file system is different from NTFS( or other Windows's // file systems ) then users will have to zero the hole themselves. } distanceToMove.QuadPart = Offset; if (!::SetFilePointerEx(handle, distanceToMove, nullptr, FILE_BEGIN)) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } } if (!::WriteFile(handle, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, nullptr)) { DWORD error = GetLastError(); if (opened) CloseHandle(handle); return DokanNtStatusFromWin32(error); } else { } // close the file when it is reopened if (opened) CloseHandle(handle); return STATUS_SUCCESS; } static NTSTATUS DOKAN_CALLBACK Sia_SetEndOfFile(LPCWSTR FileName, LONGLONG ByteOffset, PDOKAN_FILE_INFO DokanFileInfo) { String filePath = StdConstructPath(GetCacheLocation(), FileName); HANDLE handle; LARGE_INTEGER offset; handle = reinterpret_cast(DokanFileInfo->Context); if (!handle || handle == INVALID_HANDLE_VALUE) { return STATUS_INVALID_HANDLE; } offset.QuadPart = ByteOffset; if (!::SetFilePointerEx(handle, offset, nullptr, FILE_BEGIN)) { DWORD error = GetLastError(); return DokanNtStatusFromWin32(error); } if (!::SetEndOfFile(handle)) { DWORD error = GetLastError(); return DokanNtStatusFromWin32(error); } return STATUS_SUCCESS; } static void DOKAN_CALLBACK Sia_Cleanup(LPCWSTR FileName, PDOKAN_FILE_INFO DokanFileInfo) { String filePath = StdConstructPath(GetCacheLocation(), FileName); if (DokanFileInfo->Context) { ::CloseHandle(reinterpret_cast(DokanFileInfo->Context)); _openFileMap.erase(DokanFileInfo->Context); DokanFileInfo->Context = 0; } else { } if (DokanFileInfo->DeleteOnClose) { // Should already be deleted by CloseHandle // if open with FILE_FLAG_DELETE_ON_CLOSE if (DokanFileInfo->IsDirectory) { if (::RemoveDirectory(filePath.c_str())) { } else { } } else { if (::DeleteFile(filePath.c_str()) == 0) { } else { } } } } public: static void Initialize(CSiaApi* siaApi, CSiaDriveConfig* siaDriveConfig) { _siaApi = siaApi; _siaDriveConfig = siaDriveConfig; // May spend a little wait time here while files are cleaned-up and re-added to queue _uploadManager.reset(new CUploadManager(CSiaCurl(siaApi->GetHostConfig()), siaDriveConfig)); _dokanOps.Cleanup = Sia_Cleanup; _dokanOps.CloseFile = Sia_CloseFile; _dokanOps.DeleteDirectory = nullptr; _dokanOps.DeleteFileW = nullptr; _dokanOps.FindFiles = Sia_FindFiles; _dokanOps.FindFilesWithPattern = nullptr; _dokanOps.FindStreams = nullptr; _dokanOps.FlushFileBuffers = nullptr; _dokanOps.GetDiskFreeSpaceW = Sia_GetDiskFreeSpaceW; _dokanOps.GetFileInformation = Sia_GetFileInformation; _dokanOps.GetFileSecurityW = nullptr; _dokanOps.GetVolumeInformationW = Sia_GetVolumeInformation; _dokanOps.LockFile = nullptr; _dokanOps.Mounted = Sia_Mounted; _dokanOps.MoveFileW = nullptr; _dokanOps.ReadFile = Sia_ReadFile; _dokanOps.SetAllocationSize = nullptr; _dokanOps.SetEndOfFile = Sia_SetEndOfFile; _dokanOps.SetFileAttributesW = nullptr; _dokanOps.SetFileSecurityW = nullptr; _dokanOps.SetFileTime = nullptr; _dokanOps.UnlockFile = nullptr; _dokanOps.Unmounted = Sia_Unmounted; _dokanOps.WriteFile = Sia_WriteFile; _dokanOps.ZwCreateFile = Sia_ZwCreateFile; ZeroMemory(&_dokanOptions, sizeof(DOKAN_OPTIONS)); _dokanOptions.Version = DOKAN_VERSION; _dokanOptions.ThreadCount = 0; // use default _dokanOptions.Options = DOKAN_OPTION_DEBUG; } static void Mount(const wchar_t& driveLetter, const String& cacheLocation) { if (_siaApi && !_mountThread) { _cacheLocation = cacheLocation; wchar_t tmp[] = { driveLetter, ':', '\\', 0 }; _mountPoint = tmp; _mountThread.reset(new std::thread([&]() { _dokanOptions.MountPoint = _mountPoint.c_str(); _mountStatus = DokanMain(&_dokanOptions, &_dokanOps); OutputDebugString(std::to_wstring(_mountStatus).c_str()); })); } } static void Unmount() { if (_mountThread) { DokanRemoveMountPoint(_mountPoint.c_str()); _mountThread->join(); _mountThread.reset(nullptr); _mountPoint.clear(); } } static void Shutdown() { StopFileListThread(); Unmount(); _uploadManager.reset(nullptr); _siaApi = nullptr; _siaDriveConfig = nullptr; ZeroMemory(&_dokanOps, sizeof(_dokanOps)); ZeroMemory(&_dokanOptions, sizeof(_dokanOptions)); } static std::mutex& GetMutex() { return _dokanMutex; } static bool IsInitialized() { return _siaApi != nullptr; } }; // Static member variables std::mutex DokanImpl::_dokanMutex; CSiaApi* DokanImpl::_siaApi = nullptr; CSiaDriveConfig* DokanImpl::_siaDriveConfig = nullptr; std::unique_ptr DokanImpl::_uploadManager; DOKAN_OPERATIONS DokanImpl::_dokanOps; DOKAN_OPTIONS DokanImpl::_dokanOptions; String DokanImpl::_cacheLocation; bool DokanImpl::_fileListStopRequested; CSiaFileTreePtr DokanImpl::_siaFileTree; std::mutex DokanImpl::_fileTreeMutex; std::unique_ptr DokanImpl::_fileListThread; std::unordered_map DokanImpl::_openFileMap; std::unique_ptr DokanImpl::_mountThread; NTSTATUS DokanImpl::_mountStatus = STATUS_SUCCESS; String DokanImpl::_mountPoint; CSiaDokanDrive::CSiaDokanDrive(CSiaApi& siaApi, CSiaDriveConfig* siaDriveConfig) : _siaApi(siaApi), _siaDriveConfig(siaDriveConfig), _Mounted(false) { std::lock_guard l(DokanImpl::GetMutex()); if (DokanImpl::IsInitialized()) throw SiaDokanDriveException("Sia drive has already been activated"); DokanImpl::Initialize(&_siaApi, _siaDriveConfig); } CSiaDokanDrive::~CSiaDokanDrive() { std::lock_guard l(DokanImpl::GetMutex()); DokanImpl::Shutdown(); } void CSiaDokanDrive::Mount(const wchar_t& driveLetter, const String& cacheLocation, const std::uint64_t& maxCacheSizeBytes) { std::lock_guard l(DokanImpl::GetMutex()); DokanImpl::Mount(driveLetter, cacheLocation); } void CSiaDokanDrive::Unmount(const bool& clearCache) { std::lock_guard l(DokanImpl::GetMutex()); // TODO When files are open, need to prompt and prevent shutdown? DokanImpl::Unmount(); } void CSiaDokanDrive::ClearCache() { std::lock_guard l(DokanImpl::GetMutex()); }