use insert function instead of for loop
[LibreOffice.git] / desktop / source / deployment / gui / dp_gui_updateinstalldialog.cxx
blob7de11a2dadb00824c689477f661a97eb419e2bcc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
27 #include <utility>
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>
47 #include <dp_ucb.h>
48 #include <dp_misc.h>
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>
58 #include <vector>
60 using dp_misc::StrTitle;
62 namespace dp_gui {
64 class UpdateInstallDialog::Thread: public salhelper::Thread {
65 friend class UpdateCommandEnv;
66 public:
67 Thread(css::uno::Reference< css::uno::XComponentContext > const & ctx,
68 UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData);
70 void stop();
72 private:
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;
92 bool m_stop;
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;
106 public:
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;
120 // XProgressHandler
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"),
132 m_dialog(dialog),
133 m_xComponentContext(xCtx),
134 m_aVecUpdateData(aVecUpdateData),
135 m_updateCmdEnv(new UpdateCommandEnv(xCtx, this)),
136 m_stop(false)
139 void UpdateInstallDialog::Thread::stop() {
140 css::uno::Reference< css::task::XAbortChannel > abort;
142 SolarMutexGuard g;
143 abort = m_abort;
144 m_stop = true;
146 if (abort.is()) {
147 abort->sendAbort();
151 UpdateInstallDialog::Thread::~Thread() {}
153 void UpdateInstallDialog::Thread::execute()
155 try {
156 downloadExtensions();
157 installExtensions();
159 catch (...)
163 //clean up the temp directories
164 try {
165 removeTempDownloads();
166 } catch( ... ) {
170 //make sure m_dialog is still alive
171 SolarMutexGuard g;
172 if (! m_stop)
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))
186 , m_bError(false)
187 , m_bNoEntry(true)
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()
220 m_thread->launch();
221 short nRet = GenericDialogController::run();
222 m_thread->stop();
223 return nRet;
226 // make sure the solar mutex is locked before calling
227 void UpdateInstallDialog::updateDone()
229 if (!m_bError)
230 m_xMle_info->set_text(m_xMle_info->get_text() + m_sNoErrors);
231 m_xOk->set_sensitive(true);
232 m_xOk->grab_focus();
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)
241 OUString sError;
242 m_bError = true;
244 switch (err)
246 case ERROR_DOWNLOAD:
247 sError = m_sErrorDownload;
248 break;
249 case ERROR_INSTALLATION:
250 sError = m_sErrorInstallation;
251 break;
252 case ERROR_LICENSE_DECLINED:
253 sError = m_sErrorLicenseDeclined;
254 break;
256 default:
257 OSL_ASSERT(false);
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.
264 if (m_bNoEntry)
265 m_bNoEntry = false;
266 else
267 sMsg += "\n";
268 sMsg += sError;
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)
280 m_bError = true;
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
294 OUString sTempDir;
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",
315 nullptr, anyEx );
319 sal_uInt16 count = 0;
320 for (auto & updateData : m_aVecUpdateData)
322 if (!updateData.aUpdateInfo.is() || updateData.aUpdateSource.is())
323 continue;
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
330 SolarMutexGuard g;
331 if (m_stop) {
332 return;
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())
351 break;
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.
359 continue;
362 //update the progress and display download error
364 SolarMutexGuard g;
365 if (m_stop) {
366 return;
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);
372 size_t nPos = 0;
373 for (auto const& elem : vecExceptions)
375 if (nPos)
376 buf.append("\n");
377 buf.append("Could not download " + elem.first + ". " + elem.second.Message);
378 ++nPos;
380 m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, updateData.aInstalledPackage->getDisplayName(),
381 buf);
387 catch (const css::uno::Exception & e)
389 SolarMutexGuard g;
390 if (m_stop) {
391 return;
393 m_dialog.setError(e.Message);
397 void UpdateInstallDialog::Thread::installExtensions()
399 //Update the fix text in the dialog to "Installing extensions..."
401 SolarMutexGuard g;
402 if (m_stop) {
403 return;
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
414 SolarMutexGuard g;
415 if (m_stop) {
416 return;
418 //we only show progress after an extension has been installed.
419 if (count > 0) {
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());
426 bool bError = false;
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() );
435 SolarMutexGuard g;
436 if (m_stop) {
437 return;
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);
448 else
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);
465 else
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;
477 else
479 exc = de.Cause.get<css::uno::Exception>();
480 bError = true;
483 catch (css::uno::Exception& e)
485 exc = e;
486 bError = true;
489 if (bLicenseDeclined)
491 SolarMutexGuard g;
492 if (m_stop) {
493 return;
495 m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED,
496 updateData.aInstalledPackage->getDisplayName(), std::u16string_view());
498 else if (!xExtension.is() || bError)
500 SolarMutexGuard g;
501 if (m_stop) {
502 return;
504 m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION,
505 updateData.aInstalledPackage->getDisplayName(), exc.Message);
507 ++count;
510 SolarMutexGuard g;
511 if (m_stop) {
512 return;
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)
536 SolarMutexGuard g;
537 if (m_stop) {
538 return m_stop;
542 OSL_ASSERT(m_sDownloadFolder.getLength());
543 OUString destFolder, tempEntry;
544 if (::osl::File::createTempFile(
545 &m_sDownloadFolder,
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
569 SolarMutexGuard g;
570 if (m_stop) {
571 return m_stop;
573 //all errors should be handled by the command environment.
574 aUpdateData.sLocalURL = destFolder + "/" + sTitle;
577 return m_stop;
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()
590 return this;
593 css::uno::Reference<css::ucb::XProgressHandler> UpdateCommandEnv::getProgressHandler()
595 return this;
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
615 //new version.
616 approve = true;
619 if (!approve)
621 //forward to interaction handler for main dialog.
622 handleInteractionRequest( m_xContext, xRequest );
624 else
626 // select:
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:
634 break;
640 // XProgressHandler
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: */