Android: handle rename() operation with content uris
Allow renaming content uris if the destination is provided as a direct fileName (i.e. not full content scheme path), and if the destination has the same trailing path (or parent) which means a rename in the same folder structure. Pick-to: 6.5 6.4 6.2 Task-number: QTBUG-98974 Change-Id: Ibc4973366807dd5284c19912ab04ff90f2a573cb Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
This commit is contained in:
parent
30efb24d45
commit
c1fa5d602c
@ -112,6 +112,15 @@ bool AndroidContentFileEngine::remove()
|
||||
return m_documentFile->remove();
|
||||
}
|
||||
|
||||
bool AndroidContentFileEngine::rename(const QString &newName)
|
||||
{
|
||||
if (m_documentFile->rename(newName)) {
|
||||
m_initialFile = m_documentFile->uri().toString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AndroidContentFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
|
||||
std::optional<QFileDevice::Permissions> permissions) const
|
||||
{
|
||||
@ -433,6 +442,7 @@ const QLatin1String COLUMN_SIZE("_size");
|
||||
|
||||
constexpr int FLAG_DIR_SUPPORTS_CREATE = 0x00000008;
|
||||
constexpr int FLAG_SUPPORTS_DELETE = 0x00000004;
|
||||
constexpr int FLAG_SUPPORTS_RENAME = 0x00000040;
|
||||
constexpr int FLAG_SUPPORTS_WRITE = 0x00000002;
|
||||
constexpr int FLAG_VIRTUAL_DOCUMENT = 0x00000200;
|
||||
|
||||
@ -516,6 +526,19 @@ bool deleteDocument(const QJniObject &documentUri)
|
||||
documentUri.object());
|
||||
}
|
||||
|
||||
QJniObject renameDocument(const QJniObject &documentUri, const QString &displayName)
|
||||
{
|
||||
const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt();
|
||||
if (!(flags & Document::FLAG_SUPPORTS_RENAME))
|
||||
return {};
|
||||
|
||||
return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract",
|
||||
"renameDocument",
|
||||
"(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;",
|
||||
contentResolverInstance().object(),
|
||||
documentUri.object(),
|
||||
QJniObject::fromString(displayName).object());
|
||||
}
|
||||
} // End DocumentsContract namespace
|
||||
|
||||
// Start of DocumentFile
|
||||
@ -735,4 +758,37 @@ std::vector<DocumentFilePtr> DocumentFile::listFiles()
|
||||
return res;
|
||||
}
|
||||
|
||||
bool DocumentFile::rename(const QString &newName)
|
||||
{
|
||||
QJniObject uri;
|
||||
if (newName.startsWith("content://"_L1)) {
|
||||
auto lastSeparatorIndex = [](const QString &file) {
|
||||
int posDecoded = file.lastIndexOf("/");
|
||||
int posEncoded = file.lastIndexOf(QUrl::toPercentEncoding("/"));
|
||||
return posEncoded > posDecoded ? posEncoded : posDecoded;
|
||||
};
|
||||
|
||||
// first try to see if the new file is under the same tree and thus used rename only
|
||||
const QString parent = m_uri.toString().left(lastSeparatorIndex(m_uri.toString()));
|
||||
if (newName.contains(parent)) {
|
||||
QString displayName = newName.mid(lastSeparatorIndex(newName));
|
||||
if (displayName.startsWith('/'))
|
||||
displayName.remove(0, 1);
|
||||
else if (displayName.startsWith(QUrl::toPercentEncoding("/")))
|
||||
displayName.remove(0, 3);
|
||||
|
||||
uri = renameDocument(m_uri, displayName);
|
||||
}
|
||||
} else {
|
||||
uri = renameDocument(m_uri, newName);
|
||||
}
|
||||
|
||||
if (uri.isValid()) {
|
||||
m_uri = uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// End of DocumentFile
|
||||
|
@ -19,6 +19,7 @@ public:
|
||||
bool close() override;
|
||||
qint64 size() const override;
|
||||
bool remove() override;
|
||||
bool rename(const QString &newName) override;
|
||||
bool mkdir(const QString &dirName, bool createParentDirectories,
|
||||
std::optional<QFile::Permissions> permissions = std::nullopt) const override;
|
||||
bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
|
||||
@ -91,6 +92,7 @@ public:
|
||||
bool remove();
|
||||
bool exists() const;
|
||||
std::vector<DocumentFilePtr> listFiles();
|
||||
bool rename(const QString &newName);
|
||||
|
||||
protected:
|
||||
DocumentFile(const QJniObject &uri, const std::shared_ptr<DocumentFile> &parent);
|
||||
|
@ -195,6 +195,30 @@ void tst_ContentUris::fileOperations()
|
||||
QFile file(url);
|
||||
QVERIFY(file.exists());
|
||||
|
||||
// Rename
|
||||
const QString renamedFileName = "renamed_new_file.txt";
|
||||
QVERIFY(file.rename(renamedFileName));
|
||||
const auto renamedUrl = url.replace(fileName, renamedFileName);
|
||||
QVERIFY(file.fileName() == renamedUrl);
|
||||
|
||||
// NOTE: The uri doesn't seem to stay usable after a rename and it needs to get
|
||||
// permission again via the SAF picker.
|
||||
showInstructionsDialog("Choose the file that was renamed");
|
||||
QFileDialog::getOpenFileName(nullptr, tr("Open File"));
|
||||
QVERIFY(file.exists());
|
||||
|
||||
// rename now with full content uri
|
||||
const auto secondRenamedUrl = url.replace(renamedFileName, "second_nenamed_file.txt");
|
||||
QVERIFY(file.rename(secondRenamedUrl));
|
||||
QVERIFY(file.fileName() == secondRenamedUrl);
|
||||
|
||||
// NOTE: The uri doesn't seem to stay usable after a rename and it needs to get
|
||||
// permission again via the SAF picker.
|
||||
showInstructionsDialog("Choose the file that was renamed");
|
||||
QFileDialog::getOpenFileName(nullptr, tr("Open File"));
|
||||
QVERIFY(file.exists());
|
||||
|
||||
// Remove
|
||||
QVERIFY(file.remove());
|
||||
QVERIFY(!file.exists());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user