async Task RepoChanged(ILocalRepositoryModel repository) { try { await ThreadingHelper.SwitchToMainThreadAsync(); await EnsureLoggedIn(repository); if (repository != this.repository) { this.repository = repository; CurrentSession = null; sessions.Clear(); } if (string.IsNullOrWhiteSpace(repository?.CloneUrl)) { return; } var modelService = hosts.LookupHost(HostAddress.Create(repository.CloneUrl))?.ModelService; var session = CurrentSession; if (modelService != null) { var pr = await service.GetPullRequestForCurrentBranch(repository).FirstOrDefaultAsync(); if (pr?.Item1 != (CurrentSession?.PullRequest.Base.RepositoryCloneUrl.Owner) && pr?.Item2 != (CurrentSession?.PullRequest.Number)) { var pullRequest = await GetPullRequestForTip(modelService, repository); if (pullRequest != null) { var newSession = await GetSessionInternal(pullRequest); if (newSession != null) { newSession.IsCheckedOut = true; } session = newSession; } } } else { session = null; } CurrentSession = session; } catch { // TODO: Log } }
public static IObservable<bool> IsLoggedIn(this IConnectionManager cm, IRepositoryHosts hosts, HostAddress address) { return cm.Connections.ToObservable() .Where(c => c.HostAddress.Equals(address)) .SelectMany(c => c.Login()) .Any(c => hosts.LookupHost(c.HostAddress).IsLoggedIn); }
public static IObservable <bool> IsLoggedIn(this IConnectionManager cm, IRepositoryHosts hosts, HostAddress address) { return(cm.Connections.ToObservable() .Where(c => c.HostAddress.Equals(address)) .SelectMany(c => c.Login()) .Any(c => hosts.LookupHost(c.HostAddress).IsLoggedIn)); }
public static IObservable <IConnection> GetLoggedInConnections(this IConnectionManager cm, IRepositoryHosts hosts) { Guard.ArgumentNotNull(hosts, nameof(hosts)); return(cm.Connections.ToObservable() .SelectMany(c => c.Login()) .Where(c => hosts.LookupHost(c.HostAddress).IsLoggedIn)); }
public static IObservable<IConnection> GetLoggedInConnections(this IConnectionManager cm, IRepositoryHosts hosts) { Guard.ArgumentNotNull(hosts, nameof(hosts)); return cm.Connections.ToObservable() .SelectMany(c => c.Login()) .Where(c => hosts.LookupHost(c.HostAddress).IsLoggedIn); }
async Task RepoChanged(ILocalRepositoryModel repository) { try { await EnsureLoggedIn(repository); if (repository != this.repository) { this.repository = repository; CurrentSession = null; sessions.Clear(); } var modelService = hosts.LookupHost(HostAddress.Create(repository.CloneUrl))?.ModelService; var session = CurrentSession; if (modelService != null) { var number = await service.GetPullRequestForCurrentBranch(repository).FirstOrDefaultAsync(); if (number != (CurrentSession?.PullRequest.Number ?? 0)) { var pullRequest = await GetPullRequestForTip(modelService, repository); if (pullRequest != null) { var newSession = await GetSessionInternal(pullRequest);; if (newSession != null) { newSession.IsCheckedOut = true; } session = newSession; } } } else { session = null; } CurrentSession = session; } catch { // TODO: Log } }
public void Start([AllowNull] IConnection connection) { if (connection != null) { uiProvider.AddService(typeof(IConnection), connection); connection.Login() .Select(c => hosts.LookupHost(connection.HostAddress)) .Do(host => { machine.Configure(UIViewType.None) .Permit(Trigger.Auth, UIViewType.Login) .PermitIf(Trigger.Create, UIViewType.Create, () => host.IsLoggedIn) .PermitIf(Trigger.Create, UIViewType.Login, () => !host.IsLoggedIn) .PermitIf(Trigger.Clone, UIViewType.Clone, () => host.IsLoggedIn) .PermitIf(Trigger.Clone, UIViewType.Login, () => !host.IsLoggedIn) .PermitIf(Trigger.Publish, UIViewType.Publish, () => host.IsLoggedIn) .PermitIf(Trigger.Publish, UIViewType.Login, () => !host.IsLoggedIn); }) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { }, () => { Debug.WriteLine("Start ({0})", GetHashCode()); Fire((Trigger)(int)currentFlow); }); } else { connectionManager .IsLoggedIn(hosts) .Do(loggedin => { if (!loggedin && currentFlow != UIControllerFlow.Authentication) { connectionAdded = (s, e) => { if (e.Action == NotifyCollectionChangedAction.Add) { uiProvider.AddService(typeof(IConnection), e.NewItems[0]); } }; connectionManager.Connections.CollectionChanged += connectionAdded; } machine.Configure(UIViewType.None) .Permit(Trigger.Auth, UIViewType.Login) .PermitIf(Trigger.Create, UIViewType.Create, () => loggedin) .PermitIf(Trigger.Create, UIViewType.Login, () => !loggedin) .PermitIf(Trigger.Clone, UIViewType.Clone, () => loggedin) .PermitIf(Trigger.Clone, UIViewType.Login, () => !loggedin) .PermitIf(Trigger.Publish, UIViewType.Publish, () => loggedin) .PermitIf(Trigger.Publish, UIViewType.Login, () => !loggedin); }) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { }, () => { Debug.WriteLine("Start ({0})", GetHashCode()); Fire((Trigger)(int)currentFlow); }); } }
UIControllerFlow SelectActiveFlow() { var host = connection != null?hosts.LookupHost(connection.HostAddress) : null; var loggedIn = host?.IsLoggedIn ?? false; return(loggedIn ? selectedFlow : UIControllerFlow.Authentication); }
protected IConnection SetupConnection(IServiceProvider provider, IRepositoryHosts hosts, IRepositoryHost host) { var connection = provider.GetConnection(); connection.Login().Returns(Observable.Return(connection)); hosts.LookupHost(connection.HostAddress).Returns(host); host.IsLoggedIn.Returns(true); return(connection); }
UIControllerFlow SelectActiveFlow() { var host = connection != null?hosts.LookupHost(connection.HostAddress) : null; var loggedIn = host?.IsLoggedIn ?? false; if (!loggedIn || selectedFlow != UIControllerFlow.Gist) { return(loggedIn ? selectedFlow : UIControllerFlow.Authentication); } var supportsGist = host?.SupportsGist ?? false; return(supportsGist ? selectedFlow : UIControllerFlow.LogoutRequired); }
public static void SetupConnections(IRepositoryHosts hosts, IConnectionManager cm, List<HostAddress> adds, List<IConnection> conns, List<IRepositoryHost> hsts, string uri) { var add = HostAddress.Create(new Uri(uri)); var host = Substitute.For<IRepositoryHost>(); var conn = Substitute.For<IConnection>(); host.Address.Returns(add); conn.HostAddress.Returns(add); adds.Add(add); hsts.Add(host); conns.Add(conn); if (add.IsGitHubDotCom()) hosts.GitHubHost.Returns(host); else hosts.EnterpriseHost.Returns(host); hosts.LookupHost(Arg.Is(add)).Returns(host); }
public static void SetupConnections(IRepositoryHosts hosts, IConnectionManager cm, List <HostAddress> adds, List <IConnection> conns, List <IRepositoryHost> hsts, string uri) { var add = HostAddress.Create(new Uri(uri)); var host = Substitute.For <IRepositoryHost>(); var conn = Substitute.For <IConnection>(); host.Address.Returns(add); conn.HostAddress.Returns(add); adds.Add(add); hsts.Add(host); conns.Add(conn); if (add.IsGitHubDotCom()) { hosts.GitHubHost.Returns(host); } else { hosts.EnterpriseHost.Returns(host); } hosts.LookupHost(Arg.Is(add)).Returns(host); }
public static IObservable<bool> IsLoggedIn(this IConnection connection, IRepositoryHosts hosts) { Guard.ArgumentNotNull(hosts, nameof(hosts)); return connection?.Login().Any(c => hosts.LookupHost(c.HostAddress).IsLoggedIn) ?? Observable.Return(false); }
public static IObservable <bool> IsLoggedIn(this IConnection connection, IRepositoryHosts hosts) { Guard.ArgumentNotNull(hosts, nameof(hosts)); return(connection?.Login().Any(c => hosts.LookupHost(c.HostAddress).IsLoggedIn) ?? Observable.Return(false)); }
protected ConnectionRepositoryHostMap(IConnection connection, IRepositoryHosts hosts) : this(hosts.LookupHost(connection.HostAddress)) { }
public RepositoryPublishViewModel( IRepositoryHosts hosts, IRepositoryPublishService repositoryPublishService, INotificationService notificationService, IConnectionManager connectionManager, IUsageTracker usageTracker) { this.notificationService = notificationService; this.hosts = hosts; this.usageTracker = usageTracker; title = this.WhenAny( x => x.SelectedHost, x => x.Value != null ? string.Format(CultureInfo.CurrentCulture, Resources.PublishToTitle, x.Value.Title) : Resources.PublishTitle ) .ToProperty(this, x => x.Title); Connections = connectionManager.Connections; this.repositoryPublishService = repositoryPublishService; if (Connections.Any()) { SelectedConnection = Connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()) ?? Connections[0]; } accounts = this.WhenAny(x => x.SelectedConnection, x => x.Value != null ? hosts.LookupHost(x.Value.HostAddress) : RepositoryHosts.DisconnectedRepositoryHost) .Where(x => !(x is DisconnectedRepositoryHost)) .SelectMany(host => host.ModelService.GetAccounts()) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.Accounts, initialValue: new ReadOnlyCollection <IAccount>(new IAccount[] {})); this.WhenAny(x => x.Accounts, x => x.Value) .WhereNotNull() .Where(accts => accts.Any()) .Subscribe(accts => { var selectedAccount = accts.FirstOrDefault(); if (selectedAccount != null) { SelectedAccount = accts.FirstOrDefault(); } }); isHostComboBoxVisible = this.WhenAny(x => x.Connections, x => x.Value) .WhereNotNull() .Select(h => h.Count > 1) .ToProperty(this, x => x.IsHostComboBoxVisible); InitializeValidation(); PublishRepository = InitializePublishRepositoryCommand(); canKeepPrivate = CanKeepPrivateObservable.CombineLatest(PublishRepository.IsExecuting, (canKeep, publishing) => canKeep && !publishing) .ToProperty(this, x => x.CanKeepPrivate); isPublishing = PublishRepository.IsExecuting .ToProperty(this, x => x.IsPublishing); var defaultRepositoryName = repositoryPublishService.LocalRepositoryName; if (!string.IsNullOrEmpty(defaultRepositoryName)) { RepositoryName = defaultRepositoryName; } this.WhenAny(x => x.SelectedConnection, x => x.SelectedAccount, (a, b) => true) .Where(x => RepositoryNameValidator.ValidationResult != null && SafeRepositoryNameWarningValidator.ValidationResult != null) .Subscribe(async _ => { var name = RepositoryName; RepositoryName = null; await RepositoryNameValidator.ResetAsync(); await SafeRepositoryNameWarningValidator.ResetAsync(); RepositoryName = name; }); }
public void Start([AllowNull] IConnection connection) { if (connection != null) { if (currentFlow != UIControllerFlow.Authentication) { uiProvider.AddService(connection); } else // sanity check: it makes zero sense to pass a connection in when calling the auth flow { Debug.Assert(false, "Calling the auth flow with a connection makes no sense!"); } connection.Login() .Select(c => hosts.LookupHost(connection.HostAddress)) .Do(host => { machine.Configure(UIViewType.None) .Permit(Trigger.Auth, UIViewType.Login) .PermitIf(Trigger.Create, UIViewType.Create, () => host.IsLoggedIn) .PermitIf(Trigger.Create, UIViewType.Login, () => !host.IsLoggedIn) .PermitIf(Trigger.Clone, UIViewType.Clone, () => host.IsLoggedIn) .PermitIf(Trigger.Clone, UIViewType.Login, () => !host.IsLoggedIn) .PermitIf(Trigger.Publish, UIViewType.Publish, () => host.IsLoggedIn) .PermitIf(Trigger.Publish, UIViewType.Login, () => !host.IsLoggedIn); }) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { }, () => { Debug.WriteLine("Start ({0})", GetHashCode()); Fire((Trigger)(int)currentFlow); }); } else { connectionManager .GetLoggedInConnections(hosts) .FirstOrDefaultAsync() .Select(c => { bool loggedin = c != null; if (currentFlow != UIControllerFlow.Authentication) { if (loggedin) // register the first available connection so the viewmodel can use it { uiProvider.AddService(c); } else { // a connection will be added to the list when auth is done, register it so the next // viewmodel can use it connectionAdded = (s, e) => { if (e.Action == NotifyCollectionChangedAction.Add) { uiProvider.AddService(typeof(IConnection), e.NewItems[0]); } }; connectionManager.Connections.CollectionChanged += connectionAdded; } } machine.Configure(UIViewType.None) .Permit(Trigger.Auth, UIViewType.Login) .PermitIf(Trigger.Create, UIViewType.Create, () => loggedin) .PermitIf(Trigger.Create, UIViewType.Login, () => !loggedin) .PermitIf(Trigger.Clone, UIViewType.Clone, () => loggedin) .PermitIf(Trigger.Clone, UIViewType.Login, () => !loggedin) .PermitIf(Trigger.Publish, UIViewType.Publish, () => loggedin) .PermitIf(Trigger.Publish, UIViewType.Login, () => !loggedin); return(loggedin); }) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { }, () => { Debug.WriteLine("Start ({0})", GetHashCode()); Fire((Trigger)(int)currentFlow); }); } }
/// <summary> /// Configures the UI that gets loaded when entering a certain state and which state /// to go to for each trigger. Which state to go to depends on which ui flow state machine /// is currently active - when a trigger happens, the PermitDynamic conditions will /// lookup the currently active state machine from the list of available ones in `machines`, /// fire the requested trigger on it, and return the state that it went to, which causes /// `uiStateMachine` to load a new UI (or exit) /// There is a bit of redundant information regarding valid state transitions between this /// state machine and the individual state machines for each ui flow. This is unavoidable /// because permited transition triggers have to be explicit, so care should be taken to /// make sure permitted triggers per view here match the permitted triggers in the individual /// state machines. /// </summary> void ConfigureUIHandlingStates() { uiStateMachine.Configure(UIViewType.None) .OnEntry(tr => stopping = false) .PermitDynamic(Trigger.Next, () => { var loggedIn = connection != null && hosts.LookupHost(connection.HostAddress).IsLoggedIn; activeFlow = loggedIn ? mainFlow : UIControllerFlow.Authentication; return(Go(Trigger.Next)); }) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.Clone) .OnEntry(tr => RunView(UIViewType.Clone, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.Create) .OnEntry(tr => RunView(UIViewType.Create, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.Publish) .OnEntry(tr => RunView(UIViewType.Publish, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.PRList) .OnEntry(tr => RunView(UIViewType.PRList, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.PRDetail, () => Go(Trigger.PRDetail)) .PermitDynamic(Trigger.PRCreation, () => Go(Trigger.PRCreation)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); triggers.Add(Trigger.PRDetail, uiStateMachine.SetTriggerParameters <ViewWithData>(Trigger.PRDetail)); uiStateMachine.Configure(UIViewType.PRDetail) .OnEntryFrom(triggers[Trigger.PRDetail], (arg, tr) => RunView(UIViewType.PRDetail, CalculateDirection(tr), arg)) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.PRCreation) .OnEntry(tr => RunView(UIViewType.PRCreation, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.Login) .OnEntry(tr => RunView(UIViewType.Login, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.TwoFactor) .OnEntry(tr => RunView(UIViewType.TwoFactor, CalculateDirection(tr))) .PermitDynamic(Trigger.Next, () => Go(Trigger.Next)) .PermitDynamic(Trigger.Cancel, () => Go(Trigger.Cancel)) .PermitDynamic(Trigger.Finish, () => Go(Trigger.Finish)); uiStateMachine.Configure(UIViewType.End) .OnEntryFrom(Trigger.Cancel, () => End(false)) .OnEntryFrom(Trigger.Next, () => End(true)) .OnEntryFrom(Trigger.Finish, () => End(true)) // clear all the views and viewmodels created by a subflow .OnExit(() => { // it's important to have the stopping flag set before we do this if (activeFlow == mainFlow) { completion?.OnNext(Success.Value); completion?.OnCompleted(); } DisposeFlow(activeFlow); if (activeFlow == mainFlow) { uiProvider.RemoveService(typeof(IConnection), this); transition.OnCompleted(); Reset(); } else { activeFlow = stopping || LoggedIn ? mainFlow : UIControllerFlow.Authentication; } }) .PermitDynamic(Trigger.Next, () => { // sets the state to None for the current flow var state = Go(Trigger.Next); if (activeFlow != mainFlow) { if (stopping) { // triggering the End state again so that it can clear up the main flow and really end state = Go(Trigger.Finish, mainFlow); } else { // sets the state to the first UI of the main flow or the auth flow, depending on whether you're logged in state = Go(Trigger.Next, LoggedIn ? mainFlow : UIControllerFlow.Authentication); } } return(state); }) .Permit(Trigger.Finish, UIViewType.None); }
public RepositoryPublishViewModel( IRepositoryHosts hosts, IRepositoryPublishService repositoryPublishService, INotificationService notificationService, IConnectionManager connectionManager, IUsageTracker usageTracker) { this.notificationService = notificationService; this.hosts = hosts; this.usageTracker = usageTracker; title = this.WhenAny( x => x.SelectedHost, x => x.Value != null ? string.Format(CultureInfo.CurrentCulture, Resources.PublishToTitle, x.Value.Title) : Resources.PublishTitle ) .ToProperty(this, x => x.Title); Connections = connectionManager.Connections; this.repositoryPublishService = repositoryPublishService; if (Connections.Any()) SelectedConnection = Connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()) ?? Connections[0]; accounts = this.WhenAny(x => x.SelectedConnection, x => x.Value != null ? hosts.LookupHost(x.Value.HostAddress) : RepositoryHosts.DisconnectedRepositoryHost) .Where(x => !(x is DisconnectedRepositoryHost)) .SelectMany(host => host.ModelService.GetAccounts()) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.Accounts, initialValue: new ReadOnlyCollection<IAccount>(new IAccount[] {})); this.WhenAny(x => x.Accounts, x => x.Value) .WhereNotNull() .Where(accts => accts.Any()) .Subscribe(accts => { var selectedAccount = accts.FirstOrDefault(); if (selectedAccount != null) SelectedAccount = accts.FirstOrDefault(); }); isHostComboBoxVisible = this.WhenAny(x => x.Connections, x => x.Value) .WhereNotNull() .Select(h => h.Count > 1) .ToProperty(this, x => x.IsHostComboBoxVisible); InitializeValidation(); PublishRepository = InitializePublishRepositoryCommand(); canKeepPrivate = CanKeepPrivateObservable.CombineLatest(PublishRepository.IsExecuting, (canKeep, publishing) => canKeep && !publishing) .ToProperty(this, x => x.CanKeepPrivate); isPublishing = PublishRepository.IsExecuting .ToProperty(this, x => x.IsPublishing); var defaultRepositoryName = repositoryPublishService.LocalRepositoryName; if (!string.IsNullOrEmpty(defaultRepositoryName)) RepositoryName = defaultRepositoryName; this.WhenAny(x => x.SelectedConnection, x => x.SelectedAccount, (a,b) => true) .Where(x => RepositoryNameValidator.ValidationResult != null && SafeRepositoryNameWarningValidator.ValidationResult != null) .Subscribe(async _ => { var name = RepositoryName; RepositoryName = null; await RepositoryNameValidator.ResetAsync(); await SafeRepositoryNameWarningValidator.ResetAsync(); RepositoryName = name; }); }