mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-13 06:07:30 -04:00
Libation's releases are ReadyToRun, so a Serilog reference resolves lazily when the line holding it runs. An install folder whose Serilog.dll is missing or older than the build therefore threw inside the very catch blocks that were meant to report the problem, which destroyed the original exception and aborted the in-app upgrade recovery before it could roll anything back. Add StartupLog: a Serilog-free, never-throwing buffer for the window before ConfigureLogging runs, replayed into Serilog once logging exists. Move the pre-logging call sites onto it, including the crash-message and crash-dialog paths that run precisely when the install is broken. Those messages were silent before this change: Serilog.Log.Logger is still Serilog's silent logger until App.RunMigrations. They now reach the log file. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
96 lines
3.8 KiB
C#
96 lines
3.8 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace LibationFileManager;
|
|
|
|
/// <summary>Whether a path is inside a cloud sync root, and which provider owns it if Windows says.</summary>
|
|
public readonly record struct CloudSyncStatus(bool IsSynced, string? ProviderName)
|
|
{
|
|
public static readonly CloudSyncStatus NotSynced = new(false, null);
|
|
|
|
/// <summary>The provider's own name where Windows reported one, otherwise a generic description.</summary>
|
|
public string Description => ProviderName is { Length: > 0 } name ? name : "a cloud sync folder";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>The provider's name, or null when only the fact of syncing could be established.</summary>
|
|
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);
|
|
}
|