diff --git a/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/GitLabPluginFetcher.kt b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/GitLabPluginFetcher.kt index bb7a417..b79dc8d 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/GitLabPluginFetcher.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/GitLabPluginFetcher.kt @@ -4,105 +4,85 @@ import KondoKit.pluginmanager.GitLabPlugin import KondoKit.pluginmanager.PluginProperties import KondoKit.util.HttpFetcher import KondoKit.pluginmanager.GitLabConfig +import KondoKit.util.HttpStatusException import KondoKit.util.JsonParser import com.google.gson.JsonObject -import java.io.BufferedReader -import java.io.InputStreamReader -import java.net.HttpURLConnection import java.util.concurrent.* import javax.swing.SwingUtilities object GitLabPluginFetcher { - + private const val TAG = "GitLabPluginFetcher" + // Thread pool for concurrent plugin property fetching private val executorService = Executors.newFixedThreadPool(5) - + // Debug logging function - private fun debugLog(message: String) { - if (GitLabConfig.DEBUG) { - println("[GitLabPluginFetcher] $message") - } - } - + private fun debugLog(message: String) = PluginLogger.debug(TAG, message) + // Function to fetch plugins from GitLab fun fetchGitLabPlugins(onComplete: (List) -> Unit) { Thread { + val plugins = mutableListOf() try { debugLog("Starting to fetch GitLab plugins...") - val plugins = mutableListOf() val apiUrl = GitLabConfig.getRepositoryTreeUrl() debugLog("API URL: $apiUrl") - - val connection = HttpFetcher.openGetConnection(apiUrl) - - // Read response - val responseCode = connection.responseCode - debugLog("Response code: $responseCode") - - if (responseCode == HttpURLConnection.HTTP_OK) { - val reader = BufferedReader(InputStreamReader(connection.inputStream)) - val response = reader.use { it.readText() } - debugLog("Response length: ${response.length} characters") - debugLog("Response preview: ${response.take(200)}...") - - // Parse JSON response - val treeItems = JsonParser.fromJson(response, Array::class.java) - debugLog("Parsed ${treeItems.size} items from JSON") - - // Filter for directories (trees) with mode "040000" - val pluginDirectories = mutableListOf>() - for (jsonObject in treeItems) { - if (jsonObject["type"].asString == "tree" && jsonObject["mode"].asString == "040000") { - val folderId = jsonObject["id"].asString - val folderPath = jsonObject["path"].asString - pluginDirectories.add(folderId to folderPath) - debugLog("Found directory: $folderPath (ID: $folderId)") - } + + val response = HttpFetcher.fetchString(apiUrl) + debugLog("Response length: ${response.length} characters") + debugLog("Response preview: ${response.take(200)}...") + + // Parse JSON response + val treeItems = JsonParser.fromJson(response, Array::class.java) + debugLog("Parsed ${treeItems.size} items from JSON") + + // Filter for directories (trees) with mode "040000" + val pluginDirectories = mutableListOf>() + for (jsonObject in treeItems) { + if (jsonObject["type"].asString == "tree" && jsonObject["mode"].asString == "040000") { + val folderId = jsonObject["id"].asString + val folderPath = jsonObject["path"].asString + pluginDirectories.add(folderId to folderPath) + debugLog("Found directory: $folderPath (ID: $folderId)") } - debugLog("Found ${pluginDirectories.size} plugin directories") - - // Fetch plugin properties in parallel - val pluginFutures = mutableListOf>() - for ((folderId, folderPath) in pluginDirectories) { - val future = executorService.submit(Callable { - try { - val pluginProperties = fetchPluginProperties(folderPath) - debugLog("Successfully fetched properties for: $folderPath") - GitLabPlugin(folderId, folderPath, pluginProperties, null) - } catch (e: Exception) { - debugLog("Error fetching plugin.properties for $folderPath: ${e.message}") - GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties") - } - }) - pluginFutures.add(future) - } - - // Collect results - for (future in pluginFutures) { + } + debugLog("Found ${pluginDirectories.size} plugin directories") + + // Fetch plugin properties in parallel + val pluginFutures = mutableListOf>() + for ((folderId, folderPath) in pluginDirectories) { + val future = executorService.submit(Callable { try { - plugins.add(future.get()) + val pluginProperties = fetchPluginProperties(folderPath) + debugLog("Successfully fetched properties for: $folderPath") + GitLabPlugin(folderId, folderPath, pluginProperties, null) } catch (e: Exception) { - debugLog("Error getting plugin result: ${e.message}") + debugLog("Error fetching plugin.properties for $folderPath: ${e.message}") + GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties") } + }) + pluginFutures.add(future) + } + + // Collect results + for (future in pluginFutures) { + try { + plugins.add(future.get()) + } catch (e: Exception) { + debugLog("Error getting plugin result: ${e.message}") } - } else { - debugLog("HTTP error: $responseCode") - val errorReader = BufferedReader(InputStreamReader(connection.errorStream)) - val errorResponse = errorReader.use { it.readText() } - debugLog("Error response: $errorResponse") - } - - debugLog("Completed fetching plugins. Total plugins: ${plugins.size}") - // Update on UI thread - SwingUtilities.invokeLater { - onComplete(plugins) } + } catch (e: HttpStatusException) { + debugLog("HTTP error ${e.statusCode} fetching plugin tree: ${e.body.take(200)}") } catch (e: Exception) { debugLog("Exception occurred: ${e.message}") e.printStackTrace() - SwingUtilities.invokeLater { - onComplete(emptyList()) - } + } + + debugLog("Completed fetching plugins. Total plugins: ${plugins.size}") + // Update on UI thread + SwingUtilities.invokeLater { + onComplete(plugins) } }.start() } @@ -113,25 +93,16 @@ object GitLabPluginFetcher { val pluginUrl = GitLabConfig.getRawFileUrl(pluginFilePath) debugLog("Fetching plugin.properties from: $pluginUrl") - val connection = HttpFetcher.openGetConnection(pluginUrl) - - // Read response - val responseCode = connection.responseCode - debugLog("plugin.properties response code: $responseCode") - - if (responseCode == HttpURLConnection.HTTP_OK) { - val reader = BufferedReader(InputStreamReader(connection.inputStream)) - val rawContent = reader.use { it.readText() } + try { + val rawContent = HttpFetcher.fetchString(pluginUrl) debugLog("plugin.properties content length: ${rawContent.length} characters") debugLog("plugin.properties content preview: ${rawContent.take(200)}...") - + return parseProperties(rawContent) - } else { - debugLog("Failed to fetch plugin.properties. Response code: $responseCode") - val errorReader = BufferedReader(InputStreamReader(connection.errorStream)) - val errorResponse = errorReader.use { it.readText() } - debugLog("Error response for plugin.properties: $errorResponse") - throw Exception("Plugin file not found for folder: $folderPath") + } catch (e: HttpStatusException) { + debugLog("Failed to fetch plugin.properties. Response code: ${e.statusCode}") + debugLog("Error response for plugin.properties: ${e.body.take(200)}") + throw Exception("Plugin file not found for folder: $folderPath", e) } } diff --git a/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginDownloadManager.kt b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginDownloadManager.kt index 067a6b6..666af1f 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginDownloadManager.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginDownloadManager.kt @@ -18,6 +18,7 @@ import javax.swing.SwingUtilities * Manages downloading and installing plugins from GitLab with concurrent support */ object PluginDownloadManager { + private const val TAG = "PluginDownloadManager" private const val MAX_CONCURRENT_DOWNLOADS = 3 // Thread pool for concurrent downloads @@ -30,12 +31,8 @@ object PluginDownloadManager { } // Debug logging function - private fun debugLog(message: String) { - if (GitLabConfig.DEBUG) { - println("[PluginDownloadManager] $message") - } - } - + private fun debugLog(message: String) = PluginLogger.debug(TAG, message) + // Get download URL for debugging/logging purposes fun getDownloadUrlForLogging(plugin: GitLabPlugin): String { return GitLabConfig.getArchiveUrl(plugin.path) diff --git a/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginLogger.kt b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginLogger.kt new file mode 100644 index 0000000..f84de71 --- /dev/null +++ b/plugin-playground/src/main/kotlin/KondoKit/pluginmanager/PluginLogger.kt @@ -0,0 +1,13 @@ +package KondoKit.pluginmanager + +/** + * Shared lightweight logger so pluginmanager classes don't each redefine + * their own debug helper tied to GitLabConfig.DEBUG. + */ +object PluginLogger { + fun debug(tag: String, message: String) { + if (GitLabConfig.DEBUG) { + println("[$tag] $message") + } + } +} diff --git a/plugin-playground/src/main/kotlin/KondoKit/ui/components/SettingsPanel.kt b/plugin-playground/src/main/kotlin/KondoKit/ui/components/SettingsPanel.kt index a16ab08..e186317 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/ui/components/SettingsPanel.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/ui/components/SettingsPanel.kt @@ -435,50 +435,50 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() { companion object { var customToolTipWindow: JWindow? = null + private var customToolTipLabel: JLabel? = null + fun showCustomToolTip(text: String, component: JComponent) { val tooltipFont = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16 val maxWidth = 150 - // Create a dummy JLabel to get FontMetrics for the font used in the tooltip - val dummyLabel = JLabel() - dummyLabel.font = tooltipFont - val fontMetrics = dummyLabel.getFontMetrics(tooltipFont) - val lineHeight = fontMetrics.height - - // Calculate the approximate width of the text - val textWidth = fontMetrics.stringWidth(text) - - // Calculate the number of lines required based on the text width and max tooltip width - val numberOfLines = Math.ceil(textWidth.toDouble() / maxWidth).toInt() - - // Calculate the required height of the tooltip - val requiredHeight = numberOfLines * lineHeight + 6 // Adding some padding - - if (customToolTipWindow == null) { - customToolTipWindow = JWindow().apply { - val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND) - val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor) - contentPane = JLabel("
$text
").apply { - border = BorderFactory.createLineBorder(Color.BLACK) - isOpaque = true - background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND - foreground = Color.WHITE - font = tooltipFont - maximumSize = Dimension(maxWidth, Int.MAX_VALUE) - preferredSize = Dimension(maxWidth, requiredHeight) - } - pack() + val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND) + val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor) + // Constrain width via HTML so Swing's renderer wraps the text naturally. + val safeText = text + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + val html = """ + + + $safeText + + + """.trimIndent() + + val (window, label) = if (customToolTipWindow == null || customToolTipLabel == null) { + val lbl = JLabel().apply { + border = BorderFactory.createLineBorder(Color.BLACK) + isOpaque = true + background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND + foreground = Color.WHITE + font = tooltipFont + horizontalAlignment = SwingConstants.LEFT + verticalAlignment = SwingConstants.TOP } + val win = JWindow().apply { contentPane.add(lbl) } + customToolTipWindow = win + customToolTipLabel = lbl + win to lbl } else { - // Update the tooltip text - val label = customToolTipWindow!!.contentPane as JLabel - val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND) - val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor) - label.text = "
$text
" - label.preferredSize = Dimension(maxWidth, requiredHeight) - customToolTipWindow!!.pack() + customToolTipWindow!! to customToolTipLabel!! } - + + label.text = html + label.font = tooltipFont + label.preferredSize = null // let HTML + pack decide size from width constraint + window.pack() + // Position the tooltip near the component val locationOnScreen = component.locationOnScreen customToolTipWindow!!.setLocation(locationOnScreen.x, locationOnScreen.y + 15) diff --git a/plugin-playground/src/main/kotlin/KondoKit/util/GEPriceService.kt b/plugin-playground/src/main/kotlin/KondoKit/util/GEPriceService.kt index 2d64a43..72c2656 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/util/GEPriceService.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/util/GEPriceService.kt @@ -18,15 +18,23 @@ object GEPriceService { val grand_exchange_price: String? ) + private fun mapToPriceMap( + items: Array, + idSelector: (T) -> String?, + priceSelector: (T) -> String? + ): Map { + return items.mapNotNull { item -> + val id = idSelector(item) + val price = priceSelector(item) + if (id != null && price != null) id to price else null + }.toMap() + } + fun loadRemotePrices(): Map { return try { val payload = HttpFetcher.fetchString(REMOTE_GE_URL) val items = JsonParser.fromJson>(payload) - items.mapNotNull { item -> - val id = item.item_id - val value = item.value - if (id != null && value != null) id to value else null - }.toMap() + mapToPriceMap(items, { it.item_id }, { it.value }) } catch (e: Exception) { emptyMap() } @@ -36,11 +44,7 @@ object GEPriceService { return try { val json = jsonProvider() ?: return emptyMap() val items = JsonParser.fromJson>(json) - items.mapNotNull { item -> - val id = item.id - val price = item.grand_exchange_price - if (id != null && price != null) id to price else null - }.toMap() + mapToPriceMap(items, { it.id }, { it.grand_exchange_price }) } catch (e: Exception) { emptyMap() } diff --git a/plugin-playground/src/main/kotlin/KondoKit/views/LootTrackerView.kt b/plugin-playground/src/main/kotlin/KondoKit/views/LootTrackerView.kt index 5801b2f..11239c3 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/views/LootTrackerView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/views/LootTrackerView.kt @@ -15,6 +15,7 @@ import KondoKit.util.setFixedSize import KondoKit.views.XPTrackerView.wrappedWidget import KondoKit.ui.components.PopupMenuComponent import KondoKit.ui.components.ProgressBar +import KondoKit.ui.components.LabelComponent import KondoKit.ui.components.WidgetPanel import KondoKit.plugin.Companion.TITLE_BAR_COLOR import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND @@ -171,12 +172,8 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac } } - private fun createLabel(text: String): JLabel { - return JLabel(text).apply { - font = ViewConstants.FONT_RUNESCAPE_SMALL_16 - horizontalAlignment = JLabel.LEFT - } - } + private fun createLabel(text: String): LabelComponent = + LabelComponent(text, alignment = JLabel.LEFT) private fun addItemToLootPanel(lootTrackerView: JPanel, drop: Item, npcName: String) { findLootItemsPanel(lootTrackerView, npcName)?.let { lootPanel -> diff --git a/plugin-playground/src/main/kotlin/KondoKit/views/XPTrackerView.kt b/plugin-playground/src/main/kotlin/KondoKit/views/XPTrackerView.kt index 2439925..cced344 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/views/XPTrackerView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/views/XPTrackerView.kt @@ -16,6 +16,7 @@ import KondoKit.util.attachPopupMenu import KondoKit.util.XPTable import KondoKit.ui.components.PopupMenuComponent import KondoKit.ui.components.ProgressBar +import KondoKit.ui.components.LabelComponent import KondoKit.ui.components.WidgetPanel import KondoKit.ui.components.XPWidget import KondoKit.plugin.Companion.IMAGE_SIZE @@ -91,17 +92,13 @@ object XPTrackerView : View, OnUpdateCallback, OnXPUpdateCallback { } } - private fun createMetricLabel(title: String, initialValue: String = "0", topPadding: Int = 0): JLabel { - return JLabel(formatHtmlLabelText("$title ", primaryColor, initialValue, secondaryColor)).apply { - horizontalAlignment = JLabel.LEFT - font = widgetFont + private fun createMetricLabel(title: String, initialValue: String = "0", topPadding: Int = 0): LabelComponent = + LabelComponent(alignment = JLabel.LEFT).apply { + updateHtmlText("$title ", primaryColor, initialValue, secondaryColor) if (topPadding > 0) { border = BorderFactory.createEmptyBorder(topPadding, 0, 0, 0) - } else { - border = BorderFactory.createEmptyBorder(0, 0, 0, 0) } } - } private fun getSkillIcon(skillId: Int): BufferedImage { return skillIconCache[skillId] ?: getBufferedImageFromSprite(API.GetSprite(getSpriteId(skillId))).also {