2026-09-09 18:43:37 +02:00
namespace AIStudio.Tools.Services ;
2026-10-04 12:26:26 +02:00
/// <remarks>
/// A watcher here is whatever an indexer uses to notice that one of its data sources changed, a file
/// system watcher for local files, an interval for mailboxes. The indexers own them; this service
/// only decides when they run.
/// </remarks>
2026-09-09 18:43:37 +02:00
public sealed partial class DataSourceEmbeddingService
{
2026-10-04 12:26:26 +02:00
/// <summary>
/// Lets every indexer track changes to its data sources, or stops all tracking.
/// </summary>
/// <remarks>
/// Tracking runs only while local data sources refresh on their own, and not before the startup
/// hash check is done.
/// </remarks>
2026-09-09 18:43:37 +02:00
private void RefreshWatchers ()
{
if (! settingsManager . ConfigurationData . App . DataSourceIndexing . AutomaticRefresh )
{
this . RemoveAllWatchers ();
return ;
}
if ( Volatile . Read ( ref this . startupHashCheckCompleted ) == 0 )
{
logger . LogDebug ( "File watchers are not activated yet because the startup persisted hash check has not completed." );
this . RemoveAllWatchers ();
return ;
}
2026-10-04 12:26:26 +02:00
var supportedSources = this . GetConfiguredIndexedSources ();
foreach ( var indexer in this . indexers )
indexer . TrackChanges ( supportedSources . Where ( indexer . Supports ). ToList (), this . RequestRunAsync );
2026-09-09 18:43:37 +02:00
}
private void RemoveWatcher ( string dataSourceId )
{
2026-10-04 12:26:26 +02:00
foreach ( var indexer in this . indexers )
indexer . StopTracking ( dataSourceId );
2026-09-09 18:43:37 +02:00
}
2026-10-04 12:26:26 +02:00
2026-09-09 18:43:37 +02:00
private void RemoveAllWatchers ()
{
2026-10-04 12:26:26 +02:00
foreach ( var indexer in this . indexers )
indexer . StopTrackingAll ();
2026-09-09 18:43:37 +02:00
}
private void DisposeWatchers ()
{
2026-10-04 12:26:26 +02:00
foreach ( var indexer in this . indexers )
indexer . Dispose ();
2026-09-09 18:43:37 +02:00
}
2026-10-04 12:26:26 +02:00
/// <summary>
/// Queues the run an indexer asked for after it noticed a change, or because its interval came round.
/// </summary>
/// <param name="dataSourceId">The id of the data source which changed.</param>
/// <param name="refreshMode">Why the indexer asks.</param>
private async Task RequestRunAsync ( string dataSourceId , DataSourceEmbeddingRefreshMode refreshMode )
2026-09-09 18:43:37 +02:00
{
2026-10-04 12:26:26 +02:00
if (! this . TryGetConfiguredIndexedSource ( dataSourceId , out var dataSource ))
2026-09-09 18:43:37 +02:00
return ;
2026-10-04 12:26:26 +02:00
if ( this . statuses . TryGetValue ( dataSourceId , out var status ) && IsWaitingForSignIn ( status ))
2026-09-09 18:43:37 +02:00
{
2026-10-04 12:26:26 +02:00
logger . LogDebug ( "Skipped the requested run of data source '{DataSourceId}' because its sign-in failed and waits for the user. RefreshMode={RefreshMode}." , dataSourceId , refreshMode );
return ;
2026-09-09 18:43:37 +02:00
}
2026-10-04 12:26:26 +02:00
await this . QueueDataSourceAsync ( dataSource , true , refreshMode );
2026-09-09 18:43:37 +02:00
}
2026-10-04 12:26:26 +02:00
/// <summary>
/// Whether a data source waits for the user since its sign-in failed.
/// </summary>
/// <remarks>
/// Then only the user starts its next run, by saving a new password or with a retry. The indexer
/// would not sign in on any other run either, but every one of them would make the row of the
/// data source change from failed to queued and back, every interval anew.
/// </remarks>
/// <param name="status">The current status of the data source.</param>
internal static bool IsWaitingForSignIn ( DataSourceEmbeddingStatus status ) => status . Attention is DataSourceAttention . AUTH_FAILED ;
2026-09-09 18:43:37 +02:00
}