protected bool Equals(ConnectedCourse other) { return(string.Equals(CourseId, other.CourseId) && string.Equals(CourseId2, other.CourseId2)); }
/// <summary> /// This method updates the (more detailed) properties of a given course such as dates, connected courses, description etc. /// </summary> /// <param name="course">Course for which the information should be updated</param> /// <param name="db">Database context</param> /// <param name="list">List of existing courses</param> /// <param name="catalog">Course catalog</param> /// <param name="isConnectedCourse">Determines if the current parsing happens for a connected course (is used to prevent cirular parsing)</param> /// <returns></returns> public async Task GetCourseDetailAsync(Course course, DatabaseContext db, List <Course> list, CourseCatalog catalog, bool isConnectedCourse = false) { HtmlDocument doc = await GetHtmlDocumentForCourse(course, db); if (doc == null) { return; } var changed = false; //case of isConnectedCourse is set to false (on PAUL website) is not handled if (isConnectedCourse) { if (course.ParsedConnectedCourses.All(c => c.Name.Length > course.Name.Length)) { var valueBefore = course.IsConnectedCourse; course.IsConnectedCourse = false; if (course.IsConnectedCourse != valueBefore) { changed = true; } } else if (course.IsConnectedCourse != isConnectedCourse) { course.IsConnectedCourse = isConnectedCourse; changed = true; } } //Update InternalID if not set before (migration code) if (course.InternalCourseID == null) { course.InternalCourseID = course.Id.Split(',')[1]; changed = true; } //Get Shortname var descr = doc.DocumentNode.GetDescendantsByName("shortdescription").FirstOrDefault(); if (descr != null && course.ShortName != descr.Attributes["value"].Value) { course.ShortName = descr.Attributes["value"].Value; changed = true; } try { //Termine parsen var dates = GetDates(doc, db).ToList(); await UpdateDatesInDatabase(course, dates, db); await UpdateExamDates(doc, db, course); } catch { //if the updating of dates fails, not the whole update should crash PaulRepository.AddLog($"Date parsing failed for course {course.CourseId}", FatalityLevel.Error, "Date parsing"); } //Verbundene Veranstaltungen parsen var divs = doc.DocumentNode.GetDescendantsByClass("dl-ul-listview"); var courses = divs.FirstOrDefault(l => l.InnerHtml.Contains("Veranstaltung anzeigen"))?.ChildNodes.Where(l => l.Name == "li" && l.InnerHtml.Contains("Veranstaltung anzeigen")); if (courses != null) { foreach (var c in courses) { var text = c.Descendants().First(n => n.Name == "strong")?.InnerText; var name = text.Split(new[] { ' ' }, 2)[1]; var id = text.Split(new[] { ' ' }, 2)[0]; var url = c.Descendants().First(n => n.Name == "a")?.Attributes["href"].Value; var docent = c.Descendants().Where(n => n.Name == "p").Skip(2).First().InnerText; await _writeLock.WaitAsync(); Course c2 = list.FirstOrDefault(co => co.Id == $"{course.Catalogue.InternalID},{id}"); if (c2 == null) { c2 = new Course { Name = name, TrimmedUrl = url, Catalogue = course.Catalogue, Id = $"{course.Catalogue.InternalID},{id}" }; //db.Courses.Add(c2); db.Entry(c2).State = EntityState.Added; list.Add(c2); } //prevent that two separate threads add the connected courses if (course.Id != c2.Id && !course.ParsedConnectedCourses.Any(co => co.Id == c2.Id) && !c2.ParsedConnectedCourses.Any(co => co.Id == course.Id)) { var con1 = new ConnectedCourse { CourseId = course.Id, CourseId2 = c2.Id }; course.ParsedConnectedCourses.Add(c2); db.ConnectedCourses.Add(con1); var con2 = new ConnectedCourse { CourseId = c2.Id, CourseId2 = course.Id }; c2.ParsedConnectedCourses.Add(course); db.ConnectedCourses.Add(con2); } _writeLock.Release(); } } //Gruppen parsen var groups = divs.FirstOrDefault(l => l.InnerHtml.Contains("Kleingruppe anzeigen"))?.ChildNodes.Where(l => l.Name == "li"); if (groups != null) { var parsedTutorials = groups.Select(group => { var name = group.Descendants().First(n => n.Name == "strong")?.InnerText; var url = group.Descendants().First(n => n.Name == "a")?.Attributes["href"].Value; return(new Course { Id = course.Id + $",{name}", Name = name, TrimmedUrl = url, CourseId = course.Id, IsTutorial = true, Catalogue = catalog }); }); foreach (var parsedTutorial in parsedTutorials) { var tutorial = course.ParsedTutorials.FirstOrDefault(t => t == parsedTutorial); if (tutorial != null) { tutorial.NewUrl = parsedTutorial.TrimmedUrl; } } var newTutorials = parsedTutorials.Except(course.ParsedTutorials).ToList(); if (newTutorials.Any()) { await _writeLock.WaitAsync(); //db.Courses.AddRange(newTutorials); foreach (var t in newTutorials) { var entry = db.Entry(t); if (entry.State != EntityState.Added) { entry.State = EntityState.Added; } } course.ParsedTutorials.AddRange(newTutorials); _writeLock.Release(); } var oldTutorials = course.ParsedTutorials.Except(parsedTutorials).ToList(); if (oldTutorials.Any() && parsedTutorials.Any()) { await _writeLock.WaitAsync(); await db.Database.ExecuteSqlCommandAsync($"DELETE FROM Date Where CourseId IN ({string.Join(",", oldTutorials.Select(o => "'" + o.Id + "'"))})"); var selectedCourses = db.SelectedCourses.Where(p => oldTutorials.Any(o => o.Id == p.CourseId)).Include(s => s.Users).ThenInclude(u => u.User).ToList(); foreach (var selectedCourseUser in selectedCourses.SelectMany(s => s.Users)) { await db.Database.ExecuteSqlCommandAsync($"DELETE FROM SelectedCourseUser Where UserId IN ({selectedCourseUser.User.Id}) And SelectedCourseId IN ({string.Join(",", selectedCourses.Select(s => "'" + s.Id + "'"))}) "); } await db.Database.ExecuteSqlCommandAsync($"DELETE FROM SelectedCourse Where CourseId IN ({string.Join(",", oldTutorials.Select(o => "'" + o.Id + "'"))})"); await db.Database.ExecuteSqlCommandAsync($"DELETE FROM Course Where Id IN ({string.Join(",", oldTutorials.Select(o => "'" + o.Id + "'"))})"); foreach (var old in oldTutorials) { course.ParsedTutorials.Remove(old); } _writeLock.Release(); } } //mark course as modified if (changed) { await _writeLock.WaitAsync(); db.ChangeTracker.TrackObject(course); _writeLock.Release(); } }