1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include "dp_gui_updatedata.hxx"
23 #include <sal/config.h>
24 #include <osl/file.hxx>
25 #include <osl/conditn.hxx>
26 #include <cppuhelper/exc_hlp.hxx>
27 #include <vcl/svapp.hxx>
28 #include <cppuhelper/implbase.hxx>
30 #include <com/sun/star/beans/PropertyValue.hpp>
31 #include <com/sun/star/beans/NamedValue.hpp>
32 #include <com/sun/star/xml/dom/XElement.hpp>
33 #include <com/sun/star/xml/dom/XNode.hpp>
34 #include <com/sun/star/xml/dom/XNodeList.hpp>
35 #include <com/sun/star/ucb/NameClash.hpp>
36 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
37 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
38 #include <com/sun/star/ucb/XProgressHandler.hpp>
39 #include <com/sun/star/deployment/DeploymentException.hpp>
40 #include <com/sun/star/deployment/XExtensionManager.hpp>
41 #include <com/sun/star/deployment/ExtensionManager.hpp>
42 #include <com/sun/star/deployment/XUpdateInformationProvider.hpp>
43 #include <com/sun/star/deployment/DependencyException.hpp>
44 #include <com/sun/star/deployment/LicenseException.hpp>
45 #include <com/sun/star/deployment/VersionException.hpp>
46 #include <com/sun/star/deployment/ui/LicenseDialog.hpp>
47 #include <com/sun/star/task/XInteractionHandler.hpp>
48 #include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
49 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
50 #include <com/sun/star/task/XInteractionAbort.hpp>
51 #include <com/sun/star/task/XInteractionApprove.hpp>
53 #include <dp_descriptioninfoset.hxx>
54 #include <strings.hrc>
55 #include "dp_gui_updateinstalldialog.hxx"
56 #include <dp_shared.hxx>
59 #include <dp_version.hxx>
60 #include "dp_gui_extensioncmdqueue.hxx"
61 #include <ucbhelper/content.hxx>
62 #include <rtl/ref.hxx>
63 #include <salhelper/thread.hxx>
64 #include <com/sun/star/uno/Sequence.h>
65 #include <comphelper/anytostring.hxx>
69 namespace cssu
= ::com::sun::star::uno
;
71 using dp_misc::StrTitle
;
75 class UpdateInstallDialog::Thread
: public salhelper::Thread
{
76 friend class UpdateCommandEnv
;
78 Thread(cssu::Reference
< cssu::XComponentContext
> const & ctx
,
79 UpdateInstallDialog
& dialog
, std::vector
< dp_gui::UpdateData
> & aVecUpdateData
);
84 virtual ~Thread() override
;
86 virtual void execute() override
;
87 void downloadExtensions();
88 bool download(OUString
const & aUrls
, UpdateData
& aUpdatData
);
89 void installExtensions();
90 void removeTempDownloads();
92 UpdateInstallDialog
& m_dialog
;
94 // guarded by Application::GetSolarMutex():
95 cssu::Reference
< css::task::XAbortChannel
> m_abort
;
96 cssu::Reference
< cssu::XComponentContext
> m_xComponentContext
;
97 std::vector
< dp_gui::UpdateData
> & m_aVecUpdateData
;
98 ::rtl::Reference
<UpdateCommandEnv
> m_updateCmdEnv
;
100 //A folder which is created in the temp directory in which then the updates are downloaded
101 OUString m_sDownloadFolder
;
107 class UpdateCommandEnv
108 : public ::cppu::WeakImplHelper
< css::ucb::XCommandEnvironment
,
109 css::task::XInteractionHandler
,
110 css::ucb::XProgressHandler
>
112 friend class UpdateInstallDialog::Thread
;
114 ::rtl::Reference
<UpdateInstallDialog::Thread
> m_installThread
;
115 cssu::Reference
< cssu::XComponentContext
> m_xContext
;
118 UpdateCommandEnv( cssu::Reference
< cssu::XComponentContext
> const & xCtx
,
119 ::rtl::Reference
<UpdateInstallDialog::Thread
>const & thread
);
121 // XCommandEnvironment
122 virtual cssu::Reference
<css::task::XInteractionHandler
> SAL_CALL
123 getInteractionHandler() override
;
124 virtual cssu::Reference
<css::ucb::XProgressHandler
>
125 SAL_CALL
getProgressHandler() override
;
127 // XInteractionHandler
128 virtual void SAL_CALL
handle(
129 cssu::Reference
<css::task::XInteractionRequest
> const & xRequest
) override
;
132 virtual void SAL_CALL
push( cssu::Any
const & Status
) override
;
133 virtual void SAL_CALL
update( cssu::Any
const & Status
) override
;
134 virtual void SAL_CALL
pop() override
;
138 UpdateInstallDialog::Thread::Thread(
139 cssu::Reference
< cssu::XComponentContext
> const & xCtx
,
140 UpdateInstallDialog
& dialog
,
141 std::vector
< dp_gui::UpdateData
> & aVecUpdateData
):
142 salhelper::Thread("dp_gui_updateinstalldialog"),
144 m_xComponentContext(xCtx
),
145 m_aVecUpdateData(aVecUpdateData
),
146 m_updateCmdEnv(new UpdateCommandEnv(xCtx
, this)),
150 void UpdateInstallDialog::Thread::stop() {
151 cssu::Reference
< css::task::XAbortChannel
> abort
;
162 UpdateInstallDialog::Thread::~Thread() {}
164 void UpdateInstallDialog::Thread::execute()
167 downloadExtensions();
174 //clean up the temp directories
176 removeTempDownloads();
181 //make sure m_dialog is still alive
184 m_dialog
.updateDone();
186 //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it.
187 m_updateCmdEnv
->m_installThread
.clear();
190 UpdateInstallDialog::UpdateInstallDialog(
191 weld::Window
* pParent
,
192 std::vector
<dp_gui::UpdateData
> & aVecUpdateData
,
193 cssu::Reference
< cssu::XComponentContext
> const & xCtx
)
194 : GenericDialogController(pParent
, "desktop/ui/updateinstalldialog.ui",
195 "UpdateInstallDialog")
196 , m_thread(new Thread(xCtx
, *this, aVecUpdateData
))
199 , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING
))
200 , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED
))
201 , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS
))
202 , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD
))
203 , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION
))
204 , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED
))
205 , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL
))
206 , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED
))
207 , m_xFt_action(m_xBuilder
->weld_label("DOWNLOADING"))
208 , m_xStatusbar(m_xBuilder
->weld_progress_bar("STATUSBAR"))
209 , m_xFt_extension_name(m_xBuilder
->weld_label("EXTENSION_NAME"))
210 , m_xMle_info(m_xBuilder
->weld_text_view("INFO"))
211 , m_xHelp(m_xBuilder
->weld_button("help"))
212 , m_xOk(m_xBuilder
->weld_button("ok"))
213 , m_xCancel(m_xBuilder
->weld_button("cancel"))
215 m_xMle_info
->set_size_request(m_xMle_info
->get_approximate_digit_width() * 52,
216 m_xMle_info
->get_height_rows(5));
218 m_xExtensionManager
= css::deployment::ExtensionManager::get( xCtx
);
220 m_xCancel
->connect_clicked(LINK(this, UpdateInstallDialog
, cancelHandler
));
221 if ( ! dp_misc::office_is_running())
222 m_xHelp
->set_sensitive(false);
225 UpdateInstallDialog::~UpdateInstallDialog()
229 short UpdateInstallDialog::run()
232 short nRet
= GenericDialogController::run();
237 // make sure the solar mutex is locked before calling
238 void UpdateInstallDialog::updateDone()
241 m_xMle_info
->set_text(m_xMle_info
->get_text() + m_sNoErrors
);
242 m_xOk
->set_sensitive(true);
244 m_xCancel
->set_sensitive(false);
247 // make sure the solar mutex is locked before calling
248 //sets an error message in the text area
249 void UpdateInstallDialog::setError(INSTALL_ERROR err
, OUString
const & sExtension
,
250 OUString
const & exceptionMessage
)
258 sError
= m_sErrorDownload
;
260 case ERROR_INSTALLATION
:
261 sError
= m_sErrorInstallation
;
263 case ERROR_LICENSE_DECLINED
:
264 sError
= m_sErrorLicenseDeclined
;
271 OUString
sMsg(m_xMle_info
->get_text());
272 sError
= sError
.replaceFirst("%NAME", sExtension
);
273 //We want to have an empty line between the error messages. However,
274 //there shall be no empty line after the last entry.
280 //Insert more information about the error
281 if (!exceptionMessage
.isEmpty())
282 sMsg
+= m_sThisErrorOccurred
+ exceptionMessage
+ "\n";
284 sMsg
+= m_sNoInstall
+ "\n";
286 m_xMle_info
->set_text(sMsg
);
289 void UpdateInstallDialog::setError(OUString
const & exceptionMessage
)
292 m_xMle_info
->set_text(m_xMle_info
->get_text() + exceptionMessage
+ "\n");
295 IMPL_LINK_NOARG(UpdateInstallDialog
, cancelHandler
, weld::Button
&, void)
297 m_xDialog
->response(RET_CANCEL
);
300 void UpdateInstallDialog::Thread::downloadExtensions()
304 //create the download directory in the temp folder
306 if (::osl::FileBase::getTempDirURL(sTempDir
) != ::osl::FileBase::E_None
)
307 throw cssu::Exception("Could not get URL for the temp directory. No extensions will be installed.", nullptr);
309 //create a unique name for the directory
310 OUString tempEntry
, destFolder
;
311 if (::osl::File::createTempFile(&sTempDir
, nullptr, &tempEntry
) != ::osl::File::E_None
)
312 throw cssu::Exception("Could not create a temporary file in " + sTempDir
+
313 ". No extensions will be installed", nullptr );
315 tempEntry
= tempEntry
.copy( tempEntry
.lastIndexOf( '/' ) + 1 );
317 destFolder
= dp_misc::makeURL( sTempDir
, tempEntry
) + "_";
318 m_sDownloadFolder
= destFolder
;
321 dp_misc::create_folder(nullptr, destFolder
, m_updateCmdEnv
.get() );
322 } catch (const cssu::Exception
& e
)
324 css::uno::Any anyEx
= cppu::getCaughtException();
325 throw css::lang::WrappedTargetException( e
.Message
+ " No extensions will be installed",
330 sal_uInt16 count
= 0;
331 for (auto & updateData
: m_aVecUpdateData
)
333 if (!updateData
.aUpdateInfo
.is() || updateData
.aUpdateSource
.is())
335 //We assume that m_aVecUpdateData contains only information about extensions which
336 //can be downloaded directly.
337 OSL_ASSERT(updateData
.sWebsiteURL
.isEmpty());
339 //update the name of the extension which is to be downloaded
345 m_dialog
.m_xFt_extension_name
->set_label(updateData
.aInstalledPackage
->getDisplayName());
346 sal_uInt16 prog
= (sal::static_int_cast
<sal_uInt16
>(100) * ++count
) /
347 sal::static_int_cast
<sal_uInt16
>(m_aVecUpdateData
.size());
348 m_dialog
.m_xStatusbar
->set_percentage(prog
);
350 dp_misc::DescriptionInfoset
info(m_xComponentContext
, updateData
.aUpdateInfo
);
351 //remember occurring exceptions in case we need to print out error information
352 std::vector
< std::pair
<OUString
, cssu::Exception
> > vecExceptions
;
353 cssu::Sequence
<OUString
> seqDownloadURLs
= info
.getUpdateDownloadUrls();
354 OSL_ENSURE(seqDownloadURLs
.hasElements(), "No download URL provided!");
355 for (sal_Int32 j
= 0; j
< seqDownloadURLs
.getLength(); j
++)
359 OSL_ENSURE(!seqDownloadURLs
[j
].isEmpty(), "Download URL is empty!");
360 bool bCancelled
= download(seqDownloadURLs
[j
], updateData
);
361 if (bCancelled
|| !updateData
.sLocalURL
.isEmpty())
364 catch ( cssu::Exception
& e
)
366 vecExceptions
.emplace_back(seqDownloadURLs
[j
], e
);
367 //There can be several different errors, for example, the URL is wrong, webserver cannot be reached,
368 //name cannot be resolved. The UCB helper API does not specify different special exceptions for these
369 //cases. Therefore ignore and continue.
373 //update the progress and display download error
379 if (updateData
.sLocalURL
.isEmpty())
381 //Construct a string of all messages contained in the exceptions plus the respective download URLs
382 OUStringBuffer
buf(256);
384 for (auto const& elem
: vecExceptions
)
388 buf
.append("Could not download ");
389 buf
.append(elem
.first
);
391 buf
.append(elem
.second
.Message
);
394 m_dialog
.setError(UpdateInstallDialog::ERROR_DOWNLOAD
, updateData
.aInstalledPackage
->getDisplayName(),
395 buf
.makeStringAndClear());
401 catch (const cssu::Exception
& e
)
407 m_dialog
.setError(e
.Message
);
411 void UpdateInstallDialog::Thread::installExtensions()
413 //Update the fix text in the dialog to "Installing extensions..."
419 m_dialog
.m_xFt_action
->set_label(m_dialog
.m_sInstalling
);
420 m_dialog
.m_xStatusbar
->set_percentage(0);
423 sal_uInt16 count
= 0;
424 for (auto const& updateData
: m_aVecUpdateData
)
426 //update the name of the extension which is to be installed
432 //we only show progress after an extension has been installed.
434 m_dialog
.m_xStatusbar
->set_percentage(
435 (sal::static_int_cast
<sal_uInt16
>(100) * count
) /
436 sal::static_int_cast
<sal_uInt16
>(m_aVecUpdateData
.size()));
438 m_dialog
.m_xFt_extension_name
->set_label(updateData
.aInstalledPackage
->getDisplayName());
441 bool bLicenseDeclined
= false;
442 cssu::Reference
<css::deployment::XPackage
> xExtension
;
446 cssu::Reference
< css::task::XAbortChannel
> xAbortChannel(
447 updateData
.aInstalledPackage
->createAbortChannel() );
453 m_abort
= xAbortChannel
;
455 if (!updateData
.aUpdateSource
.is() && !updateData
.sLocalURL
.isEmpty())
457 css::beans::NamedValue
prop("EXTENSION_UPDATE", css::uno::makeAny(OUString("1")));
458 if (!updateData
.bIsShared
)
459 xExtension
= m_dialog
.getExtensionManager()->addExtension(
460 updateData
.sLocalURL
, css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
461 "user", xAbortChannel
, m_updateCmdEnv
.get());
463 xExtension
= m_dialog
.getExtensionManager()->addExtension(
464 updateData
.sLocalURL
, css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
465 "shared", xAbortChannel
, m_updateCmdEnv
.get());
467 else if (updateData
.aUpdateSource
.is())
469 OSL_ASSERT(updateData
.aUpdateSource
.is());
470 //I am not sure if we should obtain the install properties and pass them into
471 //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen
472 //that a license is displayed when updating from the shared repository, although the
473 //shared extension was installed using "SUPPRESS_LICENSE".
474 css::beans::NamedValue
prop("EXTENSION_UPDATE", css::uno::makeAny(OUString("1")));
475 if (!updateData
.bIsShared
)
476 xExtension
= m_dialog
.getExtensionManager()->addExtension(
477 updateData
.aUpdateSource
->getURL(), css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
478 "user", xAbortChannel
, m_updateCmdEnv
.get());
480 xExtension
= m_dialog
.getExtensionManager()->addExtension(
481 updateData
.aUpdateSource
->getURL(), css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
482 "shared", xAbortChannel
, m_updateCmdEnv
.get());
485 catch (css::deployment::DeploymentException
& de
)
487 if (de
.Cause
.has
<css::deployment::LicenseException
>())
489 bLicenseDeclined
= true;
493 exc
= de
.Cause
.get
<cssu::Exception
>();
497 catch (cssu::Exception
& e
)
503 if (bLicenseDeclined
)
509 m_dialog
.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED
,
510 updateData
.aInstalledPackage
->getDisplayName(), OUString());
512 else if (!xExtension
.is() || bError
)
518 m_dialog
.setError(UpdateInstallDialog::ERROR_INSTALLATION
,
519 updateData
.aInstalledPackage
->getDisplayName(), exc
.Message
);
528 m_dialog
.m_xStatusbar
->set_percentage(100);
529 m_dialog
.m_xFt_extension_name
->set_label(OUString());
530 m_dialog
.m_xFt_action
->set_label(m_dialog
.m_sFinished
);
534 void UpdateInstallDialog::Thread::removeTempDownloads()
536 if (!m_sDownloadFolder
.isEmpty())
538 dp_misc::erase_path(m_sDownloadFolder
,
539 cssu::Reference
<css::ucb::XCommandEnvironment
>(),false /* no throw: ignore errors */ );
540 //remove also the temp file which we have used to create the unique name
541 OUString tempFile
= m_sDownloadFolder
.copy(0, m_sDownloadFolder
.getLength() - 1);
542 dp_misc::erase_path(tempFile
, cssu::Reference
<css::ucb::XCommandEnvironment
>(),false);
543 m_sDownloadFolder
.clear();
547 bool UpdateInstallDialog::Thread::download(OUString
const & sDownloadURL
, UpdateData
& aUpdateData
)
556 OSL_ASSERT(m_sDownloadFolder
.getLength());
557 OUString destFolder
, tempEntry
;
558 if (::osl::File::createTempFile(
560 nullptr, &tempEntry
) != ::osl::File::E_None
)
562 //ToDo feedback in window that download of this component failed
563 throw cssu::Exception("Could not create temporary file in folder " + destFolder
+ ".", nullptr);
565 tempEntry
= tempEntry
.copy( tempEntry
.lastIndexOf( '/' ) + 1 );
567 destFolder
= dp_misc::makeURL( m_sDownloadFolder
, tempEntry
) + "_";
569 ::ucbhelper::Content destFolderContent
;
570 dp_misc::create_folder( &destFolderContent
, destFolder
, m_updateCmdEnv
.get() );
572 ::ucbhelper::Content sourceContent
;
573 (void)dp_misc::create_ucb_content(&sourceContent
, sDownloadURL
, m_updateCmdEnv
.get());
575 const OUString
sTitle( StrTitle::getTitle( sourceContent
) );
577 destFolderContent
.transferContent(
578 sourceContent
, ::ucbhelper::InsertOperation::Copy
,
579 sTitle
, css::ucb::NameClash::OVERWRITE
);
582 //the user may have cancelled the dialog because downloading took too long
587 //all errors should be handled by the command environment.
588 aUpdateData
.sLocalURL
= destFolder
+ "/" + sTitle
;
594 UpdateCommandEnv::UpdateCommandEnv( cssu::Reference
< cssu::XComponentContext
> const & xCtx
,
595 ::rtl::Reference
<UpdateInstallDialog::Thread
>const & thread
)
596 : m_installThread(thread
),
601 // XCommandEnvironment
602 cssu::Reference
<css::task::XInteractionHandler
> UpdateCommandEnv::getInteractionHandler()
607 cssu::Reference
<css::ucb::XProgressHandler
> UpdateCommandEnv::getProgressHandler()
612 // XInteractionHandler
613 void UpdateCommandEnv::handle(
614 cssu::Reference
< css::task::XInteractionRequest
> const & xRequest
)
616 cssu::Any
request( xRequest
->getRequest() );
617 OSL_ASSERT( request
.getValueTypeClass() == cssu::TypeClass_EXCEPTION
);
618 dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n"
619 + ::comphelper::anyToString(request
) + "\n\n");
621 css::deployment::VersionException verExc
;
622 bool approve
= false;
624 if (request
>>= verExc
)
625 { //We must catch the version exception during the update,
626 //because otherwise the user would be confronted with the dialogs, asking
627 //them if they want to replace an already installed version of the same extension.
628 //During an update we assume that we always want to replace the old version with the
635 //forward to interaction handler for main dialog.
636 handleInteractionRequest( m_xContext
, xRequest
);
641 cssu::Sequence
< cssu::Reference
< css::task::XInteractionContinuation
> > conts(
642 xRequest
->getContinuations() );
643 cssu::Reference
< css::task::XInteractionContinuation
> const * pConts
=
644 conts
.getConstArray();
645 sal_Int32 len
= conts
.getLength();
646 for ( sal_Int32 pos
= 0; pos
< len
; ++pos
)
649 cssu::Reference
< css::task::XInteractionApprove
> xInteractionApprove(
650 pConts
[ pos
], cssu::UNO_QUERY
);
651 if (xInteractionApprove
.is()) {
652 xInteractionApprove
->select();
653 // don't query again for ongoing continuations:
662 void UpdateCommandEnv::push( cssu::Any
const & /*Status*/ )
666 void UpdateCommandEnv::update( cssu::Any
const & /*Status */)
670 void UpdateCommandEnv::pop()
675 } //end namespace dp_gui
677 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */