1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-08 22:26:51 +02:00

android native copy

This commit is contained in:
Laserlicht
2025-01-03 13:51:15 +01:00
parent 833f122ab3
commit 65256e8340
5 changed files with 133 additions and 10 deletions

View File

@@ -1,18 +1,27 @@
package eu.vcmi.vcmi.util;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.content.ContentResolver;
import android.provider.OpenableColumns;
import android.database.Cursor;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import org.libsdl.app.SDL;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.lang.Exception;
import eu.vcmi.vcmi.Const;
import eu.vcmi.vcmi.Storage;
/**
@@ -104,4 +113,50 @@ public class FileUtil
target.write(buffer, 0, read);
}
}
@SuppressWarnings(Const.JNI_METHOD_SUPPRESS)
private static void copyFileFromUri(String sourceFileUri, String destinationFile)
{
try
{
final Context ctx = SDL.getContext();
final InputStream inputStream = new FileInputStream(ctx.getContentResolver().openFileDescriptor(Uri.parse(sourceFileUri), "r").getFileDescriptor());
final OutputStream outputStream = new FileOutputStream(new File(destinationFile));
copyStream(inputStream, outputStream);
}
catch (IOException e)
{
Log.e("copyFileFromUri failed: ", e);
}
}
@SuppressWarnings(Const.JNI_METHOD_SUPPRESS)
private static String getFilenameFromUri(String sourceFileUri)
{
try
{
String fileName = "";
final Context ctx = SDL.getContext();
ContentResolver contentResolver = ctx.getContentResolver();
Cursor cursor = contentResolver.query(Uri.parse(sourceFileUri), null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (nameIndex != -1) {
fileName = cursor.getString(nameIndex);
}
cursor.close();
}
return fileName;
}
catch (Exception e)
{
Log.e("getFilenameFromUri failed: ", e);
return "";
}
}
}

View File

@@ -369,8 +369,19 @@ void FirstLaunchView::extractGogData()
QString tmpFileExe = tempDir.filePath("h3_gog.exe");
QString tmpFileBin = tempDir.filePath("h3_gog-1.bin");
#ifdef VCMI_ANDROID
auto copy = [](QString src, QString dst)
{
auto srcStr = QAndroidJniObject::fromString(src);
auto dstStr = QAndroidJniObject::fromString(dst);
QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "copyFileFromUri", "(Ljava/lang/String;Ljava/lang/String;)V", srcStr.object<jstring>(), dstStr.object<jstring>());
};
copy(fileExe, tmpFileExe);
copy(fileBin, tmpFileBin);
#else
QFile(fileExe).copy(tmpFileExe);
QFile(fileBin).copy(tmpFileBin);
#endif
logGlobal->info("Installing exe '%s' ('%s')", tmpFileExe.toStdString(), fileExe.toStdString());
logGlobal->info("Installing bin '%s' ('%s')", tmpFileBin.toStdString(), fileBin.toStdString());
@@ -414,9 +425,13 @@ void FirstLaunchView::extractGogData()
{
if(!errorText.isEmpty())
{
logGlobal->error("Gog installer extraction failure! Reason: %s", errorText.toStdString());
QMessageBox::critical(this, tr("Extracting error!"), errorText, QMessageBox::Ok, QMessageBox::Ok);
if(!hashError.isEmpty())
{
logGlobal->error("Hash error: %s", hashError.toStdString());
QMessageBox::critical(this, tr("Hash error!"), hashError, QMessageBox::Ok, QMessageBox::Ok);
}
}
else
QMessageBox::critical(this, tr("No Heroes III data!"), tr("Selected files do not contain Heroes III data!"), QMessageBox::Ok, QMessageBox::Ok);
@@ -641,4 +656,3 @@ void FirstLaunchView::on_pushButtonGithub_clicked()
{
QDesktopServices::openUrl(QUrl("https://github.com/vcmi/vcmi"));
}

View File

@@ -13,6 +13,11 @@
#include <QDir>
#ifdef VCMI_ANDROID
#include <QAndroidJniObject>
#include <QtAndroid>
#endif
#include "../lib/CConfigHandler.h"
#include "../lib/VCMIDirs.h"
#include "../lib/filesystem/Filesystem.h"
@@ -264,11 +269,25 @@ void MainWindow::dropEvent(QDropEvent* event)
void MainWindow::manualInstallFile(QString filePath)
{
if(filePath.endsWith(".zip", Qt::CaseInsensitive) || filePath.endsWith(".exe", Qt::CaseInsensitive))
#ifdef VCMI_ANDROID
QString realFilePath{};
if(filePath.contains("content://", Qt::CaseInsensitive))
{
auto path = QAndroidJniObject::fromString(filePath);
realFilePath = QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "getFilenameFromUri", "(Ljava/lang/String;)Ljava/lang/String;", path.object<jstring>()).toString();
}
else
realFilePath = filePath;
#else
QString realFilePath = filePath;
#endif
if(realFilePath.endsWith(".zip", Qt::CaseInsensitive) || realFilePath.endsWith(".exe", Qt::CaseInsensitive))
switchToModsTab();
QString fileName = QFileInfo{filePath}.fileName();
if(filePath.endsWith(".zip", Qt::CaseInsensitive))
if(realFilePath.endsWith(".zip", Qt::CaseInsensitive))
{
QString filenameClean = fileName.toLower()
// mod name currently comes from zip file -> remove suffixes from github zip download
@@ -278,7 +297,7 @@ void MainWindow::manualInstallFile(QString filePath)
getModView()->downloadFile(filenameClean, QUrl::fromLocalFile(filePath), "mods");
}
else if(filePath.endsWith(".json", Qt::CaseInsensitive))
else if(realFilePath.endsWith(".json", Qt::CaseInsensitive))
{
QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string()));
QStringList configFile = configDir.entryList({fileName}, QDir::Filter::Files); // case insensitive check

View File

@@ -16,6 +16,11 @@
#include "../innoextract.h"
#ifdef VCMI_ANDROID
#include <QAndroidJniObject>
#include <QtAndroid>
#endif
ChroniclesExtractor::ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb) :
parent(p), cb(cb)
{
@@ -72,10 +77,15 @@ bool ChroniclesExtractor::extractGogInstaller(QString file)
if(!errorText.isEmpty())
{
logGlobal->error("Gog chronicles installer extraction failure! Reason: %s", errorText.toStdString());
QString hashError = Innoextract::getHashError(file, {}, {}, {});
QMessageBox::critical(parent, tr("Extracting error!"), errorText);
if(!hashError.isEmpty())
{
logGlobal->error("Hash error: %s", hashError.toStdString());
QMessageBox::critical(parent, tr("Hash error!"), hashError, QMessageBox::Ok, QMessageBox::Ok);
}
return false;
}
@@ -226,14 +236,22 @@ void ChroniclesExtractor::installChronicles(QStringList exe)
if(!createTempDir())
continue;
logGlobal->info("Copying offline installer");
// FIXME: this is required at the moment for Android (and possibly iOS)
// Incoming file names are in content URI form, e.g. content://media/internal/chronicles.exe
// Qt can handle those like it does regular files
// however, innoextract fails to open such files
// so make a copy in directory to which vcmi always has full access and operate on it
QString filepath = tempDir.filePath("chr.exe");
logGlobal->info("Copying offline installer from '%s' to '%s'", f.toStdString(), filepath.toStdString());
#ifdef VCMI_ANDROID
{
auto srcStr = QAndroidJniObject::fromString(f);
auto dstStr = QAndroidJniObject::fromString(filepath);
QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "copyFileFromUri", "(Ljava/lang/String;Ljava/lang/String;)V", srcStr.object<jstring>(), dstStr.object<jstring>());
};
#else
QFile(f).copy(filepath);
#endif
QFile file(filepath);
logGlobal->info("Extracting offline installer");

View File

@@ -17,6 +17,11 @@
#include <QCryptographicHash>
#include <QRegularExpression>
#ifdef VCMI_ANDROID
#include <QAndroidJniObject>
#include <QtAndroid>
#endif
#include "modstatemodel.h"
#include "modstateitemmodel_moc.h"
#include "modstatecontroller.h"
@@ -739,13 +744,25 @@ void CModListView::installFiles(QStringList files)
// TODO: some better way to separate zip's with mods and downloaded repository files
for(QString filename : files)
{
if(filename.endsWith(".zip", Qt::CaseInsensitive))
#ifdef VCMI_ANDROID
QString realFilename{};
if(filename.contains("content://", Qt::CaseInsensitive))
{
auto path = QAndroidJniObject::fromString(filename);
realFilename = QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "getFilenameFromUri", "(Ljava/lang/String;)Ljava/lang/String;", path.object<jstring>()).toString();
}
else
realFilename = filename;
#else
QString realFilename = filename;
#endif
if(realFilename.endsWith(".zip", Qt::CaseInsensitive))
mods.push_back(filename);
else if(filename.endsWith(".h3m", Qt::CaseInsensitive) || filename.endsWith(".h3c", Qt::CaseInsensitive) || filename.endsWith(".vmap", Qt::CaseInsensitive) || filename.endsWith(".vcmp", Qt::CaseInsensitive))
else if(realFilename.endsWith(".h3m", Qt::CaseInsensitive) || realFilename.endsWith(".h3c", Qt::CaseInsensitive) || realFilename.endsWith(".vmap", Qt::CaseInsensitive) || realFilename.endsWith(".vcmp", Qt::CaseInsensitive))
maps.push_back(filename);
if(filename.endsWith(".exe", Qt::CaseInsensitive))
if(realFilename.endsWith(".exe", Qt::CaseInsensitive))
exe.push_back(filename);
else if(filename.endsWith(".json", Qt::CaseInsensitive))
else if(realFilename.endsWith(".json", Qt::CaseInsensitive))
{
//download and merge additional files
JsonNode repoData = JsonUtils::jsonFromFile(filename);
@@ -773,7 +790,7 @@ void CModListView::installFiles(QStringList files)
JsonUtils::merge(accumulatedRepositoryData[modNameLower], repoData);
}
}
else if(filename.endsWith(".png", Qt::CaseInsensitive))
else if(realFilename.endsWith(".png", Qt::CaseInsensitive))
images.push_back(filename);
}