/// <summary> /// Asynchronously discovers plugins. /// </summary> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns a /// <see cref="IEnumerable{PluginDiscoveryResult}" /> from the target.</returns> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public async Task <IEnumerable <PluginDiscoveryResult> > DiscoverAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_results != null) { return(_results); } await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { if (_results != null) { return(_results); } _pluginFiles = GetPluginFiles(cancellationToken); var results = new List <PluginDiscoveryResult>(); for (var i = 0; i < _pluginFiles.Count; ++i) { var pluginFile = _pluginFiles[i]; var result = new PluginDiscoveryResult(pluginFile); results.Add(result); } _results = results; } finally { _semaphore.Release(); } return(_results); }
/// <summary> /// Creates a plugin from the discovered plugin. /// We firstly check the cache for the operation claims for the given request key. /// If there is a valid cache entry, and it does contain the requested operation claim, then we start the plugin, and if need be update the cache value itself. /// If there is a valid cache entry, and it does NOT contain the requested operation claim, then we return a null. /// If there is no valid cache entry or an invalid one, we start the plugin as normally, return an active plugin even if the requested claim is not available, and write a cache entry. /// </summary> /// <param name="result">plugin discovery result</param> /// <param name="requestedOperationClaim">The requested operation claim</param> /// <param name="requestKey">plugin request key</param> /// <param name="packageSourceRepository">package source repository</param> /// <param name="serviceIndex">service index</param> /// <param name="cancellationToken">cancellation token</param> /// <returns>A plugin creation result, null if the requested plugin cannot handle the given operation claim</returns> private async Task <Tuple <bool, PluginCreationResult> > TryCreatePluginAsync( PluginDiscoveryResult result, OperationClaim requestedOperationClaim, PluginRequestKey requestKey, string packageSourceRepository, JObject serviceIndex, CancellationToken cancellationToken) { PluginCreationResult pluginCreationResult = null; var cacheEntry = new PluginCacheEntry(_pluginsCacheDirectoryPath.Value, result.PluginFile.Path, requestKey.PackageSourceRepository); ConcurrencyUtilities.ExecuteWithFileLocked(cacheEntry.CacheFileName, cacheEntry.LoadFromFile); if (cacheEntry.OperationClaims == null || cacheEntry.OperationClaims.Contains(requestedOperationClaim)) { try { if (result.PluginFile.State.Value == PluginFileState.Valid) { var plugin = await _pluginFactory.GetOrCreateAsync( result.PluginFile.Path, PluginConstants.PluginArguments, new RequestHandlers(), _connectionOptions, cancellationToken); var utilities = await PerformOneTimePluginInitializationAsync(plugin, cancellationToken); // We still make the GetOperationClaims call even if we have the operation claims cached. This is a way to self-update the cache. var operationClaims = await _pluginOperationClaims.GetOrAdd( requestKey, key => new Lazy <Task <IReadOnlyList <OperationClaim> > >(() => GetPluginOperationClaimsAsync( plugin, packageSourceRepository, serviceIndex, cancellationToken))).Value; if (!EqualityUtility.SequenceEqualWithNullCheck(operationClaims, cacheEntry.OperationClaims)) { cacheEntry.OperationClaims = operationClaims; await utilities.Value.DoOncePerPluginLifetimeAsync( nameof(PluginCacheEntry), () => ConcurrencyUtilities.ExecuteWithFileLockedAsync( cacheEntry.CacheFileName, action: async lockedToken => { await cacheEntry.UpdateCacheFileAsync(); return(Task.FromResult <object>(null)); }, token: cancellationToken), cancellationToken); } pluginCreationResult = new PluginCreationResult( plugin, utilities.Value, operationClaims); } else { pluginCreationResult = new PluginCreationResult(result.Message); } } catch (Exception e) { pluginCreationResult = new PluginCreationResult( string.Format(CultureInfo.CurrentCulture, Strings.Plugin_ProblemStartingPlugin, result.PluginFile.Path, e.Message), e); } } return(new Tuple <bool, PluginCreationResult>(pluginCreationResult != null, pluginCreationResult)); }
/// <summary> /// Creates a plugin from the given pluginDiscoveryResult. /// This plugin's operations will be source agnostic ones (Authentication) /// </summary> /// <param name="pluginDiscoveryResult">plugin discovery result</param> /// <param name="requestedOperationClaim">The requested operation claim</param> /// <param name="cancellationToken">cancellation token</param> /// <returns>A plugin creation result, null if the requested plugin cannot handle the given operation claim</returns> public Task <Tuple <bool, PluginCreationResult> > TryGetSourceAgnosticPluginAsync(PluginDiscoveryResult pluginDiscoveryResult, OperationClaim requestedOperationClaim, CancellationToken cancellationToken) { if (pluginDiscoveryResult == null) { throw new ArgumentNullException(nameof(pluginDiscoveryResult)); } return(TryCreatePluginAsync( pluginDiscoveryResult, requestedOperationClaim, new PluginRequestKey(pluginDiscoveryResult.PluginFile.Path, "Source-Agnostic"), packageSourceRepository: null, serviceIndex: null, cancellationToken: cancellationToken)); }
/// <summary> /// Asynchronously discovers plugins. /// </summary> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns a /// <see cref="IEnumerable{PluginDiscoveryResult}" /> from the target.</returns> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public async Task <IEnumerable <PluginDiscoveryResult> > DiscoverAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_results != null) { return(_results); } await _semaphore.WaitAsync(cancellationToken); try { if (_results != null) { return(_results); } _pluginFiles = GetPluginFiles(cancellationToken); var results = new List <PluginDiscoveryResult>(); for (var i = 0; i < _pluginFiles.Count; ++i) { var pluginFile = _pluginFiles[i]; string message = null; switch (pluginFile.State) { case PluginFileState.Valid: break; case PluginFileState.NotFound: message = string.Format( CultureInfo.CurrentCulture, Strings.Plugin_FileNotFound, pluginFile.Path); break; case PluginFileState.InvalidFilePath: message = string.Format( CultureInfo.CurrentCulture, Strings.Plugin_InvalidPluginFilePath, pluginFile.Path); break; case PluginFileState.InvalidEmbeddedSignature: message = string.Format( CultureInfo.CurrentCulture, Strings.Plugin_InvalidEmbeddedSignature, pluginFile.Path); break; default: throw new NotImplementedException(); } var result = new PluginDiscoveryResult(pluginFile, message); results.Add(result); } _results = results; } finally { _semaphore.Release(); } return(_results); }