using System; using System.Runtime.InteropServices; using System.Text; namespace LibationFileManager; /// Whether a path is inside a cloud sync root, and which provider owns it if Windows says. public readonly record struct CloudSyncStatus(bool IsSynced, string? ProviderName) { public static readonly CloudSyncStatus NotSynced = new(false, null); /// The provider's own name where Windows reported one, otherwise a generic description. public string Description => ProviderName is { Length: > 0 } name ? name : "a cloud sync folder"; } /// /// Asks Windows whether a path sits under a registered cloud sync root. Every sync engine that /// serves files on demand registers one, so this covers OneDrive, Dropbox, Google Drive, iCloud and /// the rest without naming any of them, and without inferring anything from folder names. /// /// It matters because sync clients dehydrate files into placeholders, restore old copies, and leave /// conflict copies behind. Under an install folder that the upgrader rewrites in place, that undoes /// part of an upgrade. /// public static class CloudSyncedFolders { // cfapi.h. BASIC returns only a file id, which is all that is needed to answer "is this in a // sync root at all", and is a plain 8-byte value with no marshalling to get wrong. private const int CF_SYNC_ROOT_INFO_BASIC = 0; private const int CF_SYNC_ROOT_INFO_PROVIDER = 2; private const int CF_MAX_PROVIDER_NAME_LENGTH = 255; private const int CF_MAX_PROVIDER_VERSION_LENGTH = 255; // CF_SYNC_ROOT_BASIC_INFO is a single LARGE_INTEGER. private const int BasicInfoLength = 8; // CF_SYNC_ROOT_PROVIDER_INFO is a 4-byte status enum, then two fixed WCHAR arrays. private const int ProviderStatusLength = 4; private const int ProviderNameLengthInBytes = CF_MAX_PROVIDER_NAME_LENGTH * 2; private const int ProviderInfoLength = ProviderStatusLength + ProviderNameLengthInBytes + (CF_MAX_PROVIDER_VERSION_LENGTH * 2); public static CloudSyncStatus GetSyncStatus(string? path) { if (string.IsNullOrWhiteSpace(path) || !OperatingSystem.IsWindows()) return CloudSyncStatus.NotSynced; try { return ReadWindowsSyncStatus(path); } catch (Exception ex) { // cldapi.dll is absent before Windows 10 1709, where there are no sync roots to find. // Reached while building a crash message, so it must not need Serilog. See issue #2001. StartupLog.Debug(ex, $"Could not read cloud sync root information for {path}"); return CloudSyncStatus.NotSynced; } } private static CloudSyncStatus ReadWindowsSyncStatus(string path) { var basicInfo = new byte[BasicInfoLength]; if (CfGetSyncRootInfoByPath(path, CF_SYNC_ROOT_INFO_BASIC, basicInfo, basicInfo.Length, out _) < 0) return CloudSyncStatus.NotSynced; return new CloudSyncStatus(true, TryReadProviderName(path)); } /// The provider's name, or null when only the fact of syncing could be established. private static string? TryReadProviderName(string path) { var providerInfo = new byte[ProviderInfoLength]; if (CfGetSyncRootInfoByPath(path, CF_SYNC_ROOT_INFO_PROVIDER, providerInfo, providerInfo.Length, out var returnedLength) < 0 || returnedLength < ProviderStatusLength + 2) return null; var available = Math.Min(ProviderNameLengthInBytes, returnedLength - ProviderStatusLength); var name = Encoding.Unicode.GetString(providerInfo, ProviderStatusLength, available); var terminator = name.IndexOf('\0'); if (terminator >= 0) name = name[..terminator]; return string.IsNullOrWhiteSpace(name) ? null : name.Trim(); } [DllImport("cldapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] private static extern int CfGetSyncRootInfoByPath( string filePath, int infoClass, [Out] byte[] infoBuffer, int infoBufferLength, out int returnedLength); }