From 9e63e47d255effebc37f5ad6fb3f796669a89610 Mon Sep 17 00:00:00 2001 From: jasperd Date: Tue, 21 Jun 2016 02:10:21 +0200 Subject: [PATCH 1/9] Upgrade Youtube API libs (for Youtube API v3) and Youtube extractor --- Espera.Core/Espera.Core.csproj | 48 +++++++++--- Espera.Core/YoutubeSong.cs | 4 + Espera.Core/YoutubeSongFinder.cs | 76 +++++++++++-------- Espera.Core/packages.config | 12 ++- Espera.View/Espera.View.csproj | 6 +- .../ViewModels/YoutubeSongViewModel.cs | 1 + Espera.View/packages.config | 2 +- 7 files changed, 99 insertions(+), 50 deletions(-) diff --git a/Espera.Core/Espera.Core.csproj b/Espera.Core/Espera.Core.csproj index 3f462029..c57844bc 100644 --- a/Espera.Core/Espera.Core.csproj +++ b/Espera.Core/Espera.Core.csproj @@ -37,26 +37,46 @@ ..\packages\akavache.core.4.1.2\lib\net45\Akavache.dll True + + ..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll + True + False ..\packages\Espera-Network.1.0.36\lib\portable-net45+monoandroid+wpa81\Espera.Network.dll - - False - ..\packages\Google.GData.Client.2.2.0.0\lib\Google.GData.Client.dll + + ..\packages\Google.Apis.1.13.1\lib\net45\Google.Apis.dll + True - - False - ..\packages\Google.GData.Extensions.2.2.0.0\lib\Google.GData.Extensions.dll + + ..\packages\Google.Apis.Auth.1.13.1\lib\net45\Google.Apis.Auth.dll + True - - False - ..\packages\Google.GData.YouTube.2.2.0.0\lib\Google.GData.YouTube.dll + + ..\packages\Google.Apis.Auth.1.13.1\lib\net45\Google.Apis.Auth.PlatformServices.dll + True + + + ..\packages\Google.Apis.Core.1.13.1\lib\net45\Google.Apis.Core.dll + True + + + ..\packages\Google.Apis.1.13.1\lib\net45\Google.Apis.PlatformServices.dll + True + + + ..\packages\Google.Apis.YouTube.v3.1.13.1.522\lib\portable-net45+netcore45+wpa81+wp8\Google.Apis.YouTube.v3.dll + True False ..\packages\Lager.0.4.2\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Lager.dll + + ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll + True + False ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll @@ -133,9 +153,13 @@ False ..\packages\Xamarin.Insights.1.9.1.107\lib\portable-win+net40\Xamarin.Insights.dll - - False - ..\packages\YoutubeExtractor.0.10.6\lib\net35\YoutubeExtractor.dll + + ..\packages\YoutubeExtractor.0.10.10\lib\net35\YoutubeExtractor.dll + True + + + ..\packages\Zlib.Portable.Signed.1.11.0\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll + True diff --git a/Espera.Core/YoutubeSong.cs b/Espera.Core/YoutubeSong.cs index bc2c6750..4d380556 100644 --- a/Espera.Core/YoutubeSong.cs +++ b/Espera.Core/YoutubeSong.cs @@ -55,7 +55,11 @@ public override string PlaybackPath /// /// Gets or sets the average rating. /// + /// + /// Obsolete because Youtube API v3 does not longer provide a rating. + /// /// The average rating. null , if the rating is unknown. + [Obsolete] public double? Rating { get; set; } /// diff --git a/Espera.Core/YoutubeSongFinder.cs b/Espera.Core/YoutubeSongFinder.cs index 0bfaa4ea..62b9de0e 100644 --- a/Espera.Core/YoutubeSongFinder.cs +++ b/Espera.Core/YoutubeSongFinder.cs @@ -3,10 +3,11 @@ using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; +using System.Xml; using Akavache; -using Google.GData.Client; -using Google.GData.YouTube; -using Google.YouTube; +using Google.Apis.Services; +using Google.Apis.YouTube.v3; +using Google.Apis.YouTube.v3.Data; namespace Espera.Core { @@ -17,8 +18,10 @@ public sealed class YoutubeSongFinder : IYoutubeSongFinder /// public static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60); - private const string ApiKey = - "AI39si5_zcffmO_ErRSZ9xUkfy_XxPZLWuxTOzI_1RH9HhXDI-GaaQ-j6MONkl2JiF01yBDgBFPbC8-mn6U9Qo4Ek50nKcqH5g"; + private const string ApiAppName = "Espera"; + private const string ApiKey = "abc123 "; + + private const string YoutubeVideoUrl = "http://www.youtube.com/watch?v="; private const int RequestLimit = 50; @@ -54,40 +57,53 @@ public async Task ResolveYoutubeSongFromUrl(Uri url) private static IObservable> RealSearch(string searchTerm) { - var query = new YouTubeQuery(YouTubeQuery.DefaultVideoUri) + if (string.IsNullOrWhiteSpace(searchTerm)) + { + return Observable.Empty>(); + } + + var youTubeService = new YouTubeService(new BaseClientService.Initializer() { - OrderBy = "relevance", - Query = searchTerm, - SafeSearch = YouTubeQuery.SafeSearchValues.None, - NumberToRetrieve = RequestLimit - }; + ApiKey = ApiKey, + ApplicationName = ApiAppName + }); - // NB: I have no idea where this API blocks exactly - var settings = new YouTubeRequestSettings("Espera", ApiKey); - var request = new YouTubeRequest(settings); + var searchListRequest = youTubeService.Search.List("snippet"); + searchListRequest.MaxResults = RequestLimit; + searchListRequest.Q = searchTerm; + searchListRequest.SafeSearch = SearchResource.ListRequest.SafeSearchEnum.None; + searchListRequest.Order = SearchResource.ListRequest.OrderEnum.Relevance; + searchListRequest.Type = "video"; return Observable.FromAsync(async () => { - Feed diff --git a/Espera.View/ViewModels/YoutubeSongViewModel.cs b/Espera.View/ViewModels/YoutubeSongViewModel.cs index 2873992d..6e91b5e3 100644 --- a/Espera.View/ViewModels/YoutubeSongViewModel.cs +++ b/Espera.View/ViewModels/YoutubeSongViewModel.cs @@ -112,6 +112,7 @@ public bool IsLoadingThumbnail private set { this.RaiseAndSetIfChanged(ref this.isLoadingThumbnail, value); } } + [Obsolete] public double? Rating { get { return ((YoutubeSong)this.Model).Rating; } diff --git a/Espera.View/packages.config b/Espera.View/packages.config index ecf1c242..16dc3a84 100644 --- a/Espera.View/packages.config +++ b/Espera.View/packages.config @@ -32,5 +32,5 @@ - + \ No newline at end of file From 5253a93e83e2743fc4e0261223010f302bd2d003 Mon Sep 17 00:00:00 2001 From: jasperd Date: Tue, 21 Jun 2016 20:34:59 +0200 Subject: [PATCH 2/9] Fix thumbnail loading --- Espera.View/ViewModels/YoutubeSongViewModel.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Espera.View/ViewModels/YoutubeSongViewModel.cs b/Espera.View/ViewModels/YoutubeSongViewModel.cs index 6e91b5e3..afc83afa 100644 --- a/Espera.View/ViewModels/YoutubeSongViewModel.cs +++ b/Espera.View/ViewModels/YoutubeSongViewModel.cs @@ -225,14 +225,11 @@ await this.DownloadFromYoutube(videoInfo, () => YoutubeSong.DownloadVideoAsync(v private async Task GetThumbnailAsync() { - Uri thumbnailUrl = ((YoutubeSong)this.Model).ThumbnailSource; - thumbnailUrl = new Uri(thumbnailUrl.ToString().Replace("default", "hqdefault")); - this.IsLoadingThumbnail = true; try { - IBitmap image = await BlobCache.LocalMachine.LoadImageFromUrl(thumbnailUrl.ToString(), absoluteExpiration: DateTimeOffset.Now + TimeSpan.FromMinutes(60)); + IBitmap image = await BlobCache.LocalMachine.LoadImageFromUrl(((YoutubeSong)this.Model).ThumbnailSource.ToString(), absoluteExpiration: DateTimeOffset.Now + TimeSpan.FromMinutes(60)); this.Thumbnail = image.ToNative(); } From 680f206858b67aa5483cb920d120149319c006cf Mon Sep 17 00:00:00 2001 From: jasperd Date: Sun, 31 Jul 2016 17:26:20 +0200 Subject: [PATCH 3/9] Integrate proxy into Espera.Core --- Espera.Core.Tests/AudioPlayerTest.cs | 31 +++++-- Espera.Core.Tests/Espera.Core.Tests.csproj | 9 +- Espera.Core.Tests/Helpers.cs | 4 +- Espera.Core.Tests/HttpsProxyServiceTest.cs | 57 +++++++++++++ Espera.Core.Tests/LibraryBuilder.cs | 10 ++- Espera.Core.Tests/LibraryTest.cs | 40 +++++++-- Espera.Core.Tests/packages.config | 1 + Espera.Core/Audio/AudioPlayer.cs | 8 +- Espera.Core/Espera.Core.csproj | 18 ++++ Espera.Core/HttpsProxyMiddleware.cs | 95 ++++++++++++++++++++++ Espera.Core/HttpsProxyService.cs | 64 +++++++++++++++ Espera.Core/Management/Library.cs | 4 +- Espera.Core/Song.cs | 6 ++ Espera.Core/SoundCloudSong.cs | 6 ++ Espera.Core/SoundCloudSongFinder.cs | 2 +- Espera.Core/YoutubeSong.cs | 19 +++-- Espera.Core/packages.config | 4 + Espera.View/AppBootstrapper.cs | 6 ++ Espera.View/Espera.View.csproj | 4 + Espera.View/WpfMediaPlayer.cs | 6 +- Espera.View/packages.config | 1 + 21 files changed, 359 insertions(+), 36 deletions(-) create mode 100644 Espera.Core.Tests/HttpsProxyServiceTest.cs create mode 100644 Espera.Core/HttpsProxyMiddleware.cs create mode 100644 Espera.Core/HttpsProxyService.cs diff --git a/Espera.Core.Tests/AudioPlayerTest.cs b/Espera.Core.Tests/AudioPlayerTest.cs index a7531068..7a55586a 100644 --- a/Espera.Core.Tests/AudioPlayerTest.cs +++ b/Espera.Core.Tests/AudioPlayerTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; @@ -29,7 +30,10 @@ public void CanSetVolumeAfterConstruction() [Fact] public async Task StopsCurrentMediaPlayerWhenSwitchingAndPlaying() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var oldMediaPlayer = Substitute.For(); var newMediaPlayer = Substitute.For(); @@ -56,7 +60,10 @@ public class TheLoadAsyncMethod [Fact] public async Task DisposesCurrentAudioPlayerIfNewOneRegistered() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var oldMediaPlayer = Substitute.For(); var newMediaPlayer = Substitute.For(); @@ -76,7 +83,10 @@ public async Task DisposesCurrentAudioPlayerIfNewOneRegistered() [Fact] public async Task LoadsIntoAudioPlayerIfSongIsAudio() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var mediaPlayerCallback = Substitute.For(); audioPlayer.RegisterAudioPlayerCallback(mediaPlayerCallback); @@ -91,7 +101,10 @@ public async Task LoadsIntoAudioPlayerIfSongIsAudio() [Fact] public async Task LoadsIntoVideoPlayerIfSongIsVideo() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var mediaPlayerCallback = Substitute.For(); audioPlayer.RegisterVideoPlayerCallback(mediaPlayerCallback); @@ -106,7 +119,10 @@ public async Task LoadsIntoVideoPlayerIfSongIsVideo() [Fact] public async Task StopsCurrentPlayback() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var states = audioPlayer.PlaybackState.CreateCollection(); @@ -128,7 +144,10 @@ public class TheRegisterAudioPlayerMethod [Fact] public async Task DisposesDanglingAudioPlayer() { - var audioPlayer = new AudioPlayer(); + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + var audioPlayer = new AudioPlayer(streamProxy); var mediaPlayer = Substitute.For(); audioPlayer.RegisterAudioPlayerCallback(mediaPlayer); await audioPlayer.LoadAsync(Helpers.SetupSongMock()); diff --git a/Espera.Core.Tests/Espera.Core.Tests.csproj b/Espera.Core.Tests/Espera.Core.Tests.csproj index 8bafdd44..bfefe499 100644 --- a/Espera.Core.Tests/Espera.Core.Tests.csproj +++ b/Espera.Core.Tests/Espera.Core.Tests.csproj @@ -46,11 +46,17 @@ False ..\packages\Lager.0.4.2\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Lager.dll + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + False ..\packages\Rx-Testing.2.2.5\lib\net45\Microsoft.Reactive.Testing.dll - + + False + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll True @@ -140,6 +146,7 @@ + diff --git a/Espera.Core.Tests/Helpers.cs b/Espera.Core.Tests/Helpers.cs index 6d700900..241a2741 100644 --- a/Espera.Core.Tests/Helpers.cs +++ b/Espera.Core.Tests/Helpers.cs @@ -7,6 +7,7 @@ using System.Reactive.Threading.Tasks; using System.Text; using System.Threading.Tasks; +using Espera.Core.Audio; using Espera.Core.Management; using Espera.Core.Settings; using Microsoft.Reactive.Testing; @@ -74,13 +75,14 @@ public static async Task AwaitInitializationAndUpdate(this Library library) } public static Library CreateLibrary(CoreSettings settings = null, ILibraryReader reader = null, ILibraryWriter writer = null, - IFileSystem fileSystem = null, ILocalSongFinder localSongFinder = null) + IFileSystem fileSystem = null, ILocalSongFinder localSongFinder = null, AudioPlayer audioPlayer = null) { return new LibraryBuilder().WithReader(reader) .WithWriter(writer) .WithSettings(settings) .WithFileSystem(fileSystem) .WithSongFinder(localSongFinder) + .WithAudioPlayer(audioPlayer) .Build(); } diff --git a/Espera.Core.Tests/HttpsProxyServiceTest.cs b/Espera.Core.Tests/HttpsProxyServiceTest.cs new file mode 100644 index 00000000..f1dd94f6 --- /dev/null +++ b/Espera.Core.Tests/HttpsProxyServiceTest.cs @@ -0,0 +1,57 @@ +using System; +using System.Net; +using Espera.Core.Audio; +using Xunit; + +namespace Espera.Core.Tests +{ + public class HttpsProxyServiceTest : IDisposable + { + private readonly IHttpsProxyService httpsProxyService; + + public HttpsProxyServiceTest() + { + httpsProxyService = new HttpsProxyService(); + } + + [Fact] + public void YoutubeUrisAreProxied() + { + var song = new YoutubeSong("https://youtube.com", TimeSpan.Zero); + var path = song.GetType().GetField("playbackPath", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + path.SetValue(song, "https://youtube.com/?a=c&"); + var proxiedUri = song.GetSafePlaybackPath(httpsProxyService); + Assert.Equal("127.0.0.1", proxiedUri.Host); + Assert.Equal("/?remoteurl=" + WebUtility.UrlEncode("https://youtube.com/?a=c&"), proxiedUri.PathAndQuery); + } + + [Fact] + public void SoundCloudUrisAreRewritten() + { + var song = new SoundCloudSong("https://soundcloud.com/foobar", "https://api.soundlcoud.com/foobar"); + var safePath = song.GetSafePlaybackPath(httpsProxyService); + Assert.Equal("http://api.soundlcoud.com/foobar", safePath.ToString()); + } + + + [Fact] + public void LocalUrisAreNotRewritten() + { + var song = new LocalSong("C:\\some\\file.mp3", TimeSpan.Zero); + Assert.Equal(new Uri("C:\\some\\file.mp3"), song.GetSafePlaybackPath(httpsProxyService)); + } + + [Fact] + public void ThrowsForNullUriObject() + { + var song = new YoutubeSong("https://youtube.com", TimeSpan.Zero); + Assert.Throws(() => song.GetSafePlaybackPath(null)); + } + + + public void Dispose() + { + httpsProxyService.Dispose(); + } + } +} diff --git a/Espera.Core.Tests/LibraryBuilder.cs b/Espera.Core.Tests/LibraryBuilder.cs index 659a16c6..43356f2a 100644 --- a/Espera.Core.Tests/LibraryBuilder.cs +++ b/Espera.Core.Tests/LibraryBuilder.cs @@ -22,6 +22,7 @@ public class LibraryBuilder private CoreSettings settings; private ILocalSongFinder songFinder; private ILibraryWriter writer; + private AudioPlayer audioPlayer; public Library Build() { @@ -35,7 +36,8 @@ public Library Build() this.writer ?? Substitute.For(), this.settings ?? new CoreSettings(new InMemoryBlobCache()), this.fileSystem ?? new MockFileSystem(), - x => this.songFinder ?? SetupDefaultLocalSongFinder()); + x => this.songFinder ?? SetupDefaultLocalSongFinder(), + this.audioPlayer ?? new AudioPlayer()); Guid accessToken = library.LocalAccessControl.RegisterLocalAccessToken(); @@ -49,6 +51,12 @@ public Library Build() return library; } + public LibraryBuilder WithAudioPlayer(AudioPlayer player) + { + this.audioPlayer = player; + return this; + } + public LibraryBuilder WithAudioPlayer(IMediaPlayerCallback player) { this.audioPlayerCallback = player; diff --git a/Espera.Core.Tests/LibraryTest.cs b/Espera.Core.Tests/LibraryTest.cs index c59456c7..19ff3616 100644 --- a/Espera.Core.Tests/LibraryTest.cs +++ b/Espera.Core.Tests/LibraryTest.cs @@ -26,9 +26,12 @@ public sealed class LibraryTest [Fact] public async Task CanPlayAWholeBunchOfSongs() { + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + var song = new LocalSong("C://", TimeSpan.Zero); - using (Library library = new LibraryBuilder().WithPlaylist().Build()) + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { var awaiter = library.PlaybackState.Where(x => x == AudioPlayerState.Playing) .Select((x, i) => i + 1) @@ -231,9 +234,12 @@ public class TheContinueSongAsyncMethod [Fact] public async Task CallsAudioPlayerPlay() { + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + var audioPlayerCallback = Substitute.For(); - using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).Build()) + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Guid token = library.LocalAccessControl.RegisterLocalAccessToken(); @@ -427,7 +433,10 @@ public async Task JumpsOverCorruptedSong() } })); - using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayer).Build()) + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayer).WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Song[] songs = Helpers.SetupSongMocks(2); @@ -444,7 +453,10 @@ public async Task JumpsOverCorruptedSong() [Fact] public async Task PlaysMultipleSongsInARow() { - using (Library library = Helpers.CreateLibrary()) + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + using (Library library = Helpers.CreateLibrary(audioPlayer: new AudioPlayer(streamProxy))) { var conn = library.PlaybackState.Where(x => x == AudioPlayerState.Playing) .Take(2) @@ -460,9 +472,12 @@ public async Task PlaysMultipleSongsInARow() [Fact] public async Task SmokeTest() { + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + var audioPlayer = Substitute.For(); - using (Library library = new LibraryBuilder().WithAudioPlayer(audioPlayer).Build()) + using (Library library = new LibraryBuilder().WithAudioPlayer(audioPlayer).WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Song song = Helpers.SetupSongMock(); @@ -541,7 +556,10 @@ public class ThePlaySongAsyncMethod [Fact] public async Task PlaysNextSongAutomatically() { - using (Library library = new LibraryBuilder().WithPlaylist().Build()) + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Guid token = library.LocalAccessControl.RegisterLocalAccessToken(); @@ -564,7 +582,10 @@ public async Task ResetsSongIsCorruptedIfPlayingIsWorking() var audioPlayerCallback = Substitute.For(); audioPlayerCallback.LoadAsync(Arg.Any()).Returns(Observable.Throw(new SongLoadException()).ToTask()); - using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).Build()) + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Guid accessToken = library.LocalAccessControl.RegisterLocalAccessToken(); @@ -788,7 +809,10 @@ public void ThrowsArgumentNullExceptionIfSongListIsNull() [Fact] public async Task WhileSongIsPlayingStopsCurrentSong() { - using (Library library = new LibraryBuilder().WithPlaylist().Build()) + var streamProxy = Substitute.For(); + streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First()); + + using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build()) { Guid token = library.LocalAccessControl.RegisterLocalAccessToken(); diff --git a/Espera.Core.Tests/packages.config b/Espera.Core.Tests/packages.config index 7936b09f..d84a24cf 100644 --- a/Espera.Core.Tests/packages.config +++ b/Espera.Core.Tests/packages.config @@ -3,6 +3,7 @@ + diff --git a/Espera.Core/Audio/AudioPlayer.cs b/Espera.Core/Audio/AudioPlayer.cs index 29fda09e..23ce0629 100644 --- a/Espera.Core/Audio/AudioPlayer.cs +++ b/Espera.Core/Audio/AudioPlayer.cs @@ -5,6 +5,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -22,13 +23,16 @@ public sealed class AudioPlayer : IEnableLogger private readonly SemaphoreSlim gate; private readonly BehaviorSubject loadedSong; private readonly BehaviorSubject playbackState; + private readonly IHttpsProxyService httpsProxyService; private IMediaPlayerCallback audioPlayerCallback; private IMediaPlayerCallback currentCallback; private bool disposeCurrentAudioCallback; private IMediaPlayerCallback videoPlayerCallback; - internal AudioPlayer() + internal AudioPlayer(IHttpsProxyService httpsProxyService = null) { + + this.httpsProxyService = httpsProxyService ?? Locator.Current.GetService(); this.audioPlayerCallback = new DummyMediaPlayerCallback(); this.videoPlayerCallback = new DummyMediaPlayerCallback(); this.currentCallback = new DummyMediaPlayerCallback(); @@ -142,7 +146,7 @@ internal async Task LoadAsync(Song song) try { - await this.currentCallback.LoadAsync(new Uri(this.loadedSong.Value.PlaybackPath)); + await this.currentCallback.LoadAsync(song.GetSafePlaybackPath(httpsProxyService)); this.finishSubscription.Disposable = this.currentCallback.Finished.FirstAsync() .SelectMany(_ => this.Finished().ToObservable()) diff --git a/Espera.Core/Espera.Core.csproj b/Espera.Core/Espera.Core.csproj index c57844bc..68fa06b0 100644 --- a/Espera.Core/Espera.Core.csproj +++ b/Espera.Core/Espera.Core.csproj @@ -77,6 +77,18 @@ ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll True + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + True + False ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll @@ -85,6 +97,10 @@ ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll True + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + False ..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll @@ -179,6 +195,8 @@ + + diff --git a/Espera.Core/HttpsProxyMiddleware.cs b/Espera.Core/HttpsProxyMiddleware.cs new file mode 100644 index 00000000..b5aebc42 --- /dev/null +++ b/Espera.Core/HttpsProxyMiddleware.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.Owin; +using Owin; + + +namespace Espera.Core +{ + public class Startup + { + public void Configuration(IAppBuilder app) + { + app.Use(typeof(HttpsProxyMiddleware)); + } + } + + public class HttpsProxyMiddleware : OwinMiddleware + { + public HttpsProxyMiddleware(OwinMiddleware next) : base(next) + { + } + + public override async Task Invoke(IOwinContext ctx) + { + var url = ctx.Request.Query.Get("remoteurl"); + var rangeHeader = ctx.Request.Headers.Get("Range"); + + //Reusing httpClient for multiple request does not work for some reason. + using (var httpClient = new HttpClient()) + { + if (rangeHeader != null) + { + httpClient.DefaultRequestHeaders.Range = RangeHeaderValue.Parse(rangeHeader); + } + + try + { + using (var remoteResponse = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ctx.Request.CallCancelled)) + { + if (remoteResponse.Content.Headers.ContentLength != null) + { + var remoteContentLength = remoteResponse.Content.Headers.ContentLength.Value; + + ctx.Response.StatusCode = rangeHeader != null ? 206 : 200; + ctx.Response.Headers.Set("Content-Length", remoteContentLength.ToString()); + ctx.Response.Headers.Set("Accept-Ranges", "bytes"); + ctx.Response.ContentType = remoteResponse.Content.Headers.ContentType.ToString(); + + if (remoteResponse.StatusCode < HttpStatusCode.OK || remoteResponse.StatusCode > HttpStatusCode.PartialContent) + { + ctx.Response.StatusCode = 500; + return; + } + + await StreamContent(ctx, url, httpClient, remoteResponse, remoteContentLength); + } + } + } + catch (Exception ex) when (ex is OperationCanceledException || ex is IOException) + { + //IOExceptions/OperationCanceledException may occur if the client closes the connection. + } + finally + { + ctx.Response.Body.Close(); + } + } + } + + private static async Task StreamContent(IOwinContext ctx, string url, HttpClient httpclient, HttpResponseMessage remoteResponse, long remoteContentLength) + { + using (var stream = await httpclient.GetStreamAsync(url)) + { + var from = remoteResponse.Content?.Headers?.ContentRange?.From ?? 0; + var to = remoteContentLength - 1; + var responseContentRange = remoteResponse.Content?.Headers?.ContentRange?.ToString() ?? $"bytes {from}-{to}/{remoteContentLength}"; + + ctx.Response.Headers.Set("Content-Range", responseContentRange); + + int length; + var bufferSize = 65536; //64KB + var buffer = new byte[bufferSize]; + do + { + length = await stream.ReadAsync(buffer, 0, bufferSize, ctx.Request.CallCancelled); + await ctx.Response.Body.WriteAsync(buffer, 0, length, ctx.Request.CallCancelled); + } while (length > 0); + } + } + } +} diff --git a/Espera.Core/HttpsProxyService.cs b/Espera.Core/HttpsProxyService.cs new file mode 100644 index 00000000..f97de960 --- /dev/null +++ b/Espera.Core/HttpsProxyService.cs @@ -0,0 +1,64 @@ +using System; +using System.Net; +using System.Net.Sockets; +using Microsoft.Owin.Hosting; + +namespace Espera.Core +{ + public interface IHttpsProxyService : IDisposable + { + Uri GetProxiedUri(Uri remoteUri); + } + + public class HttpsProxyService : IHttpsProxyService + { + private IDisposable host; + private readonly string hostUri; + + public HttpsProxyService() + { + var port = GetFreeTcpPort(); + hostUri = $"http://{IPAddress.Loopback}:{port}"; + host = WebApp.Start(hostUri); + } + + public Uri GetProxiedUri(Uri uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + if (uri.Scheme != Uri.UriSchemeHttps) + { + return uri; + } + return new Uri(hostUri + "/?remoteurl=" + WebUtility.UrlEncode(uri.ToString())); + } + + private static int GetFreeTcpPort() + { + var tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); + var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; + tcpListener.Stop(); +#if DEBUG + return 8080; +#endif + return port; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + host?.Dispose(); + host = null; + } + } + } +} diff --git a/Espera.Core/Management/Library.cs b/Espera.Core/Management/Library.cs index 50ff77bb..d336a079 100644 --- a/Espera.Core/Management/Library.cs +++ b/Espera.Core/Management/Library.cs @@ -49,7 +49,7 @@ public sealed class Library : ReactiveObject, IDisposable private string songSourcePath; public Library(ILibraryReader libraryReader, ILibraryWriter libraryWriter, CoreSettings settings, - IFileSystem fileSystem, Func localSongFinderFunc = null) + IFileSystem fileSystem, Func localSongFinderFunc = null, AudioPlayer audioPlayer = null) { if (libraryReader == null) throw new ArgumentNullException("libraryReader"); @@ -75,7 +75,7 @@ public Library(ILibraryReader libraryReader, ILibraryWriter libraryWriter, CoreS this.songs = new HashSet(); this.playlists = new ReactiveList(); this.songsUpdated = new Subject(); - this.audioPlayer = new AudioPlayer(); + this.audioPlayer = audioPlayer ?? new AudioPlayer(); this.manualUpdateTrigger = new Subject(); this.LoadedSong = this.audioPlayer.LoadedSong; diff --git a/Espera.Core/Song.cs b/Espera.Core/Song.cs index 1bb87023..3bf515a4 100644 --- a/Espera.Core/Song.cs +++ b/Espera.Core/Song.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Espera.Core.Audio; namespace Espera.Core { @@ -154,6 +155,11 @@ public bool UpdateMetadataFrom(Song song) return changed; } + public virtual Uri GetSafePlaybackPath(IHttpsProxyService proxyService) + { + return new Uri(PlaybackPath); + } + /// /// Prepares the song for playback. /// diff --git a/Espera.Core/SoundCloudSong.cs b/Espera.Core/SoundCloudSong.cs index e55435c3..94ea11fa 100644 --- a/Espera.Core/SoundCloudSong.cs +++ b/Espera.Core/SoundCloudSong.cs @@ -1,6 +1,7 @@ using Espera.Network; using Newtonsoft.Json; using System; +using Espera.Core.Audio; namespace Espera.Core { @@ -104,6 +105,11 @@ public User User this.Artist = value == null ? string.Empty : value.Username; } } + + public override Uri GetSafePlaybackPath(IHttpsProxyService proxyService) + { + return new Uri(PlaybackPath.Replace("https://", "http://")); + } } public class User diff --git a/Espera.Core/SoundCloudSongFinder.cs b/Espera.Core/SoundCloudSongFinder.cs index 9ca77c94..d5f34dd1 100644 --- a/Espera.Core/SoundCloudSongFinder.cs +++ b/Espera.Core/SoundCloudSongFinder.cs @@ -53,7 +53,7 @@ private static IObservable> GetPopularSongs() private static IObservable> SearchSongs(string searchTerm) { - return RestService.For("http://api.soundcloud.com").Search(searchTerm, ClientId); + return RestService.For("https://api.soundcloud.com").Search(searchTerm, ClientId); } private static void SetupSongUrls(IEnumerable songs) diff --git a/Espera.Core/YoutubeSong.cs b/Espera.Core/YoutubeSong.cs index 4d380556..5be5e5d2 100644 --- a/Espera.Core/YoutubeSong.cs +++ b/Espera.Core/YoutubeSong.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using Espera.Core.Audio; using YoutubeExtractor; namespace Espera.Core @@ -52,15 +53,6 @@ public override string PlaybackPath get { return this.playbackPath; } } - /// - /// Gets or sets the average rating. - /// - /// - /// Obsolete because Youtube API v3 does not longer provide a rating. - /// - /// The average rating. null , if the rating is unknown. - [Obsolete] - public double? Rating { get; set; } /// /// Gets or sets the thumbnail source. @@ -187,5 +179,14 @@ private static string RemoveIllegalPathCharacters(string path) var r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); return r.Replace(path, ""); } + + public override Uri GetSafePlaybackPath(IHttpsProxyService proxyService) + { + if (proxyService == null) + { + throw new ArgumentNullException(nameof(proxyService)); + } + return proxyService.GetProxiedUri(new Uri(playbackPath)); + } } } \ No newline at end of file diff --git a/Espera.Core/packages.config b/Espera.Core/packages.config index 884919f4..f9cb1221 100644 --- a/Espera.Core/packages.config +++ b/Espera.Core/packages.config @@ -9,8 +9,12 @@ + + + + diff --git a/Espera.View/AppBootstrapper.cs b/Espera.View/AppBootstrapper.cs index 10c8ecfa..f934267d 100644 --- a/Espera.View/AppBootstrapper.cs +++ b/Espera.View/AppBootstrapper.cs @@ -23,6 +23,7 @@ using System.Reactive.Subjects; using System.Windows; using System.Windows.Threading; +using Espera.Core.Audio; namespace Espera.View { @@ -72,6 +73,8 @@ protected override void Configure() Locator.CurrentMutable.RegisterLazySingleton(() => new SQLitePersistentBlobCache(Path.Combine(AppInfo.BlobCachePath, "api-requests.cache.db")), typeof(IBlobCache), BlobCacheKeys.RequestCacheContract); + Locator.CurrentMutable.RegisterLazySingleton(() => new HttpsProxyService(), typeof(IHttpsProxyService)); + Locator.CurrentMutable.RegisterLazySingleton(() => new ShellViewModel(Locator.Current.GetService(), this.viewSettings, this.coreSettings, @@ -99,6 +102,9 @@ protected override void OnExit(object sender, EventArgs e) this.Log().Info("Shutting down the library"); Locator.Current.GetService().Dispose(); + this.Log().Info("Shutting down OWIN host"); + Locator.Current.GetService().Dispose(); + this.Log().Info("Shutting down BlobCaches"); BlobCache.Shutdown().Wait(); var requestCache = Locator.Current.GetService(BlobCacheKeys.RequestCacheContract); diff --git a/Espera.View/Espera.View.csproj b/Espera.View/Espera.View.csproj index 6c39f56e..c72903a2 100644 --- a/Espera.View/Espera.View.csproj +++ b/Espera.View/Espera.View.csproj @@ -93,6 +93,10 @@ ..\packages\MahApps.Metro.1.2.4.0\lib\net45\MahApps.Metro.dll True + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + True + ..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll diff --git a/Espera.View/WpfMediaPlayer.cs b/Espera.View/WpfMediaPlayer.cs index 435f7256..d7eb7d9e 100644 --- a/Espera.View/WpfMediaPlayer.cs +++ b/Espera.View/WpfMediaPlayer.cs @@ -32,11 +32,7 @@ public IObservable Finished public Task LoadAsync(Uri uri) { - // MediaElement is too dumb to accept https urls, so try to remove it - // https: //connect.microsoft.com/VisualStudio/feedback/details/934355/in-a-wpf-standalone-application-exe-when-the-source-of-a-mediaelement-is-set-to-a-https-uri-it-throws-a-nullreferenceexception - var strippedHttp = new Uri(uri.ToString().Replace("https://", "http://")); - - return this.mediaElement.Dispatcher.InvokeAsync(() => this.mediaElement.Source = strippedHttp).Task; + return this.mediaElement.Dispatcher.InvokeAsync(() => this.mediaElement.Source = uri).Task; } public Task PauseAsync() diff --git a/Espera.View/packages.config b/Espera.View/packages.config index 16dc3a84..db65b41b 100644 --- a/Espera.View/packages.config +++ b/Espera.View/packages.config @@ -10,6 +10,7 @@ + From e1ada2b31fc5437dec43e311e98e71176675a744 Mon Sep 17 00:00:00 2001 From: jasperd Date: Mon, 1 Aug 2016 17:53:46 +0200 Subject: [PATCH 4/9] Remove Youtube rating column - Youtube API v3 does not provide rating data --- Espera.View/SortHelpers.cs | 6 ------ Espera.View/ViewModels/YoutubeSongViewModel.cs | 6 ------ Espera.View/ViewModels/YoutubeViewModel.cs | 12 ------------ Espera.View/ViewSettings.cs | 6 ------ Espera.View/Views/YoutubeView.xaml | 8 -------- 5 files changed, 38 deletions(-) diff --git a/Espera.View/SortHelpers.cs b/Espera.View/SortHelpers.cs index 93aa7fc6..089ae300 100644 --- a/Espera.View/SortHelpers.cs +++ b/Espera.View/SortHelpers.cs @@ -45,12 +45,6 @@ public static Func, IOrderedEnumerable song.PlaybackCount, sortOrder); } - public static Func, IOrderedEnumerable> GetOrderByRating(SortOrder sortOrder) - { - return songs => songs - .OrderBy(song => song.Rating, sortOrder); - } - public static Func, IOrderedEnumerable> GetOrderByTitle(SortOrder sortOrder) where T : ISongViewModelBase { return songs => songs diff --git a/Espera.View/ViewModels/YoutubeSongViewModel.cs b/Espera.View/ViewModels/YoutubeSongViewModel.cs index afc83afa..1eed564c 100644 --- a/Espera.View/ViewModels/YoutubeSongViewModel.cs +++ b/Espera.View/ViewModels/YoutubeSongViewModel.cs @@ -112,12 +112,6 @@ public bool IsLoadingThumbnail private set { this.RaiseAndSetIfChanged(ref this.isLoadingThumbnail, value); } } - [Obsolete] - public double? Rating - { - get { return ((YoutubeSong)this.Model).Rating; } - } - public ImageSource Thumbnail { get diff --git a/Espera.View/ViewModels/YoutubeViewModel.cs b/Espera.View/ViewModels/YoutubeViewModel.cs index 1aaf34c7..74db6ecf 100644 --- a/Espera.View/ViewModels/YoutubeViewModel.cs +++ b/Espera.View/ViewModels/YoutubeViewModel.cs @@ -12,7 +12,6 @@ namespace Espera.View.ViewModels public class YoutubeViewModel : NetworkSongViewModel { private readonly ViewSettings viewSettings; - private SortOrder ratingOrder; private SortOrder viewsOrder; public YoutubeViewModel(Library library, ViewSettings viewSettings, CoreSettings coreSettings, Guid accessToken, IYoutubeSongFinder songFinder = null) @@ -25,9 +24,6 @@ public YoutubeViewModel(Library library, ViewSettings viewSettings, CoreSettings this.viewSettings = viewSettings; - this.OrderByRatingCommand = ReactiveCommand.Create(); - this.OrderByRatingCommand.Subscribe(_ => this.ApplyOrder(SortHelpers.GetOrderByRating, ref this.ratingOrder)); - this.OrderByViewsCommand = ReactiveCommand.Create(); this.OrderByViewsCommand.Subscribe(_ => this.ApplyOrder(SortHelpers.GetOrderByViews, ref this.viewsOrder)); } @@ -44,16 +40,8 @@ public int LinkColumnWidth set { this.viewSettings.YoutubeLinkColumnWidth = value; } } - public ReactiveCommand OrderByRatingCommand { get; private set; } - public ReactiveCommand OrderByViewsCommand { get; private set; } - public int RatingColumnWidth - { - get { return this.viewSettings.YoutubeRatingColumnWidth; } - set { this.viewSettings.YoutubeRatingColumnWidth = value; } - } - public int TitleColumnWidth { get { return this.viewSettings.YoutubeTitleColumnWidth; } diff --git a/Espera.View/ViewSettings.cs b/Espera.View/ViewSettings.cs index 9859373f..4087d9b6 100644 --- a/Espera.View/ViewSettings.cs +++ b/Espera.View/ViewSettings.cs @@ -112,12 +112,6 @@ public int YoutubeLinkColumnWidth set { this.SetOrCreate(value); } } - public int YoutubeRatingColumnWidth - { - get { return this.GetOrCreate(75); } - set { this.SetOrCreate(value); } - } - public int YoutubeTitleColumnWidth { get { return this.GetOrCreate(200); } diff --git a/Espera.View/Views/YoutubeView.xaml b/Espera.View/Views/YoutubeView.xaml index c692e9ce..3f9b5444 100644 --- a/Espera.View/Views/YoutubeView.xaml +++ b/Espera.View/Views/YoutubeView.xaml @@ -144,14 +144,6 @@ - - - - - - - - From d4ee3cf8049e024ee070da5de146ed4111647419 Mon Sep 17 00:00:00 2001 From: jasperd Date: Mon, 1 Aug 2016 21:46:22 +0200 Subject: [PATCH 5/9] Set limit for SoundCloud API - If no limit is set, result set is limit to 10 hits. 200 is the maximum Soundcloud allows. --- Espera.Core/ISoundCloudApi.cs | 2 +- Espera.Core/SoundCloudSongFinder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Espera.Core/ISoundCloudApi.cs b/Espera.Core/ISoundCloudApi.cs index 70e13ec1..72b74bba 100644 --- a/Espera.Core/ISoundCloudApi.cs +++ b/Espera.Core/ISoundCloudApi.cs @@ -10,7 +10,7 @@ public interface ISoundCloudApi IObservable GetPopularTracks(int limit); [Get("/tracks.json")] - IObservable> Search([AliasAs("q")] string searchTerm, [AliasAs("client_id")] string clientId); + IObservable> Search([AliasAs("q")] string searchTerm, [AliasAs("client_id")] string clientId, byte limit); } public class ExploreResponse diff --git a/Espera.Core/SoundCloudSongFinder.cs b/Espera.Core/SoundCloudSongFinder.cs index d5f34dd1..8d3795d3 100644 --- a/Espera.Core/SoundCloudSongFinder.cs +++ b/Espera.Core/SoundCloudSongFinder.cs @@ -53,7 +53,7 @@ private static IObservable> GetPopularSongs() private static IObservable> SearchSongs(string searchTerm) { - return RestService.For("https://api.soundcloud.com").Search(searchTerm, ClientId); + return RestService.For("https://api.soundcloud.com").Search(searchTerm, ClientId, 200); } private static void SetupSongUrls(IEnumerable songs) From 8f0aed897f07848b706c4ea30b05e70966a7ca8e Mon Sep 17 00:00:00 2001 From: jasperd Date: Mon, 1 Aug 2016 21:52:26 +0200 Subject: [PATCH 6/9] Fix warning about unreachable code --- Espera.Core/HttpsProxyService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Espera.Core/HttpsProxyService.cs b/Espera.Core/HttpsProxyService.cs index f97de960..28a684e1 100644 --- a/Espera.Core/HttpsProxyService.cs +++ b/Espera.Core/HttpsProxyService.cs @@ -44,7 +44,9 @@ private static int GetFreeTcpPort() #if DEBUG return 8080; #endif +#pragma warning disable 162 return port; +#pragma warning restore 162 } public void Dispose() From e5bb2e8cdd1cd48d1067560c7cc44892918d2a73 Mon Sep 17 00:00:00 2001 From: jasperd Date: Mon, 1 Aug 2016 22:45:30 +0200 Subject: [PATCH 7/9] Fixed name of test class --- .../{HttpsProxyServiceTest.cs => UriRewriteTest.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename Espera.Core.Tests/{HttpsProxyServiceTest.cs => UriRewriteTest.cs} (95%) diff --git a/Espera.Core.Tests/HttpsProxyServiceTest.cs b/Espera.Core.Tests/UriRewriteTest.cs similarity index 95% rename from Espera.Core.Tests/HttpsProxyServiceTest.cs rename to Espera.Core.Tests/UriRewriteTest.cs index f1dd94f6..5d077636 100644 --- a/Espera.Core.Tests/HttpsProxyServiceTest.cs +++ b/Espera.Core.Tests/UriRewriteTest.cs @@ -5,11 +5,11 @@ namespace Espera.Core.Tests { - public class HttpsProxyServiceTest : IDisposable + public class UriRewriteTest : IDisposable { private readonly IHttpsProxyService httpsProxyService; - public HttpsProxyServiceTest() + public UriRewriteTest() { httpsProxyService = new HttpsProxyService(); } From 07dd290ff73abcc5ca40dafae42fc6e954765e7b Mon Sep 17 00:00:00 2001 From: jasperd Date: Mon, 1 Aug 2016 22:47:08 +0200 Subject: [PATCH 8/9] Update the file name in .csproj --- Espera.Core.Tests/Espera.Core.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Espera.Core.Tests/Espera.Core.Tests.csproj b/Espera.Core.Tests/Espera.Core.Tests.csproj index bfefe499..f407f620 100644 --- a/Espera.Core.Tests/Espera.Core.Tests.csproj +++ b/Espera.Core.Tests/Espera.Core.Tests.csproj @@ -146,7 +146,7 @@ - + From 48f3e61f67de36c8826cdb42d7bb6bd93308508c Mon Sep 17 00:00:00 2001 From: jasperd Date: Fri, 5 Aug 2016 15:22:35 +0200 Subject: [PATCH 9/9] Fix build script for C# 6 --- Create-Release.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Create-Release.ps1 b/Create-Release.ps1 index a1902e22..aa1b5e94 100644 --- a/Create-Release.ps1 +++ b/Create-Release.ps1 @@ -13,8 +13,8 @@ Write-Host "Building Espera..." -ForegroundColor Green Function GetMSBuildExe { [CmdletBinding()] - $DotNetVersion = "4.0" - $RegKey = "HKLM:\software\Microsoft\MSBuild\ToolsVersions\$DotNetVersion" + $MSBuildToolsVersion = "14.0" + $RegKey = "HKLM:\software\Microsoft\MSBuild\ToolsVersions\$MSBuildToolsVersion" $RegProperty = "MSBuildToolsPath" $MSBuildExe = Join-Path -Path (Get-ItemProperty $RegKey).$RegProperty -ChildPath "msbuild.exe" Return $MSBuildExe