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

Commit 77a9f70

Browse files
committed
Move repositories to indexed cache and tracking collection
1 parent 239ee9d commit 77a9f70

File tree

11 files changed

+189
-181
lines changed

11 files changed

+189
-181
lines changed

src/GitHub.App/SampleData/SampleViewModels.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ public class RepositoryCloneViewModelDesigner : BaseViewModel, IRepositoryCloneV
332332
{
333333
public RepositoryCloneViewModelDesigner()
334334
{
335-
var repositories = new ReactiveList<IRepositoryModel>
335+
Repositories = new ObservableCollection<IRepositoryModel>
336336
{
337337
RepositoryModelDesigner.Create("encourage", "haacked"),
338338
RepositoryModelDesigner.Create("haacked.com", "haacked"),
@@ -346,10 +346,6 @@ public RepositoryCloneViewModelDesigner()
346346

347347
BrowseForDirectory = ReactiveCommand.Create();
348348

349-
FilteredRepositories = repositories.CreateDerivedCollection(
350-
x => x
351-
);
352-
353349
BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(this.WhenAny(x => x.BaseRepositoryPath, x => x.Value))
354350
.IfNullOrEmpty("Please enter a repository path")
355351
.IfTrue(x => x.Length > 200, "Path too long")
@@ -365,7 +361,7 @@ public IReactiveCommand<Unit> CloneCommand
365361

366362
public IRepositoryModel SelectedRepository { get; set; }
367363

368-
public IReactiveDerivedList<IRepositoryModel> FilteredRepositories
364+
public ObservableCollection<IRepositoryModel> Repositories
369365
{
370366
get;
371367
private set;

src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs

Lines changed: 56 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
using NullGuard;
2020
using ReactiveUI;
2121
using Rothko;
22+
using System.Collections.ObjectModel;
23+
using GitHub.Collections;
2224

2325
namespace GitHub.ViewModels
2426
{
@@ -28,14 +30,12 @@ public class RepositoryCloneViewModel : BaseViewModel, IRepositoryCloneViewModel
2830
{
2931
static readonly Logger log = LogManager.GetCurrentClassLogger();
3032

31-
readonly IRepositoryHost repositoryHost;
3233
readonly IRepositoryCloneService cloneService;
3334
readonly IOperatingSystem operatingSystem;
3435
readonly INotificationService notificationService;
3536
readonly IUsageTracker usageTracker;
36-
readonly IReactiveCommand<IReadOnlyList<IRepositoryModel>> loadRepositoriesCommand;
3737
readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create();
38-
readonly ObservableAsPropertyHelper<bool> isLoading;
38+
bool isLoading;
3939
readonly ObservableAsPropertyHelper<bool> noRepositoriesFound;
4040
readonly ObservableAsPropertyHelper<bool> canClone;
4141
string baseRepositoryPath;
@@ -58,34 +58,39 @@ public RepositoryCloneViewModel(
5858
INotificationService notificationService,
5959
IUsageTracker usageTracker)
6060
{
61-
this.repositoryHost = repositoryHost;
6261
this.cloneService = cloneService;
6362
this.operatingSystem = operatingSystem;
6463
this.notificationService = notificationService;
6564
this.usageTracker = usageTracker;
6665

6766
Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, repositoryHost.Title);
68-
Repositories = new ReactiveList<IRepositoryModel>();
69-
loadRepositoriesCommand = ReactiveCommand.CreateAsyncObservable(OnLoadRepositories);
70-
isLoading = this.WhenAny(x => x.LoadingFailed, x => x.Value)
71-
.CombineLatest(loadRepositoriesCommand.IsExecuting, (failed, loading) => !failed && loading)
72-
.ToProperty(this, x => x.IsLoading);
73-
loadRepositoriesCommand.Subscribe(Repositories.AddRange);
67+
68+
var col = new TrackingCollection<IRepositoryModel>(filter: FilterRepository);
69+
col = repositoryHost.ModelService.GetRepositories(col) as TrackingCollection<IRepositoryModel>;
70+
col.OriginalCompleted.Subscribe(
71+
_ => {}
72+
, ex =>
73+
{
74+
LoadingFailed = true;
75+
log.Error("Error while loading repositories", ex);
76+
},
77+
() => IsLoading = false
78+
);
79+
col.Subscribe(_ => IsLoading = true, () => {});
80+
81+
Repositories = col;
82+
7483
filterTextIsEnabled = this.WhenAny(x => x.Repositories.Count, x => x.Value > 0)
7584
.ToProperty(this, x => x.FilterTextIsEnabled);
85+
7686
noRepositoriesFound = this.WhenAny(x => x.FilterTextIsEnabled, x => x.IsLoading, x => x.LoadingFailed
7787
, (any, loading, failed) => !any.Value && !loading.Value && !failed.Value)
7888
.ToProperty(this, x => x.NoRepositoriesFound);
7989

80-
var filterResetSignal = this.WhenAny(x => x.FilterText, x => x.Value)
90+
this.WhenAny(x => x.FilterText, x => x.Value)
8191
.DistinctUntilChanged(StringComparer.OrdinalIgnoreCase)
82-
.Throttle(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler);
83-
84-
FilteredRepositories = Repositories.CreateDerivedCollection(
85-
x => x,
86-
filter: FilterRepository,
87-
signalReset: filterResetSignal
88-
);
92+
.Throttle(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler)
93+
.Subscribe(_ => col.Filter = FilterRepository);
8994

9095
var baseRepositoryPath = this.WhenAny(
9196
x => x.BaseRepositoryPath,
@@ -112,18 +117,7 @@ public RepositoryCloneViewModel(
112117
BaseRepositoryPath = cloneService.DefaultClonePath;
113118
}
114119

115-
IObservable<IReadOnlyList<IRepositoryModel>> OnLoadRepositories(object value)
116-
{
117-
return repositoryHost.ModelService.GetRepositories()
118-
.Catch<IReadOnlyList<IRepositoryModel>, Exception>(ex =>
119-
{
120-
log.Error("Error while loading repositories", ex);
121-
return Observable.Start(() => LoadingFailed = true, RxApp.MainThreadScheduler)
122-
.Select(_ => new IRepositoryModel[] { });
123-
});
124-
}
125-
126-
bool FilterRepository(IRepositoryModel repo)
120+
bool FilterRepository(IRepositoryModel repo, int position, IList<IRepositoryModel> list)
127121
{
128122
if (string.IsNullOrWhiteSpace(FilterText))
129123
return true;
@@ -177,6 +171,34 @@ bool IsAlreadyRepoAtPath(string path)
177171
return isAlreadyRepoAtPath;
178172
}
179173

174+
IObservable<Unit> ShowBrowseForDirectoryDialog()
175+
{
176+
return Observable.Start(() =>
177+
{
178+
// We store this in a local variable to prevent it changing underneath us while the
179+
// folder dialog is open.
180+
var localBaseRepositoryPath = BaseRepositoryPath;
181+
var browseResult = operatingSystem.Dialog.BrowseForDirectory(localBaseRepositoryPath, Resources.BrowseForDirectory);
182+
183+
if (!browseResult.Success)
184+
return;
185+
186+
var directory = browseResult.DirectoryPath ?? localBaseRepositoryPath;
187+
188+
try
189+
{
190+
BaseRepositoryPath = directory;
191+
}
192+
catch (Exception e)
193+
{
194+
// TODO: We really should limit this to exceptions we know how to handle.
195+
log.Error(string.Format(CultureInfo.InvariantCulture,
196+
"Failed to set base repository path.{0}localBaseRepositoryPath = \"{1}\"{0}BaseRepositoryPath = \"{2}\"{0}Chosen directory = \"{3}\"",
197+
System.Environment.NewLine, localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"), e);
198+
}
199+
}, RxApp.MainThreadScheduler);
200+
}
201+
180202
/// <summary>
181203
/// Path to clone repositories into
182204
/// </summary>
@@ -192,26 +214,16 @@ public string BaseRepositoryPath
192214
/// </summary>
193215
public IReactiveCommand<Unit> CloneCommand { get; private set; }
194216

195-
IReactiveList<IRepositoryModel> repositories;
217+
ObservableCollection<IRepositoryModel> repositories;
196218
/// <summary>
197219
/// List of repositories as returned by the server
198220
/// </summary>
199-
public IReactiveList<IRepositoryModel> Repositories
221+
public ObservableCollection<IRepositoryModel> Repositories
200222
{
201223
get { return repositories; }
202224
private set { this.RaiseAndSetIfChanged(ref repositories, value); }
203225
}
204226

205-
IReactiveDerivedList<IRepositoryModel> filteredRepositories;
206-
/// <summary>
207-
/// List of repositories as filtered by user
208-
/// </summary>
209-
public IReactiveDerivedList<IRepositoryModel> FilteredRepositories
210-
{
211-
get { return filteredRepositories; }
212-
private set { this.RaiseAndSetIfChanged(ref filteredRepositories, value); }
213-
}
214-
215227
IRepositoryModel selectedRepository;
216228
/// <summary>
217229
/// Selected repository to clone
@@ -244,12 +256,8 @@ public string FilterText
244256

245257
public bool IsLoading
246258
{
247-
get { return isLoading.Value; }
248-
}
249-
250-
public IReactiveCommand<IReadOnlyList<IRepositoryModel>> LoadRepositoriesCommand
251-
{
252-
get { return loadRepositoriesCommand; }
259+
get { return isLoading; }
260+
private set { this.RaiseAndSetIfChanged(ref isLoading, value); }
253261
}
254262

255263
public bool LoadingFailed
@@ -278,33 +286,5 @@ public ReactivePropertyValidator<string> BaseRepositoryPathValidator
278286
get;
279287
private set;
280288
}
281-
282-
IObservable<Unit> ShowBrowseForDirectoryDialog()
283-
{
284-
return Observable.Start(() =>
285-
{
286-
// We store this in a local variable to prevent it changing underneath us while the
287-
// folder dialog is open.
288-
var localBaseRepositoryPath = BaseRepositoryPath;
289-
var browseResult = operatingSystem.Dialog.BrowseForDirectory(localBaseRepositoryPath, Resources.BrowseForDirectory);
290-
291-
if (!browseResult.Success)
292-
return;
293-
294-
var directory = browseResult.DirectoryPath ?? localBaseRepositoryPath;
295-
296-
try
297-
{
298-
BaseRepositoryPath = directory;
299-
}
300-
catch (Exception e)
301-
{
302-
// TODO: We really should limit this to exceptions we know how to handle.
303-
log.Error(string.Format(CultureInfo.InvariantCulture,
304-
"Failed to set base repository path.{0}localBaseRepositoryPath = \"{1}\"{0}BaseRepositoryPath = \"{2}\"{0}Chosen directory = \"{3}\"",
305-
System.Environment.NewLine, localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"), e);
306-
}
307-
}, RxApp.MainThreadScheduler);
308-
}
309289
}
310290
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.Collections.Specialized;
5+
using System.ComponentModel;
46
using System.Reactive;
57

68
namespace GitHub.Collections
@@ -15,7 +17,10 @@ namespace GitHub.Collections
1517
/// for T
1618
/// </summary>
1719
/// <typeparam name="T"></typeparam>
18-
public interface ITrackingCollection<T> : IDisposable, IList<T> where T : ICopyable<T>
20+
public interface ITrackingCollection<T> : IDisposable,
21+
INotifyCollectionChanged, INotifyPropertyChanged,
22+
IList<T>, ICollection<T>, IEnumerable<T>
23+
where T : ICopyable<T>
1924
{
2025
/// <summary>
2126
/// Sets up an observable as source for the collection.
@@ -55,7 +60,6 @@ public interface ITrackingCollection<T> : IDisposable, IList<T> where T : ICopya
5560
/// How long to delay between processing incoming items
5661
/// </summary>
5762
TimeSpan ProcessingDelay { get; set; }
58-
event NotifyCollectionChangedEventHandler CollectionChanged;
5963
IObservable<Unit> OriginalCompleted { get; }
6064
}
6165
}

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ enum TheAction
152152

153153
bool originalSourceIsCompleted;
154154
bool signalOriginalSourceCompletion;
155-
readonly Subject<Unit> originalSourceCompleted = new Subject<Unit>();
155+
ReplaySubject<Unit> originalSourceCompleted;
156156
public IObservable<Unit> OriginalCompleted => originalSourceCompleted;
157157

158158
TimeSpan requestedDelay;
@@ -214,15 +214,33 @@ public IObservable<T> Listen(IObservable<T> obs)
214214
// to the cache queue, and signal that data is available
215215
// for processing
216216
dataPump = obs
217+
.Catch<T, Exception>(ex =>
218+
{
219+
originalSourceCompleted.OnError(ex);
220+
return Observable.Throw<T>(ex);
221+
})
217222
.Do(data =>
218223
{
219224
cache.Enqueue(new ActionData(data));
220225
signalHaveData.OnNext(Unit.Default);
221226
})
222227
.Finally(() =>
223228
{
229+
if (disposed)
230+
return;
231+
224232
originalSourceIsCompleted = true;
225-
signalOriginalSourceCompletion = true;
233+
if (!cache.IsEmpty)
234+
{
235+
signalOriginalSourceCompletion = true;
236+
}
237+
else
238+
{
239+
originalSourceCompleted.OnNext(Unit.Default);
240+
originalSourceCompleted.OnCompleted();
241+
signalNeedData.OnCompleted();
242+
signalHaveData.OnCompleted();
243+
}
226244
})
227245
.Publish();
228246

@@ -275,6 +293,7 @@ public IObservable<T> Listen(IObservable<T> obs)
275293
{
276294
signalOriginalSourceCompletion = false;
277295
originalSourceCompleted.OnNext(Unit.Default);
296+
originalSourceCompleted.OnCompleted();
278297
}
279298
}
280299
else
@@ -1086,6 +1105,7 @@ void Reset()
10861105
disposables.Add(signalHaveData);
10871106
signalNeedData = new Subject<Unit>();
10881107
disposables.Add(signalNeedData);
1108+
originalSourceCompleted = new ReplaySubject<Unit>();
10891109

10901110
resetting = false;
10911111
}

src/GitHub.Exports.Reactive/Services/IModelService.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ public interface IModelService : IDisposable
1616
IObservable<AccountCacheItem> GetUserFromCache();
1717
IObservable<Unit> InsertUser(AccountCacheItem user);
1818
IObservable<IReadOnlyList<IAccount>> GetAccounts();
19-
IObservable<IReadOnlyList<IRepositoryModel>> GetRepositories();
2019
IObservable<LicenseItem> GetLicenses();
2120
IObservable<GitIgnoreItem> GetGitIgnoreTemplates();
22-
ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo,
23-
ITrackingCollection<IPullRequestModel> collection = null);
21+
ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo, ITrackingCollection<IPullRequestModel> collection);
2422
IObservable<Unit> InvalidateAll();
23+
ITrackingCollection<IRepositoryModel> GetRepositories(ITrackingCollection<IRepositoryModel> collection);
2524
}
2625
}

src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Reactive;
33
using GitHub.Models;
44
using ReactiveUI;
5+
using System.Collections.ObjectModel;
56

67
namespace GitHub.ViewModels
78
{
@@ -10,11 +11,6 @@ namespace GitHub.ViewModels
1011
/// </summary>
1112
public interface IRepositoryCloneViewModel : IViewModel, IRepositoryCreationTarget
1213
{
13-
/// <summary>
14-
/// Command to load the repositories.
15-
/// </summary>
16-
IReactiveCommand<IReadOnlyList<IRepositoryModel>> LoadRepositoriesCommand { get; }
17-
1814
/// <summary>
1915
/// Command to clone the currently selected repository.
2016
/// </summary>
@@ -23,7 +19,7 @@ public interface IRepositoryCloneViewModel : IViewModel, IRepositoryCreationTarg
2319
/// <summary>
2420
/// The list of repositories the current user may clone from the specified host.
2521
/// </summary>
26-
IReactiveDerivedList<IRepositoryModel> FilteredRepositories { get; }
22+
ObservableCollection<IRepositoryModel> Repositories { get; }
2723

2824
IRepositoryModel SelectedRepository { get; set; }
2925

0 commit comments

Comments
 (0)