diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations')
11 files changed, 2026 insertions, 0 deletions
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj new file mode 100644 index 000000000..416a7d746 --- /dev/null +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.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> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{2E781478-814D-4A48-9D80-BFF206441A65}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>MediaBrowser.Server.Implementations</RootNamespace> + <AssemblyName>MediaBrowser.Server.Implementations</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <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> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Data.SQLite"> + <HintPath>..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.dll</HintPath> + </Reference> + <Reference Include="System.Data.SQLite.Linq"> + <HintPath>..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\SharedVersion.cs"> + <Link>Properties\SharedVersion.cs</Link> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="ServerApplicationPaths.cs" /> + <Compile Include="Sqlite\SQLiteDisplayPreferencesRepository.cs" /> + <Compile Include="Sqlite\SQLiteExtensions.cs" /> + <Compile Include="Sqlite\SQLiteItemRepository.cs" /> + <Compile Include="Sqlite\SQLiteRepository.cs" /> + <Compile Include="Sqlite\SQLiteUserDataRepository.cs" /> + <Compile Include="Sqlite\SQLiteUserRepository.cs" /> + <Compile Include="WorldWeatherOnline\WeatherProvider.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj"> + <Project>{c4d2573a-3fd3-441f-81af-174ac4cd4e1d}</Project> + <Name>MediaBrowser.Common.Implementations</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> + <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> + <Name>MediaBrowser.Common</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> + <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> + <Name>MediaBrowser.Controller</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> + <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> + <Name>MediaBrowser.Model</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Content Include="x64\SQLite.Interop.dll"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Include="x86\SQLite.Interop.dll"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(SolutionDir)\.nuget\nuget.targets" /> + <!-- 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>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Properties/AssemblyInfo.cs b/MediaBrowser.Server.Implementations/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..db656b934 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +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("MediaBrowser.Server.Implementations")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.Server.Implementations")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0537cdd3-a069-4d86-9318-d46d8b119903")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs new file mode 100644 index 000000000..e473808f5 --- /dev/null +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -0,0 +1,335 @@ +using MediaBrowser.Common.Implementations; +using MediaBrowser.Controller; +using System.IO; + +namespace MediaBrowser.Server.Implementations +{ + /// <summary> + /// Extends BaseApplicationPaths to add paths that are only applicable on the server + /// </summary> + public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths + { + /// <summary> + /// The _root folder path + /// </summary> + private string _rootFolderPath; + /// <summary> + /// Gets the path to the base root media directory + /// </summary> + /// <value>The root folder path.</value> + public string RootFolderPath + { + get + { + if (_rootFolderPath == null) + { + _rootFolderPath = Path.Combine(ProgramDataPath, "Root"); + if (!Directory.Exists(_rootFolderPath)) + { + Directory.CreateDirectory(_rootFolderPath); + } + } + return _rootFolderPath; + } + } + + /// <summary> + /// The _default user views path + /// </summary> + private string _defaultUserViewsPath; + /// <summary> + /// Gets the path to the default user view directory. Used if no specific user view is defined. + /// </summary> + /// <value>The default user views path.</value> + public string DefaultUserViewsPath + { + get + { + if (_defaultUserViewsPath == null) + { + _defaultUserViewsPath = Path.Combine(RootFolderPath, "Default"); + if (!Directory.Exists(_defaultUserViewsPath)) + { + Directory.CreateDirectory(_defaultUserViewsPath); + } + } + return _defaultUserViewsPath; + } + } + + /// <summary> + /// The _localization path + /// </summary> + private string _localizationPath; + /// <summary> + /// Gets the path to localization data. + /// </summary> + /// <value>The localization path.</value> + public string LocalizationPath + { + get + { + if (_localizationPath == null) + { + _localizationPath = Path.Combine(ProgramDataPath, "Localization"); + if (!Directory.Exists(_localizationPath)) + { + Directory.CreateDirectory(_localizationPath); + } + } + return _localizationPath; + } + } + + /// <summary> + /// The _ibn path + /// </summary> + private string _ibnPath; + /// <summary> + /// Gets the path to the Images By Name directory + /// </summary> + /// <value>The images by name path.</value> + public string ImagesByNamePath + { + get + { + if (_ibnPath == null) + { + _ibnPath = Path.Combine(ProgramDataPath, "ImagesByName"); + if (!Directory.Exists(_ibnPath)) + { + Directory.CreateDirectory(_ibnPath); + } + } + + return _ibnPath; + } + } + + /// <summary> + /// The _people path + /// </summary> + private string _peoplePath; + /// <summary> + /// Gets the path to the People directory + /// </summary> + /// <value>The people path.</value> + public string PeoplePath + { + get + { + if (_peoplePath == null) + { + _peoplePath = Path.Combine(ImagesByNamePath, "People"); + if (!Directory.Exists(_peoplePath)) + { + Directory.CreateDirectory(_peoplePath); + } + } + + return _peoplePath; + } + } + + /// <summary> + /// The _genre path + /// </summary> + private string _genrePath; + /// <summary> + /// Gets the path to the Genre directory + /// </summary> + /// <value>The genre path.</value> + public string GenrePath + { + get + { + if (_genrePath == null) + { + _genrePath = Path.Combine(ImagesByNamePath, "Genre"); + if (!Directory.Exists(_genrePath)) + { + Directory.CreateDirectory(_genrePath); + } + } + + return _genrePath; + } + } + + /// <summary> + /// The _studio path + /// </summary> + private string _studioPath; + /// <summary> + /// Gets the path to the Studio directory + /// </summary> + /// <value>The studio path.</value> + public string StudioPath + { + get + { + if (_studioPath == null) + { + _studioPath = Path.Combine(ImagesByNamePath, "Studio"); + if (!Directory.Exists(_studioPath)) + { + Directory.CreateDirectory(_studioPath); + } + } + + return _studioPath; + } + } + + /// <summary> + /// The _year path + /// </summary> + private string _yearPath; + /// <summary> + /// Gets the path to the Year directory + /// </summary> + /// <value>The year path.</value> + public string YearPath + { + get + { + if (_yearPath == null) + { + _yearPath = Path.Combine(ImagesByNamePath, "Year"); + if (!Directory.Exists(_yearPath)) + { + Directory.CreateDirectory(_yearPath); + } + } + + return _yearPath; + } + } + + /// <summary> + /// The _general path + /// </summary> + private string _generalPath; + /// <summary> + /// Gets the path to the General IBN directory + /// </summary> + /// <value>The general path.</value> + public string GeneralPath + { + get + { + if (_generalPath == null) + { + _generalPath = Path.Combine(ImagesByNamePath, "General"); + if (!Directory.Exists(_generalPath)) + { + Directory.CreateDirectory(_generalPath); + } + } + + return _generalPath; + } + } + + /// <summary> + /// The _ratings path + /// </summary> + private string _ratingsPath; + /// <summary> + /// Gets the path to the Ratings IBN directory + /// </summary> + /// <value>The ratings path.</value> + public string RatingsPath + { + get + { + if (_ratingsPath == null) + { + _ratingsPath = Path.Combine(ImagesByNamePath, "Ratings"); + if (!Directory.Exists(_ratingsPath)) + { + Directory.CreateDirectory(_ratingsPath); + } + } + + return _ratingsPath; + } + } + + /// <summary> + /// The _user configuration directory path + /// </summary> + private string _userConfigurationDirectoryPath; + /// <summary> + /// Gets the path to the user configuration directory + /// </summary> + /// <value>The user configuration directory path.</value> + public string UserConfigurationDirectoryPath + { + get + { + if (_userConfigurationDirectoryPath == null) + { + _userConfigurationDirectoryPath = Path.Combine(ConfigurationDirectoryPath, "users"); + if (!Directory.Exists(_userConfigurationDirectoryPath)) + { + Directory.CreateDirectory(_userConfigurationDirectoryPath); + } + } + return _userConfigurationDirectoryPath; + } + } + + /// <summary> + /// The _f F MPEG stream cache path + /// </summary> + private string _fFMpegStreamCachePath; + /// <summary> + /// Gets the FF MPEG stream cache path. + /// </summary> + /// <value>The FF MPEG stream cache path.</value> + public string FFMpegStreamCachePath + { + get + { + if (_fFMpegStreamCachePath == null) + { + _fFMpegStreamCachePath = Path.Combine(CachePath, "ffmpeg-streams"); + + if (!Directory.Exists(_fFMpegStreamCachePath)) + { + Directory.CreateDirectory(_fFMpegStreamCachePath); + } + } + + return _fFMpegStreamCachePath; + } + } + + /// <summary> + /// The _media tools path + /// </summary> + private string _mediaToolsPath; + /// <summary> + /// Gets the folder path to tools + /// </summary> + /// <value>The media tools path.</value> + public string MediaToolsPath + { + get + { + if (_mediaToolsPath == null) + { + _mediaToolsPath = Path.Combine(ProgramDataPath, "MediaTools"); + + if (!Directory.Exists(_mediaToolsPath)) + { + Directory.CreateDirectory(_mediaToolsPath); + } + } + + return _mediaToolsPath; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs new file mode 100644 index 000000000..53a467c1a --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs @@ -0,0 +1,174 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SQLiteDisplayPreferencesRepository + /// </summary> + class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository + { + /// <summary> + /// The repository name + /// </summary> + public const string RepositoryName = "SQLite"; + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return RepositoryName; + } + } + + /// <summary> + /// The _protobuf serializer + /// </summary> + private readonly IProtobufSerializer _protobufSerializer; + + /// <summary> + /// The _app paths + /// </summary> + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="protobufSerializer">The protobuf serializer.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">protobufSerializer</exception> + public SQLiteDisplayPreferencesRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogger logger) + : base(logger) + { + if (protobufSerializer == null) + { + throw new ArgumentNullException("protobufSerializer"); + } + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + + _protobufSerializer = protobufSerializer; + _appPaths = appPaths; + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db"); + + await ConnectToDB(dbFile).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists display_prefs (item_id GUID, user_id GUID, data BLOB)", + "create unique index if not exists idx_display_prefs on display_prefs (item_id, user_id)", + "create table if not exists schema_version (table_name primary key, version)", + //pragmas + "pragma temp_store = memory" + }; + + RunQueries(queries); + } + + /// <summary> + /// Save the display preferences associated with an item in the repo + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public Task SaveDisplayPrefs(Folder item, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.Run(() => + { + var cmd = connection.CreateCommand(); + + cmd.CommandText = "delete from display_prefs where item_id = @guid"; + cmd.AddParam("@guid", item.DisplayPrefsId); + + QueueCommand(cmd); + + if (item.DisplayPrefs != null) + { + foreach (var data in item.DisplayPrefs) + { + cmd = connection.CreateCommand(); + cmd.CommandText = "insert into display_prefs (item_id, user_id, data) values (@1, @2, @3)"; + cmd.AddParam("@1", item.DisplayPrefsId); + cmd.AddParam("@2", data.UserId); + + cmd.AddParam("@3", _protobufSerializer.SerializeToBytes(data)); + + QueueCommand(cmd); + } + } + }); + } + + /// <summary> + /// Gets display preferences for an item + /// </summary> + /// <param name="item">The item.</param> + /// <returns>IEnumerable{DisplayPreferences}.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public IEnumerable<DisplayPreferences> RetrieveDisplayPrefs(Folder item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + var cmd = connection.CreateCommand(); + cmd.CommandText = "select data from display_prefs where item_id = @guid"; + var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); + guidParam.Value = item.DisplayPrefsId; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + using (var stream = GetStream(reader, 0)) + { + var data = _protobufSerializer.DeserializeFromStream<DisplayPreferences>(stream); + if (data != null) + { + yield return data; + } + } + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs new file mode 100644 index 000000000..6aed8a352 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs @@ -0,0 +1,61 @@ +using System; +using System.Data; +using System.Data.SQLite; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SQLiteExtensions + /// </summary> + static class SQLiteExtensions + { + /// <summary> + /// Adds the param. + /// </summary> + /// <param name="cmd">The CMD.</param> + /// <param name="param">The param.</param> + /// <returns>SQLiteParameter.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param) + { + if (string.IsNullOrEmpty(param)) + { + throw new ArgumentNullException(); + } + + var sqliteParam = new SQLiteParameter(param); + cmd.Parameters.Add(sqliteParam); + return sqliteParam; + } + + /// <summary> + /// Adds the param. + /// </summary> + /// <param name="cmd">The CMD.</param> + /// <param name="param">The param.</param> + /// <param name="data">The data.</param> + /// <returns>SQLiteParameter.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public static SQLiteParameter AddParam(this SQLiteCommand cmd, string param, object data) + { + if (string.IsNullOrEmpty(param)) + { + throw new ArgumentNullException(); + } + + var sqliteParam = AddParam(cmd, param); + sqliteParam.Value = data; + return sqliteParam; + } + + /// <summary> + /// Determines whether the specified conn is open. + /// </summary> + /// <param name="conn">The conn.</param> + /// <returns><c>true</c> if the specified conn is open; otherwise, <c>false</c>.</returns> + public static bool IsOpen(this SQLiteConnection conn) + { + return conn.State == ConnectionState.Open; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs new file mode 100644 index 000000000..034cd2188 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs @@ -0,0 +1,303 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SQLiteItemRepository + /// </summary> + public class SQLiteItemRepository : SqliteRepository, IItemRepository + { + /// <summary> + /// The _type mapper + /// </summary> + private readonly TypeMapper _typeMapper = new TypeMapper(); + + /// <summary> + /// The repository name + /// </summary> + public const string RepositoryName = "SQLite"; + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return RepositoryName; + } + } + + /// <summary> + /// Gets the json serializer. + /// </summary> + /// <value>The json serializer.</value> + private readonly IJsonSerializer _jsonSerializer; + + /// <summary> + /// The _app paths + /// </summary> + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">appPaths</exception> + public SQLiteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogger logger) + : base(logger) + { + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + _appPaths = appPaths; + _jsonSerializer = jsonSerializer; + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "library.db"); + + await ConnectToDB(dbFile).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists items (guid GUID primary key, obj_type, data BLOB)", + "create index if not exists idx_items on items(guid)", + "create table if not exists children (guid GUID, child GUID)", + "create unique index if not exists idx_children on children(guid, child)", + "create table if not exists schema_version (table_name primary key, version)", + //triggers + TriggerSql, + //pragmas + "pragma temp_store = memory" + }; + + RunQueries(queries); + } + + //cascade delete triggers + /// <summary> + /// The trigger SQL + /// </summary> + protected string TriggerSql = + @"CREATE TRIGGER if not exists delete_item + AFTER DELETE + ON items + FOR EACH ROW + BEGIN + DELETE FROM children WHERE children.guid = old.child; + DELETE FROM children WHERE children.child = old.child; + END"; + + /// <summary> + /// Save a standard item in the repo + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public Task SaveItem(BaseItem item, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.Run(() => + { + var serialized = _jsonSerializer.SerializeToBytes(item); + + cancellationToken.ThrowIfCancellationRequested(); + + var cmd = connection.CreateCommand(); + cmd.CommandText = "replace into items (guid, obj_type, data) values (@1, @2, @3)"; + cmd.AddParam("@1", item.Id); + cmd.AddParam("@2", item.GetType().FullName); + cmd.AddParam("@3", serialized); + QueueCommand(cmd); + }); + } + + /// <summary> + /// Retrieve a standard item from the repo + /// </summary> + /// <param name="id">The id.</param> + /// <returns>BaseItem.</returns> + /// <exception cref="System.ArgumentException"></exception> + public BaseItem RetrieveItem(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException(); + } + + return RetrieveItemInternal(id); + } + + /// <summary> + /// Internal retrieve from items or users table + /// </summary> + /// <param name="id">The id.</param> + /// <returns>BaseItem.</returns> + /// <exception cref="System.ArgumentException"></exception> + protected BaseItem RetrieveItemInternal(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException(); + } + + var cmd = connection.CreateCommand(); + cmd.CommandText = "select obj_type,data from items where guid = @guid"; + var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); + guidParam.Value = id; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + var type = reader.GetString(0); + using (var stream = GetStream(reader, 1)) + { + var itemType = _typeMapper.GetType(type); + + if (itemType == null) + { + Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type); + return null; + } + + var item = _jsonSerializer.DeserializeFromStream(stream, itemType); + return item as BaseItem; + } + } + } + return null; + } + + /// <summary> + /// Retrieve all the children of the given folder + /// </summary> + /// <param name="parent">The parent.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public IEnumerable<BaseItem> RetrieveChildren(Folder parent) + { + if (parent == null) + { + throw new ArgumentNullException(); + } + + var cmd = connection.CreateCommand(); + cmd.CommandText = "select obj_type,data from items where guid in (select child from children where guid = @guid)"; + var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); + guidParam.Value = parent.Id; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + var type = reader.GetString(0); + + using (var stream = GetStream(reader, 1)) + { + var itemType = _typeMapper.GetType(type); + if (itemType == null) + { + Logger.Error("Cannot find type {0}. Probably belongs to plug-in that is no longer loaded.", type); + continue; + } + var item = _jsonSerializer.DeserializeFromStream(stream, itemType) as BaseItem; + if (item != null) + { + item.Parent = parent; + yield return item; + } + } + } + } + } + + /// <summary> + /// Save references to all the children for the given folder + /// (Doesn't actually save the child entities) + /// </summary> + /// <param name="id">The id.</param> + /// <param name="children">The children.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">id</exception> + public Task SaveChildren(Guid id, IEnumerable<BaseItem> children, CancellationToken cancellationToken) + { + if (id == Guid.Empty) + { + throw new ArgumentNullException("id"); + } + + if (children == null) + { + throw new ArgumentNullException("children"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.Run(() => + { + var cmd = connection.CreateCommand(); + + cmd.CommandText = "delete from children where guid = @guid"; + cmd.AddParam("@guid", id); + + QueueCommand(cmd); + + foreach (var child in children) + { + var guid = child.Id; + cmd = connection.CreateCommand(); + cmd.AddParam("@guid", id); + cmd.CommandText = "replace into children (guid, child) values (@guid, @child)"; + var childParam = cmd.Parameters.Add("@child", DbType.Guid); + + childParam.Value = guid; + QueueCommand(cmd); + } + }); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs new file mode 100644 index 000000000..dead97959 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs @@ -0,0 +1,317 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Data; +using System.Data.Common; +using System.Data.SQLite; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SqliteRepository + /// </summary> + public abstract class SqliteRepository : IDisposable + { + /// <summary> + /// The db file name + /// </summary> + protected string dbFileName; + /// <summary> + /// The connection + /// </summary> + protected SQLiteConnection connection; + /// <summary> + /// The delayed commands + /// </summary> + protected ConcurrentQueue<SQLiteCommand> delayedCommands = new ConcurrentQueue<SQLiteCommand>(); + /// <summary> + /// The flush interval + /// </summary> + private const int FlushInterval = 5000; + + /// <summary> + /// The flush timer + /// </summary> + private Timer FlushTimer; + + /// <summary> + /// Gets the logger. + /// </summary> + /// <value>The logger.</value> + protected ILogger Logger { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SqliteRepository" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">logger</exception> + protected SqliteRepository(ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + Logger = logger; + } + + /// <summary> + /// Connects to DB. + /// </summary> + /// <param name="dbPath">The db path.</param> + /// <returns>Task{System.Boolean}.</returns> + /// <exception cref="System.ArgumentNullException">dbPath</exception> + protected async Task ConnectToDB(string dbPath) + { + if (string.IsNullOrEmpty(dbPath)) + { + throw new ArgumentNullException("dbPath"); + } + + dbFileName = dbPath; + var connectionstr = new SQLiteConnectionStringBuilder + { + PageSize = 4096, + CacheSize = 40960, + SyncMode = SynchronizationModes.Off, + DataSource = dbPath, + JournalMode = SQLiteJournalModeEnum.Memory + }; + + connection = new SQLiteConnection(connectionstr.ConnectionString); + + await connection.OpenAsync().ConfigureAwait(false); + + // Run once + FlushTimer = new Timer(Flush, null, TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1)); + } + + /// <summary> + /// Runs the queries. + /// </summary> + /// <param name="queries">The queries.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + /// <exception cref="System.ArgumentNullException">queries</exception> + protected void RunQueries(string[] queries) + { + if (queries == null) + { + throw new ArgumentNullException("queries"); + } + + using (var tran = connection.BeginTransaction()) + { + try + { + var cmd = connection.CreateCommand(); + + foreach (var query in queries) + { + cmd.Transaction = tran; + cmd.CommandText = query; + cmd.ExecuteNonQuery(); + } + + tran.Commit(); + } + catch (Exception e) + { + Logger.ErrorException("Error running queries", e); + tran.Rollback(); + throw; + } + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + Logger.Info("Disposing " + GetType().Name); + + try + { + // If we're not already flushing, do it now + if (!IsFlushing) + { + Flush(null); + } + + // Don't dispose in the middle of a flush + while (IsFlushing) + { + Thread.Sleep(50); + } + + if (FlushTimer != null) + { + FlushTimer.Dispose(); + FlushTimer = null; + } + + if (connection.IsOpen()) + { + connection.Close(); + } + + connection.Dispose(); + } + catch (Exception ex) + { + Logger.ErrorException("Error disposing database", ex); + } + } + } + + /// <summary> + /// Queues the command. + /// </summary> + /// <param name="cmd">The CMD.</param> + /// <exception cref="System.ArgumentNullException">cmd</exception> + protected void QueueCommand(SQLiteCommand cmd) + { + if (cmd == null) + { + throw new ArgumentNullException("cmd"); + } + + delayedCommands.Enqueue(cmd); + } + + /// <summary> + /// The is flushing + /// </summary> + private bool IsFlushing; + + /// <summary> + /// Flushes the specified sender. + /// </summary> + /// <param name="sender">The sender.</param> + private void Flush(object sender) + { + // Cannot call Count on a ConcurrentQueue since it's an O(n) operation + // Use IsEmpty instead + if (delayedCommands.IsEmpty) + { + FlushTimer.Change(TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1)); + return; + } + + if (IsFlushing) + { + return; + } + + IsFlushing = true; + var numCommands = 0; + + using (var tran = connection.BeginTransaction()) + { + try + { + while (!delayedCommands.IsEmpty) + { + SQLiteCommand command; + + delayedCommands.TryDequeue(out command); + + command.Connection = connection; + command.Transaction = tran; + + command.ExecuteNonQuery(); + numCommands++; + } + + tran.Commit(); + } + catch (Exception e) + { + Logger.ErrorException("Failed to commit transaction.", e); + tran.Rollback(); + } + } + + Logger.Info("SQL Delayed writer executed " + numCommands + " commands"); + + FlushTimer.Change(TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1)); + IsFlushing = false; + } + + /// <summary> + /// Executes the command. + /// </summary> + /// <param name="cmd">The CMD.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">cmd</exception> + public async Task ExecuteCommand(DbCommand cmd) + { + if (cmd == null) + { + throw new ArgumentNullException("cmd"); + } + + using (var tran = connection.BeginTransaction()) + { + try + { + cmd.Connection = connection; + cmd.Transaction = tran; + + await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); + + tran.Commit(); + } + catch (Exception e) + { + Logger.ErrorException("Failed to commit transaction.", e); + tran.Rollback(); + } + } + } + + /// <summary> + /// Gets a stream from a DataReader at a given ordinal + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="ordinal">The ordinal.</param> + /// <returns>Stream.</returns> + /// <exception cref="System.ArgumentNullException">reader</exception> + protected static Stream GetStream(IDataReader reader, int ordinal) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + var memoryStream = new MemoryStream(); + var num = 0L; + var array = new byte[4096]; + long bytes; + do + { + bytes = reader.GetBytes(ordinal, num, array, 0, array.Length); + memoryStream.Write(array, 0, (int)bytes); + num += bytes; + } + while (bytes > 0L); + memoryStream.Position = 0; + return memoryStream; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs new file mode 100644 index 000000000..5787a614a --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs @@ -0,0 +1,173 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SQLiteUserDataRepository + /// </summary> + public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository + { + /// <summary> + /// The repository name + /// </summary> + public const string RepositoryName = "SQLite"; + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return RepositoryName; + } + } + + /// <summary> + /// The _protobuf serializer + /// </summary> + private readonly IProtobufSerializer _protobufSerializer; + + /// <summary> + /// The _app paths + /// </summary> + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="protobufSerializer">The protobuf serializer.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">protobufSerializer</exception> + public SQLiteUserDataRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogger logger) + : base(logger) + { + if (protobufSerializer == null) + { + throw new ArgumentNullException("protobufSerializer"); + } + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + + _protobufSerializer = protobufSerializer; + _appPaths = appPaths; + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db"); + + await ConnectToDB(dbFile).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists user_data (item_id GUID, user_id GUID, data BLOB)", + "create unique index if not exists idx_user_data on user_data (item_id, user_id)", + "create table if not exists schema_version (table_name primary key, version)", + //pragmas + "pragma temp_store = memory" + }; + + RunQueries(queries); + } + + /// <summary> + /// Save the user specific data associated with an item in the repo + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public Task SaveUserData(BaseItem item, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + + var cmd = connection.CreateCommand(); + + cmd.CommandText = "delete from user_data where item_id = @guid"; + cmd.AddParam("@guid", item.UserDataId); + + QueueCommand(cmd); + + if (item.UserData != null) + { + foreach (var data in item.UserData) + { + cmd = connection.CreateCommand(); + cmd.CommandText = "insert into user_data (item_id, user_id, data) values (@1, @2, @3)"; + cmd.AddParam("@1", item.UserDataId); + cmd.AddParam("@2", data.UserId); + + cmd.AddParam("@3", _protobufSerializer.SerializeToBytes(data)); + + QueueCommand(cmd); + } + } + }); + } + + /// <summary> + /// Gets user data for an item + /// </summary> + /// <param name="item">The item.</param> + /// <returns>IEnumerable{UserItemData}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public IEnumerable<UserItemData> RetrieveUserData(BaseItem item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + var cmd = connection.CreateCommand(); + cmd.CommandText = "select data from user_data where item_id = @guid"; + var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); + guidParam.Value = item.UserDataId; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + using (var stream = GetStream(reader, 0)) + { + var data = _protobufSerializer.DeserializeFromStream<UserItemData>(stream); + if (data != null) + { + yield return data; + } + } + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs new file mode 100644 index 000000000..1e6f370eb --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs @@ -0,0 +1,182 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sqlite +{ + /// <summary> + /// Class SQLiteUserRepository + /// </summary> + public class SQLiteUserRepository : SqliteRepository, IUserRepository + { + /// <summary> + /// The repository name + /// </summary> + public const string RepositoryName = "SQLite"; + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return RepositoryName; + } + } + + /// <summary> + /// Gets the json serializer. + /// </summary> + /// <value>The json serializer.</value> + private readonly IJsonSerializer _jsonSerializer; + + /// <summary> + /// The _app paths + /// </summary> + private readonly IApplicationPaths _appPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">appPaths</exception> + public SQLiteUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogger logger) + : base(logger) + { + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + _appPaths = appPaths; + _jsonSerializer = jsonSerializer; + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "users.db"); + + await ConnectToDB(dbFile).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists users (guid GUID primary key, data BLOB)", + "create index if not exists idx_users on users(guid)", + "create table if not exists schema_version (table_name primary key, version)", + //pragmas + "pragma temp_store = memory" + }; + + RunQueries(queries); + } + + /// <summary> + /// Save a user in the repo + /// </summary> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">user</exception> + public Task SaveUser(User user, CancellationToken cancellationToken) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + + var serialized = _jsonSerializer.SerializeToBytes(user); + + cancellationToken.ThrowIfCancellationRequested(); + + var cmd = connection.CreateCommand(); + cmd.CommandText = "replace into users (guid, data) values (@1, @2)"; + cmd.AddParam("@1", user.Id); + cmd.AddParam("@2", serialized); + QueueCommand(cmd); + }); + } + + /// <summary> + /// Retrieve all users from the database + /// </summary> + /// <returns>IEnumerable{User}.</returns> + public IEnumerable<User> RetrieveAllUsers() + { + var cmd = connection.CreateCommand(); + cmd.CommandText = "select data from users"; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + using (var stream = GetStream(reader, 0)) + { + var user = _jsonSerializer.DeserializeFromStream<User>(stream); + yield return user; + } + } + } + } + + /// <summary> + /// Deletes the user. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">user</exception> + public Task DeleteUser(User user, CancellationToken cancellationToken) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + + var cmd = connection.CreateCommand(); + cmd.CommandText = "delete from users where guid=@guid"; + var guidParam = cmd.Parameters.Add("@guid", DbType.Guid); + guidParam.Value = user.Id; + + return ExecuteCommand(cmd); + }); + } + } +} diff --git a/MediaBrowser.Server.Implementations/WorldWeatherOnline/WeatherProvider.cs b/MediaBrowser.Server.Implementations/WorldWeatherOnline/WeatherProvider.cs new file mode 100644 index 000000000..1b75a58b8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/WorldWeatherOnline/WeatherProvider.cs @@ -0,0 +1,345 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Weather; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Weather; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.WorldWeatherOnline +{ + /// <summary> + /// Based on http://www.worldweatheronline.com/free-weather-feed.aspx + /// The classes in this file are a reproduction of the json output, which will then be converted to our weather model classes + /// </summary> + public class WeatherProvider : IWeatherProvider + { + /// <summary> + /// Gets or sets the logger. + /// </summary> + /// <value>The logger.</value> + private ILogger Logger { get; set; } + + /// <summary> + /// Gets the json serializer. + /// </summary> + /// <value>The json serializer.</value> + protected IJsonSerializer JsonSerializer { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="WeatherProvider" /> class. + /// </summary> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="logger">The logger.</param> + /// <exception cref="System.ArgumentNullException">logger</exception> + public WeatherProvider(IJsonSerializer jsonSerializer, ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + JsonSerializer = jsonSerializer; + Logger = logger; + } + + /// <summary> + /// The _weather semaphore + /// </summary> + private readonly SemaphoreSlim _weatherSemaphore = new SemaphoreSlim(10, 10); + + /// <summary> + /// Gets the weather info async. + /// </summary> + /// <param name="location">The location.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{WeatherInfo}.</returns> + /// <exception cref="System.ArgumentNullException">location</exception> + public async Task<WeatherInfo> GetWeatherInfoAsync(string location, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(location)) + { + throw new ArgumentNullException("location"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + const int numDays = 5; + const string apiKey = "24902f60f1231941120109"; + + var url = "http://free.worldweatheronline.com/feed/weather.ashx?q=" + location + "&format=json&num_of_days=" + numDays + "&key=" + apiKey; + + Logger.Info("Accessing weather from " + url); + + using (var stream = await Kernel.Instance.HttpManager.Get(url, _weatherSemaphore, cancellationToken).ConfigureAwait(false)) + { + var data = JsonSerializer.DeserializeFromStream<WeatherResult>(stream).data; + + return GetWeatherInfo(data); + } + } + + /// <summary> + /// Converst the json output to our WeatherInfo model class + /// </summary> + /// <param name="data">The data.</param> + /// <returns>WeatherInfo.</returns> + private WeatherInfo GetWeatherInfo(WeatherData data) + { + var info = new WeatherInfo(); + + if (data.current_condition != null) + { + var condition = data.current_condition.FirstOrDefault(); + + if (condition != null) + { + info.CurrentWeather = condition.ToWeatherStatus(); + } + } + + if (data.weather != null) + { + info.Forecasts = data.weather.Select(w => w.ToWeatherForecast()).ToArray(); + } + + return info; + } + } + + /// <summary> + /// Class WeatherResult + /// </summary> + class WeatherResult + { + /// <summary> + /// Gets or sets the data. + /// </summary> + /// <value>The data.</value> + public WeatherData data { get; set; } + } + + /// <summary> + /// Class WeatherData + /// </summary> + public class WeatherData + { + /// <summary> + /// Gets or sets the current_condition. + /// </summary> + /// <value>The current_condition.</value> + public WeatherCondition[] current_condition { get; set; } + /// <summary> + /// Gets or sets the weather. + /// </summary> + /// <value>The weather.</value> + public DailyWeatherInfo[] weather { get; set; } + } + + /// <summary> + /// Class WeatherCondition + /// </summary> + public class WeatherCondition + { + /// <summary> + /// Gets or sets the temp_ C. + /// </summary> + /// <value>The temp_ C.</value> + public string temp_C { get; set; } + /// <summary> + /// Gets or sets the temp_ F. + /// </summary> + /// <value>The temp_ F.</value> + public string temp_F { get; set; } + /// <summary> + /// Gets or sets the humidity. + /// </summary> + /// <value>The humidity.</value> + public string humidity { get; set; } + /// <summary> + /// Gets or sets the weather code. + /// </summary> + /// <value>The weather code.</value> + public string weatherCode { get; set; } + + /// <summary> + /// To the weather status. + /// </summary> + /// <returns>WeatherStatus.</returns> + public WeatherStatus ToWeatherStatus() + { + return new WeatherStatus + { + TemperatureCelsius = int.Parse(temp_C), + TemperatureFahrenheit = int.Parse(temp_F), + Humidity = int.Parse(humidity), + Condition = DailyWeatherInfo.GetCondition(weatherCode) + }; + } + } + + /// <summary> + /// Class DailyWeatherInfo + /// </summary> + public class DailyWeatherInfo + { + /// <summary> + /// Gets or sets the date. + /// </summary> + /// <value>The date.</value> + public string date { get; set; } + /// <summary> + /// Gets or sets the precip MM. + /// </summary> + /// <value>The precip MM.</value> + public string precipMM { get; set; } + /// <summary> + /// Gets or sets the temp max C. + /// </summary> + /// <value>The temp max C.</value> + public string tempMaxC { get; set; } + /// <summary> + /// Gets or sets the temp max F. + /// </summary> + /// <value>The temp max F.</value> + public string tempMaxF { get; set; } + /// <summary> + /// Gets or sets the temp min C. + /// </summary> + /// <value>The temp min C.</value> + public string tempMinC { get; set; } + /// <summary> + /// Gets or sets the temp min F. + /// </summary> + /// <value>The temp min F.</value> + public string tempMinF { get; set; } + /// <summary> + /// Gets or sets the weather code. + /// </summary> + /// <value>The weather code.</value> + public string weatherCode { get; set; } + /// <summary> + /// Gets or sets the winddir16 point. + /// </summary> + /// <value>The winddir16 point.</value> + public string winddir16Point { get; set; } + /// <summary> + /// Gets or sets the winddir degree. + /// </summary> + /// <value>The winddir degree.</value> + public string winddirDegree { get; set; } + /// <summary> + /// Gets or sets the winddirection. + /// </summary> + /// <value>The winddirection.</value> + public string winddirection { get; set; } + /// <summary> + /// Gets or sets the windspeed KMPH. + /// </summary> + /// <value>The windspeed KMPH.</value> + public string windspeedKmph { get; set; } + /// <summary> + /// Gets or sets the windspeed miles. + /// </summary> + /// <value>The windspeed miles.</value> + public string windspeedMiles { get; set; } + + /// <summary> + /// To the weather forecast. + /// </summary> + /// <returns>WeatherForecast.</returns> + public WeatherForecast ToWeatherForecast() + { + return new WeatherForecast + { + Date = DateTime.Parse(date), + HighTemperatureCelsius = int.Parse(tempMaxC), + HighTemperatureFahrenheit = int.Parse(tempMaxF), + LowTemperatureCelsius = int.Parse(tempMinC), + LowTemperatureFahrenheit = int.Parse(tempMinF), + Condition = GetCondition(weatherCode) + }; + } + + /// <summary> + /// Gets the condition. + /// </summary> + /// <param name="weatherCode">The weather code.</param> + /// <returns>WeatherConditions.</returns> + public static WeatherConditions GetCondition(string weatherCode) + { + switch (weatherCode) + { + case "362": + case "365": + case "320": + case "317": + case "182": + return WeatherConditions.Sleet; + case "338": + case "335": + case "332": + case "329": + case "326": + case "323": + case "377": + case "374": + case "371": + case "368": + case "395": + case "392": + case "350": + case "227": + case "179": + return WeatherConditions.Snow; + case "314": + case "311": + case "308": + case "305": + case "302": + case "299": + case "296": + case "293": + case "284": + case "281": + case "266": + case "263": + case "359": + case "356": + case "353": + case "185": + case "176": + return WeatherConditions.Rain; + case "260": + case "248": + return WeatherConditions.Fog; + case "389": + case "386": + case "200": + return WeatherConditions.Thunderstorm; + case "230": + return WeatherConditions.Blizzard; + case "143": + return WeatherConditions.Mist; + case "122": + return WeatherConditions.Overcast; + case "119": + return WeatherConditions.Cloudy; + case "115": + return WeatherConditions.PartlyCloudy; + default: + return WeatherConditions.Sunny; + } + } + } +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config new file mode 100644 index 000000000..106618814 --- /dev/null +++ b/MediaBrowser.Server.Implementations/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="System.Data.SQLite" version="1.0.84.0" targetFramework="net45" /> +</packages>
\ No newline at end of file |
