#include <siadokandrive.h>
#include <filesystem>
#include <uploadmanager.h>
#include <dokan.h>
#include <filepath.h>
#include <siadriveconfig.h>
#include <siacommon.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
	{
    HANDLE FileHandle;
		SString SiaPath;
    FilePath CacheFilePath;
    bool Dummy;
		bool Changed;
    ACCESS_MASK DesiredAccess;
    ULONG ShareMode;
    SECURITY_ATTRIBUTES SecurityAttrib;
    ULONG CreateDisp;
    DWORD AttributesAndFlags;
    PDOKAN_FILE_INFO DokanFileInfo;
	} OpenFileInfo;

  class DownloadToCacheBegin :
    public CEvent
  {
  public:
    DownloadToCacheBegin(const OpenFileInfo& openFileInfo, const SString& tempFilePath) :
      _openFileInfo(openFileInfo),
      _tempFilePath(tempFilePath)
    {

    }

  public:
    virtual ~DownloadToCacheBegin() {}

  private:
    const OpenFileInfo _openFileInfo;
    const SString _tempFilePath;

  public:
    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() +
        "|FP|" + _openFileInfo.CacheFilePath +
        "|SP|" + _openFileInfo.SiaPath +
        "|TP|" + _tempFilePath;
    }

    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DownloadToCacheBegin(_openFileInfo, _tempFilePath));
    }

    virtual SString GetEventName() const override
    {
      return "DownloadToCacheBegin";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", static_cast<SString>(_openFileInfo.CacheFilePath) },
        { "sia_path", _openFileInfo.SiaPath},
        { "temp_file_path", _tempFilePath}
      };
    }
  };

  class DownloadToCacheEnd :
    public CEvent
  {
  public:
    DownloadToCacheEnd(const OpenFileInfo& openFileInfo, const SString& tempFilePath, const bool& result) :
      _openFileInfo(openFileInfo),
      _tempFilePath(tempFilePath),
      _result(result)
    {

    }

  public:
    virtual ~DownloadToCacheEnd() {}

  private:
    const OpenFileInfo _openFileInfo;
    const SString _tempFilePath;
    const bool _result;

  public:
    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|FP|" + static_cast<SString>(_openFileInfo.CacheFilePath) +
        "|SP|" + _openFileInfo.SiaPath +
        "|TP|" + _tempFilePath +
        "|RES|" + SString::FromBool(_result);
    }

    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DownloadToCacheEnd(_openFileInfo, _tempFilePath, _result));
    }

    virtual SString GetEventName() const override
    {
      return "DownloadToCacheEnd";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", static_cast<SString>(_openFileInfo.CacheFilePath) },
        { "sia_path", _openFileInfo.SiaPath },
        { "temp_file_path", _tempFilePath },
        { "result", _result}
      };
    }
  };

  class MoveTempToCacheResult :
    public CEvent
  {
  public:
    MoveTempToCacheResult(const OpenFileInfo& openFileInfo, const SString& tempFilePath, const bool& result) :
      _openFileInfo(openFileInfo),
      _tempFilePath(tempFilePath),
      _result(result)
    {

    }

  public:
    virtual ~MoveTempToCacheResult() {}

  private:
    const OpenFileInfo _openFileInfo;
    const SString _tempFilePath;
    const bool _result;

  public:
    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|FP|" + static_cast<SString>(_openFileInfo.CacheFilePath) +
        "|SP|" + _openFileInfo.SiaPath +
        "|TP|" + _tempFilePath +
        "|RES|" + SString::FromBool(_result);
    }

    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new MoveTempToCacheResult(_openFileInfo, _tempFilePath, _result));
    }

    virtual SString GetEventName() const override
    {
      return "MoveTempToCacheResult";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", static_cast<SString>(_openFileInfo.CacheFilePath) },
        { "sia_path", _openFileInfo.SiaPath },
        { "temp_file_path", _tempFilePath },
        { "result", _result }
      };
    }
  };

  class AddToCacheComplete :
    public CEvent
  {
  public:
    AddToCacheComplete(const OpenFileInfo& openFileInfo, const bool& result) :
      _openFileInfo(openFileInfo),
      _result(result)
    {

    }

  public:
    virtual ~AddToCacheComplete() {}

  private:
    const OpenFileInfo _openFileInfo;
    const bool _result;

  public:
    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|FP|" + static_cast<SString>(_openFileInfo.CacheFilePath) +
        "|SP|" + _openFileInfo.SiaPath +
        "|RES|" + SString::FromBool(_result);
    }

    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new AddToCacheComplete(_openFileInfo, _result));
    }

    virtual SString GetEventName() const override
    {
      return "AddToCacheComplete";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", static_cast<SString>(_openFileInfo.CacheFilePath) },
        { "sia_path", _openFileInfo.SiaPath },
        { "result", _result }
      };
    }
  };

  class DriveMountEnded :
    public CEvent
  {
  public:
    DriveMountEnded(const SString mountLocation, const NTSTATUS& result) :
      _mountLocation(mountLocation),
      _result(result)
    {
    }

  public:
    virtual ~DriveMountEnded()
    {
    }

  private:
    const SString _mountLocation;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DriveMountEnded(_mountLocation, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|LOC|" + _mountLocation + 
        "|RES|" + SString::FromInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DriveMountEnded";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "mount_location", _mountLocation },
        { "result", _result }
      };
    }
  };

  class DriveUnMounted :
    public CEvent
  {
  public:
    DriveUnMounted(const SString& mountLocation) :
      _mountLocation(mountLocation)
    {

    }

  public:
    virtual ~DriveUnMounted()
    {
    }

  private:
    const SString _mountLocation;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DriveUnMounted(_mountLocation));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|LOC|" + _mountLocation;
    }

    virtual SString GetEventName() const override
    {
      return "DriveUnMounted";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "mount_location", _mountLocation }
      };
    }
  };

  class DriveMounted :
    public CEvent
  {
  public:
    DriveMounted(const SString& mountLocation) :
      _mountLocation(mountLocation)
    {

    }

  public:
    virtual ~DriveMounted()
    {
    }

  private:
    const SString _mountLocation;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DriveMounted(_mountLocation));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|LOC|" + _mountLocation;
    }

    virtual SString GetEventName() const override
    {
      return "DriveMounted";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "mount_location", _mountLocation }
      };
    }
  };

  class DokanCreateFile :
    public CEvent
  {
  public:
    DokanCreateFile(const SString& filePath, const DWORD& fileAttributesAndFlags, const DWORD& creationDisposition, const ACCESS_MASK& genericDesiredAccess, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _filePath(filePath),
      _fileAttributesAndFlags(fileAttributesAndFlags),
      _creationDisposition(creationDisposition),
      _genericDesiredAccess(genericDesiredAccess),
      _result(result)
    {

    }

  public:
    virtual ~DokanCreateFile()
    {
    }

  private:
    const SString _filePath;
    const DWORD _fileAttributesAndFlags;
    const DWORD _creationDisposition;
    const ACCESS_MASK _genericDesiredAccess;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanCreateFile(_filePath, _fileAttributesAndFlags, _creationDisposition, _genericDesiredAccess, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|FILE|" + _filePath +
        "|ATTR|" + SString::FromUInt32(_fileAttributesAndFlags) +
        "|DISP|" + SString::FromUInt32(_creationDisposition) +
        "|MASK|" + SString::FromUInt32(_genericDesiredAccess) +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanCreateFile";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _filePath },
        { "attributes", _fileAttributesAndFlags},
        { "create_disposition", _creationDisposition},
        { "access_mask", _genericDesiredAccess},
        { "result", _result }
      };
    }
  };

  class DokanFindFiles :
    public CEvent
  {
  public:
    DokanFindFiles(const SString& cachePath, const SString& rootPath, const SString& siaQuery, const SString& findFile, const SString& fileName, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _rootPath(rootPath),
      _siaQuery(siaQuery),
      _findFile(findFile),
      _fileName(fileName),
      _result(result)
    {
    }

  public:
    virtual ~DokanFindFiles()
    {
    }

  private:
    const SString _cachePath;
    const SString _rootPath;
    const SString _siaQuery;
    const SString _findFile;
    const SString _fileName;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanFindFiles(_cachePath, _rootPath, _siaQuery, _findFile, _fileName, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() +
        "|PATH|" + _cachePath +
        "|ROOT|" + _rootPath +
        "|QUERY|" + _siaQuery +
        "|FIND|" + _findFile +
        "|FN|" + _fileName +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanFindFiles";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "root_path", _rootPath },
        { "sia_query", _siaQuery },
        { "find", _findFile },
        { "file_name", _fileName },
        { "result", _result }
      };
    }
  };

  class DokanCloseFile :
    public CEvent
  {
  public:
    DokanCloseFile(const SString& cachePath) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath)
    {
    }

  public:
    virtual ~DokanCloseFile()
    {
    }

  private:
    const SString _cachePath;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanCloseFile(_cachePath));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath;
    }

    virtual SString GetEventName() const override
    {
      return "DokanCloseFile";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath }
      };
    }
  };

  class DokanGetFileInformation :
    public CEvent
  {
  public:
    DokanGetFileInformation(const SString& cachePath, const SString& fileName, const bool& opened, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _fileName(fileName),
      _opened(opened),
      _result(result)
    {
    }

  public:
    virtual ~DokanGetFileInformation()
    {
    }

  private:
    const SString _cachePath;
    const SString _fileName;
    const bool _opened;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanGetFileInformation(_cachePath, _fileName, _opened, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|FN|" + _fileName +
        "|OPN|" + SString::FromBool(_opened) +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanGetFileInformation";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "file_name", _fileName },
        { "was_opened", _opened },
        { "result", _result }
      };
    }
  };

  class DokanReadFile :
    public CEvent
  {
  public:
    DokanReadFile(const SString& cachePath, const bool& opened, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _opened(opened),
      _result(result)
    {
    }

  public:
    virtual ~DokanReadFile()
    {
    }

  private:
    const SString _cachePath;
    const bool _opened;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanReadFile(_cachePath, _opened, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|OPN|" + SString::FromBool(_opened) +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanReadFile";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "opened", _opened },
        { "result", _result }
      };
    }
  };

  class DokanWriteFile :
    public CEvent
  {
  public:
    DokanWriteFile(const SString& cachePath, const bool& opened, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _opened(opened),
      _result(result)
    {
    }

  public:
    virtual ~DokanWriteFile()
    {
    }

  private:
    const SString _cachePath;
    const bool _opened;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanWriteFile(_cachePath, _opened, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|OPN|" + SString::FromBool(_opened) +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanWriteFile";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "opened", _opened },
        { "result", _result }
      };
    }
  };

  class DokanSetEndOfFile :
    public CEvent
  {
  public:
    DokanSetEndOfFile(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanSetEndOfFile()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanSetEndOfFile(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanSetEndOfFile";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanFlushFileBuffers :
    public CEvent
  {
  public:
    DokanFlushFileBuffers(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanFlushFileBuffers()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanFlushFileBuffers(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanFlushFileBuffers";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanDeleteDirectory :
    public CEvent
  {
  public:
    DokanDeleteDirectory(const SString& cachePath, const NTSTATUS& result) :
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanDeleteDirectory()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanDeleteDirectory(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanDeleteDirectory";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanDeleteFileW :
    public CEvent
  {
  public:
    DokanDeleteFileW(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanDeleteFileW()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanDeleteFileW(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt32(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanDeleteFileW";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanSetFileAttributesW :
    public CEvent
  {
  public:
    DokanSetFileAttributesW(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanSetFileAttributesW()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanSetFileAttributesW(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt64(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanSetFileAttributesW";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanGetFileAttributesW :
    public CEvent
  {
  public:
    DokanGetFileAttributesW(const SString& cachePath) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath)
    {
    }

  public:
    virtual ~DokanGetFileAttributesW()
    {
    }

  private:
    const SString _cachePath;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanGetFileAttributesW(_cachePath));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath;
    }

    virtual SString GetEventName() const override
    {
      return "DokanGetFileAttributesW";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath }
      };
    }
  };

  class DokanSetFileSecurityW :
    public CEvent
  {
  public:
    DokanSetFileSecurityW(const SString& cachePath) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath)
    {
    }

  public:
    virtual ~DokanSetFileSecurityW()
    {
    }

  private:
    const SString _cachePath;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanSetFileSecurityW(_cachePath));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath;
    }

    virtual SString GetEventName() const override
    {
      return "DokanSetFileSecurityW";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath }
      };
    }
  };

  class DokanSetFileTime :
    public CEvent
  {
  public:
    DokanSetFileTime(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanSetFileTime()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanSetFileTime(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt64(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanSetFileTime";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanSetAllocationSize :
    public CEvent
  {
  public:
    DokanSetAllocationSize(const SString& cachePath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _cachePath(cachePath),
      _result(result)
    {
    }

  public:
    virtual ~DokanSetAllocationSize()
    {
    }

  private:
    const SString _cachePath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanSetAllocationSize(_cachePath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() + 
        "|PATH|" + _cachePath +
        "|RES|" + SString::FromUInt64(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanSetAllocationSize";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "file_path", _cachePath },
        { "result", _result }
      };
    }
  };

  class DokanMoveFileW :
    public CEvent
  {
  public:
    DokanMoveFileW(const SString& srcPath, const SString& destPath, const NTSTATUS& result) :
      CEvent(EventLevel::Debug),
      _srcPath(srcPath),
      _destPath(destPath),
      _result(result)
    {
    }

  public:
    virtual ~DokanMoveFileW()
    {
    }

  private:
    const SString _srcPath;
    const SString _destPath;
    const NTSTATUS _result;

  public:
    virtual std::shared_ptr<CEvent> Clone() const override
    {
      return std::shared_ptr<CEvent>(new DokanMoveFileW(_srcPath, _destPath, _result));
    }

    virtual SString GetSingleLineMessage() const override
    {
      return GetEventName() +
        "|SRC|" + _srcPath +
        "|DEST|" + _destPath +
        "|RES|" + SString::FromUInt64(_result);
    }

    virtual SString GetEventName() const override
    {
      return "DokanMoveFileW";
    }

    virtual json GetEventJson() const override
    {
      return
      {
        { "event", GetEventName() },
        { "source_path", _srcPath },
        { "dest_path", _destPath },
        { "result", _result }
      };
    }
  };

private:
	static CSiaApi* _siaApi;
	static CSiaDriveConfig* _siaDriveConfig;
	static std::shared_ptr<CUploadManager> _uploadManager;
	static DOKAN_OPERATIONS _dokanOps;
	static DOKAN_OPTIONS _dokanOptions;
	static FilePath _cacheLocation;
	static HANDLE _fileListStopEvent;
	static std::mutex _openFileMutex;
	static std::unique_ptr<std::thread> _mountThread;
	static NTSTATUS _mountStatus;
	static SString _mountPoint;
  static std::vector<OpenFileInfo*> _openFiles;
  static std::mutex _downloadMutex;
  static std::vector<SString> _activeDownloads;

private:
	inline static const FilePath& GetCacheLocation()
	{
		return _cacheLocation;
	}

  inline static CSiaFileTreePtr GetFileTree()
	{
    CSiaFileTreePtr siaFileTree;
    _siaApi->GetRenter()->GetFileTree(siaFileTree);
    return siaFileTree;
	}

  template<typename T>
  static void SetCachedFileTime(const SString& filePath, T* fd)
  {
    WIN32_FIND_DATA find = { 0 };
    HANDLE findHandle = ::FindFirstFile(&filePath[0], &find);
    if (findHandle != INVALID_HANDLE_VALUE)
    {
      fd->ftCreationTime = find.ftCreationTime;
      fd->ftLastAccessTime = find.ftLastAccessTime;
      fd->ftLastWriteTime = find.ftLastWriteTime;

      ::FindClose(findHandle);
    }
  }

  static bool AddFileToCache(OpenFileInfo& openFileInfo, PDOKAN_FILE_INFO dokanFileInfo)
  {    
    bool found = true;
    bool wasFound = false;
    // Wait for active download to complete
    while (found)
    {
      {
        std::lock_guard<std::mutex> l(_downloadMutex);
        found = std::find(_activeDownloads.begin(), _activeDownloads.end(), openFileInfo.SiaPath) != _activeDownloads.end();
        wasFound = wasFound || found;
        if (!found && !wasFound)
        {
          // Add to active download tracking
          _activeDownloads.push_back(openFileInfo.SiaPath);
        }
      }

      if (found)
      {
        ::Sleep(10);
      }
    }

    bool ret = true;
    // Only download if necessary
    if (openFileInfo.Dummy && !wasFound)
    {
      FilePath tempFilePath = FilePath::GetTempDirectory();
      tempFilePath.Append(GenerateSha256(openFileInfo.SiaPath) + ".siatmp");

      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DownloadToCacheBegin(openFileInfo, tempFilePath)));
      ret = ApiSuccess(_siaApi->GetRenter()->DownloadFile(openFileInfo.SiaPath, tempFilePath));
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DownloadToCacheEnd(openFileInfo, tempFilePath, ret)));
      if (ret)
      {
        // Find all open handles for requested file
        std::vector<OpenFileInfo*> matched;
        matched.push_back(&openFileInfo);
        {
          std::lock_guard<std::mutex> l(_openFileMutex);
          std::for_each(_openFiles.begin(), _openFiles.end(), [&](OpenFileInfo* ofi)
          {
            if ((ofi->FileHandle != openFileInfo.FileHandle) && (ofi->SiaPath == openFileInfo.SiaPath))
            {
              matched.push_back(ofi);
            }
          });

          // Close all to allow move to complete
          for (auto& ofi : matched)
          {
            ::CloseHandle(ofi->FileHandle);
          }
        }

        FilePath src(tempFilePath);
        FilePath dest(openFileInfo.CacheFilePath);
        ret = dest.DeleteFile() && src.MoveFile(dest);
        if (!ret)
        {
          src.DeleteFile();
        }
        CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(MoveTempToCacheResult(openFileInfo, tempFilePath, ret)));

        // Re-open all files
        {
          std::lock_guard<std::mutex> l(_openFileMutex);
          for (auto& ofi : matched)
          {
            if (std::find(_openFiles.begin(), _openFiles.end(), ofi) != _openFiles.end())
            {
              ofi->FileHandle = ::CreateFile(&ofi->CacheFilePath[0], ofi->DesiredAccess, ofi->ShareMode, ofi->SecurityAttrib.nLength ? &ofi->SecurityAttrib : nullptr, ofi->CreateDisp, ofi->AttributesAndFlags, nullptr);
              ofi->Dummy = !ret;
            }
          }
        }

        // Remove from active download tracking
        std::lock_guard<std::mutex> l(_downloadMutex);
        _activeDownloads.erase(std::remove(_activeDownloads.begin(), _activeDownloads.end(), openFileInfo.SiaPath), _activeDownloads.end());
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(AddToCacheComplete(openFileInfo, ret)));
    return ret;
  }

  inline static bool AddDummyFileToCache(const SString& siaPath)
  {
      FilePath dest(GetCacheLocation(), siaPath);
      return dest.CreateEmptyFile();
  }

  static void HandleSiaFileClose(OpenFileInfo& openFileInfo, const std::uint64_t& fileSize, const bool& deleteOnClose)
  {
    std::function<void()> notifyCritical;

    // Lock while closing
    std::lock_guard<std::mutex> l(_openFileMutex);
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanCloseFile(openFileInfo.CacheFilePath)));
    if (deleteOnClose)
    {
      ::CloseHandle(openFileInfo.FileHandle);
      openFileInfo.FileHandle = INVALID_HANDLE_VALUE;

      auto result = _uploadManager->Remove(openFileInfo.SiaPath);
      if (!ApiSuccess(result))
      {
        SString reason = result.GetReason();
        notifyCritical = [=]()
        {
          CEventSystem::EventSystem.NotifyEvent(CreateSystemCriticalEvent(reason));
        };
      }
    }
    else if (openFileInfo.Changed)
    {
      // Retrieve the file times for the file.
      FILETIME fileTimes[3];
      ::GetFileTime(openFileInfo.FileHandle, &fileTimes[0], &fileTimes[1], &fileTimes[2]);
        
      ::CloseHandle(openFileInfo.FileHandle);
      openFileInfo.FileHandle = INVALID_HANDLE_VALUE;

      auto result = _uploadManager->AddOrUpdate(openFileInfo.SiaPath, openFileInfo.CacheFilePath, *reinterpret_cast<std::uint64_t*>(&fileTimes[2]));
      if (!ApiSuccess(result))
      {
        SString reason = result.GetReason();
        notifyCritical = [=]()
        {
          CEventSystem::EventSystem.NotifyEvent(CreateSystemCriticalEvent(reason));
        };
      }
    }
    else
    {
      ::CloseHandle(openFileInfo.FileHandle);
      openFileInfo.FileHandle = INVALID_HANDLE_VALUE;
    }

    _openFiles.erase(std::remove(_openFiles.begin(), _openFiles.end(), &openFileInfo), _openFiles.end());

    // Make sure cleanup is complete before notifying
    if (notifyCritical)
    {
      notifyCritical();
    }
  }

	// 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)
	{
    dokanFileInfo->Context = 0;

		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 = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanCreateFile(fileName, fileAttributesAndFlags, creationDisposition, genericDesiredAccess, ret)));
		}
		else
		{
			// When filePath is a directory, needs to change the flag so that the file can
			// be opened.
			FilePath cacheFilePath(GetCacheLocation(), fileName);
			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())
					{
						ret = DokanNtStatusFromWin32(::GetLastError());
					}
				}
				else if (creationDisposition == OPEN_ALWAYS) 
				{
					if (!cacheFilePath.CreateDirectory()) 
					{
						DWORD error = ::GetLastError();
						if (error != ERROR_ALREADY_EXISTS) 
						{
							ret = DokanNtStatusFromWin32(::GetLastError());
						}
					}
				}
        else if (createDisposition == OPEN_EXISTING)
        {
          if (!cacheFilePath.IsDirectory())
          {
            ret = DokanNtStatusFromWin32(ERROR_FILE_NOT_FOUND);
          }
        }

				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)) 
					{
						ret = STATUS_NOT_A_DIRECTORY;
					}
          else
          {
            // 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 | FILE_FLAG_POSIX_SEMANTICS, nullptr);
            if (handle == INVALID_HANDLE_VALUE)
            {
              ret = DokanNtStatusFromWin32(::GetLastError());
            }
            else
            {
              OpenFileInfo* ofi = new OpenFileInfo();
              ofi->CacheFilePath = cacheFilePath;
              ofi->Dummy = false;
              ofi->Changed = false;
              ofi->FileHandle = handle;
              ofi->DokanFileInfo = dokanFileInfo;
              dokanFileInfo->Context = reinterpret_cast<ULONG64>(ofi);
            }
          }
				}
			}
			else // File (cache and/or Sia operation)
			{
				// Formulate Sia path
				SString siaPath = CSiaApi::FormatToSiaPath(fileName); // Strip drive letter to get Sia path
				if (siaPath.Length())
				{
          if (ret == STATUS_SUCCESS)
          {
            // 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 siaExists = GetFileTree()->FileExists(siaPath);
              bool exists = siaExists || cacheFilePath.IsFile();
              // 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 = DokanNtStatusFromWin32(ERROR_FILE_EXISTS);
                }
                else
                {
                  isCreateOp = true;
                }
              }
              break;

              case OPEN_ALWAYS:
              {
                if (!exists)
                {
                  isCreateOp = true;
                }
              }
              break;

              case OPEN_EXISTING:
              {
                if (!exists)
                {
                  ret = DokanNtStatusFromWin32(ERROR_FILE_NOT_FOUND);
                }
              }
              break;

              case TRUNCATE_EXISTING:
              {
                if (exists)
                {
                  isCreateOp = isReplaceOp = true;
                }
                else
                {
                  ret = DokanNtStatusFromWin32(ERROR_FILE_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 = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
                    }
                  }
                  else
                  {
                    ret = DokanNtStatusFromWin32(::GetLastError());
                  }
                }

                bool isDummy = false;
                if (ret == STATUS_SUCCESS)
                {
                  if (!isCreateOp)
                  {
                    if (cacheFilePath.IsFile())
                    {
                      isDummy = (siaExists && (FileSize(&cacheFilePath[0]) == 0));
                    }
                    else if (siaExists)
                    {
                      isDummy = AddDummyFileToCache(siaPath);
                      if (!isDummy)
                      {
                        ret = DokanNtStatusFromWin32(ERROR_ACCESS_DENIED);
                      }
                    }
                    else
                    {
                      ret = DokanNtStatusFromWin32(ERROR_FILE_NOT_FOUND);
                    }
                  }

                  if (ret == STATUS_SUCCESS)
                  {
                    std::lock_guard<std::mutex> l(_openFileMutex);
                    // Create file as specified
                    HANDLE handle = ::CreateFile(
                      &cacheFilePath[0],
                      genericDesiredAccess,
                      shareAccess,
                      &securityAttrib,
                      creationDisposition,
                      fileAttributesAndFlags,
                      nullptr);
                    if (handle == INVALID_HANDLE_VALUE)
                    {
                      ret = DokanNtStatusFromWin32(::GetLastError());
                    }
                    else
                    {
                      if ((creationDisposition == OPEN_ALWAYS) || (creationDisposition == CREATE_ALWAYS))
                      {
                        DWORD error = ::GetLastError();
                        if (error == ERROR_ALREADY_EXISTS)
                        {
                          ret = STATUS_OBJECT_NAME_COLLISION;
                        }
                      }

                      OpenFileInfo* ofi = new OpenFileInfo();
                      ofi->SiaPath = siaPath;
                      ofi->CacheFilePath = cacheFilePath;
                      ofi->Dummy = isDummy;
                      ofi->Changed = false;
                      ofi->FileHandle = handle;
                      ofi->DesiredAccess = genericDesiredAccess;
                      ofi->ShareMode = shareAccess;
                      ofi->SecurityAttrib = securityAttrib;
                      ofi->CreateDisp = createDisposition;
                      ofi->AttributesAndFlags = fileAttributesAndFlags;
                      ofi->DokanFileInfo = dokanFileInfo;
                      dokanFileInfo->Context = reinterpret_cast<ULONG64>(ofi);
                      _openFiles.push_back(ofi);
                    }
                  }
                }
              }
            }
          }
				}
				else
				{
					ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
				}
			}
		  
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanCreateFile(cacheFilePath, fileAttributesAndFlags, creationDisposition, genericDesiredAccess, ret)));
		}

		return ret;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_FindFiles(LPCWSTR fileName, PFillFindData fillFindData, PDOKAN_FILE_INFO dokanFileInfo)
	{
    NTSTATUS ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
		auto siaFileTree = GetFileTree();
		if (siaFileTree)
		{
      SString siaFileQuery = CSiaApi::FormatToSiaPath(fileName);
      SString siaRootPath = CSiaApi::FormatToSiaPath(fileName);
      SString siaDirQuery;
      FilePath findFile = GetCacheLocation();
			FilePath cachePath = GetCacheLocation();
			if (FilePath::DirSep == fileName)
			{
        siaFileQuery += L"/*.*";
        siaDirQuery = "/";
        findFile.Append("*");
			}
			else
			{
				cachePath.Append(fileName);
        findFile.Append(fileName);
				if (dokanFileInfo->IsDirectory)
				{
          siaDirQuery = siaFileQuery;
					siaFileQuery += L"/*.*";
          findFile.Append("*");
				}
			}
            
      WIN32_FIND_DATA findData = { 0 };
      HANDLE findHandle = ::FindFirstFile(&findFile[0], &findData);
      if (findHandle == INVALID_HANDLE_VALUE)
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
      else
      {
        // Find local files
        std::unordered_map<SString, std::uint8_t> dirs;
        std::unordered_map<SString, std::uint8_t> files;
        do
        {
          if ((wcscmp(fileName, L"\\") != 0) || ((wcscmp(findData.cFileName, L".") != 0) && (wcscmp(findData.cFileName, L"..") != 0)))
          {
            if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            {
              dirs.insert({ findData.cFileName, 0 });
            }
            else
            {
              files.insert({ findData.cFileName, 1 });
            }
            fillFindData(&findData, dokanFileInfo);
          }
        } while (::FindNextFile(findHandle, &findData) != 0);

        DWORD error = ::GetLastError();
        ::FindClose(findHandle);

        if (error == ERROR_NO_MORE_FILES)
        {
          // Find Sia directories
          if (!static_cast<SString>(siaDirQuery).IsNullOrEmpty())
          {
            auto dirList = siaFileTree->QueryDirectories(siaDirQuery);
            for (auto& dir : dirList)
            {
              if (dirs.find(dir) == dirs.end())
              {
                // Create cache sub-folder
                FilePath subCachePath(cachePath, dir);
                if (!subCachePath.IsDirectory())
                {
                  subCachePath.CreateDirectory();
                }

                WIN32_FIND_DATA fd = { 0 };
                wcscpy_s(fd.cFileName, dir.str().c_str());
                fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
                fillFindData(&fd, dokanFileInfo);
              }
            }
          }

          // Find Sia files
          auto fileList = siaFileTree->Query(siaFileQuery);
          for (auto& file : fileList)
          {
            FilePath fp = file->GetSiaPath();
            fp.StripToFileName();
            if (files.find(fp) == files.end())
            {
              WIN32_FIND_DATA fd = { 0 };
              wcscpy_s(fd.cFileName, &fp[0]);

              LARGE_INTEGER li = { 0 };
              li.QuadPart = file->GetFileSize();
              fd.nFileSizeHigh = li.HighPart;
              fd.nFileSizeLow = li.LowPart;
              fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
              SetCachedFileTime(FilePath(GetCacheLocation(), file->GetSiaPath()), &fd);
              fillFindData(&fd, dokanFileInfo);
            }
          }

          ret = STATUS_SUCCESS;
        }
        else
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }
      }

      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanFindFiles(cachePath, siaDirQuery, siaFileQuery, findFile, fileName, ret)));
		}
    else
    {
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanFindFiles("", "", "", "", fileName, ret)));
    }

		return ret;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_GetFileInformation(LPCWSTR fileName, LPBY_HANDLE_FILE_INFORMATION handleFileInfo, PDOKAN_FILE_INFO dokanFileInfo) 
	{
		auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
		bool opened = false;
		NTSTATUS ret = STATUS_SUCCESS;

    auto siaFileTree = GetFileTree();
    auto siaFile = siaFileTree && openFileInfo ? siaFileTree->GetFile(openFileInfo->SiaPath) : nullptr;
    SString cachFileLocation = openFileInfo ? openFileInfo->CacheFilePath : FilePath(GetCacheLocation(), fileName);

    HANDLE tempHandle = openFileInfo ? openFileInfo->FileHandle : nullptr;
	  if (FilePath(cachFileLocation).IsFile() && (!tempHandle || (tempHandle == INVALID_HANDLE_VALUE)))
		{
      tempHandle = ::CreateFile(&cachFileLocation[0], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
			if (tempHandle == INVALID_HANDLE_VALUE)
			{
				ret = DokanNtStatusFromWin32(::GetLastError());
			}
			else
			{
				opened = true;
			}
		}

		if (ret == STATUS_SUCCESS)
		{
      if (siaFile && ((openFileInfo && openFileInfo->Dummy) || !FilePath(cachFileLocation).IsFile()))
      {
        LARGE_INTEGER li = { 0 };
        li.QuadPart = siaFile->GetFileSize();
        ::GetFileInformationByHandle(tempHandle, handleFileInfo);
        handleFileInfo->dwFileAttributes = FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE;
        handleFileInfo->nFileSizeHigh = li.HighPart;
        handleFileInfo->nFileSizeLow = li.LowPart;
        SetCachedFileTime(cachFileLocation, handleFileInfo);
      } 
		  else if (!::GetFileInformationByHandle(tempHandle, handleFileInfo))
			{
				// fileName is a root directory
				// in this case, FindFirstFile can't get directory information
				if (wcscmp(fileName, L"\\") == 0)
				{
					handleFileInfo->dwFileAttributes = ::GetFileAttributes(&cachFileLocation[0]);
				}
				else
				{
					WIN32_FIND_DATA find = { 0 };
					HANDLE findHandle = ::FindFirstFile(&cachFileLocation[0], &find);
					if (findHandle == INVALID_HANDLE_VALUE)
					{
            ret = DokanNtStatusFromWin32(::GetLastError());
					}
					else
					{
						handleFileInfo->dwFileAttributes = find.dwFileAttributes;
						handleFileInfo->ftCreationTime = find.ftCreationTime;
						handleFileInfo->ftLastAccessTime = find.ftLastAccessTime;
						handleFileInfo->ftLastWriteTime = find.ftLastWriteTime;
						handleFileInfo->nFileSizeHigh = find.nFileSizeHigh;
						handleFileInfo->nFileSizeLow = find.nFileSizeLow;

						::FindClose(findHandle);
					}
				}
			}
		}

    if (opened)
    {
      ::CloseHandle(tempHandle);
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanGetFileInformation(cachFileLocation, fileName, opened, ret)));

		return ret;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_Mounted(PDOKAN_FILE_INFO dokanFileInfo)
	{
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DriveMounted(_mountPoint)));
    _siaDriveConfig->SetLastMountLocation(_mountPoint);
    if (_siaDriveConfig->GetLaunchFileMgrOnMount())
    {
      ::ShellExecute(nullptr, L"open", &_mountPoint[0], nullptr, nullptr, SW_SHOWDEFAULT);
    }
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_Unmounted(PDOKAN_FILE_INFO dokanFileInfo)
	{
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DriveUnMounted(_mountPoint)));
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_GetDiskFreeSpaceW(
		PULONGLONG FreeBytesAvailable, PULONGLONG TotalNumberOfBytes,
		PULONGLONG TotalNumberOfFreeBytes, PDOKAN_FILE_INFO dokanFileInfo)
	{
		UNREFERENCED_PARAMETER(dokanFileInfo);
    
    SiaCurrency totalBytes;
    _siaApi->GetRenter()->CalculateEstimatedStorage(_siaApi->GetRenter()->GetFunds(), totalBytes);
    SiaCurrency totalUsed = _siaApi->GetRenter()->GetTotalUsedBytes();

    *FreeBytesAvailable = (totalBytes == 0) ? 0 : (totalBytes - totalUsed).ToUInt();
		*TotalNumberOfFreeBytes = *TotalNumberOfBytes = totalBytes.ToUInt();

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_GetVolumeInformationW(
		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 = 0x5E05140D;
		*MaximumComponentLength = MAX_PATH;
		*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"NTFS");

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_ReadFile(LPCWSTR fileName, LPVOID buffer,
		DWORD bufferLen,
		LPDWORD readLength,
		LONGLONG offset,
		PDOKAN_FILE_INFO dokanFileInfo) 
	{
    NTSTATUS ret = STATUS_SUCCESS;
    bool opened = false;

		FilePath filePath(GetCacheLocation(), fileName);
    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (openFileInfo && openFileInfo->Dummy)
    {
      if (!AddFileToCache(*openFileInfo, dokanFileInfo))
      {
        ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
      }
    }

    HANDLE tempHandle = openFileInfo ? openFileInfo->FileHandle : 0;
    if (ret == STATUS_SUCCESS)
    {
      if (!tempHandle || (tempHandle == INVALID_HANDLE_VALUE))
      {
        if (!filePath.IsFile())
        {
          if (!AddFileToCache(*openFileInfo, dokanFileInfo))
          {
            ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
          }
        }

        if (ret == STATUS_SUCCESS)
        {
          tempHandle = ::CreateFile(&filePath[0], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
          if (tempHandle == INVALID_HANDLE_VALUE)
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
          else
          {
            opened = true;
          }
        }
      }

      if (ret == STATUS_SUCCESS)
      {
        LARGE_INTEGER distanceToMove;
        distanceToMove.QuadPart = offset;
        if (!::SetFilePointerEx(tempHandle, distanceToMove, nullptr, FILE_BEGIN))
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }
        else if (!::ReadFile(tempHandle, buffer, bufferLen, readLength, nullptr))
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }
      }
    }

    if (opened)
    {
      ::CloseHandle(tempHandle);
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanReadFile(filePath, opened, ret)));
		return ret;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_WriteFile(LPCWSTR fileName, LPCVOID buffer,
		DWORD bytesToWrite,
		LPDWORD bytesWritten,
		LONGLONG offset,
		PDOKAN_FILE_INFO dokanFileInfo) 
	{
    NTSTATUS ret = STATUS_SUCCESS;
		FilePath filePath(GetCacheLocation(), fileName);
    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (openFileInfo && openFileInfo->Dummy)
    {
      if (!AddFileToCache(*openFileInfo, dokanFileInfo))
      {
        ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
      }
    }

		bool opened = false;
    HANDLE tempHandle = openFileInfo ? openFileInfo->FileHandle : 0;
		if (!tempHandle || (tempHandle == INVALID_HANDLE_VALUE))
		{
      if (!filePath.IsFile())
      {
        if (!AddFileToCache(*openFileInfo, dokanFileInfo))
        {
          ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
        }
      }

      if (ret == STATUS_SUCCESS)
      {
        tempHandle = ::CreateFile(&filePath[0], GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
        if (tempHandle == INVALID_HANDLE_VALUE)
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }
        else
        {
          opened = true;
        }
      }
		}

    LARGE_INTEGER li = { 0 };
    if (ret == STATUS_SUCCESS)
    {
      if (!::GetFileSizeEx(tempHandle, &li))
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
      else
      {
        LARGE_INTEGER distanceToMove;
        if (dokanFileInfo->WriteToEndOfFile)
        {
          LARGE_INTEGER z = { 0 };
          if (!::SetFilePointerEx(tempHandle, z, nullptr, FILE_END))
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
        }
        else
        {
          // Paging IO cannot write after allocate file size.
          if (dokanFileInfo->PagingIo)
          {
            if (offset >= li.QuadPart)
            {
              *bytesWritten = 0;
              if (opened)
              {
                ::CloseHandle(tempHandle);
              }

              CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanWriteFile(filePath, opened, ret)));
              return ret;
            }

            if ((offset + bytesToWrite) > li.QuadPart)
            {
              UINT64 bytes = li.QuadPart - offset;
              if (bytes >> 32)
              {
                bytesToWrite = static_cast<DWORD>(bytes & 0xFFFFFFFFUL);
              }
              else
              {
                bytesToWrite = static_cast<DWORD>(bytes);
              }
            }
          }

          if (offset > li.QuadPart)
          {
            // 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(tempHandle, distanceToMove, nullptr, FILE_BEGIN))
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
        }

        if (ret == STATUS_SUCCESS)
        {
          if (::WriteFile(tempHandle, buffer, bytesToWrite, bytesWritten, nullptr))
          {
            if (openFileInfo)
            {
              openFileInfo->Changed = true;
            }
          }
          else
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
        }
      }
    }

    if (opened)
    {
      OpenFileInfo ofi;
      ofi.FileHandle = tempHandle;
      ofi.CacheFilePath = filePath;
      ofi.Changed = true;
      ofi.Dummy = false;
      ofi.SiaPath = CSiaApi::FormatToSiaPath(fileName);
      ofi.AttributesAndFlags = 0;
      ofi.CreateDisp = OPEN_EXISTING;
      ofi.DesiredAccess = GENERIC_WRITE;
      ofi.SecurityAttrib = { 0 };
      ofi.ShareMode = FILE_SHARE_WRITE;
      HandleSiaFileClose(ofi, li.QuadPart, false);
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanWriteFile(filePath, opened, ret)));
		return ret;
	}

	static NTSTATUS DOKAN_CALLBACK Sia_SetEndOfFile(LPCWSTR fileName, LONGLONG byteOffset, PDOKAN_FILE_INFO dokanFileInfo) 
	{
    NTSTATUS ret = STATUS_SUCCESS;
    FilePath filePath(GetCacheLocation(), fileName);

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (openFileInfo && openFileInfo->Dummy)
    {
      if (!AddFileToCache(*openFileInfo, dokanFileInfo))
      {
        ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
      }
    }

    if (ret == STATUS_SUCCESS)
    {
      if (!openFileInfo || !openFileInfo->FileHandle || (openFileInfo->FileHandle == INVALID_HANDLE_VALUE))
      {
        ret = STATUS_INVALID_HANDLE;
      }
      else
      {
        LARGE_INTEGER sz = { 0 };
        ::GetFileSizeEx(openFileInfo->FileHandle, &sz);

        LARGE_INTEGER offset;
        offset.QuadPart = byteOffset;
        if (!::SetFilePointerEx(openFileInfo->FileHandle, offset, nullptr, FILE_BEGIN))
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }
        else if (!::SetEndOfFile(openFileInfo->FileHandle))
        {
          ret = DokanNtStatusFromWin32(::GetLastError());
        }

        if (ret == STATUS_SUCCESS)
        {
          openFileInfo->Changed = (offset.QuadPart != (sz.QuadPart - 1));
        }
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanSetEndOfFile(filePath, ret)));
		return ret;
	}


  static void DOKAN_CALLBACK Sia_CloseFile(LPCWSTR fileName, PDOKAN_FILE_INFO dokanFileInfo)
  {
    if (dokanFileInfo->Context)
    {
      auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
      if (openFileInfo->FileHandle != INVALID_HANDLE_VALUE)
      {
        ::CloseHandle(openFileInfo->FileHandle);
        openFileInfo->FileHandle = INVALID_HANDLE_VALUE;
      }

      dokanFileInfo->Context = 0;
      delete openFileInfo;
    }
  }

	static void DOKAN_CALLBACK Sia_Cleanup(LPCWSTR fileName, PDOKAN_FILE_INFO dokanFileInfo) 
	{
		FilePath filePath(GetCacheLocation(), fileName);
    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (dokanFileInfo->Context)
    {
      if (dokanFileInfo->IsDirectory)
      {
        ::CloseHandle(openFileInfo->FileHandle);
        openFileInfo->FileHandle = INVALID_HANDLE_VALUE;
      }
      else
      {
        LARGE_INTEGER li = { 0 };
        ::GetFileSizeEx(openFileInfo->FileHandle, &li);
        // !!! File handle will be closed in this method !!!
        HandleSiaFileClose(*openFileInfo, li.QuadPart, dokanFileInfo->DeleteOnClose ? true : false);
      }
    }

    if (dokanFileInfo->DeleteOnClose)
    {
      if (dokanFileInfo->IsDirectory)
      {
        RetryAction([&]()->BOOL {return filePath.RemoveDirectory() ? TRUE : FALSE; }, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY_MS);
      }
      else
      {
        RetryDeleteFileIfExists(filePath);
      }
    }
	}

	static NTSTATUS DOKAN_CALLBACK Sia_FlushFileBuffers(LPCWSTR fileName, PDOKAN_FILE_INFO dokanFileInfo) 
	{
    NTSTATUS ret = STATUS_SUCCESS;
    FilePath filePath(GetCacheLocation(), fileName);

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (openFileInfo && openFileInfo->FileHandle && (openFileInfo->FileHandle != INVALID_HANDLE_VALUE))
    {
      if (!::FlushFileBuffers(openFileInfo->FileHandle))
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanFlushFileBuffers(filePath, ret)));
    return ret;
	}

  static NTSTATUS DOKAN_CALLBACK Sia_DeleteDirectory(LPCWSTR fileName, PDOKAN_FILE_INFO dokanFileInfo) 
  {
    FilePath filePath = FilePath(GetCacheLocation(), fileName);
    NTSTATUS ret = STATUS_SUCCESS;
    if (dokanFileInfo->DeleteOnClose)
    {
      filePath.Append("*");

      WIN32_FIND_DATA findData;
      HANDLE findHandle = ::FindFirstFile(&filePath[0], &findData);
      if (findHandle == INVALID_HANDLE_VALUE)
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
      else
      {
        do
        {
          if ((wcscmp(findData.cFileName, L"..") != 0) && (wcscmp(findData.cFileName, L".") != 0))
          {
            ret = STATUS_DIRECTORY_NOT_EMPTY;
          }
        } 
        while ((ret == STATUS_SUCCESS) && (::FindNextFile(findHandle, &findData) != 0));
        DWORD error = ::GetLastError();
        if ((ret != STATUS_DIRECTORY_NOT_EMPTY) && (error != ERROR_NO_MORE_FILES))
        {
          ret = DokanNtStatusFromWin32(error);
        }
        
        ::FindClose(findHandle);
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanDeleteDirectory(filePath, ret)));
    return ret;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_DeleteFileW(LPCWSTR fileName, PDOKAN_FILE_INFO dokanFileInfo)
  {
    NTSTATUS ret = STATUS_SUCCESS;

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    FilePath filePath(GetCacheLocation(), fileName);

    DWORD dwAttrib = ::GetFileAttributes(&filePath[0]);
    if ((dwAttrib != INVALID_FILE_ATTRIBUTES) && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
    {
      ret = DokanNtStatusFromWin32(ERROR_ACCESS_DENIED);
    }
    else if (openFileInfo && openFileInfo->FileHandle && (openFileInfo->FileHandle != INVALID_HANDLE_VALUE)) 
    {
      FILE_DISPOSITION_INFO fdi;
      fdi.DeleteFile = dokanFileInfo->DeleteOnClose;
      if (!::SetFileInformationByHandle(openFileInfo->FileHandle, FileDispositionInfo, &fdi, sizeof(FILE_DISPOSITION_INFO)))
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanDeleteFileW(filePath, ret)));
    return ret;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_MoveFileW(LPCWSTR fileName, LPCWSTR NewFileName, BOOL ReplaceIfExisting, PDOKAN_FILE_INFO dokanFileInfo)
  {
    NTSTATUS ret = STATUS_SUCCESS;

    FilePath filePath(GetCacheLocation(), fileName);
    FilePath newFilePath(GetCacheLocation(), NewFileName);

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (!openFileInfo || !openFileInfo->FileHandle || (openFileInfo->FileHandle == INVALID_HANDLE_VALUE))
    {
      ret = STATUS_INVALID_HANDLE;
    }
    else
    {
      size_t len = wcslen(&newFilePath[0]);
      DWORD bufferSize = static_cast<DWORD>(sizeof(FILE_RENAME_INFO) + (len * sizeof(newFilePath[0])));

      PFILE_RENAME_INFO renameInfo = static_cast<PFILE_RENAME_INFO>(malloc(bufferSize));
      if (renameInfo)
      {
        if (dokanFileInfo->IsDirectory)
        {
          if (!ApiSuccess(_uploadManager->RenameFolder(CSiaApi::FormatToSiaPath(fileName), CSiaApi::FormatToSiaPath(NewFileName))))
          {
            ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
          }
        }
        else if (!ApiSuccess(_uploadManager->RenameFile(openFileInfo->SiaPath, CSiaApi::FormatToSiaPath(NewFileName))))
        {
          ret = DokanNtStatusFromWin32(ERROR_INTERNAL_ERROR);
        }

        if (ret == ERROR_SUCCESS)
        {
          ::ZeroMemory(renameInfo, bufferSize);

          renameInfo->ReplaceIfExists = ReplaceIfExisting ? TRUE : FALSE; // some warning about converting BOOL to BOOLEAN
          renameInfo->RootDirectory = nullptr; // hope it is never needed, shouldn't be
          renameInfo->FileNameLength = static_cast<DWORD>(len) * sizeof(newFilePath[0]); // they want length in bytes

          wcscpy_s(renameInfo->FileName, len + 1, &newFilePath[0]);

          BOOL result = ::SetFileInformationByHandle(openFileInfo->FileHandle, FileRenameInfo, renameInfo, bufferSize);
        }

        free(renameInfo);
      }
      else
      {
        ret = STATUS_BUFFER_OVERFLOW;
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanMoveFileW(filePath, newFilePath, ret)));
    return ret;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_SetFileAttributesW(LPCWSTR fileName, DWORD fileAttributes, PDOKAN_FILE_INFO dokanFileInfo)
  {
    UNREFERENCED_PARAMETER(dokanFileInfo);
    NTSTATUS ret = STATUS_SUCCESS;

    FilePath filePath(GetCacheLocation(), fileName);

    if (!::SetFileAttributes(&filePath[0], fileAttributes)) 
    {
      ret = DokanNtStatusFromWin32(::GetLastError());
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanSetFileAttributesW(filePath, ret)));
    return ret;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_GetFileSecurityW(
    LPCWSTR fileName, PSECURITY_INFORMATION securityInfo,
    PSECURITY_DESCRIPTOR securityDescriptor, ULONG bufferLen,
    PULONG lengthNeeded, PDOKAN_FILE_INFO dokanFileInfo) 
  {
    UNREFERENCED_PARAMETER(dokanFileInfo);
    FilePath filePath(GetCacheLocation(), fileName);
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanGetFileAttributesW(filePath)));
    SECURITY_INFORMATION requestingSaclInfo = ((*securityInfo & SACL_SECURITY_INFORMATION) || (*securityInfo & BACKUP_SECURITY_INFORMATION));

    // TODO Implement this properly
    //if (!g_HasSeSecurityPrivilege) {
    if (true)
    {
      *securityInfo &= ~SACL_SECURITY_INFORMATION;
      *securityInfo &= ~BACKUP_SECURITY_INFORMATION;
    }

    HANDLE handle = ::CreateFile(&filePath[0], READ_CONTROL | ((requestingSaclInfo && false) ? ACCESS_SYSTEM_SECURITY : 0),
      FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
      nullptr, // security attribute
      OPEN_EXISTING,
      FILE_FLAG_BACKUP_SEMANTICS, // |FILE_FLAG_NO_BUFFERING,
      nullptr);

    if (!handle || (handle == INVALID_HANDLE_VALUE)) 
    {
      return DokanNtStatusFromWin32(::GetLastError());
    }

    if (!::GetUserObjectSecurity(handle, securityInfo, securityDescriptor, bufferLen, lengthNeeded)) 
    {
      int error = ::GetLastError();
      if (error == ERROR_INSUFFICIENT_BUFFER) 
      {
        ::CloseHandle(handle);
        return STATUS_BUFFER_OVERFLOW;
      }
      else 
      {
        ::CloseHandle(handle);
        return DokanNtStatusFromWin32(::GetLastError());
      }
    }
    ::CloseHandle(handle);

    return STATUS_SUCCESS;
  }


  static NTSTATUS DOKAN_CALLBACK Sia_SetFileSecurityW(
    LPCWSTR fileName, PSECURITY_INFORMATION securityInfo,
    PSECURITY_DESCRIPTOR securityDescriptor, ULONG securityDescriptorLength,
    PDOKAN_FILE_INFO dokanFileInfo) 
  {
    UNREFERENCED_PARAMETER(securityDescriptorLength);

    FilePath filePath(GetCacheLocation(), fileName);
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanSetFileSecurityW(filePath)));

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (!openFileInfo || !openFileInfo->FileHandle || (openFileInfo->FileHandle == INVALID_HANDLE_VALUE))
    {
      return STATUS_INVALID_HANDLE;
    }

    if (!::SetUserObjectSecurity(openFileInfo->FileHandle, securityInfo, securityDescriptor))
    {
      return DokanNtStatusFromWin32(::GetLastError());
    }

    return STATUS_SUCCESS;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_SetFileTime(LPCWSTR fileName, CONST FILETIME *creationTime,
      CONST FILETIME *lastAccessTime, CONST FILETIME *lastWriteTime,
      PDOKAN_FILE_INFO dokanFileInfo) 
  {
    NTSTATUS ret = STATUS_SUCCESS;
    FilePath filePath(GetCacheLocation(), fileName);

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (!openFileInfo || !openFileInfo->FileHandle || (openFileInfo->FileHandle == INVALID_HANDLE_VALUE))
    {
      ret = STATUS_INVALID_HANDLE;
    } 
	  else if (!::SetFileTime(openFileInfo->FileHandle, creationTime, lastAccessTime, lastWriteTime)) 
    {
      ret = DokanNtStatusFromWin32(::GetLastError());
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanSetFileTime(filePath, ret)));
    return ret;
  }

  static NTSTATUS DOKAN_CALLBACK Sia_SetAllocationSize(LPCWSTR fileName, LONGLONG allocSize, PDOKAN_FILE_INFO dokanFileInfo) 
  {
    // TODO Check dummy and add to cache if not found
    NTSTATUS ret = STATUS_SUCCESS;
    FilePath filePath(GetCacheLocation(), fileName);

    auto openFileInfo = reinterpret_cast<OpenFileInfo*>(dokanFileInfo->Context);
    if (!openFileInfo || !openFileInfo->FileHandle || (openFileInfo->FileHandle == INVALID_HANDLE_VALUE))
    {
      ret = STATUS_INVALID_HANDLE;
    }
    else
    {
      LARGE_INTEGER fileSize = { 0 };
      if (::GetFileSizeEx(openFileInfo->FileHandle, &fileSize))
      {
        if (allocSize < fileSize.QuadPart)
        {
          fileSize.QuadPart = allocSize;
          if (!::SetFilePointerEx(openFileInfo->FileHandle, fileSize, nullptr, FILE_BEGIN))
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
          else if (!::SetEndOfFile(openFileInfo->FileHandle))
          {
            ret = DokanNtStatusFromWin32(::GetLastError());
          }
        }
      }
      else
      {
        ret = DokanNtStatusFromWin32(::GetLastError());
      }
    }

    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DokanSetAllocationSize(filePath, ret)));
    return ret;
  }

public:
	static void Initialize(CSiaApi* siaApi, CSiaDriveConfig* siaDriveConfig)
	{
		_siaApi = siaApi;
		_siaDriveConfig = siaDriveConfig;
		_dokanOps.Cleanup = Sia_Cleanup;
		_dokanOps.CloseFile = Sia_CloseFile;
		_dokanOps.DeleteDirectory = Sia_DeleteDirectory;
		_dokanOps.DeleteFileW = Sia_DeleteFileW;
		_dokanOps.FindFiles = Sia_FindFiles;
		_dokanOps.FindFilesWithPattern = nullptr;
		_dokanOps.FindStreams = nullptr;
		_dokanOps.FlushFileBuffers = Sia_FlushFileBuffers;
		_dokanOps.GetDiskFreeSpaceW = Sia_GetDiskFreeSpaceW;
		_dokanOps.GetFileInformation = Sia_GetFileInformation;
		_dokanOps.GetFileSecurityW = Sia_GetFileSecurityW;
		_dokanOps.GetVolumeInformationW = Sia_GetVolumeInformationW;
		_dokanOps.LockFile = nullptr;
		_dokanOps.Mounted = Sia_Mounted;
		_dokanOps.MoveFileW = Sia_MoveFileW;
		_dokanOps.ReadFile = Sia_ReadFile;
		_dokanOps.SetAllocationSize = Sia_SetAllocationSize;
		_dokanOps.SetEndOfFile = Sia_SetEndOfFile;
		_dokanOps.SetFileAttributesW = Sia_SetFileAttributesW;
		_dokanOps.SetFileSecurityW = Sia_SetFileSecurityW;
		_dokanOps.SetFileTime = Sia_SetFileTime;
		_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.Timeout = (60 * 1000) * 60;
#ifdef _DEBUG
		_dokanOptions.Options = DOKAN_OPTION_DEBUG | DOKAN_OPTION_DEBUG_LOG_FILE;
#else
    _dokanOptions.Options = 0;
#endif

    // May spend a little wait time here while files are cleaned-up and re-added to queue
    _uploadManager.reset(new CUploadManager(_siaDriveConfig, *_siaApi));
	}

	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);
        CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DriveMountEnded(_mountPoint, _mountStatus)));
			}));
		}
	}

	static void Unmount()
	{
		if (_mountThread)
		{
      RetryAction([]()->BOOL { return DokanRemoveMountPoint(&_mountPoint[0]); }, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY_MS);

      _mountThread->join();
      _mountThread.reset(nullptr);
			_mountPoint = "";
		}
	}

	static void Shutdown()
	{
    _uploadManager.reset();
		Unmount();
		_siaApi = nullptr;
		_siaDriveConfig = nullptr;
		ZeroMemory(&_dokanOps, sizeof(_dokanOps));
		ZeroMemory(&_dokanOptions, sizeof(_dokanOptions));
	}

  static bool IsMounted()
	{
    return (_mountThread != nullptr);
	}

	static bool IsInitialized()
	{
		return (_siaApi != nullptr);
	}

  static std::shared_ptr<json> GetUploadFileList()
  {
    auto uploadManager = _uploadManager;
    if (uploadManager)
    {
      return uploadManager->GetUploadFileList();
    }

    return std::make_shared<json>();
  }
};

// Static member variables
CSiaApi* DokanImpl::_siaApi = nullptr;
CSiaDriveConfig* DokanImpl::_siaDriveConfig = nullptr;
std::shared_ptr<CUploadManager> DokanImpl::_uploadManager;
DOKAN_OPERATIONS DokanImpl::_dokanOps;
DOKAN_OPTIONS DokanImpl::_dokanOptions;
FilePath DokanImpl::_cacheLocation;
HANDLE DokanImpl::_fileListStopEvent;
std::mutex DokanImpl::_openFileMutex;
std::unique_ptr<std::thread> DokanImpl::_mountThread;
NTSTATUS DokanImpl::_mountStatus = STATUS_SUCCESS;
SString DokanImpl::_mountPoint;
std::vector<DokanImpl::OpenFileInfo*> DokanImpl::_openFiles;
std::mutex DokanImpl::_downloadMutex;
std::vector<SString> DokanImpl::_activeDownloads;

CSiaDokanDrive::CSiaDokanDrive(CSiaApi* siaApi, CSiaDriveConfig* siaDriveConfig) :
	_siaApi(siaApi),
	_siaDriveConfig(siaDriveConfig)
{
	std::lock_guard<std::mutex> l(_startStopMutex);
	if (DokanImpl::IsInitialized())
		throw SiaDokanDriveException("Sia drive has already been activated");
	
	DokanImpl::Initialize(_siaApi, _siaDriveConfig);
}

CSiaDokanDrive::~CSiaDokanDrive()
{
  Shutdown();
}

bool CSiaDokanDrive::IsMounted() const
{
  return DokanImpl::IsMounted();
}

void CSiaDokanDrive::Mount(const wchar_t& driveLetter, const SString& cacheLocation, const std::uint64_t& maxCacheSizeBytes)
{
	std::lock_guard<std::mutex> l(_startStopMutex);
	DokanImpl::Mount(driveLetter, cacheLocation);
}

void CSiaDokanDrive::Unmount(const bool& clearCache)
{
	std::lock_guard<std::mutex> l(_startStopMutex);
	DokanImpl::Unmount();
}

void CSiaDokanDrive::ClearCache()
{
	std::lock_guard<std::mutex> l(_startStopMutex);
}

std::shared_ptr<json> CSiaDokanDrive::GetUploadFileList() const
{
  return DokanImpl::GetUploadFileList();
}

void CSiaDokanDrive::Shutdown()
{
  std::lock_guard<std::mutex> l(_startStopMutex);
  DokanImpl::Shutdown();
}