PDA

View Full Version : Using QProcess on 7zip and connecting Slots does not provide output to Textbox Widget



kayot
28th May 2019, 15:44
I started using Qt5 about a week or so ago. I've been using C# for about 4 years and the transition to C++ and Qt has been mostly painless. As practice I'm porting a C# program to C++ and Qt. One of the things my C# program does is allows the drag and drop of file names into a list box. I can then select the Archive type and tell it to process the list into archives. Directories get all their files put into an Archive, files get compressed on their own. I've got this working in both Windows and Linux without issue.

Now for my issue,

I was using a progress bar that updated after every line was Archived. Now I want a progress bar that updates based on the current archives status. My final goal will be to parse the output and create a progress bar that updates based on the percent complete reported, but I'm stuck. I can't get the stout from the 7z application until it finishes it's process. I need to get it while it's processing. I've been hitting google, stack, here, and the Qt documentation and I just can't figure it out. I found one post >here< (https://www.qtcentre.org/threads/64734-Qt5-C-QProcess-capture-terminal-data?highlight=7zip) that solved the issue, but it wasn't clear on how it completed the task.

I've tried to strip out as much code as possible in my post example. I left the check boxes and list control references in for clarity on what's happening. What happens is, the button on_cmdArchiveAs_clicked is pressed. This disables the button and starts a sort of event loop named ArchiveFirstListItems. The first part of the loop connects to the process, then after the process is complete it triggers the process again, until the list widget is empty. Doing it this way allows adding items to the process list while the process is underway.


static QProcess* process = new QProcess();

void MainWindow::on_cmdArchiveAs_clicked()
{
ui->cmdArchiveAs->setEnabled(false);
ArchiveFirstListItems();
}

void MainWindow::ArchiveFirstListItems()
{
if(ui->lstFiles->count()>0)
{
QString ArchiveType;
QString NewEXT;
if (ui->optZip->isChecked())
{
ArchiveType = "zip";
NewEXT = "zip";
}
else if (ui->opt7Zip->isChecked())
{
ArchiveType = "7z";
NewEXT = "7z";
}
else if (ui->optCbz->isChecked())
{
ArchiveType = "zip";
NewEXT = "cbz";
}
QFileInfo CurrentFile(ui->lstFiles->item(0)->text());
process = new QProcess(this);

//process->setReadChannel(QProcess::StandardOutput);
//process->setProcessChannelMode(QProcess::MergedChannels);
//process->setProcessChannelMode(QProcess::ForwardedChannels) ;

QObject::connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));
QObject::connect(process, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));

//QObject::connect( process, &QProcess::readyReadStandardOutput, this, &MainWindow::readyReadStandardOutput );
//QObject::connect( process, &QProcess::readyReadStandardError, this, &MainWindow::readyReadStandardError );

QObject::connect(process, SIGNAL(finished(int)), this, SLOT(processFinished()));
process->setProgram("7z");
QStringList Args;
Args << "a";
if (ArchiveType == "7z")
{
Args << "-t7z";
Args << "-m0=LZMA2:d512m:fb273";
Args << "-mx9";
Args << "-myx=9";
Args << "-mtc=off";
Args << "-mtm=off";
}
else
{
Args << "-tzip";
Args << "-mx";
Args << "-mtc=off";
}
if(ui->chkDeleteOriginalFilesOnSuccess->isChecked())
{
Args << "-sdel";
}
Args << """" + CurrentFile.filePath() + "." + NewEXT + """" ;
if(CurrentFile.isDir()){
if(ui->chkAddInfoFileDirOnly->isChecked())
{
QFile file(ui->lstFiles->item(0)->text() + "/info.txt");
if (file.open(QIODevice::ReadWrite))
{
QTextStream stream(&file);
stream << CurrentFile.fileName() << endl;
}
}
Args << """" + ui->lstFiles->item(0)->text() + "/*""";
}
else
{
Args << """" + ui->lstFiles->item(0)->text() + """";
}
qDebug() << Args;
process->setArguments(Args);
ui->lstFiles->takeItem(0);
process->start();
}
else
{
ui->cmdArchiveAs->setEnabled(true);
}
}

void MainWindow::readyReadStandardOutput()
{
ui->txtOutput->appendPlainText(process->readAllStandardOutput());
//qDebug()<<process->readAllStandardOutput();
//QObject::disconnect(process,SIGNAL(readyReadStanda rdOutput()),this,SLOT(readyReadStandardOutput()));
}

void MainWindow::readyReadStandardError()
{
ui->txtOutput->appendPlainText(process->readAllStandardError());
//qDebug()<<process->readAllStandardError();
//QObject::disconnect(process, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));
}

void MainWindow::processFinished()
{
ArchiveFirstListItems();
}

What I've Tried;
I tried to output to qDebug as well as the textbox.
I've tried to set the read channel to StandardOutput and StandardError.
I've tried setting the setProcessChannelMode to MergedChannels and ForwardedChannels.
I've compiled it in both Windows and Linux.

Topics I've read;
qprocess


I can provide the source files if it will help. I can also create a test package instead with just the source, however I'm new to Qt and C++ so it may take some time.

anda_skoa
29th May 2019, 08:28
Have you checked if the 7z program writes to its stdout and/or stderr when being called from a shell?

E.g. something like

$> 7z ...... > out.txt &
$> less -f out.txt

I.e. does "less" get new input from the file while the archiver is running?

Maybe also experiement with the "-bs" option.

Cheers,
_

kayot
29th May 2019, 13:52
The issue was -bs, I thought it defaulted to o1, e2, p1 but that isn't the case when it's ran from a C++ instance. I had to tell it to put progress or -bsp# to 1. To direct quote the manual for 7zip 18.06 (x64);


-bs (Set output stream for output/error/progress line) switch

Syntax
-bs{o|e|p}{0|1|2}
{id} Stream Type
o standard output messages
e error messages
p progress information
{N} Stream Destination
0 disable stream
1 redirect to stdout stream
2 redirect to stderr stream
Default values: o1, e2, p1.

Examples
7z t a.7z -bse1 > messages.txt
tests a.7z archive and sends error messages to stdout that is redirected to messages.txt

7z a -si -so -bsp2 -txz -an < file.tar > file.tar.xz
compresses file.tar (from stdin) to file.tar.xz (stdout stream) and shows progress information in stderr stream that can be seen at console window.

Commands that can be used with this switch
a (Add), d (Delete), h (Hash), l (List), e (Extract), u (Update), x (Extract with full paths)
I was able to use the Windows Subsystem for Linux to speed testing up in this case. When I sent stdout to a text file with > FileName.txt and used less, I saw that without -bsp1 it wasn't outputting anything. When the process finished I had the same thing I had in my Qt application. Adding -bsp1 to the 7zip command;


if (ArchiveType == "7z")
{
Args << "-t7z";
Args << "-bsp1"; //<== Added Here
Args << "-m0=LZMA2:d512m:fb273";
Args << "-mx9";
Args << "-myx=9";
Args << "-mtc=off";
Args << "-mtm=off";
}
else
{
Args << "-tzip";
Args << "-bsp1"; //<== And Here
Args << "-mx";
Args << "-mtc=off";
}

I got full feedback. I can also send the output to a different channel, -bsp2 which will send it to the error channel for easier parsing. I can use the output channel for final details and each line is absolutely positioned.

In short, my connect was working fine. It was 7zip that was giving me trouble.