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();
        }
Beispiel #3
0
        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.");
                }
            }
        }
Beispiel #4
0
        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);
        }