Beispiel #1
0
        public ActionResult GetSchedules()
        {
            var schedules = PaulRepository.GetSchedules();
            var json      = Json(schedules);

            return(json);
        }
Beispiel #2
0
        // Configure is called after ConfigureServices is called.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
#if DEBUG
            // Configure the HTTP request pipeline.
            app.UseDeveloperExceptionPage();
#endif

            loggerFactory.AddConsole();
            loggerFactory.AddDebug(LogLevel.Debug);



            // Add the following to the request pipeline only in development environment.
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
            }
            else
            {
                // Add Error handling middleware which catches all application specific errors and
                // send the request to the following path or controller action.
                //app.UseExceptionHandler("/Home/Error");
            }
            app.UseSignalR();

            app.Use(next => async context =>
            {
                context.Response.Headers.Add(new KeyValuePair <string, StringValues>("Access-Control-Allow-Origin", AllowedOrigins));

                await next.Invoke(context); // call the next guy

                // do some more stuff here as the call is unwinding
            });

            // Add the platform handler to the request pipeline.
            //app.UseIISPlatformHandler();

            // Add static files to the request pipeline.
            app.UseStaticFiles();
            // Add MVC to the request pipeline.
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");

                // Uncomment the following line to add a route for porting Web API 2 controllers.
                // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
            });

            // Add SignalR to the request pipeline.

            var db = new DatabaseContext(PaulRepository.Filename, env.ContentRootPath);
            db.Database.Migrate();
            db.Database.EnsureCreated();
            //Uncomment to see SQL queries
            //db.LogToConsole();
            PaulRepository.BasePath = env.ContentRootPath;
            PaulRepository.Initialize();
        }
Beispiel #3
0
        /// <summary>
        /// This method is used to update the courses of the course catalog in small steps (to not overwelm the server)
        /// </summary>
        /// <param name="db">Database context</param>
        /// <param name="courseList">List of already existing courses (from database)</param>
        /// <param name="c">Course catalog</param>
        /// <returns></returns>
        private async Task UpdateCoursesInDatabase(DatabaseContext db, List <Course> courseList, CourseCatalog c)
        {
            int counter        = 0;
            int stepCount      = 80;
            var stepCourses    = courseList.Take(stepCount);
            var modifiableList = new List <Course>(courseList);

            while (stepCourses.Any())
            {
                counter += stepCourses.Count();
                //Get details for all courses
                await Task.WhenAll(stepCourses.Select(course => GetCourseDetailAsync(course, db, modifiableList, c)));

                await Task.WhenAll(stepCourses.Select(course => GetTutorialDetailAsync(course, db)));

                //Get details for connected courses
                var connectedCourses = stepCourses.SelectMany(s => s.ParsedConnectedCourses).Distinct();
                await Task.WhenAll(connectedCourses.Select(course => GetCourseDetailAsync(course, db, modifiableList, c, true)));

                await Task.WhenAll(connectedCourses.Select(course => GetTutorialDetailAsync(course, db)));

                await db.SaveChangesAsync();

                PaulRepository.AddLog($"Completed parsing of {counter}/{courseList.Count} courses", FatalityLevel.Normal, "");
                stepCourses = courseList.Skip(counter).Take(stepCount);
            }
        }
Beispiel #4
0
        /// <summary>
        /// Upadtes the category filters for a given course catalog
        /// </summary>
        /// <param name="cat">Course catalog</param>
        /// <param name="allCourses">List of existing courses</param>
        /// <param name="db">Database context</param>
        /// <returns></returns>
        private async Task UpdateCategoryFiltersForCatalog(CourseCatalog cat, List <Course> allCourses, DatabaseContext db)
        {
            var doc = await SendGetRequest(_categoryUrl);

            var navi  = doc.GetElementbyId("pageTopNavi");
            var links = navi.Descendants().Where((d) => d.Name == "a" && d.Attributes.Any(a => a.Name == "class" && a.Value.Contains("depth_2")));
            var modifiedCatalogText = cat.ShortTitle.Replace("WS", "Winter").Replace("SS", "Sommer");

            if (links.Any(l => l.InnerText == modifiedCatalogText))
            {
                var url = links.First(l => l.InnerText == modifiedCatalogText).Attributes["href"].Value;
                doc = await SendGetRequest(BaseUrl + WebUtility.HtmlDecode(url));

                var nodes            = GetNodesForCategories(doc);
                var parentCategories = await UpdateCategoriesInDatabase(db, null, nodes, doc, true, cat, allCourses);

                do
                {
                    foreach (var category in parentCategories.Select(e => e.Value))
                    {
                        PaulRepository.AddLog($"Currently at filter {category.Title}", FatalityLevel.Verbose, "");
                    }
                    var tasks = parentCategories.Keys.Select(node => UpdateCategoryForHtmlNode(db, node, parentCategories[node], cat, allCourses)).ToList();
                    parentCategories = (await Task.WhenAll(tasks)).SelectMany(r => r).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
                    await db.SaveChangesAsync();
                } while (parentCategories.Keys.Any());
            }
        }
Beispiel #5
0
        /// <summary>
        /// This method is used to parse the database from a given website
        /// </summary>
        /// <param name="doc">HtmlDocument to parse</param>
        /// <param name="db">Database context</param>
        /// <returns></returns>
        static List <Date> GetDates(HtmlDocument doc, DatabaseContext db)
        {
            var list   = new List <Date>();
            var tables = doc.DocumentNode.GetDescendantsByClass("tb list rw-table rw-all");
            var table  = tables.FirstOrDefault(t => t.ChildNodes.Any(n => n.InnerText == "Termine"));

            if (table == null)
            {
                return(list);
            }
            var trs = table.ChildNodes.Where(n => n.Name == "tr").Skip(1);

            if (!table.InnerHtml.Contains("Es liegen keine Termine vor"))
            {
                foreach (var tr in trs)
                {
                    if (!tr.GetDescendantsByName("appointmentDate").First().InnerText.Contains('*'))
                    {
                        //Umlaute werden falsch geparst, deshalb werden Umlaute ersetzt
                        var date = DateTimeOffset.Parse(tr.GetDescendantsByName("appointmentDate").First().InnerText.Replace("Mär", "Mar"), new CultureInfo("de-DE"));

                        if (_timezone != null)
                        {
                            var tzOffset = _timezone.GetUtcOffset(date.DateTime);
                            date = new DateTimeOffset(date.DateTime, tzOffset);
                        }
                        else
                        {
                            PaulRepository.AddLog("Timezone not present", FatalityLevel.Critical, "");
                        }
                        var fromNode = tr.GetDescendantsByName("appointmentTimeFrom").First();
                        var toNode   = tr.GetDescendantsByName("appointmentDateTo").First();

                        var            from = date.Add(TimeSpan.Parse(fromNode.InnerText));
                        DateTimeOffset to;
                        if (toNode.InnerText.Trim() != "24:00")
                        {
                            to = date.Add(TimeSpan.Parse(toNode.InnerText));
                        }
                        else
                        {
                            to = date.Add(new TimeSpan(23, 59, 59));
                        }
                        var room = tr.GetDescendantsByName("appointmentRooms").FirstOrDefault()?.InnerText;
                        if (room == null)
                        {
                            room = tr.Descendants("td").Skip(4).FirstOrDefault()?.InnerText.TrimWhitespace();
                        }

                        var instructor = tr.GetDescendantsByName("appointmentInstructors").First().InnerText.TrimWhitespace();
                        list.Add(new Date {
                            From = from, To = to, Room = room, Instructor = instructor
                        });
                    }
                }
            }

            return(list);
        }
Beispiel #6
0
        /// <summary>
        /// Updates all courses in the database for a given course catalog
        /// </summary>
        /// <param name="catalog">Catalog for which the courses should be updated</param>
        /// <param name="allCourses">List of courses that have already been parsed (from database)</param>
        /// <param name="db">Database context</param>
        public async Task UpdateCoursesInCourseCatalog(CourseCatalog catalog, List <Course> allCourses, DatabaseContext db)
        {
            var counter = 1;

            try
            {
                PaulRepository.AddLog($"Update for {catalog.ShortTitle} started!", FatalityLevel.Normal, "");

                var courseList = allCourses.Where(co => co.Catalogue.InternalID == catalog.InternalID && !co.IsTutorial).ToList();
                //ensure that every course has the right instance of the course catalog so that we don't get a tracking exception
                courseList.ForEach(course => course.Catalogue = catalog);

                var messages = await Task.WhenAll(new[] { "1", "2" }.Select(useLogo => SendPostRequest(catalog.InternalID, "", useLogo)));

                foreach (var message in messages)
                {
                    var document = new HtmlDocument();
                    document.Load(await message.Content.ReadAsStreamAsync());
                    var pageResult = GetPageSearchResult(document, counter);
                    if (pageResult.HasCourses)
                    {
                        await GetCourseList(db, document, catalog, courseList, updateUrls : true);
                    }


                    while (pageResult.LinksToNextPages.Count > 0)
                    {
                        var docs = await Task.WhenAll(pageResult.LinksToNextPages.Select(s => SendGetRequest(BaseUrl + s)));

                        //Getting course list for at most 3 pages
                        var courses = await Task.WhenAll(docs.Select(d => GetCourseList(db, d, catalog, courseList, updateUrls: true)));

                        counter   += pageResult.LinksToNextPages.Count;
                        pageResult = GetPageSearchResult(docs.Last(), counter);
                    }
                }

                await UpdateCoursesInDatabase(db, courseList, catalog);

                PaulRepository.AddLog($"Update for {catalog.ShortTitle} completed!", FatalityLevel.Normal, "");
            }
            catch (DbUpdateConcurrencyException e)
            {
                //db.ChangeTracker.Entries().First(entry => entry.Equals(e)).State == EntityState.Detached;
                var str = new StringBuilder();
                foreach (var entry in e.Entries)
                {
                    str.AppendLine("Entry involved: " + entry.Entity + " Type: " + entry.Entity.GetType().Name);
                }

                PaulRepository.AddLog($"DbUpdateConcurrency failure: {e} in {catalog.Title} at round {counter}", FatalityLevel.Critical, "Nightly Update");
                PaulRepository.AddLog($"DbUpdateConcurrency failure: {str} in {catalog.Title}", FatalityLevel.Critical, "Nightly Update");
            }
            catch (Exception e)
            {
                PaulRepository.AddLog("Update failure: " + e + " in " + catalog.Title, FatalityLevel.Critical, "Nightly Update");
            }
        }
Beispiel #7
0
        public async Task <ActionResult> ExportShortCatalogueTitles(string token)
        {
            if (PaulRepository.IsUpdating)
            {
                return(StatusCode(503)); // Service unavailable
            }

            var catalogues = await PaulRepository.GetCourseCataloguesAsync();

            return(Json(catalogues.Select(c => c.ShortTitle)));
        }
Beispiel #8
0
        /// <summary>
        /// RPC-method for optaining metadata, such as the course catalog,
        /// from schedules specified by their IDs.
        /// </summary>
        /// <param name="scheduleIds">Schedule IDs</param>
        public object[] GetScheduleMetadata(string[] scheduleIds)
        {
            var metadata = PaulRepository.GetSchedules(scheduleIds)
                           .Select(schedule => new
            {
                Id    = schedule.Id,
                Title = schedule.Name,
                Users = string.Join(", ", schedule.Users.Select(user => user.Name))
            });

            return(metadata.ToArray());
        }
Beispiel #9
0
        public ActionResult ExportSchedule(string id, string username)
        {
            var name     = username.FromBase64String();
            var schedule = PaulRepository.GetSchedule(id);

            if (schedule != null && schedule.Users.Any(u => u.Name == name))
            {
                return(File(Encoding.UTF8.GetBytes(ScheduleExporter.ExportSchedule(schedule, name)), "text/calendar", $"schedule{schedule.Id}_{name}.ics"));
            }
            else
            {
                return(BadRequest());
            }
        }
Beispiel #10
0
 public async Task <ActionResult> UpdateAllCourses()
 {
     try
     {
         await PaulRepository.UpdateAllCoursesAsync();
     }
     catch (Exception e)
     {
         try
         {
             PaulRepository.AddLog(e.ToString(), FatalityLevel.Critical, "Manual Update");
         }
         catch { }
     }
     return(Ok());
 }
Beispiel #11
0
        private async Task <ActionResult> TestDatabaseStoring()
        {
            //var courses = PaulRepository.GetLocalCourses("Stochastik");
            //Schedule s = new Schedule();
            //var user = new User() { Name = "Test" };
            //s.User.Add(user);
            //await PaulRepository.StoreInDatabaseAsync(s, Microsoft.Data.Entity.GraphBehavior.IncludeDependents);
            //courses.ForEach((async c => await PaulRepository.AddCourseToSchedule(s, c.Id, new List<int>() { user.Id })));
            var s = PaulRepository.GetSchedules().Last();
            await PaulRepository.RemoveScheduleAsync(s);

            //var selectedCourse = s.SelectedCourses.First();
            //var user = selectedCourse.Users.First();
            //await PaulRepository.RemoveUserFromSelectedCourseAsync(selectedCourse, user);
            //await PaulRepository.AddUserToSelectedCourseAsync(selectedCourse, user.User);

            return(Ok());
        }
Beispiel #12
0
        /// <summary>
        /// This method checks PAUL for the course catalogs that are available. It parses PAUL's search website and considers the entries in the dropdown that contain the name "Vorlesungsverzeichnis"
        /// </summary>
        /// <returns>List of course catalogs</returns>
        public async Task <IEnumerable <CourseCatalog> > GetAvailableCourseCatalogs()
        {
            var doc = new HtmlDocument();

            doc.Load(await _client.GetStreamAsync(_searchUrl), Encoding.UTF8);
            var catalogue = doc.GetElementbyId("course_catalogue");

            if (catalogue == null)
            {
                PaulRepository.AddLog("No course catalogs could be found! Maybe the search url has changed?", FatalityLevel.Critical, "");
                throw new ArgumentException("No course catalogs could be found in PAUL!", nameof(_searchUrl));
            }

            var options = catalogue.Descendants().Where(c => c.Name == "option" && c.Attributes.Any(a => a.Name == "title" && a.Value.Contains("Vorlesungsverzeichnis")));

            return(options.Select(n => new CourseCatalog {
                InternalID = n.Attributes["value"].Value, Title = n.Attributes["title"].Value
            }));
        }
        public void UpdateSearchResults(ErrorReporter errorReporter)
        {
            if (SearchQuery == null || SearchQuery.Count() < 3)
            {
                SearchResults.Clear();
                errorReporter.Throw(
                    new InvalidOperationException("Search query is null or too short"),
                    UserErrorsViewModel.SearchQueryTooShortMessage);
            }

            var results = PaulRepository.SearchCourses(SearchQuery, _catalog).Take(searchResultCount);

            SearchResults.Clear();
            SearchResults.AddRange(results.Select(course =>
            {
                var added = _schedule.SelectedCourses.Any(s => s.CourseId == course.Id);
                return(new SearchResultViewModel(course, added));
            }));
        }
Beispiel #14
0
        /// <summary>
        /// Adds the specified user to the list of current users.
        /// The user name can either be a known user name (i.e. a user
        /// that has already joined the schedule sometime before) or
        /// a new user name (in this case the user's info is added to the DB).
        /// </summary>
        /// <param name="userVM"></param>
        public async Task AddUserAsync(UserViewModel userVM, ErrorReporter errorReporter)
        {
            if (userVM == null)
            {
                errorReporter.Throw(
                    new ArgumentNullException(nameof(userVM),
                                              UserErrorsViewModel.GenericErrorMessage));
            }

            if (string.IsNullOrWhiteSpace(userVM.Name))
            {
                errorReporter.Throw(
                    new ArgumentException("The name of the specified user is invalid", nameof(userVM)),
                    UserErrorsViewModel.UserNameInvalidMessage);
            }

            if (Users.Any(o => o.Name == userVM.Name))
            {
                errorReporter.Throw(
                    new ArgumentException($"The user name '{userVM.Name}' is already in use", nameof(userVM.Name)),
                    UserErrorsViewModel.UserNameAlreadyInUseMessage);
            }

            if (Schedule.Users.Any(o => o.Name == userVM.Name))
            {
                // This is a known user name
                Users.Add(userVM);
                AvailableUserNames.Remove(userVM.Name);
            }
            else
            {
                // The client is a new user
                Users.Add(userVM);

                // Create new known user in DB
                var dbUser = new User {
                    Name = userVM.Name
                };
                await PaulRepository.AddUserToScheduleAsync(Schedule, dbUser);
            }
        }
Beispiel #15
0
        public async Task <ActionResult> TestParsing()
        {
            var searchString  = "L.104.12270";
            var course        = PaulRepository.Courses.FirstOrDefault(c => c.Id.Contains(searchString));
            var parser        = new PaulParser();
            var courseCatalog = (await PaulRepository.GetCourseCataloguesAsync()).First();
            var httpClient    = new HttpClient();
            var response      = await httpClient.GetAsync("https://paul.uni-paderborn.de/scripts/mgrqispi.dll?APPNAME=CampusNet&PRGNAME=COURSEDETAILS&ARGUMENTS=-N000000000000001,-N000443,-N0,-N360765878897321,-N360765878845322,-N0,-N0,-N3,-A4150504E414D453D43616D7075734E6574265052474E414D453D414354494F4E26415247554D454E54533D2D4179675978627166464D546570353271395952533363394A33415A7A346450656F347A72514F7661686C327A34706559594179354333386A6C636975396B71334456666E492D4B6E6872493545326F45672E74437349727130616D55426B4B37627573455048356D4351544F42326B4759696B507333596C316E7555742E6E3D3D");

            var doc = new HtmlDocument();

            using (var db = new DatabaseContext(PaulRepository.Filename, ""))
            {
                db.Attach(courseCatalog);
                doc.LoadHtml(await response.Content.ReadAsStringAsync());
                await parser.UpdateExamDates(doc, db, course);

                await db.SaveChangesAsync();
            }
            return(Ok());
        }
Beispiel #16
0
        /// <summary>
        /// Used to update all category filters for every course catalog
        /// </summary>
        /// <param name="allCourses">List of existing courses</param>
        /// <param name="context">Database context</param>
        /// <returns></returns>
        public async Task UpdateCategoryFilters(List <Course> allCourses, DatabaseContext context)
        {
            PaulRepository.AddLog("Update for category filters has started!", FatalityLevel.Normal, "Update category filters");
            var catalogues = (await PaulRepository.GetCourseCataloguesAsync(context));

            foreach (var cat in catalogues)
            {
                try
                {
                    PaulRepository.AddLog($"Update for course catalog {cat.ShortTitle} has started!", FatalityLevel.Normal, "");
                    await UpdateCategoryFiltersForCatalog(cat, allCourses, context);

                    PaulRepository.AddLog($"Update for course catalog {cat.ShortTitle} completed!", FatalityLevel.Normal, "");
                }
                catch (Exception e)
                {
                    PaulRepository.AddLog($"Updating Categories failed: {e}", FatalityLevel.Critical, "Nightly Update");
                }
            }

            PaulRepository.AddLog("Update for category filters completed!", FatalityLevel.Normal, "Update category filters");
        }
Beispiel #17
0
        /// <summary>
        /// Creates a new empty schedule in the database.
        /// </summary>
        /// <returns>A ViewModel that represents the new schedule</returns>
        public async Task <SharedScheduleViewModel> CreateScheduleAsync(string catalogId, ErrorReporter errorReporter)
        {
            var catalogs = await PaulRepository.GetCourseCataloguesAsync();

            var selectedCatalog = catalogs.FirstOrDefault(o => o.InternalID == catalogId);

            if (selectedCatalog == null)
            {
                errorReporter.Throw(
                    new ArgumentException($"A CourseCatalog with the specified ID '{catalogId}' does not exist"),
                    UserErrorsViewModel.GenericErrorMessage);
            }

            // Create a new schedule in DB
            var schedule = await PaulRepository.CreateNewScheduleAsync(selectedCatalog);

            var vm = new SharedScheduleViewModel(schedule);

            _loadedSchedules.Add(schedule.Id, vm);

            return(vm);
        }
Beispiel #18
0
        /// <summary>
        /// Loads the schedule with the specified ID either from
        /// cache or from the database.
        /// </summary>
        /// <param name="scheduleId">Schedule ID</param>
        /// <returns>
        /// A ViewModel representing the schedule or null if no
        /// schedule with the specified ID exists
        /// </returns>
        public SharedScheduleViewModel GetOrLoadSchedule(string scheduleId)
        {
            SharedScheduleViewModel scheduleVm;

            if (_loadedSchedules.TryGetValue(scheduleId, out scheduleVm))
            {
                return(scheduleVm);
            }
            else
            {
                var schedule = PaulRepository.GetSchedule(scheduleId);

                if (schedule == null)
                {
                    // Schedule with that ID does not exist
                    return(null);
                }

                var vm = new SharedScheduleViewModel(schedule);
                _loadedSchedules.Add(scheduleId, vm);
                return(vm);
            }
        }
Beispiel #19
0
 public async Task RefreshAvailableSemestersAsync()
 {
     AvailableSemesters = (await PaulRepository.GetCourseCataloguesAsync()).
                          Where(c => PaulRepository.Courses.Any(course => course.Catalogue.Equals(c)))
                          .OrderByDescending(catalog => GetCourseCatalogOrder(catalog));
 }
Beispiel #20
0
        /// <summary>
        /// RPC-method for removing a course from the schedule
        /// </summary>
        /// <param name="courseId"></param>
        /// <returns></returns>
        public async Task RemoveCourse(string courseId)
        {
            using (var errorReporter = new ErrorReporter(s => CallingClient.Errors.ScheduleMessage = s))
            {
                var course = PaulRepository.Courses.FirstOrDefault(c => c.Id == courseId);

                if (course == null)
                {
                    errorReporter.Throw(
                        new ArgumentException("Course not found", nameof(courseId)),
                        UserErrorsViewModel.GenericErrorMessage);
                }

                var schedule = CallingClient.SharedScheduleVM.Schedule;

                await CallingClient.SharedScheduleVM.TimetableHubSemaphore.WaitAsync();

                try
                {
                    var selectedCourse = schedule.SelectedCourses.FirstOrDefault(c => c.CourseId == courseId);
                    if (selectedCourse != null)
                    {
                        //Find selected Tutorials
                        var selectedTutorials = schedule.SelectedCourses
                                                .Where(sel => selectedCourse.Course.Tutorials.Any(it => it.Id == sel.CourseId))
                                                .Select(s => s.Course)
                                                .ToList();

                        var courses = selectedCourse.Course.ConnectedCourses
                                      .Concat(selectedTutorials)
                                      .Concat(new[] { selectedCourse.Course });

                        foreach (var course1 in courses)
                        {
                            try
                            {
                                if (course1.Tutorials.Any())
                                {
                                    // Remove all pending tutorials from all TailoredSchedules
                                    foreach (var user in CallingClient.SharedScheduleVM.Users)
                                    {
                                        user.TailoredScheduleVM.RemovePendingTutorials(course1.Tutorials.FirstOrDefault(), errorReporter);
                                    }
                                }

                                await PaulRepository.RemoveCourseFromScheduleAsync(schedule, course1.Id);

                                UpdateAddedStateInSearchResultsAndCourseList(course1, isAdded: false);
                            }
                            catch (NullReferenceException e)
                            {
                                // This is just for purposes of compatibility
                                // with development versions. Can be safely removed
                                // after product launch
                                PaulRepository.AddLog(e.Message, FatalityLevel.Normal, typeof(TimetableHub).Name);
                            }
                        }

                        UpdateTailoredViewModels();
                    }
                    else if (course.IsTutorial)
                    {
                        // The user has decided to remove a tutorial before joining one
                        CallingClient.TailoredScheduleVM.RemovePendingTutorials(course, errorReporter);
                        UpdateTailoredViewModels();
                    }
                    else
                    {
                        errorReporter.Throw(
                            new ArgumentException("Course not found in the schedule!"),
                            UserErrorsViewModel.GenericErrorMessage);
                    }
                }
                finally
                {
                    CallingClient.SharedScheduleVM.TimetableHubSemaphore.Release();
                }
            }
        }
Beispiel #21
0
        /// <summary>
        /// RPC-method for adding a course to the schedule and for
        /// adding the calling user to a course that someone else
        /// has already added.
        /// </summary>
        /// <param name="courseId">Course ID</param>
        /// <returns></returns>
        public async Task AddCourse(string courseId)
        {
            using (var errorReporter = new ErrorReporter(s => CallingClient.Errors.ScheduleMessage = s))
            {
                var course = PaulRepository.Courses.FirstOrDefault(c => c.Id == courseId);

                if (course == null)
                {
                    errorReporter.Throw(
                        new ArgumentException("Course not found", nameof(courseId)),
                        UserErrorsViewModel.GenericErrorMessage);
                }


                var schedule = CallingClient.SharedScheduleVM.Schedule;

                await CallingClient.SharedScheduleVM.TimetableHubSemaphore.WaitAsync();

                try
                {
                    var selectedCourse = schedule.SelectedCourses.FirstOrDefault(c => c.CourseId == courseId);

                    if (course.IsTutorial)
                    {
                        // The user has decided to select a pending tutorial or one
                        // that was already selected by another user, so remove all pending
                        // tutorials of this course
                        CallingClient.TailoredScheduleVM.RemovePendingTutorials(course, errorReporter);

                        // Remove user from other possibly selected tutorial
                        var parentCourse          = schedule.SelectedCourses.First(sel => sel.Course.AllTutorials.SelectMany(it => it).Contains(course));
                        var otherSelectedTutorial = parentCourse.Course.AllTutorials
                                                    .FirstOrDefault(group => group.Contains(course))
                                                    ?.FirstOrDefault(tut => schedule.SelectedCourses.Any(sel => Equals(tut, sel.Course) && sel.Users.Select(it => it.User).Contains(CallingClient.User)));
                        if (otherSelectedTutorial != null)
                        {
                            await RemoveUserFromCourse(otherSelectedTutorial.Id, acquireSemaphore : false);
                        }


                        //If the user hasn't selected the parent course of the tutorial, it will be added here
                        if (parentCourse.Users.All(u => u.User.Name != CallingClient.Name))
                        {
                            var connectedCourses = parentCourse.Course.ConnectedCourses.Concat(new[] { parentCourse.Course });
                            foreach (var c in connectedCourses)
                            {
                                await PaulRepository.AddUserToSelectedCourseAsync(schedule.SelectedCourses.First(s => s.CourseId == c.Id), CallingClient.User);
                            }
                        }
                    }

                    if (selectedCourse == null)
                    {
                        var connectedCourses = course.ConnectedCourses.Concat(new[] { course })
                                               .Select(it => PaulRepository.CreateSelectedCourse(schedule, CallingClient.User, it))
                                               .ToList();

                        await PaulRepository.AddCourseToScheduleAsync(schedule, connectedCourses);

                        AddTutorialsForCourse(courseId);
                    }
                    else if (selectedCourse.Users.All(u => u.User != CallingClient.User))
                    {
                        // The course has already been added to the schedule by someone else.
                        // Add the calling user to the selected course (if not yet done).

                        await PaulRepository.AddUserToSelectedCourseAsync(selectedCourse, CallingClient.User);

                        var connectedCourseIds = selectedCourse.Course.ConnectedCourses.Select(it => it.Id).ToList();

                        var selectedConnectedCourses = schedule.SelectedCourses
                                                       .Where(selCo => connectedCourseIds.Contains(selCo.CourseId));

                        foreach (var connectedCourse in selectedConnectedCourses)
                        {
                            await PaulRepository.AddUserToSelectedCourseAsync(connectedCourse, CallingClient.User);
                        }
                    }

                    UpdateAddedStateInSearchResultsAndCourseList(course, isAdded: true);
                }
                finally
                {
                    CallingClient.SharedScheduleVM.TimetableHubSemaphore.Release();
                }
                UpdateTailoredViewModels();
            }
        }
Beispiel #22
0
        public async Task ChangeScheduleName(string name)
        {
            await PaulRepository.ChangeScheduleName(Schedule, name);

            RaisePropertyChanged(nameof(Name));
        }
Beispiel #23
0
        /// <summary>
        /// Removes the calling user from the specified selected course.
        /// If after the removal no other user has selected the course,
        /// the course is removed from the schedule.
        /// </summary>
        /// <remarks>
        /// If the user has not selected the course with the specified ID,
        /// nothing happens.
        /// </remarks>
        /// <param name="courseId">Course ID</param>
        /// <returns></returns>
        public async Task RemoveUserFromCourse(string courseId, bool acquireSemaphore = true)
        {
            using (var errorReporter = new ErrorReporter(s => CallingClient.Errors.ScheduleMessage = s))
            {
                if (PaulRepository.Courses.All(c => c.Id != courseId))
                {
                    errorReporter.Throw(
                        new ArgumentException("Course not found", nameof(courseId)),
                        UserErrorsViewModel.GenericErrorMessage);
                }

                var schedule = CallingClient.SharedScheduleVM.Schedule;

                var selectedCourse = schedule.SelectedCourses
                                     .FirstOrDefault(c => c.CourseId == courseId);

                if (selectedCourse == null)
                {
                    errorReporter.Throw(
                        new ArgumentException("Course not found in the schedule!"),
                        UserErrorsViewModel.GenericErrorMessage);
                }

                var selectedConnectedCourses = schedule.SelectedCourses
                                               .Where(sel => selectedCourse.Course.ConnectedCourses.Any(it => it.Id == sel.CourseId))
                                               .ToList();

                if (acquireSemaphore)
                {
                    await CallingClient.SharedScheduleVM.TimetableHubSemaphore.WaitAsync();
                }
                try
                {
                    var selectedCourseUser = selectedCourse.Users.FirstOrDefault(o => o.User == CallingClient.User);

                    //Find selected Tutorials
                    var selectedTutorials = schedule.SelectedCourses
                                            .Where(sel => selectedCourse.Course.Tutorials
                                                   .Concat(selectedConnectedCourses.SelectMany(s => s.Course.Tutorials))
                                                   .Any(it => it.Id == sel.CourseId))
                                            .ToList();

                    if (selectedCourseUser != null)
                    {
                        // Remove user from selected courses
                        foreach (var sel in selectedConnectedCourses.Concat(selectedTutorials).Concat(new[] { selectedCourse }))
                        {
                            await PaulRepository.RemoveUserFromSelectedCourseAsync(sel, selectedCourseUser);
                        }
                    }

                    if (!selectedCourse.Users.Any())
                    {
                        var firstTutorials = selectedCourse.Course.Tutorials.Take(1)
                                             .Concat(selectedConnectedCourses.SelectMany(s => s.Course.Tutorials.Take(1)));

                        // Remove all Pending Tutorials from all TailoredSchedules
                        foreach (var user in CallingClient.SharedScheduleVM.Users)
                        {
                            foreach (var t in firstTutorials)
                            {
                                user.TailoredScheduleVM.RemovePendingTutorials(t, errorReporter);
                            }
                        }

                        // The course is no longer selected by anyone
                        // -> Remove the whole course from schedule
                        foreach (var sel in selectedConnectedCourses.Concat(selectedTutorials).Concat(new[] { selectedCourse }))
                        {
                            await PaulRepository.RemoveCourseFromScheduleAsync(schedule, sel.CourseId);
                        }
                    }

                    UpdateAddedStateInSearchResultsAndCourseList(selectedCourse.Course, isAdded: false);
                    UpdateTailoredViewModels();
                }
                finally
                {
                    if (acquireSemaphore)
                    {
                        CallingClient.SharedScheduleVM.TimetableHubSemaphore.Release();
                    }
                }
            }
        }
Beispiel #24
0
        /// <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();
            }
        }
Beispiel #25
0
        /// <summary>
        /// Update the exam dates in the database for a given course
        /// </summary>
        /// <param name="doc">HtmlDocment used for parsing</param>
        /// <param name="db">Database context</param>
        /// <param name="course">Relevant course</param>
        /// <returns></returns>
        public async Task UpdateExamDates(HtmlDocument doc, DatabaseContext db, Course course)
        {
            try
            {
                var list   = new List <ExamDate>();
                var node   = doc.GetElementbyId("contentlayoutleft");
                var tables = node.ChildNodes.Where(n => n.Name == "table");
                if (tables.Count() >= 5)
                {
                    var table = tables.ElementAt(4);
                    var trs   = table.ChildNodes.Where(n => n.Name == "tr").Skip(1);
                    foreach (var tr in trs)
                    {
                        var dateString = tr.GetDescendantsByName("examDateTime").FirstOrDefault();
                        if (dateString != null)
                        {
                            var name      = tr.GetDescendantsByName("examName").First().InnerText.TrimWhitespace();
                            var lastIndex = dateString.InnerText.LastIndexOf(' ');
                            var date      = DateTimeOffset.Parse(dateString.InnerText.Substring(0, lastIndex).Replace("Mär", "Mar"), new CultureInfo("de-DE"));
                            if (_timezone != null)
                            {
                                var tzOffset = _timezone.GetUtcOffset(date.DateTime);
                                date = new DateTimeOffset(date.DateTime, tzOffset);
                            }
                            else
                            {
                                PaulRepository.AddLog("Timezone not present", FatalityLevel.Critical, "");
                            }

                            var            time     = dateString.InnerText.Substring(lastIndex, dateString.InnerText.Length - lastIndex);
                            var            from     = date.Add(TimeSpan.Parse(time.Split('-')[0]));
                            var            toString = time.Split('-')[1];
                            DateTimeOffset to;
                            if (toString.Trim() != "24:00")
                            {
                                to = date.Add(TimeSpan.Parse(toString));
                            }
                            else
                            {
                                to = date.Add(new TimeSpan(23, 59, 59));
                            }

                            var instructor = tr.GetDescendantsByClass("tbdata")[3].InnerText.TrimWhitespace();
                            list.Add(new ExamDate {
                                From = from, To = to, Description = name, Instructor = instructor
                            });
                        }
                    }

                    await _writeLock.WaitAsync();

                    var difference = list.Except(course.ExamDates).ToList();
                    var old        = course.ExamDates.Except(list).ToList();


                    if (difference.Any() && list.Any())
                    {
                        difference.ForEach(d => d.CourseId = course.Id);
                        foreach (var d in difference)
                        {
                            db.Entry(d).State = EntityState.Added;
                        }
                    }

                    if (old.Any() && list.Any())
                    {
                        await db.Database.ExecuteSqlCommandAsync($"Delete from ExamDate Where Id IN ({string.Join(",", old.Select(d => d.Id))})");
                    }

                    _writeLock.Release();
                }
            }
            catch (Exception)
            {
            }
        }
Beispiel #26
0
        // GET: api/values


        public async Task <ActionResult> GetCourseCatalogues()
        {
            return(Json(await PaulRepository.GetCourseCataloguesAsync()));
        }
Beispiel #27
0
 public ActionResult ClearLogs()
 {
     PaulRepository.ClearLogs();
     return(Ok());
 }
Beispiel #28
0
 public IActionResult GetLogs() => View("~/Views/Home/Logs.cshtml", PaulRepository.GetLogs());