Created
September 2, 2022 07:28
-
-
Save sh-zam/3d1683c3c7fbae32de62c9ca07f5dd7a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
** | |
** Copyright (C) 2019 Volker Krause <vkrause@kde.org> | |
** Contact: https://www.qt.io/licensing/ | |
** | |
** This file is part of the plugins of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** Commercial License Usage | |
** Licensees holding valid commercial Qt licenses may use this file in | |
** accordance with the commercial license agreement provided with the | |
** Software or, alternatively, in accordance with the terms contained in | |
** a written agreement between you and The Qt Company. For licensing terms | |
** and conditions see https://www.qt.io/terms-conditions. For further | |
** information use the contact form at https://www.qt.io/contact-us. | |
** | |
** GNU Lesser General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU Lesser | |
** General Public License version 3 as published by the Free Software | |
** Foundation and appearing in the file LICENSE.LGPL3 included in the | |
** packaging of this file. Please review the following information to | |
** ensure the GNU Lesser General Public License version 3 requirements | |
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU | |
** General Public License version 2.0 or (at your option) the GNU General | |
** Public license version 3 or any later version approved by the KDE Free | |
** Qt Foundation. The licenses are as published by the Free Software | |
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | |
** included in the packaging of this file. Please review the following | |
** information to ensure the GNU General Public License requirements will | |
** be met: https://www.gnu.org/licenses/gpl-2.0.html and | |
** https://www.gnu.org/licenses/gpl-3.0.html. | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "androidcontentfileengine.h" | |
#include <private/qjni_p.h> | |
#include <private/qjnihelpers_p.h> | |
#include <QDebug> | |
AndroidContentFileEngine::AndroidContentFileEngine(const QString &f) | |
: m_fd(-1) | |
, m_file(f) | |
, m_resolvedName(QString()) | |
, m_safFileManager(QJNIObjectPrivate::callStaticObjectMethod( | |
"org/qtproject/qt5/android/SAFFileManager", "instance", | |
"()Lorg/qtproject/qt5/android/SAFFileManager;")) | |
, m_safError(m_safFileManager.getObjectField("mError", | |
"Lorg/qtproject/qt5/android/FileError;")) | |
{ | |
m_resolvedName = getResolvedFileName(f); | |
setFileName(f); | |
} | |
bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode) | |
{ | |
QString openModeStr; | |
ProcessOpenModeResult res = processOpenModeFlags(openMode); | |
if (!res.ok) { | |
setError(QFileDevice::OpenError, res.error); | |
return false; | |
} | |
// if Truncate flag is set we have to set 'r' as well, else we get inconsistent results. | |
if ((res.openMode & QFileDevice::ReadOnly) || (res.openMode & QFileDevice::Truncate)) { | |
openModeStr += QLatin1Char('r'); | |
} | |
if (res.openMode & QFileDevice::WriteOnly) { | |
openModeStr += QLatin1Char('w'); | |
} | |
if (res.openMode & QFileDevice::Truncate) { | |
openModeStr += QLatin1Char('t'); | |
} else if (res.openMode & QFileDevice::Append) { | |
qWarning("Android doesn't support 'a' mode when accessing a ContentProvider"); | |
openModeStr += QLatin1Char('a'); | |
} | |
const auto fd = m_safFileManager.callMethod<jint>( | |
"openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I", | |
QJNIObjectPrivate::fromString(m_file).object(), | |
QJNIObjectPrivate::fromString(openModeStr).object()); | |
if (fd < 0) { | |
setErrorFromSAF(); | |
return false; | |
} | |
setFileDescriptor(fd); | |
return QFSFileEngine::open(openMode, m_fd, QFile::DontCloseHandle); | |
} | |
bool AndroidContentFileEngine::close() | |
{ | |
setErrorFromSAF(); | |
return m_safFileManager.callMethod<jboolean>("closeFileDescriptor", "(I)Z", m_fd); | |
} | |
QJNIObjectPrivate toJavaUri(const QString &stringUri) | |
{ | |
const auto uri = QJNIObjectPrivate::callStaticObjectMethod( | |
"android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", | |
QJNIObjectPrivate::fromString(stringUri).object()); | |
if (!uri.isValid()) { | |
qWarning("Invalid Uri returned"); | |
} | |
return uri; | |
} | |
bool AndroidContentFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const | |
{ | |
return m_safFileManager.callMethod<jboolean>( | |
"mkdir", "(Ljava/lang/String;Z)Z", | |
QJNIObjectPrivate::fromString(dirName).object(), createParentDirectories); | |
} | |
bool AndroidContentFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const | |
{ | |
if (recurseParentDirectories) { | |
qWarning() << "rmpath(): Unsupported"; | |
} | |
return m_safFileManager.callMethod<jboolean>( | |
"delete", "(Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(dirName).object()); | |
} | |
qint64 AndroidContentFileEngine::size() const | |
{ | |
const jlong size = m_safFileManager.callMethod<jlong>( | |
"getSize", "(Ljava/lang/String;)J", | |
QJNIObjectPrivate::fromString(m_file).object()); | |
return (qint64)size; | |
} | |
AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlags type) const | |
{ | |
const FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag); | |
FileFlags flags; | |
const bool exists = | |
m_safFileManager.callMethod<jboolean>("exists", "(Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(m_file).object()); | |
if (!exists) | |
return flags; | |
flags = commonFlags; | |
const bool canWrite = | |
m_safFileManager.callMethod<jboolean>("canWrite", "(Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(m_file).object()); | |
if (canWrite) { | |
flags |= (WriteOwnerPerm|WriteUserPerm|WriteGroupPerm|WriteOtherPerm); | |
} | |
const bool isDir = m_safFileManager.callMethod<jboolean>( | |
"isDir", "(Ljava/lang/String;)Z", QJNIObjectPrivate::fromString(m_file).object()); | |
if (isDir) { | |
flags = DirectoryType | flags; | |
} else { | |
flags = FileType | flags; | |
} | |
return type & flags; | |
} | |
QString AndroidContentFileEngine::fileName(FileName f) const | |
{ | |
switch (f) { | |
case DefaultName: { | |
// the file isn't created here, so the resolved filename is empty | |
if (m_resolvedName.isEmpty()) { | |
const int pos = m_file.lastIndexOf(QChar(QLatin1Char('/'))); | |
return m_file.mid(pos + 1); | |
} else { | |
return m_resolvedName; | |
} | |
} | |
case PathName: | |
case AbsoluteName: | |
case AbsolutePathName: | |
case CanonicalName: | |
return m_file; | |
case CanonicalPathName: { | |
const bool isTree = m_safFileManager.callMethod<jboolean>( | |
"isTreeUri", "(Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(m_file).object()); | |
if (isTree) { | |
const int pos = m_file.lastIndexOf(QChar(QLatin1Char('/'))); | |
return m_file.left(pos); | |
} | |
return m_file; | |
} | |
case BaseName: { | |
const int pos = m_resolvedName.lastIndexOf(QChar(QLatin1Char('/'))); | |
return m_resolvedName.mid(pos); | |
} | |
default: | |
return QString(); | |
} | |
} | |
bool AndroidContentFileEngine::isRelativePath() const | |
{ | |
if (m_file.startsWith(QLatin1String("content://"))) { | |
return false; | |
} else { | |
// well then it's just a Unix path | |
return m_file.length() ? m_file.at(0) != QLatin1Char('/') : true; | |
} | |
} | |
bool AndroidContentFileEngine::rename(const QString &newName) | |
{ | |
auto renameSaf = [this](QString newName) -> bool { | |
return m_safFileManager.callMethod<jboolean>( | |
"rename", "(Ljava/lang/String;Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(m_file).object(), | |
QJNIObjectPrivate::fromString(newName).object()); | |
}; | |
// if the file doesn't have scheme it means the newName is only the fileName part | |
if (!newName.startsWith("content://")) { | |
return renameSaf(newName); | |
} | |
auto getPos = [](QString file) { | |
int posDecoded = file.lastIndexOf(QLatin1String("/")); | |
int posEncoded = file.lastIndexOf(QLatin1String("%2F")); | |
return posEncoded > posDecoded ? posEncoded : posDecoded; | |
}; | |
const QString parent = m_file.left(getPos(m_file)); | |
if (newName.contains(parent)) { | |
const int pos = getPos(newName); | |
const QString displayName = newName.mid(pos + 1); | |
return renameSaf(displayName); | |
} | |
m_resolvedName = getResolvedFileName(m_file); | |
return false; | |
} | |
bool AndroidContentFileEngine::remove() | |
{ | |
return m_safFileManager.callMethod<jboolean>( | |
"delete", "(Ljava/lang/String;)Z", | |
QJNIObjectPrivate::fromString(m_file).object()); | |
} | |
QString AndroidContentFileEngine::getResolvedFileName(const QString &path) const | |
{ | |
QJNIObjectPrivate resolvedName = m_safFileManager.callObjectMethod( | |
"getFileName", "(Ljava/lang/String;)Ljava/lang/String;", | |
QJNIObjectPrivate::fromString(path).object()); | |
if (resolvedName.isValid()) { | |
return resolvedName.toString(); | |
} | |
return QString(); | |
} | |
QAbstractFileEngine::Iterator * | |
AndroidContentFileEngine::beginEntryList(QDir::Filters filters, | |
const QStringList &filterNames) | |
{ | |
return new AndroidContentFileEngineIterator(m_safFileManager, filters, filterNames); | |
} | |
QAbstractFileEngine::Iterator *AndroidContentFileEngine::endEntryList() | |
{ | |
return nullptr; | |
} | |
void AndroidContentFileEngine::setFileDescriptor(const int fd) { m_fd = fd; } | |
void AndroidContentFileEngine::setErrorFromSAF() | |
{ | |
auto error = m_safError.callMethod<jint>("getError"); | |
auto errorString = | |
m_safError.callObjectMethod("getErrorString", "()Ljava/lang/String;"); | |
if (errorString.isValid()) { | |
setError(static_cast<QFileDevice::FileError>(error), errorString.toString()); | |
} | |
} | |
AndroidContentFileEngineHandler::AndroidContentFileEngineHandler() = default; | |
AndroidContentFileEngineHandler::~AndroidContentFileEngineHandler() = default; | |
QAbstractFileEngine * | |
AndroidContentFileEngineHandler::create(const QString &fileName) const | |
{ | |
if (!fileName.startsWith(QLatin1String("content"))) { | |
return nullptr; | |
} | |
return new AndroidContentFileEngine(fileName); | |
} | |
AndroidContentFileEngineIterator::AndroidContentFileEngineIterator( | |
QJNIObjectPrivate safFileManager, QDir::Filters filters, | |
const QStringList &filterNames) | |
: QAbstractFileEngineIterator(filters, filterNames) | |
, m_safFileManager(safFileManager) | |
{ | |
} | |
AndroidContentFileEngineIterator::~AndroidContentFileEngineIterator() | |
{ | |
m_safFileManager.callMethod<void>("resetListCache"); | |
} | |
QString AndroidContentFileEngineIterator::next() | |
{ | |
if (!hasNext()) { | |
return QString(); | |
} | |
m_index++; | |
// just like it is in QFSFileEngineIterator | |
return currentFilePath(); | |
} | |
bool AndroidContentFileEngineIterator::hasNext() const | |
{ | |
if (m_index == -2) { | |
fetchEntries(); | |
m_index++; | |
} | |
return m_index < m_entries.size() && m_entries.size() > 0; | |
} | |
QString AndroidContentFileEngineIterator::currentFileName() const | |
{ | |
if (!hasNext()) { | |
return QString(); | |
} | |
return m_entries.at(m_index); | |
} | |
void AndroidContentFileEngineIterator::fetchEntries() const | |
{ | |
QJNIObjectPrivate fileNames = m_safFileManager.callObjectMethod( | |
"listFileNames", "(Ljava/lang/String;)[Ljava/lang/String;", | |
QJNIObjectPrivate::fromString(path()).object()); | |
if (!fileNames.isValid()) { | |
return; | |
} | |
QJNIEnvironmentPrivate env; | |
const jsize length = env->GetArrayLength(static_cast<jarray>(fileNames.object())); | |
for (int i = 0; i < length; ++i) { | |
QJNIObjectPrivate elem( | |
env->GetObjectArrayElement(static_cast<jobjectArray>(fileNames.object()), i)); | |
m_entries << elem.toString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment