diff --git a/launcher/helper.cpp b/launcher/helper.cpp index 22bedbc79..bc5cbf2f5 100644 --- a/launcher/helper.cpp +++ b/launcher/helper.cpp @@ -42,6 +42,19 @@ static QScrollerProperties generateScrollerProperties() } #endif +#ifdef VCMI_ANDROID +static QString safeEncode(QString uri) +{ + // %-encode unencoded parts of string. + // This is needed because Qt returns a mixed content url with %-encoded and unencoded parts. On Android >= 13 this causes problems reading these files, when using spaces and unicode characters in folder or filename. + // Only these should be encoded (other typically %-encoded chars should not be encoded because this leads to errors). + // Related, but seems not completly fixed (at least in our setup): https://bugreports.qt.io/browse/QTBUG-114435 + if (!uri.startsWith("content://", Qt::CaseInsensitive)) + return uri; + return QString::fromUtf8(QUrl::toPercentEncoding(uri, "!#$&'()*+,/:;=?@[]<>{}\"`^~%")); +} +#endif + namespace Helper { void loadSettings() @@ -77,35 +90,28 @@ QString getRealPath(QString path) #ifdef VCMI_ANDROID if(path.contains("content://", Qt::CaseInsensitive)) { - auto str = QAndroidJniObject::fromString(path); + auto str = QAndroidJniObject::fromString(safeEncode(path)); return QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "getFilenameFromUri", "(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;", str.object(), QtAndroid::androidContext().object()).toString(); } - else - return path; + return path; #else return path; #endif } -void performNativeCopy(QString src, QString dst) +bool performNativeCopy(QString src, QString dst) { #ifdef VCMI_ANDROID - // %-encode unencoded parts of string. - // This is needed because Qt returns a mixed content url with %-encoded and unencoded parts. On Android >= 13 this causes problems reading these files, when using spaces and unicode characters in folder or filename. - // Only these should be encoded (other typically %-encoded chars should not be encoded because this leads to errors). - // Related, but seems not completly fixed (at least in our setup): https://bugreports.qt.io/browse/QTBUG-114435 - auto safeEncode = [&](QString uri) -> QString - { - if(!uri.startsWith("content://", Qt::CaseInsensitive)) - return uri; - return QString::fromUtf8(QUrl::toPercentEncoding(uri, "!#$&'()*+,/:;=?@[]<>{}\"`^~%")); - }; - auto srcStr = QAndroidJniObject::fromString(safeEncode(src)); auto dstStr = QAndroidJniObject::fromString(safeEncode(dst)); QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/util/FileUtil", "copyFileFromUri", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/Context;)V", srcStr.object(), dstStr.object(), QtAndroid::androidContext().object()); + + if(QFileInfo(dst).exists()) + return true; + else + return false; #else - QFile::copy(src, dst); + return QFile::copy(src, dst); #endif } diff --git a/launcher/helper.h b/launcher/helper.h index 7b8dc16bf..bb515d08e 100644 --- a/launcher/helper.h +++ b/launcher/helper.h @@ -20,7 +20,7 @@ void loadSettings(); void reLoadSettings(); void enableScrollBySwiping(QObject * scrollTarget); QString getRealPath(QString path); -void performNativeCopy(QString src, QString dst); +bool performNativeCopy(QString src, QString dst); void revealDirectoryInFileBrowser(QString path); MainWindow * getMainWindow(); void keepScreenOn(bool isEnabled); diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 71b2bb0a7..f07c0982f 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -295,7 +295,7 @@ void MainWindow::manualInstallFile(QString filePath) { const auto configFilePath = configDir.filePath(configFile[0]); QFile::remove(configFilePath); - QFile::copy(filePath, configFilePath); + Helper::performNativeCopy(filePath, configFilePath); Helper::reLoadSettings(); } diff --git a/launcher/modManager/chroniclesextractor.cpp b/launcher/modManager/chroniclesextractor.cpp index f2a55effa..39d078ee7 100644 --- a/launcher/modManager/chroniclesextractor.cpp +++ b/launcher/modManager/chroniclesextractor.cpp @@ -133,7 +133,7 @@ void ChroniclesExtractor::createBaseMod() const { QDir().mkpath(pathToQString(destFolder)); QFile::remove(destFile); - QFile::copy(file, destFile); + Helper::performNativeCopy(file, destFile); } } } @@ -237,7 +237,7 @@ void ChroniclesExtractor::extractFiles(int no) const continue; auto srcName = vstd::reverseMap(mapping).at(no); auto dstName = (no == 7 || no == 8) ? srcName : "Intro"; - QFile::copy(tmpDir.filePath(QString::fromStdString(srcName + ending)), outDirVideo.filePath(QString::fromStdString(dstName + ending))); + Helper::performNativeCopy(tmpDir.filePath(QString::fromStdString(srcName + ending)), outDirVideo.filePath(QString::fromStdString(dstName + ending))); } } diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index f111a82f8..d3c1ab6a8 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -1212,7 +1212,7 @@ void CModListView::installMaps(QStringList maps) QFile::remove(destFile); } - if (QFile::copy(map, destFile)) + if (Helper::performNativeCopy(map, destFile)) successCount++; else {