1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <cstddef>
23 
24 #include <com/sun/star/beans/NamedValue.hpp>
25 
26 #include <com/sun/star/deployment/DependencyException.hpp>
27 #include <com/sun/star/deployment/LicenseException.hpp>
28 #include <com/sun/star/deployment/VersionException.hpp>
29 #include <com/sun/star/deployment/InstallException.hpp>
30 #include <com/sun/star/deployment/PlatformException.hpp>
31 
32 #include <com/sun/star/deployment/ui/LicenseDialog.hpp>
33 #include <com/sun/star/deployment/DeploymentException.hpp>
34 #include <com/sun/star/deployment/UpdateInformationProvider.hpp>
35 #include <com/sun/star/deployment/XPackage.hpp>
36 
37 #include <com/sun/star/task/InteractionHandler.hpp>
38 #include <com/sun/star/task/XAbortChannel.hpp>
39 #include <com/sun/star/task/XInteractionAbort.hpp>
40 #include <com/sun/star/task/XInteractionApprove.hpp>
41 
42 #include <com/sun/star/ucb/CommandAbortedException.hpp>
43 #include <com/sun/star/ucb/CommandFailedException.hpp>
44 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
45 
46 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
47 
48 #include <com/sun/star/uno/Reference.hxx>
49 #include <com/sun/star/uno/RuntimeException.hpp>
50 #include <com/sun/star/uno/Sequence.hxx>
51 #include <com/sun/star/uno/XInterface.hpp>
52 #include <com/sun/star/uno/TypeClass.hpp>
53 #include <o3tl/any.hxx>
54 #include <osl/diagnose.h>
55 #include <osl/mutex.hxx>
56 #include <rtl/ref.hxx>
57 #include <rtl/ustring.h>
58 #include <rtl/ustring.hxx>
59 #include <sal/types.h>
60 #include <salhelper/thread.hxx>
61 #include <ucbhelper/content.hxx>
62 #include <cppuhelper/exc_hlp.hxx>
63 #include <cppuhelper/implbase.hxx>
64 #include <comphelper/anytostring.hxx>
65 #include <vcl/svapp.hxx>
66 #include <vcl/weld.hxx>
67 #include <toolkit/helper/vclunohelper.hxx>
68 
69 #include "dp_gui.h"
70 #include "dp_gui_extensioncmdqueue.hxx"
71 #include "dp_gui_dependencydialog.hxx"
72 #include "dp_gui_dialog2.hxx"
73 #include <dp_shared.hxx>
74 #include <strings.hrc>
75 #include "dp_gui_theextmgr.hxx"
76 #include "dp_gui_updatedialog.hxx"
77 #include "dp_gui_updateinstalldialog.hxx"
78 #include <dp_dependencies.hxx>
79 #include <dp_identifier.hxx>
80 #include <dp_version.hxx>
81 
82 #include <queue>
83 #include <memory>
84 
85 #ifdef _WIN32
86 #if !defined WIN32_LEAN_AND_MEAN
87 # define WIN32_LEAN_AND_MEAN
88 #endif
89 #include <windows.h>
90 #include <objbase.h>
91 #endif
92 
93 
94 using namespace ::com::sun::star;
95 
96 namespace {
97 
getVersion(OUString const & sVersion)98 OUString getVersion( OUString const & sVersion )
99 {
100     return ( sVersion.isEmpty() ) ? OUString( "0" ) : sVersion;
101 }
102 
getVersion(const uno::Reference<deployment::XPackage> & rPackage)103 OUString getVersion( const uno::Reference< deployment::XPackage > &rPackage )
104 {
105     return getVersion( rPackage->getVersion());
106 }
107 }
108 
109 
110 namespace dp_gui {
111 
112 
113 class ProgressCmdEnv
114     : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment,
115                                       task::XInteractionHandler,
116                                       ucb::XProgressHandler >
117 {
118     uno::Reference< task::XInteractionHandler2> m_xHandler;
119     uno::Reference< uno::XComponentContext > m_xContext;
120 
121     DialogHelper*   m_pDialogHelper;
122     OUString        m_sTitle;
123     bool            m_bWarnUser;
124     sal_Int32       m_nCurrentProgress;
125 
126     void updateProgress();
127 
128     /// @throws uno::RuntimeException
129     void update_( uno::Any const & Status );
130 
131 public:
132     /** When param bAskWhenInstalling = true, then the user is asked if he
133     agrees to install this extension. In case this extension is already installed
134     then the user is also notified and asked if he wants to replace that existing
135     extension. In first case an interaction request with an InstallException
136     will be handled and in the second case a VersionException will be handled.
137     */
138 
ProgressCmdEnv(const uno::Reference<uno::XComponentContext> & rContext,DialogHelper * pDialogHelper,const OUString & rTitle)139     ProgressCmdEnv( const uno::Reference< uno::XComponentContext >& rContext,
140                     DialogHelper* pDialogHelper,
141                     const OUString& rTitle )
142         : m_xContext( rContext )
143         , m_pDialogHelper( pDialogHelper )
144         , m_sTitle( rTitle )
145         , m_bWarnUser( false )
146         , m_nCurrentProgress(0)
147         {}
148 
activeDialog()149     weld::Window* activeDialog() { return m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr; }
150 
151     void startProgress();
152     void stopProgress();
153     void progressSection( const OUString &rText,
154                           const uno::Reference< task::XAbortChannel > &xAbortChannel );
setWarnUser(bool bNewVal)155     void setWarnUser( bool bNewVal ) { m_bWarnUser = bNewVal; }
156 
157     // XCommandEnvironment
158     virtual uno::Reference< task::XInteractionHandler > SAL_CALL getInteractionHandler() override;
159     virtual uno::Reference< ucb::XProgressHandler > SAL_CALL getProgressHandler() override;
160 
161     // XInteractionHandler
162     virtual void SAL_CALL handle( uno::Reference< task::XInteractionRequest > const & xRequest ) override;
163 
164     // XProgressHandler
165     virtual void SAL_CALL push( uno::Any const & Status ) override;
166     virtual void SAL_CALL update( uno::Any const & Status ) override;
167     virtual void SAL_CALL pop() override;
168 };
169 
170 
171 struct ExtensionCmd
172 {
173     enum E_CMD_TYPE { ADD, ENABLE, DISABLE, REMOVE, CHECK_FOR_UPDATES, ACCEPT_LICENSE };
174 
175     E_CMD_TYPE  m_eCmdType;
176     bool        m_bWarnUser;
177     OUString    m_sExtensionURL;
178     OUString    m_sRepository;
179     uno::Reference< deployment::XPackage > m_xPackage;
180     std::vector< uno::Reference< deployment::XPackage > >        m_vExtensionList;
181 
ExtensionCmddp_gui::ExtensionCmd182     ExtensionCmd( const E_CMD_TYPE eCommand,
183                   const OUString &rExtensionURL,
184                   const OUString &rRepository,
185                   const bool bWarnUser )
186         : m_eCmdType( eCommand ),
187           m_bWarnUser( bWarnUser ),
188           m_sExtensionURL( rExtensionURL ),
189           m_sRepository( rRepository ) {};
ExtensionCmddp_gui::ExtensionCmd190     ExtensionCmd( const E_CMD_TYPE eCommand,
191                   const uno::Reference< deployment::XPackage > &rPackage )
192         : m_eCmdType( eCommand ),
193           m_bWarnUser( false ),
194           m_xPackage( rPackage ) {};
ExtensionCmddp_gui::ExtensionCmd195     ExtensionCmd( const E_CMD_TYPE eCommand,
196                   const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
197         : m_eCmdType( eCommand ),
198           m_bWarnUser( false ),
199           m_vExtensionList( vExtensionList ) {};
200 };
201 
202 typedef std::shared_ptr< ExtensionCmd > TExtensionCmd;
203 
204 
205 class ExtensionCmdQueue::Thread: public salhelper::Thread
206 {
207 public:
208     Thread( DialogHelper *pDialogHelper,
209             TheExtensionManager *pManager,
210             const uno::Reference< uno::XComponentContext > & rContext );
211 
212     void addExtension( const OUString &rExtensionURL,
213                        const OUString &rRepository,
214                        const bool bWarnUser );
215     void removeExtension( const uno::Reference< deployment::XPackage > &rPackage );
216     void enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
217                           const bool bEnable );
218     void checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList );
219     void acceptLicense( const uno::Reference< deployment::XPackage > &rPackage );
220     void stop();
221     bool isBusy();
222 
223 private:
224     virtual ~Thread() override;
225 
226     virtual void execute() override;
227 
228     void _insert(const TExtensionCmd& rExtCmd);
229 
230     void _addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
231                         const OUString &rPackageURL,
232                         const OUString &rRepository,
233                         const bool bWarnUser );
234     void _removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
235                            const uno::Reference< deployment::XPackage > &xPackage );
236     void _enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
237                            const uno::Reference< deployment::XPackage > &xPackage );
238     void _disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
239                             const uno::Reference< deployment::XPackage > &xPackage );
240     void _checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList );
241     void _acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
242                            const uno::Reference< deployment::XPackage > &xPackage );
243 
244     enum Input { NONE, START, STOP };
245 
246     uno::Reference< uno::XComponentContext > m_xContext;
247     std::queue< TExtensionCmd >              m_queue;
248 
249     DialogHelper *m_pDialogHelper;
250     TheExtensionManager *m_pManager;
251 
252     const OUString   m_sEnablingPackages;
253     const OUString   m_sDisablingPackages;
254     const OUString   m_sAddingPackages;
255     const OUString   m_sRemovingPackages;
256     const OUString   m_sDefaultCmd;
257     const OUString   m_sAcceptLicense;
258     osl::Condition   m_wakeup;
259     osl::Mutex       m_mutex;
260     Input            m_eInput;
261     bool             m_bStopped;
262     bool             m_bWorking;
263 };
264 
265 
startProgress()266 void ProgressCmdEnv::startProgress()
267 {
268     m_nCurrentProgress = 0;
269 
270     if ( m_pDialogHelper )
271         m_pDialogHelper->showProgress( true );
272 }
273 
274 
stopProgress()275 void ProgressCmdEnv::stopProgress()
276 {
277     if ( m_pDialogHelper )
278         m_pDialogHelper->showProgress( false );
279 }
280 
281 
progressSection(const OUString & rText,const uno::Reference<task::XAbortChannel> & xAbortChannel)282 void ProgressCmdEnv::progressSection( const OUString &rText,
283                                       const uno::Reference< task::XAbortChannel > &xAbortChannel )
284 {
285     m_nCurrentProgress = 0;
286     if ( m_pDialogHelper )
287     {
288         m_pDialogHelper->updateProgress( rText, xAbortChannel );
289         m_pDialogHelper->updateProgress( 5 );
290     }
291 }
292 
293 
updateProgress()294 void ProgressCmdEnv::updateProgress()
295 {
296     long nProgress = ((m_nCurrentProgress*5) % 100) + 5;
297     if ( m_pDialogHelper )
298         m_pDialogHelper->updateProgress( nProgress );
299 }
300 
301 // XCommandEnvironment
302 
getInteractionHandler()303 uno::Reference< task::XInteractionHandler > ProgressCmdEnv::getInteractionHandler()
304 {
305     return this;
306 }
307 
308 
getProgressHandler()309 uno::Reference< ucb::XProgressHandler > ProgressCmdEnv::getProgressHandler()
310 {
311     return this;
312 }
313 
314 
315 // XInteractionHandler
316 
handle(uno::Reference<task::XInteractionRequest> const & xRequest)317 void ProgressCmdEnv::handle( uno::Reference< task::XInteractionRequest > const & xRequest )
318 {
319     uno::Any request( xRequest->getRequest() );
320     OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
321     dp_misc::TRACE( "[dp_gui_cmdenv.cxx] incoming request:\n"
322         + ::comphelper::anyToString(request) + "\n");
323 
324     lang::WrappedTargetException wtExc;
325     deployment::DependencyException depExc;
326     deployment::LicenseException licExc;
327     deployment::VersionException verExc;
328     deployment::InstallException instExc;
329     deployment::PlatformException platExc;
330 
331     // selections:
332     bool approve = false;
333     bool abort = false;
334 
335     if (request >>= wtExc) {
336         // handable deployment error signalled, e.g.
337         // bundle item registration failed, notify cause only:
338         uno::Any cause;
339         deployment::DeploymentException dpExc;
340         if (wtExc.TargetException >>= dpExc)
341             cause = dpExc.Cause;
342         else {
343             ucb::CommandFailedException cfExc;
344             if (wtExc.TargetException >>= cfExc)
345                 cause = cfExc.Reason;
346             else
347                 cause = wtExc.TargetException;
348         }
349         update_( cause );
350 
351         // ignore intermediate errors of legacy packages, i.e.
352         // former pkgchk behaviour:
353         const uno::Reference< deployment::XPackage > xPackage( wtExc.Context, uno::UNO_QUERY );
354         OSL_ASSERT( xPackage.is() );
355         if ( xPackage.is() )
356         {
357             const uno::Reference< deployment::XPackageTypeInfo > xPackageType( xPackage->getPackageType() );
358             OSL_ASSERT( xPackageType.is() );
359             if (xPackageType.is())
360             {
361                 approve = ( xPackage->isBundle() &&
362                             xPackageType->getMediaType().match(
363                                "application/vnd.sun.star.legacy-package-bundle" ));
364             }
365         }
366         abort = !approve;
367     }
368     else if (request >>= depExc)
369     {
370         std::vector< OUString > deps;
371         deps.reserve(depExc.UnsatisfiedDependencies.getLength());
372         for (sal_Int32 i = 0; i < depExc.UnsatisfiedDependencies.getLength(); ++i)
373         {
374             deps.push_back(
375                 dp_misc::Dependencies::getErrorText( depExc.UnsatisfiedDependencies[i]) );
376         }
377         {
378             SolarMutexGuard guard;
379             if (m_pDialogHelper)
380                 m_pDialogHelper->incBusy();
381             DependencyDialog aDlg(activeDialog(), deps);
382             short n = aDlg.run();
383             if (m_pDialogHelper)
384                 m_pDialogHelper->decBusy();
385             // Distinguish between closing the dialog and programmatically
386             // canceling the dialog (headless VCL):
387             approve = n == RET_OK
388                 || (n == RET_CANCEL && !Application::IsDialogCancelEnabled());
389         }
390     }
391     else if (request >>= licExc)
392     {
393         SolarMutexGuard guard;
394 
395         weld::Window *pTopLevel = activeDialog();
396         if (m_pDialogHelper)
397             m_pDialogHelper->incBusy();
398         uno::Reference< ui::dialogs::XExecutableDialog > xDialog(
399             deployment::ui::LicenseDialog::create(
400             m_xContext, pTopLevel ? pTopLevel->GetXWindow() : nullptr,
401             licExc.ExtensionName, licExc.Text ) );
402         sal_Int16 res = xDialog->execute();
403         if (m_pDialogHelper)
404             m_pDialogHelper->decBusy();
405         if ( res == ui::dialogs::ExecutableDialogResults::CANCEL )
406             abort = true;
407         else if ( res == ui::dialogs::ExecutableDialogResults::OK )
408             approve = true;
409         else
410         {
411             OSL_ASSERT(false);
412         }
413     }
414     else if (request >>= verExc)
415     {
416         const char* id;
417         switch (dp_misc::compareVersions(
418                     verExc.NewVersion, verExc.Deployed->getVersion() ))
419         {
420         case dp_misc::LESS:
421             id = RID_STR_WARNING_VERSION_LESS;
422             break;
423         case dp_misc::EQUAL:
424             id = RID_STR_WARNING_VERSION_EQUAL;
425             break;
426         default: // dp_misc::GREATER
427             id = RID_STR_WARNING_VERSION_GREATER;
428             break;
429         }
430         OSL_ASSERT( verExc.Deployed.is() );
431         bool bEqualNames = verExc.NewDisplayName ==
432             verExc.Deployed->getDisplayName();
433         {
434             SolarMutexGuard guard;
435 
436             if (m_pDialogHelper)
437                 m_pDialogHelper->incBusy();
438 
439             std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
440                                                       VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(id)));
441             OUString s;
442             if (bEqualNames)
443             {
444                 s = xBox->get_primary_text();
445             }
446             else if (!strcmp(id, RID_STR_WARNING_VERSION_EQUAL))
447             {
448                 //hypothetical: requires two instances of an extension with the same
449                 //version to have different display names. Probably the developer forgot
450                 //to change the version.
451                 s = DpResId(RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES);
452             }
453             else if (!strcmp(id, RID_STR_WARNING_VERSION_LESS))
454             {
455                 s = DpResId(RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES);
456             }
457             else if (!strcmp(id, RID_STR_WARNING_VERSION_GREATER))
458             {
459                s = DpResId(RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES);
460             }
461             s = s.replaceAll("$NAME", verExc.NewDisplayName);
462             s = s.replaceAll("$OLDNAME", verExc.Deployed->getDisplayName());
463             s = s.replaceAll("$NEW", getVersion(verExc.NewVersion));
464             s = s.replaceAll("$DEPLOYED", getVersion(verExc.Deployed));
465             xBox->set_primary_text(s);
466             approve = xBox->run() == RET_OK;
467             if (m_pDialogHelper)
468                 m_pDialogHelper->decBusy();
469             abort = !approve;
470         }
471     }
472     else if (request >>= instExc)
473     {
474         if ( ! m_bWarnUser )
475         {
476             approve = true;
477         }
478         else
479         {
480             if ( m_pDialogHelper )
481             {
482                 SolarMutexGuard guard;
483 
484                 approve = m_pDialogHelper->installExtensionWarn( instExc.displayName );
485             }
486             else
487                 approve = false;
488             abort = !approve;
489         }
490     }
491     else if (request >>= platExc)
492     {
493         SolarMutexGuard guard;
494         OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM));
495         sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName());
496         if (m_pDialogHelper)
497             m_pDialogHelper->incBusy();
498         std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
499                                                   VclMessageType::Warning, VclButtonsType::Ok, sMsg));
500         xBox->run();
501         if (m_pDialogHelper)
502             m_pDialogHelper->decBusy();
503         approve = true;
504     }
505 
506     if (!approve && !abort)
507     {
508         // forward to UUI handler:
509         if (! m_xHandler.is()) {
510             // late init:
511             m_xHandler = task::InteractionHandler::createWithParentAndContext(m_xContext, nullptr, m_sTitle);
512         }
513         m_xHandler->handle( xRequest );
514     }
515     else
516     {
517         // select:
518         uno::Sequence< uno::Reference< task::XInteractionContinuation > > conts(
519             xRequest->getContinuations() );
520         uno::Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray();
521         sal_Int32 len = conts.getLength();
522         for ( sal_Int32 pos = 0; pos < len; ++pos )
523         {
524             if (approve) {
525                 uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY );
526                 if (xInteractionApprove.is()) {
527                     xInteractionApprove->select();
528                     // don't query again for ongoing continuations:
529                     approve = false;
530                 }
531             }
532             else if (abort) {
533                 uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY );
534                 if (xInteractionAbort.is()) {
535                     xInteractionAbort->select();
536                     // don't query again for ongoing continuations:
537                     abort = false;
538                 }
539             }
540         }
541     }
542 }
543 
544 
545 // XProgressHandler
546 
push(uno::Any const & rStatus)547 void ProgressCmdEnv::push( uno::Any const & rStatus )
548 {
549     update_( rStatus );
550 }
551 
552 
update_(uno::Any const & rStatus)553 void ProgressCmdEnv::update_( uno::Any const & rStatus )
554 {
555     OUString text;
556     if ( rStatus.hasValue() && !( rStatus >>= text) )
557     {
558         if ( auto e = o3tl::tryAccess<uno::Exception>(rStatus) )
559             text = e->Message;
560         if ( text.isEmpty() )
561             text = ::comphelper::anyToString( rStatus ); // fallback
562 
563         const SolarMutexGuard aGuard;
564         if (m_pDialogHelper)
565             m_pDialogHelper->incBusy();
566         std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(),
567                                                   VclMessageType::Warning, VclButtonsType::Ok, text));
568         xBox->run();
569         if (m_pDialogHelper)
570             m_pDialogHelper->decBusy();
571     }
572     ++m_nCurrentProgress;
573     updateProgress();
574 }
575 
576 
update(uno::Any const & rStatus)577 void ProgressCmdEnv::update( uno::Any const & rStatus )
578 {
579     update_( rStatus );
580 }
581 
582 
pop()583 void ProgressCmdEnv::pop()
584 {
585     update_( uno::Any() ); // no message
586 }
587 
588 
Thread(DialogHelper * pDialogHelper,TheExtensionManager * pManager,const uno::Reference<uno::XComponentContext> & rContext)589 ExtensionCmdQueue::Thread::Thread( DialogHelper *pDialogHelper,
590                                    TheExtensionManager *pManager,
591                                    const uno::Reference< uno::XComponentContext > & rContext ) :
592     salhelper::Thread( "dp_gui_extensioncmdqueue" ),
593     m_xContext( rContext ),
594     m_pDialogHelper( pDialogHelper ),
595     m_pManager( pManager ),
596     m_sEnablingPackages( DpResId( RID_STR_ENABLING_PACKAGES ) ),
597     m_sDisablingPackages( DpResId( RID_STR_DISABLING_PACKAGES ) ),
598     m_sAddingPackages( DpResId( RID_STR_ADDING_PACKAGES ) ),
599     m_sRemovingPackages( DpResId( RID_STR_REMOVING_PACKAGES ) ),
600     m_sDefaultCmd( DpResId( RID_STR_ADD_PACKAGES ) ),
601     m_sAcceptLicense( DpResId( RID_STR_ACCEPT_LICENSE ) ),
602     m_eInput( NONE ),
603     m_bStopped( false ),
604     m_bWorking( false )
605 {
606     OSL_ASSERT( pDialogHelper );
607 }
608 
609 
addExtension(const OUString & rExtensionURL,const OUString & rRepository,const bool bWarnUser)610 void ExtensionCmdQueue::Thread::addExtension( const OUString &rExtensionURL,
611                                               const OUString &rRepository,
612                                               const bool bWarnUser )
613 {
614     if ( !rExtensionURL.isEmpty() )
615     {
616         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::ADD, rExtensionURL, rRepository, bWarnUser ) );
617         _insert( pEntry );
618     }
619 }
620 
621 
removeExtension(const uno::Reference<deployment::XPackage> & rPackage)622 void ExtensionCmdQueue::Thread::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
623 {
624     if ( rPackage.is() )
625     {
626         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::REMOVE, rPackage ) );
627         _insert( pEntry );
628     }
629 }
630 
631 
acceptLicense(const uno::Reference<deployment::XPackage> & rPackage)632 void ExtensionCmdQueue::Thread::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
633 {
634     if ( rPackage.is() )
635     {
636         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::ACCEPT_LICENSE, rPackage ) );
637         _insert( pEntry );
638     }
639 }
640 
641 
enableExtension(const uno::Reference<deployment::XPackage> & rPackage,const bool bEnable)642 void ExtensionCmdQueue::Thread::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
643                                                  const bool bEnable )
644 {
645     if ( rPackage.is() )
646     {
647         TExtensionCmd pEntry( new ExtensionCmd( bEnable ? ExtensionCmd::ENABLE :
648                                                           ExtensionCmd::DISABLE,
649                                                 rPackage ) );
650         _insert( pEntry );
651     }
652 }
653 
654 
checkForUpdates(const std::vector<uno::Reference<deployment::XPackage>> & vExtensionList)655 void ExtensionCmdQueue::Thread::checkForUpdates(
656     const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
657 {
658     TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::CHECK_FOR_UPDATES, vExtensionList ) );
659     _insert( pEntry );
660 }
661 
662 
663 //Stopping this thread will not abort the installation of extensions.
stop()664 void ExtensionCmdQueue::Thread::stop()
665 {
666     osl::MutexGuard aGuard( m_mutex );
667     m_bStopped = true;
668     m_eInput = STOP;
669     m_wakeup.set();
670 }
671 
672 
isBusy()673 bool ExtensionCmdQueue::Thread::isBusy()
674 {
675     osl::MutexGuard aGuard( m_mutex );
676     return m_bWorking;
677 }
678 
679 
~Thread()680 ExtensionCmdQueue::Thread::~Thread() {}
681 
682 
execute()683 void ExtensionCmdQueue::Thread::execute()
684 {
685 #ifdef _WIN32
686     //Needed for use of the service "com.sun.star.system.SystemShellExecute" in
687     //DialogHelper::openWebBrowser
688     CoUninitialize();
689     (void) CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
690 #endif
691     for (;;)
692     {
693         if ( m_wakeup.wait() != osl::Condition::result_ok )
694         {
695             dp_misc::TRACE( "dp_gui::ExtensionCmdQueue::Thread::run: ignored "
696                        "osl::Condition::wait failure\n" );
697         }
698         m_wakeup.reset();
699 
700         int nSize;
701         Input eInput;
702         {
703             osl::MutexGuard aGuard( m_mutex );
704             eInput = m_eInput;
705             m_eInput = NONE;
706             nSize = m_queue.size();
707             m_bWorking = false;
708         }
709 
710         // If this thread has been woken up by anything else except start, stop
711         // then input is NONE and we wait again.
712         // We only install the extension which are currently in the queue.
713         // The progressbar will be set to show the progress of the current number
714         // of extensions. If we allowed to add extensions now then the progressbar may
715         // have reached the end while we still install newly added extensions.
716         if ( ( eInput == NONE ) || ( nSize == 0 ) )
717             continue;
718         if ( eInput == STOP )
719             break;
720 
721         ::rtl::Reference< ProgressCmdEnv > currentCmdEnv( new ProgressCmdEnv( m_xContext, m_pDialogHelper, m_sDefaultCmd ) );
722 
723         // Do not lock the following part with addExtension. addExtension may be called in the main thread.
724         // If the message box "Do you want to install the extension (or similar)" is shown and then
725         // addExtension is called, which then blocks the main thread, then we deadlock.
726         bool bStartProgress = true;
727 
728         while ( --nSize >= 0 )
729         {
730             {
731                 osl::MutexGuard aGuard( m_mutex );
732                 m_bWorking = true;
733             }
734 
735             try
736             {
737                 TExtensionCmd pEntry;
738                 {
739                     ::osl::MutexGuard queueGuard( m_mutex );
740                     pEntry = m_queue.front();
741                     m_queue.pop();
742                 }
743 
744                 if ( bStartProgress && ( pEntry->m_eCmdType != ExtensionCmd::CHECK_FOR_UPDATES ) )
745                 {
746                     currentCmdEnv->startProgress();
747                     bStartProgress = false;
748                 }
749 
750                 switch ( pEntry->m_eCmdType ) {
751                 case ExtensionCmd::ADD :
752                     _addExtension( currentCmdEnv, pEntry->m_sExtensionURL, pEntry->m_sRepository, pEntry->m_bWarnUser );
753                     break;
754                 case ExtensionCmd::REMOVE :
755                     _removeExtension( currentCmdEnv, pEntry->m_xPackage );
756                     break;
757                 case ExtensionCmd::ENABLE :
758                     _enableExtension( currentCmdEnv, pEntry->m_xPackage );
759                     break;
760                 case ExtensionCmd::DISABLE :
761                     _disableExtension( currentCmdEnv, pEntry->m_xPackage );
762                     break;
763                 case ExtensionCmd::CHECK_FOR_UPDATES :
764                     _checkForUpdates( pEntry->m_vExtensionList );
765                     break;
766                 case ExtensionCmd::ACCEPT_LICENSE :
767                     _acceptLicense( currentCmdEnv, pEntry->m_xPackage );
768                     break;
769                 }
770             }
771             catch ( const ucb::CommandAbortedException & )
772             {
773                 //This exception is thrown when the user clicks cancel on the progressbar.
774                 //Then we cancel the installation of all extensions and remove them from
775                 //the queue.
776                 {
777                     ::osl::MutexGuard queueGuard2(m_mutex);
778                     while ( --nSize >= 0 )
779                         m_queue.pop();
780                 }
781                 break;
782             }
783             catch ( const ucb::CommandFailedException & )
784             {
785                 //This exception is thrown when a user clicked cancel in the messagebox which was
786                 //started by the interaction handler. For example the user will be asked if he/she
787                 //really wants to install the extension.
788                 //These interaction are run for exactly one extension at a time. Therefore we continue
789                 //with installing the remaining extensions.
790                 continue;
791             }
792             catch ( const uno::Exception & )
793             {
794                 //Todo display the user an error
795                 //see also DialogImpl::SyncPushButton::Click()
796                 uno::Any exc( ::cppu::getCaughtException() );
797                 OUString msg;
798                 deployment::DeploymentException dpExc;
799                 if (exc >>= dpExc)
800                 {
801                     if (auto e = o3tl::tryAccess<uno::Exception>(dpExc.Cause))
802                     {
803                         // notify error cause only:
804                         msg = e->Message;
805                     }
806                 }
807                 if (msg.isEmpty()) // fallback for debugging purposes
808                     msg = ::comphelper::anyToString(exc);
809 
810                 const SolarMutexGuard guard;
811                 if (m_pDialogHelper)
812                     m_pDialogHelper->incBusy();
813 
814                 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(currentCmdEnv->activeDialog(),
815                                                           VclMessageType::Warning, VclButtonsType::Ok, msg));
816                 if (m_pDialogHelper)
817                     xBox->set_title(m_pDialogHelper->getFrameWeld()->get_title());
818                 xBox->run();
819                 if (m_pDialogHelper)
820                     m_pDialogHelper->decBusy();
821                 //Continue with installation of the remaining extensions
822             }
823             {
824                 osl::MutexGuard aGuard( m_mutex );
825                 m_bWorking = false;
826             }
827         }
828 
829         {
830             // when leaving the while loop with break, we should set working to false, too
831             osl::MutexGuard aGuard( m_mutex );
832             m_bWorking = false;
833         }
834 
835         if ( !bStartProgress )
836             currentCmdEnv->stopProgress();
837     }
838     //end for
839 #ifdef _WIN32
840     CoUninitialize();
841 #endif
842 }
843 
844 
_addExtension(::rtl::Reference<ProgressCmdEnv> const & rCmdEnv,const OUString & rPackageURL,const OUString & rRepository,const bool bWarnUser)845 void ExtensionCmdQueue::Thread::_addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
846                                                const OUString &rPackageURL,
847                                                const OUString &rRepository,
848                                                const bool bWarnUser )
849 {
850     //check if we have a string in anyTitle. For example "unopkg gui \" caused anyTitle to be void
851     //and anyTitle.get<OUString> throws as RuntimeException.
852     uno::Any anyTitle;
853     try
854     {
855         anyTitle = ::ucbhelper::Content( rPackageURL, rCmdEnv.get(), m_xContext ).getPropertyValue( "Title" );
856     }
857     catch ( const uno::Exception & )
858     {
859         return;
860     }
861 
862     OUString sName;
863     if ( ! (anyTitle >>= sName) )
864     {
865         OSL_FAIL("Could not get file name for extension.");
866         return;
867     }
868 
869     rCmdEnv->setWarnUser( bWarnUser );
870     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
871     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
872     OUString sTitle(
873         m_sAddingPackages.replaceAll("%EXTENSION_NAME", sName));
874     rCmdEnv->progressSection( sTitle, xAbortChannel );
875 
876     try
877     {
878         xExtMgr->addExtension(rPackageURL, uno::Sequence<beans::NamedValue>(),
879                               rRepository, xAbortChannel, rCmdEnv.get() );
880     }
881     catch ( const ucb::CommandFailedException & )
882     {
883         // When the extension is already installed we'll get a dialog asking if we want to overwrite. If we then press
884         // cancel this exception is thrown.
885     }
886     catch ( const ucb::CommandAbortedException & )
887     {
888         // User clicked the cancel button
889         // TODO: handle cancel
890     }
891     rCmdEnv->setWarnUser( false );
892 }
893 
894 
_removeExtension(::rtl::Reference<ProgressCmdEnv> const & rCmdEnv,const uno::Reference<deployment::XPackage> & xPackage)895 void ExtensionCmdQueue::Thread::_removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
896                                                   const uno::Reference< deployment::XPackage > &xPackage )
897 {
898     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
899     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
900     OUString sTitle(
901         m_sRemovingPackages.replaceAll("%EXTENSION_NAME",
902             xPackage->getDisplayName()));
903     rCmdEnv->progressSection( sTitle, xAbortChannel );
904 
905     OUString id( dp_misc::getIdentifier( xPackage ) );
906     try
907     {
908         xExtMgr->removeExtension( id, xPackage->getName(), xPackage->getRepositoryName(), xAbortChannel, rCmdEnv.get() );
909     }
910     catch ( const deployment::DeploymentException & )
911     {}
912     catch ( const ucb::CommandFailedException & )
913     {}
914     catch ( const ucb::CommandAbortedException & )
915     {}
916 
917     // Check, if there are still updates to be notified via menu bar icon
918     uno::Sequence< uno::Sequence< OUString > > aItemList;
919     UpdateDialog::createNotifyJob( false, aItemList );
920 }
921 
922 
_checkForUpdates(const std::vector<uno::Reference<deployment::XPackage>> & vExtensionList)923 void ExtensionCmdQueue::Thread::_checkForUpdates(
924     const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
925 {
926     const SolarMutexGuard guard;
927 
928     if (m_pDialogHelper)
929         m_pDialogHelper->incBusy();
930 
931     std::vector< UpdateData > vData;
932     UpdateDialog aUpdateDialog(m_xContext, m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, vExtensionList, &vData);
933 
934     aUpdateDialog.notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon
935 
936     bool bOk = aUpdateDialog.run() == RET_OK;
937     if (m_pDialogHelper)
938         m_pDialogHelper->decBusy();
939 
940     if (bOk && !vData.empty())
941     {
942         // If there is at least one directly downloadable extension then we
943         // open the install dialog.
944         std::vector< UpdateData > dataDownload;
945 
946         for (auto const& data : vData)
947         {
948             if ( data.sWebsiteURL.isEmpty() )
949                 dataDownload.push_back(data);
950         }
951 
952         short nDialogResult = RET_OK;
953         if ( !dataDownload.empty() )
954         {
955             if (m_pDialogHelper)
956                 m_pDialogHelper->incBusy();
957             UpdateInstallDialog aDlg(m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, dataDownload, m_xContext);
958             nDialogResult = aDlg.run();
959             if (m_pDialogHelper)
960                 m_pDialogHelper->decBusy();
961             aUpdateDialog.notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon
962         }
963         else
964             aUpdateDialog.notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon
965 
966         //Now start the webbrowser and navigate to the websites where we get the updates
967         if ( RET_OK == nDialogResult )
968         {
969             for (auto const& data : vData)
970             {
971                 if ( m_pDialogHelper && ( !data.sWebsiteURL.isEmpty() ) )
972                     m_pDialogHelper->openWebBrowser( data.sWebsiteURL, m_pDialogHelper->getFrameWeld()->get_title() );
973             }
974         }
975     }
976     else
977         aUpdateDialog.notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon
978 }
979 
980 
_enableExtension(::rtl::Reference<ProgressCmdEnv> const & rCmdEnv,const uno::Reference<deployment::XPackage> & xPackage)981 void ExtensionCmdQueue::Thread::_enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
982                                                   const uno::Reference< deployment::XPackage > &xPackage )
983 {
984     if ( !xPackage.is() )
985         return;
986 
987     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
988     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
989     OUString sTitle(
990         m_sEnablingPackages.replaceAll("%EXTENSION_NAME",
991             xPackage->getDisplayName()));
992     rCmdEnv->progressSection( sTitle, xAbortChannel );
993 
994     try
995     {
996         xExtMgr->enableExtension( xPackage, xAbortChannel, rCmdEnv.get() );
997         if ( m_pDialogHelper )
998             m_pDialogHelper->updatePackageInfo( xPackage );
999     }
1000     catch ( const ::ucb::CommandAbortedException & )
1001     {}
1002 }
1003 
1004 
_disableExtension(::rtl::Reference<ProgressCmdEnv> const & rCmdEnv,const uno::Reference<deployment::XPackage> & xPackage)1005 void ExtensionCmdQueue::Thread::_disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
1006                                                    const uno::Reference< deployment::XPackage > &xPackage )
1007 {
1008     if ( !xPackage.is() )
1009         return;
1010 
1011     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
1012     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
1013     OUString sTitle(
1014         m_sDisablingPackages.replaceAll("%EXTENSION_NAME",
1015             xPackage->getDisplayName()));
1016     rCmdEnv->progressSection( sTitle, xAbortChannel );
1017 
1018     try
1019     {
1020         xExtMgr->disableExtension( xPackage, xAbortChannel, rCmdEnv.get() );
1021         if ( m_pDialogHelper )
1022             m_pDialogHelper->updatePackageInfo( xPackage );
1023     }
1024     catch ( const ::ucb::CommandAbortedException & )
1025     {}
1026 }
1027 
1028 
_acceptLicense(::rtl::Reference<ProgressCmdEnv> const & rCmdEnv,const uno::Reference<deployment::XPackage> & xPackage)1029 void ExtensionCmdQueue::Thread::_acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv,
1030                                                 const uno::Reference< deployment::XPackage > &xPackage )
1031 {
1032     if ( !xPackage.is() )
1033         return;
1034 
1035     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
1036     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
1037     OUString sTitle(
1038         m_sAcceptLicense.replaceAll("%EXTENSION_NAME",
1039             xPackage->getDisplayName()));
1040     rCmdEnv->progressSection( sTitle, xAbortChannel );
1041 
1042     try
1043     {
1044         xExtMgr->checkPrerequisitesAndEnable( xPackage, xAbortChannel, rCmdEnv.get() );
1045         if ( m_pDialogHelper )
1046             m_pDialogHelper->updatePackageInfo( xPackage );
1047     }
1048     catch ( const ::ucb::CommandAbortedException & )
1049     {}
1050 }
1051 
_insert(const TExtensionCmd & rExtCmd)1052 void ExtensionCmdQueue::Thread::_insert(const TExtensionCmd& rExtCmd)
1053 {
1054     ::osl::MutexGuard aGuard( m_mutex );
1055 
1056     // If someone called stop then we do not process the command -> game over!
1057     if ( m_bStopped )
1058         return;
1059 
1060     m_queue.push( rExtCmd );
1061     m_eInput = START;
1062     m_wakeup.set();
1063 }
1064 
1065 
ExtensionCmdQueue(DialogHelper * pDialogHelper,TheExtensionManager * pManager,const uno::Reference<uno::XComponentContext> & rContext)1066 ExtensionCmdQueue::ExtensionCmdQueue( DialogHelper * pDialogHelper,
1067                                       TheExtensionManager *pManager,
1068                                       const uno::Reference< uno::XComponentContext > &rContext )
1069   : m_thread( new Thread( pDialogHelper, pManager, rContext ) )
1070 {
1071     m_thread->launch();
1072 }
1073 
~ExtensionCmdQueue()1074 ExtensionCmdQueue::~ExtensionCmdQueue() {
1075     stop();
1076 }
1077 
addExtension(const OUString & extensionURL,const OUString & repository,const bool bWarnUser)1078 void ExtensionCmdQueue::addExtension( const OUString & extensionURL,
1079                                       const OUString & repository,
1080                                       const bool bWarnUser )
1081 {
1082     m_thread->addExtension( extensionURL, repository, bWarnUser );
1083 }
1084 
removeExtension(const uno::Reference<deployment::XPackage> & rPackage)1085 void ExtensionCmdQueue::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
1086 {
1087     m_thread->removeExtension( rPackage );
1088 }
1089 
enableExtension(const uno::Reference<deployment::XPackage> & rPackage,const bool bEnable)1090 void ExtensionCmdQueue::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
1091                                          const bool bEnable )
1092 {
1093     m_thread->enableExtension( rPackage, bEnable );
1094 }
1095 
checkForUpdates(const std::vector<uno::Reference<deployment::XPackage>> & vExtensionList)1096 void ExtensionCmdQueue::checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
1097 {
1098     m_thread->checkForUpdates( vExtensionList );
1099 }
1100 
acceptLicense(const uno::Reference<deployment::XPackage> & rPackage)1101 void ExtensionCmdQueue::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
1102 {
1103     m_thread->acceptLicense( rPackage );
1104 }
1105 
syncRepositories(const uno::Reference<uno::XComponentContext> & xContext)1106 void ExtensionCmdQueue::syncRepositories( const uno::Reference< uno::XComponentContext > &xContext )
1107 {
1108     dp_misc::syncRepositories( false, new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) );
1109 }
1110 
stop()1111 void ExtensionCmdQueue::stop()
1112 {
1113     m_thread->stop();
1114 }
1115 
isBusy()1116 bool ExtensionCmdQueue::isBusy()
1117 {
1118     return m_thread->isBusy();
1119 }
1120 
handleInteractionRequest(const uno::Reference<uno::XComponentContext> & xContext,const uno::Reference<task::XInteractionRequest> & xRequest)1121 void handleInteractionRequest( const uno::Reference< uno::XComponentContext > & xContext,
1122                                const uno::Reference< task::XInteractionRequest > & xRequest )
1123 {
1124     ::rtl::Reference< ProgressCmdEnv > xCmdEnv( new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) );
1125     xCmdEnv->handle( xRequest );
1126 }
1127 
1128 } //namespace dp_gui
1129 
1130 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1131