diff options
| author | Luke <luke.pulverenti@gmail.com> | 2016-11-18 12:52:16 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-11-18 12:52:16 -0500 |
| commit | 9ff2d7590e7f34f26803cf658d20faeae6d9ecdb (patch) | |
| tree | 612092edc128b95ba04e4c309a0379ac22fcfe9c /Emby.Server.Implementations | |
| parent | 0accbd194a191ce750b48a00c8a4f00786fe187d (diff) | |
| parent | e0c72b605dbe124e111be9bff5cca442f86cf1ef (diff) | |
Merge pull request #2294 from MediaBrowser/dev
Dev
Diffstat (limited to 'Emby.Server.Implementations')
14 files changed, 1658 insertions, 5 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs new file mode 100644 index 000000000..ea9e537c9 --- /dev/null +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Activity +{ + public class ActivityRepository : BaseSqliteRepository, IActivityRepository + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths) + : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); + } + + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)", + "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)" + }; + + connection.RunQueries(queries); + } + } + + private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries"; + + public Task Create(ActivityLogEntry entry) + { + return Update(entry); + } + + public async Task Update(ActivityLogEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException("entry"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + db.Execute(commandText, + entry.Id.ToGuidParamValue(), + entry.Name, + entry.Overview, + entry.ShortOverview, + entry.Type, + entry.ItemId, + entry.UserId, + entry.Date.ToDateTimeParamValue(), + entry.Severity.ToString()); + }); + } + } + } + + public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) + { + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = BaseActivitySelectText; + + var whereClauses = new List<string>(); + var paramList = new List<object>(); + + if (minDate.HasValue) + { + whereClauses.Add("DateCreated>=@DateCreated"); + paramList.Add(minDate.Value.ToDateTimeParamValue()); + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + if (startIndex.HasValue && startIndex.Value > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", + pagingWhereText, + startIndex.Value.ToString(_usCulture))); + } + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + commandText += whereText; + + commandText += " ORDER BY DateCreated DESC"; + + if (limit.HasValue) + { + commandText += " LIMIT " + limit.Value.ToString(_usCulture); + } + + var totalRecordCount = connection.Query("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging, paramList.ToArray()).SelectScalarInt().First(); + + var list = new List<ActivityLogEntry>(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(GetEntry(row)); + } + + return new QueryResult<ActivityLogEntry>() + { + Items = list.ToArray(), + TotalRecordCount = totalRecordCount + }; + } + } + } + + private ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader) + { + var index = 0; + + var info = new ActivityLogEntry + { + Id = reader[index].ReadGuid().ToString("N") + }; + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Name = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Overview = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.ShortOverview = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Type = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.ItemId = reader[index].ToString(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.UserId = reader[index].ToString(); + } + + index++; + info.Date = reader[index].ReadDateTime(); + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), reader[index].ToString(), true); + } + + return info; + } + } +} diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs new file mode 100644 index 000000000..8febe83b2 --- /dev/null +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + public abstract class BaseSqliteRepository : IDisposable + { + protected string DbFilePath { get; set; } + protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1); + protected ILogger Logger { get; private set; } + + protected BaseSqliteRepository(ILogger logger) + { + Logger = logger; + } + + protected virtual bool EnableConnectionPooling + { + get { return true; } + } + + protected virtual SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false) + { + SQLite3.EnableSharedCache = false; + + ConnectionFlags connectionFlags; + + if (isReadOnly) + { + connectionFlags = ConnectionFlags.ReadOnly; + //connectionFlags = ConnectionFlags.Create; + //connectionFlags |= ConnectionFlags.ReadWrite; + } + else + { + connectionFlags = ConnectionFlags.Create; + connectionFlags |= ConnectionFlags.ReadWrite; + } + + if (EnableConnectionPooling) + { + connectionFlags |= ConnectionFlags.SharedCached; + } + else + { + connectionFlags |= ConnectionFlags.PrivateCache; + } + + connectionFlags |= ConnectionFlags.NoMutex; + + var db = SQLite3.Open(DbFilePath, connectionFlags, null); + + var queries = new[] + { + "PRAGMA page_size=4096", + "PRAGMA journal_mode=WAL", + "PRAGMA temp_store=memory", + "PRAGMA synchronous=Normal", + //"PRAGMA cache size=-10000" + }; + + //foreach (var query in queries) + //{ + // db.Execute(query); + //} + + db.ExecuteAll(string.Join(";", queries)); + + return db; + } + + private bool _disposed; + protected void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name + " has been disposed and cannot be accessed."); + } + } + + public void Dispose() + { + _disposed = true; + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// <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) + { + try + { + lock (_disposeLock) + { + WriteLock.Wait(); + + CloseConnection(); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error disposing database", ex); + } + } + } + + protected virtual void CloseConnection() + { + + } + + protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type) + { + foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) + { + if (row[1].SQLiteType != SQLiteType.Null) + { + var name = row[1].ToString(); + + if (string.Equals(name, columnName, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + + connection.ExecuteAll(string.Join(";", new string[] + { + "alter table " + table, + "add column " + columnName + " " + type + " NULL" + })); + } + } +} diff --git a/Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 88f4f1f81..dd32e2cbd 100644 --- a/Emby.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -22,7 +22,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; using Emby.Server.Implementations.ScheduledTasks; -namespace Emby.Server.Implementations.Persistence +namespace Emby.Server.Implementations.Data { public class CleanDatabaseScheduledTask : IScheduledTask { diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs new file mode 100644 index 000000000..79fc893f4 --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// <summary> + /// Class SQLiteDisplayPreferencesRepository + /// </summary> + public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository + { + private readonly IMemoryStreamFactory _memoryStreamProvider; + + public SqliteDisplayPreferencesRepository(ILogger logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider) + : base(logger) + { + _jsonSerializer = jsonSerializer; + _memoryStreamProvider = memoryStreamProvider; + DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); + } + + /// <summary> + /// Gets the name of the repository + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "SQLite"; + } + } + + /// <summary> + /// The _json serializer + /// </summary> + private readonly IJsonSerializer _jsonSerializer; + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists userdisplaypreferences (id GUID, userId GUID, client text, data BLOB)", + "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" + }; + + connection.RunQueries(queries); + } + } + + /// <summary> + /// Save the display preferences associated with an item in the repo + /// </summary> + /// <param name="displayPreferences">The display preferences.</param> + /// <param name="userId">The user id.</param> + /// <param name="client">The client.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException("displayPreferences"); + } + if (string.IsNullOrWhiteSpace(displayPreferences.Id)) + { + throw new ArgumentNullException("displayPreferences.Id"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + SaveDisplayPreferences(displayPreferences, userId, client, db); + }); + } + } + } + + private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) + { + var commandText = "replace into userdisplaypreferences (id, userid, client, data) values (?, ?, ?, ?)"; + var serialized = _jsonSerializer.SerializeToBytes(displayPreferences, _memoryStreamProvider); + + connection.Execute(commandText, + displayPreferences.Id.ToGuidParamValue(), + userId.ToGuidParamValue(), + client, + serialized); + } + + /// <summary> + /// Save all display preferences associated with a user in the repo + /// </summary> + /// <param name="displayPreferences">The display preferences.</param> + /// <param name="userId">The user id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public async Task SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException("displayPreferences"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + foreach (var displayPreference in displayPreferences) + { + SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); + } + }); + } + } + } + + /// <summary> + /// Gets the display preferences. + /// </summary> + /// <param name="displayPreferencesId">The display preferences id.</param> + /// <param name="userId">The user id.</param> + /// <param name="client">The client.</param> + /// <returns>Task{DisplayPreferences}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) + { + if (string.IsNullOrWhiteSpace(displayPreferencesId)) + { + throw new ArgumentNullException("displayPreferencesId"); + } + + var guidId = displayPreferencesId.GetMD5(); + + using (var connection = CreateConnection(true)) + { + var commandText = "select data from userdisplaypreferences where id = ? and userId=? and client=?"; + + var paramList = new List<object>(); + paramList.Add(guidId.ToGuidParamValue()); + paramList.Add(userId.ToGuidParamValue()); + paramList.Add(client); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return Get(row); + } + + return new DisplayPreferences + { + Id = guidId.ToString("N") + }; + } + } + + /// <summary> + /// Gets all display preferences for the given user. + /// </summary> + /// <param name="userId">The user id.</param> + /// <returns>Task{DisplayPreferences}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId) + { + var list = new List<DisplayPreferences>(); + + using (var connection = CreateConnection(true)) + { + var commandText = "select data from userdisplaypreferences where userId=?"; + + var paramList = new List<object>(); + paramList.Add(userId.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(Get(row)); + } + } + + return list; + } + + private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row) + { + using (var stream = _memoryStreamProvider.CreateNew(row[0].ToBlob())) + { + stream.Position = 0; + return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream); + } + } + + public Task SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) + { + return SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); + } + + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) + { + return GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); + } + } +}
\ No newline at end of file diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs new file mode 100644 index 000000000..d9536ae9c --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -0,0 +1,131 @@ +using System; +using System.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + public static class SqliteExtensions + { + public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries) + { + if (queries == null) + { + throw new ArgumentNullException("queries"); + } + + connection.RunInTransaction(conn => + { + //foreach (var query in queries) + //{ + // conn.Execute(query); + //} + conn.ExecuteAll(string.Join(";", queries)); + }); + } + + public static byte[] ToGuidParamValue(this string str) + { + return ToGuidParamValue(new Guid(str)); + } + + public static byte[] ToGuidParamValue(this Guid guid) + { + return guid.ToByteArray(); + } + + public static Guid ReadGuid(this IResultSetValue result) + { + return new Guid(result.ToBlob()); + } + + public static string ToDateTimeParamValue(this DateTime dateValue) + { + var kind = DateTimeKind.Utc; + + return (dateValue.Kind == DateTimeKind.Unspecified) + ? DateTime.SpecifyKind(dateValue, kind).ToString( + GetDateTimeKindFormat(kind), + CultureInfo.InvariantCulture) + : dateValue.ToString( + GetDateTimeKindFormat(dateValue.Kind), + CultureInfo.InvariantCulture); + } + + private static string GetDateTimeKindFormat( + DateTimeKind kind) + { + return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal; + } + + /// <summary> + /// An array of ISO-8601 DateTime formats that we support parsing. + /// </summary> + private static string[] _datetimeFormats = new string[] { + "THHmmssK", + "THHmmK", + "HH:mm:ss.FFFFFFFK", + "HH:mm:ssK", + "HH:mmK", + "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */ + "yyyy-MM-dd HH:mm:ssK", + "yyyy-MM-dd HH:mmK", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", + "yyyy-MM-ddTHH:mmK", + "yyyy-MM-ddTHH:mm:ssK", + "yyyyMMddHHmmssK", + "yyyyMMddHHmmK", + "yyyyMMddTHHmmssFFFFFFFK", + "THHmmss", + "THHmm", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss", + "HH:mm", + "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm", + "yyyy-MM-ddTHH:mm:ss", + "yyyyMMddHHmmss", + "yyyyMMddHHmm", + "yyyyMMddTHHmmssFFFFFFF", + "yyyy-MM-dd", + "yyyyMMdd", + "yy-MM-dd" + }; + + private static string _datetimeFormatUtc = _datetimeFormats[5]; + private static string _datetimeFormatLocal = _datetimeFormats[19]; + + public static DateTime ReadDateTime(this IResultSetValue result) + { + var dateText = result.ToString(); + + return DateTime.ParseExact( + dateText, _datetimeFormats, + DateTimeFormatInfo.InvariantInfo, + DateTimeStyles.None).ToUniversalTime(); + } + + /// <summary> + /// Serializes to bytes. + /// </summary> + /// <returns>System.Byte[][].</returns> + /// <exception cref="System.ArgumentNullException">obj</exception> + public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamFactory streamProvider) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + using (var stream = streamProvider.CreateNew()) + { + json.SerializeToStream(obj, stream); + return stream.ToArray(); + } + } + } +} diff --git a/Emby.Server.Implementations/Devices/DeviceRepository.cs b/Emby.Server.Implementations/Devices/DeviceRepository.cs new file mode 100644 index 000000000..f739765b3 --- /dev/null +++ b/Emby.Server.Implementations/Devices/DeviceRepository.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Session; + +namespace Emby.Server.Implementations.Devices +{ + public class DeviceRepository : IDeviceRepository + { + private readonly object _syncLock = new object(); + + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + private Dictionary<string, DeviceInfo> _devices; + + public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem) + { + _appPaths = appPaths; + _json = json; + _logger = logger; + _fileSystem = fileSystem; + } + + private string GetDevicesPath() + { + return Path.Combine(_appPaths.DataPath, "devices"); + } + + private string GetDevicePath(string id) + { + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + } + + public Task SaveDevice(DeviceInfo device) + { + var path = Path.Combine(GetDevicePath(device.Id), "device.json"); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + _json.SerializeToFile(device, path); + _devices[device.Id] = device; + } + return Task.FromResult(true); + } + + public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) + { + var device = GetDevice(reportedId); + + if (device == null) + { + throw new ArgumentException("No device has been registed with id " + reportedId); + } + + device.Capabilities = capabilities; + SaveDevice(device); + + return Task.FromResult(true); + } + + public ClientCapabilities GetCapabilities(string reportedId) + { + var device = GetDevice(reportedId); + + return device == null ? null : device.Capabilities; + } + + public DeviceInfo GetDevice(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + return GetDevices() + .FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + } + + public IEnumerable<DeviceInfo> GetDevices() + { + lock (_syncLock) + { + if (_devices == null) + { + _devices = new Dictionary<string, DeviceInfo>(StringComparer.OrdinalIgnoreCase); + + var devices = LoadDevices().ToList(); + foreach (var device in devices) + { + _devices[device.Id] = device; + } + } + return _devices.Values.ToList(); + } + } + + private IEnumerable<DeviceInfo> LoadDevices() + { + var path = GetDevicesPath(); + + try + { + return _fileSystem + .GetFilePaths(path, true) + .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) + .ToList() + .Select(i => + { + try + { + return _json.DeserializeFromFile<DeviceInfo>(i); + } + catch (Exception ex) + { + _logger.ErrorException("Error reading {0}", ex, i); + return null; + } + }) + .Where(i => i != null); + } + catch (IOException) + { + return new List<DeviceInfo>(); + } + } + + public Task DeleteDevice(string id) + { + var path = GetDevicePath(id); + + lock (_syncLock) + { + try + { + _fileSystem.DeleteDirectory(path, true); + } + catch (IOException) + { + } + + _devices = null; + } + + return Task.FromResult(true); + } + + public ContentUploadHistory GetCameraUploadHistory(string deviceId) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + + lock (_syncLock) + { + try + { + return _json.DeserializeFromFile<ContentUploadHistory>(path); + } + catch (IOException) + { + return new ContentUploadHistory + { + DeviceId = deviceId + }; + } + } + } + + public void AddCameraUpload(string deviceId, LocalFileInfo file) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + ContentUploadHistory history; + + try + { + history = _json.DeserializeFromFile<ContentUploadHistory>(path); + } + catch (IOException) + { + history = new ContentUploadHistory + { + DeviceId = deviceId + }; + } + + history.DeviceId = deviceId; + history.FilesUploaded.Add(file); + + _json.SerializeToFile(history, path); + } + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 13d184b59..a4f26bc60 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,6 +34,7 @@ <ItemGroup> <Compile Include="Activity\ActivityLogEntryPoint.cs" /> <Compile Include="Activity\ActivityManager.cs" /> + <Compile Include="Activity\ActivityRepository.cs" /> <Compile Include="Branding\BrandingConfigurationFactory.cs" /> <Compile Include="Channels\ChannelConfigurations.cs" /> <Compile Include="Channels\ChannelDynamicMediaSourceProvider.cs" /> @@ -49,8 +50,10 @@ <Compile Include="Connect\ConnectManager.cs" /> <Compile Include="Connect\Responses.cs" /> <Compile Include="Connect\Validator.cs" /> + <Compile Include="Data\SqliteDisplayPreferencesRepository.cs" /> <Compile Include="Devices\CameraUploadsDynamicFolder.cs" /> <Compile Include="Devices\DeviceManager.cs" /> + <Compile Include="Devices\DeviceRepository.cs" /> <Compile Include="Dto\DtoService.cs" /> <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> <Compile Include="EntryPoints\KeepServerAwake.cs" /> @@ -165,6 +168,7 @@ <Compile Include="LiveTv\TunerHosts\QueueStream.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="MediaEncoder\EncodingManager.cs" /> + <Compile Include="Migrations\IVersionMigration.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> <Compile Include="News\NewsService.cs" /> <Compile Include="Notifications\CoreNotificationTypes.cs" /> @@ -173,8 +177,11 @@ <Compile Include="Notifications\NotificationConfigurationFactory.cs" /> <Compile Include="Notifications\NotificationManager.cs" /> <Compile Include="Notifications\Notifications.cs" /> + <Compile Include="Notifications\SqliteNotificationsRepository.cs" /> <Compile Include="Notifications\WebSocketNotifier.cs" /> - <Compile Include="Persistence\CleanDatabaseScheduledTask.cs" /> + <Compile Include="Data\BaseSqliteRepository.cs" /> + <Compile Include="Data\CleanDatabaseScheduledTask.cs" /> + <Compile Include="Data\SqliteExtensions.cs" /> <Compile Include="Photos\PhotoAlbumImageProvider.cs" /> <Compile Include="Playlists\PlaylistImageProvider.cs" /> <Compile Include="Playlists\PlaylistManager.cs" /> @@ -186,6 +193,7 @@ <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" /> <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" /> <Compile Include="ScheduledTasks\SystemUpdateTask.cs" /> + <Compile Include="Security\AuthenticationRepository.cs" /> <Compile Include="Security\EncryptionManager.cs" /> <Compile Include="Security\MBLicenseFile.cs" /> <Compile Include="Security\PluginSecurityManager.cs" /> @@ -197,6 +205,7 @@ <Compile Include="Session\SessionWebSocketListener.cs" /> <Compile Include="Session\WebSocketController.cs" /> <Compile Include="Social\SharingManager.cs" /> + <Compile Include="Social\SharingRepository.cs" /> <Compile Include="Sorting\AiredEpisodeOrderComparer.cs" /> <Compile Include="Sorting\AirTimeComparer.cs" /> <Compile Include="Sorting\AlbumArtistComparer.cs" /> @@ -291,6 +300,14 @@ <HintPath>..\packages\MediaBrowser.Naming.1.0.2\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\SQLitePCL.pretty.1.1.0\lib\portable-net45+netcore45+wpa81+wp8\SQLitePCL.pretty.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> + <HintPath>..\packages\SQLitePCLRaw.core.1.1.0\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="UniversalDetector, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll</HintPath> <Private>True</Private> diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index c32af8eb1..6cd24058a 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -127,7 +127,11 @@ namespace Emby.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Key); - var options = new MetadataRefreshOptions(_fileSystem); + var options = new MetadataRefreshOptions(_fileSystem) + { + ImageRefreshMode = ImageRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.ValidationOnly + }; await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index d3e30a46b..5fa3995e6 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.TV; namespace Emby.Server.Implementations.LiveTv { @@ -130,6 +131,38 @@ namespace Emby.Server.Implementations.LiveTv dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days); + FillImages(dto, info); + + return dto; + } + + private void FillImages(SeriesTimerInfoDto dto, SeriesTimerInfo info) + { + var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(Series).Name }, + Name = info.Name, + Limit = 1, + ImageTypes = new ImageType[] { ImageType.Thumb } + + }).FirstOrDefault(); + + if (librarySeries != null) + { + var image = librarySeries.GetImageInfo(ImageType.Thumb, 0); + if (image != null) + { + try + { + dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); + dto.ParentThumbItemId = librarySeries.Id.ToString("N"); + } + catch (Exception ex) + { + } + } + } + if (!string.IsNullOrWhiteSpace(info.SeriesId)) { var program = _libraryManager.GetItemList(new InternalItemsQuery @@ -157,8 +190,6 @@ namespace Emby.Server.Implementations.LiveTv } } } - - return dto; } public DayPattern? GetDayPattern(List<DayOfWeek> days) diff --git a/Emby.Server.Implementations/Migrations/IVersionMigration.cs b/Emby.Server.Implementations/Migrations/IVersionMigration.cs new file mode 100644 index 000000000..7804912e3 --- /dev/null +++ b/Emby.Server.Implementations/Migrations/IVersionMigration.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Emby.Server.Implementations.Migrations +{ + public interface IVersionMigration + { + Task Run(); + } +} diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs new file mode 100644 index 000000000..fcf45b0ad --- /dev/null +++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Notifications; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Notifications +{ + public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository + { + public SqliteNotificationsRepository(ILogger logger, IServerApplicationPaths appPaths) : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db"); + } + + public event EventHandler<NotificationUpdateEventArgs> NotificationAdded; + public event EventHandler<NotificationReadEventArgs> NotificationsMarkedRead; + ////public event EventHandler<NotificationUpdateEventArgs> NotificationUpdated; + + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT NULL, Url TEXT NULL, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT NULL, PRIMARY KEY (Id, UserId))", + "create index if not exists idx_Notifications1 on Notifications(Id)", + "create index if not exists idx_Notifications2 on Notifications(UserId)" + }; + + connection.RunQueries(queries); + } + } + + /// <summary> + /// Gets the notifications. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>NotificationResult.</returns> + public NotificationResult GetNotifications(NotificationQuery query) + { + var result = new NotificationResult(); + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var clauses = new List<string>(); + var paramList = new List<object>(); + + if (query.IsRead.HasValue) + { + clauses.Add("IsRead=?"); + paramList.Add(query.IsRead.Value); + } + + clauses.Add("UserId=?"); + paramList.Add(query.UserId.ToGuidParamValue()); + + var whereClause = " where " + string.Join(" And ", clauses.ToArray()); + + result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray()).SelectScalarInt().First(); + + var commandText = string.Format("select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause); + + if (query.Limit.HasValue || query.StartIndex.HasValue) + { + var offset = query.StartIndex ?? 0; + + if (query.Limit.HasValue || offset > 0) + { + commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + } + + if (offset > 0) + { + commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + } + } + + var resultList = new List<Notification>(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + resultList.Add(GetNotification(row)); + } + + result.Notifications = resultList.ToArray(); + } + } + + return result; + } + + public NotificationsSummary GetNotificationsSummary(string userId) + { + var result = new NotificationsSummary(); + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select Level from Notifications where UserId=? and IsRead=?", userId.ToGuidParamValue(), false)) + { + var levels = new List<NotificationLevel>(); + + levels.Add(GetLevel(row, 0)); + + result.UnreadCount = levels.Count; + + if (levels.Count > 0) + { + result.MaxUnreadNotificationLevel = levels.Max(); + } + } + + return result; + } + } + } + + private Notification GetNotification(IReadOnlyList<IResultSetValue> reader) + { + var notification = new Notification + { + Id = reader[0].ReadGuid().ToString("N"), + UserId = reader[1].ReadGuid().ToString("N"), + Date = reader[2].ReadDateTime(), + Name = reader[3].ToString() + }; + + if (reader[4].SQLiteType != SQLiteType.Null) + { + notification.Description = reader[4].ToString(); + } + + if (reader[5].SQLiteType != SQLiteType.Null) + { + notification.Url = reader[5].ToString(); + } + + notification.Level = GetLevel(reader, 6); + notification.IsRead = reader[7].ToBool(); + + return notification; + } + + /// <summary> + /// Gets the level. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="index">The index.</param> + /// <returns>NotificationLevel.</returns> + private NotificationLevel GetLevel(IReadOnlyList<IResultSetValue> reader, int index) + { + NotificationLevel level; + + var val = reader[index].ToString(); + + Enum.TryParse(val, true, out level); + + return level; + } + + /// <summary> + /// Adds the notification. + /// </summary> + /// <param name="notification">The notification.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task AddNotification(Notification notification, CancellationToken cancellationToken) + { + await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); + + if (NotificationAdded != null) + { + try + { + NotificationAdded(this, new NotificationUpdateEventArgs + { + Notification = notification + }); + } + catch (Exception ex) + { + Logger.ErrorException("Error in NotificationAdded event handler", ex); + } + } + } + + /// <summary> + /// Replaces the notification. + /// </summary> + /// <param name="notification">The notification.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(notification.Id)) + { + notification.Id = Guid.NewGuid().ToString("N"); + } + if (string.IsNullOrEmpty(notification.UserId)) + { + throw new ArgumentException("The notification must have a user id"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + conn.Execute("replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + notification.Id.ToGuidParamValue(), + notification.UserId.ToGuidParamValue(), + notification.Date.ToDateTimeParamValue(), + notification.Name, + notification.Description, + notification.Url, + notification.Level.ToString(), + notification.IsRead, + string.Empty, + string.Empty); + }); + } + } + } + + /// <summary> + /// Marks the read. + /// </summary> + /// <param name="notificationIdList">The notification id list.</param> + /// <param name="userId">The user id.</param> + /// <param name="isRead">if set to <c>true</c> [is read].</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task MarkRead(IEnumerable<string> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) + { + var list = notificationIdList.ToList(); + var idArray = list.Select(i => new Guid(i)).ToArray(); + + await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false); + + if (NotificationsMarkedRead != null) + { + try + { + NotificationsMarkedRead(this, new NotificationReadEventArgs + { + IdList = list.ToArray(), + IsRead = isRead, + UserId = userId + }); + } + catch (Exception ex) + { + Logger.ErrorException("Error in NotificationsMarkedRead event handler", ex); + } + } + } + + public async Task MarkAllRead(string userId, bool isRead, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + conn.Execute("update Notifications set IsRead=? where UserId=?", userId.ToGuidParamValue(), isRead); + }); + } + } + } + + private async Task MarkReadInternal(IEnumerable<Guid> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(conn => + { + var userIdParam = userId.ToGuidParamValue(); + + foreach (var id in notificationIdList) + { + conn.Execute("update Notifications set IsRead=? where UserId=? and Id=?", userIdParam, isRead, id); + } + }); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs new file mode 100644 index 000000000..5179bd258 --- /dev/null +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Security +{ + public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository + { + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + : base(logger) + { + _appPaths = appPaths; + DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db"); + } + + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, AppVersion TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)" + }; + + connection.RunQueries(queries); + + connection.RunInTransaction(db => + { + AddColumn(db, "AccessTokens", "AppVersion", "TEXT"); + }); + } + } + + public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) + { + info.Id = Guid.NewGuid().ToString("N"); + + return Update(info, cancellationToken); + } + + public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + db.Execute(commandText, + info.Id.ToGuidParamValue(), + info.AccessToken, + info.DeviceId, + info.AppName, + info.AppVersion, + info.DeviceName, + info.UserId, + info.IsActive, + info.DateCreated.ToDateTimeParamValue(), + info.DateRevoked.HasValue ? info.DateRevoked.Value.ToDateTimeParamValue() : null); + }); + } + } + } + + private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var connection = CreateConnection(true)) + { + var commandText = BaseSelectText; + var paramList = new List<object>(); + + var whereClauses = new List<string>(); + + var startIndex = query.StartIndex ?? 0; + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=?"); + paramList.Add(query.AccessToken); + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=?"); + paramList.Add(query.UserId); + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=?"); + paramList.Add(query.DeviceId); + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=?"); + paramList.Add(query.IsActive.Value); + } + + if (query.HasUser.HasValue) + { + if (query.HasUser.Value) + { + whereClauses.Add("UserId not null"); + } + else + { + whereClauses.Add("UserId is null"); + } + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + if (startIndex > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", + pagingWhereText, + startIndex.ToString(_usCulture))); + } + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + commandText += whereText; + + commandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + var list = new List<AuthenticationInfo>(); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + list.Add(Get(row)); + } + + var count = connection.Query("select count (Id) from AccessTokens" + whereTextWithoutPaging, paramList.ToArray()) + .SelectScalarInt() + .First(); + + return new QueryResult<AuthenticationInfo>() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = BaseSelectText + " where Id=?"; + var paramList = new List<object>(); + + paramList.Add(id.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return Get(row); + } + return null; + } + } + } + + private AuthenticationInfo Get(IReadOnlyList<IResultSetValue> reader) + { + var info = new AuthenticationInfo + { + Id = reader[0].ReadGuid().ToString("N"), + AccessToken = reader[1].ToString() + }; + + if (reader[2].SQLiteType != SQLiteType.Null) + { + info.DeviceId = reader[2].ToString(); + } + + if (reader[3].SQLiteType != SQLiteType.Null) + { + info.AppName = reader[3].ToString(); + } + + if (reader[4].SQLiteType != SQLiteType.Null) + { + info.AppVersion = reader[4].ToString(); + } + + if (reader[5].SQLiteType != SQLiteType.Null) + { + info.DeviceName = reader[5].ToString(); + } + + if (reader[6].SQLiteType != SQLiteType.Null) + { + info.UserId = reader[6].ToString(); + } + + info.IsActive = reader[7].ToBool(); + info.DateCreated = reader[8].ReadDateTime(); + + if (reader[9].SQLiteType != SQLiteType.Null) + { + info.DateRevoked = reader[9].ReadDateTime(); + } + + return info; + } + } +} diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs new file mode 100644 index 000000000..e09b7f5b9 --- /dev/null +++ b/Emby.Server.Implementations/Social/SharingRepository.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Data; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Social; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Social +{ + public class SharingRepository : BaseSqliteRepository, ISharingRepository + { + public SharingRepository(ILogger logger, IApplicationPaths appPaths) + : base(logger) + { + DbFilePath = Path.Combine(appPaths.DataPath, "shares.db"); + } + + /// <summary> + /// Opens the connection to the database + /// </summary> + /// <returns>Task.</returns> + public void Initialize() + { + using (var connection = CreateConnection()) + { + string[] queries = { + + "create table if not exists Shares (Id GUID, ItemId TEXT, UserId TEXT, ExpirationDate DateTime, PRIMARY KEY (Id))", + "create index if not exists idx_Shares on Shares(Id)", + + "pragma shrink_memory" + }; + + connection.RunQueries(queries); + } + } + + public async Task CreateShare(SocialShareInfo info) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + if (string.IsNullOrWhiteSpace(info.Id)) + { + throw new ArgumentNullException("info.Id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + var commandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (?, ?, ?, ?)"; + + db.Execute(commandText, + info.Id.ToGuidParamValue(), + info.ItemId, + info.UserId, + info.ExpirationDate.ToDateTimeParamValue()); + }); + } + } + } + + public SocialShareInfo GetShareInfo(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + lock (WriteLock) + { + using (var connection = CreateConnection(true)) + { + var commandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = ?"; + + var paramList = new List<object>(); + paramList.Add(id.ToGuidParamValue()); + + foreach (var row in connection.Query(commandText, paramList.ToArray())) + { + return GetSocialShareInfo(row); + } + } + } + + return null; + } + + private SocialShareInfo GetSocialShareInfo(IReadOnlyList<IResultSetValue> reader) + { + var info = new SocialShareInfo(); + + info.Id = reader[0].ReadGuid().ToString("N"); + info.ItemId = reader[1].ToString(); + info.UserId = reader[2].ToString(); + info.ExpirationDate = reader[3].ReadDateTime(); + + return info; + } + + public async Task DeleteShare(string id) + { + + } + } +} diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 71fdbffc2..519e4a0ad 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -2,5 +2,7 @@ <packages> <package id="Emby.XmlTv" version="1.0.1" targetFramework="portable45-net45+win8" /> <package id="MediaBrowser.Naming" version="1.0.2" targetFramework="portable45-net45+win8" /> + <package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" /> + <package id="SQLitePCLRaw.core" version="1.1.0" targetFramework="portable45-net45+win8" /> <package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" /> </packages>
\ No newline at end of file |
