public CloudViewModel( CloudState state, CreateFolderViewModelFactory createFolderFactory, RenameFileViewModelFactory renameFactory, FileViewModelFactory fileFactory, FolderViewModelFactory folderFactory, IAuthViewModel auth, IFileManager files, ICloud cloud) { _cloud = cloud; Folder = createFolderFactory(this); Rename = renameFactory(this); Auth = auth; var canInteract = this .WhenAnyValue( x => x.Folder.IsVisible, x => x.Rename.IsVisible, (folder, rename) => !folder && !rename); _canInteract = canInteract .ToProperty(this, x => x.CanInteract); var canRefresh = this .WhenAnyValue( x => x.Folder.IsVisible, x => x.Rename.IsVisible, x => x.Auth.IsAuthenticated, (folder, rename, authenticated) => !folder && !rename && authenticated); Refresh = ReactiveCommand.CreateFromTask( () => cloud.GetFiles(CurrentPath), canRefresh); _files = Refresh .Select( items => items .Select(file => fileFactory(file, this)) .OrderByDescending(file => file.IsFolder) .ThenBy(file => file.Name) .ToList()) .Where(items => Files == null || !items.SequenceEqual(Files)) .ToProperty(this, x => x.Files); _isLoading = Refresh .IsExecuting .ToProperty(this, x => x.IsLoading); _isReady = Refresh .IsExecuting .Skip(1) .Select(executing => !executing) .ToProperty(this, x => x.IsReady); var canOpenCurrentPath = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && file.IsFolder) .CombineLatest(Refresh.IsExecuting, canInteract, (folder, busy, ci) => folder && ci && !busy); Open = ReactiveCommand.Create( () => Path.Combine(CurrentPath, SelectedFile.Name), canOpenCurrentPath); var canCurrentPathGoBack = this .WhenAnyValue(x => x.CurrentPath) .Where(path => path != null) .Select(path => path.Length > cloud.InitialPath.Length) .CombineLatest(Refresh.IsExecuting, canInteract, (valid, busy, ci) => valid && ci && !busy); Back = ReactiveCommand.Create( () => Path.GetDirectoryName(CurrentPath), canCurrentPathGoBack); SetPath = ReactiveCommand.Create <string, string>(path => path); _currentPath = Open .Merge(Back) .Merge(SetPath) .Select(path => path ?? cloud.InitialPath) .DistinctUntilChanged() .Log(this, $"Current path changed in {cloud.Name}") .ToProperty(this, x => x.CurrentPath, state.CurrentPath ?? cloud.InitialPath); var getBreadCrumbs = ReactiveCommand.CreateFromTask( () => cloud.GetBreadCrumbs(CurrentPath)); _breadCrumbs = getBreadCrumbs .Where(items => items != null && items.Any()) .Select(items => items.Select(folder => folderFactory(folder, this))) .ToProperty(this, x => x.BreadCrumbs); _showBreadCrumbs = getBreadCrumbs .ThrownExceptions .Select(exception => false) .Merge(getBreadCrumbs.Select(items => items != null && items.Any())) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.ShowBreadCrumbs); _hideBreadCrumbs = this .WhenAnyValue(x => x.ShowBreadCrumbs) .Select(show => !show) .ToProperty(this, x => x.HideBreadCrumbs); this.WhenAnyValue(x => x.CurrentPath, x => x.IsReady) .Where(x => x.Item1 != null && x.Item2) .Select(_ => Unit.Default) .InvokeCommand(getBreadCrumbs); this.WhenAnyValue(x => x.CurrentPath) .Skip(1) .Select(_ => Unit.Default) .InvokeCommand(Refresh); this.WhenAnyValue(x => x.CurrentPath) .Subscribe(_ => SelectedFile = null); _isCurrentPathEmpty = this .WhenAnyValue(x => x.Files) .Skip(1) .Where(items => items != null) .Select(items => !items.Any()) .ToProperty(this, x => x.IsCurrentPathEmpty); _hasErrorMessage = Refresh .ThrownExceptions .Select(exception => true) .ObserveOn(RxApp.MainThreadScheduler) .Merge(Refresh.Select(x => false)) .ToProperty(this, x => x.HasErrorMessage); var canUploadToCurrentPath = this .WhenAnyValue(x => x.CurrentPath) .Select(path => path != null) .CombineLatest(Refresh.IsExecuting, canInteract, (up, loading, can) => up && can && !loading); UploadToCurrentPath = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(files.OpenRead) .Where(response => response.Name != null && response.Stream != null) .Select(args => _cloud.UploadFile(CurrentPath, args.Stream, args.Name)) .SelectMany(task => task.ToObservable()), canUploadToCurrentPath); UploadToCurrentPath.InvokeCommand(Refresh); var canDownloadSelectedFile = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && !file.IsFolder) .CombineLatest(Refresh.IsExecuting, canInteract, (down, loading, can) => down && !loading && can); DownloadSelectedFile = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(() => files.OpenWrite(SelectedFile.Name)) .Where(stream => stream != null) .Select(stream => _cloud.DownloadFile(SelectedFile.Path, stream)) .SelectMany(task => task.ToObservable()), canDownloadSelectedFile); var canLogout = cloud .IsAuthorized .DistinctUntilChanged() .Select(loggedIn => loggedIn && ( cloud.SupportsDirectAuth || cloud.SupportsOAuth || cloud.SupportsHostAuth)) .CombineLatest(canInteract, (logout, interact) => logout && interact) .ObserveOn(RxApp.MainThreadScheduler); Logout = ReactiveCommand.CreateFromTask(cloud.Logout, canLogout); _canLogout = canLogout .ToProperty(this, x => x.CanLogout); var canDeleteSelection = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && !file.IsFolder) .CombineLatest(Refresh.IsExecuting, canInteract, (del, loading, ci) => del && !loading && ci); DeleteSelectedFile = ReactiveCommand.CreateFromTask( () => cloud.Delete(SelectedFile.Path, SelectedFile.IsFolder), canDeleteSelection); DeleteSelectedFile.InvokeCommand(Refresh); var canUnselectFile = this .WhenAnyValue(x => x.SelectedFile) .Select(selection => selection != null) .CombineLatest(Refresh.IsExecuting, canInteract, (sel, loading, ci) => sel && !loading && ci); UnselectFile = ReactiveCommand.Create( () => { SelectedFile = null; }, canUnselectFile); UploadToCurrentPath.ThrownExceptions .Merge(DeleteSelectedFile.ThrownExceptions) .Merge(DownloadSelectedFile.ThrownExceptions) .Merge(Refresh.ThrownExceptions) .Merge(getBreadCrumbs.ThrownExceptions) .Log(this, $"Exception occured in provider {cloud.Name}") .Subscribe(); this.WhenAnyValue(x => x.CurrentPath) .Subscribe(path => state.CurrentPath = path); this.WhenAnyValue(x => x.Auth.IsAuthenticated) .Select(authenticated => authenticated ? _cloud.Parameters?.Token : null) .Subscribe(token => state.Token = token); this.WhenAnyValue(x => x.Auth.IsAuthenticated) .Select(authenticated => authenticated ? _cloud.Parameters?.User : null) .Subscribe(user => state.User = user); this.WhenActivated(ActivateAutoRefresh); }
public ProviderViewModel( CreateFolderViewModelFactory createFolder, RenameFileViewModelFactory createRename, FileViewModelFactory createFile, IAuthViewModel authViewModel, IFileManager fileManager, IProvider provider, IScheduler current, IScheduler main) { _provider = provider; Folder = createFolder(this); Rename = createRename(this); var canInteract = this .WhenAnyValue( x => x.Folder.IsVisible, x => x.Rename.IsVisible, (folder, rename) => !folder && !rename); _canInteract = canInteract .DistinctUntilChanged() .ToProperty(this, x => x.CanInteract, scheduler: current); _refresh = ReactiveCommand.CreateFromTask( () => provider.Get(CurrentPath), canInteract, main); _files = _refresh .Select(files => files .Select(file => createFile(file, this)) .OrderByDescending(file => file.IsFolder) .ThenBy(file => file.Name) .ToList()) .StartWithEmpty() .Where(files => Files == null || files.Count != Files.Count() || !files.All(x => Files.Any(y => x.Path == y.Path && x.Modified == y.Modified))) .ToProperty(this, x => x.Files, scheduler: current); _isLoading = _refresh .IsExecuting .ToProperty(this, x => x.IsLoading, scheduler: current); _isReady = _refresh .IsExecuting .Select(executing => !executing) .Skip(1) .ToProperty(this, x => x.IsReady, scheduler: current); var canOpenCurrentPath = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && file.IsFolder) .CombineLatest(_refresh.IsExecuting, (folder, busy) => folder && !busy) .CombineLatest(canInteract, (open, interact) => open && interact); _open = ReactiveCommand.Create( () => Path.Combine(CurrentPath, SelectedFile.Name), canOpenCurrentPath, main); var canCurrentPathGoBack = this .WhenAnyValue(x => x.CurrentPath) .Select(path => path.Length > provider.InitialPath.Length) .CombineLatest(_refresh.IsExecuting, (valid, busy) => valid && !busy) .CombineLatest(canInteract, (back, interact) => back && interact); _back = ReactiveCommand.Create( () => Path.GetDirectoryName(CurrentPath), canCurrentPathGoBack, main); _currentPath = _open .Merge(_back) .DistinctUntilChanged() .Log(this, $"Current path changed in {provider.Name}") .ToProperty(this, x => x.CurrentPath, provider.InitialPath, scheduler: current); this.WhenAnyValue(x => x.CurrentPath) .Skip(1) .Select(path => Unit.Default) .InvokeCommand(_refresh); this.WhenAnyValue(x => x.CurrentPath) .Subscribe(path => SelectedFile = null); _isCurrentPathEmpty = this .WhenAnyValue(x => x.Files) .Skip(1) .Where(files => files != null) .Select(files => !files.Any()) .ToProperty(this, x => x.IsCurrentPathEmpty, scheduler: current); _hasErrors = _refresh .ThrownExceptions .Select(exception => true) .Merge(_refresh.Select(x => false)) .ToProperty(this, x => x.HasErrors, scheduler: current); var canUploadToCurrentPath = this .WhenAnyValue(x => x.CurrentPath) .Select(path => path != null) .CombineLatest(_refresh.IsExecuting, (up, loading) => up && !loading) .CombineLatest(canInteract, (upload, interact) => upload && interact); _uploadToCurrentPath = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(fileManager.OpenRead) .Where(response => response.Name != null && response.Stream != null) .Select(x => _provider.UploadFile(CurrentPath, x.Stream, x.Name)) .SelectMany(task => task.ToObservable()), canUploadToCurrentPath, main); _uploadToCurrentPath.InvokeCommand(_refresh); var canDownloadSelectedFile = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && !file.IsFolder) .CombineLatest(_refresh.IsExecuting, (down, loading) => down && !loading) .CombineLatest(canInteract, (download, interact) => download && interact); _downloadSelectedFile = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(() => fileManager.OpenWrite(SelectedFile.Name)) .Where(stream => stream != null) .Select(stream => _provider.DownloadFile(SelectedFile.Path, stream)) .SelectMany(task => task.ToObservable()), canDownloadSelectedFile, main); var isAuthEnabled = provider.SupportsDirectAuth || provider.SupportsOAuth; var canLogout = provider .IsAuthorized .Select(loggedIn => loggedIn && isAuthEnabled) .DistinctUntilChanged() .CombineLatest(canInteract, (logout, interact) => logout && interact) .ObserveOn(main); _logout = ReactiveCommand.CreateFromTask(provider.Logout, canLogout); _canLogout = canLogout .ToProperty(this, x => x.CanLogout, scheduler: current); var canDeleteSelection = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && !file.IsFolder) .CombineLatest(_refresh.IsExecuting, (del, loading) => del && !loading) .CombineLatest(canInteract, (delete, interact) => delete && interact); _deleteSelectedFile = ReactiveCommand.CreateFromTask( () => provider.Delete(SelectedFile.Path, SelectedFile.IsFolder), canDeleteSelection); _deleteSelectedFile.InvokeCommand(Refresh); var canUnselectFile = this .WhenAnyValue(x => x.SelectedFile) .Select(selection => selection != null) .CombineLatest(_refresh.IsExecuting, (sel, loading) => sel && !loading) .CombineLatest(canInteract, (unselect, interact) => unselect && interact); _unselectFile = ReactiveCommand.Create( () => { SelectedFile = null; }, canUnselectFile); _uploadToCurrentPath .ThrownExceptions .Merge(_deleteSelectedFile.ThrownExceptions) .Merge(_downloadSelectedFile.ThrownExceptions) .Merge(_refresh.ThrownExceptions) .Log(this, $"Exception occured in provider {provider.Name}") .Subscribe(); Auth = authViewModel; Activator = new ViewModelActivator(); this.WhenActivated(disposable => { this.WhenAnyValue(x => x.Auth.IsAuthenticated) .Where(authenticated => authenticated) .Select(ignore => Unit.Default) .InvokeCommand(_refresh) .DisposeWith(disposable); var interval = TimeSpan.FromSeconds(1); Observable.Timer(interval, interval) .Select(unit => RefreshingIn - 1) .Where(value => value >= 0) .ObserveOn(main) .Subscribe(x => RefreshingIn = x) .DisposeWith(disposable); this.WhenAnyValue(x => x.RefreshingIn) .Skip(1) .Where(refreshing => refreshing == 0) .Log(this, $"Refreshing provider {provider.Name} path {CurrentPath}") .Select(value => Unit.Default) .InvokeCommand(_refresh) .DisposeWith(disposable); const int refreshPeriod = 30; _refresh.Select(results => refreshPeriod) .StartWith(refreshPeriod) .Subscribe(x => RefreshingIn = x) .DisposeWith(disposable); this.WhenAnyValue(x => x.CanInteract) .Skip(1) .Where(interact => interact) .Select(x => Unit.Default) .InvokeCommand(_refresh); }); }