Start typing and select a procedure type from the completion suggestions:
+
+
+
+
Press Tab or Enter to insert the markup.
+
+
+
+## Add interactive elements
+
+### Tabs
+
+To add switchable content, you can make use of tabs (inject them by starting to type `tab` on a new line):
+
+
+
+ ![Alt Text](new_topic_options.png){ width=450 }
+
+
+
+ ]]>
+
+
+
+### Collapsible blocks
+
+Apart from injecting entire XML elements, you can use attributes to configure the behavior of certain elements.
+For example, you can collapse a chapter that contains non-essential information:
+
+#### Supplementary info {collapsible="true"}
+
+Content under a collapsible header will be collapsed by default,
+but you can modify the behavior by adding the following attribute:
+`default-state="expanded"`
+
+### Convert selection to XML
+
+If you need to extend an element with more functions, you can convert selected content from Markdown to semantic markup.
+For example, if you want to merge cells in a table, it's much easier to convert it to XML than do this in Markdown.
+Position the caret anywhere in the table and press Alt+Enter:
+
+
+
+## Feedback and support
+
+Please report any issues, usability improvements, or feature requests to our
+YouTrack project
+(you will need to register).
+
+You are welcome to join our
+public Slack workspace.
+Before you do, please read our [Code of conduct](https://plugins.jetbrains.com/plugin/20158-writerside/docs/writerside-code-of-conduct.html).
+We assume that you’ve read and acknowledged it before joining.
+
+You can also always email us at [writerside@jetbrains.com](mailto:writerside@jetbrains.com).
+
+
+
+ Markup reference
+ Reorder topics in the TOC
+ Build and publish
+ Configure Search
+
+
\ No newline at end of file
diff --git a/Writerside/topics/aplus-lms.yaml/API_Reference.md b/Writerside/topics/aplus-lms.yaml/API_Reference.md
new file mode 100644
index 000000000..158d9a3a3
--- /dev/null
+++ b/Writerside/topics/aplus-lms.yaml/API_Reference.md
@@ -0,0 +1,3 @@
+# API Reference
+
+Start typing here...
\ No newline at end of file
diff --git a/Writerside/topics/aplus-lms.yaml/courses_GET.md b/Writerside/topics/aplus-lms.yaml/courses_GET.md
new file mode 100644
index 000000000..b2c20189e
--- /dev/null
+++ b/Writerside/topics/aplus-lms.yaml/courses_GET.md
@@ -0,0 +1,3 @@
+# /courses GET
+
+
\ No newline at end of file
diff --git a/Writerside/topics/starter-topic.md b/Writerside/topics/starter-topic.md
new file mode 100644
index 000000000..a8557078f
--- /dev/null
+++ b/Writerside/topics/starter-topic.md
@@ -0,0 +1,131 @@
+
+ This is the title
+ Description of the starting page
+
+
+ Some topic
+ Some other topic
+
+
+
+ Main group title
+ Some topic
+ Some other topic
+ ...
+
+
+
+ highlighted group title
+ Some topic
+ Some other topic
+ ...
+
+
+
+
+ Other tools
+ Some topic
+ Some other topic
+ ...
+
+
+
+
+ Frameworks
+ Some topic
+ Some other topic
+ ...
+
+ ...
+
+
+
+# About A+ Courses
+
+
+
+## Add new topics
+
+You can create empty topics, or choose a template for different types of content that contains some boilerplate
+structure to help you get started:
+
+![Create new topic options](new_topic_options.png){ width=290 }{border-effect=line}
+
+## Write content
+
+%product% supports two types of markup: Markdown and XML.
+When you create a new help article, you can choose between two topic types, but this doesn't mean you have to stick to a
+single format.
+You can author content in Markdown and extend it with semantic attributes or inject entire XML elements.
+
+## Inject XML
+
+For example, this is how you inject a procedure:
+
+
+
+
Start typing and select a procedure type from the completion suggestions:
+
+
+
+
Press Tab or Enter to insert the markup.
+
+
+
+## Add interactive elements
+
+### Tabs
+
+To add switchable content, you can make use of tabs (inject them by starting to type `tab` on a new line):
+
+
+
+ ![Alt Text](new_topic_options.png){ width=450 }
+
+
+
+ ]]>
+
+
+
+### Collapsible blocks
+
+Apart from injecting entire XML elements, you can use attributes to configure the behavior of certain elements.
+For example, you can collapse a chapter that contains non-essential information:
+
+#### Supplementary info {collapsible="true"}
+
+Content under a collapsible header will be collapsed by default,
+but you can modify the behavior by adding the following attribute:
+`default-state="expanded"`
+
+### Convert selection to XML
+
+If you need to extend an element with more functions, you can convert selected content from Markdown to semantic markup.
+For example, if you want to merge cells in a table, it's much easier to convert it to XML than do this in Markdown.
+Position the caret anywhere in the table and press Alt+Enter:
+
+
+
+## Feedback and support
+
+Please report any issues, usability improvements, or feature requests to our
+YouTrack project
+(you will need to register).
+
+You are welcome to join our
+public Slack workspace.
+Before you do, please read our [Code of conduct](https://plugins.jetbrains.com/plugin/20158-writerside/docs/writerside-code-of-conduct.html).
+We assume that you’ve read and acknowledged it before joining.
+
+You can also always email us at [writerside@jetbrains.com](mailto:writerside@jetbrains.com).
+
+
+
+ Markup reference
+ Reorder topics in the TOC
+ Build and publish
+ Configure Search
+
+
\ No newline at end of file
diff --git a/Writerside/v.list b/Writerside/v.list
new file mode 100644
index 000000000..2d12cb39f
--- /dev/null
+++ b/Writerside/v.list
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Writerside/writerside.cfg b/Writerside/writerside.cfg
new file mode 100644
index 000000000..bde22ba56
--- /dev/null
+++ b/Writerside/writerside.cfg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 61f7b828b..3382c9651 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,24 +1,28 @@
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
+import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
fun properties(key: String): Provider = providers.gradleProperty(key)
fun environment(key: String): Provider = providers.environmentVariable(key)
plugins {
- java
- idea
+// id("java")
+// id("idea")
// scala
// jacoco
// checkstyle
- kotlin("plugin.serialization") version embeddedKotlinVersion
// ./gradle/libs.versions.toml
- alias(libs.plugins.kotlin) // Kotlin support
- alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin
- alias(libs.plugins.changelog) // Gradle Changelog Plugin
- alias(libs.plugins.qodana) // Gradle Qodana Plugin
- alias(libs.plugins.kover) // Gradle Kover Plugin
-
+ id("org.jetbrains.kotlin.jvm") version "2.0.0"
+ kotlin("plugin.serialization") version "2.0.0"
+ id("org.jetbrains.intellij.platform") version "2.0.0" // IntelliJ Platform Gradle Plugin
+ id("org.jetbrains.changelog") version "2.2.1"
+ id("org.jetbrains.qodana") version "2024.1.5"
+ id("org.jetbrains.kotlinx.kover") version "0.8.2"
+// alias(libs.plugins.kotlin) // Kotlin support
+// alias(libs.plugins.changelog) // Gradle Changelog Plugin
+// alias(libs.plugins.qodana) // Gradle Qodana Plugin
+// alias(libs.plugins.kover) // Gradle Kover Plugin
}
group = properties("pluginGroup").get()
@@ -26,9 +30,7 @@ version = properties("pluginVersion").get()
// Set the JVM language level used to build the project.
kotlin {
- jvmToolchain {
- languageVersion = JavaLanguageVersion.of(21)
- }
+ jvmToolchain(21)
}
// Configure project's dependencies
@@ -41,67 +43,63 @@ repositories {
}
}
-idea {
- module {
- isDownloadJavadoc = true
- isDownloadSources = true
+dependencies {
+ implementation(libs.zip4j)
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
+ implementation("io.ktor:ktor-client-core:2.3.11")
+ implementation("io.ktor:ktor-client-cio:2.3.11")
+ implementation("io.ktor:ktor-client-resources:2.3.11")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
+ implementation("io.ktor:ktor-client-content-negotiation:2.3.11")
+
+// compileOnly(libs.scalaLibrary)
+
+// testImplementation(libs.jupiterApi)
+// testRuntimeOnly(libs.junitPlatformLauncher)
+// testRuntimeOnly(libs.junitJupiterEngine)
+//
+// testImplementation(libs.junit)
+// testImplementation(libs.hamcrest)
+// testImplementation(libs.mockito)
+// testImplementation(libs.restAssured) {
+// exclude(group = "commons-codec", module = "commons-codec") // Excluded because of Cxeb68d52e-5509
+// }
+
+ configurations.all {
+ exclude(group = "org.jetbrains.kotlinx", module = "kotlinx.coroutines") // Only the bundled version is allowed
+ exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
+ exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm")
+ exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-jdk8")
+ exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-slf4j")
+ exclude(group = "org.slf4j")
+// resolutionStrategy.sortArtifacts(ResolutionStrategy.SortOrder.DEPENDENCY_FIRST)
}
-}
-dependencies {
// IntelliJ Platform Gradle Plugin Dependencies Extension - read more:
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
intellijPlatform {
- create(properties("platformType"), properties("platformVersion"))
+ create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion"))
// Plugin Dependencies.
// Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
- bundledPlugins(properties("platformBundledPlugins").map { it.split(',') })
+// bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') })
// Plugin Dependencies.
// Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace.
- plugins(properties("platformPlugins").map { it.split(',') })
+ plugins(providers.gradleProperty("platformPlugins").map { it.split(',') })
instrumentationTools()
pluginVerifier()
zipSigner()
- }
-
- implementation(libs.zip4j)
- implementation(libs.json)
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
- implementation("io.ktor:ktor-client-core:2.3.11")
- implementation("io.ktor:ktor-client-cio:2.3.11")
- implementation("io.ktor:ktor-client-resources:2.3.11")
-
- compileOnly(libs.scalaLibrary)
-
- testImplementation(libs.jupiterApi)
- testRuntimeOnly(libs.junitPlatformLauncher)
- testRuntimeOnly(libs.junitJupiterEngine)
-
- testImplementation(libs.junit)
- testImplementation(libs.hamcrest)
- testImplementation(libs.mockito)
- testImplementation(libs.restAssured) {
- exclude(group = "commons-codec", module = "commons-codec") // Excluded because of Cxeb68d52e-5509
- }
-
- configurations.all {
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx.coroutines") // Only the bundled version is allowed
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm")
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-jdk8")
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-slf4j")
- exclude(group = "org.slf4j")
-// resolutionStrategy.sortArtifacts(ResolutionStrategy.SortOrder.DEPENDENCY_FIRST)
+// testFramework(TestFrameworkType.Platform)
}
}
// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
intellijPlatform {
pluginConfiguration {
- version = properties("pluginVersion")
+ version = providers.gradleProperty("pluginVersion")
// Extract the section from README.md and provide for the plugin's manifest
description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
@@ -118,7 +116,7 @@ intellijPlatform {
val changelog = project.changelog // local variable for configuration cache compatibility
// Get the latest available change notes from the changelog file
- changeNotes = properties("pluginVersion").map { pluginVersion ->
+ changeNotes = providers.gradleProperty("pluginVersion").map { pluginVersion ->
with(changelog) {
renderItem(
(getOrNull(pluginVersion) ?: getUnreleased())
@@ -130,31 +128,27 @@ intellijPlatform {
}
ideaVersion {
- sinceBuild = properties("pluginSinceBuild")
- untilBuild = properties("pluginUntilBuild")
+ sinceBuild = providers.gradleProperty("pluginSinceBuild")
+ untilBuild = providers.gradleProperty("pluginUntilBuild")
}
}
signing {
- certificateChain = environment("CERTIFICATE_CHAIN")
- privateKey = environment("PRIVATE_KEY")
- password = environment("PRIVATE_KEY_PASSWORD")
+ certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
+ privateKey = providers.environmentVariable("PRIVATE_KEY")
+ password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
}
publishing {
- token = environment("INTELLIJ_PUBLISH_TOKEN")
- // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels,
- // like 2.1.7-alpha.3
- // Specify pre-release label to publish the plugin in a custom Release Channel automatically.
- // Read more:
+ token = providers.environmentVariable("PUBLISH_TOKEN")
+ // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
+ // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
// https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
-// channels = properties("pluginVersion").map { listOf(it.substringAfter('-',
-// "").substringBefore('.').ifEmpty { "default"
-// }) }
- channels = listOf(System.getenv("INTELLIJ_PUBLISH_CHANNEL"))
+ channels = providers.gradleProperty("pluginVersion")
+ .map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
}
- verifyPlugin {
+ pluginVerification {
ides {
recommended()
}
@@ -164,69 +158,67 @@ intellijPlatform {
// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
changelog {
groups.empty()
- repositoryUrl = properties("pluginRepositoryUrl")
+ repositoryUrl = providers.gradleProperty("pluginRepositoryUrl")
}
// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration
-//koverReport {
-// defaults {
-// xml {
-// onCheck = true
+//kover {
+// reports {
+// total {
+// xml {
+// onCheck = true
+// }
// }
// }
//}
-abstract class GatherBuildInfoTask : DefaultTask() {
- @get:Input
- abstract val pluginVersion: Property
-
- @get:Input
- abstract val courseVersion: Property
-
- @get:OutputDirectory
- abstract val outputDir: DirectoryProperty
-
- @TaskAction
- fun gatherBuildInfo() {
- outputDir.file("build-info.properties").get().asFile.writeText(
- """
- version=${pluginVersion.get()}
- courseVersion=${courseVersion.get()}
- """.trimIndent()
- )
- }
-}
+//abstract class GatherBuildInfoTask : DefaultTask() {
+// @get:Input
+// abstract val pluginVersion: Property
+//
+// @get:Input
+// abstract val courseVersion: Property
+//
+// @get:OutputDirectory
+// abstract val outputDir: DirectoryProperty
+//
+// @TaskAction
+// fun gatherBuildInfo() {
+// outputDir.file("build-info.properties").get().asFile.writeText(
+// """
+// version=${pluginVersion.get()}
+// courseVersion=${courseVersion.get()}
+// """.trimIndent()
+// )
+// }
+//}
tasks {
wrapper {
- gradleVersion = properties("gradleVersion").get()
+ gradleVersion = providers.gradleProperty("gradleVersion").get()
}
publishPlugin {
dependsOn(patchChangelog)
}
- test {
- useJUnitPlatform()
- }
-
- buildSearchableOptions {
- enabled = false // Disabled because it breaks dynamic reload
- }
+// buildSearchableOptions {
+// enabled = false // Disabled because it breaks dynamic reload
+// }
// jacocoTestReport {
// reports.xml.required = true
// }
- register("gatherBuildInfo") {
- pluginVersion = properties("pluginVersion").get()
- courseVersion = properties("courseVersion").get()
- outputDir = layout.buildDirectory.dir("resources/main")
- }
-
- classes {
- dependsOn("gatherBuildInfo")
- }
+// register("gatherBuildInfo") {
+// pluginVersion = properties("pluginVersion").get()
+// courseVersion = properties("courseVersion").get()
+// outputDir = layout.buildDirectory.dir("resources/main")
+// }
+//
+// classes {
+// dependsOn("gatherBuildInfo")
+// }
// check {
// dependsOn("jacocoTestReport")
@@ -235,6 +227,21 @@ tasks {
runIde {
properties("idea.is.internal=true")
}
+
+// prepareSandbox {
+// disabledPlugins = listOf("org.jetbrains.kotlin")
+// }
+ intellijPlatformTesting {
+ runIde {
+ create("runCustom") {
+ type = IntelliJPlatformType.IntellijIdeaCommunity
+ version = providers.gradleProperty("platformVersion")
+ plugins {
+ disablePlugins("org.jetbrains.kotlin")
+ }
+ }
+ }
+ }
}
//checkstyle {
diff --git a/gradle.properties b/gradle.properties
index f5512a827..e8e64824e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,7 +6,6 @@ pluginRepositoryUrl = https://github.com/Aalto-LeTech/aplus-courses
# SemVer format -> https://semver.org
pluginVersion = 4.0.0-beta1
-courseVersion = 1.0
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 242
@@ -14,16 +13,15 @@ pluginUntilBuild = 242.*
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
platformType = IC
-platformVersion = 242.16677.21
+platformVersion = 242.20224.159
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP
-platformPlugins = org.intellij.scala:2024.2.5
-# Example: platformBundledPlugins = com.intellij.java
-platformBundledPlugins = com.intellij.java
+platformPlugins = org.intellij.scala:2024.2.17
+#platformBundledPlugins = com.intellij.java
# Gradle Releases -> https://github.com/gradle/gradle/releases
-gradleVersion = 8.8
+gradleVersion = 8.9
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
kotlin.stdlib.default.dependency = false
@@ -32,8 +30,6 @@ kotlin.code.style = official
# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
org.gradle.configuration-cache = true
-#org.gradle.configuration-cache = false
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
org.gradle.caching = true
-#org.gradle.caching = false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5bdacd778..e937cbcbb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,16 +11,8 @@ hamcrest = "2.2"
mockito = "5.11.0"
restAssured = "5.4.0"
-# plugins
-kotlin = "2.0.0"
-changelog = "2.2.0"
-intelliJPlatform = "2.0.0-beta7"
-qodana = "2024.1.5"
-kover = "0.8.0"
-
[libraries]
zip4j = { group = "net.lingala.zip4j", name = "zip4j", version.ref = "zip4j" }
-json = { group = "org.json", name = "json", version.ref = "json" }
scalaLibrary = { group = "org.scala-lang", name = "scala3-library_3", version.ref = "scalaLibrary" }
@@ -32,10 +24,3 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
hamcrest = { group = "org.hamcrest", name = "hamcrest", version.ref = "hamcrest" }
mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
restAssured = { group = "io.rest-assured", name = "rest-assured", version.ref = "restAssured" }
-
-[plugins]
-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
-intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" }
-kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
-qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e6441136f..d64cd4917 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a4413138c..09523c0e5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 1aa94a426..f5feea6d6 100755
--- a/gradlew
+++ b/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
diff --git a/gradlew.bat b/gradlew.bat
index 7101f8e46..9b42019c7 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
diff --git a/qodana.yaml b/qodana.yaml
index 341b86c78..f254dc2e6 100644
--- a/qodana.yaml
+++ b/qodana.yaml
@@ -1,10 +1,13 @@
+# Qodana configuration:
+# https://www.jetbrains.com/help/qodana/qodana-yaml.html
+
version: 1.0
linter: jetbrains/qodana-jvm-community:latest
-projectJDK: "17"
+projectJDK: "21"
profile:
name: qodana.recommended
exclude:
- name: All
paths:
- .qodana
- - images
\ No newline at end of file
+ - images
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 61e1697e0..49b4eca67 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1 @@
-plugins {
- id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
-}
-
rootProject.name = "A+ Courses"
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/APlusExerciseDataSource.java b/src/main/java/fi/aalto/cs/apluscourses/dal/APlusExerciseDataSource.java
deleted file mode 100644
index f81c31aa2..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/APlusExerciseDataSource.java
+++ /dev/null
@@ -1,446 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import static fi.aalto.cs.apluscourses.utils.ListUtil.appendTwoLists;
-
-import fi.aalto.cs.apluscourses.model.Authentication;
-import fi.aalto.cs.apluscourses.model.Course;
-import fi.aalto.cs.apluscourses.model.exercise.Exercise;
-import fi.aalto.cs.apluscourses.model.ExerciseDataSource;
-import fi.aalto.cs.apluscourses.model.exercise.ExerciseGroup;
-import fi.aalto.cs.apluscourses.model.Group;
-import fi.aalto.cs.apluscourses.model.InvalidAuthenticationException;
-import fi.aalto.cs.apluscourses.model.exercise.Points;
-import fi.aalto.cs.apluscourses.model.Student;
-import fi.aalto.cs.apluscourses.model.Submission;
-import fi.aalto.cs.apluscourses.model.exercise.SubmissionInfo;
-import fi.aalto.cs.apluscourses.model.exercise.SubmissionResult;
-import fi.aalto.cs.apluscourses.model.User;
-import fi.aalto.cs.apluscourses.model.news.NewsItem;
-import fi.aalto.cs.apluscourses.utils.cache.Cache;
-import fi.aalto.cs.apluscourses.utils.cache.CachePreference;
-import fi.aalto.cs.apluscourses.utils.cache.CachePreferences;
-import fi.aalto.cs.apluscourses.utils.cache.DualCache;
-import fi.aalto.cs.apluscourses.utils.cache.JsonFileCache;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.json.JSONObject;
-
-public class APlusExerciseDataSource implements ExerciseDataSource {
-
- private static final String EXERCISES = "exercises";
- private static final String SUBMISSIONS = "submissions";
- private static final String COURSES = "courses";
- private static final String POINTS = "points";
- private static final String USERS = "users";
- private static final String STUDENTS = "students";
- public static final String RESULTS = "results";
- public static final String ME = "me";
- private static final String NEWS = "news";
-
- @NotNull
- private final Client client;
-
- @NotNull
- private final String apiUrl;
-
- @NotNull
- private final Parser parser;
-
-
- /**
- * Default constructor.
- */
- public APlusExerciseDataSource(@NotNull String apiUrl, @NotNull Path cacheFile, long courseLastModified) {
- var dataAccess = new DefaultDataAccess(new DualCache<>(new JsonFileCache(cacheFile)));
- dataAccess.updateCacheExpiration(courseLastModified);
- this.client = dataAccess;
- this.parser = dataAccess;
- this.apiUrl = apiUrl;
- }
-
- /**
- * Constructor for demanding use (e.g. tests).
- *
- * @param client Client to fetch and post.
- * @param apiUrl The base URL of API.
- * @param parser JSON parser.
- */
- public APlusExerciseDataSource(@NotNull String apiUrl,
- @NotNull Client client,
- @NotNull Parser parser) {
- this.client = client;
- this.apiUrl = apiUrl;
- this.parser = parser;
- }
-
- private List getPaginatedResults(@NotNull String url,
- @NotNull Authentication authentication,
- @NotNull CachePreference cachePreference,
- @NotNull Function parseFunction)
- throws IOException {
- JSONObject response = client.fetch(url, authentication, cachePreference);
- var results = parser.parsePaginatedResults(response, parseFunction);
- var nextPage = parser.parseNextPageUrl(response);
-
- return nextPage == null ? results
- : appendTwoLists(results, getPaginatedResults(nextPage, authentication, cachePreference, parseFunction));
- }
-
- private List getPaginatedResults(@NotNull String url,
- @NotNull Authentication authentication,
- @NotNull Function parseFunction)
- throws IOException {
- return getPaginatedResults(url, authentication, CachePreferences.GET_NEW_AND_FORGET, parseFunction);
- }
-
- @NotNull
- private Map> getExerciseOrder(@NotNull Course course, @NotNull Authentication authentication)
- throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/tree/";
- JSONObject response = client.fetch(url, authentication);
- return parser.parseExerciseOrder(response);
- }
-
- /**
- * Get all of the groups from the A+ API for the user corresponding to the given authentication. A
- * group with id 0 and a single member name "Submit alone" is added to the beginning of the list.
- *
- * @return A list of {@link Group}s that the user is a member of in the given course.
- * @throws IOException If an error occurs (e.g. network error).
- */
- @NotNull
- public List getGroups(@NotNull Course course, @NotNull Authentication authentication)
- throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/mygroups/";
- return getPaginatedResults(url, authentication, Group::fromJsonObject);
- }
-
- @Override
- public void clearCache() {
- client.clearCache();
- }
-
- @Override
- public void updateCacheExpiration(long courseLastModified) {
- client.updateCacheExpiration(courseLastModified);
- }
-
- /**
- * Get all of the exercise groups in the given course by making a request to the A+ API.
- *
- * @throws IOException If an IO error occurs (for an example a network issue). This is an instance
- * of {@link InvalidAuthenticationException} if authentication is invalid.
- */
- @Override
- @NotNull
- public List getExerciseGroups(@NotNull Course course,
- @NotNull Authentication authentication,
- @NotNull String languageCode)
- throws IOException {
- return Collections.emptyList();
-// String url = apiUrl + COURSES + "/" + course.getId() + "/" + EXERCISES + "/";
-// var exerciseOrder = getExerciseOrder(course, authentication);
-// return getPaginatedResults(url, authentication,
-// object -> ExerciseGroup.Companion.fromJsonObject(object, exerciseOrder, languageCode,
-// course.getHiddenElements()));
- }
-
- /**
- * Get all of the points for the given course by making a request to the A+ API.
- *
- * @throws IOException If an IO error occurs (for an example a network issue). This is an instance
- * of {@link InvalidAuthenticationException} if authentication is invalid.
- */
- @Override
- @NotNull
- public Points getPoints(@NotNull Course course, @NotNull Authentication authentication)
- throws IOException {
- return getPoints(course, authentication, null);
- }
-
- @Override
- @NotNull
- public Points getPoints(@NotNull Course course, @NotNull Authentication authentication,
- @Nullable Student student) throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/" + POINTS + "/"
- + (student == null ? ME : student.getId()) + "/";
- JSONObject response = client.fetch(url, authentication);
- return parser.parsePoints(response);
- }
-
- @Override
- @NotNull
- public SubmissionResult getSubmissionResult(@NotNull String submissionUrl,
- @NotNull Exercise exercise,
- @NotNull Authentication authentication,
- @NotNull Course course,
- @NotNull CachePreference cachePreference)
- throws IOException {
- JSONObject response = client.fetch(submissionUrl, authentication, cachePreference);
- return parser.parseSubmissionResult(response, exercise, course);
- }
-
- @Override
- @NotNull
- public Exercise getExercise(long exerciseId,
- @NotNull Points points,
- @NotNull Set optionalCategories,
- @NotNull Authentication authentication,
- @NotNull CachePreference cachePreference,
- @NotNull String languageCode) throws IOException {
- var url = apiUrl + "exercises/" + exerciseId + "/";
- var response = client.fetch(url, authentication, cachePreference);
- return parser.parseExercise(response, points, optionalCategories, languageCode);
- }
-
- @Override
- @NotNull
- public String getSubmissionFeedback(long submissionId,
- @NotNull Authentication authentication) throws IOException {
- var url = apiUrl + "submissions/" + submissionId + "/";
- var response = client.fetch(url, authentication, CachePreferences.GET_NEW_AND_FORGET);
- return response.getString("feedback");
- }
-
- @Override
- @NotNull
- public User getUser(@NotNull Authentication authentication) throws IOException {
- String url = apiUrl + USERS + "/" + ME + "/";
- JSONObject response = client.fetch(url, authentication);
- return parser.parseUser(authentication, response);
- }
-
- @Override
- @NotNull
- public List getStudents(@NotNull Course course,
- @NotNull Authentication authentication,
- @NotNull CachePreference cachePreference) throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/" + STUDENTS + "/";
- return getPaginatedResults(url, authentication, cachePreference, Student::fromJsonObject);
- }
-
- @Override
- @NotNull
- public ZonedDateTime getEndingTime(@NotNull Course course,
- @NotNull Authentication authentication)
- throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/";
- JSONObject response = client.fetch(url, authentication);
- return parser.parseEndingTime(response);
- }
-
- @Override
- public @NotNull List getNews(@NotNull Course course,
- @NotNull Authentication authentication,
- @NotNull String language) throws IOException {
- String url = apiUrl + COURSES + "/" + course.id + "/" + NEWS + "/";
- return getPaginatedResults(url, authentication, CachePreferences.GET_NEW_AND_FORGET,
- jsonObject -> NewsItem.fromJsonObject(jsonObject, course, language));
- }
-
- /**
- * Sends the submission to the server.
- *
- * @throws IOException If there are IO related errors.
- */
- @Override
- @Nullable
- public String submit(@NotNull Submission submission, @NotNull Authentication authentication)
- throws IOException {
- Map data = new HashMap<>();
- data.put("__aplus__", "{ \"group\": " + submission.getGroup().getId() + ", \"lang\": \""
- + submission.getLanguage() + "\" }");
- for (Map.Entry entry : submission.getFiles().entrySet()) {
- data.put(entry.getKey(), entry.getValue().toFile());
- }
- String url = apiUrl + EXERCISES + "/" + submission.getExercise().getId()
- + "/" + SUBMISSIONS + "/submit/";
- return client.post(url, authentication, data);
- }
-
- @NotNull
- public Client getClient() {
- return client;
- }
-
- @NotNull
- public String getApiUrl() {
- return apiUrl;
- }
-
- @NotNull
- public Parser getParser() {
- return parser;
- }
-
- public static class DefaultDataAccess implements Client, Parser {
-
- @NotNull
- private final Cache cache;
-
- public DefaultDataAccess(@NotNull Cache cache) {
- this.cache = cache;
- }
-
- @Override
- public JSONObject fetch(@NotNull String url,
- @Nullable Authentication authentication,
- @NotNull CachePreference cachePreference) throws IOException {
- var value = cache.getValue(url, cachePreference);
- if (value != null) {
- return value;
- }
-// try (InputStream inputStream = CoursesClient.fetch(new URL(url), authentication)) {
-// var streamString = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
-// var response = new JSONObject(new JSONTokener(streamString));
-// cache.putValue(url, response, cachePreference);
-// return response;
-// }
- return value;
- }
-
- @Override
- @Nullable
- public String post(@NotNull String url,
- @NotNull Authentication authentication,
- @NotNull Map data) throws IOException {
-// return CoursesClient.post(
-// new URL(url),
-// authentication,
-// data,
-// response -> Optional
-// .ofNullable(response.getFirstHeader("Location"))
-// .map(Header::getValue)
-// .orElse(null)
-// );
- return null;
- }
-
- @Override
- public List parsePaginatedResults(@NotNull JSONObject object,
- @NotNull Function parseFunction) {
- var jsonResults = object.getJSONArray(RESULTS);
- var results = new ArrayList();
- for (int i = 0; i < jsonResults.length(); i++) {
- results.add(parseFunction.apply(jsonResults.getJSONObject(i)));
- }
- return results;
- }
-
- @Override
- public String parseNextPageUrl(@NotNull JSONObject object) {
- return object.optString("next", null);
- }
-
- @Override
- public SubmissionInfo parseSubmissionInfo(@NotNull JSONObject object) {
- return null;
- }
-
-// @Override
-// public SubmissionInfo parseSubmissionInfo(@NotNull JSONObject object) {
-// return SubmissionInfo.fromJsonObject(object);
-// }
-
- @Override
- public Group parseGroup(@NotNull JSONObject object) {
- return Group.fromJsonObject(object);
- }
-
- @Override
- public Points parsePoints(@NotNull JSONObject object) {
- return null;
- }
-
-// @Override
-// public Points parsePoints(@NotNull JSONObject object) {
-// return Points.fromJsonObject(object);
-// }
-
- @Override
- public Map> parseExerciseOrder(@NotNull JSONObject object) {
- var modules = object.getJSONArray("modules");
- var exerciseIds = new HashMap>();
-
- for (int i = 0; i < modules.length(); i++) {
- var module = modules.getJSONObject(i);
- var moduleChildren = module.getJSONArray("children");
- var moduleExerciseIds = new ArrayList();
-
- for (int j = 0; j < moduleChildren.length(); j++) {
- var chapterChildren = moduleChildren.getJSONObject(j).getJSONArray("children");
- for (int k = 0; k < chapterChildren.length(); k++) {
- moduleExerciseIds.add(chapterChildren.getJSONObject(k).getLong("id"));
- }
- }
-
- var moduleId = module.getLong("id");
- exerciseIds.put(moduleId, moduleExerciseIds);
- }
- return exerciseIds;
- }
-
- @Override
- public Exercise parseExercise(@NotNull JSONObject jsonObject, @NotNull Points points,
- @NotNull Set optionalCategories, @NotNull String languageCode) {
- return null;
- }
-
- @Override
- public SubmissionResult parseSubmissionResult(@NotNull JSONObject jsonObject, @NotNull Exercise exercise,
- @NotNull Course course) {
- return null;
- }
-
-// @Override
-// public Exercise parseExercise(@NotNull JSONObject jsonObject,
-// @NotNull Points points,
-// @NotNull Set optionalCategories,
-// @NotNull String languageCode) {
-// return Exercise.Companion.fromJsonObject(jsonObject, points, optionalCategories, languageCode);
-// }
-
-// @Override
-// public SubmissionResult parseSubmissionResult(@NotNull JSONObject object,
-// @NotNull Exercise exercise,
-// @NotNull Course course) {
-// return SubmissionResult.fromJsonObject(object, exercise, course);
-// }
-
- @Override
- public User parseUser(@NotNull Authentication authentication,
- @NotNull JSONObject object) {
- var fullName = object.optString("full_name");
- var username = object.optString("username");
- var studentId = object.optString("student_id");
- var id = object.optInt("id");
-
- return new User(authentication, fullName.equals("") ? username : fullName, studentId, id);
- }
-
- @Override
- public ZonedDateTime parseEndingTime(@NotNull JSONObject object) {
- return ZonedDateTime.parse(object.getString("ending_time"));
- }
-
- @Override
- public void clearCache() {
- cache.clearAll();
- }
-
- @Override
- public void updateCacheExpiration(long courseLastModified) {
- cache.updateExpirationTimestamp(courseLastModified);
- }
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/APlusTokenAuthentication.java b/src/main/java/fi/aalto/cs/apluscourses/dal/APlusTokenAuthentication.java
deleted file mode 100644
index 0ec0d4dea..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/APlusTokenAuthentication.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import org.apache.http.HttpRequest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-//public class APlusTokenAuthentication extends TokenAuthentication {
-//
-// public static final String AUTHORIZATION_HEADER = "Authorization";
-//
-// public static final String APLUS_USER = "A+_user";
-//
-// /**
-// * Initializes an instance with the given token. Note, that the given token array is not cleared
-// * or overwritten.
-// */
-// public APlusTokenAuthentication(char @NotNull [] token,
-// @Nullable TokenStorage passwordStorage) {
-// super(APLUS_USER, token, passwordStorage);
-// }
-//
-// /**
-// * Creates an authentication object that cannot be persisted.
-// *
-// * @param token Token.
-// */
-// public APlusTokenAuthentication(@NotNull char[] token) {
-// this(token, null);
-// }
-//
-// @Override
-// public void addToRequest(@NotNull HttpRequest request) {
-// request.addHeader(AUTHORIZATION_HEADER, "Token " + new String(token));
-// }
-//
-// public static Factory getFactoryFor(@Nullable TokenStorage passwordStorage) {
-// return token -> new APlusTokenAuthentication(token, passwordStorage);
-// }
-//}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/Client.java b/src/main/java/fi/aalto/cs/apluscourses/dal/Client.java
deleted file mode 100644
index 80cb019f9..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/Client.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import fi.aalto.cs.apluscourses.model.Authentication;
-import fi.aalto.cs.apluscourses.utils.cache.CachePreference;
-import fi.aalto.cs.apluscourses.utils.cache.CachePreferences;
-import java.io.IOException;
-import java.util.Map;
-import org.jetbrains.annotations.NotNull;
-import org.json.JSONObject;
-
-public interface Client {
- JSONObject fetch(@NotNull String url,
- @NotNull Authentication authentication,
- @NotNull CachePreference cachePreference) throws IOException;
-
- default JSONObject fetch(@NotNull String url,
- @NotNull Authentication authentication) throws IOException {
- // Ignore all cache entries by default
- return fetch(url, authentication, CachePreferences.GET_NEW_AND_FORGET);
- }
-
- String post(@NotNull String url,
- @NotNull Authentication authentication,
- @NotNull Map data) throws IOException;
-
- default void clearCache() {}
-
- default void updateCacheExpiration(long courseLastModified) {}
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/Parser.java b/src/main/java/fi/aalto/cs/apluscourses/dal/Parser.java
deleted file mode 100644
index f66a0013f..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/Parser.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import fi.aalto.cs.apluscourses.model.Authentication;
-import fi.aalto.cs.apluscourses.model.Course;
-import fi.aalto.cs.apluscourses.model.exercise.Exercise;
-import fi.aalto.cs.apluscourses.model.Group;
-import fi.aalto.cs.apluscourses.model.exercise.Points;
-import fi.aalto.cs.apluscourses.model.exercise.SubmissionInfo;
-import fi.aalto.cs.apluscourses.model.exercise.SubmissionResult;
-import fi.aalto.cs.apluscourses.model.User;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import org.jetbrains.annotations.NotNull;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-public interface Parser {
-
- List parsePaginatedResults(@NotNull JSONObject object,
- @NotNull Function parseFunction);
-
- String parseNextPageUrl(@NotNull JSONObject object);
-
- SubmissionInfo parseSubmissionInfo(@NotNull JSONObject object);
-
- Group parseGroup(@NotNull JSONObject object);
-
- Points parsePoints(@NotNull JSONObject object);
-
- Map> parseExerciseOrder(@NotNull JSONObject object);
-
- Exercise parseExercise(@NotNull JSONObject jsonObject,
- @NotNull Points points,
- @NotNull Set optionalCategories,
- @NotNull String languageCode);
-
- SubmissionResult parseSubmissionResult(@NotNull JSONObject jsonObject,
- @NotNull Exercise exercise,
- @NotNull Course course);
-
- User parseUser(@NotNull Authentication authentication,
- @NotNull JSONObject jsonObject);
-
- ZonedDateTime parseEndingTime(@NotNull JSONObject object);
-
- /**
- * Parses an JSON array to a list using a given parsing function.
- *
- * @param array JSON array.
- * @param parse Function that parses an JSONObject to T object.
- * @param Type of the result items.
- * @return A list whose items are parsed from JSON objects of the given array.
- */
- default List parseArray(@NotNull JSONArray array, @NotNull Function parse) {
- int length = array.length();
- List results = new ArrayList<>(length);
- for (int i = 0; i < length; i++) {
- results.add(parse.apply(array.getJSONObject(i)));
- }
- return results;
- }
-
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/PasswordStorage.java b/src/main/java/fi/aalto/cs/apluscourses/dal/PasswordStorage.java
deleted file mode 100644
index df86e329d..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/PasswordStorage.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public interface PasswordStorage {
- boolean store(@NotNull String user, char @Nullable [] password);
-
- void remove();
-
- char @Nullable [] restorePassword();
-
- interface Factory {
- @Nullable
- PasswordStorage create(@NotNull String service);
-
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/dal/TokenAuthentication.java b/src/main/java/fi/aalto/cs/apluscourses/dal/TokenAuthentication.java
deleted file mode 100644
index 0d8521b29..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/dal/TokenAuthentication.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package fi.aalto.cs.apluscourses.dal;
-
-import fi.aalto.cs.apluscourses.model.Authentication;
-import java.util.Arrays;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public abstract class TokenAuthentication implements Authentication {
- @NotNull
- protected final String user;
- protected final char @NotNull [] token;
- @Nullable
- protected final TokenStorage passwordStorage;
-
- /**
- * Base class constructor.
- *
- * @param user Name of the user that is referred to when persisting the token
- * @param token Token. The array is cloned and the argument is not cleared.
- * @param passwordStorage Password storage. If null, the authentication won't be persisted.
- */
- protected TokenAuthentication(@NotNull String user,
- char @NotNull [] token,
- @Nullable TokenStorage passwordStorage) {
- this.user = user;
- this.token = token.clone();
- this.passwordStorage = passwordStorage;
- }
-
- @Override
- public boolean persist() {
- return false;//passwordStorage != null && passwordStorage.store(user, token);
- }
-
- /**
- * Returns true if the token represents the same string as the parameter.
- *
- * @param string String to compare.
- * @return True, if strings are equal, otherwise false.
- */
- public boolean tokenEquals(@Nullable String string) {
- return new String(token).equals(string);
- }
-
- @Override
- public void clear() {
- Arrays.fill(token, '\0');
- }
-
- public interface Factory {
- @NotNull
- TokenAuthentication create(char[] token);
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/APlusAuthenticationAction.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/APlusAuthenticationAction.java
deleted file mode 100644
index b42e994f4..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/APlusAuthenticationAction.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-import com.intellij.openapi.actionSystem.ActionUpdateThread;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.DumbAwareAction;
-import com.intellij.openapi.project.Project;
-import fi.aalto.cs.apluscourses.intellij.notifications.ApiTokenNotSetNotification;
-import fi.aalto.cs.apluscourses.intellij.notifications.DefaultNotifier;
-import fi.aalto.cs.apluscourses.intellij.notifications.Notifier;
-import fi.aalto.cs.apluscourses.intellij.services.CourseProjectProvider;
-import fi.aalto.cs.apluscourses.intellij.services.Dialogs;
-import fi.aalto.cs.apluscourses.model.Authentication;
-import fi.aalto.cs.apluscourses.presentation.AuthenticationViewModel;
-import fi.aalto.cs.apluscourses.services.PluginSettings;
-import fi.aalto.cs.apluscourses.utils.APlusLogger;
-import org.jetbrains.annotations.NotNull;
-
-public class APlusAuthenticationAction extends DumbAwareAction {
-
- private static final Logger logger = APlusLogger.logger;
-
- public static final String ACTION_ID = APlusAuthenticationAction.class.getCanonicalName();
-
- @NotNull
- private final CourseProjectProvider courseProjectProvider;
-
- @NotNull
- private final Dialogs dialogs;
-
-// @NotNull
-// private final PasswordStorage.Factory passwordStorageFactory;
-
- @NotNull
- private final Notifier notifier;
-
- /**
- * Called by the platform.
- */
- public APlusAuthenticationAction() {
- this(
- PluginSettings.getInstance()::getCourseProject,
- Dialogs.DEFAULT,
-// TokenStorage::new,
- new DefaultNotifier()
- );
- }
-
- /**
- * Constructor for testing.
- */
- public APlusAuthenticationAction(@NotNull CourseProjectProvider courseProjectProvider,
- @NotNull Dialogs dialogs,
-// @NotNull PasswordStorage.Factory passwordStorageFactory,
- @NotNull Notifier notifier) {
- this.courseProjectProvider = courseProjectProvider;
- this.dialogs = dialogs;
-// this.passwordStorageFactory = passwordStorageFactory;
- this.notifier = notifier;
- }
-
- @Override
- public void update(@NotNull AnActionEvent e) {
- e.getPresentation().setEnabled(courseProjectProvider.getCourseProject(e.getProject()) != null);
- }
-
- @Override
- public @NotNull ActionUpdateThread getActionUpdateThread() {
- return ActionUpdateThread.BGT;
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- logger.debug("Starting APlusAuthenticationAction");
- Project project = e.getProject();
- var courseProject = courseProjectProvider.getCourseProject(project);
- if (courseProject == null) {
- logger.warn("Course project was null");
- return;
- }
- var course = courseProject.course;
-
- String apiUrl = course.getApiUrl();
- String authenticationHtmlUrl = course.getHtmlUrl() + "accounts/accounts/";
-
-// PasswordStorage passwordStorage = passwordStorageFactory.create(apiUrl);
- AuthenticationViewModel authenticationViewModel = new AuthenticationViewModel(
-// APlusTokenAuthentication.getFactoryFor(passwordStorage),
- authenticationHtmlUrl,
- course.getExerciseDataSource()
- );
-
- if (!dialogs.create(authenticationViewModel, project).showAndGet()) {
- logger.info("Authentication cancelled");
- return;
- }
-
- Authentication authentication = authenticationViewModel.getAuthentication();
- if (authentication == null) {
- return;
- }
- if (!authentication.persist()) {
- notifier.notify(new ApiTokenNotSetNotification(), project);
- }
- courseProject.setAuthentication(authentication);
-// courseProject.exercisesUpdater.restart(courseProject);
- logger.info("Authentication finished");
- }
-
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AboutAction.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AboutAction.java
deleted file mode 100644
index 7a984e155..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AboutAction.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.DumbAwareAction;
-import fi.aalto.cs.apluscourses.ui.AboutDialog;
-import org.jetbrains.annotations.NotNull;
-
-public class AboutAction extends DumbAwareAction {
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- if (e.getProject() == null) {
- return;
- }
- new AboutDialog(e.getProject()).show();
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionGroups.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionGroups.java
deleted file mode 100644
index 51779a74f..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionGroups.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-public class ActionGroups {
-
- public static final String NEWS_ACTIONS =
- "fi.aalto.cs.apluscourses.intellij.actions.ActionGroups.NEWS_ACTIONS";
- public static final String MODULE_ACTIONS =
- "fi.aalto.cs.apluscourses.intellij.actions.ActionGroups.MODULE_ACTIONS";
- public static final String EXERCISE_ACTIONS =
- "fi.aalto.cs.apluscourses.intellij.actions.ActionGroups.EXERCISE_ACTIONS";
- public static final String MENU_ACTIONS =
- "fi.aalto.cs.apluscourses.intellij.actions.ActionGroups.MENU_ACTIONS";
- public static final String TOOL_WINDOW_ACTIONS =
- "fi.aalto.cs.apluscourses.intellij.actions.ActionGroups.TOOL_WINDOW_ACTIONS";
-
- private ActionGroups() {
-
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionUtil.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionUtil.java
deleted file mode 100644
index 04d743372..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/ActionUtil.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-import com.intellij.ide.DataManager;
-import com.intellij.openapi.actionSystem.ActionManager;
-import com.intellij.openapi.actionSystem.ActionPlaces;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.DataContext;
-import java.awt.Component;
-import java.awt.event.ActionListener;
-import org.jetbrains.annotations.NotNull;
-
-public class ActionUtil {
-
- /**
- * Launches {@link AnAction}.
- *
- * @param actionId Id of the action.
- * @param dataContext A {@link DataContext} for the action.
- */
- public static void launch(@NotNull String actionId, @NotNull DataContext dataContext) {
- // https://intellij-support.jetbrains.com/hc/en-us/community/posts/206130119/comments/206169635
- AnAction action = ActionManager.getInstance().getAction(actionId);
- AnActionEvent event = AnActionEvent.createFromAnAction(action, null, ActionPlaces.UNKNOWN,
- dataContext);
- action.actionPerformed(event);
- }
-
- /**
- * Returns an {@link ActionListener} that launches {@link AnAction} corresponding to the given
- * id each time {@code actionPerformed()} is called.
- *
- * @param actionId Id of the action.
- * @param source A {@link Component} that gives the {@link DataContext} for the action.
- * @return An {@link ActionListener}
- */
- public static ActionListener createOnEventLauncher(@NotNull String actionId,
- @NotNull Component source) {
- return actionEvent -> launch(actionId, DataManager.getInstance().getDataContext(source));
- }
-
- private ActionUtil() {
-
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AssistantModeAction.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AssistantModeAction.java
deleted file mode 100644
index 524a4c9ba..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/AssistantModeAction.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.DumbAwareToggleAction;
-import fi.aalto.cs.apluscourses.services.PluginSettings;
-import fi.aalto.cs.apluscourses.utils.APlusLogger;
-import org.jetbrains.annotations.NotNull;
-
-public class AssistantModeAction extends DumbAwareToggleAction {
- private static final Logger logger = APlusLogger.logger;
-
- @Override
- public boolean isSelected(@NotNull AnActionEvent e) {
- return PluginSettings.getInstance().isAssistantMode();
- }
-
- @Override
- public void setSelected(@NotNull AnActionEvent e, boolean state) {
- PluginSettings.getInstance().setAssistantMode(state);
- logger.info("Assistant mode set to $s".formatted(state));
- }
-}
diff --git a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/CourseProjectAction.java b/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/CourseProjectAction.java
deleted file mode 100644
index 8ff507ad0..000000000
--- a/src/main/java/fi/aalto/cs/apluscourses/intellij/actions/CourseProjectAction.java
+++ /dev/null
@@ -1,459 +0,0 @@
-package fi.aalto.cs.apluscourses.intellij.actions;
-
-import com.intellij.concurrency.JobScheduler;
-import com.intellij.openapi.actionSystem.ActionUpdateThread;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ex.ApplicationEx;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import fi.aalto.cs.apluscourses.intellij.model.SettingsImporter;
-import fi.aalto.cs.apluscourses.intellij.notifications.CourseConfigListErrorNotification;
-import fi.aalto.cs.apluscourses.intellij.notifications.CourseFileError;
-import fi.aalto.cs.apluscourses.intellij.notifications.CourseVersionOutdatedError;
-import fi.aalto.cs.apluscourses.intellij.notifications.CourseVersionTooNewError;
-import fi.aalto.cs.apluscourses.intellij.notifications.DefaultNotifier;
-import fi.aalto.cs.apluscourses.intellij.notifications.NetworkErrorNotification;
-import fi.aalto.cs.apluscourses.intellij.notifications.Notifier;
-import fi.aalto.cs.apluscourses.model.ComponentInstaller;
-import fi.aalto.cs.apluscourses.model.ComponentInstallerImpl;
-import fi.aalto.cs.apluscourses.model.Course;
-import fi.aalto.cs.apluscourses.model.CourseProject;
-import fi.aalto.cs.apluscourses.model.MalformedCourseConfigurationException;
-import fi.aalto.cs.apluscourses.presentation.CourseItemViewModel;
-import fi.aalto.cs.apluscourses.presentation.CourseProjectViewModel;
-import fi.aalto.cs.apluscourses.presentation.CourseSelectionViewModel;
-import fi.aalto.cs.apluscourses.services.PluginSettings;
-import fi.aalto.cs.apluscourses.ui.InstallerDialogs;
-import fi.aalto.cs.apluscourses.ui.courseproject.CourseProjectActionDialogs;
-import fi.aalto.cs.apluscourses.ui.courseproject.CourseProjectActionDialogsImpl;
-import fi.aalto.cs.apluscourses.ui.ideactivities.ComponentDatabase;
-import fi.aalto.cs.apluscourses.ui.utils.PluginInstallerDialogs;
-import fi.aalto.cs.apluscourses.utils.APlusLogger;
-import fi.aalto.cs.apluscourses.utils.BuildInfo;
-import fi.aalto.cs.apluscourses.utils.PluginAutoInstaller;
-import fi.aalto.cs.apluscourses.utils.PostponedRunnable;
-import fi.aalto.cs.apluscourses.utils.Version;
-import fi.aalto.cs.apluscourses.utils.async.SimpleAsyncTaskManager;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.yaml.snakeyaml.Yaml;
-
-public class CourseProjectAction extends AnAction {
-
- private static final Logger logger = APlusLogger.logger;
-
- public static final String ACTION_ID = CourseProjectAction.class.getCanonicalName();
-
-// @NotNull
-// private final CourseFactory courseFactory;
-
- private final boolean useCourseFile;
-
- @NotNull
- private final SettingsImporter settingsImporter;
-
- @NotNull
- private final ComponentInstaller.Factory installerFactory;
-
- @NotNull
- private final PostponedRunnable ideRestarter;
-
- @NotNull
- private final CourseProjectActionDialogs dialogs;
-
- @NotNull
- private final InstallerDialogs.Factory installerDialogsFactory;
-
- @NotNull
- private final Notifier notifier;
-
- private final ExecutorService executor;
-
- private static final String COURSE_LIST_URL =
- "https://version.aalto.fi/gitlab/aplus-courses/course-config-urls/-/raw/main/courses.yaml";
-
- private static final List FALLBACK_COURSES = List.of(
- new CourseItemViewModel("CS-A1141 Tietorakenteet ja algoritmit Y", "Fall 2023",
- "https://gitmanager.cs.aalto.fi/static/CS-A1141_2023Autumn/_static/apluscourses/a1141_course_config.json"),
- new CourseItemViewModel("CS-A1143 Data Structures and Algorithms Y", "Fall 2023",
- "https://gitmanager.cs.aalto.fi/static/CS-A1143_2023Autumn/_static/apluscourses/a1143_course_config.json"),
- new CourseItemViewModel("Ohjelmointistudio 2 / Programming Studio A", "Spring 2023",
- "https://gitmanager.cs.aalto.fi/static/studios-spring-2023/modules/s2_course_config.json"),
- new CourseItemViewModel("O1", "Fall 2022",
- "https://gitmanager.cs.aalto.fi/static/O1_2022/modules/o1_course_config.json")
- );
-
- /**
- * Construct a course project action with the given parameters.
- *
- * @param courseFactory An instance of {@link CourseFactory} that is used to create a course
- * instance from a URL.
- * @param useCourseFile Determines whether a course file is read/created or not. This is useful
- * only for testing purposes.
- * @param settingsImporter An instance of {@link SettingsImporter} that is used to import IDE and
- * project settings.
- * @param installerFactory The factory used to create the component installer. The component
- * installer is then used to install the automatically installed
- * components of the course. This is useful mainly for testing.
- * @param ideRestarter A {@link PostponedRunnable} that is used to restart the IDE after
- * everything related to the course project action is done. In practice,
- * this is either immediately after the action is done, or after all
- * automatically installed components for the course have been installed.
- * Since the installation of automatically installed components may take
- * quite a while, it is advisable for this to show the user a confirmation
- * dialog regarding the restart.
- * @param notifier Notification bus.
- */
- public CourseProjectAction(@NotNull CourseFactory courseFactory,
- boolean useCourseFile,
- @NotNull SettingsImporter settingsImporter,
- @NotNull ComponentInstaller.Factory installerFactory,
- @NotNull PostponedRunnable ideRestarter,
- @NotNull CourseProjectActionDialogs dialogs,
- @NotNull InstallerDialogs.Factory installerDialogsFactory,
- @NotNull Notifier notifier,
- @NotNull ExecutorService executor) {
-// this.courseFactory = courseFactory;
- this.useCourseFile = useCourseFile;
- this.settingsImporter = settingsImporter;
- this.installerFactory = installerFactory;
- this.ideRestarter = ideRestarter;
- this.dialogs = dialogs;
- this.installerDialogsFactory = installerDialogsFactory;
- this.notifier = notifier;
- this.executor = executor;
- }
-
- /**
- * Construct a course project action with sensible defaults.
- */
- public CourseProjectAction() {
-// this.courseFactory = (url, project) -> Course.Companion.fromUrl(url, new IntelliJModelFactory(project), project);
- this.useCourseFile = true;
- this.settingsImporter = new SettingsImporter();
- this.installerFactory = new ComponentInstallerImpl.FactoryImpl<>(new SimpleAsyncTaskManager());
- this.dialogs = new CourseProjectActionDialogsImpl();
- this.ideRestarter = new PostponedRunnable(() -> {
- if (dialogs.showRestartDialog()) {
- ((ApplicationEx) ApplicationManager.getApplication()).restart(true);
- }
- });
- this.installerDialogsFactory = InstallerDialogs::new;
- this.notifier = new DefaultNotifier();
- this.executor = JobScheduler.getScheduler();
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e) {
- logger.debug("Starting CourseProjectAction");
- Project project = e.getProject();
-
- if (project == null) {
- return;
- }
-
- URL courseUrl = tryGetCourseUrl(project);
- if (courseUrl == null) {
- return;
- }
-
- Course course = tryGetCourse(project, courseUrl);
- if (course == null) {
- return;
- }
-
- final Boolean pluginDependencyResult = PluginAutoInstaller.ensureDependenciesInstalled(project, notifier,
- course.getRequiredPlugins(), PluginInstallerDialogs::askForInstallationConsentOnCreation);
-
- if (pluginDependencyResult == null) {
- // the user cancelled dependency installation
- return;
- } else if (!pluginDependencyResult) {
- // new plugins installed, we must restart
- if (!PluginInstallerDialogs.askForIDERestart()) {
- return;
- }
-
- ((ApplicationEx) ApplicationManager.getApplication()).restart(true);
- return;
- }
-
- var version = BuildInfo.INSTANCE.courseVersion;
- var versionComparison = version.compareTo(course.getVersion());
-
- if (versionComparison == Version.ComparisonStatus.MAJOR_TOO_OLD
- || versionComparison == Version.ComparisonStatus.MAJOR_TOO_NEW) {
- if (versionComparison == Version.ComparisonStatus.MAJOR_TOO_OLD) {
- logger.warn("A+ Courses version outdated: installed %s, required %s".formatted(version, course.getVersion()));
- } else {
- logger.warn("A+ Courses version too new: installed %s, required %s".formatted(version, course.getVersion()));
- }
- notifier.notify(
- versionComparison == Version.ComparisonStatus.MAJOR_TOO_OLD
- ? new CourseVersionOutdatedError() : new CourseVersionTooNewError(), project);
- return;
- }
-
- CourseProjectViewModel courseProjectViewModel
- = new CourseProjectViewModel(course, settingsImporter.currentlyImportedIdeSettings());
- if (!dialogs.showMainDialog(project, courseProjectViewModel)) {
- return;
- }
-
- String language = Objects.requireNonNull(courseProjectViewModel.languageProperty.get());
- logger.info("Language chosen: %s".formatted(language));
- if (!tryCreateCourseFile(project, courseUrl, language)) {
- return;
- }
-
- boolean importIdeSettings = courseProjectViewModel.shouldApplyNewIdeSettings();
- logger.info("Should apply new IDE settings: %s".formatted(importIdeSettings));
-
- var basePath = Optional.ofNullable(project.getBasePath()).map(Paths::get).orElse(null);
- if (basePath == null) {
- logger.warn("Settings could not be imported because (default?) project does not have path.");
- return;
- }
-
- if (useCourseFile) {
- // The course file is not created in testing.
- var currentProject = PluginSettings.getInstance().getCourseProject(project);
- if (currentProject != null) {
-// currentProject.courseUpdater.restart();
-// currentProject.exercisesUpdater.restart(currentProject);
- } else {
- var courseProject = new CourseProject(course, courseUrl, project, notifier);
- PluginSettings.getInstance().registerCourseProject(courseProject);
- }
- }
-
- Future> autoInstallDone = executor.submit(() -> startAutoInstalls(course, project));
-
- Future projectSettingsImported =
- executor.submit(() -> tryImportProjectSettings(project, basePath, course));
-
- Future ideSettingsImported =
- executor.submit(() -> importIdeSettings && tryImportIdeSettings(project, course));
-
- Future customPropertiesImported =
- executor.submit(() -> tryImportCustomProperties(project, basePath, course));
-
- executor.submit(() -> tryImportFeedbackCss(project, course));
-
- ComponentDatabase.showToolWindow(ComponentDatabase.APLUS_TOOL_WINDOW, project);
-
- executor.execute(() -> {
- try {
- autoInstallDone.get();
- if (projectSettingsImported.get() && customPropertiesImported.get() // NOSONAR
- && importIdeSettings && ideSettingsImported.get()) { // NOSONAR
- ideRestarter.run();
- }
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- } catch (ExecutionException ex) {
- logger.warn("An exception was thrown in an asynchronous call", ex);
- }
- });
- }
-
- @Override
- public void update(@NotNull AnActionEvent e) {
- // This action is available only if a non-default project is open
- Project project = e.getProject();
- e.getPresentation().setEnabledAndVisible(project != null && !project.isDefault());
- }
-
- @Override
- public @NotNull ActionUpdateThread getActionUpdateThread() {
- return ActionUpdateThread.BGT;
- }
-
- @FunctionalInterface
- public interface CourseFactory {
-
- @NotNull
- Course fromUrl(@NotNull URL courseUrl, @NotNull Project project)
- throws IOException, MalformedCourseConfigurationException;
- }
-
- @Nullable
- private URL tryGetCourseUrl(@NotNull Project project) {
- logger.info("Getting course url");
- try {
- if (useCourseFile && PluginSettings.getInstance().getCourseFileManager(project).load()) {
- logger.info("Course file exists");
- return PluginSettings.getInstance().getCourseFileManager(project).getCourseUrl();
- }
-
- CourseSelectionViewModel viewModel = new CourseSelectionViewModel();
- Executors.newSingleThreadExecutor().submit(() -> viewModel.courses.set(fetchCourses(project)));
- boolean cancelled = !dialogs.showCourseSelectionDialog(project, viewModel);
- if (cancelled) {
- logger.info("Canceled course selection");
- return null;
- } else {
- var url = new URL(Objects.requireNonNull(viewModel.selectedCourseUrl.get()));
- logger.info("Got url: %s".formatted(url));
- return url;
- }
- } catch (MalformedURLException e) {
- // User entered an invalid URL (or the default list has an invalid URL, which would be a bug)
- logger.warn("Malformed course configuration file URL", e);
- notifier.notify(new NetworkErrorNotification(e), project);
- return null;
- } catch (IOException e) {
- notifier.notify(new NetworkErrorNotification(e), project);
- return null;
- }
- }
-
- private CourseItemViewModel[] fetchCourses(@NotNull Project project) {
- try {
- var url = new URL(COURSE_LIST_URL);
-// var courseStream = CoursesClient.fetch(url);
- var yaml = new Yaml();
-
- var courseList = List.of(Map.of("", ""));// (List