diff --git a/dist/addon.node b/dist/addon.node index b1db108..a03f4f6 100644 Binary files a/dist/addon.node and b/dist/addon.node differ diff --git a/include/sync_root_interface/TransferContext.h b/include/sync_root_interface/TransferContext.h index 743c01f..afbe67e 100644 --- a/include/sync_root_interface/TransferContext.h +++ b/include/sync_root_interface/TransferContext.h @@ -13,21 +13,16 @@ struct TransferContext { CF_CONNECTION_KEY connectionKey; CF_TRANSFER_KEY transferKey; + LARGE_INTEGER fileSize; LARGE_INTEGER requiredLength; LARGE_INTEGER requiredOffset; - CF_CALLBACK_INFO callbackInfo; std::wstring path; - size_t lastReadOffset = 0; - size_t lastSize = 0; - bool loadFinished = false; + bool ready = false; std::mutex mtx; std::condition_variable cv; - bool ready = false; - - std::wstring fullServerFilePath; }; std::shared_ptr CreateTransferContext(CF_TRANSFER_KEY transferKey); diff --git a/native-src/sync_root_interface/callbacks/CancelFetchData/CancelFetchDataCallback.cpp b/native-src/sync_root_interface/callbacks/CancelFetchData/CancelFetchDataCallback.cpp index 8f0e8fa..c957366 100644 --- a/native-src/sync_root_interface/callbacks/CancelFetchData/CancelFetchDataCallback.cpp +++ b/native-src/sync_root_interface/callbacks/CancelFetchData/CancelFetchDataCallback.cpp @@ -16,11 +16,11 @@ void notify_cancel_fetch_data_call(napi_env env, napi_value js_callback, void *c napi_value js_path; napi_create_string_utf16(env, (char16_t *)path->c_str(), path->length(), &js_path); - std::array args_to_js_callback = {js_path}; + std::array js_args = {js_path}; napi_value undefined; napi_get_undefined(env, &undefined); - napi_call_function(env, undefined, js_callback, 1, args_to_js_callback.data(), nullptr); + napi_call_function(env, undefined, js_callback, js_args.size(), js_args.data(), nullptr); } void CALLBACK cancel_fetch_data_callback_wrapper(_In_ CONST CF_CALLBACK_INFO *callbackInfo, _In_ CONST CF_CALLBACK_PARAMETERS *) diff --git a/native-src/sync_root_interface/callbacks/FetchData/FetchData.cpp b/native-src/sync_root_interface/callbacks/FetchData/FetchData.cpp index 7a38410..ae119a3 100644 --- a/native-src/sync_root_interface/callbacks/FetchData/FetchData.cpp +++ b/native-src/sync_root_interface/callbacks/FetchData/FetchData.cpp @@ -1,309 +1,189 @@ -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include "Utilities.h" -#include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include napi_threadsafe_function g_fetch_data_threadsafe_callback = nullptr; -#define FIELD_SIZE(type, field) (sizeof(((type *)0)->field)) - -#define CF_SIZE_OF_OP_PARAM(field) \ - (FIELD_OFFSET(CF_OPERATION_PARAMETERS, field) + \ - FIELD_SIZE(CF_OPERATION_PARAMETERS, field)) +#define FIELD_SIZE(type, field) (sizeof(((type *)nullptr)->field)) -#define CHUNK_SIZE (4096 * 1024) +#define CF_SIZE_OF_OP_PARAM(field) (FIELD_OFFSET(CF_OPERATION_PARAMETERS, field) + FIELD_SIZE(CF_OPERATION_PARAMETERS, field)) -#define CHUNKDELAYMS 250 - -napi_value create_response(napi_env env, bool finished, float progress) +HRESULT transfer_data( + _In_ CF_CONNECTION_KEY connectionKey, + _In_ LARGE_INTEGER transferKey, + _In_reads_bytes_opt_(length.QuadPart) LPCVOID transferData, + _In_ LARGE_INTEGER startingOffset, + _In_ LARGE_INTEGER length, + _In_ NTSTATUS completionStatus) { - napi_value result_object; - napi_create_object(env, &result_object); + CF_OPERATION_INFO opInfo = {0}; + opInfo.StructSize = sizeof(opInfo); + opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA; + opInfo.ConnectionKey = connectionKey; + opInfo.TransferKey = transferKey; + + CF_OPERATION_PARAMETERS opParams = {0}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData); + opParams.TransferData.CompletionStatus = completionStatus; + opParams.TransferData.Buffer = transferData; + opParams.TransferData.Offset = startingOffset; + opParams.TransferData.Length = length; + + return CfExecute(&opInfo, &opParams); +} - napi_value finished_value; - napi_get_boolean(env, finished, &finished_value); - napi_set_named_property(env, result_object, "finished", finished_value); +napi_value response_callback_fn_fetch_data(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + std::array args; - napi_value progress_value; - napi_create_double(env, progress, &progress_value); - napi_set_named_property(env, result_object, "progress", progress_value); + TransferContext *ctx = nullptr; + napi_get_cb_info(env, info, &argc, args.data(), nullptr, reinterpret_cast(&ctx)); - napi_value promise; - napi_deferred deferred; - napi_create_promise(env, &deferred, &promise); + void *data; + size_t length; + napi_get_buffer_info(env, args[0], &data, &length); - napi_resolve_deferred(env, deferred, result_object); + int64_t offset; + napi_get_value_int64(env, args[1], &offset); - return promise; -} + LARGE_INTEGER startingOffset; + LARGE_INTEGER chunkSize; + startingOffset.QuadPart = offset; + chunkSize.QuadPart = length; -static size_t file_incremental_reading(napi_env env, - TransferContext &ctx, - bool final_step, - float &progress) -{ - std::ifstream file; - file.open(ctx.fullServerFilePath, std::ios::in | std::ios::binary); + HRESULT hr = transfer_data(ctx->connectionKey, ctx->transferKey, data, startingOffset, chunkSize, STATUS_SUCCESS); - if (!file.is_open()) { - Logger::getInstance().log("Error al abrir el archivo en file_incremental_reading.", LogLevel::ERROR); - return ctx.lastReadOffset; + if (FAILED(hr)) + { + transfer_data(ctx->connectionKey, ctx->transferKey, nullptr, ctx->requiredOffset, ctx->requiredLength, STATUS_UNSUCCESSFUL); + winrt::throw_hresult(hr); } - file.clear(); - file.seekg(0, std::ios::end); - size_t newSize = static_cast(file.tellg()); - - size_t datasizeAvailableUnread = newSize - ctx.lastReadOffset; - size_t growth = newSize - ctx.lastSize; - - try { - if (datasizeAvailableUnread > 0) { - std::vector buffer(CHUNK_SIZE); - file.seekg(ctx.lastReadOffset); - file.read(buffer.data(), CHUNK_SIZE); - - LARGE_INTEGER startingOffset, chunkBufferSize; - startingOffset.QuadPart = ctx.lastReadOffset; - - chunkBufferSize.QuadPart = min(datasizeAvailableUnread, CHUNK_SIZE); - - HRESULT hr = FileCopierWithProgress::TransferData( - ctx.connectionKey, - ctx.transferKey, - buffer.data(), - startingOffset, - chunkBufferSize, - STATUS_SUCCESS); - - ctx.lastReadOffset += chunkBufferSize.QuadPart; - - if (FAILED(hr)) { - wprintf(L"Error en TransferData(). HRESULT: %lx\n", hr); - ctx.loadFinished = true; - FileCopierWithProgress::TransferData( - ctx.connectionKey, - ctx.transferKey, - NULL, - ctx.requiredOffset, - ctx.requiredLength, - STATUS_UNSUCCESSFUL); - } else { - UINT64 totalSize = static_cast(ctx.fileSize.QuadPart); - progress = static_cast(ctx.lastReadOffset) / static_cast(totalSize); - Utilities::ApplyTransferStateToFile(ctx.path.c_str(), - ctx.callbackInfo, - totalSize, - ctx.lastReadOffset); - ::Sleep(CHUNKDELAYMS); - } - } - } catch (...) { - Logger::getInstance().log("Excepción en file_incremental_reading.", LogLevel::ERROR); - FileCopierWithProgress::TransferData( - ctx.connectionKey, - ctx.transferKey, - NULL, - ctx.requiredOffset, - ctx.requiredLength, - STATUS_UNSUCCESSFUL); - } + size_t completed = offset + length; - file.close(); - ctx.lastSize = newSize; - return ctx.lastReadOffset; -} + wprintf(L"Bytes completed: %d. Total bytes: %d. Required offset: %d. Required length: %d.\n", + completed, + ctx->fileSize.QuadPart, + ctx->requiredOffset.QuadPart, + ctx->requiredLength.QuadPart); -static napi_value response_callback_fn_fetch_data(napi_env env, napi_callback_info info) -{ - Logger::getInstance().log("response_callback_fn_fetch_data called", LogLevel::DEBUG); - - size_t argc = 3; - napi_value argv[3]; - napi_status status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - if (status != napi_ok) { - Logger::getInstance().log("Failed to get callback info", LogLevel::ERROR); - return create_response(env, true, 0); - } + winrt::com_ptr shellItem; + winrt::com_ptr propStoreVolatile; - if (argc < 2) { - Logger::getInstance().log("This function must receive at least two arguments", LogLevel::ERROR); - return create_response(env, true, 0); - } + winrt::check_hresult(SHCreateItemFromParsingName(ctx->path.c_str(), nullptr, __uuidof(shellItem), shellItem.put_void())); - napi_valuetype valueType; - status = napi_typeof(env, argv[0], &valueType); - if (status != napi_ok || valueType != napi_boolean) { - Logger::getInstance().log("First argument should be boolean", LogLevel::ERROR); - return create_response(env, true, 0); - } + winrt::check_hresult( + shellItem->GetPropertyStore( + GETPROPERTYSTOREFLAGS::GPS_READWRITE | GETPROPERTYSTOREFLAGS::GPS_VOLATILEPROPERTIESONLY, + __uuidof(propStoreVolatile), + propStoreVolatile.put_void())); - bool response = false; - napi_get_value_bool(env, argv[0], &response); + if (completed < ctx->fileSize.QuadPart) + { + PROPVARIANT pvProgress; + PROPVARIANT pvStatus; + std::array values = {completed, ctx->fileSize.QuadPart}; - status = napi_typeof(env, argv[1], &valueType); - if (status != napi_ok || valueType != napi_string) { - Logger::getInstance().log("Second argument should be string", LogLevel::ERROR); - return create_response(env, true, 0); - } + InitPropVariantFromUInt64Vector(values.data(), values.size(), &pvProgress); + InitPropVariantFromUInt32(SYNC_TRANSFER_STATUS::STS_TRANSFERRING, &pvStatus); + + propStoreVolatile->SetValue(PKEY_StorageProviderTransferProgress, pvProgress); + propStoreVolatile->SetValue(PKEY_SyncTransferStatus, pvStatus); + propStoreVolatile->Commit(); - TransferContext* ctxPtr = nullptr; - napi_value thisArg = nullptr; - status = napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, reinterpret_cast(&ctxPtr)); - if (status != napi_ok || !ctxPtr) { - Logger::getInstance().log("Could not retrieve TransferContext from callback data", LogLevel::ERROR); - return create_response(env, true, 0); + PropVariantClear(&pvProgress); } + else + { + wprintf(L"Fetch data finished\n"); - if (!response) { - Logger::getInstance().log("JS responded with false; we cancel hydration.", LogLevel::DEBUG); + auto fileHandle = Placeholders::OpenFileHandle(ctx->path, FILE_WRITE_ATTRIBUTES, true); + CfSetPinState(fileHandle.get(), CF_PIN_STATE_PINNED, CF_SET_PIN_FLAG_NONE, nullptr); - ctxPtr->loadFinished = true; - ctxPtr->lastReadOffset = 0; { - std::lock_guard lock(ctxPtr->mtx); - ctxPtr->ready = true; - ctxPtr->cv.notify_one(); + std::scoped_lock lock(ctx->mtx); + ctx->ready = true; } - return create_response(env, true, 0); + ctx->cv.notify_one(); } - size_t response_len = 0; - napi_get_value_string_utf16(env, argv[1], nullptr, 0, &response_len); - - std::wstring response_wstr(response_len, L'\0'); - napi_get_value_string_utf16(env, argv[1], (char16_t*)response_wstr.data(), response_len + 1, &response_len); - Logger::getInstance().log( - "JS responded with server file path = " + Logger::fromWStringToString(response_wstr), - LogLevel::DEBUG - ); - - ctxPtr->fullServerFilePath = response_wstr; - - float progress = 0.0f; - ctxPtr->lastReadOffset = file_incremental_reading(env, *ctxPtr, /*final_step=*/false, progress); - - if (ctxPtr->lastReadOffset == (size_t)ctxPtr->fileSize.QuadPart) { - Logger::getInstance().log("File fully read.", LogLevel::DEBUG); - ctxPtr->lastReadOffset = 0; - ctxPtr->loadFinished = true; + return nullptr; +} - Utilities::ApplyTransferStateToFile( - ctxPtr->path.c_str(), - ctxPtr->callbackInfo, - ctxPtr->fileSize.QuadPart, - ctxPtr->fileSize.QuadPart - ); +napi_value response_callback_fn_fetch_data_wrapper(napi_env env, napi_callback_info info) +{ + return NAPI_SAFE_WRAP(env, info, response_callback_fn_fetch_data); +} - ::Sleep(CHUNKDELAYMS); +void notify_fetch_data_call(napi_env env, napi_value js_callback, void *, void *data) +{ + TransferContext *ctx = static_cast(data); - auto fileHandle = Placeholders::OpenFileHandle(ctxPtr->path.c_str(), FILE_WRITE_ATTRIBUTES, true); - CfSetPinState(fileHandle.get(), CF_PIN_STATE_PINNED, CF_SET_PIN_FLAG_NONE, nullptr); - } + napi_value js_path; + napi_create_string_utf16(env, (char16_t *)ctx->path.c_str(), ctx->path.length(), &js_path); - { - std::lock_guard lock(ctxPtr->mtx); - if (ctxPtr->loadFinished) { - ctxPtr->ready = true; - ctxPtr->cv.notify_one(); - } - } + napi_value js_callback_fn; + napi_create_function(env, "callback", NAPI_AUTO_LENGTH, response_callback_fn_fetch_data_wrapper, ctx, &js_callback_fn); - Logger::getInstance().log( - "fetch data => finished: " + std::to_string(ctxPtr->loadFinished) - + ", progress: " + std::to_string(progress), - LogLevel::DEBUG - ); + std::array js_args = {js_path, js_callback_fn}; - return create_response(env, ctxPtr->loadFinished, progress); + napi_value undefined; + napi_get_undefined(env, &undefined); + napi_call_function(env, undefined, js_callback, js_args.size(), js_args.data(), nullptr); } -static void notify_fetch_data_call(napi_env env, napi_value js_callback, void *context, void *data) +void CALLBACK fetch_data_callback_wrapper(_In_ CONST CF_CALLBACK_INFO *callbackInfo, _In_ CONST CF_CALLBACK_PARAMETERS *callbackParameters) { - Logger::getInstance().log("notify_fetch_data_call called context isolated", LogLevel::DEBUG); - napi_status status; - TransferContext *ctx = static_cast(data); - Logger::getInstance().log("notify_fetch_data_call: ctx->path = " + Logger::fromWStringToString(ctx->path), LogLevel::DEBUG); + wprintf(L"ConnectionKey: %lld, TransferKey: %lld\n", + std::bit_cast(callbackInfo->ConnectionKey), + callbackInfo->TransferKey.QuadPart); - std::wstring fileIdentityWstr; - { - const wchar_t *wchar_ptr = static_cast(ctx->callbackInfo.FileIdentity); - DWORD fileIdentityLength = ctx->callbackInfo.FileIdentityLength / sizeof(wchar_t); - fileIdentityWstr.assign(wchar_ptr, fileIdentityLength); - } - napi_value js_fileIdentityArg; - { - std::u16string u16_fileIdentity(fileIdentityWstr.begin(), fileIdentityWstr.end()); - napi_create_string_utf16(env, - u16_fileIdentity.c_str(), - u16_fileIdentity.size(), - &js_fileIdentityArg); - } + auto ctx = CreateTransferContext(callbackInfo->TransferKey); + ctx->connectionKey = callbackInfo->ConnectionKey; + ctx->fileSize = callbackInfo->FileSize; + ctx->requiredLength = callbackParameters->FetchData.RequiredLength; + ctx->requiredOffset = callbackParameters->FetchData.RequiredFileOffset; + ctx->path = std::wstring(callbackInfo->VolumeDosName) + callbackInfo->NormalizedPath; - napi_value js_response_callback_fn; - napi_create_function(env, - "responseCallback", - NAPI_AUTO_LENGTH, - response_callback_fn_fetch_data, - ctx, - &js_response_callback_fn); - - napi_value args_to_js_callback[2] = { js_fileIdentityArg, js_response_callback_fn }; + wprintf(L"Fetch data path: %s\n", ctx->path.c_str()); - Logger::getInstance().log("notify_fetch_data_call: calling JS function", LogLevel::DEBUG); - napi_value undefined, result; - status = napi_get_undefined(env, &undefined); - if (status != napi_ok) { - Logger::getInstance().log("Failed to get undefined in notify_fetch_data_call.", LogLevel::ERROR); - return; - } + napi_call_threadsafe_function(g_fetch_data_threadsafe_callback, ctx.get(), napi_tsfn_blocking); - Logger::getInstance().log("notify_fetch_data_call: setting ctx->ready to false", LogLevel::DEBUG); { - Logger::getInstance().log("notify_fetch_data_call: locking ctx->mtx", LogLevel::DEBUG); std::unique_lock lock(ctx->mtx); - ctx->ready = false; - } - - status = napi_call_function(env, - undefined, - js_callback, - 2, - args_to_js_callback, - &result); - if (status != napi_ok) { - Logger::getInstance().log("Failed to call JS function in notify_fetch_data_call.", LogLevel::ERROR); - return; + while (!ctx->ready) + { + ctx->cv.wait(lock); + } } - Logger::getInstance().log("Hydration concluded or user signaled to finish in notify_fetch_data_call.", LogLevel::INFO); + wprintf(L"Remove transfer context\n"); - ctx->lastReadOffset = 0; - ctx->loadFinished = false; - ctx->ready = false; - - // RemoveTransferContext(ctx->transferKey); + RemoveTransferContext(ctx->transferKey); } - void register_threadsafe_fetch_data_callback(const std::string &resource_name, napi_env env, InputSyncCallbacks input) { std::u16string converted_resource_name(resource_name.begin(), resource_name.end()); @@ -311,74 +191,22 @@ void register_threadsafe_fetch_data_callback(const std::string &resource_name, n napi_value resource_name_value; napi_create_string_utf16(env, converted_resource_name.c_str(), NAPI_AUTO_LENGTH, &resource_name_value); - napi_threadsafe_function tsfn_fetch_data; napi_value fetch_data_value; napi_get_reference_value(env, input.fetch_data_callback_ref, &fetch_data_value); - napi_status status = napi_create_threadsafe_function( + napi_threadsafe_function tsfn_fetch_data; + napi_create_threadsafe_function( env, fetch_data_value, - NULL, + nullptr, resource_name_value, 0, 1, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, notify_fetch_data_call, &tsfn_fetch_data); - if (status != napi_ok) - { - napi_throw_error(env, nullptr, "Failed to create fetch data threadsafe function"); - return; - } - - Logger::getInstance().log("Threadsafe function (fetch_data) created successfully.", LogLevel::DEBUG); - g_fetch_data_threadsafe_callback = tsfn_fetch_data; } - -void CALLBACK fetch_data_callback_wrapper( - _In_ CONST CF_CALLBACK_INFO *callbackInfo, - _In_ CONST CF_CALLBACK_PARAMETERS *callbackParameters) -{ - Logger::getInstance().log("fetch_data_callback_wrapper called", LogLevel::DEBUG); - - auto ctx = GetTransferContext(callbackInfo->TransferKey); - - ctx->connectionKey = callbackInfo->ConnectionKey; - ctx->fileSize = callbackInfo->FileSize; - ctx->requiredLength = callbackParameters->FetchData.RequiredLength; - ctx->requiredOffset = callbackParameters->FetchData.RequiredFileOffset; - ctx->callbackInfo = *callbackInfo; - - std::wstring path(callbackInfo->VolumeDosName); // e.g., "C:" - path.append(callbackInfo->NormalizedPath); // e.g., "\Users\file.txt" - ctx->path = path; // Result: "C:\Users\file.txt" - - Logger::getInstance().log("Full download path: " - + Logger::fromWStringToString(path), - LogLevel::INFO); - - if (g_fetch_data_threadsafe_callback == nullptr) { - Logger::getInstance().log("fetch_data_callback_wrapper: g_fetch_data_threadsafe_callback is null", - LogLevel::ERROR); - return; - } - - napi_call_threadsafe_function(g_fetch_data_threadsafe_callback, ctx.get(), napi_tsfn_blocking); - - Logger::getInstance().log("fetch_data_callback_wrapper after napi_call_threadsafe_function", LogLevel::DEBUG); - - { - std::unique_lock lock(ctx->mtx); - while (!ctx->ready) { - ctx->cv.wait(lock); - } - } - - Logger::getInstance().log("Hydration finish in fetch_data_callback_wrapper", LogLevel::INFO); - - RemoveTransferContext(ctx->transferKey); -}