/// <summary> /// FreshenThread() asks the MessagePuller to refresh the specified thread as soon /// as possible, ignoring its normal algorithm priorities. /// </summary> /// <remarks> /// Since this operation may require multiple REST service calls and is subject to /// Yammer rate limits, it may still take a while to process. The returned /// FreshenThreadRequest object can be used to track the progress. Only one /// FreshenThread() request can be active at a time; any previous requests are /// canceled by a new call to FreshenThread(). Note that no processing will occur /// unless MessagePuller.Enabled=true and MessagePuller.Process() is being called /// at regular intervals. /// </remarks> public FreshenThreadRequest FreshenThread(YamsterThread thread) { FreshenThreadRequest interruptedRequest = freshenThreadRequest; freshenThreadRequest = new FreshenThreadRequest(thread, this); if (interruptedRequest != null) { interruptedRequest.SetError(new Exception("The operation was interrupted by a more recent request")); } return(freshenThreadRequest); }
async Task ProcessAsync() { this.appContext.RequireForegroundThread(); if (!this.enabled) { return; } DateTime nowUtc = DateTime.UtcNow; // Don't exceed the Yammer throttling limit. For a FreshenThread() request, // we increase the priority. if (!yamsterApi.IsSafeToRequest(increasedPriority: freshenThreadRequest != null)) { return; } // Start by assuming we're not up to date, unless proven otherwise UpToDate = false; // 1. Is there a request to freshen a specific thread? if (freshenThreadRequest != null) { var freshenedThread = yamsterCoreDb.SyncingThreads .Query("WHERE ThreadId = " + freshenThreadRequest.Thread.ThreadId) .FirstOrDefault(); if (freshenThreadRequest.State == FreshenThreadState.Queued) { freshenThreadRequest.SetState(FreshenThreadState.Processing); // Is there already an existing gap for this thread? if (freshenedThread != null) { // Yes, simply reopen it freshenedThread.LastPulledMessageId = null; } else { // No, so create a new one freshenedThread = new DbSyncingThread(); freshenedThread.FeedId = freshenThreadRequest.Thread.GroupId; freshenedThread.ThreadId = freshenThreadRequest.Thread.ThreadId; // NOTE: The thread is presumed to be contiguous at this point. long latestMessageInDb = yamsterArchiveDb.Mapper.QueryScalar <long>( "SELECT MAX(Id) FROM " + this.yamsterArchiveDb.ArchiveMessages.TableName + " WHERE ThreadId = " + freshenThreadRequest.Thread.ThreadId.ToString()); freshenedThread.StopMessageId = latestMessageInDb; freshenedThread.LastPulledMessageId = null; yamsterCoreDb.SyncingThreads.InsertRecord(freshenedThread); } } if (freshenThreadRequest.State != FreshenThreadState.Processing) { // This should be impossible freshenThreadRequest.SetError(new Exception("State machine error")); freshenThreadRequest = null; return; } await ProcessGappedThreadAsync(freshenedThread); this.appContext.RequireForegroundThread(); return; } // 2. Are there any syncing threads? We must finish them before processing more spans var syncingThread = yamsterCoreDb.SyncingThreads .QueryAll() .FirstOrDefault(); if (syncingThread != null) { // Start at the top of the list await ProcessGappedThreadAsync(syncingThread); this.appContext.RequireForegroundThread(); return; } // Get the list of subscribed feeds List <DbGroupState> groupsToSync = yamsterCoreDb.GroupStates .Query("WHERE ShouldSync").ToList(); bool forceCheckNew = false; List <JsonSyncingFeed> syncingFeeds = groupsToSync .Select( groupState => { var syncingFeed = yamsterCoreDb.GetJsonSyncingFeed(groupState.GroupId) ?? new JsonSyncingFeed(); syncingFeed.GroupState = groupState; syncingFeed.FeedId = groupState.GroupId; return(syncingFeed); } ).ToList(); if (yamsterCoreDb.Properties.SyncInbox) { // The Inbox is not a real group, so it doesn't have a DbGroupState record. var inboxSyncingFeed = yamsterCoreDb.GetJsonSyncingFeed(YamsterGroup.InboxFeedId) ?? new JsonSyncingFeed(); inboxSyncingFeed.GroupState = null; inboxSyncingFeed.FeedId = YamsterGroup.InboxFeedId; syncingFeeds.Insert(0, inboxSyncingFeed); } JsonSyncingFeed chosenSyncingFeed = null; // 3. Should we interrupt work on the history and instead check for new messages? if (Algorithm == MessagePullerAlgorithm.OptimizeReading) { TimeSpan longCheckNewDuration = TimeSpan.FromMinutes(7); chosenSyncingFeed = syncingFeeds // Choose a feed that wasn't synced recently, but only if it has already // done some work on its history .Where(x => x.SpanCyclesSinceCheckNew >= 2 && (nowUtc - x.LastCheckNewUtc) > longCheckNewDuration) // Pick the feed that was synced least recently .OrderBy(x => x.LastCheckNewUtc) .FirstOrDefault(); if (chosenSyncingFeed != null) { forceCheckNew = true; } } // 4. Are there any incomplete histories? If so, choose the feed who // made the least progress syncing so far if (chosenSyncingFeed == null) { // There are two kinds of feeds that need work: // 1. If it has gaps in the spans // 2. If we did not reach the beginning of the stream yet var nextHistoricalFeed = syncingFeeds .Where(x => x.HasSpanGaps || !x.ReachedEmptyResult) .OrderByDescending(x => x.GetNextOlderThanTime()) .FirstOrDefault(); if (nextHistoricalFeed != null) { var time = nextHistoricalFeed.GetNextOlderThanTime(); if (time != DateTime.MaxValue) // don't show this degenerate value in the UI { HistoryProgress = time; } if (HistoryLimitDays > 0) { // If HistoryLimitDays is enabled, then don't pull threads that are // older than the historyLimit DateTime historyLimit = DateTime.Now.Date.Subtract(TimeSpan.FromDays(HistoryLimitDays)); if (nextHistoricalFeed.GetNextOlderThanTime() >= historyLimit) { chosenSyncingFeed = nextHistoricalFeed; } } else { chosenSyncingFeed = nextHistoricalFeed; } } } // 5. If all the histories are complete, then check for new messages at periodic intervals if (chosenSyncingFeed == null) { TimeSpan shortCheckNewDuration = TimeSpan.FromMinutes(3); chosenSyncingFeed = syncingFeeds // Don't sync more often than shortCheckNewDuration .Where(x => (nowUtc - x.LastCheckNewUtc) > shortCheckNewDuration) // Pick the feed that was synced least recently .OrderBy(x => x.LastCheckNewUtc) .FirstOrDefault(); if (chosenSyncingFeed != null) { forceCheckNew = true; } } UpToDate = chosenSyncingFeed == null; if (!UpToDate) { await ProcessSpanAsync(chosenSyncingFeed, forceCheckNew); this.appContext.RequireForegroundThread(); } else { Debug.WriteLine("Up to date."); } }