浏览代码

android native copy

Laserlicht 9 月之前
父节点
当前提交
65256e8340

+ 55 - 0
android/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java

@@ -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 "";
+        }
+    }
 }

+ 15 - 1
launcher/firstLaunch/firstlaunch_moc.cpp

@@ -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"));
 }
-

+ 22 - 3
launcher/mainwindow_moc.cpp

@@ -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

+ 19 - 1
launcher/modManager/chroniclesextractor.cpp

@@ -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");

+ 22 - 5
launcher/modManager/cmodlistview_moc.cpp

@@ -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);
 	}