From 1eeba5648bf8c277b53e8207b6e281b97bcbf41f Mon Sep 17 00:00:00 2001 From: Rikki Tooley Date: Sat, 4 Apr 2015 04:30:21 +0100 Subject: [PATCH] Started implementing SQLiteScrobbler to cache scrobbles when on dodgy internet connections --- .../Api/Enums/LastResponseStatus.cs | 6 ++ .../Api/Helpers/LastResponse.cs | 2 - src/IF.Lastfm.Core/Api/Scrobble.cs | 6 +- src/IF.Lastfm.Core/IF.Lastfm.Core.csproj | 7 ++ src/IF.Lastfm.Core/LastFm.cs | 1 + src/IF.Lastfm.Core/Scrobblers/IScrobbler.cs | 11 +++ .../Scrobblers/ScrobbleResponse.cs | 23 ++++++ .../Scrobblers/ScrobblerBase.cs | 70 +++++++++++++++++ src/IF.Lastfm.SQLite/IF.Lastfm.SQLite.csproj | 75 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 30 ++++++++ src/IF.Lastfm.SQLite/SQLiteScrobbler.cs | 38 ++++++++++ src/IF.Lastfm.SQLite/packages.config | 5 ++ 12 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 src/IF.Lastfm.Core/Scrobblers/IScrobbler.cs create mode 100644 src/IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs create mode 100644 src/IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs create mode 100644 src/IF.Lastfm.SQLite/IF.Lastfm.SQLite.csproj create mode 100644 src/IF.Lastfm.SQLite/Properties/AssemblyInfo.cs create mode 100644 src/IF.Lastfm.SQLite/SQLiteScrobbler.cs create mode 100644 src/IF.Lastfm.SQLite/packages.config diff --git a/src/IF.Lastfm.Core/Api/Enums/LastResponseStatus.cs b/src/IF.Lastfm.Core/Api/Enums/LastResponseStatus.cs index bd1308b..a0ec46a 100644 --- a/src/IF.Lastfm.Core/Api/Enums/LastResponseStatus.cs +++ b/src/IF.Lastfm.Core/Api/Enums/LastResponseStatus.cs @@ -14,6 +14,12 @@ public enum LastResponseStatus /// Cached, + /// + /// The request could not be sent, and could not be cached. + /// Check the Exception property of the response for details. + /// + CacheFailed, + /// /// The request failed, check for network connectivity /// diff --git a/src/IF.Lastfm.Core/Api/Helpers/LastResponse.cs b/src/IF.Lastfm.Core/Api/Helpers/LastResponse.cs index 180e005..2af5d19 100644 --- a/src/IF.Lastfm.Core/Api/Helpers/LastResponse.cs +++ b/src/IF.Lastfm.Core/Api/Helpers/LastResponse.cs @@ -7,8 +7,6 @@ namespace IF.Lastfm.Core.Api.Helpers { public interface ILastResponse { - bool Success { get; } - LastResponseStatus Status { get; } } diff --git a/src/IF.Lastfm.Core/Api/Scrobble.cs b/src/IF.Lastfm.Core/Api/Scrobble.cs index dfb3ac1..fb0a9bc 100644 --- a/src/IF.Lastfm.Core/Api/Scrobble.cs +++ b/src/IF.Lastfm.Core/Api/Scrobble.cs @@ -17,7 +17,11 @@ public class Scrobble public bool ChosenByUser { get; set; } public TimeSpan? Duration { get; set; } - + + public Scrobble() + { + } + public Scrobble(string artist, string album, string track, DateTimeOffset timeplayed) { Artist = artist; diff --git a/src/IF.Lastfm.Core/IF.Lastfm.Core.csproj b/src/IF.Lastfm.Core/IF.Lastfm.Core.csproj index 0ef6c60..c3d8ad3 100644 --- a/src/IF.Lastfm.Core/IF.Lastfm.Core.csproj +++ b/src/IF.Lastfm.Core/IF.Lastfm.Core.csproj @@ -130,6 +130,13 @@ + + Code + + + Code + + diff --git a/src/IF.Lastfm.Core/LastFm.cs b/src/IF.Lastfm.Core/LastFm.cs index efce964..d1f20eb 100644 --- a/src/IF.Lastfm.Core/LastFm.cs +++ b/src/IF.Lastfm.Core/LastFm.cs @@ -10,6 +10,7 @@ [assembly: InternalsVisibleTo("IF.Lastfm.Core.Tests")] [assembly: InternalsVisibleTo("IF.Lastfm.Core.Tests.Integration")] +[assembly: InternalsVisibleTo("IF.Lastfm.SQLite")] [assembly: InternalsVisibleTo("IF.Lastfm.Syro")] namespace IF.Lastfm.Core { diff --git a/src/IF.Lastfm.Core/Scrobblers/IScrobbler.cs b/src/IF.Lastfm.Core/Scrobblers/IScrobbler.cs new file mode 100644 index 0000000..9b029de --- /dev/null +++ b/src/IF.Lastfm.Core/Scrobblers/IScrobbler.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using IF.Lastfm.Core.Api; +using IF.Lastfm.Core.Api.Helpers; + +namespace IF.Lastfm.Core.Scrobblers +{ + public interface IScrobbler + { + Task ScrobbleAsync(Scrobble scrobble); + } +} diff --git a/src/IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs b/src/IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs new file mode 100644 index 0000000..938a9ae --- /dev/null +++ b/src/IF.Lastfm.Core/Scrobblers/ScrobbleResponse.cs @@ -0,0 +1,23 @@ +using System; +using IF.Lastfm.Core.Api.Enums; +using IF.Lastfm.Core.Api.Helpers; + +namespace IF.Lastfm.Core.Scrobblers +{ + public class ScrobbleResponse : ILastResponse + { + public LastResponseStatus Status { get; internal set; } + + public bool Cached + { + get { return Status == LastResponseStatus.Cached; } + } + + public Exception Exception { get; internal set; } + + public ScrobbleResponse(LastResponseStatus status) + { + Status = status; + } + } +} \ No newline at end of file diff --git a/src/IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs b/src/IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs new file mode 100644 index 0000000..29f267a --- /dev/null +++ b/src/IF.Lastfm.Core/Scrobblers/ScrobblerBase.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using IF.Lastfm.Core.Api; +using IF.Lastfm.Core.Api.Commands.Track; +using IF.Lastfm.Core.Api.Enums; +using IF.Lastfm.Core.Api.Helpers; + +namespace IF.Lastfm.Core.Scrobblers +{ + public abstract class ScrobblerBase : IScrobbler + { + private ILastAuth _auth; + + protected ScrobblerBase(ILastAuth auth) + { + _auth = auth; + } + + public async Task ScrobbleAsync(Scrobble scrobble) + { + var cached = await GetCachedAsync(); + + var pending = new List(cached.OrderBy(p => p.TimePlayed)) + { + scrobble + }; + + var command = new ScrobbleCommand(_auth, pending); + + LastResponse originalResponse = null; + HttpRequestException exception = null; + try + { + originalResponse = await command.ExecuteAsync(); + if (originalResponse.Success) + { + return new ScrobbleResponse(originalResponse.Status); + } + } + catch (HttpRequestException httpEx) + { + exception = httpEx; + } + + ScrobbleResponse cacheResponse; + try + { + await CacheAsync(scrobble); + + cacheResponse = new ScrobbleResponse(LastResponseStatus.Cached); + } + catch (Exception e) + { + cacheResponse = new ScrobbleResponse(LastResponseStatus.CacheFailed) + { + Exception = e + }; + } + + return cacheResponse; + } + + public abstract Task> GetCachedAsync(); + + public abstract Task CacheAsync(Scrobble scrobble); + } +} \ No newline at end of file diff --git a/src/IF.Lastfm.SQLite/IF.Lastfm.SQLite.csproj b/src/IF.Lastfm.SQLite/IF.Lastfm.SQLite.csproj new file mode 100644 index 0000000..5240e9e --- /dev/null +++ b/src/IF.Lastfm.SQLite/IF.Lastfm.SQLite.csproj @@ -0,0 +1,75 @@ + + + + + 10.0 + Debug + AnyCPU + {09D7D389-7D67-45B7-90EC-B1D20693DBC5} + Library + Properties + IF.Lastfm.SQLite + IF.Lastfm.SQLite + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile259 + v4.5 + ..\..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {3cf4b78f-8b48-49cb-942f-83db13474a4f} + IF.Lastfm.Core + + + + + + + + + ..\..\packages\sqlite-net-pcl.1.0.11\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll + + + ..\..\packages\SQLitePCL.raw_basic.0.7.1\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCL.raw.dll + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/IF.Lastfm.SQLite/Properties/AssemblyInfo.cs b/src/IF.Lastfm.SQLite/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..dbc8ede --- /dev/null +++ b/src/IF.Lastfm.SQLite/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("IF.Lastfm.Sql")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("IF.Lastfm.Sql")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/IF.Lastfm.SQLite/SQLiteScrobbler.cs b/src/IF.Lastfm.SQLite/SQLiteScrobbler.cs new file mode 100644 index 0000000..8b1bf4e --- /dev/null +++ b/src/IF.Lastfm.SQLite/SQLiteScrobbler.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Threading.Tasks; +using IF.Lastfm.Core.Api; +using IF.Lastfm.Core.Api.Helpers; +using IF.Lastfm.Core.Scrobblers; +using SQLite; + +namespace IF.Lastfm.SQLite +{ + public class SQLiteScrobbler : ScrobblerBase + { + public string DatabasePath { get; private set; } + + public SQLiteScrobbler(ILastAuth auth, string databasePath) : base(auth) + { + DatabasePath = databasePath; + } + + public override Task CacheAsync(Scrobble scrobble) + { + return Task.Run(() => Cache(scrobble)); + } + + private void Cache(Scrobble scrobble) + { + var connection = new SQLiteConnection(DatabasePath, SQLiteOpenFlags.ReadWrite); + + if (connection.TableMappings.All(table => table.TableName != typeof(Scrobble).Name)) + { + connection.CreateTable(); + } + + connection.Insert(scrobble); + } + } +} diff --git a/src/IF.Lastfm.SQLite/packages.config b/src/IF.Lastfm.SQLite/packages.config new file mode 100644 index 0000000..24374e9 --- /dev/null +++ b/src/IF.Lastfm.SQLite/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file