c1fa5d602c
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>
228 lines
7.1 KiB
C++
228 lines
7.1 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include <QTest>
|
|
#include <QDirIterator>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
class tst_ContentUris: public QObject
|
|
{
|
|
Q_OBJECT
|
|
private slots:
|
|
void dirFacilities();
|
|
void readWriteFile();
|
|
void readWriteNonExistingFile_data();
|
|
void readWriteNonExistingFile();
|
|
void createFileFromDirUrl_data();
|
|
void createFileFromDirUrl();
|
|
void fileOperations();
|
|
};
|
|
|
|
static QStringList listFiles(const QDir &dir, QDirIterator::IteratorFlag flag = {})
|
|
{
|
|
QDirIterator it(dir, flag);
|
|
QStringList dirs;
|
|
while (it.hasNext())
|
|
dirs << it.next();
|
|
return dirs;
|
|
}
|
|
|
|
void showInstructionsDialog(const QString &message)
|
|
{
|
|
QMessageBox::information(nullptr, "Instructions", message);
|
|
}
|
|
|
|
void tst_ContentUris::dirFacilities()
|
|
{
|
|
showInstructionsDialog("Choose a folder with no content/files/subdirs");
|
|
|
|
auto url = QFileDialog::getExistingDirectory();
|
|
QVERIFY(url.startsWith("content"_L1));
|
|
QDir dir(url);
|
|
|
|
QVERIFY(dir.exists());
|
|
QVERIFY(!dir.dirName().isEmpty());
|
|
QVERIFY(listFiles(dir).isEmpty());
|
|
|
|
QVERIFY(dir.mkdir("Sub"));
|
|
const auto dirList = listFiles(dir);
|
|
QVERIFY(dirList.size() == 1);
|
|
const QDir subDir = dirList.first();
|
|
|
|
QVERIFY(subDir.dirName() == "Sub"_L1);
|
|
QEXPECT_FAIL("", "absolutePath() is returning wrong path, cutting from 'primary' onward", Continue);
|
|
qWarning() << "subDir.absolutePath()" << subDir.absolutePath() << dirList.first();
|
|
QVERIFY(subDir.absolutePath() == dirList.first());
|
|
QVERIFY(subDir.path() == dirList.first());
|
|
|
|
QVERIFY(listFiles(dir, QDirIterator::Subdirectories).size() == 1);
|
|
QVERIFY(dir.mkdir("Sub")); // Create an existing dir
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
|
|
QVERIFY(dir.mkpath("Sub/Sub2/Sub3"));
|
|
QVERIFY(listFiles(dir).size() == 1);
|
|
QVERIFY(listFiles(dir, QDirIterator::Subdirectories).size() == 3);
|
|
QVERIFY(dir.mkpath("Sub/Sub2/Sub3")); // Create an existing dir hierarchy
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
|
|
}
|
|
|
|
void tst_ContentUris::readWriteFile()
|
|
{
|
|
const QByteArray content = "Written to file";
|
|
const QString fileName = "new_file.txt";
|
|
|
|
{
|
|
showInstructionsDialog("Choose a name for new file to create");
|
|
|
|
auto url = QFileDialog::getSaveFileName(nullptr, tr("Save File"), fileName);
|
|
QFile file(url);
|
|
QVERIFY(file.exists());
|
|
QVERIFY(file.size() == 0);
|
|
QVERIFY(file.fileName() == url);
|
|
QVERIFY(QFileInfo(url).baseName() == fileName);
|
|
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QVERIFY(file.fileTime(QFileDevice::FileModificationTime) != QDateTime());
|
|
QVERIFY(file.write(content) > 0);
|
|
QVERIFY(file.size() == content.size());
|
|
file.close();
|
|
|
|
// NOTE: The native file cursor is not returning an updated time or it takes long
|
|
// for it to get updated, for now just check that we actually received a valid QDateTime
|
|
QVERIFY(file.fileTime(QFileDevice::FileModificationTime) != QDateTime());
|
|
}
|
|
|
|
{
|
|
showInstructionsDialog("Choose the file that was created");
|
|
|
|
auto url = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName);
|
|
QFile file(url);
|
|
QVERIFY(file.exists());
|
|
|
|
QVERIFY(file.open(QFile::ReadOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isReadable());
|
|
QVERIFY(file.readAll() == content);
|
|
|
|
QVERIFY(file.remove());
|
|
}
|
|
}
|
|
|
|
void tst_ContentUris::readWriteNonExistingFile_data()
|
|
{
|
|
QTest::addColumn<QString>("path");
|
|
|
|
const QString fileName = "non-existing-file.txt";
|
|
const QString uriSchemeAuthority = "content://com.android.externalstorage.documents";
|
|
const QString id = "primary%3APictures";
|
|
const QString encSlash = QUrl::toPercentEncoding("/"_L1);
|
|
|
|
const QString docSlash = uriSchemeAuthority + "/document/"_L1 + id + "/"_L1 + fileName;
|
|
const QString docEncodedSlash = uriSchemeAuthority + "/document/"_L1 + id + encSlash + fileName;
|
|
|
|
QTest::newRow("document_with_slash") << docSlash;
|
|
QTest::newRow("document_with_encoded_slash") << docEncodedSlash;
|
|
}
|
|
|
|
void tst_ContentUris::readWriteNonExistingFile()
|
|
{
|
|
QFETCH(QString, path);
|
|
|
|
QFile file(path);
|
|
QVERIFY(!file.exists());
|
|
QVERIFY(file.size() == 0);
|
|
|
|
QVERIFY(!file.open(QFile::WriteOnly));
|
|
QVERIFY(!file.isOpen());
|
|
QVERIFY(!file.isWritable());
|
|
}
|
|
|
|
void tst_ContentUris::createFileFromDirUrl_data()
|
|
{
|
|
QTest::addColumn<QString>("path");
|
|
|
|
showInstructionsDialog("Choose a folder with no content/files/subdirs");
|
|
|
|
const QString treeUrl = QFileDialog::getExistingDirectory();
|
|
const QString fileName = "text.txt";
|
|
const QString treeSlash = treeUrl + "/"_L1 + fileName;
|
|
QTest::newRow("tree_with_slash") << treeSlash;
|
|
|
|
// TODO: This is not handled at the moment
|
|
// const QString encSlash = QUrl::toPercentEncoding("/"_L1);
|
|
// const QString treeEncodedSlash = treeUrl + encSlash + fileName;
|
|
// QTest::newRow("tree_with_encoded_slash") << treeEncodedSlash;
|
|
}
|
|
|
|
void tst_ContentUris::createFileFromDirUrl()
|
|
{
|
|
QFETCH(QString, path);
|
|
|
|
const QByteArray content = "Written to file";
|
|
|
|
QFile file(path);
|
|
QVERIFY(!file.exists());
|
|
QVERIFY(file.size() == 0);
|
|
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QVERIFY(file.exists());
|
|
QVERIFY(file.write(content));
|
|
QVERIFY(file.size() == content.size());
|
|
file.close();
|
|
|
|
QVERIFY(file.open(QFile::ReadOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isReadable());
|
|
QVERIFY(file.readAll() == content);
|
|
|
|
QVERIFY(file.remove());
|
|
}
|
|
|
|
void tst_ContentUris::fileOperations()
|
|
{
|
|
showInstructionsDialog("Choose a name for new file to create");
|
|
|
|
const QString fileName = "new_file.txt";
|
|
auto url = QFileDialog::getSaveFileName(nullptr, tr("Save File"), fileName);
|
|
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());
|
|
}
|
|
|
|
QTEST_MAIN(tst_ContentUris)
|
|
#include "tst_content_uris.moc"
|