1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2021-02-18
7  * Description : Qt5 and Qt6 interface for exiftool - private container.
8  *               Based on ZExifTool Qt interface published at 18 Feb 2021
9  *               https://github.com/philvl/ZExifTool
10  *
11  * Copyright (C) 2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12  * Copyright (c) 2021 by Philippe Vianney Liaud <philvl dot dev at gmail dot com>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option)
18  * any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * ============================================================ */
26 
27 #include "exiftoolprocess_p.h"
28 
29 namespace Digikam
30 {
31 
32 QMutex ExifToolProcess::Private::s_cmdIdMutex;
33 int    ExifToolProcess::Private::s_nextCmdId = ExifToolProcess::Private::CMD_ID_MIN;
34 
Private(ExifToolProcess * const q)35 ExifToolProcess::Private::Private(ExifToolProcess* const q)
36     : pp                  (q),
37       process             (nullptr),
38       cmdRunning          (0),
39       cmdAction           (ExifToolProcess::LOAD_METADATA),
40       writeChannelIsClosed(true),
41       processError        (QProcess::UnknownError)
42 {
43     outAwait[0] = false;
44     outAwait[1] = false;
45     outReady[0] = false;
46     outReady[1] = false;
47 }
48 
execNextCmd()49 void ExifToolProcess::Private::execNextCmd()
50 {
51     if ((process->state() != QProcess::Running) ||
52         writeChannelIsClosed)
53     {
54         qCWarning(DIGIKAM_METAENGINE_LOG) << "ExifToolProcess::execNextCmd(): ExifTool is not running";
55         return;
56     }
57 
58     if (cmdRunning || cmdQueue.isEmpty())
59     {
60         return;
61     }
62 
63     // Clear QProcess buffers
64 
65     process->readAllStandardOutput();
66     process->readAllStandardError();
67 
68     // Clear internal buffers
69 
70     outBuff[0]      = QByteArray();
71     outBuff[1]      = QByteArray();
72     outAwait[0]     = false;
73     outAwait[1]     = false;
74     outReady[0]     = false;
75     outReady[1]     = false;
76 
77     // Exec Command
78 
79     execTimer.start();
80 
81     Command command = cmdQueue.takeFirst();
82     cmdRunning      = command.id;
83     cmdAction       = command.ac;
84 
85     process->write(command.argsStr);
86 }
87 
readOutput(const QProcess::ProcessChannel channel)88 void ExifToolProcess::Private::readOutput(const QProcess::ProcessChannel channel)
89 {
90     process->setReadChannel(channel);
91 
92     while (process->canReadLine() && !outReady[channel])
93     {
94         QByteArray line = process->readLine();
95 
96         if (line.endsWith(QByteArray("\r\n")))
97         {
98             line.remove(line.size() - 2, 1); // Remove '\r' character
99         }
100 /*
101         qCDebug(DIGIKAM_METAENGINE_LOG) << channel << line;
102 */
103         if (!outAwait[channel])
104         {
105             if (line.startsWith(QByteArray("{await")) && line.endsWith(QByteArray("}\n")))
106             {
107                 outAwait[channel] = line.mid(6, line.size() - 8).toInt();
108             }
109 
110             continue;
111         }
112 
113         outBuff[channel] += line;
114 
115         if (line.endsWith(QByteArray("{ready}\n")))
116         {
117             outBuff[channel].chop(8);
118             outReady[channel] = true;
119 
120             break;
121         }
122     }
123 
124     // Check if outputChannel and errorChannel are both ready
125 
126     if (!(outReady[QProcess::StandardOutput] &&
127         outReady[QProcess::StandardError]))
128     {
129 /*
130         qCWarning(DIGIKAM_METAENGINE_LOG) << "ExifToolProcess::readOutput(): ExifTool read channels are not ready";
131 */
132         return;
133     }
134 
135     if (
136         (cmdRunning != outAwait[QProcess::StandardOutput]) ||
137         (cmdRunning != outAwait[QProcess::StandardError])
138        )
139     {
140         qCCritical(DIGIKAM_METAENGINE_LOG) << "ExifToolProcess::readOutput: Sync error between CmdID("
141                                            << cmdRunning
142                                            << "), outChannel("
143                                            << outAwait[0]
144                                            << ") and errChannel("
145                                            << outAwait[1]
146                                            << ")";
147     }
148     else
149     {
150         qCDebug(DIGIKAM_METAENGINE_LOG) << "ExifToolProcess::readOutput(): ExifTool command completed";
151 
152         Q_EMIT pp->signalCmdCompleted(cmdAction,
153                                       execTimer.elapsed(),
154                                       outBuff[QProcess::StandardOutput],
155                                       outBuff[QProcess::StandardError]);
156     }
157 
158     cmdRunning = 0; // No command is running
159 
160     execNextCmd();  // Exec next command
161 }
162 
setProcessErrorAndEmit(QProcess::ProcessError error,const QString & description)163 void ExifToolProcess::Private::setProcessErrorAndEmit(QProcess::ProcessError error, const QString& description)
164 {
165     processError = error;
166     errorString  = description;
167 
168     Q_EMIT pp->signalErrorOccurred(cmdAction, error);
169 }
170 
171 } // namespace Digikam
172