diff --git a/3758a9e6-de8b-4e1e-a4fe-53300d078e99 b/3758a9e6-de8b-4e1e-a4fe-53300d078e99 new file mode 100644 index 00000000..e69de29b diff --git a/examples/drive.ts b/examples/drive.ts index 3322737f..5d47137e 100644 --- a/examples/drive.ts +++ b/examples/drive.ts @@ -3,5 +3,5 @@ import VirtualDrive from "@/virtual-drive"; import settings from "./settings"; -export const drive = new VirtualDrive(settings.syncRootPath, settings.defaultLogPath); +export const drive = new VirtualDrive(settings.syncRootPath, settings.providerid, settings.defaultLogPath); export const logger = createLogger(settings.defaultLogPath); diff --git a/examples/register.ts b/examples/register.ts index 5ed00fbe..108bff7d 100644 --- a/examples/register.ts +++ b/examples/register.ts @@ -20,7 +20,7 @@ const handlers = { handleAdd, handleHydrate, handleDehydrate, handleChangeSize } const notify = { onTaskSuccess: async () => undefined, onTaskProcessing: async () => undefined }; const queueManager = new QueueManager(handlers, notify, settings.queuePersistPath); -drive.registerSyncRoot(settings.driveName, settings.driveVersion, settings.providerid, callbacks, settings.iconPath); +drive.registerSyncRoot(settings.driveName, settings.driveVersion, callbacks, settings.iconPath); drive.connectSyncRoot(); try { @@ -29,5 +29,5 @@ try { } catch (error) { logger.error(error); drive.disconnectSyncRoot(); - VirtualDrive.unregisterSyncRoot(settings.syncRootPath); + drive.unregisterSyncRoot(); } diff --git a/examples/unregister.ts b/examples/unregister.ts index 6c57ca89..b5446fcd 100644 --- a/examples/unregister.ts +++ b/examples/unregister.ts @@ -1,7 +1,7 @@ import VirtualDrive from "@/virtual-drive"; +import { drive } from "./drive"; import { deleteInfoItems } from "./info-items-manager"; -import settings from "./settings"; -VirtualDrive.unregisterSyncRoot(settings.syncRootPath); +drive.unregisterSyncRoot(); deleteInfoItems(); diff --git a/include/sync_root_interface/SyncRoot.h b/include/sync_root_interface/SyncRoot.h index 25238e3c..6d74f738 100644 --- a/include/sync_root_interface/SyncRoot.h +++ b/include/sync_root_interface/SyncRoot.h @@ -16,11 +16,13 @@ class SyncRoot public: static HRESULT RegisterSyncRoot(const wchar_t *syncRootPath, const wchar_t *providerName, const wchar_t *providerVersion, const GUID &providerId, const wchar_t *logoPath); static HRESULT ConnectSyncRoot(const wchar_t *syncRootPath, InputSyncCallbacks syncCallbacks, napi_env env, CF_CONNECTION_KEY *connectionKey); - static HRESULT DisconnectSyncRoot(); - static HRESULT UnregisterSyncRoot(); - static std::list GetItemsSyncRoot(const wchar_t *syncRootPath); + static HRESULT DisconnectSyncRoot(const wchar_t *syncRootPath); + static HRESULT UnregisterSyncRoot(const GUID &providerId); static std::string GetFileIdentity(const wchar_t *path); static void HydrateFile(const wchar_t *filePath); static void DehydrateFile(const wchar_t *filePath); static void DeleteFileSyncRoot(const wchar_t *path); + +private: + CF_CONNECTION_KEY connectionKey; }; \ No newline at end of file diff --git a/native-src/sync_root_interface/SyncRoot.cpp b/native-src/sync_root_interface/SyncRoot.cpp index 04125741..3cb6c7b6 100644 --- a/native-src/sync_root_interface/SyncRoot.cpp +++ b/native-src/sync_root_interface/SyncRoot.cpp @@ -1,6 +1,6 @@ -#include "stdafx.h" -#include "SyncRoot.h" #include "Callbacks.h" +#include "SyncRoot.h" +#include "stdafx.h" #include #include #include @@ -9,6 +9,7 @@ namespace fs = std::filesystem; // variable to disconect CF_CONNECTION_KEY gloablConnectionKey; +std::map connectionMap; void TransformInputCallbacksToSyncCallbacks(napi_env env, InputSyncCallbacks input) { @@ -115,16 +116,17 @@ HRESULT SyncRoot::RegisterSyncRoot(const wchar_t *syncRootPath, const wchar_t *p { try { - auto syncRootID = providerId; + // Convert GUID to string for syncRootID + wchar_t syncRootID[39]; + StringFromGUID2(providerId, syncRootID, 39); winrt::StorageProviderSyncRootInfo info; - info.Id(L"syncRootID"); + info.Id(syncRootID); auto folder = winrt::StorageFolder::GetFolderFromPathAsync(syncRootPath).get(); info.Path(folder); // The string can be in any form acceptable to SHLoadIndirectString. - info.DisplayNameResource(providerName); std::wstring completeIconResource = std::wstring(logoPath) + L",0"; @@ -135,7 +137,7 @@ HRESULT SyncRoot::RegisterSyncRoot(const wchar_t *syncRootPath, const wchar_t *p info.HydrationPolicyModifier(winrt::StorageProviderHydrationPolicyModifier::None); info.PopulationPolicy(winrt::StorageProviderPopulationPolicy::AlwaysFull); info.InSyncPolicy(winrt::StorageProviderInSyncPolicy::FileCreationTime | winrt::StorageProviderInSyncPolicy::DirectoryCreationTime); - info.Version(L"1.0.0"); + info.Version(providerVersion); info.ShowSiblingsAsGroup(false); info.HardlinkPolicy(winrt::StorageProviderHardlinkPolicy::None); @@ -145,9 +147,8 @@ HRESULT SyncRoot::RegisterSyncRoot(const wchar_t *syncRootPath, const wchar_t *p // Context std::wstring syncRootIdentity(syncRootPath); syncRootIdentity.append(L"->"); - syncRootIdentity.append(L"TestProvider"); + syncRootIdentity.append(providerName); - wchar_t const contextString[] = L"TestProviderContextString"; winrt::IBuffer contextBuffer = winrt::CryptographicBuffer::ConvertStringToBinary(syncRootIdentity.data(), winrt::BinaryStringEncoding::Utf8); info.Context(contextBuffer); @@ -165,20 +166,26 @@ HRESULT SyncRoot::RegisterSyncRoot(const wchar_t *syncRootPath, const wchar_t *p catch (...) { wprintf(L"Could not register the sync root, hr %08x\n", static_cast(winrt::to_hresult())); + return E_FAIL; } } -HRESULT SyncRoot::UnregisterSyncRoot() +HRESULT SyncRoot::UnregisterSyncRoot(const GUID &providerId) { try { + // Convert GUID to string for syncRootID + wchar_t syncRootID[39]; + StringFromGUID2(providerId, syncRootID, 39); + Logger::getInstance().log("Unregistering sync root.", LogLevel::INFO); - winrt::StorageProviderSyncRootManager::Unregister(L"syncRootID"); + winrt::StorageProviderSyncRootManager::Unregister(syncRootID); return S_OK; } catch (...) { wprintf(L"Could not unregister the sync root, hr %08x\n", static_cast(winrt::to_hresult())); + return E_FAIL; } } @@ -205,38 +212,38 @@ HRESULT SyncRoot::ConnectSyncRoot(const wchar_t *syncRootPath, InputSyncCallback CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO | CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH, connectionKey); wprintf(L"Connection key: %llu\n", *connectionKey); - gloablConnectionKey = *connectionKey; + if (SUCCEEDED(hr)) + { + connectionMap[syncRootPath] = *connectionKey; + } return hr; } catch (const std::exception &e) { wprintf(L"Excepción capturada: %hs\n", e.what()); - // Aquí puedes decidir si retornar un código de error específico o mantener el E_FAIL. + return E_FAIL; } catch (...) { wprintf(L"Excepción desconocida capturada\n"); - // Igualmente, puedes decidir el código de error a retornar. + return E_FAIL; } } // disconection sync root -HRESULT SyncRoot::DisconnectSyncRoot() +HRESULT SyncRoot::DisconnectSyncRoot(const wchar_t *syncRootPath) { - Logger::getInstance().log("Disconnecting sync root.", LogLevel::INFO); - try + auto it = connectionMap.find(syncRootPath); + if (it != connectionMap.end()) { - HRESULT hr = CfDisconnectSyncRoot(gloablConnectionKey); + HRESULT hr = CfDisconnectSyncRoot(it->second); + if (SUCCEEDED(hr)) + { + connectionMap.erase(it); + } return hr; } - catch (const std::exception &e) - { - Logger::getInstance().log("Exception caught: " + std::string(e.what()), LogLevel::ERROR); - } - catch (...) - { - Logger::getInstance().log("Unknown exception caught.", LogLevel::ERROR); - } + return E_FAIL; } // struct diff --git a/native-src/virtual_drive/Wrappers.cpp b/native-src/virtual_drive/Wrappers.cpp index a81e57e1..e8ba0e6e 100644 --- a/native-src/virtual_drive/Wrappers.cpp +++ b/native-src/virtual_drive/Wrappers.cpp @@ -112,19 +112,27 @@ napi_value UnregisterSyncRootWrapper(napi_env env, napi_callback_info args) if (argc < 1) { - napi_throw_error(env, nullptr, "The sync root path is required for UnregisterSyncRoot"); + napi_throw_error(env, nullptr, "The provider ID is required for UnregisterSyncRoot"); return nullptr; } - LPCWSTR syncRootPath; - size_t pathLength; - napi_get_value_string_utf16(env, argv[0], nullptr, 0, &pathLength); - syncRootPath = new WCHAR[pathLength + 1]; - napi_get_value_string_utf16(env, argv[0], reinterpret_cast(const_cast(syncRootPath)), pathLength + 1, nullptr); + GUID providerId; + LPCWSTR providerIdStr; + size_t providerIdStrLength; + napi_get_value_string_utf16(env, argv[0], nullptr, 0, &providerIdStrLength); + providerIdStr = new WCHAR[providerIdStrLength + 1]; + napi_get_value_string_utf16(env, argv[0], reinterpret_cast(const_cast(providerIdStr)), providerIdStrLength + 1, nullptr); - HRESULT result = SyncRoot::UnregisterSyncRoot(); + if (FAILED(CLSIDFromString(providerIdStr, &providerId))) + { + napi_throw_error(env, nullptr, "Invalid GUID format"); + delete[] providerIdStr; + return nullptr; + } - delete[] syncRootPath; + HRESULT result = SyncRoot::UnregisterSyncRoot(providerId); + + delete[] providerIdStr; napi_value napiResult; napi_create_int32(env, static_cast(result), &napiResult); @@ -429,8 +437,8 @@ napi_value DisconnectSyncRootWrapper(napi_env env, napi_callback_info args) syncRootPath = new WCHAR[pathLength + 1]; napi_get_value_string_utf16(env, argv[0], reinterpret_cast(const_cast(syncRootPath)), pathLength + 1, nullptr); - HRESULT result = SyncRoot::DisconnectSyncRoot(); - // wprintf(L"DisconnectSyncRootWrapper: %08x\n", static_cast(result)); + HRESULT result = SyncRoot::DisconnectSyncRoot(syncRootPath); + delete[] syncRootPath; napi_value napiResult; diff --git a/src/addon-wrapper.ts b/src/addon-wrapper.ts index 5e39f33a..14a2097b 100644 --- a/src/addon-wrapper.ts +++ b/src/addon-wrapper.ts @@ -32,13 +32,13 @@ export class Addon { return this.parseAddonZod("connectSyncRoot", result); } - unregisterSyncRoot({ syncRootPath }: { syncRootPath: string }) { - const result = addon.unregisterSyncRoot(syncRootPath); + unregisterSyncRoot({ providerId }: { providerId: string }) { + const result = addon.unregisterSyncRoot(providerId); return this.parseAddonZod("unregisterSyncRoot", result); } - disconnectSyncRoot() { - return addon.disconnectSyncRoot(this.syncRootPath); + disconnectSyncRoot({ syncRootPath }: { syncRootPath: string }) { + return addon.disconnectSyncRoot(syncRootPath); } addLogger({ logPath }: { logPath: string }) { diff --git a/src/types/callbacks.type.ts b/src/types/callbacks.type.ts index 96b09a5d..7e3be7d4 100644 --- a/src/types/callbacks.type.ts +++ b/src/types/callbacks.type.ts @@ -1,7 +1,11 @@ export type NapiCallbackFunction = (...args: any[]) => any; +export type FilePlaceholderIdPrefixType = "FILE:"; + +export type FilePlaceholderId = `${FilePlaceholderIdPrefixType}${string}`; + export type TFetchDataCallback = ( - id: string, + id: FilePlaceholderId, callback: (data: boolean, path: string, errorHandler?: () => void) => Promise<{ finished: boolean; progress: number }>, ) => void; diff --git a/src/virtual-drive.ts b/src/virtual-drive.ts index 3b881b24..4c68b25c 100644 --- a/src/virtual-drive.ts +++ b/src/virtual-drive.ts @@ -1,14 +1,13 @@ -import path, { join, win32 } from "path"; +import { logger } from "examples/drive"; import fs from "fs"; -import { Watcher } from "./watcher/watcher"; -import { Callbacks } from "./types/callbacks.type"; -import { IQueueManager } from "./queue/queueManager"; - -import { createLogger } from "./logger"; -import { Addon } from "./addon-wrapper"; +import path, { join, win32 } from "path"; import winston from "winston"; -const addon = new Addon(); +import { Addon } from "./addon-wrapper"; +import { createLogger } from "./logger"; +import { IQueueManager } from "./queue/queueManager"; +import { Callbacks } from "./types/callbacks.type"; +import { Watcher } from "./watcher/watcher"; const PLACEHOLDER_ATTRIBUTES = { FILE_ATTRIBUTE_READONLY: 0x1, @@ -19,15 +18,20 @@ const PLACEHOLDER_ATTRIBUTES = { class VirtualDrive { syncRootPath: string; + providerId: string; callbacks?: Callbacks; watcher = new Watcher(); logger: winston.Logger; - constructor(syncRootPath: string, loggerPath: string) { + addon: Addon; + + constructor(syncRootPath: string, providerId: string, loggerPath: string) { + this.addon = new Addon(); this.syncRootPath = this.convertToWindowsPath(syncRootPath); loggerPath = this.convertToWindowsPath(loggerPath); + this.providerId = providerId; - addon.syncRootPath = this.syncRootPath; + this.addon.syncRootPath = this.syncRootPath; this.createSyncRootFolder(); this.addLoggerPath(loggerPath); @@ -52,15 +56,15 @@ class VirtualDrive { } addLoggerPath(logPath: string) { - addon.addLogger({ logPath }); + this.addon.addLogger({ logPath }); } getPlaceholderState(path: string) { - return addon.getPlaceholderState({ path: this.fixPath(path) }); + return this.addon.getPlaceholderState({ path: this.fixPath(path) }); } getPlaceholderWithStatePending() { - return addon.getPlaceholderWithStatePending(); + return this.addon.getPlaceholderWithStatePending(); } createSyncRootFolder() { @@ -70,11 +74,11 @@ class VirtualDrive { } getFileIdentity(relativePath: string) { - return addon.getFileIdentity({ path: this.fixPath(relativePath) }); + return this.addon.getFileIdentity({ path: this.fixPath(relativePath) }); } async deleteFileSyncRoot(relativePath: string) { - return addon.deleteFileSyncRoot({ path: this.fixPath(relativePath) }); + return this.addon.deleteFileSyncRoot({ path: this.fixPath(relativePath) }); } connectSyncRoot() { @@ -82,7 +86,14 @@ class VirtualDrive { throw new Error("Callbacks are not defined"); } - return addon.connectSyncRoot({ callbacks: this.callbacks }); + const connectionKey = this.addon.connectSyncRoot({ callbacks: this.callbacks }); + + this.logger.debug({ fn: "connectSyncRoot", connectionKey }); + return connectionKey; + } + + disconnectSyncRoot() { + this.addon.disconnectSyncRoot({ syncRootPath: this.syncRootPath }); } createPlaceholderFile( @@ -93,13 +104,13 @@ class VirtualDrive { creationTime: number, lastWriteTime: number, lastAccessTime: number, - basePath: string + basePath: string, ): any { const creationTimeStr = this.convertToWindowsTime(creationTime).toString(); const lastWriteTimeStr = this.convertToWindowsTime(lastWriteTime).toString(); const lastAccessTimeStr = this.convertToWindowsTime(lastAccessTime).toString(); - return addon.createPlaceholderFile({ + return this.addon.createPlaceholderFile({ fileName, fileId, fileSize, @@ -107,7 +118,7 @@ class VirtualDrive { creationTime: creationTimeStr, lastWriteTime: lastWriteTimeStr, lastAccessTime: lastAccessTimeStr, - basePath + basePath, }); } @@ -120,13 +131,13 @@ class VirtualDrive { creationTime: number, lastWriteTime: number, lastAccessTime: number, - path: string + path: string, ) { const creationTimeStr = this.convertToWindowsTime(creationTime).toString(); const lastWriteTimeStr = this.convertToWindowsTime(lastWriteTime).toString(); const lastAccessTimeStr = this.convertToWindowsTime(lastAccessTime).toString(); - - return addon.createPlaceholderDirectory({ + + return this.addon.createPlaceholderDirectory({ itemName, itemId, isDirectory, @@ -135,36 +146,27 @@ class VirtualDrive { creationTime: creationTimeStr, lastWriteTime: lastWriteTimeStr, lastAccessTime: lastAccessTimeStr, - path + path, }); } - async registerSyncRoot( - providerName: string, - providerVersion: string, - providerId: string, - callbacks: Callbacks, - logoPath: string - ): Promise { + async registerSyncRoot(providerName: string, providerVersion: string, callbacks: Callbacks, logoPath: string): Promise { this.callbacks = callbacks; - return addon.registerSyncRoot({ + console.log("Registering sync root: ", this.syncRootPath); + return this.addon.registerSyncRoot({ providerName, providerVersion, - providerId, - logoPath + providerId: this.providerId, + logoPath, }); } - static unregisterSyncRoot(syncRootPath: string) { - return addon.unregisterSyncRoot({ syncRootPath }); + unregisterSyncRoot() { + return this.addon.unregisterSyncRoot({ providerId: this.providerId }); } - watchAndWait( - path: string, - queueManager: IQueueManager, - loggerPath: string - ): void { - this.watcher.addon = addon; + watchAndWait(path: string, queueManager: IQueueManager, loggerPath: string): void { + this.watcher.addon = this.addon; this.watcher.queueManager = queueManager; this.watcher.logger = this.logger; this.watcher.syncRootPath = this.syncRootPath; @@ -189,7 +191,7 @@ class VirtualDrive { itemId: string, size: number = 0, creationTime: number = Date.now(), - lastWriteTime: number = Date.now() + lastWriteTime: number = Date.now(), ) { const fullPath = path.join(this.syncRootPath, relativePath); const splitPath = relativePath.split("/").filter((p) => p); @@ -211,7 +213,7 @@ class VirtualDrive { creationTime, lastWriteTime, Date.now(), - currentPath + currentPath, ); } catch (error) { //@ts-ignore @@ -224,7 +226,7 @@ class VirtualDrive { itemId: string, size: number = 0, creationTime: number = Date.now(), - lastWriteTime: number = Date.now() + lastWriteTime: number = Date.now(), ) { const splitPath = relativePath.split("/").filter((p) => p); const directoryPath = path.resolve(this.syncRootPath); @@ -244,7 +246,7 @@ class VirtualDrive { creationTime, lastWriteTime, Date.now(), - currentPath + currentPath, ); } } @@ -252,31 +254,23 @@ class VirtualDrive { } } - disconnectSyncRoot() { - return addon.disconnectSyncRoot(); - } - - updateSyncStatus( - itemPath: string, - isDirectory: boolean, - sync: boolean = true - ) { - return addon.updateSyncStatus({ path: this.fixPath(itemPath), isDirectory, sync }); + updateSyncStatus(itemPath: string, isDirectory: boolean, sync: boolean = true) { + return this.addon.updateSyncStatus({ path: this.fixPath(itemPath), isDirectory, sync }); } convertToPlaceholder(itemPath: string, id: string) { - return addon.convertToPlaceholder({ path: this.fixPath(itemPath), id }); + return this.addon.convertToPlaceholder({ path: this.fixPath(itemPath), id }); } updateFileIdentity(itemPath: string, id: string, isDirectory: boolean) { - return addon.updateFileIdentity({ path: this.fixPath(itemPath), id, isDirectory }); + return this.addon.updateFileIdentity({ path: this.fixPath(itemPath), id, isDirectory }); } dehydrateFile(itemPath: string) { - return addon.dehydrateFile({ path: this.fixPath(itemPath) }); + return this.addon.dehydrateFile({ path: this.fixPath(itemPath) }); } hydrateFile(itemPath: string) { - return addon.hydrateFile({ path: this.fixPath(itemPath) }); + return this.addon.hydrateFile({ path: this.fixPath(itemPath) }); } } diff --git a/src/virtual-drive.unit.test.ts b/src/virtual-drive.unit.test.ts index 14c9c711..712d6366 100644 --- a/src/virtual-drive.unit.test.ts +++ b/src/virtual-drive.unit.test.ts @@ -29,8 +29,10 @@ describe("VirtualDrive", () => { }); describe("When convertToWindowsPath is called", () => { + const providerId = v4(); + // Arrange - const drive = new VirtualDrive(syncRootPath, logPath); + const drive = new VirtualDrive(syncRootPath, providerId, logPath); it("When unix path, then convert to windows path", () => { // Assert @@ -46,8 +48,10 @@ describe("VirtualDrive", () => { }); describe("When fixPath is called", () => { + const providerId = v4(); + // Arrange - const drive = new VirtualDrive(syncRootPath, logPath); + const drive = new VirtualDrive(syncRootPath, providerId, logPath); it("When absolute windows path, then do not modify it", () => { // Assert @@ -80,8 +84,10 @@ describe("VirtualDrive", () => { // Arrange mockExistsSync.mockReturnValue(false); + const providerId = v4(); + // Act - new VirtualDrive(syncRootPath, logPath); + new VirtualDrive(syncRootPath, providerId, logPath); // Assert expect(fs.mkdirSync).toHaveBeenCalledWith(syncRootPath, { @@ -93,8 +99,10 @@ describe("VirtualDrive", () => { // Arrange mockExistsSync.mockReturnValue(true); + const providerId = v4(); + // Act - new VirtualDrive(syncRootPath, logPath); + new VirtualDrive(syncRootPath, providerId, logPath); // Assert expect(fs.mkdirSync).not.toHaveBeenCalled(); @@ -102,7 +110,9 @@ describe("VirtualDrive", () => { it("Then it calls addon.addLoggerPath with logPath provided", () => { // Act - new VirtualDrive(syncRootPath, logPath); + const providerId = v4(); + + new VirtualDrive(syncRootPath, providerId, logPath); // Assert expect(addon.addLoggerPath).toHaveBeenCalledWith(logPath); @@ -113,7 +123,9 @@ describe("VirtualDrive", () => { it("Then it calls addon.createPlaceholderFile", () => { // Arrange mockExistsSync.mockReturnValue(true); - const drive = new VirtualDrive(syncRootPath, logPath); + const providerId = v4(); + + const drive = new VirtualDrive(syncRootPath, providerId, logPath); // Act drive.createFileByPath("folder/subfolder/file.txt", "file-id", 1234, 1660000000000, 1660000001000); @@ -135,16 +147,16 @@ describe("VirtualDrive", () => { describe("When call registerSyncRoot", () => { it("Then it assigns callbacks and calls addon.registerSyncRoot", async () => { // Arrange - const drive = new VirtualDrive(syncRootPath, logPath); + const providerId = v4(); + const drive = new VirtualDrive(syncRootPath, providerId, logPath); const providerName = "MyProvider"; const providerVersion = "1.0.0"; - const providerId = v4(); const logoPath = "C:\\iconPath"; const callbacks = mockDeep(); // Act expect(drive.callbacks).toBe(undefined); - await drive.registerSyncRoot(providerName, providerVersion, providerId, callbacks, logoPath); + await drive.registerSyncRoot(providerName, providerVersion, callbacks, logoPath); // Assert expect(drive.callbacks).not.toBe(undefined);