| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376 | using MediaBrowser.Common.Configuration;using MediaBrowser.Controller.Entities;using MediaBrowser.Controller.Entities.TV;using MediaBrowser.Controller.LiveTv;using MediaBrowser.Controller.Persistence;using MediaBrowser.Model.Entities;using MediaBrowser.Model.Logging;using MediaBrowser.Model.Querying;using MediaBrowser.Model.Serialization;using System;using System.Collections.Generic;using System.Data;using System.Globalization;using System.IO;using System.Linq;using System.Runtime.Serialization;using System.Threading;using System.Threading.Tasks;namespace MediaBrowser.Server.Implementations.Persistence{    /// <summary>    /// Class SQLiteItemRepository    /// </summary>    public class SqliteItemRepository : IItemRepository    {        private IDbConnection _connection;        private readonly ILogger _logger;        private readonly TypeMapper _typeMapper = new TypeMapper();        /// <summary>        /// Gets the name of the repository        /// </summary>        /// <value>The name.</value>        public string Name        {            get            {                return "SQLite";            }        }        /// <summary>        /// Gets the json serializer.        /// </summary>        /// <value>The json serializer.</value>        private readonly IJsonSerializer _jsonSerializer;        /// <summary>        /// The _app paths        /// </summary>        private readonly IApplicationPaths _appPaths;        /// <summary>        /// The _save item command        /// </summary>        private IDbCommand _saveItemCommand;        private readonly string _criticReviewsPath;        private SqliteChapterRepository _chapterRepository;        private SqliteMediaStreamsRepository _mediaStreamsRepository;        private IDbCommand _deleteChildrenCommand;        private IDbCommand _saveChildrenCommand;        private IDbCommand _deleteItemCommand;        private IDbCommand _deletePeopleCommand;        private IDbCommand _savePersonCommand;        /// <summary>        /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.        /// </summary>        /// <param name="appPaths">The app paths.</param>        /// <param name="jsonSerializer">The json serializer.</param>        /// <param name="logManager">The log manager.</param>        /// <exception cref="System.ArgumentNullException">        /// appPaths        /// or        /// jsonSerializer        /// </exception>        public SqliteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)        {            if (appPaths == null)            {                throw new ArgumentNullException("appPaths");            }            if (jsonSerializer == null)            {                throw new ArgumentNullException("jsonSerializer");            }            _appPaths = appPaths;            _jsonSerializer = jsonSerializer;            _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews");            _logger = logManager.GetLogger(GetType().Name);            var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db");            var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile, _logger).Result;            _chapterRepository = new SqliteChapterRepository(chapterConnection, logManager);            var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");            var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result;            _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager);        }        /// <summary>        /// Opens the connection to the database        /// </summary>        /// <returns>Task.</returns>        public async Task Initialize()        {            var dbFile = Path.Combine(_appPaths.DataPath, "library.db");            _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);            string[] queries = {                                "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB)",                                "create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",                                "create table if not exists ChildrenIds (ParentId GUID, ItemId GUID, PRIMARY KEY (ParentId, ItemId))",                                "create index if not exists idx_ChildrenIds on ChildrenIds(ParentId,ItemId)",                                "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",                                //pragmas                                "pragma temp_store = memory",                                "pragma shrink_memory"                               };            _connection.RunQueries(queries, _logger);            _connection.AddColumn(_logger, "TypedBaseItems", "Path", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "StartDate", "DATETIME");            _connection.AddColumn(_logger, "TypedBaseItems", "EndDate", "DATETIME");            _connection.AddColumn(_logger, "TypedBaseItems", "ChannelId", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "IsMovie", "BIT");            _connection.AddColumn(_logger, "TypedBaseItems", "IsSports", "BIT");            _connection.AddColumn(_logger, "TypedBaseItems", "IsKids", "BIT");            _connection.AddColumn(_logger, "TypedBaseItems", "CommunityRating", "Float");            _connection.AddColumn(_logger, "TypedBaseItems", "CustomRating", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "IndexNumber", "INT");            _connection.AddColumn(_logger, "TypedBaseItems", "IsLocked", "BIT");            _connection.AddColumn(_logger, "TypedBaseItems", "Name", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "OfficialRating", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "MediaType", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "Overview", "Text");            _connection.AddColumn(_logger, "TypedBaseItems", "ParentIndexNumber", "INT");            _connection.AddColumn(_logger, "TypedBaseItems", "PremiereDate", "DATETIME");            _connection.AddColumn(_logger, "TypedBaseItems", "ProductionYear", "INT");            _connection.AddColumn(_logger, "TypedBaseItems", "ParentId", "GUID");            PrepareStatements();            _mediaStreamsRepository.Initialize();            _chapterRepository.Initialize();        }        /// <summary>        /// The _write lock        /// </summary>        private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);        /// <summary>        /// Prepares the statements.        /// </summary>        private void PrepareStatements()        {            var saveColumns = new List<string>            {                "guid",                "type",                "data",				"Path",                "StartDate",                "EndDate",                "ChannelId",                "IsKids",                "IsMovie",                "IsSports",                "CommunityRating",                "CustomRating",                "IndexNumber",                "IsLocked",                "Name",                "OfficialRating",                "MediaType",                "Overview",                "ParentIndexNumber",                "PremiereDate",                "ProductionYear",                "ParentId"            };            _saveItemCommand = _connection.CreateCommand();            _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22)";            for (var i = 1; i <= saveColumns.Count; i++)            {                _saveItemCommand.Parameters.Add(_saveItemCommand, "@" + i.ToString(CultureInfo.InvariantCulture));            }            _deleteChildrenCommand = _connection.CreateCommand();            _deleteChildrenCommand.CommandText = "delete from ChildrenIds where ParentId=@ParentId";            _deleteChildrenCommand.Parameters.Add(_deleteChildrenCommand, "@ParentId");            _deleteItemCommand = _connection.CreateCommand();            _deleteItemCommand.CommandText = "delete from TypedBaseItems where guid=@Id";            _deleteItemCommand.Parameters.Add(_deleteItemCommand, "@Id");            _saveChildrenCommand = _connection.CreateCommand();            _saveChildrenCommand.CommandText = "replace into ChildrenIds (ParentId, ItemId) values (@ParentId, @ItemId)";            _saveChildrenCommand.Parameters.Add(_saveChildrenCommand, "@ParentId");            _saveChildrenCommand.Parameters.Add(_saveChildrenCommand, "@ItemId");            _deletePeopleCommand = _connection.CreateCommand();            _deletePeopleCommand.CommandText = "delete from People where ItemId=@Id";            _deletePeopleCommand.Parameters.Add(_deletePeopleCommand, "@Id");            _savePersonCommand = _connection.CreateCommand();            _savePersonCommand.CommandText = "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)";            _savePersonCommand.Parameters.Add(_savePersonCommand, "@ItemId");            _savePersonCommand.Parameters.Add(_savePersonCommand, "@Name");            _savePersonCommand.Parameters.Add(_savePersonCommand, "@Role");            _savePersonCommand.Parameters.Add(_savePersonCommand, "@PersonType");            _savePersonCommand.Parameters.Add(_savePersonCommand, "@SortOrder");            _savePersonCommand.Parameters.Add(_savePersonCommand, "@ListOrder");        }        /// <summary>        /// Save a standard item in the repo        /// </summary>        /// <param name="item">The item.</param>        /// <param name="cancellationToken">The cancellation token.</param>        /// <returns>Task.</returns>        /// <exception cref="System.ArgumentNullException">item</exception>        public Task SaveItem(BaseItem item, CancellationToken cancellationToken)        {            if (item == null)            {                throw new ArgumentNullException("item");            }            return SaveItems(new[] { item }, cancellationToken);        }        /// <summary>        /// Saves the items.        /// </summary>        /// <param name="items">The items.</param>        /// <param name="cancellationToken">The cancellation token.</param>        /// <returns>Task.</returns>        /// <exception cref="System.ArgumentNullException">        /// items        /// or        /// cancellationToken        /// </exception>        public async Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)        {            if (items == null)            {                throw new ArgumentNullException("items");            }            cancellationToken.ThrowIfCancellationRequested();            CheckDisposed();            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);            IDbTransaction transaction = null;            try            {                transaction = _connection.BeginTransaction();                foreach (var item in items)                {                    cancellationToken.ThrowIfCancellationRequested();                    var index = 0;                    _saveItemCommand.GetParameter(index++).Value = item.Id;                    _saveItemCommand.GetParameter(index++).Value = item.GetType().FullName;                    _saveItemCommand.GetParameter(index++).Value = _jsonSerializer.SerializeToBytes(item);                    _saveItemCommand.GetParameter(index++).Value = item.Path;                    var hasStartDate = item as IHasStartDate;                    if (hasStartDate != null)                    {                        _saveItemCommand.GetParameter(index++).Value = hasStartDate.StartDate;                    }                    else                    {                        _saveItemCommand.GetParameter(index++).Value = null;                    }                    _saveItemCommand.GetParameter(index++).Value = item.EndDate;                    _saveItemCommand.GetParameter(index++).Value = item.ChannelId;                    var hasProgramAttributes = item as IHasProgramAttributes;                    if (hasProgramAttributes != null)                    {                        _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsKids;                        _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsMovie;                        _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsSports;                    }                    else                    {                        _saveItemCommand.GetParameter(index++).Value = null;                        _saveItemCommand.GetParameter(index++).Value = null;                        _saveItemCommand.GetParameter(index++).Value = null;                    }                    _saveItemCommand.GetParameter(index++).Value = item.CommunityRating;                    _saveItemCommand.GetParameter(index++).Value = item.CustomRating;                    _saveItemCommand.GetParameter(index++).Value = item.IndexNumber;                    _saveItemCommand.GetParameter(index++).Value = item.IsLocked;                    _saveItemCommand.GetParameter(index++).Value = item.Name;                    _saveItemCommand.GetParameter(index++).Value = item.OfficialRating;                    _saveItemCommand.GetParameter(index++).Value = item.MediaType;                    _saveItemCommand.GetParameter(index++).Value = item.Overview;                    _saveItemCommand.GetParameter(index++).Value = item.ParentIndexNumber;                    _saveItemCommand.GetParameter(index++).Value = item.PremiereDate;                    _saveItemCommand.GetParameter(index++).Value = item.ProductionYear;                    if (item.ParentId == Guid.Empty)                    {                        _saveItemCommand.GetParameter(index++).Value = null;                    }                    else                    {                        _saveItemCommand.GetParameter(index++).Value = item.ParentId;                    }                    _saveItemCommand.Transaction = transaction;                    _saveItemCommand.ExecuteNonQuery();                }                transaction.Commit();            }            catch (OperationCanceledException)            {                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            catch (Exception e)            {                _logger.ErrorException("Failed to save items:", e);                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            finally            {                if (transaction != null)                {                    transaction.Dispose();                }                _writeLock.Release();            }        }        /// <summary>        /// Internal retrieve from items or users table        /// </summary>        /// <param name="id">The id.</param>        /// <returns>BaseItem.</returns>        /// <exception cref="System.ArgumentNullException">id</exception>        /// <exception cref="System.ArgumentException"></exception>        public BaseItem RetrieveItem(Guid id)        {            if (id == Guid.Empty)            {                throw new ArgumentNullException("id");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select type,data from TypedBaseItems where guid = @guid";                cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = id;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))                {                    if (reader.Read())                    {                        return GetItem(reader);                    }                }                return null;            }        }        private BaseItem GetItem(IDataReader reader)        {            var typeString = reader.GetString(0);            var type = _typeMapper.GetType(typeString);            if (type == null)            {                _logger.Debug("Unknown type {0}", typeString);                return null;            }            using (var stream = reader.GetMemoryStream(1))            {                try                {                    return _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;                }                catch (SerializationException ex)                {                    _logger.ErrorException("Error deserializing item", ex);                    return null;                }            }        }        /// <summary>        /// Gets the critic reviews.        /// </summary>        /// <param name="itemId">The item id.</param>        /// <returns>Task{IEnumerable{ItemReview}}.</returns>        public IEnumerable<ItemReview> GetCriticReviews(Guid itemId)        {            try            {                var path = Path.Combine(_criticReviewsPath, itemId + ".json");                return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path);            }            catch (DirectoryNotFoundException)            {                return new List<ItemReview>();            }            catch (FileNotFoundException)            {                return new List<ItemReview>();            }        }        private readonly Task _cachedTask = Task.FromResult(true);        /// <summary>        /// Saves the critic reviews.        /// </summary>        /// <param name="itemId">The item id.</param>        /// <param name="criticReviews">The critic reviews.</param>        /// <returns>Task.</returns>        public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)        {            Directory.CreateDirectory(_criticReviewsPath);            var path = Path.Combine(_criticReviewsPath, itemId + ".json");            _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);            return _cachedTask;        }        /// <summary>        /// Gets chapters for an item        /// </summary>        /// <param name="id">The id.</param>        /// <returns>IEnumerable{ChapterInfo}.</returns>        /// <exception cref="System.ArgumentNullException">id</exception>        public IEnumerable<ChapterInfo> GetChapters(Guid id)        {            CheckDisposed();            return _chapterRepository.GetChapters(id);        }        /// <summary>        /// Gets a single chapter for an item        /// </summary>        /// <param name="id">The id.</param>        /// <param name="index">The index.</param>        /// <returns>ChapterInfo.</returns>        /// <exception cref="System.ArgumentNullException">id</exception>        public ChapterInfo GetChapter(Guid id, int index)        {            CheckDisposed();            return _chapterRepository.GetChapter(id, index);        }        /// <summary>        /// Saves the chapters.        /// </summary>        /// <param name="id">The id.</param>        /// <param name="chapters">The chapters.</param>        /// <param name="cancellationToken">The cancellation token.</param>        /// <returns>Task.</returns>        /// <exception cref="System.ArgumentNullException">        /// id        /// or        /// chapters        /// or        /// cancellationToken        /// </exception>        public Task SaveChapters(Guid id, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken)        {            CheckDisposed();            return _chapterRepository.SaveChapters(id, chapters, cancellationToken);        }        /// <summary>        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }        private readonly object _disposeLock = new object();        private bool _disposed;        private void CheckDisposed()        {            if (_disposed)            {                throw new ObjectDisposedException(GetType().Name + " has been disposed and cannot be accessed.");            }        }        /// <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)            {                _disposed = true;                try                {                    lock (_disposeLock)                    {                        _writeLock.Wait();                        if (_connection != null)                        {                            if (_connection.IsOpen())                            {                                _connection.Close();                            }                            _connection.Dispose();                            _connection = null;                        }                        if (_chapterRepository != null)                        {                            _chapterRepository.Dispose();                            _chapterRepository = null;                        }                        if (_mediaStreamsRepository != null)                        {                            _mediaStreamsRepository.Dispose();                            _mediaStreamsRepository = null;                        }                    }                }                catch (Exception ex)                {                    _logger.ErrorException("Error disposing database", ex);                }            }        }        public IEnumerable<Guid> GetChildren(Guid parentId)        {            if (parentId == Guid.Empty)            {                throw new ArgumentNullException("parentId");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select ItemId from ChildrenIds where ParentId = @ParentId";                cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        yield return reader.GetGuid(0);                    }                }            }        }        public IEnumerable<BaseItem> GetChildrenItems(Guid parentId)        {            if (parentId == Guid.Empty)            {                throw new ArgumentNullException("parentId");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select type,data from TypedBaseItems where guid in (select ItemId from ChildrenIds where ParentId = @ParentId)";                cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        var item = GetItem(reader);                        if (item != null)                        {                            yield return item;                        }                    }                }            }        }        public IEnumerable<BaseItem> GetItemsOfType(Type type)        {            if (type == null)            {                throw new ArgumentNullException("type");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select type,data from TypedBaseItems where type = @type";                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        var item = GetItem(reader);                        if (item != null)                        {                            yield return item;                        }                    }                }            }        }        public QueryResult<BaseItem> GetItems(InternalItemsQuery query)        {            if (query == null)            {                throw new ArgumentNullException("query");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select type,data from TypedBaseItems";                var whereClauses = GetWhereClauses(query, cmd, false);                var whereTextWithoutPaging = whereClauses.Count == 0 ?                    string.Empty :                    " where " + string.Join(" AND ", whereClauses.ToArray());                whereClauses = GetWhereClauses(query, cmd, true);                var whereText = whereClauses.Count == 0 ?                    string.Empty :                    " where " + string.Join(" AND ", whereClauses.ToArray());                cmd.CommandText += whereText;                if (query.Limit.HasValue)                {                    cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);                }                cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;                var list = new List<BaseItem>();                var count = 0;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))                {                    while (reader.Read())                    {                        var item = GetItem(reader);                        if (item != null)                        {                            list.Add(item);                        }                    }                    if (reader.NextResult() && reader.Read())                    {                        count = reader.GetInt32(0);                    }                }                return new QueryResult<BaseItem>()                {                    Items = list.ToArray(),                    TotalRecordCount = count                };            }        }        public List<Guid> GetItemIdsList(InternalItemsQuery query)        {            if (query == null)            {                throw new ArgumentNullException("query");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select guid from TypedBaseItems";                var whereClauses = GetWhereClauses(query, cmd, true);                var whereText = whereClauses.Count == 0 ?                    string.Empty :                    " where " + string.Join(" AND ", whereClauses.ToArray());                cmd.CommandText += whereText;                if (query.Limit.HasValue)                {                    cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);                }                var list = new List<Guid>();                _logger.Debug(cmd.CommandText);                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))                {                    while (reader.Read())                    {                        list.Add(reader.GetGuid(0));                    }                }                return list;            }        }        public QueryResult<Guid> GetItemIds(InternalItemsQuery query)        {            if (query == null)            {                throw new ArgumentNullException("query");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select guid from TypedBaseItems";                var whereClauses = GetWhereClauses(query, cmd, false);                var whereTextWithoutPaging = whereClauses.Count == 0 ?                    string.Empty :                    " where " + string.Join(" AND ", whereClauses.ToArray());                whereClauses = GetWhereClauses(query, cmd, true);                var whereText = whereClauses.Count == 0 ?                    string.Empty :                    " where " + string.Join(" AND ", whereClauses.ToArray());                cmd.CommandText += whereText;                if (query.Limit.HasValue)                {                    cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);                }                cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;                var list = new List<Guid>();                var count = 0;                _logger.Debug(cmd.CommandText);                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))                {                    while (reader.Read())                    {                        list.Add(reader.GetGuid(0));                    }                    if (reader.NextResult() && reader.Read())                    {                        count = reader.GetInt32(0);                    }                }                return new QueryResult<Guid>()                {                    Items = list.ToArray(),                    TotalRecordCount = count                };            }        }        private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging)        {            var whereClauses = new List<string>();            if (query.IsMovie.HasValue)            {                whereClauses.Add("IsMovie=@IsMovie");                cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;            }            if (query.IsKids.HasValue)            {                whereClauses.Add("IsKids=@IsKids");                cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids;            }            if (query.IsSports.HasValue)            {                whereClauses.Add("IsSports=@IsSports");                cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;            }            var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();            if (includeTypes.Length == 1)            {                whereClauses.Add("type=@type");                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = includeTypes[0];            }            if (includeTypes.Length > 1)            {                var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'").ToArray());                whereClauses.Add(string.Format("type in ({0})", inClause));            }            if (query.ChannelIds.Length == 1)            {                whereClauses.Add("ChannelId=@ChannelId");                cmd.Parameters.Add(cmd, "@ChannelId", DbType.String).Value = query.ChannelIds[0];            }            if (query.ChannelIds.Length > 1)            {                var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray());                whereClauses.Add(string.Format("ChannelId in ({0})", inClause));            }            if (query.MinEndDate.HasValue)            {                whereClauses.Add("EndDate>=@MinEndDate");                cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = query.MinEndDate.Value;            }            if (query.MaxEndDate.HasValue)            {                whereClauses.Add("EndDate<=@MaxEndDate");                cmd.Parameters.Add(cmd, "@MaxEndDate", DbType.Date).Value = query.MaxEndDate.Value;            }            if (query.MinStartDate.HasValue)            {                whereClauses.Add("StartDate>=@MinStartDate");                cmd.Parameters.Add(cmd, "@MinStartDate", DbType.Date).Value = query.MinStartDate.Value;            }            if (query.MaxStartDate.HasValue)            {                whereClauses.Add("StartDate<=@MaxStartDate");                cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = query.MaxStartDate.Value;            }            if (query.IsAiring.HasValue)            {                if (query.IsAiring.Value)                {                    whereClauses.Add("StartDate<=@MaxStartDate");                    cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = DateTime.UtcNow;                    whereClauses.Add("EndDate>=@MinEndDate");                    cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = DateTime.UtcNow;                }                else                {                    whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");                    cmd.Parameters.Add(cmd, "@IsAiringDate", DbType.Date).Value = DateTime.UtcNow;                }            }            if (!string.IsNullOrWhiteSpace(query.Person))            {                whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");                cmd.Parameters.Add(cmd, "@PersonName", DbType.String).Value = query.Person;            }            if (addPaging)            {                if (query.StartIndex.HasValue && query.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 TypedBaseItems {0} ORDER BY DateCreated DESC LIMIT {1})",                        pagingWhereText,                        query.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));                }            }            return whereClauses;        }        // Not crazy about having this all the way down here, but at least it's in one place        readonly Dictionary<string, string[]> _types = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)            {                {typeof(LiveTvProgram).Name, new []{typeof(LiveTvProgram).FullName}},                {typeof(LiveTvChannel).Name, new []{typeof(LiveTvChannel).FullName}},                {typeof(LiveTvVideoRecording).Name, new []{typeof(LiveTvVideoRecording).FullName}},                {typeof(LiveTvAudioRecording).Name, new []{typeof(LiveTvAudioRecording).FullName}},                {typeof(Series).Name, new []{typeof(Series).FullName}},                {"Recording", new []{typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName}}            };        private IEnumerable<string> MapIncludeItemTypes(string value)        {            string[] result;            if (_types.TryGetValue(value, out result))            {                return result;            }            return new[] { value };        }        public IEnumerable<Guid> GetItemIdsOfType(Type type)        {            if (type == null)            {                throw new ArgumentNullException("type");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select guid from TypedBaseItems where type = @type";                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        yield return reader.GetGuid(0);                    }                }            }        }        public async Task DeleteItem(Guid id, CancellationToken cancellationToken)        {            if (id == Guid.Empty)            {                throw new ArgumentNullException("id");            }            CheckDisposed();            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);            IDbTransaction transaction = null;            try            {                transaction = _connection.BeginTransaction();                // First delete children                _deleteChildrenCommand.GetParameter(0).Value = id;                _deleteChildrenCommand.Transaction = transaction;                _deleteChildrenCommand.ExecuteNonQuery();                // Delete people                _deletePeopleCommand.GetParameter(0).Value = id;                _deletePeopleCommand.Transaction = transaction;                _deletePeopleCommand.ExecuteNonQuery();                // Delete the item                _deleteItemCommand.GetParameter(0).Value = id;                _deleteItemCommand.Transaction = transaction;                _deleteItemCommand.ExecuteNonQuery();                transaction.Commit();            }            catch (OperationCanceledException)            {                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            catch (Exception e)            {                _logger.ErrorException("Failed to save children:", e);                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            finally            {                if (transaction != null)                {                    transaction.Dispose();                }                _writeLock.Release();            }        }        public async Task SaveChildren(Guid parentId, IEnumerable<Guid> children, CancellationToken cancellationToken)        {            if (parentId == Guid.Empty)            {                throw new ArgumentNullException("parentId");            }            if (children == null)            {                throw new ArgumentNullException("children");            }            CheckDisposed();            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);            IDbTransaction transaction = null;            try            {                transaction = _connection.BeginTransaction();                // First delete                 _deleteChildrenCommand.GetParameter(0).Value = parentId;                _deleteChildrenCommand.Transaction = transaction;                _deleteChildrenCommand.ExecuteNonQuery();                foreach (var id in children)                {                    cancellationToken.ThrowIfCancellationRequested();                    _saveChildrenCommand.GetParameter(0).Value = parentId;                    _saveChildrenCommand.GetParameter(1).Value = id;                    _saveChildrenCommand.Transaction = transaction;                    _saveChildrenCommand.ExecuteNonQuery();                }                transaction.Commit();            }            catch (OperationCanceledException)            {                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            catch (Exception e)            {                _logger.ErrorException("Failed to save children:", e);                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            finally            {                if (transaction != null)                {                    transaction.Dispose();                }                _writeLock.Release();            }        }        public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)        {            CheckDisposed();            return _mediaStreamsRepository.GetMediaStreams(query);        }        public Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)        {            CheckDisposed();            return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken);        }        public List<string> GetPeopleNames(InternalPeopleQuery query)        {            if (query == null)            {                throw new ArgumentNullException("query");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select Distinct Name from People";                var whereClauses = GetPeopleWhereClauses(query, cmd);                if (whereClauses.Count > 0)                {                    cmd.CommandText += "  where " + string.Join(" AND ", whereClauses.ToArray());                }                cmd.CommandText += " order by ListOrder";                var list = new List<string>();                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        list.Add(reader.GetString(0));                    }                }                return list;            }        }        public List<PersonInfo> GetPeople(InternalPeopleQuery query)        {            if (query == null)            {                throw new ArgumentNullException("query");            }            CheckDisposed();            using (var cmd = _connection.CreateCommand())            {                cmd.CommandText = "select ItemId, Name, Role, PersonType, SortOrder from People";                var whereClauses = GetPeopleWhereClauses(query, cmd);                if (whereClauses.Count > 0)                {                    cmd.CommandText += "  where " + string.Join(" AND ", whereClauses.ToArray());                }                cmd.CommandText += " order by ListOrder";                var list = new List<PersonInfo>();                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))                {                    while (reader.Read())                    {                        list.Add(GetPerson(reader));                    }                }                return list;            }        }        private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IDbCommand cmd)        {            var whereClauses = new List<string>();            if (query.ItemId != Guid.Empty)            {                whereClauses.Add("ItemId=@ItemId");                cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = query.ItemId;            }            if (query.AppearsInItemId != Guid.Empty)            {                whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");                cmd.Parameters.Add(cmd, "@AppearsInItemId", DbType.Guid).Value = query.AppearsInItemId;            }            if (query.PersonTypes.Count == 1)            {                whereClauses.Add("PersonType=@PersonType");                cmd.Parameters.Add(cmd, "@PersonType", DbType.String).Value = query.PersonTypes[0];            }            if (query.PersonTypes.Count > 1)            {                var val = string.Join(",", query.PersonTypes.Select(i => "'" + i + "'").ToArray());                whereClauses.Add("PersonType in (" + val + ")");            }            if (query.ExcludePersonTypes.Count == 1)            {                whereClauses.Add("PersonType<>@PersonType");                cmd.Parameters.Add(cmd, "@PersonType", DbType.String).Value = query.ExcludePersonTypes[0];            }            if (query.ExcludePersonTypes.Count > 1)            {                var val = string.Join(",", query.ExcludePersonTypes.Select(i => "'" + i + "'").ToArray());                whereClauses.Add("PersonType not in (" + val + ")");            }            if (query.MaxListOrder.HasValue)            {                whereClauses.Add("ListOrder<=@MaxListOrder");                cmd.Parameters.Add(cmd, "@MaxListOrder", DbType.Int32).Value = query.MaxListOrder.Value;            }            if (!string.IsNullOrWhiteSpace(query.NameContains))            {                whereClauses.Add("Name like @NameContains");                cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%"+query.NameContains+"%";            }            return whereClauses;        }        public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)        {            if (itemId == Guid.Empty)            {                throw new ArgumentNullException("itemId");            }            if (people == null)            {                throw new ArgumentNullException("people");            }            CheckDisposed();            var cancellationToken = CancellationToken.None;            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);            IDbTransaction transaction = null;            try            {                transaction = _connection.BeginTransaction();                // First delete                 _deletePeopleCommand.GetParameter(0).Value = itemId;                _deletePeopleCommand.Transaction = transaction;                _deletePeopleCommand.ExecuteNonQuery();                var listIndex = 0;                foreach (var person in people)                {                    cancellationToken.ThrowIfCancellationRequested();                    _savePersonCommand.GetParameter(0).Value = itemId;                    _savePersonCommand.GetParameter(1).Value = person.Name;                    _savePersonCommand.GetParameter(2).Value = person.Role;                    _savePersonCommand.GetParameter(3).Value = person.Type;                    _savePersonCommand.GetParameter(4).Value = person.SortOrder;                    _savePersonCommand.GetParameter(5).Value = listIndex;                    _savePersonCommand.Transaction = transaction;                    _savePersonCommand.ExecuteNonQuery();                    listIndex++;                }                transaction.Commit();            }            catch (OperationCanceledException)            {                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            catch (Exception e)            {                _logger.ErrorException("Failed to save people:", e);                if (transaction != null)                {                    transaction.Rollback();                }                throw;            }            finally            {                if (transaction != null)                {                    transaction.Dispose();                }                _writeLock.Release();            }        }        private PersonInfo GetPerson(IDataReader reader)        {            var item = new PersonInfo();            item.ItemId = reader.GetGuid(0);            item.Name = reader.GetString(1);            if (!reader.IsDBNull(2))            {                item.Role = reader.GetString(2);            }            if (!reader.IsDBNull(3))            {                item.Type = reader.GetString(3);            }            if (!reader.IsDBNull(4))            {                item.SortOrder = reader.GetInt32(4);            }            return item;        }    }}
 |