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

Commit c86a1c5

Browse files
committed
Move gitignores and licenses to the indexed cache
Move gitignores and licenses lists to the indexed cache and have ModelService return observables of the cached data. The view model is responsible for whatever sorting/filtering/adding is required to make the list consumable by the view. Fix all the related tests.
1 parent fc02bbe commit c86a1c5

File tree

9 files changed

+221
-185
lines changed

9 files changed

+221
-185
lines changed

src/GitHub.App/Services/ModelService.cs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using NLog;
1717
using NullGuard;
1818
using Octokit;
19-
using ReactiveUI;
2019

2120
namespace GitHub.Services
2221
{
@@ -37,35 +36,37 @@ public ModelService(IApiClient apiClient, IBlobCache hostCache, IAvatarProvider
3736
this.avatarProvider = avatarProvider;
3837
}
3938

40-
public IObservable<IReadOnlyList<GitIgnoreItem>> GetGitIgnoreTemplates()
39+
public IObservable<GitIgnoreItem> GetGitIgnoreTemplates()
4140
{
4241
return Observable.Defer(() =>
43-
hostCache.GetAndRefreshObject(
44-
"gitignores",
45-
GetOrderedGitIgnoreTemplatesFromApi,
46-
TimeSpan.FromDays(1),
47-
TimeSpan.FromDays(7))
48-
.ToReadOnlyList(GitIgnoreItem.Create, GitIgnoreItem.None))
49-
.Catch<IReadOnlyList<GitIgnoreItem>, Exception>(e =>
42+
hostCache.GetAndFetchLatestFromIndex("global|ignores", () =>
43+
GetGitIgnoreTemplatesFromApi(),
44+
item => { },
45+
TimeSpan.FromMinutes(1),
46+
TimeSpan.FromDays(7))
47+
)
48+
.Select(Create)
49+
.Catch<GitIgnoreItem, Exception>(e =>
5050
{
5151
log.Info("Failed to retrieve GitIgnoreTemplates", e);
52-
return Observable.Return(new[] { GitIgnoreItem.None });
52+
return Observable.Empty<GitIgnoreItem>();
5353
});
5454
}
5555

56-
public IObservable<IReadOnlyList<LicenseItem>> GetLicenses()
56+
public IObservable<LicenseItem> GetLicenses()
5757
{
5858
return Observable.Defer(() =>
59-
hostCache.GetAndRefreshObject(
60-
"licenses",
61-
GetOrderedLicensesFromApi,
62-
TimeSpan.FromDays(1),
63-
TimeSpan.FromDays(7))
64-
.ToReadOnlyList(Create, LicenseItem.None))
65-
.Catch<IReadOnlyList<LicenseItem>, Exception>(e =>
59+
hostCache.GetAndFetchLatestFromIndex("global|licenses", () =>
60+
GetLicensesFromApi(),
61+
item => { },
62+
TimeSpan.FromMinutes(1),
63+
TimeSpan.FromDays(7))
64+
)
65+
.Select(Create)
66+
.Catch<LicenseItem, Exception>(e =>
6667
{
67-
log.Info("Failed to retrieve GitIgnoreTemplates", e);
68-
return Observable.Return(new[] { LicenseItem.None });
68+
log.Info("Failed to retrieve licenses", e);
69+
return Observable.Empty<LicenseItem>();
6970
});
7071
}
7172

@@ -78,21 +79,18 @@ public IObservable<IReadOnlyList<IAccount>> GetAccounts()
7879
.ToReadOnlyList(Create);
7980
}
8081

81-
IObservable<IEnumerable<LicenseCacheItem>> GetOrderedLicensesFromApi()
82+
IObservable<LicenseCacheItem> GetLicensesFromApi()
8283
{
8384
return apiClient.GetLicenses()
8485
.WhereNotNull()
85-
.Select(LicenseCacheItem.Create)
86-
.ToList()
87-
.Select(licenses => licenses.OrderByDescending(lic => LicenseItem.IsRecommended(lic.Key)));
86+
.Select(LicenseCacheItem.Create);
8887
}
8988

90-
IObservable<IEnumerable<string>> GetOrderedGitIgnoreTemplatesFromApi()
89+
IObservable<GitIgnoreCacheItem> GetGitIgnoreTemplatesFromApi()
9190
{
9291
return apiClient.GetGitIgnoreTemplates()
9392
.WhereNotNull()
94-
.ToList()
95-
.Select(templates => templates.OrderByDescending(GitIgnoreItem.IsRecommended));
93+
.Select(GitIgnoreCacheItem.Create);
9694
}
9795

9896
IObservable<IEnumerable<AccountCacheItem>> GetUser()
@@ -232,6 +230,11 @@ IObservable<IReadOnlyList<IRepositoryModel>> GetOrganizationRepositories(string
232230
});
233231
}
234232

233+
static GitIgnoreItem Create(GitIgnoreCacheItem item)
234+
{
235+
return GitIgnoreItem.Create(item.Name);
236+
}
237+
235238
static LicenseItem Create(LicenseCacheItem licenseCacheItem)
236239
{
237240
return new LicenseItem(licenseCacheItem.Key, licenseCacheItem.Name);
@@ -288,18 +291,28 @@ public void Dispose()
288291
GC.SuppressFinalize(this);
289292
}
290293

291-
public class LicenseCacheItem
294+
public class GitIgnoreCacheItem : CacheItem
295+
{
296+
public static GitIgnoreCacheItem Create(string ignore)
297+
{
298+
return new GitIgnoreCacheItem { Key = ignore, Name = ignore, Timestamp = DateTime.Now };
299+
}
300+
301+
public string Name { get; set; }
302+
}
303+
304+
305+
public class LicenseCacheItem : CacheItem
292306
{
293307
public static LicenseCacheItem Create(LicenseMetadata licenseMetadata)
294308
{
295-
return new LicenseCacheItem { Key = licenseMetadata.Key, Name = licenseMetadata.Name };
309+
return new LicenseCacheItem { Key = licenseMetadata.Key, Name = licenseMetadata.Name, Timestamp = DateTime.Now };
296310
}
297311

298-
public string Key { get; set; }
299312
public string Name { get; set; }
300313
}
301314

302-
public class RepositoryCacheItem
315+
public class RepositoryCacheItem : CacheItem
303316
{
304317
public static RepositoryCacheItem Create(Repository apiRepository)
305318
{
@@ -315,6 +328,8 @@ public RepositoryCacheItem(Repository apiRepository)
315328
CloneUrl = apiRepository.CloneUrl;
316329
Private = apiRepository.Private;
317330
Fork = apiRepository.Fork;
331+
Key = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Name, Owner);
332+
Timestamp = apiRepository.UpdatedAt;
318333
}
319334

320335
public string Name { get; set; }

src/GitHub.App/ViewModels/PullRequestListViewModel.cs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -198,54 +198,4 @@ public void Dispose()
198198
GC.SuppressFinalize(this);
199199
}
200200
}
201-
202-
// doing this as an extension because I get the feeling it might be useful in other places
203-
public static class TrackingCollectionExtensions
204-
{
205-
public static ObservableCollection<T> CreateListenerCollection<T>(this ITrackingCollection<T> tcol,
206-
IList<T> stickieItemsOnTop = null)
207-
where T : ICopyable<T>
208-
{
209-
var col = new ObservableCollection<T>();
210-
tcol.CollectionChanged += (s, e) =>
211-
{
212-
var offset = 0;
213-
if (stickieItemsOnTop != null)
214-
{
215-
foreach (var item in stickieItemsOnTop)
216-
{
217-
if (col.Contains(item))
218-
offset++;
219-
}
220-
}
221-
222-
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move)
223-
{
224-
for (int i = 0, oldIdx = e.OldStartingIndex, newIdx = e.NewStartingIndex;
225-
i < e.OldItems.Count; i++, oldIdx++, newIdx++)
226-
col.Move(oldIdx + offset, newIdx + offset);
227-
}
228-
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
229-
foreach (T item in e.NewItems)
230-
col.Add(item);
231-
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
232-
foreach (T item in e.OldItems)
233-
col.Remove(item);
234-
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
235-
for (int i = 0, idx = e.OldStartingIndex;
236-
i < e.OldItems.Count; i++, idx++)
237-
col[idx + offset] = (T)e.NewItems[i];
238-
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
239-
{
240-
col.Clear();
241-
if (stickieItemsOnTop != null)
242-
{
243-
foreach (var item in stickieItemsOnTop)
244-
col.Add(item);
245-
}
246-
}
247-
};
248-
return col;
249-
}
250-
}
251201
}

src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Octokit;
2222
using ReactiveUI;
2323
using Rothko;
24+
using GitHub.Collections;
2425

2526
namespace GitHub.ViewModels
2627
{
@@ -32,8 +33,6 @@ public class RepositoryCreationViewModel : RepositoryFormViewModel, IRepositoryC
3233

3334
readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create();
3435
readonly ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts;
35-
readonly ObservableAsPropertyHelper<IReadOnlyList<LicenseItem>> licenses;
36-
readonly ObservableAsPropertyHelper<IReadOnlyList<GitIgnoreItem>> gitIgnoreTemplates;
3736
readonly IRepositoryHost repositoryHost;
3837
readonly IRepositoryCreationService repositoryCreationService;
3938
readonly ObservableAsPropertyHelper<bool> isCreating;
@@ -118,22 +117,20 @@ public RepositoryCreationViewModel(
118117
isCreating = CreateRepository.IsExecuting
119118
.ToProperty(this, x => x.IsCreating);
120119

121-
gitIgnoreTemplates = repositoryHost.ModelService.GetGitIgnoreTemplates()
122-
.ObserveOn(RxApp.MainThreadScheduler)
123-
.ToProperty(this, x => x.GitIgnoreTemplates, initialValue: new GitIgnoreItem[] { });
124-
125-
this.WhenAny(x => x.GitIgnoreTemplates, x => x.Value)
126-
.WhereNotNull()
127-
.Where(ignores => ignores.Any())
128-
.Subscribe(ignores =>
120+
GitIgnoreTemplates = TrackingCollection.CreateListenerCollectionAndRun(
121+
repositoryHost.ModelService.GetGitIgnoreTemplates(),
122+
new[] { GitIgnoreItem.None },
123+
OrderedComparer<GitIgnoreItem>.OrderByDescending(item => GitIgnoreItem.IsRecommended(item.Name)).Compare,
124+
x =>
129125
{
130-
SelectedGitIgnoreTemplate = ignores.FirstOrDefault(
131-
template => template.Name.Equals("VisualStudio", StringComparison.OrdinalIgnoreCase));
126+
if (x.Name.Equals("VisualStudio", StringComparison.OrdinalIgnoreCase))
127+
SelectedGitIgnoreTemplate = x;
132128
});
133129

134-
licenses = repositoryHost.ModelService.GetLicenses()
135-
.ObserveOn(RxApp.MainThreadScheduler)
136-
.ToProperty(this, x => x.Licenses, initialValue: new LicenseItem[] { });
130+
Licenses = TrackingCollection.CreateListenerCollectionAndRun(
131+
repositoryHost.ModelService.GetLicenses(),
132+
new[] { LicenseItem.None },
133+
OrderedComparer<LicenseItem>.OrderByDescending(item => LicenseItem.IsRecommended(item.Name)).Compare);
137134

138135
BaseRepositoryPath = repositoryCreationService.DefaultClonePath;
139136
}
@@ -164,14 +161,18 @@ public string BaseRepositoryPath
164161
/// </summary>
165162
public bool CanKeepPrivate { get { return canKeepPrivate.Value; } }
166163

164+
IReadOnlyList<GitIgnoreItem> gitIgnoreTemplates;
167165
public IReadOnlyList<GitIgnoreItem> GitIgnoreTemplates
168166
{
169-
get { return gitIgnoreTemplates.Value; }
167+
get { return gitIgnoreTemplates; }
168+
set { this.RaiseAndSetIfChanged(ref gitIgnoreTemplates, value); }
170169
}
171170

171+
IReadOnlyList<LicenseItem> licenses;
172172
public IReadOnlyList<LicenseItem> Licenses
173173
{
174-
get { return licenses.Value; }
174+
get { return licenses; }
175+
set { this.RaiseAndSetIfChanged(ref licenses, value); }
175176
}
176177

177178
GitIgnoreItem selectedGitIgnoreTemplate;

src/GitHub.Exports.Reactive/Collections/TrackingCollection.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,84 @@
1818

1919
namespace GitHub.Collections
2020
{
21+
public static class TrackingCollection
22+
{
23+
public static TrackingCollection<T> Create<T>(IObservable<T> source,
24+
Func<T, T, int> comparer = null,
25+
Func<T, int, IList<T>, bool> filter = null,
26+
Func<T, T, int> newer = null,
27+
IScheduler scheduler = null)
28+
where T : class, ICopyable<T>
29+
{
30+
return new TrackingCollection<T>(source, comparer, filter, newer, scheduler);
31+
}
32+
33+
public static ObservableCollection<T> CreateListenerCollectionAndRun<T>(IObservable<T> source,
34+
IList<T> stickieItemsOnTop = null,
35+
Func<T, T, int> comparer = null,
36+
Action<T> onNext = null)
37+
where T : class, ICopyable<T>
38+
{
39+
var col = Create(source, comparer);
40+
var ret = col.CreateListenerCollection(stickieItemsOnTop);
41+
col.Subscribe(onNext ?? (_ => {}), () => {});
42+
return ret;
43+
}
44+
45+
public static ObservableCollection<T> CreateListenerCollection<T>(this ITrackingCollection<T> tcol,
46+
IList<T> stickieItemsOnTop = null)
47+
where T : ICopyable<T>
48+
{
49+
var col = new ObservableCollection<T>(stickieItemsOnTop);
50+
tcol.CollectionChanged += (s, e) =>
51+
{
52+
var offset = 0;
53+
if (stickieItemsOnTop != null)
54+
{
55+
foreach (var item in stickieItemsOnTop)
56+
{
57+
if (col.Contains(item))
58+
offset++;
59+
}
60+
}
61+
62+
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move)
63+
{
64+
for (int i = 0, oldIdx = e.OldStartingIndex, newIdx = e.NewStartingIndex;
65+
i < e.OldItems.Count; i++, oldIdx++, newIdx++)
66+
{
67+
col.Move(oldIdx + offset, newIdx + offset);
68+
}
69+
}
70+
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
71+
{
72+
foreach (T item in e.NewItems)
73+
col.Add(item);
74+
}
75+
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
76+
{
77+
foreach (T item in e.OldItems)
78+
col.Remove(item);
79+
}
80+
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
81+
{
82+
for (int i = 0, idx = e.OldStartingIndex; i < e.OldItems.Count; i++, idx++)
83+
col[idx + offset] = (T)e.NewItems[i];
84+
}
85+
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
86+
{
87+
col.Clear();
88+
if (stickieItemsOnTop != null)
89+
{
90+
foreach (var item in stickieItemsOnTop)
91+
col.Add(item);
92+
}
93+
}
94+
};
95+
return col;
96+
}
97+
}
98+
2199
/// <summary>
22100
/// TrackingCollection is a specialization of ObservableCollection that gets items from
23101
/// an observable sequence and updates its contents in such a way that two updates to

src/GitHub.Exports.Reactive/Models/GitIgnoreItem.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
using System;
1+
using GitHub.Collections;
2+
using System;
23
using System.Diagnostics;
34
using System.Globalization;
45
using System.Linq;
56

67
namespace GitHub.Models
78
{
89
[DebuggerDisplay("{DebuggerDisplay,nq}")]
9-
public sealed class GitIgnoreItem
10+
public sealed class GitIgnoreItem : ICopyable<GitIgnoreItem>
1011
{
1112
static readonly string[] recommendedIgnoreFiles = { "None", "VisualStudio", "Node", "Eclipse", "C++", "Windows" };
1213

@@ -24,6 +25,12 @@ public static GitIgnoreItem Create(string name)
2425
Recommended = IsRecommended(name);
2526
}
2627

28+
public void CopyFrom(GitIgnoreItem other)
29+
{
30+
Name = other.Name;
31+
Recommended = other.Recommended;
32+
}
33+
2734
public string Name { get; private set; }
2835

2936
public bool Recommended { get; private set; }

0 commit comments

Comments
 (0)