Пример #1
0
        public AdministratorViewModel(Library library)
        {
            if (library == null)
                Throw.ArgumentNullException(() => library);

            this.library = library;
        }
Пример #2
0
        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;
        }
Пример #3
0
        public static Library CreateLibraryWithPlaylist(string playlistName = "Playlist")
        {
            var library = new Library(new Mock<IRemovableDriveWatcher>().Object);
            library.AddAndSwitchToPlaylist(playlistName);

            return library;
        }
Пример #4
0
        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;
        }
Пример #5
0
        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());
            }
        }
Пример #6
0
        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;
        }
Пример #7
0
        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;
        }
Пример #8
0
        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();
        }
Пример #9
0
        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}
            };
        }
Пример #10
0
        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();
        }
Пример #11
0
        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;
        }
Пример #12
0
        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;
        }
Пример #13
0
        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));
        }
Пример #14
0
        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);
        }
Пример #15
0
        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;
        }