1
0
This repository has been archived on 2025-07-27. You can view files and clone it, but cannot push or open issues or pull requests.
Files
siadrive/src/siadrive_dokan_api/siadokandrive.cpp
Scott E. Graves f6669f7f18 Dokan logging
2017-03-24 16:54:52 -05:00

979 lines
26 KiB
C++

#include <siadokandrive.h>
#include <filesystem>
#include <uploadmanager.h>
#include <dokan.h>
#include <filepath.h>
using namespace Sia::Api;
using namespace Sia::Api::Dokan;
// TODO Handle paths greater than MAX_PATH!!
// 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 SIADRIVE_DOKAN_EXPORTABLE DokanImpl
{
private:
typedef struct
{
FilePath SiaPath;
FilePath CacheFilePath;
bool ReadOnly;
} OpenFileInfo;
private:
static std::mutex _dokanMutex;
static CSiaApi* _siaApi;
static CSiaDriveConfig* _siaDriveConfig;
static std::unique_ptr<CUploadManager> _uploadManager;
static DOKAN_OPERATIONS _dokanOps;
static DOKAN_OPTIONS _dokanOptions;
static FilePath _cacheLocation;
static std::unordered_map<ULONG64, OpenFileInfo> _openFileMap;
static std::unique_ptr<std::thread> _fileListThread;
static bool _fileListStopRequested;
static CSiaFileTreePtr _siaFileTree;
static std::mutex _fileTreeMutex;
static std::unique_ptr<std::thread> _mountThread;
static NTSTATUS _mountStatus;
static SString _mountPoint;
private:
inline static const FilePath& GetCacheLocation()
{
return _cacheLocation;
}
static bool AddFileToCache(const SString& siaPath, const SString& cacheLocation)
{
bool ret = false;
FilePath tempFilePath = FilePath::GetTempDirectory();
tempFilePath.Append(GenerateSha256(siaPath) + ".siatmp");
// TODO Check cache size is large enough to hold new file
ret = ApiSuccess(_siaApi->GetRenter()->DownloadFile(siaPath, tempFilePath));
if (ret)
{
FilePath src(tempFilePath);
FilePath dest(GetCacheLocation(), siaPath);
ret = src.MoveFile(dest);
if (!ret)
{
src.DeleteFile();
}
}
return ret;
}
static void QueueUploadIfChanged(const ULONG64& id, const std::uint64_t& size)
{
return;
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<std::mutex> 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)
{
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 (FilePath(FileName).IsUNC())
{
ret = STATUS_ILLEGAL_ELEMENT_ADDRESS;
}
else
{
// When filePath is a directory, needs to change the flag so that the file can
// be opened.
FilePath cacheFilePath(GetCacheLocation(), &FileName[1]);
DWORD fileAttr = ::GetFileAttributes(&cacheFilePath[0]);
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 (!cacheFilePath.CreateDirectory())
{
DWORD error = GetLastError();
ret = DokanNtStatusFromWin32(error);
}
}
else if (creationDisposition == OPEN_ALWAYS)
{
if (cacheFilePath.CreateDirectory())
{
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[0], 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<ULONG64>(handle); // save the file handle in Context
}
}
}
else // File (cache and/or Sia operation)
{
// Formulate Sia path and cache path
SString siaPath = CSiaApi::FormatToSiaPath(FilePath(FileName).SkipRoot()); // 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[0]);
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 (!cacheFilePath.IsFile() || cacheFilePath.DeleteFile())
{
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 && !cacheFilePath.IsFile())
{
if (!AddFileToCache(siaPath, cacheFilePath))
{
ret = STATUS_INVALID_SERVER_STATE;
}
}
if (ret == STATUS_SUCCESS)
{
// Create file as specified
HANDLE handle = ::CreateFile(
&cacheFilePath[0],
genericDesiredAccess,
ShareAccess,
&securityAttrib,
creationDisposition,
fileAttributesAndFlags,
nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
ret = DokanNtStatusFromWin32(GetLastError());
}
else
{
DokanFileInfo->Context = reinterpret_cast<ULONG64>(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;
std::lock_guard<std::mutex> l(_dokanMutex);
_openFileMap.insert({ DokanFileInfo->Context, ofi });
}
}
}
}
}
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)
{
auto siaFileTree = _siaFileTree;
if (siaFileTree)
{
SString siaQuery = CSiaApi::FormatToSiaPath(FilePath(FileName).SkipRoot());
FilePath cachePath = GetCacheLocation();;
FilePath rootPath = siaQuery;
if (FilePath::DirSep == FileName)
{
siaQuery += L"/*.*";
}
else
{
cachePath.Append(&FileName[1]);
if (cachePath.IsDirectory())
{
siaQuery += L"/*.*";
}
else
{
rootPath = cachePath;
rootPath = CSiaApi::FormatToSiaPath(rootPath.RemoveFileName());
}
}
auto dirList = siaFileTree->QueryDirectories(rootPath);
for (auto& dir : dirList)
{
WIN32_FIND_DATA fd = { 0 };
wcscpy_s(fd.cFileName, dir.str().c_str());
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
FillFindData(&fd, DokanFileInfo);
// Create cache sub-folder
FilePath subCachePath(cachePath, dir);
if (!subCachePath.IsDirectory())
{
subCachePath.CreateDirectory();
}
}
auto fileList = siaFileTree->Query(siaQuery);
for (auto& file : fileList)
{
WIN32_FIND_DATA fd = { 0 };
wcscpy_s(fd.cFileName, ::PathFindFileName(file->GetSiaPath().str().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)
{
ULONG64 id = DokanFileInfo->Context;
if (id)
{
HANDLE handle = reinterpret_cast<HANDLE>(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);
}
std::lock_guard<std::mutex> l(_dokanMutex);
_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<HANDLE>(DokanFileInfo->Context);
BOOL opened = FALSE;
NTSTATUS ret = STATUS_SUCCESS;
FilePath cachePath = GetCacheLocation();
if (FilePath::DirSep == FileName)
{
cachePath.Append(FileName);
}
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 = STATUS_SUCCESS;// 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)
{
// 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));
StartFileListThread();
return STATUS_SUCCESS;
}
static NTSTATUS DOKAN_CALLBACK Sia_Unmounted(PDOKAN_FILE_INFO DokanFileInfo)
{
_uploadManager.reset(nullptr);
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<ULONGLONG>(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)
{
FilePath filePath(GetCacheLocation(), FileName);
HANDLE handle = reinterpret_cast<HANDLE>(DokanFileInfo->Context);
BOOL opened = FALSE;
if (!handle || (handle == INVALID_HANDLE_VALUE))
{
handle = ::CreateFile(&filePath[0], 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)
{
FilePath filePath(GetCacheLocation(), FileName);
HANDLE handle = reinterpret_cast<HANDLE>(DokanFileInfo->Context);
BOOL opened = FALSE;
// reopen the file
if (!handle || (handle == INVALID_HANDLE_VALUE))
{
// TODO Get from cache if not found
handle = ::CreateFile(&filePath[0], GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
DWORD error = GetLastError();
return DokanNtStatusFromWin32(error);
}
opened = TRUE;
}
DWORD fileSizeHigh = 0;
DWORD fileSizeLow = ::GetFileSize(handle, &fileSizeHigh);
if (fileSizeLow == INVALID_FILE_SIZE)
{
DWORD error = GetLastError();
if (opened)
CloseHandle(handle);
return DokanNtStatusFromWin32(error);
}
UINT64 fileSize = (static_cast<UINT64>(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<UINT64>(Offset) >= fileSize)
{
*NumberOfBytesWritten = 0;
if (opened)
CloseHandle(handle);
return STATUS_SUCCESS;
}
if ((static_cast<UINT64>(Offset) + NumberOfBytesToWrite) > fileSize)
{
UINT64 bytes = fileSize - Offset;
if (bytes >> 32)
{
NumberOfBytesToWrite = static_cast<DWORD>(bytes & 0xFFFFFFFFUL);
}
else
{
NumberOfBytesToWrite = static_cast<DWORD>(bytes);
}
}
}
if (static_cast<UINT64>(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)
{
HANDLE handle;
LARGE_INTEGER offset;
handle = reinterpret_cast<HANDLE>(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)
{
FilePath filePath(GetCacheLocation(), FileName);
if (DokanFileInfo->Context)
{
::CloseHandle(reinterpret_cast<HANDLE>(DokanFileInfo->Context));
{
std::lock_guard<std::mutex> l(_dokanMutex);
_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 (filePath.RemoveDirectory())
{
}
else
{
}
}
else
{
if (filePath.DeleteFile())
{
}
else
{
}
}
}
}
static NTSTATUS DOKAN_CALLBACK Sia_FlushFileBuffers(LPCWSTR FileName, PDOKAN_FILE_INFO DokanFileInfo)
{
HANDLE handle = reinterpret_cast<HANDLE>(DokanFileInfo->Context);
if (!handle || (handle == INVALID_HANDLE_VALUE))
{
return STATUS_SUCCESS;
}
if (::FlushFileBuffers(handle))
{
return STATUS_SUCCESS;
}
else
{
DWORD error = GetLastError();
return DokanNtStatusFromWin32(error);
}
}
public:
static void Initialize(CSiaApi* siaApi, CSiaDriveConfig* siaDriveConfig)
{
_siaApi = siaApi;
_siaDriveConfig = 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 = Sia_FlushFileBuffers;
_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
#ifdef _DEBUG
_dokanOptions.Options = DOKAN_OPTION_DEBUG | DOKAN_OPTION_DEBUG_LOG;
#endif
}
static void Mount(const wchar_t& driveLetter, const SString& cacheLocation)
{
if (_siaApi && !_mountThread)
{
_cacheLocation = cacheLocation;
wchar_t tmp[] = { driveLetter, ':', '\\', 0 };
_mountPoint = tmp;
_mountThread.reset(new std::thread([&]()
{
_dokanOptions.MountPoint = _mountPoint.ToUpper().str().c_str();
_mountStatus = DokanMain(&_dokanOptions, &_dokanOps);
}));
}
}
static void Unmount()
{
if (_mountThread)
{
while (!DokanRemoveMountPoint(&_mountPoint[0]))
::Sleep(1000);
// HACK - DokanMain() never returns
::TerminateThread(_mountThread->native_handle(), -1);
_mountThread->join();
_mountThread.reset(nullptr);
_mountPoint = "";
}
}
static void Shutdown()
{
StopFileListThread();
Unmount();
_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<CUploadManager> DokanImpl::_uploadManager;
DOKAN_OPERATIONS DokanImpl::_dokanOps;
DOKAN_OPTIONS DokanImpl::_dokanOptions;
FilePath DokanImpl::_cacheLocation;
bool DokanImpl::_fileListStopRequested;
CSiaFileTreePtr DokanImpl::_siaFileTree;
std::mutex DokanImpl::_fileTreeMutex;
std::unique_ptr<std::thread> DokanImpl::_fileListThread;
std::unordered_map<ULONG64, DokanImpl::OpenFileInfo> DokanImpl::_openFileMap;
std::unique_ptr<std::thread> DokanImpl::_mountThread;
NTSTATUS DokanImpl::_mountStatus = STATUS_SUCCESS;
SString DokanImpl::_mountPoint;
CSiaDokanDrive::CSiaDokanDrive(CSiaApi& siaApi, CSiaDriveConfig* siaDriveConfig) :
_siaApi(siaApi),
_siaDriveConfig(siaDriveConfig),
_Mounted(false)
{
std::lock_guard<std::mutex> l(DokanImpl::GetMutex());
if (DokanImpl::IsInitialized())
throw SiaDokanDriveException("Sia drive has already been activated");
DokanImpl::Initialize(&_siaApi, _siaDriveConfig);
}
CSiaDokanDrive::~CSiaDokanDrive()
{
std::lock_guard<std::mutex> l(DokanImpl::GetMutex());
DokanImpl::Shutdown();
}
void CSiaDokanDrive::Mount(const wchar_t& driveLetter, const SString& cacheLocation, const std::uint64_t& maxCacheSizeBytes)
{
std::lock_guard<std::mutex> l(DokanImpl::GetMutex());
DokanImpl::Mount(driveLetter, cacheLocation);
}
void CSiaDokanDrive::Unmount(const bool& clearCache)
{
std::lock_guard<std::mutex> l(DokanImpl::GetMutex());
// TODO When files are open, need to prompt and prevent shutdown?
DokanImpl::Unmount();
}
void CSiaDokanDrive::ClearCache()
{
std::lock_guard<std::mutex> l(DokanImpl::GetMutex());
}