From 6fad6ec6e4c0b4738f88778d82d70d7c869a0484 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 19 Aug 2024 18:00:49 +0300 Subject: [PATCH 1/5] Implemented the Aggregate function and sorting by contributions descending --- src/contributors/GitHubService.kt | 5 ++--- src/tasks/Aggregation.kt | 9 +++++++-- src/tasks/Request1Blocking.kt | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/contributors/GitHubService.kt b/src/contributors/GitHubService.kt index 3dc8b7c..685fdb8 100644 --- a/src/contributors/GitHubService.kt +++ b/src/contributors/GitHubService.kt @@ -7,12 +7,11 @@ import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import retrofit2.Call -import retrofit2.Response import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.http.GET import retrofit2.http.Path -import java.util.Base64 +import java.util.* interface GitHubService { @GET("orgs/{org}/repos?per_page=100") @@ -36,7 +35,7 @@ data class Repo( @Serializable data class User( val login: String, - val contributions: Int + var contributions: Int ) @Serializable diff --git a/src/tasks/Aggregation.kt b/src/tasks/Aggregation.kt index f2d6fa7..6af1afd 100644 --- a/src/tasks/Aggregation.kt +++ b/src/tasks/Aggregation.kt @@ -14,5 +14,10 @@ TODO: Write aggregation code. The corresponding test can be found in test/tasks/AggregationKtTest.kt. You can use 'Navigate | Test' menu action (note the shortcut) to navigate to the test. */ -fun List.aggregate(): List = - this \ No newline at end of file +fun List.aggregate(): List { + return this + .groupBy { user -> user.login } + .map { (_, users) -> + users.first().copy(contributions = users.sumOf { user -> user.contributions }) + }.sortedByDescending { user -> user.contributions } +} diff --git a/src/tasks/Request1Blocking.kt b/src/tasks/Request1Blocking.kt index 346c149..5c1c89e 100644 --- a/src/tasks/Request1Blocking.kt +++ b/src/tasks/Request1Blocking.kt @@ -3,12 +3,12 @@ package tasks import contributors.* import retrofit2.Response -fun loadContributorsBlocking(service: GitHubService, req: RequestData) : List { +fun loadContributorsBlocking(service: GitHubService, req: RequestData): List { val repos = service .getOrgReposCall(req.org) .execute() // Executes request and blocks the current thread .also { logRepos(req, it) } - .body() ?: emptyList() + .bodyList() return repos.flatMap { repo -> service @@ -21,4 +21,4 @@ fun loadContributorsBlocking(service: GitHubService, req: RequestData) : List Response>.bodyList(): List { return body() ?: emptyList() -} \ No newline at end of file +} From 7c1c2853d00124963dc189460b85ee61a58fd55e Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 19 Aug 2024 18:17:46 +0300 Subject: [PATCH 2/5] We use a "thread" to load and display data when in BACKGROUND mode. --- src/tasks/Request2Background.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/Request2Background.kt b/src/tasks/Request2Background.kt index 0ccfaf4..b490780 100644 --- a/src/tasks/Request2Background.kt +++ b/src/tasks/Request2Background.kt @@ -7,6 +7,6 @@ import kotlin.concurrent.thread fun loadContributorsBackground(service: GitHubService, req: RequestData, updateResults: (List) -> Unit) { thread { - loadContributorsBlocking(service, req) + updateResults(loadContributorsBlocking(service, req)) } -} \ No newline at end of file +} From 3cfaf546e92f400aed159c01eccd06498ce3d0f7 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 19 Aug 2024 18:42:46 +0300 Subject: [PATCH 3/5] Implementing load contributors with callbacks. --- src/tasks/Request3Callbacks.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tasks/Request3Callbacks.kt b/src/tasks/Request3Callbacks.kt index 392c57a..0400b88 100644 --- a/src/tasks/Request3Callbacks.kt +++ b/src/tasks/Request3Callbacks.kt @@ -5,21 +5,23 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.* -import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.CountDownLatch fun loadContributorsCallbacks(service: GitHubService, req: RequestData, updateResults: (List) -> Unit) { service.getOrgReposCall(req.org).onResponse { responseRepos -> logRepos(req, responseRepos) val repos = responseRepos.bodyList() - val allUsers = mutableListOf() + val allUsers = Collections.synchronizedList(mutableListOf()) + val countDownLatch = CountDownLatch(repos.size) for (repo in repos) { service.getRepoContributorsCall(req.org, repo.name).onResponse { responseUsers -> logUsers(repo, responseUsers) val users = responseUsers.bodyList() allUsers += users + countDownLatch.countDown() } } - // TODO: Why this code doesn't work? How to fix that? + countDownLatch.await() updateResults(allUsers.aggregate()) } } From 1d3bc0b1c735bcb01ce1d2f0c50804b07d051c76 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 19 Aug 2024 19:07:02 +0300 Subject: [PATCH 4/5] Use the suspend functions to load repos --- src/contributors/GitHubService.kt | 10 ++++---- src/tasks/Request1Blocking.kt | 31 +++++++++++++----------- src/tasks/Request3Callbacks.kt | 39 ++++++++++++++++--------------- src/tasks/Request4Suspend.kt | 13 +++++++++-- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/contributors/GitHubService.kt b/src/contributors/GitHubService.kt index 685fdb8..f4956f7 100644 --- a/src/contributors/GitHubService.kt +++ b/src/contributors/GitHubService.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import retrofit2.Call +import retrofit2.Response import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.http.GET @@ -15,15 +15,15 @@ import java.util.* interface GitHubService { @GET("orgs/{org}/repos?per_page=100") - fun getOrgReposCall( + suspend fun getOrgReposCall( @Path("org") org: String - ): Call> + ): Response> @GET("repos/{owner}/{repo}/contributors?per_page=100") - fun getRepoContributorsCall( + suspend fun getRepoContributorsCall( @Path("owner") owner: String, @Path("repo") repo: String - ): Call> + ): Response> } @Serializable diff --git a/src/tasks/Request1Blocking.kt b/src/tasks/Request1Blocking.kt index 5c1c89e..893bfcf 100644 --- a/src/tasks/Request1Blocking.kt +++ b/src/tasks/Request1Blocking.kt @@ -1,22 +1,25 @@ package tasks -import contributors.* +import contributors.GitHubService +import contributors.RequestData +import contributors.User import retrofit2.Response fun loadContributorsBlocking(service: GitHubService, req: RequestData): List { - val repos = service - .getOrgReposCall(req.org) - .execute() // Executes request and blocks the current thread - .also { logRepos(req, it) } - .bodyList() - - return repos.flatMap { repo -> - service - .getRepoContributorsCall(req.org, repo.name) - .execute() // Executes request and blocks the current thread - .also { logUsers(repo, it) } - .bodyList() - }.aggregate() +// val repos = service +// .getOrgReposCall(req.org) +// .execute() // Executes request and blocks the current thread +// .also { logRepos(req, it) } +// .bodyList() +// +// return repos.flatMap { repo -> +// service +// .getRepoContributorsCall(req.org, repo.name) +// .execute() // Executes request and blocks the current thread +// .also { logUsers(repo, it) } +// .bodyList() +// }.aggregate() + return emptyList() } fun Response>.bodyList(): List { diff --git a/src/tasks/Request3Callbacks.kt b/src/tasks/Request3Callbacks.kt index 0400b88..7988470 100644 --- a/src/tasks/Request3Callbacks.kt +++ b/src/tasks/Request3Callbacks.kt @@ -1,29 +1,30 @@ package tasks -import contributors.* +import contributors.GitHubService +import contributors.RequestData +import contributors.User +import contributors.log import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import java.util.* -import java.util.concurrent.CountDownLatch fun loadContributorsCallbacks(service: GitHubService, req: RequestData, updateResults: (List) -> Unit) { - service.getOrgReposCall(req.org).onResponse { responseRepos -> - logRepos(req, responseRepos) - val repos = responseRepos.bodyList() - val allUsers = Collections.synchronizedList(mutableListOf()) - val countDownLatch = CountDownLatch(repos.size) - for (repo in repos) { - service.getRepoContributorsCall(req.org, repo.name).onResponse { responseUsers -> - logUsers(repo, responseUsers) - val users = responseUsers.bodyList() - allUsers += users - countDownLatch.countDown() - } - } - countDownLatch.await() - updateResults(allUsers.aggregate()) - } +// service.getOrgReposCall(req.org).onResponse { responseRepos -> +// logRepos(req, responseRepos) +// val repos = responseRepos.bodyList() +// val allUsers = Collections.synchronizedList(mutableListOf()) +// val countDownLatch = CountDownLatch(repos.size) +// for (repo in repos) { +// service.getRepoContributorsCall(req.org, repo.name).onResponse { responseUsers -> +// logUsers(repo, responseUsers) +// val users = responseUsers.bodyList() +// allUsers += users +// countDownLatch.countDown() +// } +// } +// countDownLatch.await() +// updateResults(allUsers.aggregate()) +// } } inline fun Call.onResponse(crossinline callback: (Response) -> Unit) { diff --git a/src/tasks/Request4Suspend.kt b/src/tasks/Request4Suspend.kt index 37949e7..4cd6ba0 100644 --- a/src/tasks/Request4Suspend.kt +++ b/src/tasks/Request4Suspend.kt @@ -3,5 +3,14 @@ package tasks import contributors.* suspend fun loadContributorsSuspend(service: GitHubService, req: RequestData): List { - TODO() -} \ No newline at end of file + val repos = service + .getOrgReposCall(req.org) + .also { logRepos(req, it) } + .bodyList() + + return repos.flatMap { repo -> + service.getRepoContributorsCall(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + }.aggregate() +} From e2c939dbfad3edd3bbf3501b07e025082786db1b Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 19 Aug 2024 19:40:21 +0300 Subject: [PATCH 5/5] Use coroutines and concurrent run with different thread pools/Dispatchers --- src/contributors/Contributors.kt | 18 +++++++++++++----- src/tasks/Request5Concurrent.kt | 23 ++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/contributors/Contributors.kt b/src/contributors/Contributors.kt index 4bea4c3..03637a9 100644 --- a/src/contributors/Contributors.kt +++ b/src/contributors/Contributors.kt @@ -20,7 +20,7 @@ enum class Variant { CHANNELS // Request7Channels } -interface Contributors: CoroutineScope { +interface Contributors : CoroutineScope { val job: Job @@ -58,6 +58,7 @@ interface Contributors: CoroutineScope { val users = loadContributorsBlocking(service, req) updateResults(users, startTime) } + BACKGROUND -> { // Blocking a background thread loadContributorsBackground(service, req) { users -> SwingUtilities.invokeLater { @@ -65,6 +66,7 @@ interface Contributors: CoroutineScope { } } } + CALLBACKS -> { // Using callbacks loadContributorsCallbacks(service, req) { users -> SwingUtilities.invokeLater { @@ -72,24 +74,30 @@ interface Contributors: CoroutineScope { } } } + SUSPEND -> { // Using coroutines launch { val users = loadContributorsSuspend(service, req) updateResults(users, startTime) }.setUpCancellation() } + CONCURRENT -> { // Performing requests concurrently - launch { + launch(Dispatchers.Default) { val users = loadContributorsConcurrent(service, req) - updateResults(users, startTime) + withContext(Dispatchers.Main) { + updateResults(users, startTime) + } }.setUpCancellation() } + NOT_CANCELLABLE -> { // Performing requests in a non-cancellable way launch { val users = loadContributorsNotCancellable(service, req) updateResults(users, startTime) }.setUpCancellation() } + PROGRESS -> { // Showing progress launch(Dispatchers.Default) { loadContributorsProgress(service, req) { users, completed -> @@ -99,6 +107,7 @@ interface Contributors: CoroutineScope { } }.setUpCancellation() } + CHANNELS -> { // Performing requests concurrently and showing progress launch(Dispatchers.Default) { loadContributorsChannels(service, req) { users, completed -> @@ -178,8 +187,7 @@ interface Contributors: CoroutineScope { val params = getParams() if (params.username.isEmpty() && params.password.isEmpty()) { removeStoredParams() - } - else { + } else { saveParams(params) } } diff --git a/src/tasks/Request5Concurrent.kt b/src/tasks/Request5Concurrent.kt index 03ab317..bd3c889 100644 --- a/src/tasks/Request5Concurrent.kt +++ b/src/tasks/Request5Concurrent.kt @@ -1,8 +1,25 @@ package tasks import contributors.* -import kotlinx.coroutines.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope suspend fun loadContributorsConcurrent(service: GitHubService, req: RequestData): List = coroutineScope { - TODO() -} \ No newline at end of file + val repos = service + .getOrgReposCall(req.org) + .also { logRepos(req, it) } + .bodyList() + + val deferredListOfUsers = repos.map { repo -> + async { + log("starting loading for ${repo.name}") + service.getRepoContributorsCall(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + } + } + + val list = deferredListOfUsers.awaitAll() + list.flatten().aggregate() +}