Started implementing SQLiteScrobbler to cache scrobbles when on dodgy internet connections

This commit is contained in:
Rikki Tooley 2015-04-04 04:30:21 +01:00
parent de2eb870a8
commit 1eeba5648b
12 changed files with 271 additions and 3 deletions

View File

@ -14,6 +14,12 @@ public enum LastResponseStatus
/// </summary>
Cached,
/// <summary>
/// The request could not be sent, and could not be cached.
/// Check the Exception property of the response for details.
/// </summary>
CacheFailed,
/// <summary>
/// The request failed, check for network connectivity
/// </summary>

View File

@ -7,8 +7,6 @@ namespace IF.Lastfm.Core.Api.Helpers
{
public interface ILastResponse
{
bool Success { get; }
LastResponseStatus Status { get; }
}

View File

@ -18,6 +18,10 @@ public class Scrobble
public TimeSpan? Duration { get; set; }
public Scrobble()
{
}
public Scrobble(string artist, string album, string track, DateTimeOffset timeplayed)
{
Artist = artist;

View File

@ -130,6 +130,13 @@
<Compile Include="Objects\LastUser.cs" />
<Compile Include="Objects\LastUserSession.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scrobblers\IScrobbler.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Scrobblers\ScrobblerBase.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Scrobblers\ScrobbleResponse.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">

View File

@ -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
{

View File

@ -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<ScrobbleResponse> ScrobbleAsync(Scrobble scrobble);
}
}

View File

@ -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;
}
}
}

View File

@ -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<ScrobbleResponse> ScrobbleAsync(Scrobble scrobble)
{
var cached = await GetCachedAsync();
var pending = new List<Scrobble>(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<IEnumerable<Scrobble>> GetCachedAsync();
public abstract Task CacheAsync(Scrobble scrobble);
}
}

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{09D7D389-7D67-45B7-90EC-B1D20693DBC5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>IF.Lastfm.SQLite</RootNamespace>
<AssemblyName>IF.Lastfm.SQLite</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\IF.Lastfm.Core\IF.Lastfm.Core.csproj">
<Project>{3cf4b78f-8b48-49cb-942f-83db13474a4f}</Project>
<Name>IF.Lastfm.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="SQLiteScrobbler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="SQLite-net">
<HintPath>..\..\packages\sqlite-net-pcl.1.0.11\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll</HintPath>
</Reference>
<Reference Include="SQLitePCL.raw">
<HintPath>..\..\packages\SQLitePCL.raw_basic.0.7.1\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCL.raw.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -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")]

View File

@ -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<Scrobble>();
}
connection.Insert(scrobble);
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="sqlite-net-pcl" version="1.0.11" targetFramework="portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10" />
<package id="SQLitePCL.raw_basic" version="0.7.1" targetFramework="portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10" />
</packages>