/// <summary> /// Gets season/episode information from database. /// </summary> /// <param name="show">Show to load episode information into</param> public void FullUpdate(TvShow show) { // Check for invalid ID if (show.Id <= 0) return; // Try multiple times - databases requests tend to fail randomly for (int i = 0; i < 5; i++) if (Update(show)) { // Remove episodes that are no longer in the TvDb for (int k = show.Episodes.Count - 1; k >= 0; k--) { TvEpisode ep = show.Episodes[k]; if (!ep.InDatabase && !ep.UserDefined && !ep.PreventDatabaseUpdates) show.Episodes.Remove(ep); } // Update missing episodes for show show.UpdateMissing(); show.LastUpdated = DateTime.Now; break; } }
/// <summary> /// Match a folder path to show in database /// </summary> /// <param name="rootFolder">Root folder content will belong to</param> /// <param name="path">Current path of content</param> /// <returns>Show from database that was matched, null if no match</returns> public bool PathMatch(string rootFolder, string path, bool fast, bool threaded, out TvShow match) { Content contentMatch; bool results = base.PathMatch(rootFolder, path, fast, threaded, out contentMatch, null); match = new TvShow(contentMatch); match.UpdateInfoFromDatabase(); return results; }
/// <summary> /// Attempts to match string to show from the online database. /// </summary> /// <param name="search">Search string to match against</param> /// <param name="rootFolder">The root folder the content will belong to</param> /// <param name="folderPath">Folder path where the content should be moved to</param> /// <param name="threaded">Whether search is threaded, setting to false can help with debugging</param> /// <returns>Match show item, null if no match</returns> public bool ContentMatch(string search, string rootFolder, string folderPath, bool fast, bool threaded, out TvShow match) { Content contentMatch; bool results = base.ContentMatch(search, rootFolder, folderPath, fast, threaded, out contentMatch, null); match = new TvShow(contentMatch); if (results) match.UpdateInfoFromDatabase(); return results; }
/// <summary> /// Constructor for episode with known properties. /// </summary> /// <param name="name">Name of the episode</param> /// <param name="show">Name of show the episode belongs to</param> /// <param name="season">Season number the episode belongs to</param> /// <param name="number">Number of the episode within the season</param> /// <param name="air">Air data string</param> /// <param name="overview">Episdoe overview/description</param> public TvEpisode(String name, TvShow show, int season, int number, string air, string overview) : this(show) { this.DatabaseName = name; this.Season = season; this.DatabaseNumber = number; DateTime airDate; DateTime.TryParse(air, out airDate); this.DatabaseAirDate = airDate; this.DatabaseOverview = overview; }
/// <summary> /// Attempts to match string to show from the online database. /// </summary> /// <param name="search">Search string to match against</param> /// <param name="rootFolder">The root folder the content will belong to</param> /// <param name="folderPath">Folder path where the content should be moved to</param> /// <param name="threaded">Whether search is threaded, setting to false can help with debugging</param> /// <returns>Match show item, null if no match</returns> public bool ContentMatch(string search, string rootFolder, string folderPath, bool fast, bool threaded, out TvShow match) { Content contentMatch; bool results = base.ContentMatch(search, rootFolder, folderPath, fast, threaded, out contentMatch, null); match = new TvShow(contentMatch); if (results) { match.UpdateInfoFromDatabase(); } return(results); }
/// <summary> /// Attempts to find matches between a file name and name of this instance /// </summary> /// <param name="fileName">File name to match to</param> /// <returns>Collection of matches for file name and content<</returns> public MatchCollection MatchFileToContent(string fileName) { List <string> names = new List <string>(); names.Add(this.DatabaseName); // Load in alternate name for TV shows if (this is TvShow) { TvShow show = (TvShow)this; foreach (string altName in show.AlternativeNameMatches) { names.Add(altName); } } // Try to match to each name foreach (string name in names) { // Build regular expression to match content to - try first without removing whitespace string re = BuildNameRegularExpresionString(true, name); MatchCollection matches = null; if (!string.IsNullOrEmpty(re)) { matches = Regex.Matches(FileHelper.SimplifyFileName(System.IO.Path.GetFileNameWithoutExtension(FileHelper.GetSafeFileName(fileName))), re, RegexOptions.IgnoreCase); } // Return matches if found if (matches != null && matches.Count > 0) { return(matches); } // Build regular expression to match content to - this time with removed whitespace re = BuildNameRegularExpresionString(false, name); if (!string.IsNullOrEmpty(re)) { matches = Regex.Matches(FileHelper.SimplifyFileName(System.IO.Path.GetFileNameWithoutExtension(fileName)), re, RegexOptions.IgnoreCase); } // Return matches if found if (matches != null && matches.Count > 0) { return(matches); } } return(null); }
/// <summary> /// Performs search for show in database. Should be overriden /// </summary> /// <param name="mirror">Mirror to use</param> /// <param name="searchString">Search string for show</param> /// <param name="includeSummaries">Whether to include summaries in search results (takes longer - set to false unless user is seeing them)</param> /// <returns>Results as list of shows</returns> protected override List <Content> DoSearch(string mirror, string searchString, bool includeSummaries) { // Init results List <Content> searchResults = new List <Content>(); // Get search results from databasse string seriesLookupUrl = mirror + "/search.php?key=" + this.API_KEY + "&show=" + searchString; WebClient webClient = new WebClient(); string seriesList = webClient.DownloadString(seriesLookupUrl); // Create xml object with text from mirrors url XmlDocument seriesDoc = new XmlDocument(); seriesDoc.InnerXml = seriesList; // Get root element and children XmlElement root = seriesDoc.DocumentElement; XmlNodeList nodes = root.ChildNodes; // Go through each node and get parse into shows for (int i = 0; i < nodes.Count; i++) { // Create show TvShow searchResult = ParseShowInfo(nodes[i]); // Get summaries for each resulting show - this may be slow! if (includeSummaries) { // Get show info from database string showInfoUrl = mirror + "/showinfo.php?key=" + this.API_KEY + "&sid=" + searchResult.Id; string showInfo = webClient.DownloadString(showInfoUrl); // Create XML object XmlDocument showInfoDoc = new XmlDocument(); showInfoDoc.InnerXml = showInfo; // Parse show info from XML searchResult = ParseShowInfo(showInfoDoc.DocumentElement); } // Add parsed show to results searchResult.DatabaseSelection = (int)TvDataBaseSelection.TvRage; searchResults.Add(searchResult); } // Return results return(searchResults); }
/// <summary> /// Parse show properties from show XML node from database /// </summary> /// <param name="node">XML node to parse info from</param> /// <returns>Parse TvShow object</returns> private static TvShow ParseShowInfo(XmlNode node) { // Init show TvShow show = new TvShow(); // Parse properties out from child nodes XmlNodeList seriesNodes = node.ChildNodes; foreach (XmlNode subNode in seriesNodes) { switch (subNode.Name.ToLower()) { case "showname": case "name": show.DatabaseName = subNode.InnerText; break; case "showid": int id; int.TryParse(subNode.InnerText, out id); show.Id = id; break; case "started": int year; int.TryParse(subNode.InnerText, out year); if (year == 0) { year = 1; } show.DatabaseYear = year; break; case "genres": show.DatabaseGenres = new GenreCollection(GenreCollection.CollectionType.Tv); foreach (XmlNode genreNode in subNode.ChildNodes) { show.DatabaseGenres.Add(genreNode.InnerText); } break; case "summary": show.Overview = subNode.InnerText.Replace('\n', ' '); break; } } return(show); }
public EpisodeCollectionControlViewModel(ObservableCollection<TvEpisode> episodes, TvShow show = null ) { this.show = show; List<TvEpisodeFilter> filters = TvEpisodeFilter.BuildFilters(show, show == null); this.EpisodeFilters = new ObservableCollection<TvEpisodeFilter>(); foreach (TvEpisodeFilter filter in filters) this.EpisodeFilters.Add(filter); if (this.EpisodeFilters.Count > 0) this.SelectedEpisodeFilter = this.EpisodeFilters[0]; this.EpisodesCollectionView = CollectionViewSource.GetDefaultView(episodes); this.EpisodesCollectionView.Filter = new Predicate<object>(FilterEpisode); // TODO: this make loading the control slow as f**k EnableLiveFiltering(); this.Groupings = new ObservableCollection<TvGroupingType>(); this.Groupings.Add(TvGroupingType.None); if (show == null) { this.Groupings.Add(TvGroupingType.Show); this.DisplayOverview = false; } else { this.Groupings.Add(TvGroupingType.Season); this.DisplayOverview = true; } this.Groupings.Add(TvGroupingType.Status); if (show == null) { this.SelectedGrouping = TvGroupingType.Show; this.DisplayShowName = Visibility.Visible; } else { this.SelectedGrouping = TvGroupingType.Season; this.DisplayShowName = Visibility.Collapsed; } }
/// <summary> /// Build array of possible episode filters that can be used for a TvShow. /// </summary> /// <param name="show">The show to build filters for</param> /// <param name="displayIgnored">Whether to add ignored season filters</param> /// <returns></returns> public static List<TvEpisodeFilter> BuildFilters(TvShow show, bool schedule) { List<TvEpisodeFilter> filters = new List<TvEpisodeFilter>(); filters.Add(new TvEpisodeFilter(FilterType.Regular, 0)); filters.Add(new TvEpisodeFilter(FilterType.All, 0)); if (!schedule) filters.Add(new TvEpisodeFilter(FilterType.Ignored, 0)); filters.Add(new TvEpisodeFilter(FilterType.Missing, 0)); filters.Add(new TvEpisodeFilter(FilterType.Located, 0)); filters.Add(new TvEpisodeFilter(FilterType.InScanDir, 0)); if (!schedule) filters.Add(new TvEpisodeFilter(FilterType.Unaired, 0)); if (!schedule) foreach (int season in show.Seasons) filters.Add(new TvEpisodeFilter(FilterType.Season, season)); return filters; }
public override bool Equals(object obj) { if (!(obj is TvShow)) { return(false); } if (!base.Equals(obj)) { return(false); } TvShow show = obj as TvShow; if (this.IncludeInSchedule != show.IncludeInSchedule || this.DoMissingCheck != show.DoMissingCheck || this.DvdEpisodeOrder != show.DvdEpisodeOrder || this.Episodes.Count != show.Episodes.Count || this.AlternativeNameMatches.Count != show.AlternativeNameMatches.Count) { return(false); } for (int i = 0; i < this.Episodes.Count; i++) { if (!this.Episodes[i].Equals(show.Episodes[i])) { return(false); } } for (int i = 0; i < this.AlternativeNameMatches.Count; i++) { if (!this.AlternativeNameMatches[i].Equals(show.AlternativeNameMatches[i])) { return(false); } } return(true); }
/// <summary> /// Get search match with lowest modification to search string /// </summary> /// <param name="status">Search status instance</param> /// <param name="lowestModsMatchStrLen">Length of best result's content name</param> /// <param name="results">Best resulting content</param> /// <returns>Whether a valid content match result was found</returns> public bool GetSearchResultWithLowestMods(out ContentSearchMod modsOnResultsSearch, out Content results) { int lowestModsMatchStrLen = 0; modsOnResultsSearch = ContentSearchMod.All; switch (this.ContentType) { case ContentType.Movie: results = new Movie(); break; case ContentType.TvShow: results = new TvShow(); break; default: throw new Exception("Unknown content type"); } // Use match with lowest amount of modification made to search string and longest length (I think this is the most likely to the content we actually want to match to) for (int i = 0; i < this.matches.Length; i++) { if (this.matches[i] != null) { for (int j = 0; j < this.matches[i].Count; j++) { if (this.matches[i][j].Mods < modsOnResultsSearch || (this.matches[i][j].Mods == modsOnResultsSearch && this.matches[i][j].MatchedString.Length > lowestModsMatchStrLen)) { results = this.matches[i][j].Content; modsOnResultsSearch = this.matches[i][j].Mods; lowestModsMatchStrLen = this.matches[i][j].MatchedString.Length; } } } } return(!string.IsNullOrWhiteSpace(results.DatabaseName)); }
/// <summary> /// Copies properties from another instance into this instance. /// </summary> /// <param name="content">Instance to copy properties from</param> /// <param name="replacePath">Whether path related properties should be cloned or not</param> /// <param name="handleEmptyPath">Whether to build path if one being cloned is empty</param> public override void CloneAndHandlePath(Content content, bool replacePath, bool handleEmptyPath = true) { if (!(content is TvShow)) { throw new Exception("Content must be TvShow"); } TvShow show = content as TvShow; bool updateRequired = show.Id != this.Id; base.CloneAndHandlePath(show, replacePath, handleEmptyPath); this.IncludeInSchedule = show.IncludeInSchedule; this.DoMissingCheck = show.DoMissingCheck; this.DvdEpisodeOrder = show.DvdEpisodeOrder; if (updateRequired) { this.UpdateInfoFromDatabase(); } // TODO: this is a hack App.Current.Dispatcher.BeginInvoke((Action) delegate { this.Episodes.Clear(); foreach (TvEpisode episode in show.Episodes) { TvEpisode copyEp = new TvEpisode(episode); this.Episodes.Add(copyEp); copyEp.Show = this; } }); this.AlternativeNameMatches = new ObservableCollection <string>(); foreach (string altName in show.AlternativeNameMatches) { this.AlternativeNameMatches.Add(altName); } }
/// <summary> /// Constructor for episode with know show name only. /// </summary> /// <param name="show">Name of show the episode belongs to</param> public TvEpisode(TvShow show) { this.Show = show; }
/// <summary> /// Parse show properties from show XML node from database /// </summary> /// <param name="node">XML node to parse info from</param> /// <returns>Parse TvShow object</returns> private static TvShow ParseShowInfo(XmlNode node) { // Init show TvShow show = new TvShow(); // Parse properties out from child nodes XmlNodeList seriesNodes = node.ChildNodes; foreach (XmlNode subNode in seriesNodes) switch (subNode.Name.ToLower()) { case "showname": case "name": show.DatabaseName = subNode.InnerText; break; case "showid": int id; int.TryParse(subNode.InnerText, out id); show.Id = id; break; case "started": int year; int.TryParse(subNode.InnerText, out year); if (year == 0) year = 1; show.DatabaseYear = year; break; case "genres": show.DatabaseGenres = new GenreCollection(GenreCollection.CollectionType.Tv); foreach (XmlNode genreNode in subNode.ChildNodes) show.DatabaseGenres.Add(genreNode.InnerText); break; case "summary": show.Overview = subNode.InnerText.Replace('\n', ' '); break; } return show; }
/// <summary> /// Updates show's season/episode information from database. Use for newly /// added shows only, as it will replace all episode information in show. /// </summary> /// <param name="show">Show to load episode information into</param> protected override bool DoUpdate(string mirror, Content content) { TvShow show = (TvShow)content; bool success = true; string tempPath = string.Empty; try { // Download episodes XML string string showUrl = mirror + "/api/" + API_KEY + "/series/" + show.Id + "/all/en.zip"; string basePath = Organization.GetBasePath(true); Guid guid = Guid.NewGuid(); tempPath = Path.Combine(basePath, guid.ToString()); Directory.CreateDirectory(tempPath); string zipPath = Path.Combine(tempPath, "data" + ".zip"); string extractPath = Path.Combine(tempPath, "en.xml"); WebClient webClient = new WebClient(); webClient.DownloadFile(showUrl, zipPath); ZipFile zip = ZipFile.Read(zipPath); foreach (ZipEntry entry in zip.Entries) { if (entry.FileName == "en.xml") { entry.Extract(tempPath, ExtractExistingFileAction.OverwriteSilently); break; } } zip.Dispose(); XmlDocument showDoc = new XmlDocument(); showDoc.Load(extractPath); // Get root element and children XmlElement root = showDoc.DocumentElement; XmlNodeList rootNodes = root.ChildNodes; // Go through each node and get info for each episode foreach (XmlNode node in rootNodes) { if (node.Name == "Series") { XmlNodeList serNodes = node.ChildNodes; foreach (XmlNode subNode in serNodes) { switch (subNode.Name) { case "Genre": string[] genres = subNode.InnerText.Split('|'); foreach (string genre in genres) { if (!string.IsNullOrWhiteSpace(genre) && !show.DatabaseGenres.Contains(genre)) { show.DatabaseGenres.Add(genre); } } break; } } } else if (node.Name == "Episode") { TvEpisode ep = new TvEpisode(show); int season = -1; XmlNodeList subNodes = node.ChildNodes; foreach (XmlNode subNode in subNodes) { switch (subNode.Name) { case "EpisodeNumber": int epNumber = 0; int.TryParse(subNode.InnerText, out epNumber); ep.DatabaseNumber = epNumber; break; case "DVD_episodenumber": double dvdNumber = -1; if (double.TryParse(subNode.InnerText, out dvdNumber)) { ep.DatabaseDvdNumber = (int)dvdNumber; } break; case "EpisodeName": ep.DatabaseName = subNode.InnerText; break; case "SeasonNumber": season = Convert.ToInt32(subNode.InnerText); ep.Season = season; break; case "FirstAired": DateTime airData; DateTime.TryParse(subNode.InnerText, out airData); ep.DatabaseAirDate = airData; break; case "Overview": ep.DatabaseOverview = subNode.InnerText; break; } } ep.InDatabase = true; if (ep.DisplayNumber > -1 && season > -1) { // If episode already exists just update it, else add it TvEpisode existingMatch; if (show.FindEpisode(ep.Season, ep.DatabaseNumber, true, out existingMatch)) { existingMatch.DatabaseName = ep.DatabaseName; existingMatch.DatabaseAirDate = ep.DatabaseAirDate; existingMatch.DatabaseOverview = ep.DatabaseOverview; existingMatch.InDatabase = true; } else { show.Episodes.Add(ep); } } } } showDoc = null; } catch (Exception e) { Console.WriteLine("Exception caught on TvDb update: " + e.ToString()); success = false; } finally { if (Directory.Exists(tempPath)) { Directory.Delete(tempPath, true); } } return(success); }
/// <summary> /// Match a folder path to show in database /// </summary> /// <param name="rootFolder">Root folder content will belong to</param> /// <param name="path">Current path of content</param> /// <returns>Show from database that was matched, null if no match</returns> public bool PathMatch(string rootFolder, string path, bool fast, bool threaded, out TvShow match) { Content contentMatch; bool results = base.PathMatch(rootFolder, path, fast, threaded, out contentMatch, null); match = new TvShow(contentMatch); match.UpdateInfoFromDatabase(); return(results); }
/// <summary> /// Performs search for a show in TheTvDb. /// </summary> /// <param name="searchString">The string to search for</param> /// <returns>Array of results from the search</returns> protected override List<Content> DoSearch(string mirror, string searchString, bool includeSummaries) { // Get list of results from search List<Content> searchResults = new List<Content>(); string seriesLookupUrl = mirror + "/api/GetSeries.php?seriesname=" + searchString; WebClient webClient = new WebClient(); string seriesList = webClient.DownloadString(seriesLookupUrl); // Create xml object with text from mirrors url XmlDocument seriesDoc = new XmlDocument(); seriesDoc.InnerXml = seriesList; // Get root element and children XmlElement root = seriesDoc.DocumentElement; XmlNodeList nodes = root.ChildNodes; // Go through each node and get the url for all mirrors for (int i = 0; i < nodes.Count; i++) { // Create show TvShow searchResult = new TvShow(); XmlNodeList seriesNodes = nodes[i].ChildNodes; foreach (XmlNode subNode in seriesNodes) switch (subNode.Name.ToLower()) { case "seriesname": searchResult.DatabaseName = subNode.InnerText; char[] invalidChars = { '\\', '/', ':', '*', '?', '<', '>', '|' }; foreach (char c in invalidChars) if (searchResult.DatabaseName.Contains(c)) searchResult.DatabaseName = searchResult.DatabaseName.Replace(c.ToString(), " "); break; case "seriesid": int id; int.TryParse(subNode.InnerText, out id); searchResult.Id = id; break; case "overview": searchResult.Overview = subNode.InnerText; break; case "firstaired": DateTime airDate; DateTime.TryParse(subNode.InnerText, out airDate); searchResult.DatabaseYear = airDate.Year; break; } searchResult.DatabaseSelection = (int)TvDataBaseSelection.TheTvDb; searchResults.Add(searchResult); } return searchResults; }
/// <summary> /// Update processing method (thread) for single content folder in root. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="processNumber">The identifier for the OrgProcessing instance</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> /// <param name="numItemsStarted">Number of paths that have had been added to thread pool for processing</param> /// <param name="processSpecificArgs">Arguments specific to this process</param> private void UpdateProcess(OrgPath orgPath, int pathNum, int totalPaths, int processNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Check for cancellation - this method is called from thread pool, so cancellation could have occured by the time this is run if (updateCancelled || this.updateNumber != processNumber) { return; } // First pass run does quick folder update by skipping online database searching bool firstPass = (bool)processSpecificArgs; string passString = firstPass ? " (First Pass)" : " (Second Pass)"; // Set processing messge string progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' started"; OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); // Get content collection to add content to ContentCollection content = GetContentCollection(); // Check if folder already has a match to existing content bool contentExists = false; bool contentComplete = false; Content newContent = null; int index = 0; for (int j = 0; j < content.Count; j++) { if (Path.Equals(orgPath.Path, content[j].Path)) { contentExists = true; content[j].Found = true; if (!string.IsNullOrEmpty(content[j].DatabaseName)) { contentComplete = true; } newContent = content[j]; index = j; break; } } // Set completed progess message progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' complete"; // Check if content found if (contentExists && contentComplete) { // Check if content needs updating if ((DateTime.Now - newContent.LastUpdated).TotalDays > 7 && this.ContentType == ContentType.TvShow) { newContent.UpdateInfoFromDatabase(); } // Update progress if (this.updateNumber == processNumber) { OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); } return; } // Folder wasn't matched to an existing content instance, try tmatch folder to content from online database Content match; bool matchSucess; switch (this.ContentType) { case ContentType.TvShow: TvShow showMatch; matchSucess = SearchHelper.TvShowSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out showMatch); match = showMatch; break; case ContentType.Movie: Movie movieMatch; matchSucess = SearchHelper.MovieSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out movieMatch); match = movieMatch; break; default: throw new Exception("unknown content type"); } // Check that current process hasn't been replaced - search can be slow, so update may have been cancelled by the time it gets here if (updateCancelled || this.updateNumber != processNumber) { return; } // Folder already existed, but wasn't previously match to valid content if (contentExists && matchSucess) { switch (this.ContentType) { case ContentType.TvShow: ((TvShow)newContent).CloneAndHandlePath((TvShow)match, true); ((TvShow)newContent).UpdateMissing(); break; case ContentType.Movie: ((Movie)newContent).CloneAndHandlePath((Movie)match, true); break; default: throw new Exception("unknown content type"); } newContent.LastUpdated = DateTime.Now; } else if (matchSucess) { newContent = match; } else { switch (this.ContentType) { case ContentType.TvShow: newContent = new TvShow(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; case ContentType.Movie: newContent = new Movie(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; default: throw new Exception("unknown content type"); } } // Set found flag newContent.Found = true; // Add content to list if new if (!contentExists) { content.Add(newContent); } // Update progress OnUpdateProgressChange(this, true, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); }
public OrgItem ProcessPath(OrgPath orgPath, bool tvOnlyCheck, bool skipMatching, bool fast, bool threaded, int procNumber, bool allowFromLog, out bool fromLog) { fromLog = false; FileCategory fileCat = FileHelper.CategorizeFile(orgPath, orgPath.Path); // Search through dir scan log for matching source if (allowFromLog) { for (int i = 0; i < Organization.DirScanLog.Count; i++) { if (Organization.DirScanLog[i].SourcePath == orgPath.Path) { fromLog = true; if (fileCat != Organization.DirScanLog[i].Category) { break; } OrgItem newItem = UpdateItemFromPrevious(orgPath, Organization.DirScanLog[i], threaded, false, false); if (newItem != null && newItem.Action != OrgAction.None) { if (newItem.CanEnable) { newItem.Enable = true; } return(newItem); } } } } // If similar to earlier item, wait for it to complete before continuing while (!skipMatching && orgPath.SimilarTo >= 0 && orgPath.SimilarTo < this.Items.Count && (this.Items[orgPath.SimilarTo].Action == OrgAction.TBD || this.Items[orgPath.SimilarTo].Action == OrgAction.Processing)) { // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } Thread.Sleep(50); } // If similar to earlier item, check if we can use it's info if (orgPath.SimilarTo > 0 && orgPath.SimilarTo < this.Items.Count) { OrgItem newItem = UpdateItemFromPrevious(orgPath, this.Items[orgPath.SimilarTo], threaded, fast, skipMatching); if (newItem != null) { return(newItem); } } // Create default none item OrgItem noneItem = new OrgItem(OrgAction.None, orgPath.Path, fileCat, orgPath.OrgFolder); // Setup match to filename and folder name (if it's in a folder inside of downloads) string pathBase; if (!string.IsNullOrEmpty(orgPath.OrgFolder.FolderPath)) { pathBase = orgPath.Path.Replace(orgPath.OrgFolder.FolderPath, ""); } else { pathBase = orgPath.Path; } string[] pathSplit; if (!string.IsNullOrEmpty(orgPath.OrgFolder.FolderPath)) { pathSplit = pathBase.Split('\\'); } else { pathSplit = orgPath.Path.Split('\\'); } pathSplit[pathSplit.Length - 1] = Path.GetFileNameWithoutExtension(pathSplit[pathSplit.Length - 1]); List <string> possibleMatchPaths = new List <string>(); // Looking to remove dummy video files by rip (e.g. "ERTG.mp4" inside "The Matrix 1999 hd ERGT rip/" folder) List <string> validSplits = new List <string>(); for (int i = pathSplit.Length - 1; i > 0; i--) { if (string.IsNullOrWhiteSpace(pathSplit[i])) { continue; } bool containedInOther = false; for (int j = i - 1; j > 0; j--) { if (pathSplit[j].Length > pathSplit[i].Length && pathSplit[j].ToLower().Contains(pathSplit[i].ToLower())) { containedInOther = true; break; } } if (!containedInOther) { validSplits.Add(pathSplit[i]); possibleMatchPaths.Add(pathSplit[i] + Path.GetExtension(orgPath.Path)); } } for (int i = validSplits.Count - 1; i >= 0; i--) { string build = string.Empty; for (int j = validSplits.Count - 1; j >= i; j--) { build += validSplits[j] + " "; } build = build.Trim(); build += Path.GetExtension(orgPath.Path); if (!possibleMatchPaths.Contains(build)) { possibleMatchPaths.Add(build); } } // Try to match to each string foreach (string matchString in possibleMatchPaths) { OnDebugNotificationd("Attempting to match to path: \" " + matchString + "\""); // Get simple file name string simpleFile = FileHelper.BasicSimplify(Path.GetFileNameWithoutExtension(matchString), false); OnDebugNotificationd("Simplifies to: \" " + simpleFile + "\""); // Categorize current match string FileCategory matchFileCat = FileHelper.CategorizeFile(orgPath, matchString); OnDebugNotificationd("Classified as: " + matchFileCat); // Check tv if (tvOnlyCheck && matchFileCat != FileCategory.TvVideo) { continue; } // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } // Set whether item is for new show bool newShow = false; // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } // Add appropriate action based on file category switch (matchFileCat) { // TV item case FileCategory.TvVideo: // Try to match file to existing show Dictionary <TvShow, MatchCollection> matches = new Dictionary <TvShow, MatchCollection>(); for (int j = 0; j < Organization.Shows.Count; j++) { MatchCollection match = Organization.Shows[j].MatchFileToContent(matchString); if (match != null && match.Count > 0) { matches.Add((TvShow)Organization.Shows[j], match); } } // Try to match to temporary show lock (directoryScanLock) foreach (TvShow show in temporaryShows) { MatchCollection match = show.MatchFileToContent(matchString); if (match != null && match.Count > 0 && !matches.ContainsKey(show)) { matches.Add(show, match); } } // Debug notification for show matches string matchNotification = "Show name matches found: "; if (matches.Count == 0) { matchNotification += "NONE"; } else { foreach (TvShow match in matches.Keys) { matchNotification += match.DisplayName + ", "; } matchNotification = matchNotification.Substring(0, matchNotification.Length - 2); } OnDebugNotificationd(matchNotification); // Check for matches to existing show TvShow bestMatch = null; if (matches.Count > 0) { // Find best match, based on length int longestMatch = 2; // minimum of 3 letters must match (acronym) foreach (KeyValuePair <TvShow, MatchCollection> match in matches) { foreach (Match m in match.Value) { // Check that match is not part of a word int matchWordCnt = 0; for (int l = m.Index; l < simpleFile.Length; l++) { if (simpleFile[l] == ' ') { break; } else { ++matchWordCnt; } } if (m.Value.Trim().Length > longestMatch && m.Length >= matchWordCnt) { longestMatch = m.Length; bestMatch = match.Key; } } } if (bestMatch != null) { OnDebugNotificationd("Matched to show " + bestMatch.DisplayName); } } // Episode not matched to a TV show, search database! if (bestMatch == null && !skipMatching) { OnDebugNotificationd("Not matched to existing show; searching database."); // Setup search string string showFile = Path.GetFileNameWithoutExtension(matchString); // Perform search for matching TV show if (SearchHelper.TvShowSearch.ContentMatch(showFile, string.Empty, string.Empty, fast, threaded, out bestMatch)) { ContentRootFolder defaultTvFolder; string path = NO_TV_FOLDER; if (Settings.GetTvFolderForContent(bestMatch, out defaultTvFolder)) { path = defaultTvFolder.FullPath; } bestMatch.RootFolder = path; bestMatch.Path = bestMatch.BuildFolderPath(); // Save show in temporary shows list (in case there are more files that may match to it during scan) lock (directoryScanLock) if (!temporaryShows.Contains(bestMatch)) { temporaryShows.Add(bestMatch); } newShow = true; } else { bestMatch = null; } } else if (temporaryShows.Contains(bestMatch)) { newShow = true; } // Episode has been matched to a TV show if (bestMatch != null) { // Try to get episode information from file int seasonNum, episodeNum1, episodeNum2; if (FileHelper.GetEpisodeInfo(matchString, bestMatch.DatabaseName, out seasonNum, out episodeNum1, out episodeNum2)) { // No season match means file only had episode number, allow this for single season shows if (seasonNum == -1) { int maxSeason = 0; foreach (int season in bestMatch.Seasons) { if (season > maxSeason) { maxSeason = season; } } if (maxSeason == 1) { seasonNum = 1; } } // Try to get the episode from the show TvEpisode episode1, episode2 = null; if (bestMatch.FindEpisode(seasonNum, episodeNum1, false, out episode1)) { if (episodeNum2 != -1) { bestMatch.FindEpisode(seasonNum, episodeNum2, false, out episode2); } OrgAction action = orgPath.Copy ? OrgAction.Copy : OrgAction.Move; // If item episode already exists set action to duplicate if (episode1.Missing == MissingStatus.Located) { action = OrgAction.AlreadyExists; } // Build action and add it to results string destination = bestMatch.BuildFilePath(episode1, episode2, Path.GetExtension(orgPath.Path)); OrgItem newItem = new OrgItem(action, orgPath.Path, destination, episode1, episode2, fileCat, orgPath.OrgFolder); if (destination.StartsWith(NO_TV_FOLDER)) { newItem.Action = OrgAction.NoRootFolder; } if (newItem.Action == OrgAction.AlreadyExists || newItem.Action == OrgAction.NoRootFolder) { newItem.Enable = false; } else { newItem.Enable = true; } newItem.Category = matchFileCat; newItem.IsNewShow = newShow; return(newItem); } else { OnDebugNotificationd("Couldn't find episode for season " + seasonNum + " episods " + episodeNum1); } } } // No match to TV show if (!tvOnlyCheck && !fast) { // Try to match to a movie OrgItem movieItem; if (CreateMovieAction(orgPath, matchString, out movieItem, threaded, fast, skipMatching, null)) { noneItem.Clone(movieItem); } } break; // Movie item case FileCategory.MovieVideo: // Create action OrgItem item; CreateMovieAction(orgPath, matchString, out item, threaded, fast, skipMatching, null); // If delete action created (for sample file) if (item.Action == OrgAction.Delete) { return(BuildDeleteAction(orgPath, fileCat)); } else if (item.Action != OrgAction.None) { return(item); } break; // Trash case FileCategory.Trash: return(BuildDeleteAction(orgPath, matchFileCat)); // Ignore case FileCategory.Ignored: return(new OrgItem(OrgAction.None, orgPath.Path, matchFileCat, orgPath.OrgFolder)); // Unknown default: return(new OrgItem(OrgAction.None, orgPath.Path, matchFileCat, orgPath.OrgFolder)); } // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(noneItem); } } // If no match on anything set none action return(noneItem); }
/// <summary> /// Attempts to match string to content from the online database. /// </summary> /// <param name="search">Search string to match against</param> /// <param name="rootFolder">The root folder the content will belong to</param> /// <param name="folderPath">Folder path where the content should be moved to</param> /// <returns>Match content item, null if no match</returns> protected bool ContentMatch(string search, string rootFolder, string folderPath, bool fast, bool threaded, out Content match, Content knownContent) { // Create empty content Content emptyContent; switch (this.ContentType) { case ContentType.Movie: emptyContent = new Movie(); break; case ContentType.TvShow: emptyContent = new TvShow(); break; default: throw new Exception("Unknown content type"); } emptyContent.Path = folderPath; emptyContent.RootFolder = rootFolder; emptyContent.Found = true; // Check for empty search condition if (string.IsNullOrEmpty(search)) { match = emptyContent; return false; } // Get year from search string int dirYear = FileHelper.GetYear(search); // Get list of simplified strings List<FileHelper.SimplifyStringResults> searches = new List<FileHelper.SimplifyStringResults>(); // Get list of search bases List<string> searchBases = GetModifiedSearches(search); // Fast search: use first search base only if (fast) { FileHelper.SimplifyStringResults result = FileHelper.BuildSimplifyResults(searchBases[0], false, false, FileHelper.OptionalSimplifyRemoves.YearAndFollowing, true, false, true, false); searches.Add(result); } // Full search: Go through each search base and get simplified search options else foreach (string searchBase in searchBases) { // Get results from current base List<FileHelper.SimplifyStringResults> currSearches = FileHelper.SimplifyString(searchBase); currSearches.Add(new FileHelper.SimplifyStringResults(searchBase, new Dictionary<FileWordType, List<string>>(), ContentSearchMod.None)); // Add each result to full list of searches foreach (FileHelper.SimplifyStringResults results in currSearches) { // Check if search already exist bool exists = false; foreach (FileHelper.SimplifyStringResults s in searches) if (s.SimplifiedString == results.SimplifiedString) { exists = true; break; } // If doesn't exist add it to searches if (!exists && !string.IsNullOrWhiteSpace(results.SimplifiedString)) searches.Add(results); } } searches.Sort(); // Create new status int currSeachCnt; MatchStatus status; lock (searchLock) { currSeachCnt = ++searchCount; status = new MatchStatus(searches.Count, this.ContentType); searchStatus.Add(currSeachCnt, status); } ContentSearchMod lowMods; Content lowestModsMatch; // Add thread to pool for each search that need to be performed int searchNum = 0; while (searchNum < searches.Count) { // Check for any search results so far if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { // If search results have no mods or just year removed use them as final results if (lowMods == ContentSearchMod.None || lowMods == ContentSearchMod.YearRemoved) { match = lowestModsMatch; return true; } } // Limit number of search threads created if (status.NumStarted - status.NumCompleted >= Settings.General.NumSimultaneousSearches) { Thread.Sleep(100); continue; } // Build search arguments object[] args = { currSeachCnt, searchNum, searches[searchNum].SimplifiedString, folderPath, rootFolder, dirYear, searches[searchNum].Modifications, knownContent }; // Threaded: add a search to thread pool if (threaded) { ThreadPool.QueueUserWorkItem(new WaitCallback(SearchThread), args); lock (searchLock) status.SetSearchStarted(searchNum); } // Synchronized: call search method else SearchThread(args); searchNum++; } // Wait for all search to complete while (status.NumCompleted < searches.Count) { // Check for any search results so far if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { // If search results have no mods or just year removed use them as final results if (lowMods == ContentSearchMod.None || lowMods == ContentSearchMod.YearRemoved) { match = lowestModsMatch; return true; } } Thread.Sleep(100); } // Clear status lock (searchLock) searchStatus.Remove(currSeachCnt); // Return result with lowest mods to search string if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { match = lowestModsMatch; return true; } else { match = emptyContent; return false; } }
/// <summary> /// Attempts to match string to content from the online database. /// </summary> /// <param name="search">Search string to match against</param> /// <param name="rootFolder">The root folder the content will belong to</param> /// <param name="folderPath">Folder path where the content should be moved to</param> /// <returns>Match content item, null if no match</returns> protected bool ContentMatch(string search, string rootFolder, string folderPath, bool fast, bool threaded, out Content match, Content knownContent) { // Create empty content Content emptyContent; switch (this.ContentType) { case ContentType.Movie: emptyContent = new Movie(); break; case ContentType.TvShow: emptyContent = new TvShow(); break; default: throw new Exception("Unknown content type"); } emptyContent.Path = folderPath; emptyContent.RootFolder = rootFolder; emptyContent.Found = true; // Check for empty search condition if (string.IsNullOrEmpty(search)) { match = emptyContent; return(false); } // Get year from search string int dirYear = FileHelper.GetYear(search); // Get list of simplified strings List <FileHelper.SimplifyStringResults> searches = new List <FileHelper.SimplifyStringResults>(); // Get list of search bases List <string> searchBases = GetModifiedSearches(search); // Fast search: use first search base only if (fast) { FileHelper.SimplifyStringResults result = FileHelper.BuildSimplifyResults(searchBases[0], false, false, FileHelper.OptionalSimplifyRemoves.YearAndFollowing, true, false, true, false); searches.Add(result); } // Full search: Go through each search base and get simplified search options else { foreach (string searchBase in searchBases) { // Get results from current base List <FileHelper.SimplifyStringResults> currSearches = FileHelper.SimplifyString(searchBase); currSearches.Add(new FileHelper.SimplifyStringResults(searchBase, new Dictionary <FileWordType, List <string> >(), ContentSearchMod.None)); // Add each result to full list of searches foreach (FileHelper.SimplifyStringResults results in currSearches) { // Check if search already exist bool exists = false; foreach (FileHelper.SimplifyStringResults s in searches) { if (s.SimplifiedString == results.SimplifiedString) { exists = true; break; } } // If doesn't exist add it to searches if (!exists && !string.IsNullOrWhiteSpace(results.SimplifiedString)) { searches.Add(results); } } } } searches.Sort(); // Create new status int currSeachCnt; MatchStatus status; lock (searchLock) { currSeachCnt = ++searchCount; status = new MatchStatus(searches.Count, this.ContentType); searchStatus.Add(currSeachCnt, status); } ContentSearchMod lowMods; Content lowestModsMatch; // Add thread to pool for each search that need to be performed int searchNum = 0; while (searchNum < searches.Count) { // Check for any search results so far if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { // If search results have no mods or just year removed use them as final results if (lowMods == ContentSearchMod.None || lowMods == ContentSearchMod.YearRemoved) { match = lowestModsMatch; return(true); } } // Limit number of search threads created if (status.NumStarted - status.NumCompleted >= Settings.General.NumSimultaneousSearches) { Thread.Sleep(100); continue; } // Build search arguments object[] args = { currSeachCnt, searchNum, searches[searchNum].SimplifiedString, folderPath, rootFolder, dirYear, searches[searchNum].Modifications, knownContent }; // Threaded: add a search to thread pool if (threaded) { ThreadPool.QueueUserWorkItem(new WaitCallback(SearchThread), args); lock (searchLock) status.SetSearchStarted(searchNum); } // Synchronized: call search method else { SearchThread(args); } searchNum++; } // Wait for all search to complete while (status.NumCompleted < searches.Count) { // Check for any search results so far if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { // If search results have no mods or just year removed use them as final results if (lowMods == ContentSearchMod.None || lowMods == ContentSearchMod.YearRemoved) { match = lowestModsMatch; return(true); } } Thread.Sleep(100); } // Clear status lock (searchLock) searchStatus.Remove(currSeachCnt); // Return result with lowest mods to search string if (status.GetSearchResultWithLowestMods(out lowMods, out lowestModsMatch)) { match = lowestModsMatch; return(true); } else { match = emptyContent; return(false); } }
/// <summary> /// Run through all TV shows all looks for episodes that may need to be renamed and for missing episodes. /// For missing episodes it attempts to match them to files from the search directories. /// </summary> /// <param name="shows">Shows to scan</param> /// <param name="queuedItems">Items currently in queue (to be skipped)</param> /// <returns></returns> public List <OrgItem> RunScan(List <Content> shows, List <OrgItem> queuedItems) { // Set running flag scanRunning = true; // Initialiaze scan items List <OrgItem> missingCheckItem = new List <OrgItem>(); // Initialize item numbers int number = 0; // Go through each show for (int i = 0; i < shows.Count; i++) { TvShow show = (TvShow)shows[i]; if (cancelRequested) { break; } OnProgressChange(ScanProcess.TvRename, shows[i].DatabaseName, (int)Math.Round((double)i / (shows.Count) * 30) + 70); // Go each show foreach (TvEpisode ep in show.Episodes) { // Check for cancellation if (cancelRequested) { break; } // Skipped ignored episodes if (ep.Ignored) { continue; } // Init found flag bool found = false; // Rename check if (ep.Missing == MissingStatus.Located) { if (shows[i].DoRenaming) { found = true; TvEpisode ep2 = null; if (ep.File.MultiPart) { if (ep.File.Part == 1) { foreach (TvEpisode epEnumerated in show.Episodes) { if (epEnumerated.Season == ep.Season && epEnumerated.DisplayNumber == ep.DisplayNumber + 1) { ep2 = epEnumerated; break; } } } else { continue; } } // Build desired path string builtPath = show.BuildFilePath(ep, ep2, Path.GetExtension(ep.File.FilePath)); // Check if rename needed (or move within folder) if (ep.File.FilePath != builtPath) { OrgItem newItem = new OrgItem(OrgStatus.Organization, OrgAction.Rename, ep.File.FilePath, builtPath, ep, ep2, FileCategory.TvVideo, null); newItem.Enable = true; if (!show.DoRenaming) { newItem.Category = FileCategory.Ignored; } newItem.Number = number++; missingCheckItem.Add(newItem); } } else { continue; } } else { continue; } // Add empty item for missing if (!found && ep.Aired && show.DoMissingCheck) { OrgItem newItem = new OrgItem(OrgStatus.Missing, OrgAction.None, ep, null, FileCategory.TvVideo, null); if (!show.DoRenaming) { newItem.Category = FileCategory.Ignored; } newItem.Number = number++; missingCheckItem.Add(newItem); } } } // Convert all TV folders to org folders List <OrgFolder> tvFoldersAsOrgFolders = new List <OrgFolder>(); foreach (ContentRootFolder tvFolder in Settings.TvFolders) { OrgFolder orgFolder = new OrgFolder(tvFolder.FullPath, false, false, false, false); tvFoldersAsOrgFolders.Add(orgFolder); } // Update progress OnProgressChange(ScanProcess.TvRename, string.Empty, 100); // Clear flags scanRunning = false; cancelRequested = false; // Return results return(missingCheckItem); }
/// <summary> /// Update processing method (thread) for single content folder in root. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="processNumber">The identifier for the OrgProcessing instance</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> /// <param name="numItemsStarted">Number of paths that have had been added to thread pool for processing</param> /// <param name="processSpecificArgs">Arguments specific to this process</param> private void UpdateProcess(OrgPath orgPath, int pathNum, int totalPaths, int processNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Check for cancellation - this method is called from thread pool, so cancellation could have occured by the time this is run if (updateCancelled || this.updateNumber != processNumber) return; // First pass run does quick folder update by skipping online database searching bool firstPass = (bool)processSpecificArgs; string passString = firstPass ? " (First Pass)" : " (Second Pass)"; // Set processing messge string progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' started"; OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); // Get content collection to add content to ContentCollection content = GetContentCollection(); // Check if folder already has a match to existing content bool contentExists = false; bool contentComplete = false; Content newContent = null; int index = 0; for (int j = 0; j < content.Count; j++) if (Path.Equals(orgPath.Path, content[j].Path)) { contentExists = true; content[j].Found = true; if (!string.IsNullOrEmpty(content[j].DatabaseName)) contentComplete = true; newContent = content[j]; index = j; break; } // Set completed progess message progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' complete"; // Check if content found if (contentExists && contentComplete) { // Check if content needs updating if ((DateTime.Now - newContent.LastUpdated).TotalDays > 7 && this.ContentType == ContentType.TvShow) newContent.UpdateInfoFromDatabase(); // Update progress if (this.updateNumber == processNumber) OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); return; } // Folder wasn't matched to an existing content instance, try tmatch folder to content from online database Content match; bool matchSucess; switch (this.ContentType) { case ContentType.TvShow: TvShow showMatch; matchSucess = SearchHelper.TvShowSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out showMatch); match = showMatch; break; case ContentType.Movie: Movie movieMatch; matchSucess = SearchHelper.MovieSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out movieMatch); match = movieMatch; break; default: throw new Exception("unknown content type"); } // Check that current process hasn't been replaced - search can be slow, so update may have been cancelled by the time it gets here if (updateCancelled || this.updateNumber != processNumber) return; // Folder already existed, but wasn't previously match to valid content if (contentExists && matchSucess) { switch (this.ContentType) { case ContentType.TvShow: ((TvShow)newContent).CloneAndHandlePath((TvShow)match, true); ((TvShow)newContent).UpdateMissing(); break; case ContentType.Movie: ((Movie)newContent).CloneAndHandlePath((Movie)match, true); break; default: throw new Exception("unknown content type"); } newContent.LastUpdated = DateTime.Now; } else if (matchSucess) newContent = match; else switch (this.ContentType) { case ContentType.TvShow: newContent = new TvShow(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; case ContentType.Movie: newContent = new Movie(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; default: throw new Exception("unknown content type"); } // Set found flag newContent.Found = true; // Add content to list if new if (!contentExists) content.Add(newContent); // Update progress OnUpdateProgressChange(this, true, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); }
/// <summary> /// Gets season/episode information from database. /// </summary> /// <param name="show">Show to load episode information into</param> public static void FullShowSeasonsUpdate(TvShow show) { GetDataBaseAccess(show.Database).FullUpdate(show); show.LastUpdated = DateTime.Now; }
/// <summary> /// Load collection from saved XML file /// </summary> public void Load(bool doUpdating) { XmlTextReader reader = null; XmlDocument xmlDoc = new XmlDocument(); try { string path = Path.Combine(Organization.GetBasePath(false), XML_ROOT + ".xml"); if (File.Exists(path)) { // Use dummy collection to load into so that loading doesn't hog use of object ContentCollection loadContent = new ContentCollection(this.ContentType, "Loading Shows"); lock (XmlLock) { // Load XML reader = new XmlTextReader(path); xmlDoc.Load(reader); // Extract data XmlNodeList contentNodes = xmlDoc.DocumentElement.ChildNodes; for (int i = 0; i < contentNodes.Count; i++) { // Update loading progress OnLoadProgressChange((int)(((double)i / contentNodes.Count) * 100)); // All elements will be content items or last update time if (contentNodes[i].Name == "LastUpdate") { loadContent.LastUpdate = contentNodes[i].InnerText; } else { // Load content from element based on type switch (this.ContentType) { case ContentType.TvShow: TvShow show = new TvShow(); if (show.Load(contentNodes[i])) { bool rootFolderExists = false; foreach (ContentRootFolder folder in Settings.GetAllRootFolders(this.ContentType, true)) { if (folder.FullPath == show.RootFolder) { rootFolderExists = true; } } if (!rootFolderExists || !Directory.Exists(show.Path)) { continue; } loadContent.Add(show); if (doUpdating) { show.UpdateMissing(); } } break; case ContentType.Movie: Movie movie = new Movie(); if (movie.Load(contentNodes[i])) { loadContent.Add(movie); } break; default: throw new Exception("Unknown content type"); } } } } // Update progress OnLoadProgressChange(100); this.LastUpdate = loadContent.LastUpdate; this.Clear(); AddMultiple(loadContent); } } catch (Exception e) { MessageBox.Show(e.ToString(), "Error loading " + this.ContentType + "s from saved data!"); } finally { if (reader != null) { reader.Close(); } } // Start updating of TV episode in scan dirs. if (this.ContentType == ContentType.TvShow && doUpdating) { TvItemInScanDirHelper.DoUpdate(false); TvItemInScanDirHelper.StartUpdateTimer(); } // Trigger load complete event OnLoadComplete(); }
/// <summary> /// Run through all TV shows all looks for episodes that may need to be renamed and for missing episodes. /// For missing episodes it attempts to match them to files from the search directories. /// </summary> /// <param name="shows">Shows to scan</param> /// <param name="queuedItems">Items currently in queue (to be skipped)</param> /// <returns></returns> public List <OrgItem> RunScan(List <Content> shows, List <OrgItem> queuedItems, bool fast) { // Set running flag scanRunning = true; cancelRequested = false; // Do directory check on all directories (to look for missing episodes) while (!TvItemInScanDirHelper.Initialized) { Thread.Sleep(100); } List <OrgItem> directoryItems = TvItemInScanDirHelper.Items; // Initialiaze scan items List <OrgItem> missingCheckItem = new List <OrgItem>(); // Initialize item numbers int number = 0; double progressPerShow = 1D / shows.Count * 100D; // Go through each show for (int i = 0; i < shows.Count; i++) { TvShow show = (TvShow)shows[i]; if (cancelRequested) { break; } double showsProgress = (double)i * progressPerShow; OnProgressChange(ScanProcess.TvMissing, shows[i].DatabaseName, (int)Math.Round(showsProgress)); double progressPerEp = 1D / show.Episodes.Count * progressPerShow; // Go through missing episodes for (int j = 0; j < show.Episodes.Count; j++) { // Get episode TvEpisode ep = show.Episodes[j]; // Update progress OnProgressChange(ScanProcess.TvMissing, shows[i].DatabaseName, (int)Math.Round(showsProgress + j * progressPerEp)); // Check for cancellation if (cancelRequested) { break; } // Skipped ignored episodes if (ep.Ignored || !show.DoMissingCheck) { continue; } // Init found flag bool found = false; // Check if episode is missing if (ep.Missing == MissingStatus.Missing || ep.Missing == MissingStatus.InScanDirectory) { // Check directory item for episode foreach (OrgItem item in directoryItems) { if ((item.Action == OrgAction.Move || item.Action == OrgAction.Copy) && item.TvEpisode != null && item.TvEpisode.Show.DatabaseName == show.DatabaseName) { // Only add item for first part of multi-part file if (ep.Equals(item.TvEpisode)) { OrgItem newItem = new OrgItem(OrgStatus.Found, item.Action, item.SourcePath, item.DestinationPath, ep, item.TvEpisode2, FileCategory.TvVideo, item.ScanDirectory); newItem.Enable = true; newItem.Number = number++; if (!show.DoMissingCheck) { newItem.Category = FileCategory.Ignored; } missingCheckItem.Add(newItem); found = true; break; } else if (ep.Equals(item.TvEpisode2)) { found = true; break; } } } } else { continue; } // Add empty item for missing if (!found && ep.Aired && show.DoMissingCheck) { OrgItem newItem; TvEpisodeTorrent ezTvEpisode = TvTorrentHelper.GetEpisodeTorrent(ep); if (ezTvEpisode != null) { newItem = new OrgItem(OrgStatus.Missing, OrgAction.Torrent, ep, null, FileCategory.TvVideo, null); newItem.BuildDestination(); } else { newItem = new OrgItem(OrgStatus.Missing, OrgAction.None, ep, null, FileCategory.TvVideo, null); } newItem.Number = number++; missingCheckItem.Add(newItem); } } } // Update progress OnProgressChange(ScanProcess.TvMissing, string.Empty, 100); // Clear flags scanRunning = false; // Return results return(missingCheckItem); }
/// <summary> /// Constructor for cloning instance. /// </summary> /// <param name="show"></param> public TvShow(TvShow show) : this() { Clone(show); }
/// <summary> /// Gets season/episode information from database /// </summary> /// <param name="show">Show to load episode information into</param> protected override bool DoUpdate(string mirror, Content content) { TvShow show = (TvShow)content; try { // Get show info from database string showInfoUrl = mirror + "/showinfo.php?key=" + this.API_KEY + "&sid=" + show.Id; WebClient webClient = new WebClient(); string showInfo = webClient.DownloadString(showInfoUrl); // Create XML object from results XmlDocument seriesDoc = new XmlDocument(); seriesDoc.InnerXml = showInfo; // Parse show info show.Overview = ParseShowInfo(seriesDoc.DocumentElement).Overview; // Get episode info from database string episodeListUrl = mirror + "/episode_list.php?key=" + this.API_KEY + "&sid=" + show.Id; webClient = new WebClient(); string seriesList = webClient.DownloadString(episodeListUrl); // Create xml object with text from mirrors url seriesDoc = new XmlDocument(); seriesDoc.InnerXml = seriesList; // Get root element and children XmlElement root = seriesDoc.DocumentElement; XmlNodeList nodes = root.ChildNodes; // Go through each node and parse out episodes foreach (XmlNode subNode in nodes) { switch (subNode.Name.ToLower()) { case "episodelist": foreach (XmlNode seasonNode in subNode.ChildNodes) { if (seasonNode.Name.ToLower() == "season") { string seasonNoStr = seasonNode.Attributes["no"].Value; int seasonNo; int.TryParse(seasonNoStr, out seasonNo); foreach (XmlNode epNode in seasonNode.ChildNodes) { TvEpisode ep = new TvEpisode(show); ep.Season = seasonNo; foreach (XmlNode epPropNode in epNode.ChildNodes) { switch (epPropNode.Name.ToLower()) { case "seasonnum": // episode number within season int epNum; int.TryParse(epPropNode.InnerText, out epNum); ep.DatabaseNumber = epNum; break; case "airdate": DateTime airDate; DateTime.TryParse(epPropNode.InnerText, out airDate); ep.DatabaseAirDate = airDate; break; case "title": ep.DatabaseName = epPropNode.InnerText; break; case "summary": ep.DatabaseOverview = epPropNode.InnerText.Replace('\n', ' '); break; } } ep.InDatabase = true; // If episode already exists just update it, else add it TvEpisode existingMatch; if (show.FindEpisode(ep.Season, ep.DatabaseNumber, true, out existingMatch)) { if (!existingMatch.PreventDatabaseUpdates) { existingMatch.DatabaseName = ep.DatabaseName; existingMatch.DatabaseAirDate = ep.DatabaseAirDate; existingMatch.DatabaseOverview = ep.DatabaseOverview; existingMatch.InDatabase = true; } } else { show.Episodes.Add(ep); } } } } break; } } return(true); } catch { return(false); } }
/// <summary> /// Get search match with lowest modification to search string /// </summary> /// <param name="status">Search status instance</param> /// <param name="lowestModsMatchStrLen">Length of best result's content name</param> /// <param name="results">Best resulting content</param> /// <returns>Whether a valid content match result was found</returns> public bool GetSearchResultWithLowestMods(out ContentSearchMod modsOnResultsSearch, out Content results) { int lowestModsMatchStrLen = 0; modsOnResultsSearch = ContentSearchMod.All; switch (this.ContentType) { case ContentType.Movie: results = new Movie(); break; case ContentType.TvShow: results = new TvShow(); break; default: throw new Exception("Unknown content type"); } // Use match with lowest amount of modification made to search string and longest length (I think this is the most likely to the content we actually want to match to) for (int i = 0; i < this.matches.Length; i++) if (this.matches[i] != null) for (int j = 0; j < this.matches[i].Count; j++) { if (this.matches[i][j].Mods < modsOnResultsSearch || (this.matches[i][j].Mods == modsOnResultsSearch && this.matches[i][j].MatchedString.Length > lowestModsMatchStrLen)) { results = this.matches[i][j].Content; modsOnResultsSearch = this.matches[i][j].Mods; lowestModsMatchStrLen = this.matches[i][j].MatchedString.Length; } } return !string.IsNullOrWhiteSpace(results.DatabaseName); }
/// <summary> /// Performs search for a show in TheTvDb. /// </summary> /// <param name="searchString">The string to search for</param> /// <returns>Array of results from the search</returns> protected override List <Content> DoSearch(string mirror, string searchString, bool includeSummaries) { // Get list of results from search List <Content> searchResults = new List <Content>(); string seriesLookupUrl = mirror + "/api/GetSeries.php?seriesname=" + searchString; WebClient webClient = new WebClient(); string seriesList = webClient.DownloadString(seriesLookupUrl); // Create xml object with text from mirrors url XmlDocument seriesDoc = new XmlDocument(); seriesDoc.InnerXml = seriesList; // Get root element and children XmlElement root = seriesDoc.DocumentElement; XmlNodeList nodes = root.ChildNodes; // Go through each node and get the url for all mirrors for (int i = 0; i < nodes.Count; i++) { // Create show TvShow searchResult = new TvShow(); XmlNodeList seriesNodes = nodes[i].ChildNodes; foreach (XmlNode subNode in seriesNodes) { switch (subNode.Name.ToLower()) { case "seriesname": searchResult.DatabaseName = subNode.InnerText; char[] invalidChars = { '\\', '/', ':', '*', '?', '<', '>', '|' }; foreach (char c in invalidChars) { if (searchResult.DatabaseName.Contains(c)) { searchResult.DatabaseName = searchResult.DatabaseName.Replace(c.ToString(), " "); } } break; case "seriesid": int id; int.TryParse(subNode.InnerText, out id); searchResult.Id = id; break; case "overview": searchResult.Overview = subNode.InnerText; break; case "firstaired": DateTime airDate; DateTime.TryParse(subNode.InnerText, out airDate); searchResult.DatabaseYear = airDate.Year; break; } } searchResult.DatabaseSelection = (int)TvDataBaseSelection.TheTvDb; searchResults.Add(searchResult); } return(searchResults); }