1 #include "tarsnaptask.h"
2 #include "debug.h"
3 #include "utils.h"
4 
5 #if defined Q_OS_UNIX
6 #include <signal.h>
7 #endif
8 
9 #define DEFAULT_TIMEOUT_MS 5000
10 #define LOG_MAX_LENGTH 3072
11 #define LOG_MAX_SEARCH_NL 1024
12 
TarsnapTask()13 TarsnapTask::TarsnapTask()
14     : QObject(),
15       _id(QUuid::createUuid()),
16       _process(nullptr),
17       _truncateLogOutput(false)
18 {
19 }
20 
~TarsnapTask()21 TarsnapTask::~TarsnapTask()
22 {
23 }
24 
run()25 void TarsnapTask::run()
26 {
27     _process = new QProcess();
28     _process->setProgram(_command);
29     _process->setArguments(_arguments);
30     if(!_stdOutFile.isEmpty())
31         _process->setStandardOutputFile(_stdOutFile);
32 
33     LOG << tr("Task %1 started:\n[%2 %3]")
34                .arg(_id.toString())
35                .arg(_process->program())
36                .arg(Utils::quoteCommandLine(_process->arguments()));
37 
38     _process->start();
39 
40     if(_process->waitForStarted(DEFAULT_TIMEOUT_MS))
41     {
42         emit started(_data);
43     }
44     else
45     {
46         processError();
47         goto cleanup;
48     }
49 
50     if(!_stdIn.isEmpty())
51     {
52         QByteArray password(_stdIn.toUtf8());
53         _process->write(password.data(), password.size());
54         _process->closeWriteChannel();
55     }
56 
57     if(_process->waitForFinished(-1))
58     {
59         readProcessOutput();
60         processFinished();
61     }
62     else
63     {
64         readProcessOutput();
65         processError();
66         goto cleanup;
67     }
68 
69 cleanup:
70     delete _process;
71     _process = nullptr;
72     emit dequeue();
73 }
74 
stop(bool kill)75 void TarsnapTask::stop(bool kill)
76 {
77     if(_process->state() == QProcess::Running)
78     {
79         _process->terminate();
80         if(kill && (false == _process->waitForFinished(DEFAULT_TIMEOUT_MS)))
81             _process->kill();
82     }
83 }
84 
interrupt()85 void TarsnapTask::interrupt()
86 {
87 #if defined Q_OS_UNIX
88     kill(_process->pid(), SIGQUIT);
89 #endif
90 }
91 
cancel()92 void TarsnapTask::cancel()
93 {
94     emit canceled(_data);
95 }
96 
waitForTask()97 bool TarsnapTask::waitForTask()
98 {
99     return _process->waitForFinished(-1);
100 }
101 
taskStatus()102 QProcess::ProcessState TarsnapTask::taskStatus()
103 {
104     return _process->state();
105 }
106 
command() const107 QString TarsnapTask::command() const
108 {
109     return _command;
110 }
111 
setCommand(const QString & command)112 void TarsnapTask::setCommand(const QString &command)
113 {
114     _command = command;
115 }
116 
arguments() const117 QStringList TarsnapTask::arguments() const
118 {
119     return _arguments;
120 }
121 
setArguments(const QStringList & arguments)122 void TarsnapTask::setArguments(const QStringList &arguments)
123 {
124     _arguments = arguments;
125 }
126 
setStdIn(const QString & standardIn)127 void TarsnapTask::setStdIn(const QString &standardIn)
128 {
129     _stdIn = standardIn;
130 }
131 
setStdOutFile(const QString & fileName)132 void TarsnapTask::setStdOutFile(const QString &fileName)
133 {
134     _stdOutFile = fileName;
135 }
136 
data() const137 QVariant TarsnapTask::data() const
138 {
139     return _data;
140 }
141 
setData(const QVariant & data)142 void TarsnapTask::setData(const QVariant &data)
143 {
144     _data = data;
145 }
146 
truncateLogOutput() const147 bool TarsnapTask::truncateLogOutput() const
148 {
149     return _truncateLogOutput;
150 }
151 
setTruncateLogOutput(bool truncateLogOutput)152 void TarsnapTask::setTruncateLogOutput(bool truncateLogOutput)
153 {
154     _truncateLogOutput = truncateLogOutput;
155 }
156 
readProcessOutput()157 void TarsnapTask::readProcessOutput()
158 {
159     if(_stdOutFile.isEmpty())
160         _stdOut.append(_process->readAllStandardOutput().trimmed());
161     _stdErr.append(_process->readAllStandardError().trimmed());
162 }
163 
processFinished()164 void TarsnapTask::processFinished()
165 {
166     switch(_process->exitStatus())
167     {
168     case QProcess::NormalExit:
169     {
170         emit finished(_data, _process->exitCode(), QString(_stdOut),
171                       QString(_stdErr));
172 
173         // Truncate LOG output
174         QByteArray stdOut(_stdOut);
175         if(!stdOut.isEmpty() && _truncateLogOutput
176            && (stdOut.size() > LOG_MAX_LENGTH))
177         {
178             int nextNL =
179                 stdOut.lastIndexOf(QChar('\n'),
180                                    LOG_MAX_LENGTH
181                                        + std::min(stdOut.size() - LOG_MAX_LENGTH,
182                                                   LOG_MAX_SEARCH_NL));
183             stdOut.truncate(std::max(LOG_MAX_LENGTH, nextNL));
184             stdOut.append(
185                 tr("\n...\n-- %1 output lines truncated by Tarsnap GUI --\n")
186                     .arg(_stdOut.mid(stdOut.size()).count(QChar('\n').toLatin1())));
187         }
188 
189         LOG << tr("Task %1 finished with exit code %2:\n[%3 %4]\n%5")
190                    .arg(_id.toString())
191                    .arg(_process->exitCode())
192                    .arg(_command)
193                    .arg(Utils::quoteCommandLine(_arguments))
194                    .arg(QString(stdOut + _stdErr));
195         break;
196     }
197     case QProcess::CrashExit:
198     {
199         processError();
200         break;
201     }
202     }
203 }
204 
processError()205 void TarsnapTask::processError()
206 {
207     LOG << tr("Task %1 finished with error %2 (%3) occured (exit code %4):\n[%5 %6]\n%7")
208                .arg(_id.toString())
209                .arg(_process->error())
210                .arg(_process->errorString())
211                .arg(_process->exitCode())
212                .arg(_command)
213                .arg(Utils::quoteCommandLine(_arguments))
214                .arg(QString(_stdOut + _stdErr).trimmed());
215     cancel();
216 }
217