/// <summary> /// Gets the list of categories from the upstream server and adds them to the specified metadata collection. /// </summary> /// <param name="destination">Metadata collection where to add the results. If the collection implements <see cref="IMetadataSource"/>, only delta changes are retrieved and added to the destination.</param> public async Task GetCategories(IMetadataSink destination) { var deltaMetadataSource = destination as IMetadataSource; var progress = new MetadataQueryProgress(); if (AccessToken == null || AccessToken.ExpiresIn(TimeSpan.FromMinutes(2))) { await RefreshAccessToken(deltaMetadataSource?.UpstreamAccountName, deltaMetadataSource?.UpstreamAccountGuid); } // If no configuration is known, query it now if (ConfigData == null) { await RefreshServerConfigData(); } // Query IDs for all categories known to the upstream server progress.CurrentTask = MetadataQueryStage.GetRevisionIdsStart; MetadataQueryProgress?.Invoke(this, progress); var categoriesAnchor = deltaMetadataSource?.CategoriesAnchor; var categoryQueryResult = await GetCategoryIds(categoriesAnchor); progress.CurrentTask = MetadataQueryStage.GetRevisionIdsEnd; MetadataQueryProgress?.Invoke(this, progress); var cachedCategories = deltaMetadataSource?.CategoriesIndex; // Find all updates that did not change var unchangedUpdates = GetUnchangedUpdates(cachedCategories, categoryQueryResult.identities); // Create the list of updates to query data for. Remove those updates that were reported as "new" // but for which we already have metadata var updatesToRetrieveDataFor = categoryQueryResult.identities.Where( newUpdateId => !unchangedUpdates.ContainsKey(newUpdateId)); // Retrieve metadata for all new categories progress.CurrentTask = MetadataQueryStage.GetUpdateMetadataStart; progress.Maximum = updatesToRetrieveDataFor.Count(); progress.Current = 0; MetadataQueryProgress?.Invoke(this, progress); destination.SetCategoriesAnchor(categoryQueryResult.anchor); GetUpdateDataForIds( updatesToRetrieveDataFor.Select(id => id.Raw).ToList(), destination); progress.CurrentTask = MetadataQueryStage.GetUpdateMetadataEnd; MetadataQueryProgress?.Invoke(this, progress); }
/// <summary> /// Performs authentication with an upstream update server, using a previously issued service access token. /// </summary> /// <remarks> /// Refreshing an old token with this method is faster than obtaining a new token as it requires fewer server roundtrips. /// /// If the access cookie does not expire within 30 minutes, the function succeeds and the old token is returned. /// </remarks> /// <param name="cachedAccessToken">The previously issued access token.</param> /// <returns>The new ServiceAccessToken</returns> public async Task <ServiceAccessToken> Authenticate(ServiceAccessToken cachedAccessToken) { if (cachedAccessToken == null) { return(await Authenticate()); } ServiceAccessToken newAccessToken = new ServiceAccessToken() { AuthCookie = cachedAccessToken.AuthCookie, AccessCookie = cachedAccessToken.AccessCookie, AuthenticationInfo = cachedAccessToken.AuthenticationInfo }; // Check if the cached access cookie expires in the next 30 minutes; if not, return the new token // with this cookie if (!newAccessToken.ExpiresIn(TimeSpan.FromMinutes(30))) { return(newAccessToken); } bool restartAuthenticationRequired = false; // Get a new access cookie try { newAccessToken.AccessCookie = await GetServerAccessCookie(newAccessToken.AuthCookie); } catch (UpstreamServerException ex) { if (ex.ErrorCode == UpstreamServerErrorCode.InvalidAuthorizationCookie) { // The authorization cookie is expired or invalid. Restart the authentication protocol restartAuthenticationRequired = true; } else { throw ex; } } return(restartAuthenticationRequired ? await Authenticate() : newAccessToken); }