Show the window before slow location probes and wrap git.exe for commit, diff, and merge.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using Explorer.Domain;
|
||||
using Explorer.Domain.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -12,6 +13,11 @@ public sealed class SourceManager
|
||||
private readonly IClock _clock;
|
||||
private readonly ILogger<SourceManager> _logger;
|
||||
|
||||
private readonly object _refreshLock = new();
|
||||
private Task<IReadOnlyList<Source>>? _refreshInFlight;
|
||||
private long _refreshCacheTimestamp;
|
||||
private static readonly TimeSpan RefreshCacheTtl = TimeSpan.FromSeconds(2);
|
||||
|
||||
public SourceManager(
|
||||
IIndexStore store,
|
||||
IVolumeService volumes,
|
||||
@@ -34,8 +40,37 @@ public sealed class SourceManager
|
||||
await RefreshOnlineStateAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Source>> RefreshOnlineStateAsync(CancellationToken cancellationToken = default)
|
||||
public Task<IReadOnlyList<Source>> RefreshOnlineStateAsync(CancellationToken cancellationToken = default)
|
||||
=> RefreshOnlineStateAsync(forceRefresh: false, cancellationToken);
|
||||
|
||||
public Task<IReadOnlyList<Source>> RefreshOnlineStateAsync(bool forceRefresh, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_refreshLock)
|
||||
{
|
||||
if (!forceRefresh && _refreshInFlight is { IsCompleted: false })
|
||||
{
|
||||
return _refreshInFlight;
|
||||
}
|
||||
|
||||
if (!forceRefresh
|
||||
&& BoundedWait.IsFresh(_refreshCacheTimestamp, RefreshCacheTtl))
|
||||
{
|
||||
return _store.Sources.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
if (forceRefresh && _refreshInFlight is { IsCompleted: false })
|
||||
{
|
||||
return _refreshInFlight;
|
||||
}
|
||||
|
||||
_refreshInFlight = RefreshOnlineStateCoreAsync(cancellationToken);
|
||||
return _refreshInFlight;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<Source>> RefreshOnlineStateCoreAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var started = Stopwatch.GetTimestamp();
|
||||
var known = (await _store.Sources.GetAllAsync(cancellationToken).ConfigureAwait(false)).ToList();
|
||||
var online = _volumes.EnumerateOnlineVolumes();
|
||||
var seenIds = new HashSet<long>();
|
||||
@@ -47,6 +82,7 @@ public sealed class SourceManager
|
||||
if (match.Source is not null && !match.Ambiguous)
|
||||
{
|
||||
source = match.Source;
|
||||
var wasOffline = source.Status == SourceStatus.Offline;
|
||||
source.LastRootPath = fp.RootPath;
|
||||
source.DisplayName = fp.DisplayName ?? source.DisplayName;
|
||||
source.Label = fp.Label ?? source.Label;
|
||||
@@ -58,10 +94,16 @@ public sealed class SourceManager
|
||||
source.LastSeenUtc = _clock.UtcNow;
|
||||
source.Status = await ResolveReachableStatusAsync(source, cancellationToken).ConfigureAwait(false);
|
||||
source.LastError = null;
|
||||
await _store.Sources.UpsertAsync(source, cancellationToken).ConfigureAwait(false);
|
||||
if (source.IsIndexed)
|
||||
await TryIndexWrite(
|
||||
() => _store.Sources.UpsertAsync(source, cancellationToken),
|
||||
"source upsert",
|
||||
source.Id).ConfigureAwait(false);
|
||||
if (source.IsIndexed && wasOffline)
|
||||
{
|
||||
await _store.Entries.MarkSourceOnlinePresentAsync(source.Id, cancellationToken).ConfigureAwait(false);
|
||||
await TryIndexWrite(
|
||||
() => _store.Entries.MarkSourceOnlinePresentAsync(source.Id, cancellationToken),
|
||||
"mark online",
|
||||
source.Id).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (fp.Kind.IsNetwork())
|
||||
@@ -99,17 +141,24 @@ public sealed class SourceManager
|
||||
continue;
|
||||
}
|
||||
|
||||
var reachable = source.LastRootPath is not null && _volumes.IsPathReachable(source.LastRootPath);
|
||||
if (reachable)
|
||||
if (source.LastRootPath is not null
|
||||
&& (source.Kind.IsNetwork() || PathRules.IsUnc(source.LastRootPath))
|
||||
&& _volumes.IsPathReachable(source.LastRootPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source.Status != SourceStatus.Offline)
|
||||
{
|
||||
await _store.Sources.UpdateStatusAsync(source.Id, SourceStatus.Offline, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await _store.Entries.MarkSourceOfflineAsync(source.Id, cancellationToken).ConfigureAwait(false);
|
||||
await TryIndexWrite(
|
||||
() => _store.Sources.UpdateStatusAsync(source.Id, SourceStatus.Offline, null, cancellationToken),
|
||||
"mark source offline",
|
||||
source.Id).ConfigureAwait(false);
|
||||
await TryIndexWrite(
|
||||
() => _store.Entries.MarkSourceOfflineAsync(source.Id, cancellationToken),
|
||||
"mark entries offline",
|
||||
source.Id).ConfigureAwait(false);
|
||||
source.Status = SourceStatus.Offline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +174,29 @@ public sealed class SourceManager
|
||||
await AddUncAsync(unc, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await _store.Sources.GetAllAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = await _store.Sources.GetAllAsync(cancellationToken).ConfigureAwait(false);
|
||||
lock (_refreshLock)
|
||||
{
|
||||
_refreshCacheTimestamp = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Refreshed {Count} sources in {Ms} ms",
|
||||
result.Count,
|
||||
(long)Stopwatch.GetElapsedTime(started).TotalMilliseconds);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task TryIndexWrite(Func<Task> work, string what, long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await work().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Skipped {What} for source {Id}; index may be busy", what, id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Source> AddUncAsync(string path, CancellationToken cancellationToken = default)
|
||||
@@ -148,6 +219,7 @@ public sealed class SourceManager
|
||||
existing.Status = _volumes.IsPathReachable(root) ? SourceStatus.Online : SourceStatus.Offline;
|
||||
await _store.Sources.UpsertAsync(existing, cancellationToken).ConfigureAwait(false);
|
||||
RememberUnc(root);
|
||||
InvalidateRefreshCache();
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -163,6 +235,7 @@ public sealed class SourceManager
|
||||
};
|
||||
source.Id = await _store.Sources.UpsertAsync(source, cancellationToken).ConfigureAwait(false);
|
||||
RememberUnc(root);
|
||||
InvalidateRefreshCache();
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -210,6 +283,7 @@ public sealed class SourceManager
|
||||
}
|
||||
|
||||
_logger.LogInformation("Forgot disconnected source {DisplayName} ({Path})", source.DisplayName, source.LastRootPath);
|
||||
InvalidateRefreshCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -258,6 +332,7 @@ public sealed class SourceManager
|
||||
? await ResolveReachableStatusAsync(existing, cancellationToken).ConfigureAwait(false)
|
||||
: SourceStatus.Offline;
|
||||
await _store.Sources.UpsertAsync(existing, cancellationToken).ConfigureAwait(false);
|
||||
InvalidateRefreshCache();
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -280,6 +355,7 @@ public sealed class SourceManager
|
||||
source.LastSeenUtc = _clock.UtcNow;
|
||||
source.Status = _volumes.IsPathReachable(fp.RootPath) ? SourceStatus.Online : SourceStatus.Offline;
|
||||
await _store.Sources.UpsertAsync(source, cancellationToken).ConfigureAwait(false);
|
||||
InvalidateRefreshCache();
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -303,6 +379,7 @@ public sealed class SourceManager
|
||||
LastSeenUtc = _clock.UtcNow
|
||||
};
|
||||
created.Id = await _store.Sources.UpsertAsync(created, cancellationToken).ConfigureAwait(false);
|
||||
InvalidateRefreshCache();
|
||||
return created;
|
||||
}
|
||||
|
||||
@@ -373,6 +450,14 @@ public sealed class SourceManager
|
||||
}
|
||||
}
|
||||
|
||||
private void InvalidateRefreshCache()
|
||||
{
|
||||
lock (_refreshLock)
|
||||
{
|
||||
_refreshCacheTimestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Source?> GetAsync(long id, CancellationToken cancellationToken = default)
|
||||
=> _store.Sources.GetAsync(id, cancellationToken);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user