mirror of
https://github.com/Darkrock-Studios/hammer-editor.git
synced 2026-10-19 01:31:24 +00:00
Fix PDF export crash on desktop (#789)
ProGuard stripped org.apache.commons.logging.impl.LogFactoryImpl, which LogFactory resolves by name, so PDDocument's static init threw ExceptionInInitializerError on every PDF export from a packaged build. Only pdfkmp-jvm pulls in PDFBox, so Android is unaffected. Export failures also went uncaught in ExportDirectoryPicker on all three platforms, killing the app instead of showing the existing failure toast.
This commit is contained in:
parent
540392a7a7
commit
5c3d90ac7f
4 changed files with 54 additions and 16 deletions
|
|
@ -19,6 +19,8 @@ import com.darkrockstudios.apps.hammer.common.fileio.ExternalFileIo
|
|||
import com.darkrockstudios.apps.hammer.common.getCacheDirectory
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_failure
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_success
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
|
@ -42,22 +44,30 @@ actual fun ExportDirectoryPicker(
|
|||
val options = confirmedOptions
|
||||
if (uri != null && options != null) {
|
||||
scope.launch(ioDispatcher) {
|
||||
val exportTempFile = getCacheDirectory()
|
||||
val tempFilePath = component.exportProject(exportTempFile, options)
|
||||
val tempFile = File(tempFilePath.path)
|
||||
val bytes = tempFile.readBytes()
|
||||
tempFile.delete()
|
||||
// A renderer failure must surface as a toast; an uncaught throw here takes down the app.
|
||||
try {
|
||||
val exportTempFile = getCacheDirectory()
|
||||
val tempFilePath = component.exportProject(exportTempFile, options)
|
||||
val tempFile = File(tempFilePath.path)
|
||||
val bytes = tempFile.readBytes()
|
||||
tempFile.delete()
|
||||
|
||||
val ok = externalFileIo.writeExternalFile(
|
||||
path = uri.toString(),
|
||||
content = bytes,
|
||||
)
|
||||
val toast = if (ok) {
|
||||
Res.string.project_home_action_export_toast_success
|
||||
} else {
|
||||
Res.string.project_home_action_export_toast_failure
|
||||
val ok = externalFileIo.writeExternalFile(
|
||||
path = uri.toString(),
|
||||
content = bytes,
|
||||
)
|
||||
val toast = if (ok) {
|
||||
Res.string.project_home_action_export_toast_success
|
||||
} else {
|
||||
Res.string.project_home_action_export_toast_failure
|
||||
}
|
||||
component.showToast(toast)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Throwable) {
|
||||
Napier.e("Story export failed", e)
|
||||
component.showToast(Res.string.project_home_action_export_toast_failure)
|
||||
}
|
||||
component.showToast(toast)
|
||||
}
|
||||
} else {
|
||||
component.endProjectExport()
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ import com.darkrockstudios.apps.hammer.Res
|
|||
import com.darkrockstudios.apps.hammer.common.components.projecthome.ProjectHome
|
||||
import com.darkrockstudios.apps.hammer.common.components.projecthome.fileExtension
|
||||
import com.darkrockstudios.apps.hammer.common.compose.rememberDefaultDispatcher
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_failure
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_success
|
||||
import io.github.aakira.napier.Napier
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.absolutePath
|
||||
import io.github.vinceglb.filekit.dialogs.openFileSaver
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -33,8 +36,16 @@ actual fun ExportDirectoryPicker(
|
|||
val file = FileKit.openFileSaver(suggestedName = baseName, defaultExtension = extension)
|
||||
if (file != null) {
|
||||
scope.launch(defaultDispatcher) {
|
||||
component.exportProjectToFile(file.absolutePath(), options)
|
||||
component.showToast(Res.string.project_home_action_export_toast_success)
|
||||
// A renderer failure must surface as a toast; an uncaught throw here takes down the app.
|
||||
try {
|
||||
component.exportProjectToFile(file.absolutePath(), options)
|
||||
component.showToast(Res.string.project_home_action_export_toast_success)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Throwable) {
|
||||
Napier.e("Story export failed", e)
|
||||
component.showToast(Res.string.project_home_action_export_toast_failure)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
component.endProjectExport()
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ import com.darkrockstudios.apps.hammer.Res
|
|||
import com.darkrockstudios.apps.hammer.common.components.projecthome.ProjectHome
|
||||
import com.darkrockstudios.apps.hammer.common.compose.rememberDefaultDispatcher
|
||||
import com.darkrockstudios.apps.hammer.common.data.ExportOptions
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_failure
|
||||
import com.darkrockstudios.apps.hammer.project_home_action_export_toast_success
|
||||
import io.github.aakira.napier.Napier
|
||||
import io.github.vinceglb.filekit.dialogs.compose.rememberDirectoryPickerLauncher
|
||||
import io.github.vinceglb.filekit.path
|
||||
import io.github.vinceglb.filekit.startAccessingSecurityScopedResource
|
||||
import io.github.vinceglb.filekit.stopAccessingSecurityScopedResource
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -37,9 +40,15 @@ actual fun ExportDirectoryPicker(
|
|||
// posix sink can't open — use .path for the bare filesystem path. UIDocumentPicker URLs are
|
||||
// security-scoped, so writes outside the sandbox require start/stopAccessingSecurityScopedResource.
|
||||
val accessGranted = directory.startAccessingSecurityScopedResource()
|
||||
// A renderer failure must surface as a toast; an uncaught throw here takes down the app.
|
||||
try {
|
||||
component.exportProject(directory.path, options)
|
||||
component.showToast(Res.string.project_home_action_export_toast_success)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Throwable) {
|
||||
Napier.e("Story export failed", e)
|
||||
component.showToast(Res.string.project_home_action_export_toast_failure)
|
||||
} finally {
|
||||
if (accessGranted) directory.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
|
|
|||
8
desktop/proguard-rules.pro
vendored
8
desktop/proguard-rules.pro
vendored
|
|
@ -91,6 +91,14 @@
|
|||
################################################################################
|
||||
-keep class * implements org.slf4j.spi.SLF4JServiceProvider { *; }
|
||||
|
||||
################################################################################
|
||||
# COMMONS-LOGGING (PDFBox, used by PDF export)
|
||||
# LogFactory resolves its implementation by name, so the shrinker sees no
|
||||
# reference to LogFactoryImpl and drops it. PDDocument's static init then dies.
|
||||
################################################################################
|
||||
-keep class org.apache.commons.logging.** { *; }
|
||||
-dontwarn org.apache.commons.logging.**
|
||||
|
||||
################################################################################
|
||||
# CLEANUP & WARNING SUPPRESSION
|
||||
################################################################################
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue