public AdministratorViewModel(Library library) { if (library == null) Throw.ArgumentNullException(() => library); this.library = library; }
public Library Build() { var finishSubject = new Subject<Unit>(); var mediaPlayerCallback = Substitute.For<IMediaPlayerCallback>(); mediaPlayerCallback.PlayAsync().Returns(_ => Task.Run(() => finishSubject.OnNext(Unit.Default))); mediaPlayerCallback.Finished.Returns(finishSubject); var library = new Library( this.reader ?? Substitute.For<ILibraryReader>(), this.writer ?? Substitute.For<ILibraryWriter>(), this.settings ?? new CoreSettings(new InMemoryBlobCache()), this.fileSystem ?? new MockFileSystem(), x => this.songFinder ?? SetupDefaultLocalSongFinder()); Guid accessToken = library.LocalAccessControl.RegisterLocalAccessToken(); if (this.playlistName != null) { library.AddAndSwitchToPlaylist(playlistName, accessToken); } library.RegisterAudioPlayerCallback(this.audioPlayerCallback ?? mediaPlayerCallback, accessToken); return library; }
public static Library CreateLibraryWithPlaylist(string playlistName = "Playlist") { var library = new Library(new Mock<IRemovableDriveWatcher>().Object); library.AddAndSwitchToPlaylist(playlistName); return library; }
public StatusViewModel(Library library) { if (library == null) Throw.ArgumentNullException(() => library); this.library = library; this.library.Updating += (sender, e) => this.IsUpdating = true; this.library.Updated += (sender, args) => this.IsUpdating = false; }
static DesignTimeShellViewModel() { if (Execute.InDesignMode) { string directoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Espera\"); string filePath = Path.Combine(directoryPath, "Library.xml"); Library = new Library(new RemovableDriveWatcher(), new LibraryFileReader(filePath), new LibraryFileWriter(filePath), new LibrarySettingsWrapper()); } }
public static Library LoadLibrary() { if (library == null) { library = new Library(new LibraryFileReader(AppInfo.LibraryFilePath), new LibraryFileWriter(AppInfo.LibraryFilePath), new CoreSettings(), new MockFileSystem()); library.Initialize(); } return library; }
public static Library LoadLibrary() { if (library == null) { string directoryPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Espera\"); string filePath = Path.Combine(directoryPath, "Library.json"); library = new Library(new LibraryFileReader(filePath), new LibraryFileWriter(filePath), new CoreSettings(), new MockFileSystem()); } return library; }
public MobileApi(int port, Library library) { if (port < NetworkConstants.MinPort || port > NetworkConstants.MaxPort) Throw.ArgumentOutOfRangeException(() => port); if (library == null) Throw.ArgumentNullException(() => library); this.port = port; this.library = library; this.clients = new ReactiveList<MobileClient>(); this.clientListGate = new object(); this.isPortOccupied = new BehaviorSubject<bool>(false); this.listenerSubscriptions = new CompositeDisposable(); }
public MobileClient(TcpClient socket, TcpClient fileSocket, Library library) { if (socket == null) Throw.ArgumentNullException(() => socket); if (fileSocket == null) Throw.ArgumentNullException(() => socket); if (library == null) Throw.ArgumentNullException(() => library); this.socket = socket; this.fileSocket = fileSocket; this.library = library; this.disposable = new CompositeDisposable(); this.gate = new SemaphoreSlim(1, 1); this.disconnected = new Subject<Unit>(); this.lastSoundCloudRequest = new List<SoundCloudSong>(); this.lastYoutubeRequest = new List<YoutubeSong>(); this.videoPlayerToggleRequest = new Subject<Unit>(); this.messageActionMap = new Dictionary<RequestAction, Func<JToken, Task<ResponseInfo>>> { {RequestAction.GetConnectionInfo, this.GetConnectionInfo}, {RequestAction.ToggleYoutubePlayer, this.ToggleVideoPlayer}, {RequestAction.GetLibraryContent, this.GetLibraryContent}, {RequestAction.GetSoundCloudSongs, this.GetSoundCloudSongs}, {RequestAction.GetYoutubeSongs, this.GetYoutubeSongs}, {RequestAction.AddPlaylistSongs, this.AddPlaylistSongs}, {RequestAction.AddPlaylistSongsNow, this.AddPlaylistSongsNow}, {RequestAction.GetCurrentPlaylist, this.GetCurrentPlaylist}, {RequestAction.PlayPlaylistSong, this.PlayPlaylistSong}, {RequestAction.ContinueSong, this.ContinueSong}, {RequestAction.PauseSong, this.PauseSong}, {RequestAction.PlayNextSong, this.PlayNextSong}, {RequestAction.PlayPreviousSong, this.PlayPreviousSong}, {RequestAction.RemovePlaylistSong, this.PostRemovePlaylistSong}, {RequestAction.MovePlaylistSongUp, this.MovePlaylistSongUp}, {RequestAction.MovePlaylistSongDown, this.MovePlaylistSongDown}, {RequestAction.GetVolume, this.GetVolume}, {RequestAction.SetVolume, this.SetVolume}, {RequestAction.VoteForSong, this.VoteForSong}, {RequestAction.QueueRemoteSong, this.QueueRemoteSong}, {RequestAction.SetCurrentTime, this.SetCurrentTime} }; }
public ShellViewModel() { string directoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Espera\"); string filePath = Path.Combine(directoryPath, "Library.xml"); if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } this.library = new Library(new RemovableDriveWatcher(), new LibraryFileReader(filePath), new LibraryFileWriter(filePath)); this.library.Initialize(); this.library.SongStarted += (sender, args) => this.HandleSongStarted(); this.library.SongFinished += (sender, args) => this.HandleSongFinished(); this.library.SongCorrupted += (sender, args) => this.HandleSongCorrupted(); this.library.AccessModeChanged += (sender, e) => this.UpdateUserAccess(); this.library.PlaylistChanged += (sender, e) => this.UpdatePlaylist(); if (!this.library.Playlists.Any()) { this.library.AddAndSwitchToPlaylist(this.GetNewPlaylistName()); } else { this.library.SwitchToPlaylist(this.library.Playlists.First().Name); } this.AdministratorViewModel = new AdministratorViewModel(this.library); this.LocalViewModel = new LocalViewModel(this.library); this.LocalViewModel.TimeoutWarning += (sender, e) => this.TriggerTimeoutWarning(); this.YoutubeViewModel = new YoutubeViewModel(this.library); this.YoutubeViewModel.TimeoutWarning += (sender, e) => this.TriggerTimeoutWarning(); this.updateTimer = new Timer(333); this.updateTimer.Elapsed += (sender, e) => this.UpdateCurrentTime(); this.playlistTimeoutUpdateTimer = new Timer(333); this.playlistTimeoutUpdateTimer.Elapsed += (sender, e) => this.UpdateRemainingPlaylistTimeout(); this.playlistTimeoutUpdateTimer.Start(); }
public ShellViewModel(Library library) { this.library = library; this.library.Initialize(); this.library.SongStarted += (sender, args) => this.HandleSongStarted(); this.library.SongFinished += (sender, args) => this.HandleSongFinished(); this.library.SongCorrupted += (sender, args) => this.HandleSongCorrupted(); this.library.AccessModeChanged += (sender, e) => this.UpdateUserAccess(); this.library.PlaylistChanged += (sender, e) => this.UpdatePlaylist(); if (!this.library.Playlists.Any()) { this.library.AddAndSwitchToPlaylist(this.GetNewPlaylistName()); } else { this.library.SwitchToPlaylist(this.library.Playlists.First()); } this.AdministratorViewModel = new AdministratorViewModel(this.library); this.LocalViewModel = new LocalViewModel(this.library); this.LocalViewModel.TimeoutWarning += (sender, e) => this.TriggerTimeoutWarning(); this.YoutubeViewModel = new YoutubeViewModel(this.library); this.YoutubeViewModel.TimeoutWarning += (sender, e) => this.TriggerTimeoutWarning(); this.updateTimer = new Timer(333); this.updateTimer.Elapsed += (sender, e) => this.UpdateCurrentTime(); this.playlistTimeoutUpdateTimer = new Timer(333); this.playlistTimeoutUpdateTimer.Elapsed += (sender, e) => this.UpdateRemainingPlaylistTimeout(); this.playlistTimeoutUpdateTimer.Start(); this.IsLocal = true; }
public ShellViewModel(Library library, ViewSettings viewSettings, CoreSettings coreSettings, IWindowManager windowManager, MobileApiInfo mobileApiInfo) { this.library = library; this.ViewSettings = viewSettings; this.coreSettings = coreSettings; this.disposable = new CompositeDisposable(); this.UpdateViewModel = new UpdateViewModel(viewSettings); this.library.Initialize(); this.accessToken = this.library.LocalAccessControl.RegisterLocalAccessToken(); this.library.WhenAnyValue(x => x.CurrentPlaylist).Subscribe(x => this.RaisePropertyChanged("CurrentPlaylist")); this.canChangeTime = this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockTime), this.accessToken) .ToProperty(this, x => x.CanChangeTime); this.canChangeVolume = this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockVolume), this.accessToken) .ToProperty(this, x => x.CanChangeVolume); this.canAlterPlaylist = this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlaylist), this.accessToken) .ToProperty(this, x => x.CanAlterPlaylist); this.showVotes = this.library.RemoteAccessControl.WhenAnyValue(x => x.IsGuestSystemReallyEnabled) .CombineLatest(mobileApiInfo.ConnectedClientCount, (enableGuestSystem, connectedClients) => enableGuestSystem && connectedClients > 0) .ToProperty(this, x => x.ShowVotes); mobileApiInfo.VideoPlayerToggleRequest.Subscribe(_ => this.ShowVideoPlayer = !this.ShowVideoPlayer); this.isAdmin = this.library.LocalAccessControl.ObserveAccessPermission(this.accessToken) .Select(x => x == AccessPermission.Admin) .ToProperty(this, x => x.IsAdmin); this.NextSongCommand = ReactiveCommand.CreateAsyncTask(this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.accessToken) .CombineLatest(this.library.WhenAnyValue(x => x.CurrentPlaylist.CanPlayNextSong), (x1, x2) => x1 && x2) .ObserveOn(RxApp.MainThreadScheduler), _ => this.library.PlayNextSongAsync(this.accessToken)); this.PreviousSongCommand = ReactiveCommand.CreateAsyncTask(this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.accessToken) .CombineLatest(this.library.WhenAnyValue(x => x.CurrentPlaylist.CanPlayPreviousSong), (x1, x2) => x1 && x2) .ObserveOn(RxApp.MainThreadScheduler), _ => this.library.PlayPreviousSongAsync(this.accessToken)); if (!this.library.Playlists.Any()) { this.library.AddAndSwitchToPlaylist(this.GetNewPlaylistName(), this.accessToken); } else { this.library.SwitchToPlaylist(this.library.Playlists.First(), this.accessToken); } this.SettingsViewModel = new SettingsViewModel(this.library, this.ViewSettings, this.coreSettings, windowManager, this.accessToken, mobileApiInfo); this.LocalViewModel = new LocalViewModel(this.library, this.ViewSettings, this.coreSettings, accessToken); this.YoutubeViewModel = new YoutubeViewModel(this.library, this.ViewSettings, this.coreSettings, accessToken); this.SoundCloudViewModel = new SoundCloudViewModel(this.library, accessToken, this.coreSettings, this.ViewSettings); this.DirectYoutubeViewModel = new DirectYoutubeViewModel(this.library, this.coreSettings, accessToken); this.currentSongSource = this.WhenAnyValue(x => x.IsLocal, x => x.IsYoutube, x => x.IsSoundCloud, (local, youtube, soundcloud) => { if (local) { return (ISongSourceViewModel)this.LocalViewModel; } if (youtube) { return this.YoutubeViewModel; } if (soundcloud) { return this.SoundCloudViewModel; } return this.LocalViewModel; }) .ToProperty(this, x => x.CurrentSongSource, null, ImmediateScheduler.Instance); this.MuteCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsAdmin)); this.MuteCommand.Subscribe(x => this.Volume = 0); this.UnMuteCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsAdmin)); this.UnMuteCommand.Subscribe(x => this.Volume = 1); this.canModifyWindow = this.library.LocalAccessControl.HasAccess(this.ViewSettings.WhenAnyValue(x => x.LockWindow), this.accessToken) .ToProperty(this, x => x.CanModifyWindow); this.isPlaying = this.library.PlaybackState .Select(x => x == AudioPlayerState.Playing) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsPlaying); this.currentTime = this.library.CurrentPlaybackTime .StartWith(TimeSpan.Zero) .Select(x => x.FormatAdaptive()) .ToProperty(this, x => x.CurrentTime); this.currentSeconds = this.library.CurrentPlaybackTime .Select(x => (int)x.TotalSeconds) .ToProperty(this, x => x.CurrentSeconds); this.totalTime = this.library.TotalTime .Select(x => x.FormatAdaptive()) .ToProperty(this, x => x.TotalTime); this.totalSeconds = this.library.TotalTime .Select(x => (int)x.TotalSeconds) .ToProperty(this, x => x.TotalSeconds); this.volume = this.library.WhenAnyValue(x => x.Volume, x => (double)x) .ToProperty(this, x => x.Volume); this.AddPlaylistCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanAlterPlaylist)); this.AddPlaylistCommand.Subscribe(x => this.AddPlaylist()); this.Playlists = this.library.Playlists.CreateDerivedCollection(this.CreatePlaylistViewModel, x => x.Dispose()); this.ShowSettingsCommand = ReactiveCommand.Create(); this.ShowSettingsCommand.Subscribe(x => this.SettingsViewModel.HandleSettings()); this.ShufflePlaylistCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanAlterPlaylist)); this.ShufflePlaylistCommand.Subscribe(x => this.library.ShufflePlaylist(this.accessToken)); IObservable<bool> canPlay = this.WhenAnyValue(x => x.CurrentPlaylist.SelectedEntries) .CombineLatest(this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.accessToken), this.library.LoadedSong, this.library.PlaybackState, (selectedPlaylistEntries, hasPlayAccess, loadedSong, playBackState) => // The admin can always play, but if we are in party mode, we have to check // whether it is allowed to play hasPlayAccess && // If exactly one song is selected, the command can be executed (selectedPlaylistEntries != null && selectedPlaylistEntries.Count() == 1 || // If the current song is paused, the command can be executed (loadedSong != null || playBackState == AudioPlayerState.Paused))); this.PlayCommand = ReactiveCommand.CreateAsyncTask(canPlay, async _ => { if (await this.library.PlaybackState.FirstAsync() == AudioPlayerState.Paused || await this.library.LoadedSong.FirstAsync() != null) { await this.library.ContinueSongAsync(this.accessToken); } else { await this.library.PlaySongAsync(this.CurrentPlaylist.SelectedEntries.First().Index, this.accessToken); } }); this.PlayOverrideCommand = ReactiveCommand.CreateAsyncTask(this.WhenAnyValue(x => x.CurrentPlaylist.SelectedEntries) .CombineLatest(this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.accessToken), (selectedPlaylistEntries, hasAccess) => hasAccess && (selectedPlaylistEntries != null && selectedPlaylistEntries.Count() == 1)), _ => this.library.PlaySongAsync(this.CurrentPlaylist.SelectedEntries.First().Index, this.accessToken)); this.PauseCommand = ReactiveCommand.CreateAsyncTask(this.library.LocalAccessControl.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.accessToken) .CombineLatest(this.WhenAnyValue(x => x.IsPlaying), (hasAccess, isPlaying) => hasAccess && isPlaying), _ => this.library.PauseSongAsync(this.accessToken)); var pauseOrContinueCommand = this.WhenAnyValue(x => x.IsPlaying) .Select(x => x ? this.PauseCommand : this.PlayCommand).Publish(null); pauseOrContinueCommand.Connect(); this.PauseContinueCommand = ReactiveCommand.CreateAsyncTask( pauseOrContinueCommand.Select(x => x.CanExecuteObservable).Switch().ObserveOn(RxApp.MainThreadScheduler), async _ => { IReactiveCommand<Unit> command = await pauseOrContinueCommand.FirstAsync(); await command.ExecuteAsync(); }); this.EditPlaylistNameCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanAlterPlaylist, x => x.CurrentPlaylist, (x1, x2) => x1 && !x2.Model.IsTemporary)); this.EditPlaylistNameCommand.Subscribe(x => this.CurrentPlaylist.EditName = true); this.RemovePlaylistCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CurrentEditedPlaylist, x => x.CurrentPlaylist, x => x.CanAlterPlaylist, (currentEditedPlaylist, currentPlaylist, canAlterPlaylist) => (currentEditedPlaylist != null || currentPlaylist != null) && canAlterPlaylist)); this.RemovePlaylistCommand.Subscribe(x => this.RemoveCurrentPlaylist()); this.IsLocal = true; }
public PlaylistViewModel(Playlist playlist, Library library, Guid accessToken, CoreSettings coreSettings) { if (playlist == null) throw new ArgumentNullException("playlist"); if (library == null) throw new ArgumentNullException("library"); if (coreSettings == null) throw new ArgumentNullException("coreSettings"); this.playlist = playlist; this.library = library; this.disposable = new CompositeDisposable(); this.entries = playlist .CreateDerivedCollection(entry => new PlaylistEntryViewModel(entry), x => x.Dispose()) .DisposeWith(this.disposable); this.playlist.WhenAnyValue(x => x.CurrentSongIndex).ToUnit() .Merge(this.entries.Changed.ToUnit()) .Subscribe(_ => this.UpdateCurrentSong()) .DisposeWith(this.disposable); IObservable<List<PlaylistEntryViewModel>> remainingSongs = this.entries.Changed .Select(x => Unit.Default) .Merge(this.playlist.WhenAnyValue(x => x.CurrentSongIndex).ToUnit()) .Select(x => this.entries.Reverse().TakeWhile(entry => !entry.IsPlaying).ToList()); this.songsRemaining = remainingSongs .Select(x => x.Count) .ToProperty(this, x => x.SongsRemaining) .DisposeWith(this.disposable); this.timeRemaining = remainingSongs .Select(x => x.Any() ? x.Select(entry => entry.Duration).Aggregate((t1, t2) => t1 + t2) : (TimeSpan?)null) .ToProperty(this, x => x.TimeRemaining) .DisposeWith(this.disposable); this.CurrentPlayingEntry = this.Model.WhenAnyValue(x => x.CurrentSongIndex).Select(x => x == null ? null : this.entries[x.Value]); this.canAlterPlaylist = this.library.LocalAccessControl.HasAccess(coreSettings.WhenAnyValue(x => x.LockPlaylist), accessToken) .ToProperty(this, x => x.CanAlterPlaylist) .DisposeWith(disposable); // We re-evaluate the selected entries after each up or down move here, because WPF // doesn't send us proper updates about the selection var reEvaluateSelectedPlaylistEntry = new Subject<Unit>(); this.MovePlaylistSongUpCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.SelectedEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedEntries)) .Select(x => x != null && x.Count() == 1 && x.First().Index > 0) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveUp, canAlterPlaylist) => canMoveUp && canAlterPlaylist)); this.MovePlaylistSongUpCommand.Subscribe(_ => { int index = this.SelectedEntries.First().Index; this.library.MovePlaylistSong(index, index - 1, accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.MovePlaylistSongDownCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.SelectedEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedEntries)) .Select(x => x != null && x.Count() == 1 && x.First().Index < this.Songs.Count - 1) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveDown, canAlterPlaylist) => canMoveDown && canAlterPlaylist)); this.MovePlaylistSongDownCommand.Subscribe(_ => { int index = this.SelectedEntries.First().Index; this.library.MovePlaylistSong(index, index + 1, accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.MovePlaylistSongCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.SelectedEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedEntries)) .Select(x => x != null && x.Count() == 1) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveUp, canAlterPlaylist) => canMoveUp && canAlterPlaylist)); this.MovePlaylistSongCommand.Subscribe(x => { int fromIndex = this.SelectedEntries.First().Index; int toIndex = (int?)x ?? this.Songs.Last().Index + 1; // If we move a song from the front of the playlist to the back, we want it move be // in front of the target song if (fromIndex < toIndex) { toIndex--; } this.library.MovePlaylistSong(fromIndex, toIndex, accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.RemoveSelectedPlaylistEntriesCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.SelectedEntries, x => x.CanAlterPlaylist, (selectedPlaylistEntries, canAlterPlaylist) => selectedPlaylistEntries != null && selectedPlaylistEntries.Any() && canAlterPlaylist)); this.RemoveSelectedPlaylistEntriesCommand.Subscribe(x => this.library.RemoveFromPlaylist(this.SelectedEntries.Select(entry => entry.Index), accessToken)); }
public SettingsViewModel(Library library, ViewSettings viewSettings, CoreSettings coreSettings, IWindowManager windowManager, Guid accessToken, MobileApiInfo mobileApiInfo) { if (library == null) Throw.ArgumentNullException(() => library); if (viewSettings == null) Throw.ArgumentNullException(() => viewSettings); if (coreSettings == null) Throw.ArgumentNullException(() => coreSettings); if (mobileApiInfo == null) throw new ArgumentNullException("mobileApiInfo"); this.library = library; this.viewSettings = viewSettings; this.coreSettings = coreSettings; this.windowManager = windowManager; this.accessToken = accessToken; this.canCreateAdmin = this .WhenAnyValue(x => x.CreationPassword, x => !string.IsNullOrWhiteSpace(x) && !this.isAdminCreated) .ToProperty(this, x => x.CanCreateAdmin); this.CreateAdminCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanCreateAdmin), ImmediateScheduler.Instance); // Immediate execution, because we set the password to an empty string afterwards this.CreateAdminCommand.Subscribe(p => { this.library.LocalAccessControl.SetLocalPassword(this.accessToken, this.CreationPassword); this.isAdminCreated = true; }); this.ChangeToPartyCommand = ReactiveCommand.Create(this.CreateAdminCommand.Select(x => true).StartWith(false)); this.ChangeToPartyCommand.Subscribe(p => { this.library.LocalAccessControl.DowngradeLocalAccess(this.accessToken); this.ShowSettings = false; }); this.canLogin = this.WhenAnyValue(x => x.LoginPassword, x => !string.IsNullOrWhiteSpace(x)) .ToProperty(this, x => x.CanLogin); this.LoginCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanLogin), ImmediateScheduler.Instance); // Immediate execution, because we set the password to an empty string afterwards this.LoginCommand.Subscribe(p => { try { this.library.LocalAccessControl.UpgradeLocalAccess(this.accessToken, this.LoginPassword); this.IsWrongPassword = false; this.ShowLogin = false; this.ShowSettings = true; } catch (WrongPasswordException) { this.IsWrongPassword = true; } }); this.OpenLinkCommand = ReactiveCommand.Create(); this.OpenLinkCommand.Cast<string>().Subscribe(x => { try { Process.Start(x); } catch (Win32Exception ex) { this.Log().ErrorException(String.Format("Could not open link {0}", x), ex); } }); this.ReportBugCommand = ReactiveCommand.Create(); this.ReportBugCommand.Subscribe(p => this.windowManager.ShowWindow(new BugReportViewModel())); this.ChangeAccentColorCommand = ReactiveCommand.Create(); this.ChangeAccentColorCommand.Subscribe(x => this.viewSettings.AccentColor = (string)x); this.ChangeAppThemeCommand = ReactiveCommand.Create(); this.ChangeAppThemeCommand.Subscribe(x => this.viewSettings.AppTheme = (string)x); this.UpdateLibraryCommand = ReactiveCommand.Create(this.library.WhenAnyValue(x => x.IsUpdating, x => !x) .ObserveOn(RxApp.MainThreadScheduler) .CombineLatest(this.library.WhenAnyValue(x => x.SongSourcePath).Select(x => !String.IsNullOrEmpty(x)), (x1, x2) => x1 && x2)); this.UpdateLibraryCommand.Subscribe(_ => this.library.UpdateNow()); this.librarySource = this.library.WhenAnyValue(x => x.SongSourcePath) .ToProperty(this, x => x.LibrarySource); this.port = this.coreSettings.Port; this.ChangePortCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.Port, NetworkHelpers.IsPortValid)); this.ChangePortCommand.Subscribe(_ => this.coreSettings.Port = this.Port); this.remoteControlPassword = this.coreSettings.RemoteControlPassword; this.ChangeRemoteControlPasswordCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.RemoteControlPassword) .Select(x => !String.IsNullOrWhiteSpace(x))); this.ChangeRemoteControlPasswordCommand.Subscribe(x => this.library.RemoteAccessControl.SetRemotePassword(this.accessToken, this.RemoteControlPassword)); this.showRemoteControlPasswordError = this.WhenAnyValue(x => x.RemoteControlPassword, x => x.LockRemoteControl, (password, lockRemoteControl) => String.IsNullOrWhiteSpace(password) && lockRemoteControl) .ToProperty(this, x => x.ShowRemoteControlPasswordError); this.isRemoteAccessReallyLocked = this.library.RemoteAccessControl.WhenAnyValue(x => x.IsRemoteAccessReallyLocked) .ToProperty(this, x => x.IsRemoteAccessReallyLocked); this.isPortOccupied = mobileApiInfo.IsPortOccupied.ToProperty(this, x => x.IsPortOccupied); this.enableChangelog = this.viewSettings.WhenAnyValue(x => x.EnableChangelog) .ToProperty(this, x => x.EnableChangelog); this.defaultPlaybackEngine = this.coreSettings.WhenAnyValue(x => x.DefaultPlaybackEngine) .ToProperty(this, x => x.DefaultPlaybackEngine); }
public ShellViewModel(Library library, ViewSettings viewSettings, CoreSettings coreSettings, IWindowManager windowManager, MobileApiInfo mobileApiInfo) { this.library = library; this.ViewSettings = viewSettings; this.coreSettings = coreSettings; this.disposable = new CompositeDisposable(); this.UpdateViewModel = new UpdateViewModel(viewSettings); this.library.Initialize(); this.accessToken = this.library.LocalAccessControl.RegisterLocalAccessToken(); this.library.CurrentPlaylistChanged.Subscribe(x => this.RaisePropertyChanged("CurrentPlaylist")); this.canChangeTime = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockTime)) .ToProperty(this, x => x.CanChangeTime); this.canChangeVolume = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockVolume)) .ToProperty(this, x => x.CanChangeVolume); this.canAlterPlaylist = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlaylist)) .ToProperty(this, x => x.CanAlterPlaylist); this.showVotes = this.coreSettings.WhenAnyValue(x => x.EnableVotingSystem) .CombineLatest(mobileApiInfo.ConnectedClientCount, (enableVoting, connectedClients) => enableVoting && connectedClients > 0) .ToProperty(this, x => x.ShowVotes); this.isAdmin = this.library.LocalAccessControl.ObserveAccessPermission(this.accessToken) .Select(x => x == AccessPermission.Admin) .ToProperty(this, x => x.IsAdmin); this.NextSongCommand = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause)) .CombineLatest(this.library.CanPlayNextSong, (x1, x2) => x1 && x2) .ToCommand(); this.NextSongCommand.RegisterAsyncTask(_ => this.library.PlayNextSongAsync(this.accessToken)); this.PreviousSongCommand = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause)) .CombineLatest(this.library.CanPlayPreviousSong, (x1, x2) => x1 && x2) .ToCommand(); this.PreviousSongCommand.RegisterAsyncTask(_ => this.library.PlayPreviousSongAsync(this.accessToken)); if (!this.library.Playlists.Any()) { this.library.AddAndSwitchToPlaylist(this.GetNewPlaylistName(), this.accessToken); } else { this.library.SwitchToPlaylist(this.library.Playlists.First(), this.accessToken); } this.SettingsViewModel = new SettingsViewModel(this.library, this.ViewSettings, this.coreSettings, windowManager, this.accessToken, mobileApiInfo); this.LocalViewModel = new LocalViewModel(this.library, this.ViewSettings, this.coreSettings, accessToken); this.YoutubeViewModel = new YoutubeViewModel(this.library, this.ViewSettings, this.coreSettings, accessToken); this.SoundCloudViewModel = new SoundCloudViewModel(this.library, accessToken, this.coreSettings, this.ViewSettings); this.DirectYoutubeViewModel = new DirectYoutubeViewModel(this.library, accessToken); Observable.Interval(TimeSpan.FromMilliseconds(300), RxApp.TaskpoolScheduler) .Where(_ => this.RemainingPlaylistTimeout > TimeSpan.Zero) .Subscribe(x => this.RaisePropertyChanged("RemainingPlaylistTimeout")) .DisposeWith(this.disposable); this.currentSongSource = this.WhenAnyValue(x => x.IsLocal, x => x.IsYoutube, x => x.IsSoundCloud, (local, youtube, soundcloud) => { if (local) { return (ISongSourceViewModel)this.LocalViewModel; } if (youtube) { return this.YoutubeViewModel; } if (soundcloud) { return this.SoundCloudViewModel; } return this.LocalViewModel; }) .ToProperty(this, x => x.CurrentSongSource, null, ImmediateScheduler.Instance); this.displayTimeoutWarning = Observable.Merge(this.LocalViewModel.TimeoutWarning, this.YoutubeViewModel.TimeoutWarning, this.DirectYoutubeViewModel.TimeoutWarning, this.SoundCloudViewModel.TimeoutWarning) .SelectMany(x => new[] { true, false }.ToObservable()) .ToProperty(this, x => x.DisplayTimeoutWarning); this.showPlaylistTimeout = this.WhenAnyValue(x => x.IsAdmin) .CombineLatest(this.WhenAnyValue(x => x.SettingsViewModel.EnablePlaylistTimeout), (isAdmin, enableTimeout) => !isAdmin && enableTimeout) .ToProperty(this, x => x.ShowPlaylistTimeout); this.MuteCommand = new ReactiveCommand(this.WhenAnyValue(x => x.IsAdmin)); this.MuteCommand.Subscribe(x => this.Volume = 0); this.UnMuteCommand = new ReactiveCommand(this.WhenAnyValue(x => x.IsAdmin)); this.UnMuteCommand.Subscribe(x => this.Volume = 1); this.canModifyWindow = this.HasAccess(this.ViewSettings.WhenAnyValue(x => x.LockWindow)) .ToProperty(this, x => x.CanModifyWindow); this.isPlaying = this.library.PlaybackState .Select(x => x == AudioPlayerState.Playing) .ToProperty(this, x => x.IsPlaying); this.currentTime = this.library.CurrentPlaybackTime .StartWith(TimeSpan.Zero) .Select(x => x.FormatAdaptive()) .ToProperty(this, x => x.CurrentTime); this.currentSeconds = this.library.CurrentPlaybackTime .Select(x => (int)x.TotalSeconds) .ToProperty(this, x => x.CurrentSeconds); this.totalTime = this.library.TotalTime .Select(x => x.FormatAdaptive()) .ToProperty(this, x => x.TotalTime); this.totalSeconds = this.library.TotalTime .Select(x => (int)x.TotalSeconds) .ToProperty(this, x => x.TotalSeconds); this.volume = this.library.WhenAnyValue(x => x.Volume, x => (double)x) .ToProperty(this, x => x.Volume); this.AddPlaylistCommand = new ReactiveCommand(this.WhenAnyValue(x => x.CanAlterPlaylist)); this.AddPlaylistCommand.Subscribe(x => this.AddPlaylist()); this.Playlists = this.library.Playlists.CreateDerivedCollection(this.CreatePlaylistViewModel); this.Playlists.ItemsRemoved.Subscribe(x => x.Dispose()); this.ShowSettingsCommand = new ReactiveCommand(); this.ShowSettingsCommand.Subscribe(x => this.SettingsViewModel.HandleSettings()); this.ShufflePlaylistCommand = new ReactiveCommand(this.WhenAnyValue(x => x.CanAlterPlaylist)); this.ShufflePlaylistCommand.Subscribe(x => this.library.ShufflePlaylist(this.accessToken)); this.PlayCommand = new ReactiveCommand(this.WhenAnyValue(x => x.SelectedPlaylistEntries) .CombineLatest(this.WhenAnyValue(x => x.IsAdmin), this.coreSettings.WhenAnyValue(x => x.LockPlayPause), this.library.LoadedSong, this.library.PlaybackState, (selectedPlaylistEntries, isAdmin, lockPlayPause, loadedSong, playBackState) => // The admin can always play, but if we are in party mode, we have to check // whether it is allowed to play (isAdmin || !lockPlayPause) && // If exactly one song is selected, the command can be executed (selectedPlaylistEntries != null && selectedPlaylistEntries.Count() == 1 || // If the current song is paused, the command can be executed (loadedSong != null || playBackState == AudioPlayerState.Paused)))); this.PlayCommand.SelectMany(async x => { if (await this.library.PlaybackState.FirstAsync() == AudioPlayerState.Paused || await this.library.LoadedSong.FirstAsync() != null) { await this.library.ContinueSongAsync(this.accessToken); } else { await this.library.PlaySongAsync(this.SelectedPlaylistEntries.First().Index, this.accessToken); } return Unit.Default; }).Subscribe(); this.PlayOverrideCommand = this.WhenAnyValue(x => x.SelectedPlaylistEntries) .CombineLatest(this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause)), (selectedPlaylistEntries, hasAccess) => hasAccess && (selectedPlaylistEntries != null && selectedPlaylistEntries.Count() == 1)) .ToCommand(); this.PlayOverrideCommand.RegisterAsyncTask(_ => this.library.PlaySongAsync(this.SelectedPlaylistEntries.First().Index, this.accessToken)); // The default play command differs whether we are in party mode or not and depends on // the selected setting in administrator mode and the song source. // // In party mode, it is always "Add To Playlist", in administrator mode we look at the // value that the song source returns this.defaultPlaybackCommand = this.WhenAnyValue(x => x.CurrentSongSource, x => x.IsAdmin, (songSource, isAdmin) => !isAdmin || songSource.DefaultPlaybackAction == DefaultPlaybackAction.AddToPlaylist ? songSource.AddToPlaylistCommand : songSource.PlayNowCommand) .ToProperty(this, x => x.DefaultPlaybackCommand); this.PauseCommand = this.HasAccess(this.coreSettings.WhenAnyValue(x => x.LockPlayPause)) .CombineLatest(this.isPlaying, (hasAccess, isPlaying) => hasAccess && isPlaying) .ToCommand(); this.PauseCommand.RegisterAsyncTask(_ => this.library.PauseSongAsync(this.accessToken)); var pauseOrContinueCommand = this.WhenAnyValue(x => x.IsPlaying) .Select(x => x ? this.PauseCommand : this.PlayCommand).Publish(null); pauseOrContinueCommand.Connect(); this.PauseContinueCommand = pauseOrContinueCommand.Select(x => x.CanExecuteObservable).Switch().ToCommand(); this.PauseContinueCommand.SelectMany(async _ => await pauseOrContinueCommand.FirstAsync()).Subscribe(x => x.Execute(null)); this.EditPlaylistNameCommand = this.WhenAnyValue(x => x.CanAlterPlaylist, x => x.CurrentPlaylist, (x1, x2) => x1 && !x2.Model.IsTemporary) .ToCommand(); this.EditPlaylistNameCommand.Subscribe(x => this.CurrentPlaylist.EditName = true); this.RemovePlaylistCommand = this.WhenAnyValue(x => x.CurrentEditedPlaylist, x => x.CurrentPlaylist, x => x.CanAlterPlaylist, (currentEditedPlaylist, currentPlaylist, canAlterPlaylist) => (currentEditedPlaylist != null || currentPlaylist != null) && canAlterPlaylist) .ToCommand(); this.RemovePlaylistCommand.Subscribe(x => this.RemoveCurrentPlaylist()); this.RemoveSelectedPlaylistEntriesCommand = this.WhenAnyValue(x => x.SelectedPlaylistEntries, x => x.CanAlterPlaylist, (selectedPlaylistEntries, canAlterPlaylist) => selectedPlaylistEntries != null && selectedPlaylistEntries.Any() && canAlterPlaylist) .ToCommand(); this.RemoveSelectedPlaylistEntriesCommand.Subscribe(x => this.library.RemoveFromPlaylist(this.SelectedPlaylistEntries.Select(entry => entry.Index), this.accessToken)); // We re-evaluate the selected entries after each up or down move here, because WPF // doesn't send us proper updates about the selection var reEvaluateSelectedPlaylistEntry = new Subject<Unit>(); this.MovePlaylistSongUpCommand = this.WhenAnyValue(x => x.SelectedPlaylistEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedPlaylistEntries)) .Select(x => x != null && x.Count() == 1 && x.First().Index > 0) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveUp, canAlterPlaylist) => canMoveUp && canAlterPlaylist) .ToCommand(); this.MovePlaylistSongUpCommand.Subscribe(_ => { int index = this.SelectedPlaylistEntries.First().Index; this.library.MovePlaylistSong(index, index - 1, this.accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.MovePlaylistSongDownCommand = this.WhenAnyValue(x => x.SelectedPlaylistEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedPlaylistEntries)) .Select(x => x != null && x.Count() == 1 && x.First().Index < this.CurrentPlaylist.Songs.Count - 1) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveDown, canAlterPlaylist) => canMoveDown && canAlterPlaylist) .ToCommand(); this.MovePlaylistSongDownCommand.Subscribe(_ => { int index = this.SelectedPlaylistEntries.First().Index; this.library.MovePlaylistSong(index, index + 1, this.accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.MovePlaylistSongCommand = this.WhenAnyValue(x => x.SelectedPlaylistEntries) .Merge(reEvaluateSelectedPlaylistEntry.Select(_ => this.SelectedPlaylistEntries)) .Select(x => x != null && x.Count() == 1) .CombineLatest(this.WhenAnyValue(x => x.CanAlterPlaylist), (canMoveUp, canAlterPlaylist) => canMoveUp && canAlterPlaylist) .ToCommand(); this.MovePlaylistSongCommand.Subscribe(x => { int fromIndex = this.SelectedPlaylistEntries.First().Index; int toIndex = (int?)x ?? this.CurrentPlaylist.Songs.Last().Index + 1; // If we move a song from the front of the playlist to the back, we want it move be // in front of the target song if (fromIndex < toIndex) { toIndex--; } this.library.MovePlaylistSong(fromIndex, toIndex, this.accessToken); reEvaluateSelectedPlaylistEntry.OnNext(Unit.Default); }); this.IsLocal = true; }