remote downloading

This commit is contained in:
downthecrop 2025-09-18 20:08:53 -07:00
parent 905393d2be
commit dc70d4406e
3 changed files with 836 additions and 26 deletions

View file

@ -5,6 +5,8 @@ import KondoKit.components.*
import KondoKit.components.ReflectiveEditorComponents.CustomSearchField
import KondoKit.components.ReflectiveEditorComponents.GitLabPlugin
import KondoKit.components.ReflectiveEditorComponents.GitLabPluginFetcher
import KondoKit.components.ReflectiveEditorComponents.PluginDownloadManager
import KondoKit.components.ReflectiveEditorComponents.PluginProperties
import KondoKit.components.ReflectiveEditorComponents.PluginStatus
import KondoKit.Helpers.showToast
import KondoKit.plugin
@ -19,9 +21,12 @@ import plugin.PluginInfo
import plugin.PluginRepository
import rt4.GlobalJsonConfig
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import javax.imageio.ImageIO
import javax.swing.*
import kotlin.math.ceil
@ -268,6 +273,22 @@ object ReflectiveEditorView : View {
panel.add(headerPanel)
panel.add(Box.createVerticalStrut(5))
// Add download all button if there are multiple plugins
if (matchingPluginStatuses.size > 1) {
val downloadAllPanel = JPanel(FlowLayout(FlowLayout.LEFT))
downloadAllPanel.background = VIEW_BACKGROUND_COLOR
val downloadAllButton = JButton("Download All")
downloadAllButton.background = TITLE_BAR_COLOR
downloadAllButton.foreground = secondaryColor
downloadAllButton.font = Font("RuneScape Small", Font.PLAIN, 14)
downloadAllButton.addActionListener {
startMultiplePluginDownloads(matchingPluginStatuses)
}
downloadAllPanel.add(downloadAllButton)
panel.add(downloadAllPanel)
panel.add(Box.createVerticalStrut(5))
}
// Add matching plugin statuses
for (pluginStatus in matchingPluginStatuses) {
System.out.println("Adding plugin to UI: ${pluginStatus.name}")
@ -352,6 +373,12 @@ object ReflectiveEditorView : View {
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
// Add right-click context menu (except for KondoKit itself)
if (packageName != "KondoKit") {
val popupMenu = createPluginContextMenu(plugin, pluginInfo, packageName, false)
addContextMenuToPanel(panel, popupMenu)
}
return panel
}
@ -391,6 +418,12 @@ object ReflectiveEditorView : View {
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
// Add right-click context menu (except for KondoKit itself)
if (pluginName != "KondoKit") {
val popupMenu = createPluginContextMenu(null, null, pluginName, true)
addContextMenuToPanel(panel, popupMenu)
}
return panel
}
@ -453,12 +486,22 @@ object ReflectiveEditorView : View {
actionButton.foreground = secondaryColor
actionButton.font = Font("RuneScape Small", Font.PLAIN, 14)
if (pluginStatus.isInstalled) {
// Progress bar for downloads
val progressBar = JProgressBar(0, 100)
progressBar.isStringPainted = true
progressBar.isVisible = false
progressBar.string = "Downloading..."
if (pluginStatus.isDownloading) {
actionButton.isVisible = false
progressBar.isVisible = true
progressBar.value = pluginStatus.downloadProgress
} else if (pluginStatus.isInstalled) {
if (pluginStatus.needsUpdate) {
actionButton.text = "Update"
actionButton.addActionListener {
// TODO: Implement update functionality
showToast(mainPanel, "Update functionality not yet implemented", JOptionPane.INFORMATION_MESSAGE)
// Start downloading the plugin to update it
startPluginDownload(pluginStatus)
}
} else {
actionButton.text = "Installed"
@ -467,8 +510,8 @@ object ReflectiveEditorView : View {
} else {
actionButton.text = "Download"
actionButton.addActionListener {
// TODO: Implement download functionality
showToast(mainPanel, "Download functionality not yet implemented", JOptionPane.INFORMATION_MESSAGE)
// Start downloading the plugin
startPluginDownload(pluginStatus)
}
}
@ -494,11 +537,18 @@ object ReflectiveEditorView : View {
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
controlsPanel.background = WIDGET_COLOR
controlsPanel.add(actionButton)
controlsPanel.add(progressBar)
controlsPanel.add(toggleSwitch)
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
// Add right-click context menu (except for KondoKit itself)
if (pluginStatus.name != "KondoKit") {
val popupMenu = createPluginContextMenu(null, null, pluginStatus.name, false)
addContextMenuToPanel(panel, popupMenu)
}
return panel
}
@ -810,26 +860,51 @@ object ReflectiveEditorView : View {
customToolTipWindow!!.isVisible = true
}
// Helper method to reset a ScrollablePanel to the top
private fun resetScrollablePanel(scrollablePanel: ScrollablePanel) {
// Access the content panel and reset its position
// Since we can't directly access the private fields, we'll use reflection
// Add this helper function to read plugin properties from a disabled plugin directory
private fun readDisabledPluginProperties(pluginName: String): PluginProperties? {
try {
val contentField = ScrollablePanel::class.java.getDeclaredField("content")
contentField.isAccessible = true
val contentPanel = contentField.get(scrollablePanel) as JPanel
val disabledDir = File(pluginsDirectory, "disabled")
val pluginDir = File(disabledDir, pluginName)
val propertiesFile = File(pluginDir, "plugin.properties")
// Reset the location to the top
contentPanel.setLocation(0, 0)
// Force a repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
if (propertiesFile.exists()) {
val content = propertiesFile.readText()
return parsePluginProperties(content)
}
} catch (e: Exception) {
// If reflection fails, at least try to repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
System.out.println("Error reading properties for disabled plugin $pluginName: ${e.message}")
}
return null
}
// Helper function to parse plugin.properties content
private fun parsePluginProperties(content: String): PluginProperties {
var author = "Unknown"
var version = "Unknown"
var description = "No description available"
val lines = content.split("\n")
for (line in lines) {
val parts = line.split("=")
if (parts.size == 2) {
val key = parts[0].trim()
val value = parts[1].replace("\"", "").replace("'", "").trim()
when {
key.startsWith("AUTHOR", ignoreCase = true) -> {
author = value
}
key.startsWith("VERSION", ignoreCase = true) -> {
version = value
}
key.startsWith("DESCRIPTION", ignoreCase = true) -> {
description = value
}
}
}
}
return PluginProperties(author, version, description)
}
// Helper method to find and reset ScrollablePanel components within a container
@ -868,6 +943,148 @@ object ReflectiveEditorView : View {
return loadedPluginNames
}
// Method to start downloading a plugin
private fun startPluginDownload(pluginStatus: PluginStatus) {
val gitLabPlugin = pluginStatus.gitLabPlugin
if (gitLabPlugin == null) {
showToast(mainPanel, "Plugin information not available", JOptionPane.ERROR_MESSAGE)
return
}
// Log the download URL for debugging
val downloadUrl = PluginDownloadManager.testDownloadUrl(gitLabPlugin)
System.out.println("Download URL for plugin ${gitLabPlugin.path}: $downloadUrl")
// Update plugin status to show downloading
val updatedStatuses = pluginStatuses.map {
if (it.name == pluginStatus.name) {
it.copy(isDownloading = true, downloadProgress = 0)
} else {
it
}
}
pluginStatuses = updatedStatuses
// Refresh UI to show progress bar
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
// Start the download
PluginDownloadManager.downloadPlugin(gitLabPlugin, object : PluginDownloadManager.DownloadProgressCallback {
override fun onProgress(pluginName: String, progress: Int) {
// Update progress in plugin statuses
val updatedStatuses = pluginStatuses.map {
if (it.name == pluginName) {
it.copy(downloadProgress = progress)
} else {
it
}
}
pluginStatuses = updatedStatuses
// Refresh UI to show progress
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
}
override fun onComplete(pluginName: String, success: Boolean, errorMessage: String?) {
// Update plugin status
val updatedStatuses = pluginStatuses.map {
if (it.name == pluginName) {
it.copy(isDownloading = false, downloadProgress = if (success) 100 else 0)
} else {
it
}
}
pluginStatuses = updatedStatuses
// Show result to user
if (success) {
showToast(mainPanel, "Plugin downloaded successfully!", JOptionPane.INFORMATION_MESSAGE)
// Reload plugins to make the newly downloaded plugin available
PluginRepository.reloadPlugins()
} else {
showToast(mainPanel, "Failed to download plugin: ${errorMessage ?: "Unknown error"}", JOptionPane.ERROR_MESSAGE)
}
// Refresh UI
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
}
})
}
// Method to start downloading multiple plugins
private fun startMultiplePluginDownloads(pluginStatuses: List<PluginStatus>) {
val gitLabPlugins = pluginStatuses.mapNotNull { it.gitLabPlugin }
if (gitLabPlugins.isEmpty()) {
showToast(mainPanel, "No valid plugins to download", JOptionPane.ERROR_MESSAGE)
return
}
// Update all plugin statuses to show downloading
val pluginNames = pluginStatuses.map { it.name }.toSet()
val updatedStatuses = pluginStatuses.map {
if (pluginNames.contains(it.name)) {
it.copy(isDownloading = true, downloadProgress = 0)
} else {
it
}
}
this.pluginStatuses = updatedStatuses
// Refresh UI to show progress bars
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
// Counter to track completed downloads
val completedCount = AtomicInteger(0)
val totalCount = gitLabPlugins.size
val failedPlugins = mutableListOf<String>()
// Start the downloads
PluginDownloadManager.downloadPlugins(gitLabPlugins) { pluginName, success, errorMessage ->
// Update progress in plugin statuses
val updatedStatuses = this.pluginStatuses.map {
if (it.name == pluginName) {
it.copy(isDownloading = false, downloadProgress = if (success) 100 else 0)
} else {
it
}
}
this.pluginStatuses = updatedStatuses
// Track completion
if (!success) {
failedPlugins.add(pluginName)
}
val completed = completedCount.incrementAndGet()
// If all downloads are complete, show final message
if (completed == totalCount) {
SwingUtilities.invokeLater {
if (failedPlugins.isEmpty()) {
showToast(mainPanel, "All plugins downloaded successfully!", JOptionPane.INFORMATION_MESSAGE)
// Reload plugins to make the newly downloaded plugins available
PluginRepository.reloadPlugins()
} else {
val failedList = failedPlugins.joinToString(", ")
showToast(mainPanel, "Completed with errors. Failed plugins: $failedList", JOptionPane.WARNING_MESSAGE)
}
// Refresh UI
addPlugins(reflectiveEditorView!!)
}
}
}
}
// Update plugin statuses by comparing installed and remote plugins
private fun updatePluginStatuses() {
val loadedPluginNames = getLoadedPluginNames()
@ -875,6 +1092,12 @@ object ReflectiveEditorView : View {
System.out.println("Updating plugin statuses. Loaded plugins: ${loadedPluginNames.joinToString(", ")}")
// Get disabled plugin names and their versions
val disabledPluginInfo = getDisabledPluginInfo()
// Handle duplicate plugins (loaded and disabled) - delete the disabled version
handleDuplicatePlugins(loadedPluginNames, disabledPluginInfo)
// Process remote plugins
for (gitLabPlugin in gitLabPlugins) {
val pluginName = gitLabPlugin.path
@ -883,7 +1106,15 @@ object ReflectiveEditorView : View {
val author = gitLabPlugin.pluginProperties?.author ?: "Unknown"
val isLoaded = loadedPluginNames.contains(pluginName)
System.out.println("Processing plugin: $pluginName, isLoaded: $isLoaded")
val isDisabled = disabledPluginInfo.containsKey(pluginName)
val disabledVersion = disabledPluginInfo[pluginName]
System.out.println("Processing plugin: $pluginName, isLoaded: $isLoaded, isDisabled: $isDisabled, disabledVersion: $disabledVersion")
// Check if this plugin is currently being downloaded
val existingStatus = pluginStatuses.find { it.name == pluginName }
val isDownloading = existingStatus?.isDownloading ?: false
val downloadProgress = existingStatus?.downloadProgress ?: 0
if (isLoaded) {
// Plugin is currently loaded
@ -895,10 +1126,31 @@ object ReflectiveEditorView : View {
author = author,
gitLabPlugin = gitLabPlugin,
isInstalled = true,
needsUpdate = false // We assume it's up-to-date since it's loaded
needsUpdate = false, // We assume it's up-to-date since it's loaded
isDownloading = isDownloading,
downloadProgress = downloadProgress
))
} else if (isDisabled) {
// Plugin is disabled, check if versions match
val versionsMatch = disabledVersion != null && disabledVersion == remoteVersion
if (!versionsMatch) {
// Versions don't match, show update option
statuses.add(PluginStatus(
name = pluginName,
installedVersion = disabledVersion,
remoteVersion = remoteVersion,
description = description,
author = author,
gitLabPlugin = gitLabPlugin,
isInstalled = true, // It's installed but disabled
needsUpdate = true, // Needs update since versions don't match
isDownloading = isDownloading,
downloadProgress = downloadProgress
))
}
// If versions match, we don't add it to the list since there's no point showing it
} else {
// Plugin is not loaded
// Plugin is not installed at all
statuses.add(PluginStatus(
name = pluginName,
installedVersion = null,
@ -907,7 +1159,9 @@ object ReflectiveEditorView : View {
author = author,
gitLabPlugin = gitLabPlugin,
isInstalled = false,
needsUpdate = false
needsUpdate = false,
isDownloading = isDownloading,
downloadProgress = downloadProgress
))
}
}
@ -915,4 +1169,145 @@ object ReflectiveEditorView : View {
System.out.println("Updated plugin statuses. Total statuses: ${statuses.size}")
pluginStatuses = statuses
}
// Helper method to handle duplicate plugins (loaded and disabled)
private fun handleDuplicatePlugins(loadedPluginNames: Set<String>, disabledPluginInfo: Map<String, String>) {
for (pluginName in loadedPluginNames) {
if (disabledPluginInfo.containsKey(pluginName)) {
System.out.println("Found duplicate plugin: $pluginName. Deleting disabled version.")
try {
val disabledDir = File(pluginsDirectory, "disabled")
val pluginDir = File(disabledDir, pluginName)
if (pluginDir.exists() && pluginDir.isDirectory) {
if (deleteRecursively(pluginDir)) {
System.out.println("Successfully deleted disabled version of $pluginName")
} else {
System.out.println("Failed to delete disabled version of $pluginName")
}
}
} catch (e: Exception) {
System.out.println("Error deleting disabled version of $pluginName: ${e.message}")
}
}
}
}
// Helper method to get disabled plugin names and their versions
private fun getDisabledPluginInfo(): Map<String, String> {
val disabledPluginInfo = mutableMapOf<String, String>()
val disabledDir = File(pluginsDirectory, "disabled")
if (disabledDir.exists() && disabledDir.isDirectory) {
val disabledPlugins = disabledDir.listFiles { file -> file.isDirectory } ?: arrayOf()
for (pluginDir in disabledPlugins) {
try {
val propertiesFile = File(pluginDir, "plugin.properties")
if (propertiesFile.exists()) {
val content = propertiesFile.readText()
val properties = parsePluginProperties(content)
disabledPluginInfo[pluginDir.name] = properties.version
System.out.println("Found disabled plugin: ${pluginDir.name}, version: ${properties.version}")
}
} catch (e: Exception) {
System.out.println("Error reading properties for disabled plugin ${pluginDir.name}: ${e.message}")
}
}
}
return disabledPluginInfo
}
// Helper method to add context menu to a panel
private fun addContextMenuToPanel(panel: JPanel, popupMenu: JPopupMenu) {
val rightClickListener = object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
if (e.isPopupTrigger) {
popupMenu.show(e.component, e.x, e.y)
}
}
override fun mouseReleased(e: MouseEvent) {
if (e.isPopupTrigger) {
popupMenu.show(e.component, e.x, e.y)
}
}
}
panel.addMouseListener(rightClickListener)
}
// Helper method to create context menu for plugins
private fun createPluginContextMenu(plugin: Plugin?, pluginInfo: PluginInfo?, pluginName: String, isDisabled: Boolean): JPopupMenu {
val popupMenu = PopupMenuComponent()
// Add "Delete" menu item
popupMenu.addMenuItem("Delete Plugin") {
deletePlugin(pluginName, isDisabled)
}
return popupMenu
}
// Helper method to delete a plugin
private fun deletePlugin(pluginName: String, isDisabled: Boolean) {
try {
val pluginDir = if (isDisabled) {
File(File(pluginsDirectory, "disabled"), pluginName)
} else {
File(pluginsDirectory, pluginName)
}
if (pluginDir.exists() && pluginDir.isDirectory) {
// Recursively delete the directory
if (deleteRecursively(pluginDir)) {
showToast(mainPanel, "Plugin deleted successfully", JOptionPane.INFORMATION_MESSAGE)
// Refresh the plugin list view
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
} else {
showToast(mainPanel, "Failed to delete plugin", JOptionPane.ERROR_MESSAGE)
}
} else {
showToast(mainPanel, "Plugin directory not found", JOptionPane.ERROR_MESSAGE)
}
} catch (e: Exception) {
e.printStackTrace()
showToast(mainPanel, "Error deleting plugin: ${e.message}", JOptionPane.ERROR_MESSAGE)
}
}
// Helper method to recursively delete a directory
private fun deleteRecursively(file: File): Boolean {
if (file.isDirectory) {
file.listFiles()?.forEach { child ->
if (!deleteRecursively(child)) {
return false
}
}
}
return file.delete()
}
// Helper method to reset a ScrollablePanel to the top
private fun resetScrollablePanel(scrollablePanel: ScrollablePanel) {
// Access the content panel and reset its position
// Since we can't directly access the private fields, we'll use reflection
try {
val contentField = ScrollablePanel::class.java.getDeclaredField("content")
contentField.isAccessible = true
val contentPanel = contentField.get(scrollablePanel) as JPanel
// Reset the location to the top
contentPanel.setLocation(0, 0)
// Force a repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
} catch (e: Exception) {
// If reflection fails, at least try to repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
}
}
}