private static async Task <SubscriptionResponse> GetSubscriptionResponse(User user) { var messages = new List <SyncMessageBase>(); var mockConnection = new Mock <ISyncConnection>(); mockConnection .Setup(x => x.SendJsonAsync(It.IsAny <object>())) .Returns((object obj) => { Assert.IsInstanceOf <SyncMessageBase>(obj); messages.Add((SyncMessageBase)obj); return(Task.CompletedTask); }); var principal = new ShipHubPrincipal(user.Id, user.Login); var syncContext = new SyncContext(principal, mockConnection.Object, new SyncVersions()); var changeSummary = new ChangeSummary(); changeSummary.Add(userId: user.Id); await syncContext.Sync(changeSummary); var result = messages .Where(x => x.MessageType.Equals("subscription")) .SingleOrDefault(); Assert.IsNotNull(result, "Should have been sent a SubscriptionEntry."); return((SubscriptionResponse)result); }
private void Subscribe(IUserActor userActor) { Log.Info($"{_user.Login}"); if (_syncSubscription != null) { throw new InvalidOperationException("Already subscribed to changes."); } var start = new ChangeSummary(); start.Add(userId: _user.UserId); // Changes streamed from the queue _syncSubscription = _syncManager.Changes .ObserveOn(TaskPoolScheduler.Default) .StartWith(start) // Run an initial sync no matter what. .Select(c => Observable.FromAsync(() => _syncContext.Sync(c)) .Catch <Unit, Exception>(LogError <Unit>)) .Concat() // Force sequential evaluation .Subscribe(); // Polling for updates _pollSubscription = _PollInterval .ObserveOn(TaskPoolScheduler.Default) .StartWith(0) .Select(_ => Observable.FromAsync(() => userActor.Sync()) .Catch <Unit, Exception>(LogError <Unit>)) .Concat() // Force sequential evaluation .Subscribe(); }
public async Task Run(IAsyncCollector <ChangeMessage> notifyChanges) { using (var context = new ShipHubContext()) { // Get all the tokens var tokens = await context.Tokens .AsNoTracking() .Where(x => x.Version < TokenVersion) .ToArrayAsync(); if (tokens.Any()) { Log.Info($"{tokens.Length} tokens need to be rolled."); foreach (var token in tokens) { var speedLimit = Task.Delay(1000); try { var newToken = await ResetToken(token.Token); if (newToken == null) { // Delete the single token await context.DeleteUserAccessToken(token.UserId, token.Token); Log.Info("Deleted expired token."); } else { // Replace the token await context.RollUserAccessToken(token.UserId, token.Token, newToken, TokenVersion); Log.Info("Updated valid token."); } var cs = new ChangeSummary(); cs.Add(userId: token.UserId); await notifyChanges.AddAsync(new ChangeMessage(cs)); } catch (Exception e) { Log.Exception(e, $"Error rolling token for {token.UserId}:{token.Version}"); } await speedLimit; } Log.Info($"Done processing tokens."); } } }
public async Task <bool> InternalSync(DataUpdater updater) { if (_syncLimit.CurrentCount != 0) { throw new InvalidOperationException($"{nameof(InternalSync)} requires the sync semaphore be held."); } var metaDataMeaningfullyChanged = false; try { // NOTE: The following requests are (relatively) infrequent and important for access control (repos/orgs) // Give them high priority. // Update this user's org memberships if (_orgMetadata.IsExpired()) { var orgs = await _github.OrganizationMemberships(cacheOptions : _orgMetadata, priority : RequestPriority.Interactive); if (orgs.IsOk) { metaDataMeaningfullyChanged = true; await updater.SetUserOrganizations(_userId, orgs.Date, orgs.Result); _orgActors = orgs.Result .ToDictionary(x => x.Organization.Id, x => _grainFactory.GetGrain <IOrganizationActor>(x.Organization.Id)); // Also re-evaluate their linked repos Interlocked.Increment(ref _linkedReposDesired); } // Don't update until saved. _orgMetadata = GitHubMetadata.FromResponse(orgs); } // Update this user's repo memberships var savedLinkCurrent = _linkedReposCurrent; var savedLinkDesired = _linkedReposDesired; if ((savedLinkCurrent < savedLinkDesired) || _repoMetadata.IsExpired()) { var repos = await _github.Repositories(_repoMetadata, RequestPriority.Interactive); if (repos.IsOk) { metaDataMeaningfullyChanged = true; Interlocked.Increment(ref _syncReposDesired); await updater.SetUserRepositories(_userId, repos.Date, repos.Result); _linkedRepos = repos.Result.Select(x => x.Id).ToHashSet(); // don't update _repoActors yet } // Don't update until saved. _repoMetadata = GitHubMetadata.FromResponse(repos); Interlocked.CompareExchange(ref _linkedReposCurrent, savedLinkDesired, savedLinkCurrent); } // Update this user's sync repos var savedReposCurrent = _syncReposCurrent; var savedReposDesired = _syncReposDesired; if (savedReposCurrent < savedReposDesired) { IEnumerable <g.Repository> updateRepos = null; IDictionary <long, GitHubMetadata> combinedRepoMetadata = null; var date = DateTimeOffset.UtcNow; // Not *technically* correct, but probably ok if (_syncSettings?.Include.Any() == true) { // Request all "included" repos to verify access var repoReqs = _syncSettings.Include .Where(x => !_linkedRepos.Contains(x)) // Exclude linked repos (access already known) .Select(x => (RepoId: x, Request: _github.Repository(x, _includeRepoMetadata.Val(x), RequestPriority.Interactive))) .ToArray(); await Task.WhenAll(repoReqs.Select(x => x.Request)); // Collect the "successful" responses // Check explicitly for 404, since 502s are so common :/ var successful = repoReqs .Where(x => x.Request.Status == TaskStatus.RanToCompletion) .Where(x => x.Request.Result.Status != HttpStatusCode.NotFound) .ToArray(); _includeRepoMetadata = successful.ToDictionary(x => x.RepoId, x => GitHubMetadata.FromResponse(x.Request.Result)); updateRepos = successful .Where(x => x.Request.Result.IsOk) .Select(x => x.Request.Result.Result) .ToArray(); // now union/intersect all the things combinedRepoMetadata = new Dictionary <long, GitHubMetadata>(); foreach (var repoId in _syncSettings.Include) { if (_linkedRepos.Contains(repoId)) { combinedRepoMetadata.Add(repoId, null); } else if (_includeRepoMetadata.ContainsKey(repoId)) { combinedRepoMetadata.Add(repoId, _includeRepoMetadata[repoId]); } // else drop it } } var syncRepoMetadata = await updater.UpdateAccountSyncRepositories( _userId, _syncSettings?.AutoTrack ?? true, date, updateRepos, combinedRepoMetadata, _syncSettings?.Exclude); _repoActors = syncRepoMetadata.Keys .ToDictionary(x => x, x => _grainFactory.GetGrain <IRepositoryActor>(x)); _includeRepoMetadata = syncRepoMetadata .Where(x => !_linkedRepos.Contains(x.Key)) .ToDictionary(x => x.Key, x => x.Value); // TODO: Actually detect changes. var cs = new ChangeSummary(); cs.Add(userId: _userId); updater.UnionWithExternalChanges(cs); Interlocked.CompareExchange(ref _syncReposCurrent, savedReposDesired, savedReposCurrent); } } catch (GitHubRateException) { // nothing to do } // We do this here so newly added repos and orgs sync immediately // Sync repos foreach (var repo in _repoActors.Values) { repo.Sync().LogFailure(_userInfo); } // Sync orgs foreach (var org in _orgActors.Values) { org.Sync().LogFailure(_userInfo); } return(metaDataMeaningfullyChanged); }