From 8ce3e74e8112a94773df22827849bf274fc88198 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Sun, 24 Feb 2013 16:53:54 -0500 Subject: More DI --- .../MediaBrowser.Server.Implementations.csproj | 102 ++++++ .../Properties/AssemblyInfo.cs | 30 ++ .../ServerApplicationPaths.cs | 335 ++++++++++++++++++++ .../Sqlite/SQLiteDisplayPreferencesRepository.cs | 174 +++++++++++ .../Sqlite/SQLiteExtensions.cs | 61 ++++ .../Sqlite/SQLiteItemRepository.cs | 303 ++++++++++++++++++ .../Sqlite/SQLiteRepository.cs | 317 +++++++++++++++++++ .../Sqlite/SQLiteUserDataRepository.cs | 173 +++++++++++ .../Sqlite/SQLiteUserRepository.cs | 182 +++++++++++ .../WorldWeatherOnline/WeatherProvider.cs | 345 +++++++++++++++++++++ .../packages.config | 4 + 11 files changed, 2026 insertions(+) create mode 100644 MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj create mode 100644 MediaBrowser.Server.Implementations/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.Server.Implementations/ServerApplicationPaths.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteExtensions.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs create mode 100644 MediaBrowser.Server.Implementations/WorldWeatherOnline/WeatherProvider.cs create mode 100644 MediaBrowser.Server.Implementations/packages.config (limited to 'MediaBrowser.Server.Implementations') 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 @@ + + + + + Debug + AnyCPU + {2E781478-814D-4A48-9D80-BFF206441A65} + Library + Properties + MediaBrowser.Server.Implementations + MediaBrowser.Server.Implementations + v4.5 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.dll + + + ..\packages\System.Data.SQLite.1.0.84.0\lib\net45\System.Data.SQLite.Linq.dll + + + + + + + + + + Properties\SharedVersion.cs + + + + + + + + + + + + + + {c4d2573a-3fd3-441f-81af-174ac4cd4e1d} + MediaBrowser.Common.Implementations + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + Always + + + Always + + + + + + + + + + \ 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 +{ + /// + /// Extends BaseApplicationPaths to add paths that are only applicable on the server + /// + public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths + { + /// + /// The _root folder path + /// + private string _rootFolderPath; + /// + /// Gets the path to the base root media directory + /// + /// The root folder path. + public string RootFolderPath + { + get + { + if (_rootFolderPath == null) + { + _rootFolderPath = Path.Combine(ProgramDataPath, "Root"); + if (!Directory.Exists(_rootFolderPath)) + { + Directory.CreateDirectory(_rootFolderPath); + } + } + return _rootFolderPath; + } + } + + /// + /// The _default user views path + /// + private string _defaultUserViewsPath; + /// + /// Gets the path to the default user view directory. Used if no specific user view is defined. + /// + /// The default user views path. + public string DefaultUserViewsPath + { + get + { + if (_defaultUserViewsPath == null) + { + _defaultUserViewsPath = Path.Combine(RootFolderPath, "Default"); + if (!Directory.Exists(_defaultUserViewsPath)) + { + Directory.CreateDirectory(_defaultUserViewsPath); + } + } + return _defaultUserViewsPath; + } + } + + /// + /// The _localization path + /// + private string _localizationPath; + /// + /// Gets the path to localization data. + /// + /// The localization path. + public string LocalizationPath + { + get + { + if (_localizationPath == null) + { + _localizationPath = Path.Combine(ProgramDataPath, "Localization"); + if (!Directory.Exists(_localizationPath)) + { + Directory.CreateDirectory(_localizationPath); + } + } + return _localizationPath; + } + } + + /// + /// The _ibn path + /// + private string _ibnPath; + /// + /// Gets the path to the Images By Name directory + /// + /// The images by name path. + public string ImagesByNamePath + { + get + { + if (_ibnPath == null) + { + _ibnPath = Path.Combine(ProgramDataPath, "ImagesByName"); + if (!Directory.Exists(_ibnPath)) + { + Directory.CreateDirectory(_ibnPath); + } + } + + return _ibnPath; + } + } + + /// + /// The _people path + /// + private string _peoplePath; + /// + /// Gets the path to the People directory + /// + /// The people path. + public string PeoplePath + { + get + { + if (_peoplePath == null) + { + _peoplePath = Path.Combine(ImagesByNamePath, "People"); + if (!Directory.Exists(_peoplePath)) + { + Directory.CreateDirectory(_peoplePath); + } + } + + return _peoplePath; + } + } + + /// + /// The _genre path + /// + private string _genrePath; + /// + /// Gets the path to the Genre directory + /// + /// The genre path. + public string GenrePath + { + get + { + if (_genrePath == null) + { + _genrePath = Path.Combine(ImagesByNamePath, "Genre"); + if (!Directory.Exists(_genrePath)) + { + Directory.CreateDirectory(_genrePath); + } + } + + return _genrePath; + } + } + + /// + /// The _studio path + /// + private string _studioPath; + /// + /// Gets the path to the Studio directory + /// + /// The studio path. + public string StudioPath + { + get + { + if (_studioPath == null) + { + _studioPath = Path.Combine(ImagesByNamePath, "Studio"); + if (!Directory.Exists(_studioPath)) + { + Directory.CreateDirectory(_studioPath); + } + } + + return _studioPath; + } + } + + /// + /// The _year path + /// + private string _yearPath; + /// + /// Gets the path to the Year directory + /// + /// The year path. + public string YearPath + { + get + { + if (_yearPath == null) + { + _yearPath = Path.Combine(ImagesByNamePath, "Year"); + if (!Directory.Exists(_yearPath)) + { + Directory.CreateDirectory(_yearPath); + } + } + + return _yearPath; + } + } + + /// + /// The _general path + /// + private string _generalPath; + /// + /// Gets the path to the General IBN directory + /// + /// The general path. + public string GeneralPath + { + get + { + if (_generalPath == null) + { + _generalPath = Path.Combine(ImagesByNamePath, "General"); + if (!Directory.Exists(_generalPath)) + { + Directory.CreateDirectory(_generalPath); + } + } + + return _generalPath; + } + } + + /// + /// The _ratings path + /// + private string _ratingsPath; + /// + /// Gets the path to the Ratings IBN directory + /// + /// The ratings path. + public string RatingsPath + { + get + { + if (_ratingsPath == null) + { + _ratingsPath = Path.Combine(ImagesByNamePath, "Ratings"); + if (!Directory.Exists(_ratingsPath)) + { + Directory.CreateDirectory(_ratingsPath); + } + } + + return _ratingsPath; + } + } + + /// + /// The _user configuration directory path + /// + private string _userConfigurationDirectoryPath; + /// + /// Gets the path to the user configuration directory + /// + /// The user configuration directory path. + public string UserConfigurationDirectoryPath + { + get + { + if (_userConfigurationDirectoryPath == null) + { + _userConfigurationDirectoryPath = Path.Combine(ConfigurationDirectoryPath, "users"); + if (!Directory.Exists(_userConfigurationDirectoryPath)) + { + Directory.CreateDirectory(_userConfigurationDirectoryPath); + } + } + return _userConfigurationDirectoryPath; + } + } + + /// + /// The _f F MPEG stream cache path + /// + private string _fFMpegStreamCachePath; + /// + /// Gets the FF MPEG stream cache path. + /// + /// The FF MPEG stream cache path. + public string FFMpegStreamCachePath + { + get + { + if (_fFMpegStreamCachePath == null) + { + _fFMpegStreamCachePath = Path.Combine(CachePath, "ffmpeg-streams"); + + if (!Directory.Exists(_fFMpegStreamCachePath)) + { + Directory.CreateDirectory(_fFMpegStreamCachePath); + } + } + + return _fFMpegStreamCachePath; + } + } + + /// + /// The _media tools path + /// + private string _mediaToolsPath; + /// + /// Gets the folder path to tools + /// + /// The media tools path. + 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 +{ + /// + /// Class SQLiteDisplayPreferencesRepository + /// + class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository + { + /// + /// The repository name + /// + public const string RepositoryName = "SQLite"; + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return RepositoryName; + } + } + + /// + /// The _protobuf serializer + /// + private readonly IProtobufSerializer _protobufSerializer; + + /// + /// The _app paths + /// + private readonly IApplicationPaths _appPaths; + + /// + /// Initializes a new instance of the class. + /// + /// The app paths. + /// The protobuf serializer. + /// The logger. + /// protobufSerializer + 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; + } + + /// + /// Opens the connection to the database + /// + /// Task. + 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); + } + + /// + /// Save the display preferences associated with an item in the repo + /// + /// The item. + /// The cancellation token. + /// Task. + /// item + 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); + } + } + }); + } + + /// + /// Gets display preferences for an item + /// + /// The item. + /// IEnumerable{DisplayPreferences}. + /// + public IEnumerable 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(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 +{ + /// + /// Class SQLiteExtensions + /// + static class SQLiteExtensions + { + /// + /// Adds the param. + /// + /// The CMD. + /// The param. + /// SQLiteParameter. + /// + 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; + } + + /// + /// Adds the param. + /// + /// The CMD. + /// The param. + /// The data. + /// SQLiteParameter. + /// + 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; + } + + /// + /// Determines whether the specified conn is open. + /// + /// The conn. + /// true if the specified conn is open; otherwise, false. + 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 +{ + /// + /// Class SQLiteItemRepository + /// + public class SQLiteItemRepository : SqliteRepository, IItemRepository + { + /// + /// The _type mapper + /// + private readonly TypeMapper _typeMapper = new TypeMapper(); + + /// + /// The repository name + /// + public const string RepositoryName = "SQLite"; + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return RepositoryName; + } + } + + /// + /// Gets the json serializer. + /// + /// The json serializer. + private readonly IJsonSerializer _jsonSerializer; + + /// + /// The _app paths + /// + private readonly IApplicationPaths _appPaths; + + /// + /// Initializes a new instance of the class. + /// + /// The app paths. + /// The json serializer. + /// The logger. + /// appPaths + 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; + } + + /// + /// Opens the connection to the database + /// + /// Task. + 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 + /// + /// The trigger SQL + /// + 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"; + + /// + /// Save a standard item in the repo + /// + /// The item. + /// The cancellation token. + /// Task. + /// item + 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); + }); + } + + /// + /// Retrieve a standard item from the repo + /// + /// The id. + /// BaseItem. + /// + public BaseItem RetrieveItem(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException(); + } + + return RetrieveItemInternal(id); + } + + /// + /// Internal retrieve from items or users table + /// + /// The id. + /// BaseItem. + /// + 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; + } + + /// + /// Retrieve all the children of the given folder + /// + /// The parent. + /// IEnumerable{BaseItem}. + /// + public IEnumerable 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; + } + } + } + } + } + + /// + /// Save references to all the children for the given folder + /// (Doesn't actually save the child entities) + /// + /// The id. + /// The children. + /// The cancellation token. + /// Task. + /// id + public Task SaveChildren(Guid id, IEnumerable 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 +{ + /// + /// Class SqliteRepository + /// + public abstract class SqliteRepository : IDisposable + { + /// + /// The db file name + /// + protected string dbFileName; + /// + /// The connection + /// + protected SQLiteConnection connection; + /// + /// The delayed commands + /// + protected ConcurrentQueue delayedCommands = new ConcurrentQueue(); + /// + /// The flush interval + /// + private const int FlushInterval = 5000; + + /// + /// The flush timer + /// + private Timer FlushTimer; + + /// + /// Gets the logger. + /// + /// The logger. + protected ILogger Logger { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// logger + protected SqliteRepository(ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + Logger = logger; + } + + /// + /// Connects to DB. + /// + /// The db path. + /// Task{System.Boolean}. + /// dbPath + 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)); + } + + /// + /// Runs the queries. + /// + /// The queries. + /// true if XXXX, false otherwise + /// queries + 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; + } + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + 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); + } + } + } + + /// + /// Queues the command. + /// + /// The CMD. + /// cmd + protected void QueueCommand(SQLiteCommand cmd) + { + if (cmd == null) + { + throw new ArgumentNullException("cmd"); + } + + delayedCommands.Enqueue(cmd); + } + + /// + /// The is flushing + /// + private bool IsFlushing; + + /// + /// Flushes the specified sender. + /// + /// The sender. + 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; + } + + /// + /// Executes the command. + /// + /// The CMD. + /// Task. + /// cmd + 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(); + } + } + } + + /// + /// Gets a stream from a DataReader at a given ordinal + /// + /// The reader. + /// The ordinal. + /// Stream. + /// reader + 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 +{ + /// + /// Class SQLiteUserDataRepository + /// + public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository + { + /// + /// The repository name + /// + public const string RepositoryName = "SQLite"; + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return RepositoryName; + } + } + + /// + /// The _protobuf serializer + /// + private readonly IProtobufSerializer _protobufSerializer; + + /// + /// The _app paths + /// + private readonly IApplicationPaths _appPaths; + + /// + /// Initializes a new instance of the class. + /// + /// The app paths. + /// The protobuf serializer. + /// The logger. + /// protobufSerializer + 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; + } + + /// + /// Opens the connection to the database + /// + /// Task. + 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); + } + + /// + /// Save the user specific data associated with an item in the repo + /// + /// The item. + /// The cancellation token. + /// Task. + /// item + 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); + } + } + }); + } + + /// + /// Gets user data for an item + /// + /// The item. + /// IEnumerable{UserItemData}. + /// item + public IEnumerable 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(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 +{ + /// + /// Class SQLiteUserRepository + /// + public class SQLiteUserRepository : SqliteRepository, IUserRepository + { + /// + /// The repository name + /// + public const string RepositoryName = "SQLite"; + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return RepositoryName; + } + } + + /// + /// Gets the json serializer. + /// + /// The json serializer. + private readonly IJsonSerializer _jsonSerializer; + + /// + /// The _app paths + /// + private readonly IApplicationPaths _appPaths; + + /// + /// Initializes a new instance of the class. + /// + /// The app paths. + /// The json serializer. + /// The logger. + /// appPaths + 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; + } + + /// + /// Opens the connection to the database + /// + /// Task. + 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); + } + + /// + /// Save a user in the repo + /// + /// The user. + /// The cancellation token. + /// Task. + /// user + 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); + }); + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public IEnumerable 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(stream); + yield return user; + } + } + } + } + + /// + /// Deletes the user. + /// + /// The user. + /// The cancellation token. + /// Task. + /// user + 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 +{ + /// + /// 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 + /// + public class WeatherProvider : IWeatherProvider + { + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Gets the json serializer. + /// + /// The json serializer. + protected IJsonSerializer JsonSerializer { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The json serializer. + /// The logger. + /// logger + public WeatherProvider(IJsonSerializer jsonSerializer, ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + + JsonSerializer = jsonSerializer; + Logger = logger; + } + + /// + /// The _weather semaphore + /// + private readonly SemaphoreSlim _weatherSemaphore = new SemaphoreSlim(10, 10); + + /// + /// Gets the weather info async. + /// + /// The location. + /// The cancellation token. + /// Task{WeatherInfo}. + /// location + public async Task 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(stream).data; + + return GetWeatherInfo(data); + } + } + + /// + /// Converst the json output to our WeatherInfo model class + /// + /// The data. + /// WeatherInfo. + 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; + } + } + + /// + /// Class WeatherResult + /// + class WeatherResult + { + /// + /// Gets or sets the data. + /// + /// The data. + public WeatherData data { get; set; } + } + + /// + /// Class WeatherData + /// + public class WeatherData + { + /// + /// Gets or sets the current_condition. + /// + /// The current_condition. + public WeatherCondition[] current_condition { get; set; } + /// + /// Gets or sets the weather. + /// + /// The weather. + public DailyWeatherInfo[] weather { get; set; } + } + + /// + /// Class WeatherCondition + /// + public class WeatherCondition + { + /// + /// Gets or sets the temp_ C. + /// + /// The temp_ C. + public string temp_C { get; set; } + /// + /// Gets or sets the temp_ F. + /// + /// The temp_ F. + public string temp_F { get; set; } + /// + /// Gets or sets the humidity. + /// + /// The humidity. + public string humidity { get; set; } + /// + /// Gets or sets the weather code. + /// + /// The weather code. + public string weatherCode { get; set; } + + /// + /// To the weather status. + /// + /// WeatherStatus. + public WeatherStatus ToWeatherStatus() + { + return new WeatherStatus + { + TemperatureCelsius = int.Parse(temp_C), + TemperatureFahrenheit = int.Parse(temp_F), + Humidity = int.Parse(humidity), + Condition = DailyWeatherInfo.GetCondition(weatherCode) + }; + } + } + + /// + /// Class DailyWeatherInfo + /// + public class DailyWeatherInfo + { + /// + /// Gets or sets the date. + /// + /// The date. + public string date { get; set; } + /// + /// Gets or sets the precip MM. + /// + /// The precip MM. + public string precipMM { get; set; } + /// + /// Gets or sets the temp max C. + /// + /// The temp max C. + public string tempMaxC { get; set; } + /// + /// Gets or sets the temp max F. + /// + /// The temp max F. + public string tempMaxF { get; set; } + /// + /// Gets or sets the temp min C. + /// + /// The temp min C. + public string tempMinC { get; set; } + /// + /// Gets or sets the temp min F. + /// + /// The temp min F. + public string tempMinF { get; set; } + /// + /// Gets or sets the weather code. + /// + /// The weather code. + public string weatherCode { get; set; } + /// + /// Gets or sets the winddir16 point. + /// + /// The winddir16 point. + public string winddir16Point { get; set; } + /// + /// Gets or sets the winddir degree. + /// + /// The winddir degree. + public string winddirDegree { get; set; } + /// + /// Gets or sets the winddirection. + /// + /// The winddirection. + public string winddirection { get; set; } + /// + /// Gets or sets the windspeed KMPH. + /// + /// The windspeed KMPH. + public string windspeedKmph { get; set; } + /// + /// Gets or sets the windspeed miles. + /// + /// The windspeed miles. + public string windspeedMiles { get; set; } + + /// + /// To the weather forecast. + /// + /// WeatherForecast. + 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) + }; + } + + /// + /// Gets the condition. + /// + /// The weather code. + /// WeatherConditions. + 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 @@ + + + + \ No newline at end of file -- cgit v1.2.3