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/diagnose.h>
25 #include <osl/file.hxx>
26 #include <cppuhelper/exc_hlp.hxx>
28 #include <vcl/svapp.hxx>
29 #include <cppuhelper/implbase.hxx>
31 #include <com/sun/star/beans/NamedValue.hpp>
32 #include <com/sun/star/lang/WrappedTargetException.hpp>
33 #include <com/sun/star/ucb/NameClash.hpp>
34 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
35 #include <com/sun/star/ucb/XProgressHandler.hpp>
36 #include <com/sun/star/deployment/DeploymentException.hpp>
37 #include <com/sun/star/deployment/ExtensionManager.hpp>
38 #include <com/sun/star/deployment/LicenseException.hpp>
39 #include <com/sun/star/deployment/VersionException.hpp>
40 #include <com/sun/star/task/XInteractionHandler.hpp>
41 #include <com/sun/star/task/XInteractionApprove.hpp>
43 #include <dp_descriptioninfoset.hxx>
44 #include <strings.hrc>
45 #include "dp_gui_updateinstalldialog.hxx"
46 #include <dp_shared.hxx>
49 #include "dp_gui_extensioncmdqueue.hxx"
50 #include <ucbhelper/content.hxx>
51 #include <rtl/ustrbuf.hxx>
52 #include <rtl/ref.hxx>
53 #include <salhelper/thread.hxx>
54 #include <com/sun/star/uno/Sequence.h>
55 #include <comphelper/anytostring.hxx>
57 #include <string_view>
60 using dp_misc::StrTitle
;
64 class UpdateInstallDialog::Thread
: public salhelper::Thread
{
65 friend class UpdateCommandEnv
;
67 Thread(css::uno::Reference
< css::uno::XComponentContext
> const & ctx
,
68 UpdateInstallDialog
& dialog
, std::vector
< dp_gui::UpdateData
> & aVecUpdateData
);
73 virtual ~Thread() override
;
75 virtual void execute() override
;
76 void downloadExtensions();
77 bool download(OUString
const & aUrls
, UpdateData
& aUpdatData
);
78 void installExtensions();
79 void removeTempDownloads();
81 UpdateInstallDialog
& m_dialog
;
83 // guarded by Application::GetSolarMutex():
84 css::uno::Reference
< css::task::XAbortChannel
> m_abort
;
85 css::uno::Reference
< css::uno::XComponentContext
> m_xComponentContext
;
86 std::vector
< dp_gui::UpdateData
> & m_aVecUpdateData
;
87 ::rtl::Reference
<UpdateCommandEnv
> m_updateCmdEnv
;
89 //A folder which is created in the temp directory in which then the updates are downloaded
90 OUString m_sDownloadFolder
;
96 class UpdateCommandEnv
97 : public ::cppu::WeakImplHelper
< css::ucb::XCommandEnvironment
,
98 css::task::XInteractionHandler
,
99 css::ucb::XProgressHandler
>
101 friend class UpdateInstallDialog::Thread
;
103 ::rtl::Reference
<UpdateInstallDialog::Thread
> m_installThread
;
104 css::uno::Reference
< css::uno::XComponentContext
> m_xContext
;
107 UpdateCommandEnv( css::uno::Reference
< css::uno::XComponentContext
> xCtx
,
108 ::rtl::Reference
<UpdateInstallDialog::Thread
> thread
);
110 // XCommandEnvironment
111 virtual css::uno::Reference
<css::task::XInteractionHandler
> SAL_CALL
112 getInteractionHandler() override
;
113 virtual css::uno::Reference
<css::ucb::XProgressHandler
>
114 SAL_CALL
getProgressHandler() override
;
116 // XInteractionHandler
117 virtual void SAL_CALL
handle(
118 css::uno::Reference
<css::task::XInteractionRequest
> const & xRequest
) override
;
121 virtual void SAL_CALL
push( css::uno::Any
const & Status
) override
;
122 virtual void SAL_CALL
update( css::uno::Any
const & Status
) override
;
123 virtual void SAL_CALL
pop() override
;
127 UpdateInstallDialog::Thread::Thread(
128 css::uno::Reference
< css::uno::XComponentContext
> const & xCtx
,
129 UpdateInstallDialog
& dialog
,
130 std::vector
< dp_gui::UpdateData
> & aVecUpdateData
):
131 salhelper::Thread("dp_gui_updateinstalldialog"),
133 m_xComponentContext(xCtx
),
134 m_aVecUpdateData(aVecUpdateData
),
135 m_updateCmdEnv(new UpdateCommandEnv(xCtx
, this)),
139 void UpdateInstallDialog::Thread::stop() {
140 css::uno::Reference
< css::task::XAbortChannel
> abort
;
151 UpdateInstallDialog::Thread::~Thread() {}
153 void UpdateInstallDialog::Thread::execute()
156 downloadExtensions();
163 //clean up the temp directories
165 removeTempDownloads();
170 //make sure m_dialog is still alive
173 m_dialog
.updateDone();
175 //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it.
176 m_updateCmdEnv
->m_installThread
.clear();
179 UpdateInstallDialog::UpdateInstallDialog(
180 weld::Window
* pParent
,
181 std::vector
<dp_gui::UpdateData
> & aVecUpdateData
,
182 css::uno::Reference
< css::uno::XComponentContext
> const & xCtx
)
183 : GenericDialogController(pParent
, u
"desktop/ui/updateinstalldialog.ui"_ustr
,
184 u
"UpdateInstallDialog"_ustr
)
185 , m_thread(new Thread(xCtx
, *this, aVecUpdateData
))
188 , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING
))
189 , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED
))
190 , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS
))
191 , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD
))
192 , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION
))
193 , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED
))
194 , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL
))
195 , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED
))
196 , m_xFt_action(m_xBuilder
->weld_label(u
"DOWNLOADING"_ustr
))
197 , m_xStatusbar(m_xBuilder
->weld_progress_bar(u
"STATUSBAR"_ustr
))
198 , m_xFt_extension_name(m_xBuilder
->weld_label(u
"EXTENSION_NAME"_ustr
))
199 , m_xMle_info(m_xBuilder
->weld_text_view(u
"INFO"_ustr
))
200 , m_xHelp(m_xBuilder
->weld_button(u
"help"_ustr
))
201 , m_xOk(m_xBuilder
->weld_button(u
"ok"_ustr
))
202 , m_xCancel(m_xBuilder
->weld_button(u
"cancel"_ustr
))
204 m_xMle_info
->set_size_request(m_xMle_info
->get_approximate_digit_width() * 52,
205 m_xMle_info
->get_height_rows(5));
207 m_xExtensionManager
= css::deployment::ExtensionManager::get( xCtx
);
209 m_xCancel
->connect_clicked(LINK(this, UpdateInstallDialog
, cancelHandler
));
210 if ( ! dp_misc::office_is_running())
211 m_xHelp
->set_sensitive(false);
214 UpdateInstallDialog::~UpdateInstallDialog()
218 short UpdateInstallDialog::run()
221 short nRet
= GenericDialogController::run();
226 // make sure the solar mutex is locked before calling
227 void UpdateInstallDialog::updateDone()
230 m_xMle_info
->set_text(m_xMle_info
->get_text() + m_sNoErrors
);
231 m_xOk
->set_sensitive(true);
233 m_xCancel
->set_sensitive(false);
236 // make sure the solar mutex is locked before calling
237 //sets an error message in the text area
238 void UpdateInstallDialog::setError(INSTALL_ERROR err
, std::u16string_view sExtension
,
239 std::u16string_view exceptionMessage
)
247 sError
= m_sErrorDownload
;
249 case ERROR_INSTALLATION
:
250 sError
= m_sErrorInstallation
;
252 case ERROR_LICENSE_DECLINED
:
253 sError
= m_sErrorLicenseDeclined
;
260 OUString
sMsg(m_xMle_info
->get_text());
261 sError
= sError
.replaceFirst("%NAME", sExtension
);
262 //We want to have an empty line between the error messages. However,
263 //there shall be no empty line after the last entry.
269 //Insert more information about the error
270 if (!exceptionMessage
.empty())
271 sMsg
+= m_sThisErrorOccurred
+ exceptionMessage
+ "\n";
273 sMsg
+= m_sNoInstall
+ "\n";
275 m_xMle_info
->set_text(sMsg
);
278 void UpdateInstallDialog::setError(std::u16string_view exceptionMessage
)
281 m_xMle_info
->set_text(m_xMle_info
->get_text() + exceptionMessage
+ "\n");
284 IMPL_LINK_NOARG(UpdateInstallDialog
, cancelHandler
, weld::Button
&, void)
286 m_xDialog
->response(RET_CANCEL
);
289 void UpdateInstallDialog::Thread::downloadExtensions()
293 //create the download directory in the temp folder
295 if (::osl::FileBase::getTempDirURL(sTempDir
) != ::osl::FileBase::E_None
)
296 throw css::uno::Exception(u
"Could not get URL for the temp directory. No extensions will be installed."_ustr
, nullptr);
298 //create a unique name for the directory
299 OUString tempEntry
, destFolder
;
300 if (::osl::File::createTempFile(&sTempDir
, nullptr, &tempEntry
) != ::osl::File::E_None
)
301 throw css::uno::Exception("Could not create a temporary file in " + sTempDir
+
302 ". No extensions will be installed", nullptr );
304 tempEntry
= tempEntry
.copy( tempEntry
.lastIndexOf( '/' ) + 1 );
306 destFolder
= dp_misc::makeURL( sTempDir
, tempEntry
) + "_";
307 m_sDownloadFolder
= destFolder
;
310 dp_misc::create_folder(nullptr, destFolder
, m_updateCmdEnv
);
311 } catch (const css::uno::Exception
& e
)
313 css::uno::Any anyEx
= cppu::getCaughtException();
314 throw css::lang::WrappedTargetException( e
.Message
+ " No extensions will be installed",
319 sal_uInt16 count
= 0;
320 for (auto & updateData
: m_aVecUpdateData
)
322 if (!updateData
.aUpdateInfo
.is() || updateData
.aUpdateSource
.is())
324 //We assume that m_aVecUpdateData contains only information about extensions which
325 //can be downloaded directly.
326 OSL_ASSERT(updateData
.sWebsiteURL
.isEmpty());
328 //update the name of the extension which is to be downloaded
334 m_dialog
.m_xFt_extension_name
->set_label(updateData
.aInstalledPackage
->getDisplayName());
335 sal_uInt16 prog
= (sal::static_int_cast
<sal_uInt16
>(100) * ++count
) /
336 sal::static_int_cast
<sal_uInt16
>(m_aVecUpdateData
.size());
337 m_dialog
.m_xStatusbar
->set_percentage(prog
);
339 dp_misc::DescriptionInfoset
info(m_xComponentContext
, updateData
.aUpdateInfo
);
340 //remember occurring exceptions in case we need to print out error information
341 std::vector
< std::pair
<OUString
, css::uno::Exception
> > vecExceptions
;
342 css::uno::Sequence
<OUString
> seqDownloadURLs
= info
.getUpdateDownloadUrls();
343 OSL_ENSURE(seqDownloadURLs
.hasElements(), "No download URL provided!");
344 for (sal_Int32 j
= 0; j
< seqDownloadURLs
.getLength(); j
++)
348 OSL_ENSURE(!seqDownloadURLs
[j
].isEmpty(), "Download URL is empty!");
349 bool bCancelled
= download(seqDownloadURLs
[j
], updateData
);
350 if (bCancelled
|| !updateData
.sLocalURL
.isEmpty())
353 catch ( css::uno::Exception
& e
)
355 vecExceptions
.emplace_back(seqDownloadURLs
[j
], e
);
356 //There can be several different errors, for example, the URL is wrong, webserver cannot be reached,
357 //name cannot be resolved. The UCB helper API does not specify different special exceptions for these
358 //cases. Therefore ignore and continue.
362 //update the progress and display download error
368 if (updateData
.sLocalURL
.isEmpty())
370 //Construct a string of all messages contained in the exceptions plus the respective download URLs
371 OUStringBuffer
buf(256);
373 for (auto const& elem
: vecExceptions
)
377 buf
.append("Could not download " + elem
.first
+ ". " + elem
.second
.Message
);
380 m_dialog
.setError(UpdateInstallDialog::ERROR_DOWNLOAD
, updateData
.aInstalledPackage
->getDisplayName(),
387 catch (const css::uno::Exception
& e
)
393 m_dialog
.setError(e
.Message
);
397 void UpdateInstallDialog::Thread::installExtensions()
399 //Update the fix text in the dialog to "Installing extensions..."
405 m_dialog
.m_xFt_action
->set_label(m_dialog
.m_sInstalling
);
406 m_dialog
.m_xStatusbar
->set_percentage(0);
409 sal_uInt16 count
= 0;
410 for (auto const& updateData
: m_aVecUpdateData
)
412 //update the name of the extension which is to be installed
418 //we only show progress after an extension has been installed.
420 m_dialog
.m_xStatusbar
->set_percentage(
421 (sal::static_int_cast
<sal_uInt16
>(100) * count
) /
422 sal::static_int_cast
<sal_uInt16
>(m_aVecUpdateData
.size()));
424 m_dialog
.m_xFt_extension_name
->set_label(updateData
.aInstalledPackage
->getDisplayName());
427 bool bLicenseDeclined
= false;
428 css::uno::Reference
<css::deployment::XPackage
> xExtension
;
429 css::uno::Exception exc
;
432 css::uno::Reference
< css::task::XAbortChannel
> xAbortChannel(
433 updateData
.aInstalledPackage
->createAbortChannel() );
439 m_abort
= xAbortChannel
;
441 if (!updateData
.aUpdateSource
.is() && !updateData
.sLocalURL
.isEmpty())
443 css::beans::NamedValue
prop(u
"EXTENSION_UPDATE"_ustr
, css::uno::Any(u
"1"_ustr
));
444 if (!updateData
.bIsShared
)
445 xExtension
= m_dialog
.getExtensionManager()->addExtension(
446 updateData
.sLocalURL
, css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
447 u
"user"_ustr
, xAbortChannel
, m_updateCmdEnv
);
449 xExtension
= m_dialog
.getExtensionManager()->addExtension(
450 updateData
.sLocalURL
, css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
451 u
"shared"_ustr
, xAbortChannel
, m_updateCmdEnv
);
453 else if (updateData
.aUpdateSource
.is())
455 OSL_ASSERT(updateData
.aUpdateSource
.is());
456 //I am not sure if we should obtain the install properties and pass them into
457 //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen
458 //that a license is displayed when updating from the shared repository, although the
459 //shared extension was installed using "SUPPRESS_LICENSE".
460 css::beans::NamedValue
prop(u
"EXTENSION_UPDATE"_ustr
, css::uno::Any(u
"1"_ustr
));
461 if (!updateData
.bIsShared
)
462 xExtension
= m_dialog
.getExtensionManager()->addExtension(
463 updateData
.aUpdateSource
->getURL(), css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
464 u
"user"_ustr
, xAbortChannel
, m_updateCmdEnv
);
466 xExtension
= m_dialog
.getExtensionManager()->addExtension(
467 updateData
.aUpdateSource
->getURL(), css::uno::Sequence
<css::beans::NamedValue
>(&prop
, 1),
468 u
"shared"_ustr
, xAbortChannel
, m_updateCmdEnv
);
471 catch (css::deployment::DeploymentException
& de
)
473 if (de
.Cause
.has
<css::deployment::LicenseException
>())
475 bLicenseDeclined
= true;
479 exc
= de
.Cause
.get
<css::uno::Exception
>();
483 catch (css::uno::Exception
& e
)
489 if (bLicenseDeclined
)
495 m_dialog
.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED
,
496 updateData
.aInstalledPackage
->getDisplayName(), std::u16string_view());
498 else if (!xExtension
.is() || bError
)
504 m_dialog
.setError(UpdateInstallDialog::ERROR_INSTALLATION
,
505 updateData
.aInstalledPackage
->getDisplayName(), exc
.Message
);
514 m_dialog
.m_xStatusbar
->set_percentage(100);
515 m_dialog
.m_xFt_extension_name
->set_label(OUString());
516 m_dialog
.m_xFt_action
->set_label(m_dialog
.m_sFinished
);
520 void UpdateInstallDialog::Thread::removeTempDownloads()
522 if (!m_sDownloadFolder
.isEmpty())
524 dp_misc::erase_path(m_sDownloadFolder
,
525 css::uno::Reference
<css::ucb::XCommandEnvironment
>(),false /* no throw: ignore errors */ );
526 //remove also the temp file which we have used to create the unique name
527 OUString tempFile
= m_sDownloadFolder
.copy(0, m_sDownloadFolder
.getLength() - 1);
528 dp_misc::erase_path(tempFile
, css::uno::Reference
<css::ucb::XCommandEnvironment
>(),false);
529 m_sDownloadFolder
.clear();
533 bool UpdateInstallDialog::Thread::download(OUString
const & sDownloadURL
, UpdateData
& aUpdateData
)
542 OSL_ASSERT(m_sDownloadFolder
.getLength());
543 OUString destFolder
, tempEntry
;
544 if (::osl::File::createTempFile(
546 nullptr, &tempEntry
) != ::osl::File::E_None
)
548 //ToDo feedback in window that download of this component failed
549 throw css::uno::Exception("Could not create temporary file in folder " + destFolder
+ ".", nullptr);
551 tempEntry
= tempEntry
.copy( tempEntry
.lastIndexOf( '/' ) + 1 );
553 destFolder
= dp_misc::makeURL( m_sDownloadFolder
, tempEntry
) + "_";
555 ::ucbhelper::Content destFolderContent
;
556 dp_misc::create_folder( &destFolderContent
, destFolder
, m_updateCmdEnv
);
558 ::ucbhelper::Content sourceContent
;
559 (void)dp_misc::create_ucb_content(&sourceContent
, sDownloadURL
, m_updateCmdEnv
);
561 const OUString
sTitle( StrTitle::getTitle( sourceContent
) );
563 destFolderContent
.transferContent(
564 sourceContent
, ::ucbhelper::InsertOperation::Copy
,
565 sTitle
, css::ucb::NameClash::OVERWRITE
);
568 //the user may have cancelled the dialog because downloading took too long
573 //all errors should be handled by the command environment.
574 aUpdateData
.sLocalURL
= destFolder
+ "/" + sTitle
;
580 UpdateCommandEnv::UpdateCommandEnv( css::uno::Reference
< css::uno::XComponentContext
> xCtx
,
581 ::rtl::Reference
<UpdateInstallDialog::Thread
> thread
)
582 : m_installThread(std::move(thread
)),
583 m_xContext(std::move(xCtx
))
587 // XCommandEnvironment
588 css::uno::Reference
<css::task::XInteractionHandler
> UpdateCommandEnv::getInteractionHandler()
593 css::uno::Reference
<css::ucb::XProgressHandler
> UpdateCommandEnv::getProgressHandler()
598 // XInteractionHandler
599 void UpdateCommandEnv::handle(
600 css::uno::Reference
< css::task::XInteractionRequest
> const & xRequest
)
602 css::uno::Any
request( xRequest
->getRequest() );
603 OSL_ASSERT( request
.getValueTypeClass() == css::uno::TypeClass_EXCEPTION
);
604 dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n"
605 + ::comphelper::anyToString(request
) + "\n\n");
607 css::deployment::VersionException verExc
;
608 bool approve
= false;
610 if (request
>>= verExc
)
611 { //We must catch the version exception during the update,
612 //because otherwise the user would be confronted with the dialogs, asking
613 //them if they want to replace an already installed version of the same extension.
614 //During an update we assume that we always want to replace the old version with the
621 //forward to interaction handler for main dialog.
622 handleInteractionRequest( m_xContext
, xRequest
);
627 for (auto& cont
: xRequest
->getContinuations())
629 css::uno::Reference
< css::task::XInteractionApprove
> xInteractionApprove(
630 cont
, css::uno::UNO_QUERY
);
631 if (xInteractionApprove
.is()) {
632 xInteractionApprove
->select();
633 // don't query again for ongoing continuations:
641 void UpdateCommandEnv::push( css::uno::Any
const & /*Status*/ )
645 void UpdateCommandEnv::update( css::uno::Any
const & /*Status */)
649 void UpdateCommandEnv::pop()
654 } //end namespace dp_gui
656 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */