/// <summary> /// Takes Bleb channel names and populates the TvChannels table /// </summary> /// <param name="tag"></param> /// <param name="blebChannelNames"></param> /// <param name="days"></param> /// <param name="channelHandler"></param> /// <param name="handler"></param> /// <returns></returns> public void ImportChannels(TvProgramBroadCastListingImport tag, IEnumerable<string> blebChannelNames, AddTvChannelSourceInfoHandler channelHandler) { var importer = new BlebXmltvProvider(); var channelNames = blebChannelNames.ToArray(); int[] days = new[] { 0 }; var results = importer.GetFeedResults(blebChannelNames, days).Result; if (results.channel.Length != channelNames.Length) throw new InvalidOperationException("Got wrong number of result channels so can't import pair them up with names"); // look through the response and create each channel for (int i = 0; i < channelNames.Length; i++) { var channel = results.channel[i]; channelHandler(tag, new AddTvChannelInfo { DisplayName = channel.displayname.Trim(), SourceName = channelNames[i], Uri = channel.id }); } }
/// <summary> /// This method basically does what it can to possibly sanitise the input. /// Tries to match a program, extract a series and episode number, extract attributes like S for subtitles etc. /// It should be specific to each feed probably. The regexes are quite specific thouogh. /// </summary> /// <param name="tag"></param> /// <param name="handler"></param> /// <param name="channelUriLookup"></param> /// <param name="results"></param> private void AddPrograms(TvProgramBroadCastListingImport tag, AddTvBroadCastListingInfoHandler handler, Dictionary<string, TvChannelListingSource> channelUriLookup, tvProgramme[] results) { foreach (var program in results) { // the xmltv sometimes have missing end times but these are recognised by start == stop var start = BlebXmltvProvider.ParseDateStringToUtc(program.start); DateTime? end = BlebXmltvProvider.ParseDateStringToUtc(program.stop); // we use a null in the DB if we don't know the end time if (start == end) { end = null; } // try and clean up the program titles etc. var programTitle = program.title.Value; var episodeTitle = program.subtitle?.Value; var episodeDescription = program.desc?.Value; // and see if we can extract any of this metadata int? seriesNumber = null; int? episodeNumber = null; BroadCastListingAttributes attribs = 0; // try and clean up the program titles etc. if (episodeDescription != null) { // the bleb import feed titles sometimes run into the descriptions using ...s if (programTitle.EndsWith("...") && episodeDescription.StartsWith("...")) { // find when the runon ends, ignoring the start ... var endOfRunOn = episodeDescription.IndexOf('.', 3); if (endOfRunOn != -1) { // trim out the ...s and append the episode description programTitle = programTitle.Substring(0, programTitle.Length - 3) + ' ' + episodeDescription.Substring(3, endOfRunOn - 3); // then trim off title from the start of the description, and the full stop and any spaces episodeDescription = episodeDescription.Substring(endOfRunOn + 1).Trim(' '); } } // do a bit of basic sanitisation (necessary for the runons above! but shouldn't be harmful here) programTitle = programTitle.Trim(' ', '.'); // AlsoInHdRegex try { var match = AlsoInHdRegex.Match(episodeDescription); if (match.Success) { attribs = attribs | BroadCastListingAttributes.AvailableInHD; episodeDescription = match.Groups["pre"].Value + match.Groups["post"].Value; } } catch { } // BBCAttribsRegex try { // then try and pull out BBC style attribs like HD, subtitles, audio descriptions like [S, AD] var match = BBCAttribsRegex.Match(episodeDescription); if (match.Success) { if (match.Groups["attribs"].Success) { var strings = match.Groups["attribs"].Value.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries); if (strings.Contains("S")) attribs = attribs | BroadCastListingAttributes.Subtitles; if (strings.Contains("AD")) attribs = attribs | BroadCastListingAttributes.AudioDescription; } episodeDescription = match.Groups["pre"].Value + match.Groups["post"].Value; } } catch { } try { // then try and pull out some episode information with the N/N format var match = BBCEpisodeRegex.Match(episodeDescription); if (match.Success) { episodeNumber = int.Parse(match.Groups["episode"].Value); episodeDescription = match.Groups["post"].Value; } else { // and then try SN, EN format match = OtherEpisodeRegex.Match(episodeDescription); if (match.Success) { seriesNumber = int.Parse(match.Groups["series"].Value); episodeNumber = int.Parse(match.Groups["episode"].Value); episodeDescription = match.Groups["pre"].Value + match.Groups["post"].Value; } } } // ignore any metadata that fails extraction but still save the rest catch (ArgumentNullException) { } catch (FormatException) { } } // and remove any double spaces we might have introduced episodeDescription = episodeDescription?.Replace(" ", " ").Trim(); try { handler( tag, channelUriLookup[program.channel], new AddBroadcastListingInfo { StartAt = start, EndAt = end, ProgramTitle = programTitle, EpisodeTitle = episodeTitle, EpisodeDescription = episodeDescription, SeriesNumber = seriesNumber, EpisodeNumber = episodeNumber, Attributes = (int)attribs } ); } catch (TvBroadCastListingAlreadyExistsException) { // the feed just violated the PK for this entry, carry on with the next one continue; } } }
/// <summary> /// Creates a TvBroadcastListing /// </summary> /// <param name="importTag"></param> /// <param name="channelSource"></param> /// <param name="info"></param> /// <returns></returns> public TvProgramBroadCastListing AddBroadcastListing(TvProgramBroadCastListingImport importTag, TvChannelListingSource channelSource, IAddTvBroadCastListingInfo info) { // check if this import's going to violate the PK by trying to import duplicate Listings (on the same ImportId) if (CheckForViolatedPK(importTag, channelSource, info) != null) throw new TvBroadCastListingAlreadyExistsException( string.Format( "An item with that PK (import, channel, startat) {{{0}, {1}, {2}}} already exists", importTag.TvProgramBroadCastListingImportId, channelSource.TvChannelId, info.StartAt ) ); // clean up any whitespace on the text fields and replace as nulls where possible info.ProgramTitle = info.ProgramTitle.Trim(); info.EpisodeTitle = string.IsNullOrWhiteSpace(info.EpisodeTitle) ? null : info.EpisodeTitle.Trim(); info.EpisodeDescription = string.IsNullOrWhiteSpace(info.EpisodeDescription) ? null : info.EpisodeDescription.Trim(); // try and find or generate program information from this listing TvProgram program = GetOrCreateTvProgramFromListingInfo(info); // try and find or generate episode information from this listing TvProgramEpisode episode = GetOrCreateTvProgramEpisodeFromListingInfo(program, info); // if we don't have an EndDate we can't create a listing if (info.EndAt == null || info.StartAt == info.EndAt) return null; // see if there's an existing listing for this channel with these exact times var existing = FindExistingListing(channelSource, info.StartAt, info.EndAt.Value); if (existing != null) { // check if it's the same in every way except the importer if( // TvChannelId, StartAt, EndAt, Superceded==false are done by the DB existing.ProgramTitle == info.ProgramTitle && existing.EpisodeTitle == info.EpisodeTitle && existing.EpisodeDescription == info.EpisodeDescription && existing.TvProgramId == program.TvProgramId && existing.TvProgramEpisodeId == episode?.TvProgramEpisodeId && existing.Attributes == info.Attributes ) { // already an exact match, return that instead return existing; } } // create and add the new listing var newItem = new TvProgramBroadCastListing { TvProgramBroadCastListingImportId = importTag.TvProgramBroadCastListingImportId, TvChannelId = channelSource.TvChannelId, StartAt = info.StartAt, EndAt = info.EndAt, ProgramTitle = info.ProgramTitle, EpisodeTitle = info.EpisodeTitle, EpisodeDescription = info.EpisodeDescription, TvProgramId = program?.TvProgramId, TvProgramEpisodeId = episode?.TvProgramEpisodeId, Attributes = info.Attributes, Superceded = false }; TvListings.Add(newItem); // if we had an existing item that wasn't an exact match we mark it as superceded since we've just added a new one if (existing != null) { existing.Superceded = true; TvListings.Update(existing); } // return the new item we just made return newItem; }
/// <summary> /// Performs an update for the specified channels for the specified days! /// </summary> /// <param name="tag"></param> /// <param name="channels"></param> /// <param name="days"></param> /// <param name="handler"></param> /// <returns></returns> public void ImportPrograms(TvProgramBroadCastListingImport tag, IEnumerable<TvChannelListingSource> channels, IEnumerable<int> days, AddTvBroadCastListingInfoHandler handler) { var importer = new BlebXmltvProvider(); // get a list of the source names to pass to the importer var blebChannelNames = channels.Select(x => x.SourceChannelName); var results = importer.GetFeedResults(blebChannelNames, days).Result; // create a dictionary of channel URI -> TvChannel for the importer to find the TvChannelId from the URI var channelUriLookup = channels.ToDictionary(x => x.SourceChannelUri, x => x); // add each program if (results.programme != null) { AddPrograms(tag, handler, channelUriLookup, results.programme); } }
/// <summary> /// Checks for an exact PK match on local changes only /// </summary> /// <param name="importTag"></param> /// <param name="channelSource"></param> /// <param name="info"></param> /// <returns></returns> protected TvProgramBroadCastListing CheckForViolatedPK(TvProgramBroadCastListingImport importTag, TvChannelListingSource channelSource, IAddTvBroadCastListingInfo info) { // in-memory dupe detection to stop duplicate PKs generated by broken feed / import processes var local = ChangeTracker.Entries<TvProgramBroadCastListing>() .SingleOrDefault(x => x.Property(y => y.TvProgramBroadCastListingImportId).CurrentValue == importTag.TvProgramBroadCastListingImportId && x.Property(y => y.TvChannelId).CurrentValue == channelSource.TvChannelId && x.Property(y => y.StartAt).CurrentValue == info.StartAt ); if (local != null) return local.Entity; // we don't need to check the database here because this TvProgramBroadCastListingImportId has been created by this thread return null; }
public void MarkImportFailed(TvProgramBroadCastListingImport tag, string error) { var import = Imports.Single(x => x.TvProgramBroadCastListingImportId == tag.TvProgramBroadCastListingImportId); import.Running = false; import.End = DateTime.UtcNow; import.Error = error; Imports.Update(import); SaveChanges(); }
public void MarkImportComplete(TvProgramBroadCastListingImport tag, DateTime publishDateUtc) { tag.Running = false; tag.End = DateTime.UtcNow; tag.PublishDate = publishDateUtc; Imports.Update(tag); SaveChanges(); }
public TvProgramBroadCastListingImport CreateImportTag(TvListingSource source) { var import = new TvProgramBroadCastListingImport { ListingSource = source, Start = DateTime.UtcNow, Running = true }; Imports.Add(import); SaveChanges(); return import; }
/// <summary> /// Locates or creates a TvChannel and its corresponding TvChannelListingSource /// </summary> /// <param name="import"></param> /// <param name="info"></param> /// <returns></returns> public TvChannelListingSource AddTvChannelListingSource(TvProgramBroadCastListingImport import, IAddTvChannelSourceInfo info) { // try and find a matching channel by uri var local = ChangeTracker.Entries<TvChannelListingSource>().SingleOrDefault(x => x.Property(y => y.SourceChannelUri).CurrentValue == info.Uri); if (local != null) return local.Entity; // try and find a matching channel by uri var remote = TvChannelListingSources.SingleOrDefault(x => x.SourceChannelUri == info.Uri); if (remote != null) return remote; // add a channel with that displayname var newChan = new TvChannel { ChannelName = info.DisplayName.Trim() }; TvChannels.Add(newChan); // and a link between it and the source with the info it needs for future requests var newSource = new TvChannelListingSource { SourceChannelUri = info.Uri, SourceChannelName = info.SourceName }; TvChannelListingSources.Add(newSource); return newSource; }