Skip to content

Commit

Permalink
Merge branch 'develop' into 'main'
Browse files Browse the repository at this point in the history
improv: Allow starting of update from within the app when using installers

See merge request Griefed/ServerPackCreator!605
  • Loading branch information
Griefed committed Aug 30, 2024
2 parents 1e99a7b + be6e0b0 commit 19fb457
Show file tree
Hide file tree
Showing 4 changed files with 441 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ menubar.gui.config.load.new=New Tab
update.dialog.new=An update to ServerPackCreator is available at:\n{0}\n\nWhat would you like to do?
update.dialog.available=Update available!
update.dialog.yes=Open in Browser
update.dialog.update=Download & Update
update.dialog.update.message=Download is complete, the new version will now be installed.
update.dialog.update.failed.message=Update could not be downloaded.
update.dialog.update.failed.cause=An error has occurred: {0}
update.dialog.update.title=ServerPackCreator
update.dialog.no=Neither
update.dialog.clipboard=Copy to clipboard
filebrowser=File Browser
Expand Down
4 changes: 4 additions & 0 deletions serverpackcreator-app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.ir.backend.js.compile

plugins {
id("serverpackcreator.dokka-conventions")
id("org.springframework.boot") apply false
Expand All @@ -7,6 +9,7 @@ plugins {
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://maven.ej-technologies.com/repository") }
}

dependencyManagement {
Expand Down Expand Up @@ -45,6 +48,7 @@ dependencies {
api("net.java.balloontip:balloontip:1.2.4.1")
api("com.cronutils:cron-utils:9.2.1")
api("tokyo.northside:tipoftheday:0.4.2")
compileOnly("com.install4j:install4j-runtime:10.0.9")

//WEB
api("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
package de.griefed.serverpackcreator.app.gui.window

import Translations
import com.install4j.api.Util
import com.install4j.api.context.UserCanceledException
import com.install4j.api.launcher.ApplicationLauncher
import com.install4j.api.launcher.Variables
import com.install4j.api.update.ApplicationDisplayMode
import com.install4j.api.update.UpdateDescriptor
import com.install4j.api.update.UpdateDescriptorEntry
import de.griefed.serverpackcreator.api.ApiProperties
import de.griefed.serverpackcreator.api.utilities.common.WebUtilities
import de.griefed.serverpackcreator.app.gui.GuiProps
Expand All @@ -30,11 +37,13 @@ import org.apache.logging.log4j.kotlin.cachedLoggerOf
import java.awt.Toolkit
import java.awt.datatransfer.Clipboard
import java.awt.datatransfer.StringSelection
import java.io.IOException
import java.net.URISyntaxException
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
import javax.swing.JFrame
import javax.swing.JOptionPane
import javax.swing.JTextPane
import java.util.concurrent.ExecutionException
import javax.swing.*
import javax.swing.text.*

/**
Expand All @@ -56,6 +65,12 @@ class UpdateDialogs(
apiProperties.isCheckingForPreReleasesEnabled
)
private set
var i4JUpdatable = false
private set

init {
checkForUpdateWithApi()
}

/**
* If an update for ServerPackCreator is available, display a dialog letting the user choose whether they want to
Expand All @@ -81,7 +96,11 @@ class UpdateDialogs(
)
jTextPane.isOpaque = false
jTextPane.isEditable = false
options[0] = Translations.update_dialog_yes.toString()
if (i4JUpdatable) {
options[0] = Translations.update_dialog_update.toString()
} else {
options[0] = Translations.update_dialog_yes.toString()
}
options[1] = Translations.update_dialog_no.toString()
options[2] = Translations.update_dialog_clipboard.toString()
try {
Expand All @@ -100,7 +119,11 @@ class UpdateDialogs(
options[0]
)) {
0 -> try {
webUtilities.openLinkInBrowser(update.get().url().toURI())
if (i4JUpdatable) {
downloadAndUpdate()
} else {
webUtilities.openLinkInBrowser(update.get().url().toURI())
}
} catch (ex: RuntimeException) {
log.error("Error opening browser.", ex)
} catch (ex: URISyntaxException) {
Expand All @@ -121,6 +144,7 @@ class UpdateDialogs(
*/
fun checkForUpdate(): Boolean {
update = updateChecker.checkForUpdate(apiProperties.apiVersion, apiProperties.isCheckingForPreReleasesEnabled)
checkForUpdateWithApi()
if (!displayUpdateDialog()) {
DialogUtilities.createDialog(
Translations.menubar_gui_menuitem_updates_none.toString() + " ",
Expand All @@ -133,4 +157,129 @@ class UpdateDialogs(
}
return update.isPresent
}

private fun isUpdatable(): Boolean {
try {
val installationDirectory = Paths.get(Variables.getInstallerVariable("sys.installationDir").toString())
return !Files.getFileStore(installationDirectory).isReadOnly && (Util.isWindows() || Util.isMacOS() || (Util.isLinux() && !Util.isArchive()))
} catch (ex: IOException) {
log.error("Error checking for install4j updatability.", ex)
}
return false
}

private fun checkForUpdateWithApi() {
try {
if (isUpdatable()) {
// Here we check for updates in the background with the API.
object : SwingWorker<UpdateDescriptorEntry, Any?>() {
@Throws(Exception::class)
override fun doInBackground(): UpdateDescriptorEntry {
// The compiler variable sys.updatesUrl holds the URL where the updates.xml file is hosted.
// That URL is defined on the "Installer->Auto Update Options" step.
// The same compiler variable is used by the "Check for update" actions that are contained in the update
// downloaders.
val updateUrl: String = Variables.getCompilerVariable("sys.updatesUrl")
val updateDescriptor: UpdateDescriptor =
com.install4j.api.update.UpdateChecker.getUpdateDescriptor(updateUrl, ApplicationDisplayMode.GUI)
// If getPossibleUpdateEntry returns a non-null value, the version number in the updates.xml file
// is greater than the version number of the local installation.
return updateDescriptor.possibleUpdateEntry
}

override fun done() {
try {
val updateDescriptorEntry: UpdateDescriptorEntry? = get()
// only installers and single bundle archives on macOS are supported for background updates
if (updateDescriptorEntry != null && (!updateDescriptorEntry.isArchive || updateDescriptorEntry.isSingleBundle)) {
// An update is available for download
i4JUpdatable = true
}
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {
val cause = e.cause
// UserCanceledException means that the user has canceled the proxy dialog
if (cause !is UserCanceledException) {
e.printStackTrace()
}
}
}
}.execute()
} else {
i4JUpdatable = false
}
} catch (ncdfe: NoClassDefFoundError) {
i4JUpdatable = false
log.debug("Not an install4j installation.")
}
}

private fun downloadAndUpdate() {
// Here the background update downloader is launched in the background
// See checkForUpdate(), where the interactive updater is launched for comments on launching an update downloader.
object : SwingWorker<Any?, Any?>() {
@Throws(java.lang.Exception::class)
override fun doInBackground(): Any? {
// Note the third argument which makes the call to the background update downloader blocking.
// The callback receives progress information from the update downloader and changes the text on the button
ApplicationLauncher.launchApplication("442", null, true, null)
// At this point, the update downloader has returned, and we can check if the "Schedule update installation"
// action has registered an update installer for execution
// We now switch to the EDT in done() for terminating the application
return null
}

override fun done() {
try {
get() // rethrow exceptions that occurred in doInBackground() wrapped in an ExecutionException
if (com.install4j.api.update.UpdateChecker.isUpdateScheduled()) {
JOptionPane.showMessageDialog(
mainFrame,
Translations.update_dialog_update_message.toString(),
Translations.update_dialog_update_title.toString(),
JOptionPane.INFORMATION_MESSAGE
)
// We execute the update immediately, but you could ask the user whether the update should be
// installed now. The scheduling of update installers is persistent, so this will also work
// after a restart of the launcher.
executeUpdate()
} else {
JOptionPane.showMessageDialog(
mainFrame,
Translations.update_dialog_update_failed_message.toString(),
Translations.update_dialog_update_title.toString(),
JOptionPane.ERROR_MESSAGE
)
}
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {
e.printStackTrace()
JOptionPane.showMessageDialog(
mainFrame,
Translations.update_dialog_update_failed_cause(e.cause!!.message.toString()),
Translations.update_dialog_update_title.toString(),
JOptionPane.ERROR_MESSAGE
)
}
}
}.execute()
}

private fun executeUpdate() {
// The arguments that are passed to the installer switch the default GUI mode to an unattended
// mode with a progress bar. "-q" activates unattended mode, and "-splash Updating hello world ..."
// shows a progress bar with the specified title.
Thread {
com.install4j.api.update.UpdateChecker.executeScheduledUpdate(
mutableListOf(
"-q",
"-splash",
"Updating ServerPackCreator ...",
"-alerts"
), true, null
)
}.start()
}
}
Loading

0 comments on commit 19fb457

Please sign in to comment.