Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit b419c1c

Browse files
committed
Added UsageTracker unit tests.
- Separated IO and date/time comparisons into a separate `UsageService` to allow for easier unit testing - Added some unit tests.
1 parent 49ba6cf commit b419c1c

File tree

9 files changed

+530
-167
lines changed

9 files changed

+530
-167
lines changed

src/GitHub.Exports/GitHub.Exports.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@
137137
<Compile Include="Models\ICommentModel.cs" />
138138
<Compile Include="Models\IInlineCommentModel.cs" />
139139
<Compile Include="Models\IPullRequestReviewCommentModel.cs" />
140+
<Compile Include="Models\UsageData.cs" />
141+
<Compile Include="Services\IUsageService.cs" />
140142
<Compile Include="Settings\PkgCmdID.cs" />
141143
<Compile Include="ViewModels\IHasErrorState.cs" />
142144
<Compile Include="ViewModels\IHasLoading.cs" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
3+
namespace GitHub.Models
4+
{
5+
/// <summary>
6+
/// Wraps a <see cref="UsageModel"/> with a <see cref="LastUpdated"/> field.
7+
/// </summary>
8+
public class UsageData
9+
{
10+
/// <summary>
11+
/// Gets or sets the last update time.
12+
/// </summary>
13+
public DateTimeOffset LastUpdated { get; set; }
14+
15+
/// <summary>
16+
/// Gets the model containing the current usage data.
17+
/// </summary>
18+
public UsageModel Model { get; set; }
19+
}
20+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using GitHub.Models;
4+
5+
namespace GitHub.Services
6+
{
7+
/// <summary>
8+
/// Provides services for <see cref="IUsageTracker"/>.
9+
/// </summary>
10+
public interface IUsageService
11+
{
12+
/// <summary>
13+
/// Checks whether the last updated date is the same day as today.
14+
/// </summary>
15+
/// <param name="lastUpdated">The last updated date.</param>
16+
/// <returns>True if the last updated date is the same day as today; otherwise false.</returns>
17+
bool IsSameDay(DateTimeOffset lastUpdated);
18+
19+
/// <summary>
20+
/// Checks whether the last updated date is the same week as today.
21+
/// </summary>
22+
/// <param name="lastUpdated">The last updated date.</param>
23+
/// <returns>True if the last updated date is the same week as today; otherwise false.</returns>
24+
bool IsSameWeek(DateTimeOffset lastUpdated);
25+
26+
/// <summary>
27+
/// Checks whether the last updated date is the same month as today.
28+
/// </summary>
29+
/// <param name="lastUpdated">The last updated date.</param>
30+
/// <returns>True if the last updated date is the same month as today; otherwise false.</returns>
31+
bool IsSameMonth(DateTimeOffset lastUpdated);
32+
33+
/// <summary>
34+
/// Starts a timer.
35+
/// </summary>
36+
/// <param name="callback">The callback to call when the timer ticks.</param>
37+
/// <param name="dueTime">The timespan after which the callback will be called the first time.</param>
38+
/// <param name="period">The timespan after which the callback will be called subsequent times.</param>
39+
/// <returns>A disposable used to cancel the timer.</returns>
40+
IDisposable StartTimer(Func<Task> callback, TimeSpan dueTime, TimeSpan period);
41+
42+
/// <summary>
43+
/// Reads the local usage data from disk.
44+
/// </summary>
45+
/// <returns>A task returning a <see cref="UsageData"/> object.</returns>
46+
Task<UsageData> ReadLocalData();
47+
48+
/// <summary>
49+
/// Writes the local usage data to disk.
50+
/// </summary>
51+
Task WriteLocalData(UsageData data);
52+
}
53+
}

src/GitHub.VisualStudio/GitHub.VisualStudio.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@
303303
<Compile Include="Helpers\Browser.cs" />
304304
<Compile Include="Services\JsonConnectionCache.cs" />
305305
<Compile Include="Services\UIProvider.cs" />
306+
<Compile Include="Services\UsageService.cs" />
306307
<Compile Include="Services\UsageTracker.cs" />
307308
<Compile Include="Services\LoginManagerDispatcher.cs" />
308309
<Compile Include="Services\UsageTrackerDispatcher.cs" />

src/GitHub.VisualStudio/GitHubPackage.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,10 @@ async Task<object> CreateService(IAsyncServiceContainer container, CancellationT
216216
}
217217
else if (serviceType == typeof(IUsageTracker))
218218
{
219-
var uiProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider;
220-
return new UsageTracker(uiProvider);
219+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
220+
var serviceProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider;
221+
var usageService = serviceProvider.GetService<IUsageService>();
222+
return new UsageTracker(serviceProvider, usageService);
221223
}
222224
else if (serviceType == typeof(IUIProvider))
223225
{
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.ComponentModel.Composition;
3+
using System.Globalization;
4+
using System.IO;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using GitHub.Models;
9+
using Task = System.Threading.Tasks.Task;
10+
11+
namespace GitHub.Services
12+
{
13+
[Export(typeof(IUsageService))]
14+
public class UsageService : IUsageService
15+
{
16+
const string StoreFileName = "ghfvs.usage";
17+
static readonly Calendar cal = CultureInfo.InvariantCulture.Calendar;
18+
readonly IGitHubServiceProvider serviceProvider;
19+
string storePath;
20+
21+
[ImportingConstructor]
22+
public UsageService(IGitHubServiceProvider serviceProvider)
23+
{
24+
this.serviceProvider = serviceProvider;
25+
}
26+
27+
public bool IsSameDay(DateTimeOffset lastUpdated)
28+
{
29+
return lastUpdated.Date == DateTimeOffset.Now.Date;
30+
}
31+
32+
public bool IsSameWeek(DateTimeOffset lastUpdated)
33+
{
34+
return GetIso8601WeekOfYear(lastUpdated) == GetIso8601WeekOfYear(DateTimeOffset.Now);
35+
}
36+
37+
public bool IsSameMonth(DateTimeOffset lastUpdated)
38+
{
39+
return lastUpdated.Month == DateTimeOffset.Now.Month;
40+
}
41+
42+
public IDisposable StartTimer(Func<Task> callback, TimeSpan dueTime, TimeSpan period)
43+
{
44+
return new Timer(
45+
async _ =>
46+
{
47+
try { await callback(); }
48+
catch { /* log.Warn("Failed submitting usage data", ex); */ }
49+
},
50+
null,
51+
dueTime,
52+
period);
53+
}
54+
55+
public async Task<UsageData> ReadLocalData()
56+
{
57+
Initialize();
58+
59+
var json = File.Exists(storePath) ? await ReadAllTextAsync(storePath) : null;
60+
61+
try
62+
{
63+
return json != null ?
64+
SimpleJson.DeserializeObject<UsageData>(json) :
65+
new UsageData { Model = new UsageModel() };
66+
}
67+
catch
68+
{
69+
return new UsageData { Model = new UsageModel() };
70+
}
71+
}
72+
73+
public async Task WriteLocalData(UsageData data)
74+
{
75+
try
76+
{
77+
Directory.CreateDirectory(Path.GetDirectoryName(storePath));
78+
var json = SimpleJson.SerializeObject(data);
79+
await WriteAllTextAsync(storePath, json);
80+
}
81+
catch
82+
{
83+
// log.Warn("Failed to write usage data", ex);
84+
}
85+
}
86+
87+
void Initialize()
88+
{
89+
if (storePath == null)
90+
{
91+
var program = serviceProvider.GetService<IProgram>();
92+
storePath = Path.Combine(
93+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
94+
program.ApplicationName,
95+
StoreFileName);
96+
}
97+
}
98+
99+
async Task<string> ReadAllTextAsync(string path)
100+
{
101+
using (var s = File.OpenRead(path))
102+
using (var r = new StreamReader(s, Encoding.UTF8))
103+
{
104+
return await r.ReadToEndAsync();
105+
}
106+
}
107+
108+
async Task WriteAllTextAsync(string path, string text)
109+
{
110+
using (var s = File.OpenWrite(path))
111+
using (var w = new StreamWriter(s, Encoding.UTF8))
112+
{
113+
await w.WriteAsync(text);
114+
}
115+
}
116+
117+
// http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx
118+
static int GetIso8601WeekOfYear(DateTimeOffset time)
119+
{
120+
// Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll
121+
// be the same week# as whatever Thursday, Friday or Saturday are,
122+
// and we always get those right
123+
DayOfWeek day = cal.GetDayOfWeek(time.UtcDateTime);
124+
if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
125+
{
126+
time = time.AddDays(3);
127+
}
128+
129+
// Return the week of our adjusted day
130+
return cal.GetWeekOfYear(time.UtcDateTime, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)