Traversing directories and their sub-directories - c++

I traverse directories and their sub-directories using this piece of code. I do it in order to get path of only one random file from each end-sub-directories and pass to next end-sub-directory. But, in my implementation, it traverses all the files in end-sub-directories.
For example, from /cars/bmw/model1/ directory , it is enough to get just /cars/bmw/model1/f.png path.
QDirIterator it(selectedReferenceFullDirectory, QDir::Files, QDirIterator::Subdirectories);
while(it.hasNext())
{
...
}
My sub-directories are like:
/cars/bmw/model1/h.png
/cars/bmw/model1/f.png
/cars/bmw/model2/q.png
/cars/bmw/model1/hb/a.png
/cars/bmw/model1/sed/y.png
/cars/audi/model2/sed/y.png
...
So, there is no certain number of sub-diretory. Since there are tons of photos inside the directories, while loop takes long time. Do you have any idea to have better performance? Thank in advance

I propose to iterate not over all files, but over sub directories only. For each directory just take a single file (randomly). Here is how I would do it (simplest solution):
QStringList randomFiles(const QString &path)
{
QDirIterator it(path, QDir::AllDirs | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories);
QStringList filePaths;
while (it.hasNext())
{
it.next();
QDir dir(it.filePath());
auto files = dir.entryInfoList(QDir::Files);
if (files.size() > 0)
{
// Take the first file from each directory.
// This might be a random file too, though.
filePaths.append(files.at(0).absoluteFilePath());
}
}
return filePaths;
}

Related

Zip directory recursion

I am working on creating zip archive using old Qt - ZipWriter class. The problem is when I want to add the directory. The default Qt code for addDirectory method - d->addEntry(ZipWriterPrivate::Directory, archDirName, QByteArray());. It does not add any content, only the empty directory. So, I have improved it to add the directories and content as well.
My code:
QList<QString> dirs;
int recursion = 0;
void ZipWriter::addDirectory(const QString &dirPath)
{
QDir archDir(dirPath);
archDir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
dirs << addDirSeparator(archDir.dirName());
if (archDir.exists()) {
QString archDirName = "";
if (recursion > 0) {
for (int i = recursion; i < dirs.count(); i++) {
archDirName = dirs.first().append(dirs.at(i));
}
} else {
archDirName = dirs.at(recursion);
}
if (!archDir.isEmpty()) {
const QStringList archFileList = archDir.entryList();
if (archFileList.count() > 0) {
for (QString archFile : archFileList) {
QFileInfo archFileInfo(QDir::toNativeSeparators(QString("%1/%2").arg(archDir.absolutePath(), archFile)));
if (archFileInfo.isDir()) {
recursion++;
addDirectory(archFileInfo.absoluteFilePath());
} else {
QFile zipFile(archFileInfo.absoluteFilePath());
zipFile.open(QIODevice::ReadOnly);
addFile(QString("%1%2").arg(archDirName, archFile), zipFile.readAll());
zipFile.close();
}
}
}
} else {
d->addEntry(ZipWriterPrivate::Directory, archDirName, QByteArray());
}
}
}
Now, it adds the directory and content recursively but it has issue when directory is on the same level, it appends it to the end. I think, I must use the STL container to keep track of the directory for example QMap but the question is how to get the current directory level? Any ideas? Thank you.
Updated: 01.05.2022
I have change my code to this:
void ZipWriter::addDirectory(const QString &dirPath)
{
QDirIterator dirIt(dirPath, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (dirIt.hasNext()) {
QString archDirPath = dirIt.next();
QFile zipFile(archDirPath);
zipFile.open(QIODevice::ReadOnly);
if (!dirIt.fileInfo().isDir()) {
addFile(archDirPath, zipFile.readAll());
}
zipFile.close();
}
}
It adds everything recursively and in correct order but I have another issue. Now, it adds the full path to the archive. For example, I want to add this folder and it's content to the archive: 22610.1_amd64_en-us_professional_00fb7ba0_convert.
In the archive I get: C:\Users\userProfile\Downloads\22610.1_amd64_en-us_professional_00fb7ba0_convert. Any ideas how to make this relative path or trim it? Thank you.
You can use QDirIterator to recursively iterate over directory and subdirectories, and I believe you dont need to add nonempty directories at all, just files will be fine.
And why would you use stl container in qt, please use qt containers.

QDirIterator - Skip Folders and its subfolders

How can you skip folders with the QDirIterator?
I've tried it with:
QString nameFilter = "*.h";
QDirIterator dirIterator(folder, nameFilter, QDir::Files, QDirIterator::Subdirectories);
QString str("folder");
QStringList filenames;
while (dirIterator.hasNext())
{
if(dirIterator.next() == str) continue;
filenames.append(dirIterator.next());
}
but it only ignores the specific folder but not its subdirectories.
Any idea?
Bellow instruction working 100 percent :
This method QString QDirIterator::next() Advances the iterator to the next entry, and returns the "file path" of this new entry. If hasNext() returns false, this function does nothing, and returns a null QString.
If QDirIterator found a file with specific filter (based on const QStringList &nameFilters parameter) then QDirIterator::next() returns "file-path" and you cannot compare the whole "file-path" with "skiped-folder" to iterate!
Because of this i had to write a function as a directory parser as bellow :
QStringList Widget::getCurrDirsOfFile(const QFileInfo &file_info, const QString &default_folder)
{
QString file_path = file_info.filePath();
QString file_name = file_info.fileName();
file_path.truncate(file_path.lastIndexOf("/" + file_name));
file_path = file_path.mid(file_path.lastIndexOf("/") + 1);
return file_path;
}
This function get "file-path" and return last "folder-name's" of it like bellow :
input : /home/msi/Desktop/123/untitled/123/widget.h
output(list) : 123, 123
We have QString skiped_dir("untitled"); as skiped folders!
In this state when you got something excluding 123 "folder-name's", you can append that file to QStringList filenames :
if(folder_list.indexOf(QRegExp(skiped_dir)) == -1)
filenames.append(it.filePath());
So try this (notice in comments) :
void Widget::btn_iterate_clicked()
{
// folder to iterate
QString folder("/home/msi/Desktop/");
// folder-name you want to skip
QString skiped_dir("untitled");
// file-names which are match with .h filter stored in filenames list
QStringList filenames;
// this list used in QStringList getCurrDirsOfFile() method
QStringList folder_list;
// iterator
QDirIterator it(folder, QStringList() << "*.h" , QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) // if next object exist for iterate
{
// we have the file with .h extension
it.next();
// get previous folders that exist in filepath address of it.fileInfo()
folder_list = getCurrDirsOfFile(it.fileInfo(), folder);
// if folder_list contained skiped folder then reject that or appent it
if(folder_list.indexOf(QRegExp(skiped_dir)) == -1)
filenames.append(it.filePath());
}
for(int i = 0;i < filenames.count();i++)
{
QMessageBox::information(this, "", filenames.at(i));
}
}
Suppose we have bellow tree (directories) :
This program just shows :
/home/msi/Desktop/build-untitled-Desktop_Qt_5_0_2_GCC_64bit-Debug/ui_widget.h
/home/msi/Desktop/1/1.h
I know this is a bit old, but your original code was close...there's two problems. First, the iterator covers every entry and what it returns is a full path and filename, not just the entry name within the current directory. Checking for an exact match is the problem rather than for containment. The second issue is that you have two calls to "next" without a call to "hasNext" between them, so it's possible you'll have a crash.
while (dirIterator.hasNext())
{
QString fname = dirIterator.next ();
if (fname.contains (str)) continue;
filenames.append (fname);
}
Note that you may need to be more specific with the value of "str". Any filename could contain the word "folder", so you'll probably want to check for str.endsWith ("/folder") as well as str.contains ("/folder/").
If you inspect the values returned from "next", you'll better see how to compare what you're excluding to the current entry, so just tweak the string comparison to get what you want.

why QIterator object behave differently for same folder placed in two different locations?

I have a folder inside which i have following folder(s)/file(s)
(Folder in bold)
1.make_file_example
1.1.main.cpp
1.2.MakeFile
1.3.message.cpp
1.4.message.h
2.makeFileExample.txt
3.other_sources.txt
4.QTnotes.txt
[I've copy/pasted the same folder in two different locations]
In following images you can clearly see how same folder when placed in different location give different results
the code snippet I've used is as follows:
void MainWindow::on_pushButton_browse_clicked()
{
ui->textBrowser_filename->setLineWrapMode(QTextEdit::NoWrap);
ui->textBrowser_filename->setText("");
QString dir= QFileDialog::getExistingDirectory(this, tr("Open Directory"),
"/home",
QFileDialog::ShowDirsOnly
| QFileDialog::DontResolveSymlinks);
ui->lineEdit_dir->setText(dir);
QDirIterator it(dir, QDir::NoDotAndDotDot | QDir::AllEntries , QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
while(it.hasNext())
{
QString path=it.fileName();
ui->textBrowser_filename->append(path);
it.next();
}
}
the culprit is the next() function being called after the path is printed in the textBrowser_filename change the while loop as follows:
while(it.hasNext())
{
it.next();
QString path=it.fileName();
ui->textBrowser_filename->append(path);
}
if the entry is still there, as said by the hasNext() function, it must be iterated first.

How to iterate through only certain type of files using QDirIterator

I want to iterate through only .xml files (all files under selected folder and its sub-directories), is that possible with QDirIterator?
QDirIterator iter( rootDir, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
QString fileName;
while(iter.hasNext() )
{
qDebug() << iter.next();
fileName = iter.fileName();
// now I have to check if fileName is indeed a .xml extension
}
As can be seen in code above, if my iterator can jump to .xml files only than I don't have to check for file extension in the loop..is it possible?
One of the constructors of QDirIterator allows a nameFilters argument:
QDirIterator::QDirIterator ( const QString & path, const QStringList & nameFilters, QDir::Filters filters = QDir::NoFilter, IteratorFlags flags = NoIteratorFlags )
Constructs a QDirIterator that can iterate over path, using
nameFilters and filters.
The nameFilters argument is not properly documented, but there is a good chance it works like in QDir::setNameFilters.
QDirIterator it(rootdir, QStringList() << "*.xml", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}

Delete all files in a directory

I need to delete all files in a directory using Qt.
All of the files in the directory will have the extension ".txt".
I don't want to delete the directory itself.
Does anyone know how I can do this? I've looked at QDir but am having no luck.
Bjorns Answer tweeked to not loop forever
QString path = "whatever";
QDir dir(path);
dir.setNameFilters(QStringList() << "*.*");
dir.setFilter(QDir::Files);
foreach(QString dirFile, dir.entryList())
{
dir.remove(dirFile);
}
Ignoring the txt extension filtering... Here's a way to delete everything in the folder, including non-empty sub directories:
In QT5, you can use removeRecursively() on dirs. Unfortunately, that removes the whole directory - rather than just emptying it. Here is basic a function to just clear a directory's contents.
void clearDir( const QString path )
{
QDir dir( path );
dir.setFilter( QDir::NoDotAndDotDot | QDir::Files );
foreach( QString dirItem, dir.entryList() )
dir.remove( dirItem );
dir.setFilter( QDir::NoDotAndDotDot | QDir::Dirs );
foreach( QString dirItem, dir.entryList() )
{
QDir subDir( dir.absoluteFilePath( dirItem ) );
subDir.removeRecursively();
}
}
Alternatively, you could use removeRecursively() on the directory you want to clear (which would remove it altogether). Then, recreate it with the same name after that... The effect would be the same, but with fewer lines of code. This more verbose function, however, provides more potential for detailed exception handling to be added if desired, e.g. detecting access violations on specific files / folders...
Call QDir::entryList(QDir::Files) to get a list of all the files in the directory, and then for each fileName that ends in ".txt" call QDir::remove(fileName) to delete the file.
You started in a good way, look at entryList and of course pass the namefilter you want.
To improve on #user3191791's answer (which removes all files and directories), this answer:
Modernises the code with a range-based for loop
Provides optional error checking
The code:
struct FileOperationResult
{
bool success;
QString errorMessage;
};
FileOperationResult removeDirContents(const QString &dirPath)
{
QDir dir(dirPath);
dir.setFilter(QDir::NoDotAndDotDot | QDir::Files);
const QStringList files = dir.entryList();
for (const QString &fileName : files) {
if (!dir.remove(fileName)) {
const QString failureMessage = QString::fromUtf8(
"Failed to remove file %1 from %2").arg(fileName, dirPath);
return { false, failureMessage };
}
}
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs);
const QStringList dirs = dir.entryList();
for (const QString &dirName : dirs) {
QDir subDir(dir.absoluteFilePath(dirName));
if (!subDir.removeRecursively()) {
const QString failureMessage = QString::fromUtf8(
"Failed to recursively remove directory %1 from %2").arg(dirName, dirPath);
return { false, failureMessage };
}
}
return { true, QString() };
}
Usage:
const FileOperationResult removeResult = removeDirContents(path);
if (!removeResult.success)
qWarning() << removeResult.errorMessage;
This is how I would do it:
QString path = "name-of-directory";
QDir dir(path);
dir.setNameFilters(QStringList() << "*.txt");
dir.setFilters(QDir::Files);
while(dir.entryList().size() > 0){
dir.remove(dir.entryList().first());
}
Other variant of rreeves's code:
QDir dir("/path/to/file");
dir.setNameFilters(QStringList() << "*.*");
dir.setFilter(QDir::Files);
for(const QString &dirFile: dir.entryList()) {
dir.remove(dirFile);
}
You can achieve this without using Qt: to do so, opendir, readdir, unlink, and even rmdir will be your friends. It's easy to use, just browse the man pages ;).