diff --git a/src/main/kotlin/fi/aalto/cs/apluscourses/api/APlusApi.kt b/src/main/kotlin/fi/aalto/cs/apluscourses/api/APlusApi.kt index 11c5836d2..c2030dd27 100644 --- a/src/main/kotlin/fi/aalto/cs/apluscourses/api/APlusApi.kt +++ b/src/main/kotlin/fi/aalto/cs/apluscourses/api/APlusApi.kt @@ -403,6 +403,7 @@ object APlusApi { body.fullName ?: body.username, body.studentId, body.id, + body.enrolledCourses.map { it.id }, body.staffCourses.map { it.id } ) } @@ -413,6 +414,7 @@ object APlusApi { val fullName: String?, val studentId: String, val id: Long, + val enrolledCourses: List = emptyList(), val staffCourses: List = emptyList(), ) diff --git a/src/main/kotlin/fi/aalto/cs/apluscourses/model/people/User.kt b/src/main/kotlin/fi/aalto/cs/apluscourses/model/people/User.kt index c33c902b7..1519ae326 100644 --- a/src/main/kotlin/fi/aalto/cs/apluscourses/model/people/User.kt +++ b/src/main/kotlin/fi/aalto/cs/apluscourses/model/people/User.kt @@ -1,7 +1,5 @@ package fi.aalto.cs.apluscourses.model.people -import fi.aalto.cs.apluscourses.model.Course - /** * @property userName The name of the user. If the full name is not available, the username is used. * @property studentId The student ID of the user. @@ -12,9 +10,14 @@ class User( val userName: String, val studentId: String, val aplusId: Long, - private val staffCourses: List, + val enrolledCourses: List, + val staffCourses: List, ) { - fun isStaffOf(course: Course): Boolean { - return course.id in staffCourses + fun isStaffOf(courseId: Long): Boolean { + return courseId in staffCourses + } + + fun isEnrolledIn(courseId: Long): Boolean { + return courseId in enrolledCourses } } diff --git a/src/main/kotlin/fi/aalto/cs/apluscourses/services/course/CourseManager.kt b/src/main/kotlin/fi/aalto/cs/apluscourses/services/course/CourseManager.kt index fb9cdaae4..d99143bfe 100644 --- a/src/main/kotlin/fi/aalto/cs/apluscourses/services/course/CourseManager.kt +++ b/src/main/kotlin/fi/aalto/cs/apluscourses/services/course/CourseManager.kt @@ -43,6 +43,7 @@ class CourseManager( var aPlusUrl: String? = null var grading: CourseConfig.Grading? = null var settingsImported = false + var error: Error? = null var missingDependencies = mapOf>>() fun clearAll() { course = null @@ -54,18 +55,23 @@ class CourseManager( } } + enum class Error { + NOT_ENROLLED, + NETWORK_ERROR, + } + val state = State() private val notifiedModules: MutableSet = ConcurrentHashMap.newKeySet() private var job: Job? = null fun restart() { - job?.cancel(CancellationException("test")) + job?.cancel() run() } fun stop() { - job?.cancel(CancellationException("test")) + job?.cancel() } private fun run( @@ -110,71 +116,80 @@ class CourseManager( return } state.authenticated = true + state.error = null try { - runBlocking { - state.grading = courseConfig.grading -// async { - val extraCourseData = APlusApi.Course(courseConfig.id.toLong()).get(project) - val modules = courseConfig.modules.map { - Module( - it.name, - it.url, - it.changelog, - it.version, - it.language, - project - ) - } - val exerciseModules = courseConfig.exerciseModules.map { (exerciseId, languagesToModule) -> - println("exerciseId: $exerciseId languagesToModule: $languagesToModule") - exerciseId to - languagesToModule - .map { (language, moduleName) -> - var module = modules.find { it.name == moduleName } - if (module == null) { - println("Module $moduleName not found") - module = Module( - moduleName, - "", - "", - Version.EMPTY, - null, - project - ) - } - language to module - } - .toMap() - }.toMap() - state.course = Course( - id = courseConfig.id.toLong(), - name = courseConfig.name, - htmlUrl = extraCourseData.htmlUrl, - imageUrl = extraCourseData.image, - endingTime = extraCourseData.endingTime, - languages = courseConfig.languages, - modules = modules, - exerciseModules = exerciseModules, - resourceUrls = CourseConfig.resourceUrls(courseConfig.resources), - optionalCategories = courseConfig.optionalCategories, - autoInstallComponentNames = courseConfig.autoInstall, - replInitialCommands = courseConfig.scalaRepl?.initialCommands, - replAdditionalArguments = courseConfig.scalaRepl?.arguments, - minimumPluginVersion = courseConfig.version, - hiddenElements = courseConfig.hiddenElements, - callbacks = Callbacks.fromJsonObject(courseConfig.callbacks), - project - ) - importSettings(state.course!!) - state.course?.components?.values?.forEach { it.load() } -// } -// async { + state.grading = courseConfig.grading + + val extraCourseData = try { state.user = withContext(Dispatchers.IO) { APlusApi.me().get(project) } -// } + APlusApi.Course(courseConfig.id.toLong()).get(project) + } catch (_: Exception) { + val courseId = courseConfig.id.toLong() + val user = state.user + if (user != null && (!user.isStaffOf(courseId) || !user.isEnrolledIn(courseId))) { + state.error = Error.NOT_ENROLLED + } else { + state.error = Error.NETWORK_ERROR + } + fireCourseUpdated() + throw CancellationException() } + val modules = courseConfig.modules.map { + Module( + it.name, + it.url, + it.changelog, + it.version, + it.language, + project + ) + } + val exerciseModules = courseConfig.exerciseModules.map { (exerciseId, languagesToModule) -> + println("exerciseId: $exerciseId languagesToModule: $languagesToModule") + exerciseId to + languagesToModule + .map { (language, moduleName) -> + var module = modules.find { it.name == moduleName } + if (module == null) { + println("Module $moduleName not found") + module = Module( + moduleName, + "", + "", + Version.EMPTY, + null, + project + ) + } + language to module + } + .toMap() + }.toMap() + state.course = Course( + id = courseConfig.id.toLong(), + name = courseConfig.name, + htmlUrl = extraCourseData.htmlUrl, + imageUrl = extraCourseData.image, + endingTime = extraCourseData.endingTime, + languages = courseConfig.languages, + modules = modules, + exerciseModules = exerciseModules, + resourceUrls = CourseConfig.resourceUrls(courseConfig.resources), + optionalCategories = courseConfig.optionalCategories, + autoInstallComponentNames = courseConfig.autoInstall, + replInitialCommands = courseConfig.scalaRepl?.initialCommands, + replAdditionalArguments = courseConfig.scalaRepl?.arguments, + minimumPluginVersion = courseConfig.version, + hiddenElements = courseConfig.hiddenElements, + callbacks = Callbacks.fromJsonObject(courseConfig.callbacks), + project + ) + importSettings(state.course!!) + state.course?.components?.values?.forEach { it.load() } + val course = state.course ?: return course.autoInstallComponents.forEach { val status = it.loadAndGetStatus() @@ -192,7 +207,9 @@ class CourseManager( state.news = newNews fireNewsUpdated(newNews) - } catch (e: IOException) { + } catch (_: IOException) { + state.error = Error.NETWORK_ERROR + fireCourseUpdated() return } @@ -334,6 +351,10 @@ class CourseManager( return getInstance(project).state.course } + fun error(project: Project): Error? { + return getInstance(project).state.error + } + fun user(project: Project): User? { return getInstance(project).state.user } diff --git a/src/main/kotlin/fi/aalto/cs/apluscourses/services/exercise/ExercisesUpdater.kt b/src/main/kotlin/fi/aalto/cs/apluscourses/services/exercise/ExercisesUpdater.kt index 525af1944..20c242595 100644 --- a/src/main/kotlin/fi/aalto/cs/apluscourses/services/exercise/ExercisesUpdater.kt +++ b/src/main/kotlin/fi/aalto/cs/apluscourses/services/exercise/ExercisesUpdater.kt @@ -55,16 +55,16 @@ class ExercisesUpdater( private var points = -1 fun restart() { - exerciseJob?.cancel(CancellationException("test")) - gradingJob?.cancel(CancellationException("test")) + exerciseJob?.cancel() + gradingJob?.cancel() println("restart") runExerciseUpdater() runGradingUpdater() } fun stop() { - exerciseJob?.cancel(CancellationException("test")) - gradingJob?.cancel(CancellationException("test")) + exerciseJob?.cancel() + gradingJob?.cancel() } private fun runExerciseUpdater( @@ -83,7 +83,7 @@ class ExercisesUpdater( cs.ensureActive() delay(updateInterval) } - } catch (e: CancellationException) { + } catch (_: CancellationException) { println("Task was cancelled 1") } } diff --git a/src/main/kotlin/fi/aalto/cs/apluscourses/ui/overview/OverviewView.kt b/src/main/kotlin/fi/aalto/cs/apluscourses/ui/overview/OverviewView.kt index 4d8619c41..3ee74accf 100644 --- a/src/main/kotlin/fi/aalto/cs/apluscourses/ui/overview/OverviewView.kt +++ b/src/main/kotlin/fi/aalto/cs/apluscourses/ui/overview/OverviewView.kt @@ -54,28 +54,74 @@ class OverviewView(private val project: Project) : SimpleToolWindowPanel(true, t } } - private fun createPanel(loading: Boolean = false): DialogPanel { - if (loading) return loadingPanel() - val authenticated = CourseManager.authenticated(project) ?: return loadingPanel() - if (!authenticated) { + private fun authPanel(): DialogPanel { + return panel { val courseName = CourseManager.getInstance(project).state.courseName ?: "" val tokenForm = TokenForm(project) { update(loading = true) CourseManager.getInstance(project).restart() } - return panel { - panel { - row { - text("Welcome to $courseName").applyToComponent { - font = JBFont.h1() - }.comment("You need to log in to access the course content:") + panel { + row { + text("Welcome to $courseName").applyToComponent { + font = JBFont.h1() + }.comment("You need to log in to access the course content:") + } + with(tokenForm) { + token() + validation() + } + }.customize(UnscaledGaps(16, 32, 16, 32)) + } + } + + private fun networkErrorPanel(): DialogPanel { + return panel { + panel { + row { + text("The plugin encountered a network error").applyToComponent { + font = JBFont.h1() + }.comment("Please check your internet connection and that A+ is accessible.") + } + row { + button("Refresh") { + update(loading = true) + CourseManager.getInstance(project).restart() } - with(tokenForm) { - token() - validation() + } + }.customize(UnscaledGaps(16, 32, 16, 32)) + } + } + + private fun notEnrolledPanel(): DialogPanel { + return panel { + val courseName = CourseManager.getInstance(project).state.courseName + panel { + row { + text("Looks like you are not enrolled on this course").applyToComponent { + font = JBFont.h1() + }.comment("Please check the ${courseName ?: "course"} page on A+.") + } + row { + button("Refresh") { + update(loading = true) + CourseManager.getInstance(project).restart() } - }.customize(UnscaledGaps(16, 32, 16, 32)) - } + } + + }.customize(UnscaledGaps(16, 32, 16, 32)) + } + } + + private fun createPanel(loading: Boolean = false): DialogPanel { + if (loading) return loadingPanel() + val authenticated = CourseManager.authenticated(project) ?: return loadingPanel() + if (!authenticated) return authPanel() + val error = CourseManager.error(project) + if (error == CourseManager.Error.NETWORK_ERROR) { + return networkErrorPanel() + } else if (error == CourseManager.Error.NOT_ENROLLED) { + return notEnrolledPanel() } val course = CourseManager.course(project) ?: return loadingPanel() val user = CourseManager.user(project) ?: return loadingPanel()