Skip to content

Commit 964d956

Browse files
committed
Use GitHub's paging API properly to make sure we always get all issues
1 parent bc21284 commit 964d956

File tree

10 files changed

+97
-31
lines changed

10 files changed

+97
-31
lines changed

src/main/kotlin/com/demonwav/mcdev/creator/PlatformVersion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package com.demonwav.mcdev.creator
1212

1313
import com.demonwav.mcdev.platform.PlatformType
1414
import com.demonwav.mcdev.util.fromJson
15-
import com.google.gson.Gson
15+
import com.demonwav.mcdev.util.gson
1616
import org.jetbrains.concurrency.runAsync
1717
import java.net.URL
1818
import java.util.Arrays
@@ -45,7 +45,7 @@ fun getVersionSelector(type: PlatformType) = runAsync {
4545
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"
4646
)
4747
val text = connection.getInputStream().use { it.reader().use { it.readText() } }
48-
Gson().fromJson<PlatformVersion>(text)
48+
gson.fromJson<PlatformVersion>(text)
4949
}
5050

5151
data class PlatformVersion(var versions: Array<String>, var selectedIndex: Int) {

src/main/kotlin/com/demonwav/mcdev/error/AnonymousFeedback.kt

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ package com.demonwav.mcdev.error
1212

1313
import com.demonwav.mcdev.update.PluginUtil
1414
import com.demonwav.mcdev.util.fromJson
15-
import com.google.gson.Gson
15+
import com.demonwav.mcdev.util.gson
1616
import com.intellij.ide.plugins.PluginManager
17+
import com.intellij.util.io.readCharSequence
1718
import org.apache.commons.io.IOUtils
19+
import java.io.InputStreamReader
1820
import java.net.HttpURLConnection
1921
import java.net.URL
2022

@@ -25,7 +27,7 @@ object AnonymousFeedback {
2527
const val url = "https://www.demonwav.com/errorReport"
2628

2729
fun sendFeedback(factory: HttpConnectionFactory, envDetails: LinkedHashMap<String, String?>): FeedbackData {
28-
val duplicateId = findDuplicateIssue(envDetails)
30+
val duplicateId = findDuplicateIssue(envDetails, factory)
2931
if (duplicateId != null) {
3032
// This is a duplicate
3133
val commentUrl = sendCommentOnDuplicateIssue(duplicateId, factory, convertToGitHubIssueFormat(envDetails))
@@ -40,7 +42,7 @@ object AnonymousFeedback {
4042
val result = LinkedHashMap<String, String>(5)
4143
result.put("title", "[auto-generated] Exception in plugin")
4244
result.put("body", generateGitHubIssueBody(envDetails))
43-
return Gson().toJson(result).toByteArray()
45+
return gson.toJson(result).toByteArray()
4446
}
4547

4648
private fun generateGitHubIssueBody(body: LinkedHashMap<String, String?>): String {
@@ -77,6 +79,7 @@ object AnonymousFeedback {
7779

7880
private fun sendFeedback(factory: HttpConnectionFactory, payload: ByteArray): Pair<String, Int> {
7981
val connection = getConnection(factory, url)
82+
connection.connect()
8083
connection.outputStream.use {
8184
it.write(payload)
8285
}
@@ -90,8 +93,9 @@ object AnonymousFeedback {
9093
val body = connection.inputStream.use {
9194
IOUtils.toString(it, contentEncoding)
9295
}
96+
connection.disconnect()
9397

94-
val json = Gson().fromJson<Map<*, *>>(body)
98+
val json = gson.fromJson<Map<*, *>>(body)
9599
return json["html_url"] as String to (json["number"] as Double).toInt()
96100
}
97101

@@ -102,11 +106,11 @@ object AnonymousFeedback {
102106
return connection
103107
}
104108

105-
private fun findDuplicateIssue(envDetails: LinkedHashMap<String, String?>): Int? {
109+
private fun findDuplicateIssue(envDetails: LinkedHashMap<String, String?>, factory: HttpConnectionFactory): Int? {
106110
val stack = envDetails["error.stacktrace"]?.replace("\\d+".toRegex(), "") ?: return null
107111

108-
val text = URL("https://api.github.com/repos/minecraft-dev/MinecraftDev/issues?state=all&creator=minecraft-dev-autoreporter").readText()
109-
val list = Gson().fromJson<List<Map<*, *>>>(text)
112+
val list = getAllIssues("https://api.github.com/repos/minecraft-dev/MinecraftDev/issues" +
113+
"?state=all&creator=minecraft-dev-autoreporter&per_page=100", factory) ?: return null
110114
val block = list.firstOrNull {
111115
val body = it["body"] as? String ?: return@firstOrNull false
112116

@@ -124,6 +128,65 @@ object AnonymousFeedback {
124128
return (block["number"] as Double).toInt()
125129
}
126130

131+
private fun getAllIssues(url: String, factory: HttpConnectionFactory): List<Map<*, *>>? {
132+
var connection = connect(factory, url)
133+
connection.requestMethod = "GET"
134+
connection.setRequestProperty("User-Agent", userAgent)
135+
136+
connection.connect()
137+
if (connection.responseCode != 200) {
138+
connection.disconnect()
139+
return null
140+
}
141+
142+
val list = mutableListOf<Map<*, *>>()
143+
var data = connection.inputStream.reader().use(InputStreamReader::readCharSequence).toString()
144+
145+
var response = gson.fromJson<List<Map<*, *>>>(data)
146+
list.addAll(response)
147+
148+
var link = connection.getHeaderField("Link")
149+
connection.disconnect()
150+
151+
var next = getNextLink(link)
152+
while (next != null) {
153+
connection = connect(factory, next)
154+
connection.requestMethod = "GET"
155+
connection.setRequestProperty("User-Agent", userAgent)
156+
157+
connection.connect()
158+
if (connection.responseCode != 200) {
159+
connection.disconnect()
160+
continue
161+
}
162+
163+
data = connection.inputStream.reader().use(InputStreamReader::readCharSequence).toString()
164+
165+
response = gson.fromJson<List<Map<*, *>>>(data)
166+
list.addAll(response)
167+
168+
link = connection.getHeaderField("Link")
169+
connection.disconnect()
170+
next = getNextLink(link)
171+
}
172+
173+
return list
174+
}
175+
176+
private fun getNextLink(link: String): String? {
177+
val lines = link.split(",")
178+
for (line in lines) {
179+
if (!line.contains("rel=\"next\"")) {
180+
continue
181+
}
182+
183+
val parts = line.split(";")
184+
return parts[0].substring(1, parts[0].length-1)
185+
}
186+
187+
return null
188+
}
189+
127190
private fun sendCommentOnDuplicateIssue(id: Int, factory: HttpConnectionFactory, payload: ByteArray): String {
128191
val commentUrl = "$url/$id/comments"
129192
val connection = getConnection(factory, commentUrl)
@@ -140,21 +203,13 @@ object AnonymousFeedback {
140203
val body = connection.inputStream.use {
141204
IOUtils.toString(it, contentEncoding)
142205
}
206+
connection.disconnect()
143207

144-
val json = Gson().fromJson<Map<*, *>>(body)
208+
val json = gson.fromJson<Map<*, *>>(body)
145209
return json["html_url"] as String
146210
}
147211

148212
private fun getConnection(factory: HttpConnectionFactory, url: String): HttpURLConnection {
149-
var userAgent = "Minecraft Development IntelliJ IDEA plugin"
150-
151-
val pluginDescription = PluginManager.getPlugin(PluginUtil.PLUGIN_ID)
152-
if (pluginDescription != null) {
153-
val name = pluginDescription.name
154-
val version = pluginDescription.version
155-
userAgent = "$name ($version)"
156-
}
157-
158213
val connection = connect(factory, url)
159214
connection.doOutput = true
160215
connection.requestMethod = "POST"
@@ -164,6 +219,18 @@ object AnonymousFeedback {
164219
return connection
165220
}
166221

222+
private val userAgent by lazy {
223+
var agent = "Minecraft Development IntelliJ IDEA plugin"
224+
225+
val pluginDescription = PluginManager.getPlugin(PluginUtil.PLUGIN_ID)
226+
if (pluginDescription != null) {
227+
val name = pluginDescription.name
228+
val version = pluginDescription.version
229+
agent = "$name ($version)"
230+
}
231+
agent
232+
}
233+
167234
open class HttpConnectionFactory {
168235
open fun openHttpConnection(url: String) = URL(url).openConnection() as HttpURLConnection
169236
}

src/main/kotlin/com/demonwav/mcdev/insight/ColorAnnotator.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import com.intellij.lang.annotation.AnnotationHolder
1515
import com.intellij.lang.annotation.Annotator
1616
import com.intellij.lang.annotation.HighlightSeverity
1717
import com.intellij.openapi.editor.colors.TextAttributesKey
18-
import com.intellij.openapi.editor.colors.TextAttributesScheme
1918
import com.intellij.openapi.editor.markup.TextAttributes
2019
import com.intellij.psi.PsiElement
2120
import java.awt.Color

src/main/kotlin/com/demonwav/mcdev/platform/bukkit/BukkitModule.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import com.intellij.psi.PsiLiteralExpression
3434
import com.intellij.psi.PsiMethod
3535
import com.intellij.psi.PsiMethodCallExpression
3636
import com.intellij.psi.PsiType
37-
import com.intellij.psi.impl.compiled.ClsMethodImpl
3837
import com.intellij.psi.search.GlobalSearchScope
3938
import com.intellij.psi.util.PsiTypesUtil
4039
import org.jetbrains.annotations.Contract

src/main/kotlin/com/demonwav/mcdev/platform/forge/version/ForgeVersion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
package com.demonwav.mcdev.platform.forge.version
1212

13+
import com.demonwav.mcdev.util.gson
1314
import com.demonwav.mcdev.util.sortVersions
14-
import com.google.gson.Gson
1515
import java.io.IOException
1616
import java.net.URL
1717
import java.util.ArrayList
@@ -81,7 +81,7 @@ class ForgeVersion private constructor(private val map: Map<*, *>) {
8181
try {
8282
val text = URL("https://files.minecraftforge.net/maven/net/minecraftforge/forge/json").readText()
8383

84-
val map = Gson().fromJson(text, Map::class.java)
84+
val map = gson.fromJson(text, Map::class.java)
8585
val forgeVersion = ForgeVersion(map)
8686
forgeVersion.sortedMcVersions // sort em up
8787
return forgeVersion

src/main/kotlin/com/demonwav/mcdev/platform/liteloader/version/LiteLoaderVersion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
package com.demonwav.mcdev.platform.liteloader.version
1212

1313
import com.demonwav.mcdev.util.fromJson
14+
import com.demonwav.mcdev.util.gson
1415
import com.demonwav.mcdev.util.sortVersions
15-
import com.google.gson.Gson
1616
import java.io.IOException
1717
import java.net.URL
1818

@@ -28,7 +28,7 @@ class LiteLoaderVersion private constructor(private var map: Map<*, *>) {
2828
try {
2929
val text = URL("http://dl.liteloader.com/versions/versions.json").readText()
3030

31-
val map = Gson().fromJson<Map<*, *>>(text)
31+
val map = gson.fromJson<Map<*, *>>(text)
3232
val liteLoaderVersion = LiteLoaderVersion(map)
3333
liteLoaderVersion.sortedMcVersions
3434
return liteLoaderVersion

src/main/kotlin/com/demonwav/mcdev/platform/mcp/version/McpVersion.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
package com.demonwav.mcdev.platform.mcp.version
1212

1313
import com.demonwav.mcdev.util.fromJson
14+
import com.demonwav.mcdev.util.gson
1415
import com.demonwav.mcdev.util.sortVersions
15-
import com.google.gson.Gson
16-
import com.google.gson.reflect.TypeToken
1716
import java.io.IOException
1817
import java.net.URL
1918
import java.util.ArrayList
@@ -77,7 +76,7 @@ class McpVersion private constructor(private val map: Map<String, Map<String, Li
7776
fun downloadData(): McpVersion? {
7877
try {
7978
val text = URL("http://export.mcpbot.bspk.rs/versions.json").readText()
80-
val map = Gson().fromJson<Map<String, Map<String, List<Int>>>>(text)
79+
val map = gson.fromJson<Map<String, Map<String, List<Int>>>>(text)
8180
val mcpVersion = McpVersion(map)
8281
mcpVersion.versions
8382
return mcpVersion

src/main/kotlin/com/demonwav/mcdev/platform/sponge/SpongeModule.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import com.demonwav.mcdev.platform.sponge.generation.SpongeGenerationData
2020
import com.demonwav.mcdev.platform.sponge.util.SpongeConstants
2121
import com.demonwav.mcdev.util.extendsOrImplements
2222
import com.demonwav.mcdev.util.findContainingMethod
23-
import com.intellij.codeInspection.ProblemDescriptor
2423
import com.intellij.psi.JavaPsiFacade
2524
import com.intellij.psi.PsiAnnotationMemberValue
2625
import com.intellij.psi.PsiClass

src/main/kotlin/com/demonwav/mcdev/platform/sponge/SpongeVersion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
package com.demonwav.mcdev.platform.sponge
1212

1313
import com.demonwav.mcdev.util.fromJson
14-
import com.google.gson.Gson
14+
import com.demonwav.mcdev.util.gson
1515
import org.jetbrains.concurrency.runAsync
1616
import java.net.URL
1717
import java.util.Objects
@@ -58,7 +58,7 @@ data class SpongeVersion(var versions: LinkedHashMap<String, String>, var select
5858
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"
5959
)
6060
val text = connection.getInputStream().use { it.reader().use { it.readText() } }
61-
return Gson().fromJson<SpongeVersion>(text)
61+
return gson.fromJson<SpongeVersion>(text)
6262
}
6363
}
6464
}

src/main/kotlin/com/demonwav/mcdev/util/Util.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ fun Module.findChildren(): Set<Module> {
133133
}
134134
}
135135

136+
// Keep a single gson constant around rather than initializing it everywhere
137+
val gson = Gson()
138+
136139
inline fun <reified T : Any> Gson.fromJson(text: String): T {
137140
// Using the ugly TypeToken approach we can any complex generic signature, including
138141
// nested generics

0 commit comments

Comments
 (0)