mirror of https://github.com/qt/qtbase.git
505 lines
15 KiB
C++
505 lines
15 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include <QTest>
|
|
#include <QDirIterator>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QSharedPointer>
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
class ContentUris: public QObject
|
|
{
|
|
Q_OBJECT
|
|
private slots:
|
|
void initTestCase();
|
|
|
|
void validateDirPath();
|
|
void createFile();
|
|
void removeFile();
|
|
void renameFile();
|
|
void moveFile();
|
|
void fileInfoPath();
|
|
void dirOperations();
|
|
void listingFiles();
|
|
|
|
void specialFileNames_data();
|
|
void specialFileNames();
|
|
|
|
void accessNonExistentFile();
|
|
|
|
void filePickerSave();
|
|
void filePickerOpen();
|
|
|
|
void validateStaticFileUris_data();
|
|
void validateStaticFileUris();
|
|
void validateStaticDirUris_data();
|
|
void validateStaticDirUris();
|
|
|
|
private:
|
|
QString m_url;
|
|
};
|
|
|
|
const QByteArray content = "Written to file";
|
|
const QByteArray fileName = "text.txt";
|
|
|
|
void ContentUris::initTestCase()
|
|
{
|
|
QMessageBox::information(nullptr, "Select a Directory",
|
|
"Create an new directory and grant permission for it from "
|
|
"the Android File Picker will be shown after this dialog...");
|
|
m_url = QFileDialog::getExistingDirectory();
|
|
}
|
|
|
|
void ContentUris::validateDirPath()
|
|
{
|
|
QDir rootDir(m_url);
|
|
QVERIFY(rootDir.isReadable());
|
|
|
|
QFileInfo info(m_url);
|
|
QVERIFY(info.isDir());
|
|
QVERIFY(!info.isFile());
|
|
QVERIFY(info.isReadable());
|
|
}
|
|
|
|
void ContentUris::createFile()
|
|
{
|
|
QFile file(m_url + "/"_L1 + fileName);
|
|
QVERIFY(!file.exists());
|
|
QCOMPARE(file.size(), 0);
|
|
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QCOMPARE_NE(file.fileTime(QFileDevice::FileModificationTime), QDateTime());
|
|
QVERIFY(file.exists());
|
|
QVERIFY(file.write(content));
|
|
QCOMPARE(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
|
|
QCOMPARE_NE(file.fileTime(QFileDevice::FileModificationTime), QDateTime());
|
|
|
|
QVERIFY(file.open(QFile::ReadOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isReadable());
|
|
QCOMPARE(file.readAll(), content);
|
|
file.close();
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
}
|
|
|
|
void ContentUris::removeFile()
|
|
{
|
|
QFile file(m_url + "/initial-file.txt");
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.exists());
|
|
file.close();
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
}
|
|
|
|
void ContentUris::renameFile()
|
|
{
|
|
QFile file(m_url + u'/' + fileName);
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.exists());
|
|
file.close();
|
|
|
|
// rename with only file name
|
|
QString newName = "renamed-"_L1 + fileName;
|
|
QVERIFY(file.rename(newName));
|
|
QCOMPARE(file.fileName(), QFile(m_url + u'/' + newName).fileName());
|
|
QVERIFY(file.exists());
|
|
|
|
// rename now with full content uri
|
|
QFile doubleRenamedFile(m_url + "/double-" + newName);
|
|
QVERIFY(file.rename(doubleRenamedFile.fileName()));
|
|
QCOMPARE(file.fileName(), doubleRenamedFile.fileName());
|
|
QVERIFY(file.exists());
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
}
|
|
|
|
void ContentUris::moveFile()
|
|
{
|
|
// Create a file
|
|
QFile file(m_url + u'/' + fileName);
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
file.close();
|
|
QVERIFY(file.exists());
|
|
|
|
const auto moveToDir = "movedir"_L1;
|
|
QVERIFY(QDir(m_url).mkdir(moveToDir));
|
|
QString destDir = m_url + u'/' + moveToDir;
|
|
QFile destFile = QFile(destDir + u'/' + fileName);
|
|
QVERIFY(file.rename(destFile.fileName()));
|
|
QCOMPARE(file.fileName(), destFile.fileName());
|
|
QFileInfo info(file.fileName());
|
|
QCOMPARE(info.fileName(), fileName);
|
|
|
|
// move under new name
|
|
QFile destFile2 = QFile(m_url + "/renamed-"_L1 + fileName);
|
|
QVERIFY(file.rename(destFile2.fileName()));
|
|
QCOMPARE(file.fileName(), destFile2.fileName());
|
|
QCOMPARE(info.fileName(), fileName);
|
|
info = QFileInfo(file.fileName());
|
|
QCOMPARE(info.fileName(), "renamed-"_L1 + fileName);
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
|
|
QVERIFY(QDir(m_url).rmdir(moveToDir));
|
|
QVERIFY(!QDir(destDir).exists());
|
|
}
|
|
|
|
void ContentUris::specialFileNames_data()
|
|
{
|
|
QTest::addColumn<QString>("fileName");
|
|
|
|
QTest::newRow("spaces") << u"a file with spaces.txt"_s;
|
|
QTest::newRow("special_chars") << u"file-with_special$chars!.log"_s;
|
|
QTest::newRow("arabic") << u"ملف.txt"_s;
|
|
QTest::newRow("japanese") << u"ファイル.txt"_s;
|
|
QTest::newRow("chinese") << u"文件.txt"_s;
|
|
}
|
|
|
|
void ContentUris::specialFileNames()
|
|
{
|
|
QFETCH(QString, fileName);
|
|
|
|
QFile file(m_url + u'/' + fileName);
|
|
QVERIFY(!file.exists());
|
|
QCOMPARE(file.size(), 0);
|
|
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QVERIFY(file.write(content));
|
|
QCOMPARE(file.size(), content.size());
|
|
QCOMPARE(QFileInfo(file.fileName()).fileName(), fileName);
|
|
file.close();
|
|
|
|
QVERIFY(file.open(QFile::ReadOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isReadable());
|
|
QCOMPARE(file.readAll(), content);
|
|
file.close();
|
|
|
|
QVERIFY(file.rename(u'_' + fileName));
|
|
QCOMPARE(QFileInfo(file.fileName()).fileName(), u'_' + fileName);
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
|
|
QDir rootDir(m_url);
|
|
QVERIFY(rootDir.mkdir(fileName));
|
|
QDir dir(m_url + u'/' + fileName);
|
|
QVERIFY(rootDir.exists(fileName));
|
|
QCOMPARE(dir.dirName(), fileName);
|
|
|
|
QVERIFY(rootDir.rmdir(fileName));
|
|
QVERIFY(!rootDir.exists(fileName));
|
|
}
|
|
|
|
void ContentUris::validateStaticFileUris_data()
|
|
{
|
|
QTest::addColumn<QString>("uri");
|
|
QTest::addColumn<QString>("expectedFileName");
|
|
QTest::addColumn<QString>("expectedFilePath");
|
|
|
|
// External Storage Provider
|
|
QTest::newRow("file_in_tree_implicit")
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/a_file.txt"
|
|
<< "a_file.txt"
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/document/primary%3AMyDir%2Fa_file.txt";
|
|
|
|
QTest::newRow("document_with_encoded_path")
|
|
<< "content://com.android.externalstorage.documents/document/primary%3AMyDir%2Ffile.txt"
|
|
<< "file.txt"
|
|
<< "content://com.android.externalstorage.documents/document/primary%3AMyDir%2Ffile.txt";
|
|
|
|
QTest::newRow("mixed_tree_and_document")
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/document/primary%3Afile.txt"
|
|
<< "file.txt"
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/document/primary%3Afile.txt";
|
|
|
|
QTest::newRow("mixed_tree_and_document_long")
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/document/primary%3AMyDir%2Ffile.txt"
|
|
<< "file.txt"
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir/document/primary%3AMyDir%2Ffile.txt";
|
|
|
|
// Downloads Provider
|
|
QTest::newRow("downloads_provider_doc")
|
|
<< "content://com.android.providers.downloads.documents/document/1234"
|
|
<< "1234"
|
|
<< "content://com.android.providers.downloads.documents/document/1234";
|
|
|
|
// MediaStore Provider
|
|
QTest::newRow("mediastore_provider_doc")
|
|
<< "content://media/external/images/media/5678"
|
|
<< "5678"
|
|
<< "content://media/external/images/media/5678";
|
|
|
|
// Hypothetical Cloud Provider
|
|
QTest::newRow("cloud_provider_doc")
|
|
<< "content://com.example.cloudstorage.documents/document/some_file_id"
|
|
<< "some_file_id"
|
|
<< "content://com.example.cloudstorage.documents/document/some_file_id";
|
|
|
|
QTest::newRow("cloud_provider_nested_doc")
|
|
<< "content://com.example.mycloudstorage.documents/tree/user_id%3Amy_docs/document/user_id%3Amy_docs%2Freport.docx"
|
|
<< "report.docx"
|
|
<< "content://com.example.mycloudstorage.documents/tree/user_id%3Amy_docs/document/user_id%3Amy_docs%2Freport.docx";
|
|
|
|
// Contacts Provider
|
|
QTest::newRow("contacts_people_item")
|
|
<< "content://com.contacts/people/123"
|
|
<< "123"
|
|
<< "content://com.contacts/people/123";
|
|
}
|
|
|
|
void ContentUris::validateStaticFileUris()
|
|
{
|
|
QFETCH(QString, uri);
|
|
QFETCH(QString, expectedFileName);
|
|
QFETCH(QString, expectedFilePath);
|
|
|
|
QFile file(uri);
|
|
QCOMPARE(file.fileName(), expectedFilePath);
|
|
|
|
QFileInfo fileInfo(uri);
|
|
QCOMPARE(fileInfo.filePath(), uri);
|
|
QCOMPARE(fileInfo.absoluteFilePath(), expectedFilePath);
|
|
QCOMPARE(fileInfo.fileName(), expectedFileName);
|
|
}
|
|
|
|
|
|
void ContentUris::validateStaticDirUris_data()
|
|
{
|
|
QTest::addColumn<QString>("uri");
|
|
QTest::addColumn<QString>("expectedDirName");
|
|
|
|
// External Storage Provider
|
|
QTest::newRow("tree_root")
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyDir"
|
|
<< "MyDir";
|
|
|
|
QTest::newRow("children_uri")
|
|
<< "content://com.android.externalstorage.documents/tree/primary%3AMyProject/document/primary%3AMyProject/children"
|
|
<< "MyProject";
|
|
|
|
QTest::newRow("base_provider_uri")
|
|
<< "content://com.android.externalstorage.documents/"
|
|
<< "";
|
|
|
|
QTest::newRow("tree_with_device_id")
|
|
<< "content://com.android.externalstorage.documents/tree/ABCD-EFGH%3ADownloads"
|
|
<< "Downloads";
|
|
|
|
// Hypothetical Cloud Provider
|
|
QTest::newRow("cloud_provider_tree")
|
|
<< "content://com.example.cloudstorage.documents/tree/user_id%3Amy_docs"
|
|
<< "my_docs";
|
|
|
|
// Contacts Provider (not file-system, but should be handled gracefully)
|
|
QTest::newRow("contacts_people_base")
|
|
<< "content://com.contacts/people"
|
|
<< "people";
|
|
|
|
QTest::newRow("contacts_data_base")
|
|
<< "content://com.contacts/data"
|
|
<< "data";
|
|
}
|
|
|
|
void ContentUris::validateStaticDirUris()
|
|
{
|
|
QFETCH(QString, uri);
|
|
QFETCH(QString, expectedDirName);
|
|
|
|
QDir dir(uri);
|
|
QCOMPARE(dir.dirName(), expectedDirName);
|
|
// NOTE: since QDir APIs don't necessarily return a path with scheme
|
|
// we can't really rely on them returning us a valid content uri.
|
|
}
|
|
|
|
void ContentUris::fileInfoPath()
|
|
{
|
|
QFileInfo info(m_url + u'/' + fileName);
|
|
QCOMPARE(info.fileName(), fileName);
|
|
|
|
QFile file(info.absoluteFilePath());
|
|
QCOMPARE(file.fileName(), info.absoluteFilePath());
|
|
QVERIFY(!file.exists());
|
|
QCOMPARE(file.size(), 0);
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QVERIFY(file.exists());
|
|
QVERIFY(file.write(content));
|
|
QCOMPARE(file.size(), content.size());
|
|
QCOMPARE(info.size(), content.size());
|
|
file.close();
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
}
|
|
|
|
void ContentUris::accessNonExistentFile()
|
|
{
|
|
const QString uri =
|
|
"content://com.android.externalstorage.documents/document/primary%3APictures/non-existing-file.txt";
|
|
|
|
QFile file(uri);
|
|
QVERIFY(!file.exists());
|
|
QCOMPARE(file.size(), 0);
|
|
|
|
QVERIFY(!file.open(QFile::WriteOnly));
|
|
QVERIFY(!file.isOpen());
|
|
QVERIFY(!file.isWritable());
|
|
file.close();
|
|
|
|
QVERIFY(!file.open(QFile::ReadOnly));
|
|
QVERIFY(!file.isOpen());
|
|
QVERIFY(!file.isReadable());
|
|
file.close();
|
|
}
|
|
|
|
void ContentUris::dirOperations()
|
|
{
|
|
QVERIFY(m_url.startsWith("content"_L1));
|
|
QDir dir(m_url);
|
|
|
|
QVERIFY(dir.exists());
|
|
QVERIFY(!dir.dirName().isEmpty());
|
|
|
|
QVERIFY(dir.mkdir("Sub"));
|
|
QVERIFY(dir.exists());
|
|
|
|
QFileInfo info(m_url);
|
|
QVERIFY(info.isDir());
|
|
QVERIFY(info.exists());
|
|
|
|
const QString subDirPath = m_url + "/Sub";
|
|
QDir subDir(subDirPath);
|
|
QCOMPARE(subDir.dirName(), "Sub");
|
|
|
|
QFileInfo subDirInfo(subDirPath);
|
|
QVERIFY(subDirInfo.isDir());
|
|
QVERIFY(subDirInfo.exists());
|
|
QCOMPARE(subDirInfo.completeBaseName(), "Sub");
|
|
|
|
QVERIFY(dir.mkdir("Sub")); // Create an existing dir
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
|
|
QVERIFY(dir.mkpath("Sub/Sub2/Sub3"));
|
|
QVERIFY(QDir(m_url + "/Sub/Sub2").exists());
|
|
QVERIFY(QDir(m_url + "/Sub/Sub2/Sub3").exists());
|
|
|
|
QVERIFY(dir.mkpath("Sub/Sub2/Sub3")); // Create an existing dir hierarchy
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
}
|
|
|
|
void ContentUris::listingFiles()
|
|
{
|
|
auto listDirDocuments = [](const QDir &dir, QDirIterator::IteratorFlag flag = {}) {
|
|
QDirIterator it(dir, flag);
|
|
QStringList docs;
|
|
while (it.hasNext())
|
|
docs << it.next();
|
|
return docs;
|
|
};
|
|
|
|
QDir dir(m_url);
|
|
|
|
QVERIFY(dir.exists());
|
|
QVERIFY(!dir.dirName().isEmpty());
|
|
QVERIFY(listDirDocuments(dir).isEmpty());
|
|
|
|
QVERIFY(dir.mkdir("Sub"));
|
|
const auto dirList = listDirDocuments(dir);
|
|
QCOMPARE(dirList.size(), 1);
|
|
const QDir subDir = dirList.first();
|
|
|
|
QCOMPARE(subDir.dirName(), "Sub"_L1);
|
|
QCOMPARE(subDir.absolutePath(), dirList.first());
|
|
QCOMPARE(subDir.path(), dirList.first());
|
|
|
|
QCOMPARE(listDirDocuments(dir, QDirIterator::Subdirectories).size(), 1);
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
QCOMPARE(listDirDocuments(dir, QDirIterator::Subdirectories).size(), 0);
|
|
|
|
QVERIFY(dir.mkpath("Sub/Sub2/Sub3"));
|
|
QCOMPARE(listDirDocuments(dir).size(), 1);
|
|
QCOMPARE(listDirDocuments(dir, QDirIterator::Subdirectories).size(), 3);
|
|
|
|
QList<QSharedPointer<QFile>> files;
|
|
auto createFiles = [&files](const QString &path, int size) {
|
|
for (int i = 0; i < size; ++i) {
|
|
auto file = QSharedPointer<QFile>::create(path + "/file" + QString::number(i));
|
|
QVERIFY(file->open(QFile::WriteOnly));
|
|
QVERIFY(file->write(content));
|
|
files.append(file);
|
|
}
|
|
};
|
|
|
|
createFiles(m_url, 2);
|
|
QCOMPARE(listDirDocuments(dir).size(), 3);
|
|
QCOMPARE(listDirDocuments(dir, QDirIterator::Subdirectories).size(), 5);
|
|
|
|
createFiles(m_url + "/Sub", 2);
|
|
QCOMPARE(listDirDocuments(dir).size(), 3);
|
|
QCOMPARE(listDirDocuments(dir, QDirIterator::Subdirectories).size(), 7);
|
|
|
|
for (const QSharedPointer<QFile> &file : files)
|
|
QVERIFY(file->remove());
|
|
|
|
QVERIFY(dir.rmdir("Sub"));
|
|
}
|
|
|
|
void ContentUris::filePickerSave()
|
|
{
|
|
QMessageBox::information(nullptr, "Saving a File", "Agree to create a new file...");
|
|
auto url = QFileDialog::getSaveFileName(nullptr, tr("Save File"), fileName);
|
|
|
|
QFile file(url);
|
|
QVERIFY(file.exists());
|
|
QCOMPARE(file.size(), 0);
|
|
QCOMPARE(file.fileName(), url);
|
|
QCOMPARE(QFileInfo(url).fileName(), fileName);
|
|
|
|
QVERIFY(file.open(QFile::WriteOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isWritable());
|
|
QCOMPARE_NE(file.fileTime(QFileDevice::FileModificationTime), QDateTime());
|
|
QCOMPARE_GT(file.write(content), 0);
|
|
QCOMPARE(file.size(), content.size());
|
|
file.close();
|
|
|
|
}
|
|
|
|
void ContentUris::filePickerOpen()
|
|
{
|
|
QMessageBox::information(nullptr, "Reading a File", "Select the file that you just saved...");
|
|
auto url = QFileDialog::getOpenFileName(nullptr, tr("Open File"));
|
|
|
|
QFile file(url);
|
|
QVERIFY(file.exists());
|
|
|
|
QVERIFY(file.open(QFile::ReadOnly));
|
|
QVERIFY(file.isOpen());
|
|
QVERIFY(file.isReadable());
|
|
QCOMPARE(file.readAll(), content);
|
|
file.close();
|
|
|
|
QVERIFY(file.remove());
|
|
QVERIFY(!file.exists());
|
|
}
|
|
|
|
QTEST_MAIN(ContentUris)
|
|
#include "contenturis.moc"
|