#include "stdafx.h" #include "UploadManager.h" #include "SiaDriveConfig.h" #include "SiaApi.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, temp_path text unique not null, status integer not null" #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, status from upload_table where sia_path=@sia_path and (status=@status1 or status=@status2) order by id desc limit 1;" template 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())); } CUploadManager::CUploadManager(const CSiaCurl& siaCurl, CSiaDriveConfig* siaDriveConfig) : CAutoThread(siaCurl, siaDriveConfig), _uploadDatabase(siaDriveConfig->GetRenter_UploadDbFilePath(), SQLite::OPEN_CREATE | SQLite::OPEN_READWRITE), _siaDriveConfig(siaDriveConfig) { CreateTableIfNotFound(&_uploadDatabase, UPLOAD_TABLE, UPLOAD_TABLE_COLUMNS); StartAutoThread(); } CUploadManager::~CUploadManager() { StopAutoThread(); } 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 l(_uploadMutex); SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_2_STATUS); query.bind("@status1", static_cast(UploadStatus::Uploading)); query.bind("@status2", static_cast(UploadStatus::Modified)); fileTree->BuildTree(result); if (query.executeStep()) { std::string tempSiaPath = query.getColumn(1); String siaPath = CA2W(tempSiaPath.c_str()).m_psz; UploadStatus uploadStatus = static_cast(query.getColumn(2).getUInt()); auto fileList = fileTree->GetFileList(); auto it = std::find_if(fileList.begin(), fileList.end(), [&](const CSiaFilePtr& ptr) { return ptr->GetSiaPath() == siaPath; }); if (it == fileList.end()) { // error condition - should always exist. delete from db and log warning, but continue processing } else if (uploadStatus == UploadStatus::Modified) { // delete existing, change status to queued } else if ((*it)->GetUploadProgress() >= 100) { // upload complete - change status } else { // upload still active processNext = false; } } } else { // error condition - host down? processNext = false; } } catch (const SQLite::Exception& e) { // error condition - database not initialized (i.e. no table)? std::string msg = e.what(); processNext = false; } if (processNext) { try { SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_STATUS); query.bind("@status", static_cast(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(1)).m_psz; String tempFilePath; json response; SiaCurlError cerror = siaCurl.Post(String(L"/renter/upload/") + siaPath, { {L"source", tempFilePath} }, response); if (ApiSuccess(cerror)) { // TODO Update status in DB } } } catch (const SQLite::Exception& e) { // error condition std::string msg = e.what(); } } } 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(static_cast(query.getColumn(2))); } return uploadStatus; } void CUploadManager::AddOrUpdate(const String& siaPath, const String& filePath) { // Lock here - if file is modified again before a prior upload is complete, delete it and // start again later std::lock_guard l(_uploadMutex); 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(UploadStatus::Uploading)); query.bind("@status2", static_cast(UploadStatus::Modified)); // Check copying if (query.executeStep()) { if (static_cast(static_cast(query.getColumn(2))) == UploadStatus::Uploading) { // set to modified // update file path } } else { /*SQLite::Statement addOrUpdate(_uploadDatabase, ADD_UPDATE_UPLOAD); addOrUpdate.bind("@sia_path", CW2A(siaPath.c_str()).m_psz); addOrUpdate.bind("@file_path", CW2A(filePath.c_str()).m_psz); addOrUpdate.bind("@temp_path", CW2A(tempPath.c_str()).m_psz); addOrUpdate.bind("@status", static_cast(UploadStatus::Queued));*/ // While copy is active, point to normal file. // After copy, use temp file as real file until upload is complete // This allows modifications to the file to occur in a more timely manner. // Error Scenarios: // Crash before db update to status copying - file will be re-uploaded automatically, if complete; otherwise, deleted // Need to keep track of files as being copied and then there status // Crash Scenarios: // 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 String tempPath; tempPath.resize(MAX_PATH + 1); PathCombine(&tempPath[0], CA2W(_siaDriveConfig->GetTempFolder().c_str()), (GenerateSha256(filePath) + L".siadrive").c_str()); // Queue this if (::CopyFile(filePath.c_str(), tempPath.c_str(), FALSE)) { tempPath = L""; } else { if (!::DeleteFile(tempPath.c_str())) { } // error condition } } } void CUploadManager::PurgeCompleteStatus() { } void CUploadManager::PurgeErrorStatus() { } void CUploadManager::Remove(const String& siaPath) { }