1 /*
2     SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "svnjobbase.h"
8 
9 #include <QStandardItemModel>
10 
11 #include <KPasswordDialog>
12 #include <KLocalizedString>
13 #include <KMessageBox>
14 
15 #include <ThreadWeaver/QObjectDecorator>
16 
17 #include <interfaces/icore.h>
18 #include <interfaces/iplugincontroller.h>
19 #include <interfaces/iplugin.h>
20 #include <outputview/ioutputview.h>
21 
22 #include "svninternaljobbase.h"
23 #include "svnssldialog.h"
24 
SvnJobBase(KDevSvnPlugin * parent,KDevelop::OutputJob::OutputJobVerbosity verbosity)25 SvnJobBase::SvnJobBase( KDevSvnPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity )
26     : VcsJob( parent, verbosity ), m_part( parent ),
27       m_status( KDevelop::VcsJob::JobNotStarted )
28 {
29     setCapabilities( KJob::Killable );
30     setTitle( QStringLiteral("Subversion") );
31 }
32 
~SvnJobBase()33 SvnJobBase::~SvnJobBase()
34 {
35 }
36 
startInternalJob()37 void SvnJobBase::startInternalJob()
38 {
39     auto job = internalJob();
40     connect( job.data(), &SvnInternalJobBase::failed,
41              this, &SvnJobBase::internalJobFailed, Qt::QueuedConnection );
42     connect( job.data(), &SvnInternalJobBase::done,
43              this, &SvnJobBase::internalJobDone, Qt::QueuedConnection );
44     connect( job.data(), &SvnInternalJobBase::started,
45              this, &SvnJobBase::internalJobStarted, Qt::QueuedConnection );
46     // add as shared pointer
47     // the signals "done" & "failed" are emitted when the queue and the executor still
48     // have and use a reference to the job, in the execution thread.
49     // As the this parent job will be deleted in the main/other thread
50     // (due to deleteLater() being called on it in the KJob::exec())
51     // and the ThreadWeaver queue will release the last reference to the passed
52     // JobInterface pointer only after the JobInterface::execute() method has been left,
53     // the internal threaded job thus needs to get shared memory management via the QSharedPointer.
54     m_part->jobQueue()->stream() << job;
55 }
56 
doKill()57 bool SvnJobBase::doKill()
58 {
59     internalJob()->kill();
60     m_status = VcsJob::JobCanceled;
61     return true;
62 }
63 
64 
status() const65 KDevelop::VcsJob::JobStatus SvnJobBase::status() const
66 {
67     return m_status;
68 }
69 
askForLogin(const QString & realm)70 void SvnJobBase::askForLogin( const QString& realm )
71 {
72     qCDebug(PLUGIN_SVN) << "login";
73     KPasswordDialog dlg( nullptr, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword );
74     dlg.setPrompt( i18n("Enter Login for: %1", realm ) );
75     if (dlg.exec()) { // krazy:exclude=crashy
76         internalJob()->m_login_username = dlg.username();
77         internalJob()->m_login_password = dlg.password();
78         internalJob()->m_maySave = dlg.keepPassword();
79     } else {
80         internalJob()->m_login_username.clear();
81         internalJob()->m_login_password.clear();
82     }
83     internalJob()->m_guiSemaphore.release( 1 );
84 }
85 
showNotification(const QString & path,const QString & msg)86 void SvnJobBase::showNotification( const QString& path, const QString& msg )
87 {
88     Q_UNUSED(path);
89     outputMessage(msg);
90 }
91 
askForCommitMessage()92 void SvnJobBase::askForCommitMessage()
93 {
94     qCDebug(PLUGIN_SVN) << "commit msg";
95     internalJob()->m_guiSemaphore.release( 1 );
96 }
97 
askForSslServerTrust(const QStringList & failures,const QString & host,const QString & print,const QString & from,const QString & until,const QString & issuer,const QString & realm)98 void SvnJobBase::askForSslServerTrust( const QStringList& failures, const QString& host,
99                                        const QString& print, const QString& from,
100                                        const QString& until, const QString& issuer,
101                                        const QString& realm )
102 {
103 
104     qCDebug(PLUGIN_SVN) << "servertrust";
105     SvnSSLTrustDialog dlg;
106     dlg.setCertInfos( host, print, from, until, issuer, realm, failures );
107     if( dlg.exec() == QDialog::Accepted )
108     {
109         qCDebug(PLUGIN_SVN) << "accepted with:" << dlg.useTemporarily();
110         if( dlg.useTemporarily() )
111         {
112             internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_TEMPORARILY;
113         }else
114         {
115         internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_PERMANENTLY;
116         }
117     }else
118     {
119         qCDebug(PLUGIN_SVN) << "didn't accept";
120         internalJob()->m_trustAnswer = svn::ContextListener::DONT_ACCEPT;
121     }
122     internalJob()->m_guiSemaphore.release( 1 );
123 }
124 
askForSslClientCert(const QString & realm)125 void SvnJobBase::askForSslClientCert( const QString& realm )
126 {
127     KMessageBox::information( nullptr, realm );
128     qCDebug(PLUGIN_SVN) << "clientrust";
129     internalJob()->m_guiSemaphore.release( 1 );
130 }
131 
askForSslClientCertPassword(const QString &)132 void SvnJobBase::askForSslClientCertPassword( const QString& )
133 {
134     qCDebug(PLUGIN_SVN) << "clientpw";
135     internalJob()->m_guiSemaphore.release( 1 );
136 }
137 
internalJobStarted()138 void SvnJobBase::internalJobStarted()
139 {
140     qCDebug(PLUGIN_SVN)  << "job started" << static_cast<void*>(internalJob().data());
141     m_status = KDevelop::VcsJob::JobRunning;
142 }
143 
internalJobDone()144 void SvnJobBase::internalJobDone()
145 {
146     qCDebug(PLUGIN_SVN) << "job done" << internalJob();
147     if ( m_status == VcsJob::JobFailed ) {
148         // see: https://bugs.kde.org/show_bug.cgi?id=273759
149         // this gets also called when the internal job failed
150         // then the emit result in internalJobFailed might trigger
151         // a nested event loop (i.e. error dialog)
152         // during that the internalJobDone gets called and triggers
153         // deleteLater and eventually deletes this job
154         // => havoc
155         //
156         // catching this state here works but I don't like it personally...
157         return;
158     }
159 
160     outputMessage(i18n("Completed"));
161     if( m_status != VcsJob::JobCanceled ) {
162         m_status = KDevelop::VcsJob::JobSucceeded;
163     }
164 
165     emitResult();
166 }
167 
internalJobFailed()168 void SvnJobBase::internalJobFailed()
169 {
170     qCDebug(PLUGIN_SVN) << "job failed" << internalJob();
171 
172     setError( 255 );
173     QString msg = internalJob()->errorMessage();
174     if( !msg.isEmpty() )
175         setErrorText( i18n( "Error executing Job:\n%1", msg ) );
176     outputMessage(errorText());
177     qCDebug(PLUGIN_SVN) << "Job failed";
178     if( m_status != VcsJob::JobCanceled )
179     {
180         m_status = KDevelop::VcsJob::JobFailed;
181     }
182 
183     emitResult();
184 }
185 
vcsPlugin() const186 KDevelop::IPlugin* SvnJobBase::vcsPlugin() const
187 {
188     return m_part;
189 }
190 
outputMessage(const QString & message)191 void SvnJobBase::outputMessage(const QString& message)
192 {
193     if (!model()) return;
194     if (verbosity() == KDevelop::OutputJob::Silent) return;
195 
196     auto *m = qobject_cast<QStandardItemModel*>(model());
197     QStandardItem *previous = m->item(m->rowCount()-1);
198     if (message == QLatin1String(".") && previous && previous->text().contains(QRegExp(QStringLiteral("\\.+"))))
199         previous->setText(previous->text() + message);
200     else
201         m->appendRow(new QStandardItem(message));
202     KDevelop::IPlugin* i = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView"));
203     if( i )
204     {
205         auto* view = i->extension<KDevelop::IOutputView>();
206         if( view )
207         {
208             view->raiseOutput( outputId() );
209         }
210     }
211 }
212 
213 
214