OSDN Git Service

TimelineSchedulerのテストコードを追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Fri, 4 Feb 2022 15:52:46 +0000 (00:52 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Fri, 4 Feb 2022 16:25:56 +0000 (01:25 +0900)
OpenTween.Tests/TimelineScheduerTest.cs [new file with mode: 0644]
OpenTween/TimelineScheduler.cs
OpenTween/Tween.cs

diff --git a/OpenTween.Tests/TimelineScheduerTest.cs b/OpenTween.Tests/TimelineScheduerTest.cs
new file mode 100644 (file)
index 0000000..087f9dd
--- /dev/null
@@ -0,0 +1,189 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2022 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
+// for more details.
+//
+// You should have received a copy of the GNU General public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace OpenTween
+{
+    public class TimelineScheduerTest
+    {
+        private class TestTimelineScheduler : TimelineScheduler
+        {
+            public MockTimer mockTimer = new MockTimer(() => Task.CompletedTask);
+
+            public TestTimelineScheduler()
+                : base()
+            {
+            }
+
+            protected override ITimer CreateTimer(Func<Task> callback)
+                => this.mockTimer = new MockTimer(callback);
+        }
+
+        [Fact]
+        public async Task Callback_Test()
+        {
+            using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
+            {
+                using var scheduler = new TestTimelineScheduler();
+                var mockTimer = scheduler.mockTimer;
+
+                Assert.False(mockTimer.IsTimerRunning);
+
+                var count = 0;
+                scheduler.UpdateFunc[TimelineSchedulerTaskType.Home] = () =>
+                {
+                    count++;
+                    TestUtils.DriftTime(TimeSpan.FromSeconds(10));
+                    return Task.CompletedTask;
+                };
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = TimeSpan.FromMinutes(1);
+                scheduler.Enabled = true;
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.Zero, mockTimer.DueTime);
+
+                // 0:00:00 - 0:00:10
+                await mockTimer.Invoke();
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromSeconds(50), mockTimer.DueTime); // UpdateFunc 内で掛かった時間を減算する
+            }
+        }
+
+        [Fact]
+        public async Task Callback_SystemResumeTest()
+        {
+            using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
+            {
+                using var scheduler = new TestTimelineScheduler();
+                var mockTimer = scheduler.mockTimer;
+
+                Assert.False(mockTimer.IsTimerRunning);
+
+                var count = 0;
+                scheduler.UpdateFunc[TimelineSchedulerTaskType.Home] = () =>
+                {
+                    count++;
+                    return Task.CompletedTask;
+                };
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = TimeSpan.FromMinutes(1);
+                scheduler.UpdateAfterSystemResume = TimeSpan.FromMinutes(10);
+                scheduler.Enabled = true;
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.Zero, mockTimer.DueTime);
+
+                // 0:00:00
+                await mockTimer.Invoke();
+
+                Assert.Equal(1, count); // invoked
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
+
+                scheduler.SystemResumed();
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(10), mockTimer.DueTime);
+
+                // 0:10:00
+                TestUtils.DriftTime(TimeSpan.FromMinutes(10));
+                await mockTimer.Invoke();
+
+                Assert.Equal(2, count); // invoked
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
+            }
+        }
+
+        [Fact]
+        public void RefreshSchedule_Test()
+        {
+            using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
+            {
+                using var scheduler = new TestTimelineScheduler();
+                var mockTimer = scheduler.mockTimer;
+
+                scheduler.Enabled = true;
+                Assert.False(mockTimer.IsTimerRunning);
+
+                scheduler.LastUpdatedAt[TimelineSchedulerTaskType.Home] = DateTimeUtc.Now;
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = TimeSpan.FromMinutes(1);
+                scheduler.RefreshSchedule();
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
+            }
+        }
+
+        [Fact]
+        public void RefreshSchedule_EmptyTest()
+        {
+            using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
+            {
+                using var scheduler = new TestTimelineScheduler();
+                var mockTimer = scheduler.mockTimer;
+
+                scheduler.Enabled = true;
+                Assert.False(mockTimer.IsTimerRunning);
+
+                scheduler.RefreshSchedule();
+                Assert.False(mockTimer.IsTimerRunning);
+            }
+        }
+
+        [Fact]
+        public void RefreshSchedule_MultipleTest()
+        {
+            using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
+            {
+                using var scheduler = new TestTimelineScheduler();
+                var mockTimer = scheduler.mockTimer;
+
+                scheduler.Enabled = true;
+                Assert.False(mockTimer.IsTimerRunning);
+
+                scheduler.LastUpdatedAt[TimelineSchedulerTaskType.Home] = DateTimeUtc.Now;
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = TimeSpan.FromMinutes(2);
+                scheduler.LastUpdatedAt[TimelineSchedulerTaskType.Mention] = DateTimeUtc.Now;
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Mention] = TimeSpan.FromMinutes(3);
+                scheduler.RefreshSchedule();
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
+
+                scheduler.LastUpdatedAt[TimelineSchedulerTaskType.Home] = DateTimeUtc.Now;
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = TimeSpan.FromMinutes(2);
+                scheduler.LastUpdatedAt[TimelineSchedulerTaskType.Mention] = DateTimeUtc.Now - TimeSpan.FromMinutes(2);
+                scheduler.UpdateInterval[TimelineSchedulerTaskType.Mention] = TimeSpan.FromMinutes(3);
+                scheduler.RefreshSchedule();
+
+                Assert.True(mockTimer.IsTimerRunning);
+                Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
+            }
+        }
+    }
+}
index d25da19..fc80169 100644 (file)
@@ -31,7 +31,18 @@ namespace OpenTween
 {
     public class TimelineScheduler : IDisposable
     {
-        private readonly AsyncTimer timer;
+        private static readonly TimelineSchedulerTaskType[] AllTaskTypes =
+        {
+            TimelineSchedulerTaskType.Home,
+            TimelineSchedulerTaskType.Mention,
+            TimelineSchedulerTaskType.Dm,
+            TimelineSchedulerTaskType.PublicSearch,
+            TimelineSchedulerTaskType.User,
+            TimelineSchedulerTaskType.List,
+            TimelineSchedulerTaskType.Config,
+        };
+
+        private readonly ITimer timer;
 
         private bool enabled = false;
         private bool systemResumeMode = false;
@@ -47,88 +58,45 @@ namespace OpenTween
                 if (this.enabled == value)
                     return;
 
-                if (value)
-                {
-                    var now = DateTimeUtc.Now;
-                    this.LastUpdateHome = now;
-                    this.LastUpdateMention = now;
-                    this.LastUpdateDm = now;
-                    this.LastUpdatePublicSearch = now;
-                    this.LastUpdateUser = now;
-                    this.LastUpdateList = now;
-                    this.LastUpdateConfig = now;
-                }
                 this.enabled = value;
-                this.RefreshSchedule();
+                this.Reset();
             }
         }
 
-        public DateTimeUtc LastUpdateHome { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdateMention { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdateDm { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdatePublicSearch { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdateUser { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdateList { get; private set; } = DateTimeUtc.MinValue;
-        public DateTimeUtc LastUpdateConfig { get; private set; } = DateTimeUtc.MinValue;
         public DateTimeUtc SystemResumedAt { get; private set; } = DateTimeUtc.MinValue;
-
-        public TimeSpan UpdateIntervalHome { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalMention { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalDm { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalPublicSearch { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalUser { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalList { get; set; } = Timeout.InfiniteTimeSpan;
-        public TimeSpan UpdateIntervalConfig { get; set; } = Timeout.InfiniteTimeSpan;
         public TimeSpan UpdateAfterSystemResume { get; set; } = Timeout.InfiniteTimeSpan;
 
-        public bool EnableUpdateHome
-            => this.UpdateIntervalHome != Timeout.InfiniteTimeSpan;
-
-        public bool EnableUpdateMention
-            => this.UpdateIntervalMention != Timeout.InfiniteTimeSpan;
-
-        public bool EnableUpdateDm
-            => this.UpdateIntervalDm != Timeout.InfiniteTimeSpan;
-
-        public bool EnableUpdatePublicSearch
-            => this.UpdateIntervalPublicSearch != Timeout.InfiniteTimeSpan;
-
-        public bool EnableUpdateUser
-            => this.UpdateIntervalUser != Timeout.InfiniteTimeSpan;
+        public bool EnableUpdateSystemResume
+            => this.UpdateAfterSystemResume != Timeout.InfiniteTimeSpan;
 
-        public bool EnableUpdateList
-            => this.UpdateIntervalList != Timeout.InfiniteTimeSpan;
+        public Dictionary<TimelineSchedulerTaskType, DateTimeUtc> LastUpdatedAt { get; }
+            = new Dictionary<TimelineSchedulerTaskType, DateTimeUtc>();
 
-        public bool EnableUpdateConfig
-            => this.UpdateIntervalConfig != Timeout.InfiniteTimeSpan;
+        public Dictionary<TimelineSchedulerTaskType, TimeSpan> UpdateInterval { get; }
+            = new Dictionary<TimelineSchedulerTaskType, TimeSpan>();
 
-        public bool EnableUpdateSystemResume
-            => this.UpdateAfterSystemResume != Timeout.InfiniteTimeSpan;
+        public Dictionary<TimelineSchedulerTaskType, Func<Task>> UpdateFunc { get; }
+            = new Dictionary<TimelineSchedulerTaskType, Func<Task>>();
 
-        public Func<Task>? UpdateHome;
-        public Func<Task>? UpdateMention;
-        public Func<Task>? UpdateDm;
-        public Func<Task>? UpdatePublicSearch;
-        public Func<Task>? UpdateUser;
-        public Func<Task>? UpdateList;
-        public Func<Task>? UpdateConfig;
+        public IEnumerable<TimelineSchedulerTaskType> EnabledTaskTypes
+            => TimelineScheduler.AllTaskTypes.Where(x => this.IsEnabledType(x));
 
-        [Flags]
-        private enum UpdateTask
+        public TimelineScheduler()
         {
-            None = 0,
-            Home = 1,
-            Mention = 1 << 2,
-            Dm = 1 << 3,
-            PublicSearch = 1 << 4,
-            User = 1 << 5,
-            List = 1 << 6,
-            Config = 1 << 7,
-            All = Home | Mention | Dm | PublicSearch | User | List | Config,
+            this.timer = this.CreateTimer(this.TimerCallback);
+
+            foreach (var taskType in TimelineScheduler.AllTaskTypes)
+            {
+                this.LastUpdatedAt[taskType] = DateTimeUtc.MinValue;
+                this.UpdateInterval[taskType] = Timeout.InfiniteTimeSpan;
+            }
         }
 
-        public TimelineScheduler()
-            => this.timer = new AsyncTimer(this.TimerCallback);
+        protected virtual ITimer CreateTimer(Func<Task> callback)
+            => new AsyncTimer(callback);
+
+        public bool IsEnabledType(TimelineSchedulerTaskType task)
+            => this.UpdateInterval[task] != Timeout.InfiniteTimeSpan;
 
         public void RefreshSchedule()
         {
@@ -151,6 +119,15 @@ namespace OpenTween
             this.RefreshSchedule();
         }
 
+        public void Reset()
+        {
+            foreach (var taskType in TimelineScheduler.AllTaskTypes)
+                this.LastUpdatedAt[taskType] = DateTimeUtc.MinValue;
+
+            this.systemResumeMode = false;
+            this.RefreshSchedule();
+        }
+
         private async Task TimerCallback()
         {
             try
@@ -173,55 +150,13 @@ namespace OpenTween
         {
             var now = DateTimeUtc.Now;
             var round = TimeSpan.FromSeconds(1); // 1秒未満の差異であればまとめて実行する
-            var tasks = UpdateTask.None;
-
-            if (this.EnableUpdateHome)
-            {
-                var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
-                if (nextScheduledHome - now < round)
-                    tasks |= UpdateTask.Home;
-            }
-
-            if (this.EnableUpdateMention)
-            {
-                var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
-                if (nextScheduledMention - now < round)
-                    tasks |= UpdateTask.Mention;
-            }
-
-            if (this.EnableUpdateDm)
-            {
-                var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
-                if (nextScheduledDm - now < round)
-                    tasks |= UpdateTask.Dm;
-            }
-
-            if (this.EnableUpdatePublicSearch)
-            {
-                var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
-                if (nextScheduledPublicSearch - now < round)
-                    tasks |= UpdateTask.PublicSearch;
-            }
-
-            if (this.EnableUpdateUser)
-            {
-                var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
-                if (nextScheduledUser - now < round)
-                    tasks |= UpdateTask.User;
-            }
-
-            if (this.EnableUpdateList)
-            {
-                var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
-                if (nextScheduledList - now < round)
-                    tasks |= UpdateTask.List;
-            }
+            var tasks = new List<TimelineSchedulerTaskType>(capacity: TimelineScheduler.AllTaskTypes.Length);
 
-            if (this.EnableUpdateConfig)
+            foreach (var taskType in this.EnabledTaskTypes)
             {
-                var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
-                if (nextScheduledConfig - now < round)
-                    tasks |= UpdateTask.Config;
+                var nextScheduledAt = this.LastUpdatedAt[taskType] + this.UpdateInterval[taskType];
+                if (nextScheduledAt - now < round)
+                    tasks.Add(taskType);
             }
 
             await this.RunUpdateTasks(tasks, now).ConfigureAwait(false);
@@ -233,60 +168,18 @@ namespace OpenTween
             var now = DateTimeUtc.Now;
 
             this.systemResumeMode = false;
-            await this.RunUpdateTasks(UpdateTask.All, now).ConfigureAwait(false);
+            await this.RunUpdateTasks(TimelineScheduler.AllTaskTypes, now).ConfigureAwait(false);
         }
 
-        private async Task RunUpdateTasks(UpdateTask tasks, DateTimeUtc now)
+        private async Task RunUpdateTasks(IEnumerable<TimelineSchedulerTaskType> taskTypes, DateTimeUtc now)
         {
-            var updateTasks = new List<Func<Task>>(capacity: 7);
+            var updateTasks = new List<Func<Task>>(capacity: TimelineScheduler.AllTaskTypes.Length);
 
-            if ((tasks & UpdateTask.Home) == UpdateTask.Home)
+            foreach (var taskType in taskTypes)
             {
-                this.LastUpdateHome = now;
-                if (this.UpdateHome != null)
-                    updateTasks.Add(this.UpdateHome);
-            }
-
-            if ((tasks & UpdateTask.Mention) == UpdateTask.Mention)
-            {
-                this.LastUpdateMention = now;
-                if (this.UpdateMention != null)
-                    updateTasks.Add(this.UpdateMention);
-            }
-
-            if ((tasks & UpdateTask.Dm) == UpdateTask.Dm)
-            {
-                this.LastUpdateDm = now;
-                if (this.UpdateDm != null)
-                    updateTasks.Add(this.UpdateDm);
-            }
-
-            if ((tasks & UpdateTask.PublicSearch) == UpdateTask.PublicSearch)
-            {
-                this.LastUpdatePublicSearch = now;
-                if (this.UpdatePublicSearch != null)
-                    updateTasks.Add(this.UpdatePublicSearch);
-            }
-
-            if ((tasks & UpdateTask.User) == UpdateTask.User)
-            {
-                this.LastUpdateUser = now;
-                if (this.UpdateUser != null)
-                    updateTasks.Add(this.UpdateUser);
-            }
-
-            if ((tasks & UpdateTask.List) == UpdateTask.List)
-            {
-                this.LastUpdateList = now;
-                if (this.UpdateList != null)
-                    updateTasks.Add(this.UpdateList);
-            }
-
-            if ((tasks & UpdateTask.Config) == UpdateTask.Config)
-            {
-                this.LastUpdateConfig = now;
-                if (this.UpdateConfig != null)
-                    updateTasks.Add(this.UpdateConfig);
+                this.LastUpdatedAt[taskType] = now;
+                if (this.UpdateFunc.TryGetValue(taskType, out var func))
+                    updateTasks.Add(func);
             }
 
             await Task.WhenAll(updateTasks.Select(x => Task.Run(x)))
@@ -302,64 +195,25 @@ namespace OpenTween
                 // systemResumeMode が有効な間は UpdateAfterSystemResume 以外の設定値を見ない
                 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
                 delay = nextScheduledUpdateAll - DateTimeUtc.Now;
-
-                return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
             }
-
-            // 次に更新が予定される時刻を判定する
-            var min = DateTimeUtc.MaxValue;
-
-            if (this.EnableUpdateHome)
-            {
-                var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
-                if (nextScheduledHome < min)
-                    min = nextScheduledHome;
-            }
-
-            if (this.EnableUpdateMention)
-            {
-                var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
-                if (nextScheduledMention < min)
-                    min = nextScheduledMention;
-            }
-
-            if (this.EnableUpdateDm)
-            {
-                var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
-                if (nextScheduledDm < min)
-                    min = nextScheduledDm;
-            }
-
-            if (this.EnableUpdatePublicSearch)
+            else
             {
-                var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
-                if (nextScheduledPublicSearch < min)
-                    min = nextScheduledPublicSearch;
-            }
+                // 次に更新が予定される時刻を判定する
+                var min = DateTimeUtc.MaxValue;
 
-            if (this.EnableUpdateUser)
-            {
-                var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
-                if (nextScheduledUser < min)
-                    min = nextScheduledUser;
-            }
+                foreach (var taskType in this.EnabledTaskTypes)
+                {
+                    var nextScheduledAt = this.LastUpdatedAt[taskType] + this.UpdateInterval[taskType];
+                    if (nextScheduledAt < min)
+                        min = nextScheduledAt;
+                }
 
-            if (this.EnableUpdateList)
-            {
-                var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
-                if (nextScheduledList < min)
-                    min = nextScheduledList;
-            }
+                if (min == DateTimeUtc.MaxValue)
+                    return Timeout.InfiniteTimeSpan;
 
-            if (this.EnableUpdateConfig)
-            {
-                var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
-                if (nextScheduledConfig < min)
-                    min = nextScheduledConfig;
+                delay = min - DateTimeUtc.Now;
             }
 
-            delay = min - DateTimeUtc.Now;
-
             return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
         }
 
@@ -380,4 +234,15 @@ namespace OpenTween
             GC.SuppressFinalize(this);
         }
     }
+
+    public enum TimelineSchedulerTaskType
+    {
+        Home,
+        Mention,
+        Dm,
+        PublicSearch,
+        User,
+        List,
+        Config,
+    }
 }
index 23ecd0c..726366f 100644 (file)
@@ -1177,13 +1177,13 @@ namespace OpenTween
 
             //タイマー設定
 
-            this.timelineScheduler.UpdateHome = () => this.InvokeAsync(() => this.RefreshTabAsync<HomeTabModel>());
-            this.timelineScheduler.UpdateMention = () => this.InvokeAsync(() => this.RefreshTabAsync<MentionsTabModel>());
-            this.timelineScheduler.UpdateDm = () => this.InvokeAsync(() => this.RefreshTabAsync<DirectMessagesTabModel>());
-            this.timelineScheduler.UpdatePublicSearch = () => this.InvokeAsync(() => this.RefreshTabAsync<PublicSearchTabModel>());
-            this.timelineScheduler.UpdateUser = () => this.InvokeAsync(() => this.RefreshTabAsync<UserTimelineTabModel>());
-            this.timelineScheduler.UpdateList = () => this.InvokeAsync(() => this.RefreshTabAsync<ListTimelineTabModel>());
-            this.timelineScheduler.UpdateConfig = () => this.InvokeAsync(() => Task.WhenAll(new[]
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.Home] = () => this.InvokeAsync(() => this.RefreshTabAsync<HomeTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.Mention] = () => this.InvokeAsync(() => this.RefreshTabAsync<MentionsTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.Dm] = () => this.InvokeAsync(() => this.RefreshTabAsync<DirectMessagesTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.PublicSearch] = () => this.InvokeAsync(() => this.RefreshTabAsync<PublicSearchTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.User] = () => this.InvokeAsync(() => this.RefreshTabAsync<UserTimelineTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.List] = () => this.InvokeAsync(() => this.RefreshTabAsync<ListTimelineTabModel>());
+            this.timelineScheduler.UpdateFunc[TimelineSchedulerTaskType.Config] = () => this.InvokeAsync(() => Task.WhenAll(new[]
             {
                 this.doGetFollowersMenu(),
                 this.RefreshBlockIdsAsync(),
@@ -1354,13 +1354,13 @@ namespace OpenTween
             static TimeSpan intervalSecondsOrDisabled(int seconds)
                 => seconds == 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(seconds);
 
-            this.timelineScheduler.UpdateIntervalHome = intervalSecondsOrDisabled(SettingManager.Common.TimelinePeriod);
-            this.timelineScheduler.UpdateIntervalMention = intervalSecondsOrDisabled(SettingManager.Common.ReplyPeriod);
-            this.timelineScheduler.UpdateIntervalDm = intervalSecondsOrDisabled(SettingManager.Common.DMPeriod);
-            this.timelineScheduler.UpdateIntervalPublicSearch = intervalSecondsOrDisabled(SettingManager.Common.PubSearchPeriod);
-            this.timelineScheduler.UpdateIntervalUser = intervalSecondsOrDisabled(SettingManager.Common.UserTimelinePeriod);
-            this.timelineScheduler.UpdateIntervalList = intervalSecondsOrDisabled(SettingManager.Common.ListsPeriod);
-            this.timelineScheduler.UpdateIntervalConfig = TimeSpan.FromHours(6);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.Home] = intervalSecondsOrDisabled(SettingManager.Common.TimelinePeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.Mention] = intervalSecondsOrDisabled(SettingManager.Common.ReplyPeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.Dm] = intervalSecondsOrDisabled(SettingManager.Common.DMPeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.PublicSearch] = intervalSecondsOrDisabled(SettingManager.Common.PubSearchPeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.User] = intervalSecondsOrDisabled(SettingManager.Common.UserTimelinePeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.List] = intervalSecondsOrDisabled(SettingManager.Common.ListsPeriod);
+            this.timelineScheduler.UpdateInterval[TimelineSchedulerTaskType.Config] = TimeSpan.FromHours(6);
             this.timelineScheduler.UpdateAfterSystemResume = TimeSpan.FromSeconds(30);
 
             this.timelineScheduler.RefreshSchedule();