703 lines
24 KiB
C++
703 lines
24 KiB
C++
#include "stdafx.h"
|
|
#include "UploadManager.h"
|
|
#include "SiaDriveConfig.h"
|
|
#include "SiaApi.h"
|
|
#include "EventSystem.h"
|
|
using namespace Sia::Api;
|
|
|
|
#define TABLE_CREATE L"create table if not exists %s (%s);"
|
|
#define UPLOAD_TABLE L"upload_table"
|
|
#define UPLOAD_TABLE_COLUMNS L"id integer primary key autoincrement, sia_path text unique not null, file_path text unique not null, sd_file_path text unique not null, status integer not null"
|
|
#define QUERY_STATUS "select id, sia_path, file_path, sd_file_path, status from upload_table where sia_path=@sia_path order by id desc limit 1;"
|
|
#define QUERY_UPLOADS_BY_STATUS "select id, sia_path, status from upload_table where status=@status order by id desc limit 1;"
|
|
#define QUERY_UPLOADS_BY_2_STATUS "select id, sia_path, status from upload_table where (status=@status1 or status=@status2) order by id desc limit 1;"
|
|
#define QUERY_UPLOADS_BY_SIA_PATH "select id, sia_path, status from upload_table where sia_path=@sia_path order by id desc limit 1;"
|
|
#define QUERY_UPLOADS_BY_SIA_PATH_AND_2_STATUS "select id, sia_path, file_path, sd_file_path, status from upload_table where sia_path=@sia_path and (status=@status1 or status=@status2) order by id desc limit 1;"
|
|
#define UPDATE_STATUS "update upload_table set status=@status where sia_path=@sia_path;"
|
|
#define INSERT_UPLOAD "insert into upload_table (sia_path, status, file_path, sd_file_path) values (@sia_path, @status, @file_path, @sd_file_path);"
|
|
#define DELETE_UPLOAD "delete from upload_table where sia_path=@sia_path;"
|
|
|
|
#define SET_STATUS(status, success_event, fail_event)\
|
|
bool statusUpdated = false;\
|
|
try\
|
|
{\
|
|
SQLite::Statement update(_uploadDatabase, UPDATE_STATUS);\
|
|
update.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);\
|
|
update.bind("@status", static_cast<unsigned>(status));\
|
|
if (update.exec() != 1)\
|
|
{\
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(fail_event(siaPath, filePath, status, CA2W(update.getErrorMsg()).m_psz)));\
|
|
}\
|
|
else\
|
|
{\
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(success_event(siaPath, filePath)));\
|
|
statusUpdated = true;\
|
|
}\
|
|
}\
|
|
catch (SQLite::Exception e)\
|
|
{\
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));\
|
|
}
|
|
|
|
template <typename... Ts>
|
|
String fmt(const String &fmt, Ts... vs)
|
|
{
|
|
size_t required = _sntprintf(nullptr, 0, fmt.c_str(), vs...);
|
|
|
|
String ret;
|
|
ret.resize(required);
|
|
_sntprintf(&ret[0], required, fmt.c_str(), vs...);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void CreateTableIfNotFound(SQLite::Database* database, const String& tableName, const String& columns)
|
|
{
|
|
String sqlCreate = fmt(TABLE_CREATE, &tableName[0], &columns[0]);
|
|
database->exec(CW2A(sqlCreate.c_str()));
|
|
}
|
|
|
|
String CUploadManager::UploadStatusToString(const UploadStatus& uploadStatus)
|
|
{
|
|
switch (uploadStatus)
|
|
{
|
|
case UploadStatus::Complete:
|
|
return L"Complete";
|
|
|
|
case UploadStatus::Copying:
|
|
return L"Copying";
|
|
|
|
case UploadStatus::Error:
|
|
return L"Error";
|
|
|
|
case UploadStatus::Modified:
|
|
return L"Modified";
|
|
|
|
case UploadStatus::NotFound:
|
|
return L"Not Found";
|
|
|
|
case UploadStatus::Queued:
|
|
return L"Queued";
|
|
|
|
case UploadStatus::Remove:
|
|
return L"Remove";
|
|
|
|
case UploadStatus::Uploading:
|
|
return L"Uploading";
|
|
|
|
default:
|
|
return L"!!Not Defined!!";
|
|
}
|
|
}
|
|
|
|
CUploadManager::CUploadManager(const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig) :
|
|
CAutoThread(siaCurl, siaDriveConfig),
|
|
_uploadDatabase(siaDriveConfig->GetRenter_UploadDbFilePath(), SQLite::OPEN_CREATE | SQLite::OPEN_READWRITE),
|
|
_fileThread(siaCurl, siaDriveConfig, [this](const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig) { this->FileThreadCallback(siaCurl, siaDriveConfig); })
|
|
{
|
|
CreateTableIfNotFound(&_uploadDatabase, UPLOAD_TABLE, UPLOAD_TABLE_COLUMNS);
|
|
|
|
// Clean-up cache folder
|
|
if (!RecurDeleteFilesByExtentsion(CA2W(siaDriveConfig->GetCacheFolder().c_str()).m_psz, L".siadrive"))
|
|
{
|
|
throw StartupException(L"Failed to remove '.siadrive' files");
|
|
}
|
|
|
|
if (!RecurDeleteFilesByExtentsion(CA2W(siaDriveConfig->GetCacheFolder().c_str()).m_psz, L".siadrive.temp"))
|
|
{
|
|
throw StartupException(L"Failed to remove '.siadrive.temp' files");
|
|
}
|
|
|
|
// Re-add files to file action queue
|
|
UpdateFileQueueOnStartup();
|
|
|
|
// Detect files that have been removed since last startup
|
|
DeleteFilesRemovedFromSia(siaCurl, siaDriveConfig, true);
|
|
|
|
// Begin normal processing
|
|
StartAutoThread();
|
|
_fileThread.StartAutoThread();
|
|
}
|
|
|
|
CUploadManager::~CUploadManager()
|
|
{
|
|
// Stop all processing
|
|
_fileThread.StopAutoThread();
|
|
StopAutoThread();
|
|
}
|
|
|
|
void CUploadManager::UpdateFileQueueOnStartup()
|
|
{
|
|
try
|
|
{
|
|
// Re-add Copying and Remove
|
|
SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH_AND_2_STATUS);
|
|
query.bind("@status1", static_cast<unsigned>(UploadStatus::Copying));
|
|
query.bind("@status1", static_cast<unsigned>(UploadStatus::Remove));
|
|
while (query.executeStep())
|
|
{
|
|
String siaPath = CA2W(query.getColumn(query.getColumnIndex("sia_path"))).m_psz;
|
|
String filePath = CA2W(query.getColumn(query.getColumnIndex("file_path"))).m_psz;
|
|
String siaDriveFilePath = CA2W(query.getColumn(query.getColumnIndex("sd_file_path"))).m_psz;
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(query.getColumn(query.getColumnIndex("status")).getUInt());
|
|
|
|
String temp = filePath;
|
|
::PathRemoveFileSpec(&temp[0]);
|
|
String rootPath = temp;
|
|
|
|
// Strip drive specification (i.e. C:\)
|
|
// TODO If mount to folder is ever enabled, this will need to change
|
|
String siaDriveFileName = GenerateSha256(&filePath[3]) + L".siadrive";
|
|
|
|
String tempSourcePath;
|
|
tempSourcePath.resize(MAX_PATH + 1);
|
|
PathCombine(&tempSourcePath[0], rootPath.c_str(), (siaDriveFileName + L".temp").c_str());
|
|
|
|
std::lock_guard<std::mutex> l(_fileQueueMutex);
|
|
if (uploadStatus == UploadStatus::Remove)
|
|
{
|
|
_fileQueue.push_back([=]() { this->FileAction(CSiaCurl(GetHostConfig()), siaPath, filePath, nullptr, siaDriveFilePath, true); });
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemoveAdded(siaPath)));
|
|
}
|
|
else
|
|
{
|
|
_fileQueue.push_back([=]() { this->FileAction(CSiaCurl(GetHostConfig()), siaPath, filePath, tempSourcePath, siaDriveFilePath, false); });
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(NewFileAdded(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
throw StartupException(e.getErrorStr());
|
|
}
|
|
}
|
|
|
|
void CUploadManager::DeleteFilesRemovedFromSia(const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig, const bool& isStartup)
|
|
{
|
|
CSiaFileTreePtr fileTree(new CSiaFileTree(siaCurl, siaDriveConfig));
|
|
json result;
|
|
SiaCurlError cerror = siaCurl.Get(L"/renter/files", result);
|
|
if (ApiSuccess(cerror))
|
|
{
|
|
fileTree->BuildTree(result);
|
|
auto fileList = fileTree->GetFileList();
|
|
}
|
|
else
|
|
{
|
|
if (isStartup)
|
|
{
|
|
throw StartupException(L"Failed to get Sia files");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CUploadManager::FileThreadCallback(const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig)
|
|
{
|
|
std::function<void()> nextFile = nullptr;
|
|
{
|
|
std::lock_guard<std::mutex> l(_fileQueueMutex);
|
|
if (_fileQueue.size())
|
|
{
|
|
nextFile = _fileQueue.front();
|
|
_fileQueue.pop_front();
|
|
}
|
|
}
|
|
|
|
if (nextFile)
|
|
{
|
|
nextFile();
|
|
}
|
|
}
|
|
|
|
void CUploadManager::HandleFileRemove(const CSiaCurl& siaCurl, const String& siaPath, const String& siaDriveFilePath)
|
|
{
|
|
try
|
|
{
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
SQLite::Statement query(_uploadDatabase, QUERY_STATUS);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
if (query.executeStep())
|
|
{
|
|
String removeFilePath = CA2W(query.getColumn(query.getColumnIndex("file_path"))).m_psz;
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
// Make sure status is still remove
|
|
if (uploadStatus == UploadStatus::Remove)
|
|
{
|
|
bool deleteFromDb = true;
|
|
if (::PathFileExists(removeFilePath.c_str()))
|
|
{
|
|
if (RetryDeleteFileIfExists(removeFilePath.c_str()))
|
|
{
|
|
if (!RetryDeleteFileIfExists(siaDriveFilePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteSiaDriveFileFailed(siaPath, removeFilePath, siaDriveFilePath)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(RemoveFileFailed(siaPath, removeFilePath)));
|
|
deleteFromDb = false;
|
|
}
|
|
}
|
|
|
|
if (deleteFromDb)
|
|
{
|
|
json response;
|
|
SiaCurlError cerror = siaCurl.Post(String(L"/renter/delete") + siaPath, {}, response);
|
|
if (ApiSuccess(cerror))
|
|
{
|
|
// TODO validate response
|
|
|
|
SQLite::Statement del(_uploadDatabase, DELETE_UPLOAD);
|
|
del.bind("@sia_path", CW2A(siaPath.c_str()));
|
|
if (del.exec() == 1)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemoved(siaPath, removeFilePath)));
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseDeleteFailed(siaPath, removeFilePath, CA2W(del.getErrorMsg()).m_psz)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FailedToDeleteFromSia(siaPath, removeFilePath, cerror)));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
}
|
|
}
|
|
|
|
void CUploadManager::HandleAddFile(const String& siaPath, const String& filePath, const String& tempSourcePath, const String& siaDriveFilePath)
|
|
{
|
|
// Check for retry condition
|
|
if (!::PathFileExists(tempSourcePath.c_str()) && ::PathFileExists(siaDriveFilePath.c_str()))
|
|
{
|
|
try
|
|
{
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
SQLite::Statement query(_uploadDatabase, QUERY_STATUS);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
if (query.executeStep())
|
|
{
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
if (uploadStatus == UploadStatus::Copying)
|
|
{
|
|
if (RetryDeleteFileIfExists(siaDriveFilePath))
|
|
{
|
|
SET_STATUS(UploadStatus::Queued, UploadAddedToQueue, ModifyUploadStatusFailed)
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteSiaDriveFileFailed(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(CreatingTemporarySiaDriveFile(siaPath, filePath, tempSourcePath)));
|
|
if (RetryableAction(::CopyFile(filePath.c_str(), tempSourcePath.c_str(), FALSE), DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY_MS))
|
|
{
|
|
// Delete existing '.siadrive' file, if found
|
|
// !!Should never come here. If so, there was a problem with startup clean-up
|
|
if (!RetryDeleteFileIfExists(siaDriveFilePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteSiaDriveFileFailed(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
|
|
// Rename '.siadrive.temp' to '.siadrive'
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(RenamingTemporarySiaDriveFile(siaPath, filePath, tempSourcePath, siaDriveFilePath)));
|
|
if (RetryableAction(::MoveFile(tempSourcePath.c_str(), siaDriveFilePath.c_str()), DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY_MS))
|
|
{
|
|
if (!RetryDeleteFileIfExists(tempSourcePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteTemporarySiaDriveFileFailed(siaPath, filePath, tempSourcePath)));
|
|
}
|
|
|
|
try
|
|
{
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
SQLite::Statement query(_uploadDatabase, QUERY_STATUS);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
if (query.executeStep())
|
|
{
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
if (uploadStatus == UploadStatus::Copying)
|
|
{
|
|
SET_STATUS(UploadStatus::Queued, UploadAddedToQueue, ModifyUploadStatusFailed)
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(RenamingTemporarySiaDriveFileFailed(siaPath, filePath, tempSourcePath, siaDriveFilePath)));
|
|
if (!RetryDeleteFileIfExists(tempSourcePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteTemporarySiaDriveFileFailed(siaPath, filePath, tempSourcePath)));
|
|
}
|
|
|
|
if (!RetryDeleteFileIfExists(siaDriveFilePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteSiaDriveFileFailed(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
|
|
// Requeued
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(CreatingTemporarySiaDriveFileFailed(siaPath, filePath, tempSourcePath)));
|
|
|
|
// If temp copy fails, try to delete
|
|
// If partial copy and file is unable to be deleted, log warning
|
|
if (!RetryDeleteFileIfExists(tempSourcePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteTemporarySiaDriveFileFailed(siaPath, filePath, tempSourcePath)));
|
|
}
|
|
|
|
// Requeued
|
|
}
|
|
}
|
|
}
|
|
|
|
void CUploadManager::FileAction(const CSiaCurl& siaCurl, const String& siaPath, const String& filePath, const String& tempSourcePath, const String& siaDriveFilePath, const bool& remove)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> l(_fileActionMutex);
|
|
_activeSiaPath = siaPath;
|
|
}
|
|
|
|
if (remove)
|
|
{
|
|
HandleFileRemove(siaCurl, siaPath, siaDriveFilePath);
|
|
}
|
|
else
|
|
{
|
|
HandleAddFile(siaPath, filePath, tempSourcePath, siaDriveFilePath);
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> l(_fileActionMutex);
|
|
_activeSiaPath.empty();
|
|
}
|
|
}
|
|
|
|
void CUploadManager::AutoThreadCallback(const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig)
|
|
{
|
|
bool processNext = true;
|
|
|
|
try
|
|
{
|
|
CSiaFileTreePtr fileTree(new CSiaFileTree(siaCurl, siaDriveConfig));
|
|
json result;
|
|
if (ApiSuccess(siaCurl.Get(L"/renter/files", result)))
|
|
{
|
|
// Lock here - if file is modified again before a prior upload is complete, delete it and
|
|
// start again later
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_2_STATUS);
|
|
query.bind("@status1", static_cast<unsigned>(UploadStatus::Uploading));
|
|
query.bind("@status2", static_cast<unsigned>(UploadStatus::Modified));
|
|
|
|
fileTree->BuildTree(result);
|
|
if (query.executeStep())
|
|
{
|
|
String siaPath = CA2W(query.getColumn(query.getColumnIndex("sia_path"))).m_psz;
|
|
String filePath = CA2W(query.getColumn(query.getColumnIndex("file_path"))).m_psz;
|
|
String siaDriveFilePath = CA2W(query.getColumn(query.getColumnIndex("sd_file_path"))).m_psz;
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(query.getColumn(query.getColumnIndex("status")).getUInt());
|
|
|
|
auto fileList = fileTree->GetFileList();
|
|
auto it = std::find_if(fileList.begin(), fileList.end(), [&](const CSiaFilePtr& ptr)
|
|
{
|
|
return ptr->GetSiaPath() == siaPath;
|
|
});
|
|
|
|
// Removed by another client
|
|
if (it == fileList.end())
|
|
{
|
|
SET_STATUS(UploadStatus::Remove, ExternallyRemovedFileDetected, ModifyUploadStatusFailed)
|
|
if (statusUpdated)
|
|
{
|
|
std::lock_guard<std::mutex> l2(_fileQueueMutex);
|
|
_fileQueue.push_back([=]() { this->FileAction(CSiaCurl(GetHostConfig()), siaPath, filePath, nullptr, siaDriveFilePath, true); });
|
|
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemoveAdded(siaPath)));
|
|
}
|
|
}
|
|
// Changed file detected
|
|
else if (uploadStatus == UploadStatus::Modified)
|
|
{
|
|
json response;
|
|
SiaCurlError cerror = siaCurl.Post(String(L"/renter/delete") + siaPath, {}, response);
|
|
if (ApiSuccess(cerror))
|
|
{
|
|
// TODO validate response
|
|
SET_STATUS(UploadStatus::Queued, ModifiedUploadQueued, ModifyUploadStatusFailed)
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FailedToDeleteFromSia(siaPath, filePath, cerror)));
|
|
}
|
|
}
|
|
// Upload is complete
|
|
else if ((*it)->GetUploadProgress() >= 100)
|
|
{
|
|
SET_STATUS(UploadStatus::Complete, UploadComplete, ModifyUploadStatusFailed)
|
|
if (!RetryDeleteFileIfExists(siaDriveFilePath))
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DeleteSiaDriveFileFailed(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
}
|
|
// Upload still active, don't process another file
|
|
else
|
|
{
|
|
processNext = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// error condition - host down?
|
|
processNext = false;
|
|
}
|
|
}
|
|
catch (const SQLite::Exception& e)
|
|
{
|
|
// error condition - database not initialized (i.e. no table)?
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
processNext = false;
|
|
}
|
|
|
|
if (processNext)
|
|
{
|
|
try
|
|
{
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_STATUS);
|
|
query.bind("@status", static_cast<unsigned>(UploadStatus::Queued));
|
|
|
|
// Lock here - if file is modified again before a prior upload is complete, delete it and
|
|
// start again later
|
|
if (query.executeStep())
|
|
{
|
|
String siaPath = CA2W(query.getColumn(query.getColumnIndex("sia_path"))).m_psz;
|
|
String filePath = CA2W(query.getColumn(query.getColumnIndex("file_path"))).m_psz;
|
|
|
|
// TODO Validate response
|
|
json response;
|
|
SiaCurlError cerror = siaCurl.Post(String(L"/renter/upload") + siaPath, { {L"source", filePath} }, response);
|
|
if (ApiSuccess(cerror))
|
|
{
|
|
SET_STATUS(UploadStatus::Uploading, UploadToSiaStarted, ModifyUploadStatusFailed)
|
|
}
|
|
}
|
|
}
|
|
catch (const SQLite::Exception& e)
|
|
{
|
|
// error condition
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
}
|
|
}
|
|
}
|
|
|
|
UploadStatus CUploadManager::GetUploadStatus(const String& siaPath)
|
|
{
|
|
UploadStatus uploadStatus = UploadStatus::NotFound;
|
|
|
|
SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
if (query.executeStep())
|
|
{
|
|
uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
}
|
|
|
|
return uploadStatus;
|
|
}
|
|
|
|
// The real source file is copied to a hidden file. The hidden filename is constructed by generating an SHA25 hash of the
|
|
// source path (all lowercase). '.siadrive.temp' will be used as the extension. After copy is successful, the extension
|
|
// is renamed to '.siadrive'. '.siadrive' files will be hidden/system and used by the Dokan API for file i/o until Sia upload
|
|
// is complete. If a change occurs, the file will be deleted from Sia (cancelling the in-progress upload) and renamed to the
|
|
// real source path. The process will then start over again as if the file was new.
|
|
// If the file has been fully uploaded, the hidden file should not exist, so rename will not occur; however, the file
|
|
// will be deleted from Sia and treated as new.
|
|
// Uploads will always use the real file. User i/o will occur against the hidden file temporarily. Since upload will take
|
|
// longer to handle than a normal file copy, this seems to be the best compromise for performance.
|
|
//
|
|
// Error Scenarios:
|
|
// Crash before db update to status copying - file will be re-uploaded automatically, if complete; otherwise, deleted
|
|
// Crash before copy begins - on startup, check for copying status with no .siadrive
|
|
// Crash during copy - on startup, check for copying status and delete .siadrive
|
|
// Crash after copy but before db update - on startup, check for copying status and delete .siadrive
|
|
UploadError CUploadManager::AddOrUpdate(const String& siaPath, String filePath)
|
|
{
|
|
UploadError ret = UploadError::Success;
|
|
|
|
// Relative to absolute and grab parent folder of source
|
|
String rootPath;
|
|
{
|
|
String temp;
|
|
if (::PathIsRelative(filePath.c_str()))
|
|
{
|
|
temp.resize(MAX_PATH + 1);
|
|
filePath = _wfullpath(&temp[0], filePath.c_str(), MAX_PATH);
|
|
}
|
|
|
|
temp = filePath;
|
|
::PathRemoveFileSpec(&temp[0]);
|
|
rootPath = temp;
|
|
}
|
|
|
|
if (::PathFileExists(filePath.c_str()))
|
|
{
|
|
// Lock here - if file is modified again before a prior upload is complete, delete it and
|
|
// start again later
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
|
|
try
|
|
{
|
|
SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH_AND_2_STATUS);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
query.bind("@status1", static_cast<unsigned>(UploadStatus::Uploading));
|
|
query.bind("@status2", static_cast<unsigned>(UploadStatus::Modified));
|
|
// Check copying
|
|
if (query.executeStep())
|
|
{
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(ExistingUploadFound(siaPath, filePath, uploadStatus)));
|
|
if (uploadStatus == UploadStatus::Uploading)
|
|
{
|
|
SET_STATUS(UploadStatus::Modified, UploadStatusSetToModified, ModifyUploadStatusFailed)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Strip drive specification (i.e. C:\)
|
|
// TODO If mount to folder is ever enabled, this will need to change
|
|
String siaDriveFileName = GenerateSha256(&filePath[3]) + L".siadrive";
|
|
|
|
String siaDriveFilePath;
|
|
siaDriveFilePath.resize(MAX_PATH + 1);
|
|
PathCombine(&siaDriveFilePath[0], rootPath.c_str(), siaDriveFileName.c_str());
|
|
|
|
String tempSourcePath;
|
|
tempSourcePath.resize(MAX_PATH + 1);
|
|
PathCombine(&tempSourcePath[0], rootPath.c_str(), (siaDriveFileName + L".temp").c_str());
|
|
|
|
// Add to db
|
|
try
|
|
{
|
|
SQLite::Statement insert(_uploadDatabase, INSERT_UPLOAD);
|
|
insert.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
insert.bind("@file_path", CW2A(filePath.c_str()).m_psz);
|
|
insert.bind("@sd_file_path", CW2A(siaDriveFileName.c_str()).m_psz);
|
|
insert.bind("@status", static_cast<unsigned>(UploadStatus::Copying));
|
|
if (insert.exec() == 1)
|
|
{
|
|
// Queue file upload operation
|
|
std::lock_guard<std::mutex> l2(_fileQueueMutex);
|
|
_fileQueue.push_back([=]() { this->FileAction(CSiaCurl(GetHostConfig()), siaPath, filePath, tempSourcePath, siaDriveFilePath, false); });
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(NewFileAdded(siaPath, filePath, siaDriveFilePath)));
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseInsertFailed(siaPath, filePath, CA2W(insert.getErrorMsg()).m_psz)));
|
|
ret = UploadError::DatabaseError;
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
ret = UploadError::DatabaseError;
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
ret = UploadError::DatabaseError;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(SourceFileNotFound(siaPath, filePath)));
|
|
ret = UploadError::SourceFileNotFound;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
UploadError CUploadManager::Remove(const String& siaPath)
|
|
{
|
|
UploadError ret = UploadError::Success;
|
|
std::lock_guard<std::mutex> l(_uploadMutex);
|
|
try
|
|
{
|
|
bool remove = false;
|
|
|
|
SQLite::Statement query(_uploadDatabase, QUERY_STATUS);
|
|
query.bind("@sia_path", CW2A(siaPath.c_str()).m_psz);
|
|
if (query.executeStep())
|
|
{
|
|
String filePath = CA2W(query.getColumn(query.getColumnIndex("file_path"))).m_psz;
|
|
String siaDriveFilePath = CA2W(query.getColumn(query.getColumnIndex("sd_file_path"))).m_psz;
|
|
UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
|
|
switch (uploadStatus)
|
|
{
|
|
case UploadStatus::Complete:
|
|
case UploadStatus::Queued:
|
|
case UploadStatus::Modified:
|
|
case UploadStatus::Copying:
|
|
case UploadStatus::Uploading:
|
|
{
|
|
SET_STATUS(UploadStatus::Remove, UploadStatusSetToRemoved, ModifyUploadStatusFailed)
|
|
remove = statusUpdated;
|
|
if (!statusUpdated)
|
|
{
|
|
ret = UploadError::DatabaseError;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
remove = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (remove)
|
|
{
|
|
std::lock_guard<std::mutex> l2(_fileQueueMutex);
|
|
_fileQueue.push_back([=]() { this->FileAction(CSiaCurl(GetHostConfig()), siaPath, filePath, nullptr, siaDriveFilePath, true); });
|
|
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemoveAdded(siaPath)));
|
|
}
|
|
}
|
|
}
|
|
catch (SQLite::Exception e)
|
|
{
|
|
CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred(e)));
|
|
ret = UploadError::DatabaseError;
|
|
}
|
|
|
|
return ret;
|
|
} |