From 327c8a805e508303c245020123ec3ecbfd2c898b Mon Sep 17 00:00:00 2001 From: Rob Samblanet Date: Tue, 23 Oct 2018 12:52:06 -0500 Subject: android: Properly indicate successful Qt install This patch ensures that installed files are written to physical disk before the 'cache.version' file is written. QtLoader.java uses the 'cache.version' file written during self-installation to indicate whether re-installation is necessary. The 'cache.version' file, however, was being written at the start of installation, so its existence merely indicated that the installation was attempted. In the case of power loss during installation, the existence of 'cache.version' would prevent retrying installation on the next launch, so the bad installation was irrecoverable. [ChangeLog][Android] Fixed an issue where an application installation would be irrecoverably broken if power loss or a crash occurred during its first initialization run. Fixes: QTBUG-71523 Change-Id: If771b223a0a709a994c766eea5a4ba14ae95201e Reviewed-by: Eskil Abrahamsen Blomfeldt --- .../qtproject/qt5/android/bindings/QtLoader.java | 100 +++++++++++++++------ 1 file changed, 75 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtLoader.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtLoader.java index 9d5578ed6d..d3b0600b2f 100644 --- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtLoader.java +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtLoader.java @@ -159,6 +159,9 @@ public abstract class QtLoader { protected ComponentInfo m_contextInfo; private Class m_delegateClass; + private static ArrayList m_fileOutputStreams = new ArrayList(); + // List of open file streams associated with files copied during installation. + QtLoader(ContextWrapper context, Class clazz) { m_context = context; m_delegateClass = clazz; @@ -357,7 +360,7 @@ public abstract class QtLoader { AssetManager assetsManager = m_context.getAssets(); InputStream inputStream = null; - OutputStream outputStream = null; + FileOutputStream outputStream = null; try { inputStream = assetsManager.open(source); outputStream = new FileOutputStream(destinationFile); @@ -369,8 +372,12 @@ public abstract class QtLoader { inputStream.close(); if (outputStream != null) - outputStream.close(); + // Ensure that the buffered data is flushed to the OS for writing. + outputStream.flush(); } + // Mark the output stream as still needing to be written to physical disk. + // The output stream will be closed after this sync completes. + m_fileOutputStreams.add(outputStream); } private static void createBundledBinary(String source, String destination) @@ -388,7 +395,7 @@ public abstract class QtLoader { destinationFile.createNewFile(); InputStream inputStream = null; - OutputStream outputStream = null; + FileOutputStream outputStream = null; try { inputStream = new FileInputStream(source); outputStream = new FileOutputStream(destinationFile); @@ -400,8 +407,12 @@ public abstract class QtLoader { inputStream.close(); if (outputStream != null) - outputStream.close(); + // Ensure that the buffered data is flushed to the OS for writing. + outputStream.flush(); } + // Mark the output stream as still needing to be written to physical disk. + // The output stream will be closed after this sync completes. + m_fileOutputStreams.add(outputStream); } private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) @@ -449,27 +460,6 @@ public abstract class QtLoader { if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) return; - { - File versionFile = new File(pluginsPrefix + "cache.version"); - - File parentDirectory = versionFile.getParentFile(); - if (!parentDirectory.exists()) - parentDirectory.mkdirs(); - - versionFile.createNewFile(); - - DataOutputStream outputStream = null; - try { - outputStream = new DataOutputStream(new FileOutputStream(versionFile)); - outputStream.writeLong(packageVersion); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (outputStream != null) - outputStream.close(); - } - } - { // why can't we load the plugins directly from libs ?!?! String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; @@ -499,6 +489,66 @@ public abstract class QtLoader { } } + + // The Java compiler must be assured that variables belonging to this parent thread will not + // go out of scope during the runtime of the spawned thread (since in general spawned + // threads can outlive their parent threads). Copy variables and declare as 'final' before + // passing into the spawned thread. + final String pluginsPrefixFinal = pluginsPrefix; + final long packageVersionFinal = packageVersion; + + // Spawn a worker thread to write all installed files to physical disk and indicate + // successful installation by creating the 'cache.version' file. + new Thread(new Runnable() { + @Override + public void run() { + try { + finalizeInstallation(pluginsPrefixFinal, packageVersionFinal); + } catch (Exception e) { + Log.e(QtApplication.QtTAG, e.getMessage()); + e.printStackTrace(); + return; + } + } + }).start(); + } + + private void finalizeInstallation(String pluginsPrefix, long packageVersion) + throws IOException + { + { + // Write all installed files to physical disk and close each output stream + for (FileOutputStream fileOutputStream : m_fileOutputStreams) { + fileOutputStream.getFD().sync(); + fileOutputStream.close(); + } + + m_fileOutputStreams.clear(); + } + + { + // Create 'cache.version' file + + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + versionFile.createNewFile(); + + DataOutputStream outputStream = null; + try { + outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (outputStream != null) + outputStream.close(); + } + } + } private void deleteRecursively(File directory) -- cgit v1.2.3