├── Play
├── App.ico
├── Images
│ ├── PlayButton.png
│ ├── StopButton.png
│ ├── status-icon-off.ico
│ └── status-icon-on.ico
├── Play_TemporaryKey.pfx
├── Fonts
│ ├── OpenSans-Italic.ttf
│ ├── OpenSans-Regular.ttf
│ ├── OpenSans-Semibold.ttf
│ └── OpenSans-LightItalic.ttf
├── App.config
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── App.xaml.cs
├── App.xaml
├── packages.config
├── FadeImageButton.cs
├── Views
│ ├── SearchView.xaml.cs
│ ├── WelcomeView.xaml.cs
│ ├── SongTileView.xaml.cs
│ ├── PlayView.xaml.cs
│ ├── WelcomeView.xaml
│ ├── SearchView.xaml
│ ├── SongTileView.xaml
│ └── PlayView.xaml
├── Models
│ ├── Song.cs
│ ├── PusherHelper.cs
│ └── PlayApi.cs
├── RestSharpRxHelper.cs
├── ViewModels
│ ├── SearchViewModel.cs
│ ├── SongTileViewModel.cs
│ ├── WelcomeViewModel.cs
│ ├── PlayViewModel.cs
│ └── AppBootstrapper.cs
├── HardwareRenderingHelper.cs
├── MainWindow.xaml.cs
├── MainWindow.xaml
├── Play.csproj
└── BorderlessWindowBehavior.cs
├── ext
├── NLog.dll
├── Akavache.dll
├── Akavache.pdb
├── ReactiveUI.dll
├── ReactiveUI.pdb
├── WPFMediaKit.dll
├── EVRPresenter32.dll
├── EVRPresenter64.dll
├── ReactiveUI.Blend.dll
├── ReactiveUI.Blend.pdb
├── ReactiveUI.Xaml.dll
├── ReactiveUI.Xaml.pdb
├── System.Reactive.dll
├── PusherClientDotNet.dll
├── PusherClientDotNet.pdb
├── ReactiveUI.Routing.dll
├── ReactiveUI.Routing.pdb
├── ReactiveUI.Testing.dll
├── ReactiveUI.Testing.pdb
├── ShaderEffectLibrary.dll
├── Microsoft.Reactive.Testing.dll
├── Microsoft.ServiceModel.Tcp.dll
├── System.Windows.Interactivity.dll
├── Microsoft.Expression.Interactions.dll
├── Microsoft.ServiceModel.WebSockets.dll
├── System.Reactive.Windows.Threading.dll
├── en
│ ├── System.Windows.Interactivity.resources.dll
│ └── Microsoft.Expression.Interactions.resources.dll
├── Microsoft.ServiceModel.WebSockets.DesktopClient.dll
├── ReactiveUI.Blend.xml
├── ReactiveUI.Testing.xml
├── System.Reactive.Windows.Threading.xml
└── ReactiveUI.Routing.XML
├── .nuget
├── NuGet.exe
├── NuGet.Config
└── NuGet.targets
├── README.md
├── Play.Tests
├── packages.config
├── IntegrationTestUrl.cs
├── Models
│ ├── PusherHelper.cs
│ ├── Fakes.cs
│ └── PlayApiTests.cs
├── Properties
│ └── AssemblyInfo.cs
├── ViewModels
│ ├── SongTileViewModelTests.cs
│ ├── SearchViewModel.cs
│ ├── PlayViewModel.cs
│ └── WelcomeViewModel.cs
└── Play.Tests.csproj
├── .gitattributes
├── Play.sln
├── .gitignore
└── Play.sln.DotSettings
/Play/App.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/App.ico
--------------------------------------------------------------------------------
/ext/NLog.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/NLog.dll
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/ext/Akavache.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Akavache.dll
--------------------------------------------------------------------------------
/ext/Akavache.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Akavache.pdb
--------------------------------------------------------------------------------
/ext/ReactiveUI.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.pdb
--------------------------------------------------------------------------------
/ext/WPFMediaKit.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/WPFMediaKit.dll
--------------------------------------------------------------------------------
/ext/EVRPresenter32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/EVRPresenter32.dll
--------------------------------------------------------------------------------
/ext/EVRPresenter64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/EVRPresenter64.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Blend.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Blend.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Blend.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Blend.pdb
--------------------------------------------------------------------------------
/ext/ReactiveUI.Xaml.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Xaml.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Xaml.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Xaml.pdb
--------------------------------------------------------------------------------
/ext/System.Reactive.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/System.Reactive.dll
--------------------------------------------------------------------------------
/Play/Images/PlayButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Images/PlayButton.png
--------------------------------------------------------------------------------
/Play/Images/StopButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Images/StopButton.png
--------------------------------------------------------------------------------
/Play/Play_TemporaryKey.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Play_TemporaryKey.pfx
--------------------------------------------------------------------------------
/ext/PusherClientDotNet.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/PusherClientDotNet.dll
--------------------------------------------------------------------------------
/ext/PusherClientDotNet.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/PusherClientDotNet.pdb
--------------------------------------------------------------------------------
/ext/ReactiveUI.Routing.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Routing.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Routing.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Routing.pdb
--------------------------------------------------------------------------------
/ext/ReactiveUI.Testing.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Testing.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Testing.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ReactiveUI.Testing.pdb
--------------------------------------------------------------------------------
/ext/ShaderEffectLibrary.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/ShaderEffectLibrary.dll
--------------------------------------------------------------------------------
/Play/Fonts/OpenSans-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Fonts/OpenSans-Italic.ttf
--------------------------------------------------------------------------------
/Play/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/Play/Images/status-icon-off.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Images/status-icon-off.ico
--------------------------------------------------------------------------------
/Play/Images/status-icon-on.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Images/status-icon-on.ico
--------------------------------------------------------------------------------
/Play/Fonts/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Fonts/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/ext/Microsoft.Reactive.Testing.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Microsoft.Reactive.Testing.dll
--------------------------------------------------------------------------------
/ext/Microsoft.ServiceModel.Tcp.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Microsoft.ServiceModel.Tcp.dll
--------------------------------------------------------------------------------
/Play/Fonts/OpenSans-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/Play/Fonts/OpenSans-LightItalic.ttf
--------------------------------------------------------------------------------
/ext/System.Windows.Interactivity.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/System.Windows.Interactivity.dll
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Play for Windows
2 |
3 | This is a sample application that no longer works, but is here for historical purposes.
4 |
--------------------------------------------------------------------------------
/ext/Microsoft.Expression.Interactions.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Microsoft.Expression.Interactions.dll
--------------------------------------------------------------------------------
/ext/Microsoft.ServiceModel.WebSockets.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Microsoft.ServiceModel.WebSockets.dll
--------------------------------------------------------------------------------
/ext/System.Reactive.Windows.Threading.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/System.Reactive.Windows.Threading.dll
--------------------------------------------------------------------------------
/ext/en/System.Windows.Interactivity.resources.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/en/System.Windows.Interactivity.resources.dll
--------------------------------------------------------------------------------
/ext/en/Microsoft.Expression.Interactions.resources.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/en/Microsoft.Expression.Interactions.resources.dll
--------------------------------------------------------------------------------
/ext/Microsoft.ServiceModel.WebSockets.DesktopClient.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/play/play-windows/master/ext/Microsoft.ServiceModel.WebSockets.DesktopClient.dll
--------------------------------------------------------------------------------
/ext/ReactiveUI.Blend.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ReactiveUI.Blend
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Play/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Play/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Play/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace Play
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Play/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Play/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Play.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text diff=csharp
17 | *.vb text
18 | *.c text
19 | *.cpp text
20 | *.cxx text
21 | *.h text
22 | *.hxx text
23 | *.py text
24 | *.rb text
25 | *.java text
26 | *.html text
27 | *.htm text
28 | *.css text
29 | *.scss text
30 | *.sass text
31 | *.less text
32 | *.js text
33 | *.lisp text
34 | *.clj text
35 | *.sql text
36 | *.php text
37 | *.lua text
38 | *.m text
39 | *.asm text
40 | *.erl text
41 | *.fs text
42 | *.fsx text
43 | *.hs text
44 |
45 | *.csproj text merge=union
46 | *.vbproj text merge=union
47 | *.fsproj text merge=union
48 | *.dbproj text merge=union
49 | *.sln text eol=crlf merge=union
50 |
51 |
--------------------------------------------------------------------------------
/Play.Tests/IntegrationTestUrl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Play.Tests
8 | {
9 | public static class IntegrationTestUrl
10 | {
11 | public static string Current {
12 | get {
13 | //return "https://play.yourcompany.com";
14 | throw new Exception("Configure IntegrationTestUrl.cs first");
15 | }
16 | }
17 |
18 | public static string Token {
19 | get {
20 | //return "a04d03";
21 | throw new Exception("Configure IntegrationTestUrl.cs first");
22 | }
23 | }
24 |
25 | public static string PusherApiKey {
26 | get {
27 | //return "a04d03ca023f0ab99a";
28 | throw new Exception("Configure IntegrationTestUrl.cs first");
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Play/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.17379
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Play.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Play/FadeImageButton.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Controls.Primitives;
9 |
10 | namespace Play
11 | {
12 | public class FadeImageButton : Button
13 | {
14 | public object HighlightContent {
15 | get { return (object)GetValue(HighlightContentProperty); }
16 | set { SetValue(HighlightContentProperty, value); }
17 | }
18 | public static readonly DependencyProperty HighlightContentProperty =
19 | DependencyProperty.Register("HighlightContent", typeof(object), typeof(FadeImageButton), new UIPropertyMetadata(null));
20 | }
21 |
22 | public class FadeImageToggleButton : ToggleButton
23 | {
24 | public object HighlightContent {
25 | get { return (object)GetValue(HighlightContentProperty); }
26 | set { SetValue(HighlightContentProperty, value); }
27 | }
28 | public static readonly DependencyProperty HighlightContentProperty =
29 | DependencyProperty.Register("HighlightContent", typeof(object), typeof(FadeImageToggleButton), new UIPropertyMetadata(null));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Play/Views/SearchView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using Play.ViewModels;
16 | using ReactiveUI.Routing;
17 |
18 | namespace Play.Views
19 | {
20 | ///
21 | /// Interaction logic for SearchViewModel.xaml
22 | ///
23 | public partial class SearchView : UserControl, IViewForViewModel
24 | {
25 | public SearchView()
26 | {
27 | InitializeComponent();
28 | }
29 |
30 | public SearchViewModel ViewModel {
31 | get { return (SearchViewModel)GetValue(ViewModelProperty); }
32 | set { SetValue(ViewModelProperty, value); }
33 | }
34 | public static readonly DependencyProperty ViewModelProperty =
35 | DependencyProperty.Register("ViewModel", typeof(SearchViewModel), typeof(SearchView), new UIPropertyMetadata(null));
36 |
37 | object IViewForViewModel.ViewModel {
38 | get { return ViewModel; }
39 | set { ViewModel = (SearchViewModel)value; }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Play/Views/WelcomeView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using Play.ViewModels;
16 | using ReactiveUI.Routing;
17 |
18 | namespace Play.Views
19 | {
20 | ///
21 | /// Interaction logic for WelcomeViewModel.xaml
22 | ///
23 | public partial class WelcomeView : UserControl, IViewForViewModel
24 | {
25 | public WelcomeView()
26 | {
27 | InitializeComponent();
28 | }
29 |
30 | public WelcomeViewModel ViewModel {
31 | get { return (WelcomeViewModel)GetValue(ViewModelProperty); }
32 | set { SetValue(ViewModelProperty, value); }
33 | }
34 | public static readonly DependencyProperty ViewModelProperty =
35 | DependencyProperty.Register("ViewModel", typeof(WelcomeViewModel), typeof(WelcomeView), new UIPropertyMetadata(null));
36 |
37 | object IViewForViewModel.ViewModel {
38 | get { return ViewModel; }
39 | set { ViewModel = (WelcomeViewModel)value; }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Play/Views/SongTileView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using Play.ViewModels;
16 | using ReactiveUI.Routing;
17 |
18 | namespace Play.Views
19 | {
20 | ///
21 | /// Interaction logic for SongTileView.xaml
22 | ///
23 | public partial class SongTileView : UserControl, IViewForViewModel
24 | {
25 | public SongTileViewModel ViewModel {
26 | get { return (SongTileViewModel)GetValue(ViewModelProperty); }
27 | set { SetValue(ViewModelProperty, value); }
28 | }
29 | public static readonly DependencyProperty ViewModelProperty =
30 | DependencyProperty.Register("ViewModel", typeof(SongTileViewModel), typeof(SongTileView), new UIPropertyMetadata(null));
31 |
32 | public SongTileView()
33 | {
34 | InitializeComponent();
35 | }
36 |
37 | object IViewForViewModel.ViewModel {
38 | get { return ViewModel; }
39 | set { ViewModel = (SongTileViewModel)value; }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Play.Tests/Models/PusherHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using FluentAssertions;
8 | using Newtonsoft.Json;
9 | using Newtonsoft.Json.Linq;
10 | using Ninject;
11 | using Ninject.MockingKernel.Moq;
12 | using Play.Models;
13 | using PusherClientDotNet;
14 | using ReactiveUI;
15 | using Xunit;
16 |
17 | namespace Play.Tests.Models
18 | {
19 | public class PusherHelperTests
20 | {
21 | [Fact(Skip = "This test is super slow and is for debug purposes")]
22 | public void PusherIntegrationSmokeTest()
23 | {
24 | #if FALSE
25 | Func factory = () => new Pusher(IntegrationTestUrl.PusherApiKey);
26 |
27 | var result = PusherHelper.Connect>(factory, "now_playing_updates", "update_now_playing")
28 | .Timeout(TimeSpan.FromMinutes(4.0), RxApp.TaskpoolScheduler)
29 | .Take(1)
30 | /*
31 | .Select(x =>
32 | new Tuple>(
33 | JsonConvert.DeserializeObject(x["now_playing"].ToString()),
34 | JsonConvert.DeserializeObject(x["songs"].ToString()).songs))
35 | */
36 | .First();
37 |
38 | result.Should().NotBeNull();
39 | #endif
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Play.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Play.Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Play.Tests")]
13 | [assembly: AssemblyCopyright("Copyright © 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("46547f0f-f777-43b6-bb55-46586744043b")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Play/Models/Song.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Linq;
5 | using System.Text;
6 | using System.Windows.Media.Imaging;
7 | using Akavache;
8 | using Newtonsoft.Json;
9 | using Ninject;
10 | using Play.ViewModels;
11 | using RestSharp;
12 |
13 | namespace Play.Models
14 | {
15 | public class Song : IEquatable
16 | {
17 | // ReSharper disable InconsistentNaming
18 | public string album { get; set; }
19 | public bool starred { get; set; }
20 | public bool queued { get; set; }
21 | public string artist { get; set; }
22 | public string name { get; set; }
23 | public string id { get; set; }
24 | public string last_played { get; set; }
25 | // ReSharper restore InconsistentNaming
26 |
27 | public DateTimeOffset? LastPlayedAsDate {
28 | get {
29 | if (String.IsNullOrWhiteSpace(last_played)) {
30 | return null;
31 | }
32 |
33 | return DateTimeOffset.Parse(last_played);
34 | }
35 | }
36 |
37 | public bool Equals(Song other)
38 | {
39 | return this.id == other.id;
40 | }
41 |
42 | public override string ToString()
43 | {
44 | return JsonConvert.SerializeObject(this);
45 | }
46 | }
47 |
48 | public class SongQueue
49 | {
50 | // ReSharper disable InconsistentNaming
51 | public List songs { get; set; }
52 | // ReSharper restore InconsistentNaming
53 |
54 | public override string ToString()
55 | {
56 | return JsonConvert.SerializeObject(this);
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Play.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 11
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Play", "Play\Play.csproj", "{0B5F8E32-7626-4FD9-851D-FFDCD8DD8B78}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Play.Tests", "Play.Tests\Play.Tests.csproj", "{0E8BF004-6A4F-4848-9F20-B89E1E09DD61}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2DD7ED65-8850-4F84-AC5E-37BD72644E02}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\NuGet.exe = .nuget\NuGet.exe
11 | .nuget\NuGet.targets = .nuget\NuGet.targets
12 | EndProjectSection
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {0B5F8E32-7626-4FD9-851D-FFDCD8DD8B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {0B5F8E32-7626-4FD9-851D-FFDCD8DD8B78}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {0B5F8E32-7626-4FD9-851D-FFDCD8DD8B78}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {0B5F8E32-7626-4FD9-851D-FFDCD8DD8B78}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {0E8BF004-6A4F-4848-9F20-B89E1E09DD61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {0E8BF004-6A4F-4848-9F20-B89E1E09DD61}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {0E8BF004-6A4F-4848-9F20-B89E1E09DD61}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {0E8BF004-6A4F-4848-9F20-B89E1E09DD61}.Release|Any CPU.Build.0 = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/Play/Models/PusherHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Disposables;
5 | using System.Reactive.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using PusherClientDotNet;
9 | using ReactiveUI;
10 |
11 | namespace Play.Models
12 | {
13 | public static class PusherHelper
14 | {
15 | public static IObservable Connect(Func pusherFactory, string channel, string eventName)
16 | {
17 | var pusher = pusherFactory();
18 | var disp = new CompositeDisposable();
19 |
20 | return Observable.Create(subj => {
21 | bool hasCompleted = false;
22 | try {
23 | pusher.Connect();
24 | disp.Add(Disposable.Create(pusher.Disconnect));
25 |
26 | // NB: Pusher is racey, give it some time to connect
27 | var postConnect = Observable.Start(() => {
28 | var ch = pusher.Subscribe(channel);
29 | disp.Add(Disposable.Create(() => pusher.Unsubscribe(channel)));
30 |
31 | ch.Bind(eventName, x => {
32 | if (hasCompleted) return;
33 | subj.OnNext((T) x);
34 | });
35 |
36 | disp.Add(Disposable.Create(ch.Disconnect));
37 | }, RxApp.TaskpoolScheduler);
38 |
39 | postConnect.Subscribe(_ => { },
40 | ex => LogHost.Default.WarnException("Couldn't connect to Pusher", ex));
41 |
42 | } catch (Exception ex) {
43 | subj.OnError(ex);
44 | hasCompleted = true;
45 | }
46 |
47 | return disp;
48 | });
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Play.Tests/Models/Fakes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Newtonsoft.Json;
7 | using Play.Models;
8 |
9 | namespace Play.Tests
10 | {
11 | public static class Fakes
12 | {
13 | const string albumJson = "{\"songs\":[{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Get Innocuous!\",\"id\":\"4B52884E1E293890\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Time To Get Away\",\"id\":\"044198D2344B2CAE\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"North American Scum\",\"id\":\"2659D99BCA5BF132\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Someone Great\",\"id\":\"EDC2A0C6F45A31C3\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"All My Friends\",\"id\":\"F85FDE2A63393803\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Us V Them\",\"id\":\"BA41D8967BBDD1B6\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Watch The Tapes\",\"id\":\"4DBE327B3EAE10AA\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"Sound Of Silver\",\"id\":\"ADFE3123DC34FCD9\",\"artist\":\"LCD Soundsystem\"},{\"starred\":false,\"album\":\"Sound Of Silver\",\"queued\":false,\"name\":\"New York, I Love You But You're Bringing Me Down\",\"id\":\"9EA31D331E9ED436\",\"artist\":\"LCD Soundsystem\"}]}";
14 |
15 | public static List GetAlbum()
16 | {
17 | return JsonConvert.DeserializeObject(albumJson).songs;
18 | }
19 |
20 | public static Song GetSong()
21 | {
22 | return GetAlbum().First();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Play/Views/PlayView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Linq;
5 | using System.Reactive.Subjects;
6 | using System.Text;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using Ninject;
17 | using Play.ViewModels;
18 | using Play.ViewModels;
19 | using ReactiveUI;
20 | using ReactiveUI.Routing;
21 | using ReactiveUI.Xaml;
22 | using RestSharp;
23 |
24 | namespace Play.Views
25 | {
26 | public partial class PlayView : UserControl, IViewForViewModel
27 | {
28 | public PlayViewModel ViewModel {
29 | get { return (PlayViewModel)GetValue(ViewModelProperty); }
30 | set { SetValue(ViewModelProperty, value); }
31 | }
32 | public static readonly DependencyProperty ViewModelProperty =
33 | DependencyProperty.Register("ViewModel", typeof(PlayViewModel), typeof(PlayView), new UIPropertyMetadata(null));
34 |
35 | public PlayView()
36 | {
37 | InitializeComponent();
38 | }
39 |
40 | public override void OnApplyTemplate()
41 | {
42 | base.OnApplyTemplate();
43 | DataContext = ViewModel;
44 |
45 | mediaElement.LoadedBehavior = MediaState.Manual;
46 |
47 | ViewModel.TogglePlay
48 | .Where(_ => ViewModel.ListenUrl != null)
49 | .Subscribe(nowPlayingUrl => {
50 | if (!ViewModel.IsPlaying) {
51 | mediaElement.Source = new Uri(ViewModel.ListenUrl);
52 | mediaElement.Play();
53 | } else {
54 | mediaElement.Stop();
55 | }
56 |
57 | ViewModel.IsPlaying = !ViewModel.IsPlaying;
58 | });
59 | }
60 |
61 | object IViewForViewModel.ViewModel {
62 | get { return ViewModel; }
63 | set { ViewModel = (PlayViewModel)value; }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Play/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("Play")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("Play")]
15 | [assembly: AssemblyCopyright("Copyright © 2012")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.1.5")]
55 | [assembly: AssemblyFileVersion("1.0.1.5")]
56 |
--------------------------------------------------------------------------------
/Play/RestSharpRxHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Reactive.Linq;
6 | using System.Reactive.Subjects;
7 | using System.Text;
8 | using ReactiveUI;
9 | using RestSharp;
10 |
11 | namespace Play
12 | {
13 | public static class RestSharpRxHelper
14 | {
15 | public static IObservable> RequestAsync(this IRestClient client, IRestRequest request) where T : new()
16 | {
17 | var ret = Observable.Start(() => client.Execute(request), RxApp.TaskpoolScheduler);
18 | return ret.throwOnRestResponseFailure();
19 | }
20 |
21 | public static IObservable RequestAsync(this IRestClient client, IRestRequest request)
22 | {
23 | var ret = Observable.Start(() => client.Execute(request), RxApp.TaskpoolScheduler);
24 | return ret.throwOnRestResponseFailure();
25 | }
26 |
27 | static IObservable throwOnRestResponseFailure(this IObservable observable)
28 | where T : RestResponseBase
29 | {
30 | return observable.SelectMany(x =>
31 | {
32 | if (x == null)
33 | {
34 | return Observable.Return(x);
35 | }
36 |
37 | if (x.ErrorException != null)
38 | {
39 | return Observable.Throw(x.ErrorException);
40 | }
41 |
42 | if (x.ResponseStatus == ResponseStatus.Error)
43 | {
44 | LogHost.Default.Warn("Response Status failed for {0}: {1}", x.ResponseUri, x.ResponseStatus);
45 | return Observable.Throw(new Exception("Request Error"));
46 | }
47 |
48 | if (x.ResponseStatus == ResponseStatus.TimedOut)
49 | {
50 | LogHost.Default.Warn("Response Status failed for {0}: {1}", x.ResponseUri, x.ResponseStatus);
51 | return Observable.Throw(new Exception("Request Timed Out"));
52 | }
53 |
54 | if ((int)x.StatusCode >= 400)
55 | {
56 | LogHost.Default.Warn("Response Status failed for {0}: {1}", x.ResponseUri, x.StatusCode);
57 | return Observable.Throw(new WebException(x.Content));
58 | }
59 |
60 | if (x.ResponseStatus == ResponseStatus.Aborted)
61 | {
62 | LogHost.Default.Warn("Response Status failed for {0}: {1}", x.ResponseUri, x.ResponseStatus);
63 | return Observable.Throw(new Exception("Request aborted"));
64 | }
65 |
66 | return Observable.Return(x);
67 | });
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/Play.Tests/ViewModels/SongTileViewModelTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive;
5 | using System.Reactive.Linq;
6 | using System.Windows.Media.Imaging;
7 | using Akavache;
8 | using FluentAssertions;
9 | using Moq;
10 | using Ninject;
11 | using Ninject.MockingKernel.Moq;
12 | using Play.Models;
13 | using Play.ViewModels;
14 | using ReactiveUI;
15 | using Xunit;
16 |
17 | namespace Play.Tests.ViewModels
18 | {
19 | public class SongTileViewModelTests : IEnableLogger
20 | {
21 | [Fact]
22 | public void QueuingASongShouldCallPlayApi()
23 | {
24 | var kernel = new MoqMockingKernel();
25 | var song = Fakes.GetSong();
26 | var fixture = setupStandardFixture(song, kernel);
27 |
28 | fixture.QueueSong.Execute(null);
29 | kernel.GetMock().Verify(x => x.QueueSong(It.IsAny()));
30 | }
31 |
32 | [Fact]
33 | public void QueueingAlbumShouldCallQueueSongForEverySong()
34 | {
35 | var kernel = new MoqMockingKernel();
36 | var song = Fakes.GetSong();
37 |
38 | var fakeAlbum = Fakes.GetAlbum();
39 | var queuedSongs = new List();
40 | var fixture = setupStandardFixture(song, kernel);
41 |
42 | kernel.GetMock().Setup(x => x.QueueSong(It.IsAny()))
43 | .Callback(queuedSongs.Add)
44 | .Returns(Observable.Return(Unit.Default))
45 | .Verifiable();
46 |
47 | fixture.QueueAlbum.Execute(null);
48 |
49 | this.Log().Info("Queued songs: {0}", String.Join(",", queuedSongs.Select(x => x.name)));
50 | queuedSongs.Count.Should().Be(fakeAlbum.Count);
51 | fakeAlbum.Zip(queuedSongs, (e, a) => e.id == a.id).All(x => x).Should().BeTrue();
52 | }
53 |
54 | static ISongTileViewModel setupStandardFixture(Song song, MoqMockingKernel kernel)
55 | {
56 | kernel.Bind().To().Named("UserAccount");
57 | kernel.Bind().To().Named("LocalMachine");
58 |
59 | kernel.GetMock().Setup(x => x.QueueSong(It.IsAny()))
60 | .Returns(Observable.Return(Unit.Default))
61 | .Verifiable();
62 |
63 | kernel.GetMock().Setup(x => x.AllSongsOnAlbum(It.IsAny(), It.IsAny()))
64 | .Returns(Observable.Return(Fakes.GetAlbum()))
65 | .Verifiable();
66 |
67 | kernel.GetMock().Setup(x => x.FetchImageForAlbum(It.IsAny()))
68 | .Returns(Observable.Return(new BitmapImage()));
69 |
70 | return new SongTileViewModel(song, kernel.Get());
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | #################
3 | ## Eclipse
4 | #################
5 |
6 | *.pydevproject
7 | .project
8 | .metadata
9 | bin/**
10 | tmp/**
11 | tmp/**/*
12 | *.tmp
13 | *.bak
14 | *.swp
15 | *~.nib
16 | local.properties
17 | .classpath
18 | .settings/
19 | .loadpath
20 |
21 | # External tool builders
22 | .externalToolBuilders/
23 |
24 | # Locally stored "Eclipse launch configurations"
25 | *.launch
26 |
27 | # CDT-specific
28 | .cproject
29 |
30 | # PDT-specific
31 | .buildpath
32 |
33 |
34 | #################
35 | ## Visual Studio
36 | #################
37 |
38 | ## Ignore Visual Studio temporary files, build results, and
39 | ## files generated by popular Visual Studio add-ons.
40 |
41 | # User-specific files
42 | *.suo
43 | *.user
44 | *.sln.docstates
45 |
46 | # Build results
47 | **/[Dd]ebug/
48 | **/[Rr]elease/
49 | *_i.c
50 | *_p.c
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.vspscc
65 | .builds
66 | **/*.dotCover
67 | **/packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # Installshield output folder
84 | [Ee]xpress
85 |
86 | # DocProject is a documentation generator add-in
87 | DocProject/buildhelp/
88 | DocProject/Help/*.HxT
89 | DocProject/Help/*.HxC
90 | DocProject/Help/*.hhc
91 | DocProject/Help/*.hhk
92 | DocProject/Help/*.hhp
93 | DocProject/Help/Html2
94 | DocProject/Help/html
95 |
96 | # Click-Once directory
97 | publish
98 |
99 | # Others
100 | [Bb]in
101 | [Oo]bj
102 | sql
103 | TestResults
104 | *.Cache
105 | ClientBin
106 | stylecop.*
107 | ~$*
108 | *.dbmdl
109 | Generated_Code #added for RIA/Silverlight projects
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 |
118 |
119 | ############
120 | ## Windows
121 | ############
122 |
123 | # Windows image file caches
124 | Thumbs.db
125 |
126 | # Folder config file
127 | Desktop.ini
128 |
129 |
130 | #############
131 | ## Python
132 | #############
133 |
134 | *.py[co]
135 |
136 | # Packages
137 | *.egg
138 | *.egg-info
139 | dist
140 | build
141 | eggs
142 | parts
143 | bin
144 | var
145 | sdist
146 | develop-eggs
147 | .installed.cfg
148 |
149 | # Installer logs
150 | pip-log.txt
151 |
152 | # Unit test / coverage reports
153 | .coverage
154 | .tox
155 |
156 | #Translations
157 | *.mo
158 |
159 | #Mr Developer
160 | .mr.developer.cfg
161 |
162 | # Mac crap
163 | .DS_Store
164 |
165 | packages
166 | Play.Tests/IntegrationTestUrl.cs
167 |
--------------------------------------------------------------------------------
/Play/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.17379
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Play.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Play.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Play.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | False
3 | False
4 | False
5 | False
6 | False
7 | False
8 | False
9 | False
10 | False
11 | False
12 | END_OF_LINE
13 | END_OF_LINE
14 | False
15 | False
16 | False
17 | False
18 | END_OF_LINE
19 | END_OF_LINE
20 |
21 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private Fields/Methods/Properties"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /><Kind Name="CONSTANT_FIELD" /><Kind Name="METHOD" /><Kind Name="PROPERTY" /><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
22 |
--------------------------------------------------------------------------------
/Play/Views/WelcomeView.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Play.Tests/ViewModels/SearchViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reactive.Linq;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using System.Windows.Media.Imaging;
6 | using Akavache;
7 | using FluentAssertions;
8 | using Moq;
9 | using Ninject;
10 | using Ninject.MockingKernel.Moq;
11 | using Play.Models;
12 | using Play.ViewModels;
13 | using ReactiveUI;
14 | using ReactiveUI.Routing;
15 | using ReactiveUI.Xaml;
16 | using Xunit;
17 |
18 | namespace Play.Tests.ViewModels
19 | {
20 | public class SearchViewModelTests
21 | {
22 | [Fact]
23 | public void PlayApiShouldBeCalledOnPerformSearch()
24 | {
25 | var kernel = new MoqMockingKernel();
26 | var fixture = setupStandardFixture(kernel);
27 | fixture.PerformSearch.CanExecute(null).Should().BeFalse();
28 |
29 | fixture.SearchQuery = "Foo";
30 | fixture.PerformSearch.CanExecute(null).Should().BeTrue();
31 | fixture.PerformSearch.Execute(null);
32 |
33 | kernel.GetMock().Verify(x => x.Search("Foo"), Times.Once());
34 |
35 | fixture.SearchResults.Count.Should().Be(1);
36 | fixture.SearchResults[0].Model.id.Should().Be("4B52884E1E293890");
37 | }
38 |
39 | [Fact]
40 | public void DontThrashPlayApiOnMultipleSearchCalls()
41 | {
42 | var kernel = new MoqMockingKernel();
43 | var fixture = setupStandardFixture(kernel);
44 |
45 | fixture.SearchQuery = "Foo";
46 | fixture.PerformSearch.Execute(null);
47 | fixture.PerformSearch.Execute(null);
48 | fixture.PerformSearch.Execute(null);
49 |
50 | kernel.GetMock().Verify(x => x.Search("Foo"), Times.Once());
51 | }
52 |
53 | [Fact]
54 | public void EnsureWeFetchAnAlbumCover()
55 | {
56 | var kernel = new MoqMockingKernel();
57 | var fixture = setupStandardFixture(kernel);
58 |
59 | fixture.SearchQuery = "Foo";
60 | fixture.PerformSearch.Execute(null);
61 |
62 | kernel.GetMock().Verify(x => x.Search("Foo"), Times.Once());
63 | kernel.GetMock().Verify(x => x.FetchImageForAlbum(It.IsAny()), Times.Once());
64 | }
65 |
66 | static ISearchViewModel setupStandardFixture(MoqMockingKernel kernel, IRoutingState router = null)
67 | {
68 | router = router ?? new RoutingState();
69 | kernel.Bind().To();
70 | kernel.Bind().To().Named("UserAccount");
71 | kernel.Bind().To().Named("LocalMachine");
72 |
73 | kernel.GetMock().Setup(x => x.Search("Foo"))
74 | .Returns(Observable.Return(new List() { Fakes.GetSong() }))
75 | .Verifiable();
76 |
77 | kernel.GetMock().Setup(x => x.CurrentAuthenticatedClient).Returns(kernel.Get());
78 | kernel.GetMock().Setup(x => x.Router).Returns(router);
79 |
80 | var img = new BitmapImage();
81 | kernel.GetMock().Setup(x => x.FetchImageForAlbum(It.IsAny()))
82 | .Returns(Observable.Return(img))
83 | .Verifiable();
84 |
85 | RxApp.ConfigureServiceLocator((t,s) => kernel.Get(t,s), (t,s) => kernel.GetAll(t,s));
86 |
87 | var fixture = kernel.Get();
88 | return fixture;
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/Play/ViewModels/SearchViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using Akavache;
9 | using Ninject;
10 | using Play.Models;
11 | using ReactiveUI;
12 | using ReactiveUI.Routing;
13 | using ReactiveUI.Xaml;
14 |
15 | namespace Play.ViewModels
16 | {
17 | public interface ISearchViewModel : IRoutableViewModel
18 | {
19 | string SearchQuery { get; set; }
20 | Visibility SearchBusySpinner { get; }
21 |
22 | ReactiveCollection SearchResults { get; }
23 | ReactiveAsyncCommand PerformSearch { get; }
24 | ReactiveCommand GoBack { get; }
25 | }
26 |
27 | public class SearchViewModel : ReactiveObject, ISearchViewModel
28 | {
29 | public string UrlPathSegment {
30 | get { return "/search"; }
31 | }
32 |
33 | public IScreen HostScreen { get; protected set; }
34 |
35 | string _SearchQuery;
36 | public string SearchQuery {
37 | get { return _SearchQuery; }
38 | set { this.RaiseAndSetIfChanged(x => x.SearchQuery, value); }
39 | }
40 |
41 | ObservableAsPropertyHelper _SearchBusySpinner;
42 | public Visibility SearchBusySpinner {
43 | get { return _SearchBusySpinner.Value; }
44 | }
45 |
46 | public ReactiveCollection SearchResults { get; protected set; }
47 | public ReactiveAsyncCommand PerformSearch { get; protected set; }
48 | public ReactiveCommand GoBack { get; protected set; }
49 |
50 | [Inject]
51 | public SearchViewModel(IScreen hostScreen, ILoginMethods loginMethods, [Named("UserAccount")] IBlobCache userCache)
52 | {
53 | HostScreen = hostScreen;
54 | SearchResults = new ReactiveCollection();
55 | var playApi = loginMethods.CurrentAuthenticatedClient;
56 |
57 | if (playApi == null) {
58 | hostScreen.Router.Navigate.Execute(RxApp.GetService());
59 | return;
60 | }
61 |
62 | var canSearch = this.WhenAny(x => x.SearchQuery, x => !String.IsNullOrWhiteSpace(x.Value));
63 | PerformSearch = new ReactiveAsyncCommand(canSearch);
64 |
65 | this.ObservableForProperty(x => x.SearchQuery)
66 | .Throttle(TimeSpan.FromMilliseconds(700), RxApp.DeferredScheduler)
67 | .InvokeCommand(PerformSearch);
68 |
69 | var searchResults = PerformSearch.RegisterAsyncObservable(_ =>
70 | userCache.GetOrFetchObject(
71 | "search__" + SearchQuery,
72 | () => playApi.Search(SearchQuery),
73 | RxApp.TaskpoolScheduler.Now + TimeSpan.FromMinutes(1.0)));
74 |
75 | SearchResults = searchResults
76 | .Do(_ => SearchResults.Clear())
77 | .SelectMany(list => list.ToObservable())
78 | .LoggedCatch(this, Observable.Empty())
79 | .CreateCollection(x => (ISongTileViewModel) new SongTileViewModel(x, playApi));
80 |
81 | PerformSearch.ItemsInflight.StartWith(0)
82 | .Select(x => x > 0 ? Visibility.Visible : Visibility.Hidden)
83 | .ToProperty(this, x => x.SearchBusySpinner);
84 |
85 | PerformSearch.ThrownExceptions.Subscribe(_ => { });
86 |
87 | GoBack = new ReactiveCommand();
88 | GoBack.InvokeCommand(hostScreen.Router.NavigateBack);
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/Play/HardwareRenderingHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Management;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 | using System.Windows.Interop;
9 | using System.Windows.Media;
10 | using NLog;
11 |
12 | namespace Play
13 | {
14 | public static class HardwareRenderingHelper
15 | {
16 | static readonly Logger log = LogManager.GetCurrentClassLogger();
17 | static readonly string[] videoCardBlacklist = { };
18 |
19 | public static bool IsInSoftwareMode { get; private set; }
20 |
21 | public static void DisableHwRenderingForCrapVideoCards()
22 | {
23 | if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GH_FORCE_HW_RENDERING")))
24 | {
25 | return;
26 | }
27 |
28 | if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GH_FORCE_SW_RENDERING")))
29 | {
30 | EnableSoftwareMode();
31 | return;
32 | }
33 |
34 | int osVersion = Environment.OSVersion.Version.Major * 100 + Environment.OSVersion.Version.Minor;
35 | if (osVersion < 601)
36 | {
37 | log.Warn("Hardware acceleration is much more glitchy on OS's earlier than Vista");
38 | log.Warn("If you believe this isn't the case, set the GH_FORCE_HW_RENDERING environment variable");
39 | EnableSoftwareMode();
40 | return;
41 | }
42 |
43 | var re = new Regex(@"VEN_([0-9A-Z]{4})&DEV_([0-9A-Z]{4})");
44 | var wmiSearch = new ManagementObjectSearcher("root\\cimv2", "SELECT * FROM win32_videocontroller");
45 | var items = wmiSearch.Get();
46 | var venIds = new List();
47 |
48 | foreach (var item in items)
49 | {
50 | var val = (string)item.GetPropertyValue("PNPDeviceID");
51 | var m = re.Match(val);
52 | if (!m.Success || m.Captures.Count != 3) continue;
53 |
54 | venIds.Add(m.Groups[1].Value);
55 |
56 | var pnpId = String.Format("{0}:{1}", m.Groups[1].Value, m.Groups[2].Value);
57 | log.Info("Video PNPID: {0}", pnpId);
58 | if (videoCardBlacklist.Contains(pnpId))
59 | {
60 | log.Warn("Your video card is known to cause graphical glitches, so we have disabled hardware rendering");
61 | log.Warn("If you believe this isn't the case, set the GH_FORCE_HW_RENDERING environment variable");
62 | EnableSoftwareMode();
63 | }
64 | }
65 |
66 | // NB: If the machine has more than one video card by different
67 | // vendors (i.e. one monitor is driven by an Intel card, one by
68 | // ATI), glitches happen when the window crosses devices
69 | if (venIds.Distinct().Count() > 1)
70 | {
71 | log.Warn("You appear to have two active video cards by separate manufacturers, so we have disabled hardware rendering");
72 | log.Warn("This is known to cause graphical issues, but if you want to enable hardware rendering anyways,");
73 | log.Warn("set the GH_FORCE_HW_RENDERING environment variable.");
74 | }
75 |
76 | log.Info("Your video card appears to support hardware rendering. If this isn't the case and you see glitches");
77 | log.Info("set the GH_FORCE_SW_RENDERING environment variable to 1");
78 | }
79 |
80 | static void EnableSoftwareMode()
81 | {
82 | RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
83 | IsInSoftwareMode = true;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
8 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
9 | $([System.IO.Path]::Combine($(SolutionDir), "packages"))
10 |
11 |
12 | $(SolutionDir).nuget
13 | packages.config
14 | $(SolutionDir)packages
15 |
16 |
17 | $(NuGetToolsPath)\nuget.exe
18 | "$(NuGetExePath)"
19 | mono --runtime=v4.0.30319 $(NuGetExePath)
20 |
21 | $(TargetDir.Trim('\\'))
22 |
23 |
24 | ""
25 |
26 |
27 | false
28 |
29 |
30 | false
31 |
32 |
33 | $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)"
34 | $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
35 |
36 |
37 |
38 | RestorePackages;
39 | $(BuildDependsOn);
40 |
41 |
42 |
43 |
44 | $(BuildDependsOn);
45 | BuildPackage;
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
57 |
58 |
61 |
62 |
63 |
64 |
66 |
67 |
70 |
71 |
--------------------------------------------------------------------------------
/Play/ViewModels/SongTileViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive;
3 | using System.Reactive.Linq;
4 | using System.Windows;
5 | using System.Windows.Media.Imaging;
6 | using Play.Models;
7 | using ReactiveUI;
8 | using ReactiveUI.Xaml;
9 |
10 | namespace Play.ViewModels
11 | {
12 | public interface ISongTileViewModel : IReactiveNotifyPropertyChanged
13 | {
14 | Song Model { get; }
15 | BitmapImage AlbumArt { get; }
16 | bool IsStarred { get; set; }
17 | Visibility QueueSongVisibility { get; set; }
18 |
19 | ReactiveAsyncCommand QueueSong { get; }
20 | ReactiveAsyncCommand QueueAlbum { get; }
21 |
22 | ReactiveAsyncCommand ShowSongsFromArtist { get; }
23 | ReactiveAsyncCommand ShowSongsFromAlbum { get; }
24 |
25 | ReactiveAsyncCommand ToggleStarred { get; }
26 | }
27 |
28 | public class SongTileViewModel : ReactiveObject, ISongTileViewModel
29 | {
30 | public Song Model { get; protected set; }
31 |
32 | ObservableAsPropertyHelper _AlbumArt;
33 | public BitmapImage AlbumArt {
34 | get { return _AlbumArt.Value; }
35 | }
36 |
37 | bool _IsStarred;
38 | public bool IsStarred {
39 | get { return _IsStarred; }
40 | set { this.RaiseAndSetIfChanged(x => x.IsStarred, value); }
41 | }
42 |
43 | Visibility _QueueSongVisibility;
44 | public Visibility QueueSongVisibility {
45 | get { return _QueueSongVisibility; }
46 | set { this.RaiseAndSetIfChanged(x => x.QueueSongVisibility, value); }
47 | }
48 |
49 | public ReactiveAsyncCommand QueueSong { get; protected set; }
50 | public ReactiveAsyncCommand QueueAlbum { get; protected set; }
51 |
52 | public ReactiveAsyncCommand ShowSongsFromArtist { get; protected set; }
53 | public ReactiveAsyncCommand ShowSongsFromAlbum { get; protected set; }
54 |
55 | public ReactiveAsyncCommand ToggleStarred { get; protected set; }
56 |
57 | public SongTileViewModel(Song model, IPlayApi playApi)
58 | {
59 | Model = model;
60 |
61 | playApi.FetchImageForAlbum(model)
62 | .LoggedCatch(this, Observable.Return(default(BitmapImage)))
63 | .ToProperty(this, x => x.AlbumArt);
64 |
65 | QueueSong = new ReactiveAsyncCommand();
66 | QueueAlbum = new ReactiveAsyncCommand();
67 |
68 | QueueSong.RegisterAsyncObservable(_ => playApi.QueueSong(Model))
69 | .Subscribe(
70 | x => this.Log().Info("Queued {0}", Model.name),
71 | ex => this.Log().WarnException("Failed to queue", ex));
72 |
73 | QueueAlbum.RegisterAsyncObservable(_ => playApi.AllSongsOnAlbum(Model.artist, Model.album))
74 | .SelectMany(x => x.ToObservable())
75 | .Select(x => reallyTryToQueueSong(playApi, x)).Concat()
76 | .Subscribe(
77 | x => this.Log().Info("Queued song"),
78 | ex => this.Log().WarnException("Failed to queue album", ex));
79 |
80 | QueueAlbum.ThrownExceptions.Subscribe(x => { });
81 |
82 | IsStarred = model.starred;
83 | ToggleStarred = new ReactiveAsyncCommand();
84 |
85 | ToggleStarred.RegisterAsyncObservable(_ => IsStarred ? playApi.Unstar(Model) : playApi.Star(Model))
86 | .Select(_ => true).LoggedCatch(this, Observable.Return(false))
87 | .Subscribe(result => {
88 | if (result) IsStarred = !IsStarred;
89 | }, ex => this.Log().WarnException("Couldn't star/unstar song", ex));
90 |
91 | ToggleStarred.ThrownExceptions.Subscribe(x => { });
92 | }
93 |
94 | IObservable reallyTryToQueueSong(IPlayApi playApi, Song song)
95 | {
96 | return Observable.Defer(() => playApi.QueueSong(song))
97 | .Timeout(TimeSpan.FromSeconds(20), RxApp.TaskpoolScheduler)
98 | .Retry(3);
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/Play.Tests/Models/PlayApiTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Akavache;
8 | using FluentAssertions;
9 | using Ninject;
10 | using Ninject.MockingKernel.Moq;
11 | using Play.Models;
12 | using ReactiveUI;
13 | using RestSharp;
14 | using Xunit;
15 |
16 | namespace Play.Tests.Models
17 | {
18 | public class PlayApiTests : IEnableLogger
19 | {
20 | [Fact]
21 | public void FetchNowPlayingIntegrationTest()
22 | {
23 | var kernel = new MoqMockingKernel();
24 | var client = new RestClient(IntegrationTestUrl.Current);
25 |
26 | client.AddDefaultHeader("Authorization", IntegrationTestUrl.Token);
27 | kernel.Bind().To();
28 |
29 | var api = new PlayApi(client, kernel.Get());
30 |
31 | var result = api.NowPlaying()
32 | .Timeout(TimeSpan.FromSeconds(9.0), RxApp.TaskpoolScheduler)
33 | .First();
34 |
35 | this.Log().Info(result.ToString());
36 | result.id.Should().NotBeNullOrEmpty();
37 | }
38 |
39 | [Fact]
40 | public void FetchQueueIntegrationTest()
41 | {
42 | var kernel = new MoqMockingKernel();
43 | var client = new RestClient(IntegrationTestUrl.Current);
44 |
45 | client.AddDefaultHeader("Authorization", IntegrationTestUrl.Token);
46 | kernel.Bind().To();
47 |
48 | var api = new PlayApi(client, kernel.Get());
49 |
50 | var result = api.Queue()
51 | .Timeout(TimeSpan.FromSeconds(9.0), RxApp.TaskpoolScheduler)
52 | .First();
53 |
54 | this.Log().Info(String.Join(",", result.Select(x => x.name)));
55 | result.Count.Should().BeGreaterThan(2);
56 | }
57 |
58 | [Fact]
59 | public void MakeSearchIntegrationTest()
60 | {
61 | var kernel = new MoqMockingKernel();
62 | var client = new RestClient(IntegrationTestUrl.Current);
63 |
64 | client.AddDefaultHeader("Authorization", IntegrationTestUrl.Token);
65 | kernel.Bind().To();
66 |
67 | var api = new PlayApi(client, kernel.Get());
68 |
69 | var result = api.Search("LCD Soundsystem")
70 | .Timeout(TimeSpan.FromSeconds(9.0), RxApp.TaskpoolScheduler)
71 | .First();
72 |
73 | this.Log().Info(String.Join(",", result.Select(x => x.name)));
74 | result.Count.Should().BeGreaterThan(2);
75 | }
76 |
77 | [Fact]
78 | public void MakeArtistSearchIntegrationTest()
79 | {
80 | var kernel = new MoqMockingKernel();
81 | var client = new RestClient(IntegrationTestUrl.Current);
82 |
83 | client.AddDefaultHeader("Authorization", IntegrationTestUrl.Token);
84 | kernel.Bind().To();
85 |
86 | var api = new PlayApi(client, kernel.Get());
87 |
88 | var result = api.AllSongsForArtist("LCD Soundsystem")
89 | .Timeout(TimeSpan.FromSeconds(9.0), RxApp.TaskpoolScheduler)
90 | .First();
91 |
92 | this.Log().Info(String.Join(",", result.Select(x => x.name)));
93 | result.Count.Should().BeGreaterThan(2);
94 | }
95 |
96 | [Fact]
97 | public void MakeAlbumSearchIntegrationTest()
98 | {
99 | var kernel = new MoqMockingKernel();
100 | var client = new RestClient(IntegrationTestUrl.Current);
101 |
102 | client.AddDefaultHeader("Authorization", IntegrationTestUrl.Token);
103 | kernel.Bind().To();
104 |
105 | var api = new PlayApi(client, kernel.Get());
106 |
107 | var result = api.AllSongsOnAlbum("LCD Soundsystem", "Sound Of Silver")
108 | .Timeout(TimeSpan.FromSeconds(9.0), RxApp.TaskpoolScheduler)
109 | .First();
110 |
111 | this.Log().Info(String.Join(",", result.Select(x => x.name)));
112 | result.Count.Should().BeGreaterThan(2);
113 | }
114 |
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Play/ViewModels/WelcomeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Reactive;
6 | using System.Reactive.Linq;
7 | using System.Reactive.Subjects;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using Akavache;
11 | using Ninject;
12 | using Play.Models;
13 | using ReactiveUI;
14 | using ReactiveUI.Routing;
15 | using ReactiveUI.Xaml;
16 | using RestSharp;
17 |
18 | namespace Play.ViewModels
19 | {
20 | public interface IWelcomeViewModel : IRoutableViewModel
21 | {
22 | string BaseUrl { get; set; }
23 | string Token { get; set; }
24 | string ErrorMessage { get; }
25 | ReactiveCommand OkButton { get; }
26 | ReactiveCommand OpenTokenPage { get; }
27 | }
28 |
29 | public class WelcomeViewModel : ReactiveObject, IWelcomeViewModel
30 | {
31 | string _BaseUrl;
32 | public string BaseUrl {
33 | get { return _BaseUrl; }
34 | set { this.RaiseAndSetIfChanged(x => x.BaseUrl, value); }
35 | }
36 |
37 | string _Token;
38 | public string Token {
39 | get { return _Token; }
40 | set { this.RaiseAndSetIfChanged(x => x.Token, value); }
41 | }
42 |
43 | ObservableAsPropertyHelper _ErrorMessage;
44 | public string ErrorMessage {
45 | get { return _ErrorMessage.Value; }
46 | }
47 |
48 | public ReactiveCommand OkButton { get; protected set; }
49 | public ReactiveCommand OpenTokenPage { get; protected set; }
50 |
51 | public string UrlPathSegment {
52 | get { return "login"; }
53 | }
54 |
55 | public IScreen HostScreen { get; protected set; }
56 |
57 | [Inject]
58 | public WelcomeViewModel(
59 | IScreen screen,
60 | ILoginMethods loginMethods,
61 | [Named("connectToServer")] [Optional] Func> connectToServerMock)
62 | {
63 | HostScreen = screen;
64 |
65 | var canOk = this.WhenAny(x => x.BaseUrl, x => x.Token,
66 | (b, u) => isValidUrl(b.Value) && !String.IsNullOrWhiteSpace(u.Value));
67 |
68 | OkButton = new ReactiveCommand(canOk);
69 |
70 | OpenTokenPage = new ReactiveCommand(this.WhenAny(x => x.BaseUrl, x => isValidUrl(x.Value)));
71 |
72 | var connectToServer = connectToServerMock ?? ConnectToPlay;
73 |
74 | Observable.Defer(() => OkButton.SelectMany(_ => connectToServer(BaseUrl, Token)))
75 | .Select(_ => true).Catch(Observable.Return(false))
76 | .Repeat()
77 | .Subscribe(result => {
78 | if (result == false) {
79 | UserError.Throw("Couldn't connect to Play instance.");
80 | return;
81 | }
82 |
83 | loginMethods.SaveCredentials(BaseUrl, Token);
84 | screen.Router.NavigateBack.Execute(null);
85 | });
86 |
87 | OpenTokenPage.Subscribe(_ => Process.Start(String.Format("{0}/token", BaseUrl)));
88 |
89 | var error = new Subject();
90 | UserError.RegisterHandler(ex => {
91 | error.OnNext(ex.ErrorMessage);
92 | return Observable.Return(RecoveryOptionResult.CancelOperation);
93 | });
94 |
95 | this.WhenAny(x => x.Token, x => x.BaseUrl, (_, __) => Unit.Default)
96 | .Subscribe(_ => error.OnNext(null));
97 |
98 | error.ToProperty(this, x => x.ErrorMessage);
99 | }
100 |
101 | bool isValidUrl(string url)
102 | {
103 | if (String.IsNullOrWhiteSpace(url)) return false;
104 |
105 | try {
106 | var dontcare = new Uri(url);
107 | this.Log().Debug("Entered {0}", url);
108 | } catch (Exception ex) {
109 | return false;
110 | }
111 |
112 | return url.StartsWith("http", StringComparison.InvariantCultureIgnoreCase);
113 | }
114 |
115 | public IObservable ConnectToPlay(string baseUrl, string token)
116 | {
117 | var client = new RestClient(baseUrl);
118 | client.AddDefaultHeader("Authorization", token);
119 |
120 | var api = new PlayApi(client, null);
121 | return api.NowPlaying().Select(_ => Unit.Default);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Play/Models/PlayApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reactive;
6 | using System.Reactive.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Media.Imaging;
10 | using Akavache;
11 | using Ninject;
12 | using PusherClientDotNet;
13 | using ReactiveUI;
14 | using RestSharp;
15 | using RestSharp.Contrib;
16 |
17 | namespace Play.Models
18 | {
19 | public interface IPlayApi
20 | {
21 | IObservable NowPlaying();
22 | IObservable FetchImageForAlbum(Song song);
23 | IObservable ListenUrl();
24 | IObservable> Queue();
25 | IObservable QueueSong(Song song);
26 | IObservable Star(Song song);
27 | IObservable Unstar(Song song);
28 | IObservable> Search(string query);
29 | IObservable> AllSongsForArtist(string name);
30 | IObservable> AllSongsOnAlbum(string artist, string album);
31 |
32 | IObservable ConnectToSongChangeNotifications();
33 | }
34 |
35 | public class StreamingInfo
36 | {
37 | public string stream_url { get; set; }
38 | public string pusher_key { get; set; }
39 | }
40 |
41 | public class PlayApi : IPlayApi, IEnableLogger
42 | {
43 | readonly IRestClient client;
44 | readonly IBlobCache cache;
45 |
46 | [Inject]
47 | public PlayApi(IRestClient authedClient, [Named("LocalMachine")] IBlobCache blobCache)
48 | {
49 | client = authedClient;
50 | cache = blobCache;
51 | }
52 |
53 | public IObservable NowPlaying()
54 | {
55 | var rq = new RestRequest("now_playing");
56 | return client.RequestAsync(rq).Select(x => x.Data);
57 | }
58 |
59 | public IObservable> Queue()
60 | {
61 | var rq = new RestRequest("queue");
62 | return client.RequestAsync(rq).Select(x => {
63 | return x.Data.songs.ToList();
64 | });
65 | }
66 |
67 | public IObservable QueueSong(Song song)
68 | {
69 | var rq = new RestRequest("queue") {Method = Method.POST};
70 | rq.AddParameter("id", song.id);
71 |
72 | return client.RequestAsync(rq).Select(_ => Unit.Default);
73 | }
74 |
75 | public IObservable Star(Song song)
76 | {
77 | var rq = new RestRequest("star") {Method = Method.POST};
78 | rq.AddParameter("id", song.id);
79 |
80 | return client.RequestAsync(rq).Select(_ => Unit.Default);
81 | }
82 |
83 | public IObservable Unstar(Song song)
84 | {
85 | var rq = new RestRequest("star") {Method = Method.DELETE};
86 | rq.AddParameter("id", song.id);
87 |
88 | return client.RequestAsync(rq).Select(_ => Unit.Default);
89 | }
90 |
91 | public IObservable FetchImageForAlbum(Song song)
92 | {
93 | var rq = new RestRequest(String.Format("images/art/{0}.png", song.id));
94 |
95 | var fullUrl = client.BuildUri(rq).ToString();
96 | this.Log().Info("Fetching URL for image: {0}", fullUrl);
97 | return cache.LoadImageFromUrl(fullUrl);
98 | }
99 |
100 | public IObservable> Search(string query)
101 | {
102 | var rq = new RestRequest("search");
103 | rq.AddParameter("q", query);
104 |
105 | return client.RequestAsync(rq).Select(x => x.Data.songs);
106 | }
107 |
108 | public IObservable> AllSongsForArtist(string name)
109 | {
110 | // NB: https://github.com/play/play/issues/135
111 | var rq = new RestRequest(String.Format("artist/{0}",
112 | HttpUtility.UrlEncode(name).Replace("+", "%20")));
113 |
114 | return client.RequestAsync(rq).Select(x => x.Data.songs);
115 | }
116 |
117 | public IObservable> AllSongsOnAlbum(string artist, string album)
118 | {
119 | // NB: https://github.com/play/play/issues/135
120 | var rq = new RestRequest(String.Format("artist/{0}/album/{1}",
121 | HttpUtility.UrlEncode(artist).Replace("+", "%20"),
122 | HttpUtility.UrlEncode(album).Replace("+", "%20")));
123 |
124 | return client.RequestAsync(rq).Select(x => x.Data.songs);
125 | }
126 |
127 | public IObservable ConnectToSongChangeNotifications()
128 | {
129 | var rq = new RestRequest("streaming_info");
130 |
131 | return client.RequestAsync(rq)
132 | .SelectMany(x => PusherHelper.Connect