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 <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Rob Samblanet 2018-10-23 12:52:06 -05:00 committed by Corey Pendleton
parent 093a1a6232
commit 327c8a805e

View File

@ -159,6 +159,9 @@ public abstract class QtLoader {
protected ComponentInfo m_contextInfo; protected ComponentInfo m_contextInfo;
private Class<?> m_delegateClass; private Class<?> m_delegateClass;
private static ArrayList<FileOutputStream> m_fileOutputStreams = new ArrayList<FileOutputStream>();
// List of open file streams associated with files copied during installation.
QtLoader(ContextWrapper context, Class<?> clazz) { QtLoader(ContextWrapper context, Class<?> clazz) {
m_context = context; m_context = context;
m_delegateClass = clazz; m_delegateClass = clazz;
@ -357,7 +360,7 @@ public abstract class QtLoader {
AssetManager assetsManager = m_context.getAssets(); AssetManager assetsManager = m_context.getAssets();
InputStream inputStream = null; InputStream inputStream = null;
OutputStream outputStream = null; FileOutputStream outputStream = null;
try { try {
inputStream = assetsManager.open(source); inputStream = assetsManager.open(source);
outputStream = new FileOutputStream(destinationFile); outputStream = new FileOutputStream(destinationFile);
@ -369,8 +372,12 @@ public abstract class QtLoader {
inputStream.close(); inputStream.close();
if (outputStream != null) 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) private static void createBundledBinary(String source, String destination)
@ -388,7 +395,7 @@ public abstract class QtLoader {
destinationFile.createNewFile(); destinationFile.createNewFile();
InputStream inputStream = null; InputStream inputStream = null;
OutputStream outputStream = null; FileOutputStream outputStream = null;
try { try {
inputStream = new FileInputStream(source); inputStream = new FileInputStream(source);
outputStream = new FileOutputStream(destinationFile); outputStream = new FileOutputStream(destinationFile);
@ -400,8 +407,12 @@ public abstract class QtLoader {
inputStream.close(); inputStream.close();
if (outputStream != null) 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) private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion)
@ -449,27 +460,6 @@ public abstract class QtLoader {
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion))
return; 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 ?!?! // why can't we load the plugins directly from libs ?!?!
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; 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) private void deleteRecursively(File directory)