/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_desktop.hxx" #include "dp_gui_updatedata.hxx" #include "sal/config.h" #include "osl/file.hxx" #include "osl/conditn.hxx" #include "cppuhelper/exc_hlp.hxx" #include "tools/resid.hxx" #include "tools/resmgr.hxx" #include "tools/solar.h" #include "tools/string.hxx" #include "vcl/dialog.hxx" #include "vcl/msgbox.hxx" #include "vcl/svapp.hxx" #include "vos/mutex.hxx" #include "vcl/dialog.hxx" #include "cppuhelper/implbase3.hxx" #include "com/sun/star/beans/PropertyValue.hpp" #include "com/sun/star/beans/NamedValue.hpp" #include "com/sun/star/xml/dom/XElement.hpp" #include "com/sun/star/xml/dom/XNode.hpp" #include "com/sun/star/xml/dom/XNodeList.hpp" #include "com/sun/star/ucb/NameClash.hpp" #include "com/sun/star/ucb/InteractiveAugmentedIOException.hpp" #include "com/sun/star/ucb/XCommandEnvironment.hpp" #include "com/sun/star/ucb/XProgressHandler.hpp" #include "com/sun/star/deployment/XExtensionManager.hpp" #include "com/sun/star/deployment/ExtensionManager.hpp" #include "com/sun/star/deployment/XUpdateInformationProvider.hpp" #include "com/sun/star/deployment/DependencyException.hpp" #include "com/sun/star/deployment/LicenseException.hpp" #include "com/sun/star/deployment/VersionException.hpp" #include "com/sun/star/deployment/ui/LicenseDialog.hpp" #include "com/sun/star/task/XInteractionHandler.hpp" #include "com/sun/star/ui/dialogs/XExecutableDialog.hpp" #include "com/sun/star/ui/dialogs/ExecutableDialogResults.hpp" #include "com/sun/star/task/XInteractionAbort.hpp" #include "com/sun/star/task/XInteractionApprove.hpp" #include "dp_descriptioninfoset.hxx" #include "dp_gui.hrc" #include "dp_gui_updateinstalldialog.hxx" #include "dp_gui_shared.hxx" #include "dp_gui_updatedata.hxx" #include "dp_ucb.h" #include "dp_misc.h" #include "dp_version.hxx" #include "dp_gui_thread.hxx" #include "dp_gui_extensioncmdqueue.hxx" #include "ucbhelper/content.hxx" #include "osl/mutex.hxx" #include "vos/mutex.hxx" #include "rtl/ref.hxx" #include "com/sun/star/uno/Sequence.h" #include "comphelper/anytostring.hxx" #include "toolkit/helper/vclunohelper.hxx" #include class Window; namespace cssu = ::com::sun::star::uno; namespace css = ::com::sun::star; using ::rtl::OUString; namespace dp_gui { class UpdateInstallDialog::Thread: public dp_gui::Thread { friend class UpdateCommandEnv; public: Thread(cssu::Reference< cssu::XComponentContext > ctx, UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData); void stop(); private: Thread(Thread &); // not defined void operator =(Thread &); // not defined virtual ~Thread(); virtual void execute(); void downloadExtensions(); void download(::rtl::OUString const & aUrls, UpdateData & aUpdatData); void installExtensions(); void removeTempDownloads(); UpdateInstallDialog & m_dialog; cssu::Reference< css::deployment::XUpdateInformationProvider > m_updateInformation; // guarded by Application::GetSolarMutex(): cssu::Reference< css::task::XAbortChannel > m_abort; cssu::Reference< cssu::XComponentContext > m_xComponentContext; std::vector< dp_gui::UpdateData > & m_aVecUpdateData; ::rtl::Reference m_updateCmdEnv; //A folder which is created in the temp directory in which then the updates are downloaded ::rtl::OUString m_sDownloadFolder; bool m_stop; }; class UpdateCommandEnv : public ::cppu::WeakImplHelper3< css::ucb::XCommandEnvironment, css::task::XInteractionHandler, css::ucb::XProgressHandler > { friend class UpdateInstallDialog::Thread; UpdateInstallDialog & m_updateDialog; ::rtl::Reference m_installThread; cssu::Reference< cssu::XComponentContext > m_xContext; public: virtual ~UpdateCommandEnv(); UpdateCommandEnv( cssu::Reference< cssu::XComponentContext > const & xCtx, UpdateInstallDialog & updateDialog, ::rtl::Referenceconst & thread); // XCommandEnvironment virtual cssu::Reference SAL_CALL getInteractionHandler() throw (cssu::RuntimeException); virtual cssu::Reference SAL_CALL getProgressHandler() throw (cssu::RuntimeException); // XInteractionHandler virtual void SAL_CALL handle( cssu::Reference const & xRequest ) throw (cssu::RuntimeException); // XProgressHandler virtual void SAL_CALL push( cssu::Any const & Status ) throw (cssu::RuntimeException); virtual void SAL_CALL update( cssu::Any const & Status ) throw (cssu::RuntimeException); virtual void SAL_CALL pop() throw (cssu::RuntimeException); }; UpdateInstallDialog::Thread::Thread( cssu::Reference< cssu::XComponentContext> xCtx, UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData): m_dialog(dialog), m_xComponentContext(xCtx), m_aVecUpdateData(aVecUpdateData), m_updateCmdEnv(new UpdateCommandEnv(xCtx, m_dialog, this)), m_stop(false) {} void UpdateInstallDialog::Thread::stop() { cssu::Reference< css::task::XAbortChannel > abort; { vos::OGuard g(Application::GetSolarMutex()); abort = m_abort; m_stop = true; } if (abort.is()) { abort->sendAbort(); } } UpdateInstallDialog::Thread::~Thread() {} void UpdateInstallDialog::Thread::execute() { try { downloadExtensions(); installExtensions(); } catch (...) { } //clean up the temp directories try { removeTempDownloads(); } catch( ... ) { } { //make sure m_dialog is still alive ::vos::OGuard g(Application::GetSolarMutex()); if (! m_stop) m_dialog.updateDone(); } //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it. m_updateCmdEnv->m_installThread.clear(); } UpdateInstallDialog::UpdateInstallDialog( Window * parent, std::vector & aVecUpdateData, cssu::Reference< cssu::XComponentContext > const & xCtx): ModalDialog( parent, DpGuiResId(RID_DLG_UPDATEINSTALL)), m_thread(new Thread(xCtx, *this, aVecUpdateData)), m_xComponentContext(xCtx), m_bError(false), m_bNoEntry(true), m_bActivated(false), m_sInstalling(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_INSTALLING))), m_sFinished(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_FINISHED))), m_sNoErrors(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS))), m_sErrorDownload(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD))), m_sErrorInstallation(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION))), m_sErrorLicenseDeclined(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED))), m_sNoInstall(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL))), m_sThisErrorOccurred(String(DpGuiResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED))), m_ft_action(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_DOWNLOADING)), m_statusbar(this,DpGuiResId(RID_DLG_UPDATE_INSTALL_STATUSBAR)), m_ft_extension_name(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NAME)), m_ft_results(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_RESULTS)), m_mle_info(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_INFO)), m_line(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_LINE)), m_help(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_HELP)), m_ok(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_OK)), m_cancel(this, DpGuiResId(RID_DLG_UPDATE_INSTALL_ABORT)) { FreeResource(); m_xExtensionManager = css::deployment::ExtensionManager::get( xCtx ); m_cancel.SetClickHdl(LINK(this, UpdateInstallDialog, cancelHandler)); m_mle_info.EnableCursor(FALSE); if ( ! dp_misc::office_is_running()) m_help.Disable(); } UpdateInstallDialog::~UpdateInstallDialog() {} BOOL UpdateInstallDialog::Close() { m_thread->stop(); return ModalDialog::Close(); } short UpdateInstallDialog::Execute() { m_thread->launch(); return ModalDialog::Execute(); } // make sure the solar mutex is locked before calling void UpdateInstallDialog::updateDone() { if (!m_bError) m_mle_info.InsertText(m_sNoErrors); m_ok.Enable(); m_ok.GrabFocus(); m_cancel.Disable(); } // make sure the solar mutex is locked before calling //sets an error message in the text area void UpdateInstallDialog::setError(INSTALL_ERROR err, ::rtl::OUString const & sExtension, OUString const & exceptionMessage) { String sError; m_bError = true; switch (err) { case ERROR_DOWNLOAD: sError = m_sErrorDownload; break; case ERROR_INSTALLATION: sError = m_sErrorInstallation; break; case ERROR_LICENSE_DECLINED: sError = m_sErrorLicenseDeclined; break; default: OSL_ASSERT(0); } sError.SearchAndReplace(String(OUSTR("%NAME")), String(sExtension), 0); //We want to have an empty line between the error messages. However, //there shall be no empty line after the last entry. if (m_bNoEntry) m_bNoEntry = false; else m_mle_info.InsertText(OUSTR("\n")); m_mle_info.InsertText(sError); //Insert more information about the error if (exceptionMessage.getLength()) m_mle_info.InsertText(m_sThisErrorOccurred + exceptionMessage + OUSTR("\n")); m_mle_info.InsertText(m_sNoInstall); m_mle_info.InsertText(OUSTR("\n")); } void UpdateInstallDialog::setError(OUString const & exceptionMessage) { m_bError = true; m_mle_info.InsertText(exceptionMessage + OUSTR("\n")); } IMPL_LINK(UpdateInstallDialog, cancelHandler, void *, EMPTYARG) { m_thread->stop(); EndDialog(RET_CANCEL); return 0; } //------------------------------------------------------------------------------------------------ void UpdateInstallDialog::Thread::downloadExtensions() { try { //create the download directory in the temp folder OUString sTempDir; if (::osl::FileBase::getTempDirURL(sTempDir) != ::osl::FileBase::E_None) throw cssu::Exception(OUSTR("Could not get URL for the temp directory. No extensions will be installed."), 0); //create a unique name for the directory OUString tempEntry, destFolder; if (::osl::File::createTempFile(&sTempDir, 0, &tempEntry ) != ::osl::File::E_None) throw cssu::Exception(OUSTR("Could not create a temporary file in ") + sTempDir + OUSTR(". No extensions will be installed"), 0 ); tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); destFolder = dp_misc::makeURL( sTempDir, tempEntry ); destFolder += OUSTR("_"); m_sDownloadFolder = destFolder; try { dp_misc::create_folder(0, destFolder, m_updateCmdEnv.get(), true ); } catch (cssu::Exception & e) { throw cssu::Exception(e.Message + OUSTR(" No extensions will be installed."), 0); } sal_uInt16 count = 0; typedef std::vector::iterator It; for (It i = m_aVecUpdateData.begin(); i != m_aVecUpdateData.end(); i++) { UpdateData & curData = *i; if (!curData.aUpdateInfo.is() || curData.aUpdateSource.is()) continue; //We assume that m_aVecUpdateData contains only information about extensions which //can be downloaded directly. OSL_ASSERT(curData.sWebsiteURL.getLength() == 0); //update the name of the extension which is to be downloaded { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.m_ft_extension_name.SetText(curData.aInstalledPackage->getDisplayName()); sal_uInt16 prog = (sal::static_int_cast(100) * ++count) / sal::static_int_cast(m_aVecUpdateData.size()); m_dialog.m_statusbar.SetValue(prog); } dp_misc::DescriptionInfoset info(m_xComponentContext, curData.aUpdateInfo); //remember occurring exceptions in case we need to print out error information ::std::vector< ::std::pair > vecExceptions; cssu::Sequence seqDownloadURLs = info.getUpdateDownloadUrls(); OSL_ENSURE(seqDownloadURLs.getLength() > 0, "No download URL provided!"); for (sal_Int32 j = 0; j < seqDownloadURLs.getLength(); j++) { try { OSL_ENSURE(seqDownloadURLs[j].getLength() > 0, "Download URL is empty!"); download(seqDownloadURLs[j], curData); if (curData.sLocalURL.getLength() > 0) break; } catch ( cssu::Exception & e ) { vecExceptions.push_back( ::std::make_pair(seqDownloadURLs[j], e)); //There can be several different errors, for example, the URL is wrong, webserver cannot be reached, //name cannot be resolved. The UCB helper API does not specify different special exceptions for these //cases. Therefore ignore and continue. continue; } } //update the progress and display download error { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } if (curData.sLocalURL.getLength() == 0) { //Construct a string of all messages contained in the exceptions plus the respective download URLs ::rtl::OUStringBuffer buf(256); typedef ::std::vector< ::std::pair >::const_iterator CIT; for (CIT j = vecExceptions.begin(); j != vecExceptions.end(); j++) { if (j != vecExceptions.begin()) buf.appendAscii("\n"); buf.append(OUSTR("Could not download ")); buf.append(j->first); buf.appendAscii(". "); buf.append(j->second.Message); } m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, curData.aInstalledPackage->getDisplayName(), buf.makeStringAndClear()); } } } } catch (cssu::Exception & e) { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.setError(e.Message); } } void UpdateInstallDialog::Thread::installExtensions() { //Update the fix text in the dialog to "Installing extensions..." { vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.m_ft_action.SetText(m_dialog.m_sInstalling); m_dialog.m_statusbar.SetValue(0); } sal_uInt16 count = 0; typedef std::vector::iterator It; for (It i = m_aVecUpdateData.begin(); i != m_aVecUpdateData.end(); i++, count++) { //update the name of the extension which is to be installed { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } //we only show progress after an extension has been installed. if (count > 0) { m_dialog.m_statusbar.SetValue( (sal::static_int_cast(100) * count) / sal::static_int_cast(m_aVecUpdateData.size())); } m_dialog.m_ft_extension_name.SetText(i->aInstalledPackage->getDisplayName()); } // TimeValue v = {1, 0}; // osl::Thread::wait(v); bool bError = false; bool bLicenseDeclined = false; cssu::Reference xExtension; UpdateData & curData = *i; cssu::Exception exc; try { cssu::Reference< css::task::XAbortChannel > xAbortChannel( curData.aInstalledPackage->createAbortChannel() ); { vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_abort = xAbortChannel; } if (!curData.aUpdateSource.is() && curData.sLocalURL.getLength()) { css::beans::NamedValue prop(OUSTR("EXTENSION_UPDATE"), css::uno::makeAny(OUSTR("1"))); if (!curData.bIsShared) xExtension = m_dialog.getExtensionManager()->addExtension( curData.sLocalURL, css::uno::Sequence(&prop, 1), OUSTR("user"), xAbortChannel, m_updateCmdEnv.get()); else xExtension = m_dialog.getExtensionManager()->addExtension( curData.sLocalURL, css::uno::Sequence(&prop, 1), OUSTR("shared"), xAbortChannel, m_updateCmdEnv.get()); } else if (curData.aUpdateSource.is()) { OSL_ASSERT(curData.aUpdateSource.is()); //I am not sure if we should obtain the install properties and pass them into //add extension. Currently it contains only "SUPPRESS_LICENSE". So it it could happen //that a license is displayed when updating from the shared repository, although the //shared extension was installed using "SUPPRESS_LICENSE". css::beans::NamedValue prop(OUSTR("EXTENSION_UPDATE"), css::uno::makeAny(OUSTR("1"))); if (!curData.bIsShared) xExtension = m_dialog.getExtensionManager()->addExtension( curData.aUpdateSource->getURL(), css::uno::Sequence(&prop, 1), OUSTR("user"), xAbortChannel, m_updateCmdEnv.get()); else xExtension = m_dialog.getExtensionManager()->addExtension( curData.aUpdateSource->getURL(), css::uno::Sequence(&prop, 1), OUSTR("shared"), xAbortChannel, m_updateCmdEnv.get()); } } catch (css::deployment::DeploymentException & de) { if (de.Cause.has()) { bLicenseDeclined = true; } else { exc = de.Cause.get(); bError = true; } } catch (cssu::Exception& e) { exc = e; bError = true; } if (bLicenseDeclined) { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED, curData.aInstalledPackage->getDisplayName(), OUString()); } else if (!xExtension.is() || bError) { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION, curData.aInstalledPackage->getDisplayName(), exc.Message); } } { vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } m_dialog.m_statusbar.SetValue(100); m_dialog.m_ft_extension_name.SetText(OUString()); m_dialog.m_ft_action.SetText(m_dialog.m_sFinished); } } void UpdateInstallDialog::Thread::removeTempDownloads() { if (m_sDownloadFolder.getLength()) { dp_misc::erase_path(m_sDownloadFolder, cssu::Reference(),false /* no throw: ignore errors */ ); //remove also the temp file which we have used to create the unique name OUString tempFile = m_sDownloadFolder.copy(0, m_sDownloadFolder.getLength() - 1); dp_misc::erase_path(tempFile, cssu::Reference(),false); m_sDownloadFolder = OUString(); } } void UpdateInstallDialog::Thread::download(OUString const & sDownloadURL, UpdateData & aUpdateData) { { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } } OSL_ASSERT(m_sDownloadFolder.getLength()); OUString destFolder, tempEntry; if (::osl::File::createTempFile( &m_sDownloadFolder, 0, &tempEntry ) != ::osl::File::E_None) { //ToDo feedback in window that download of this component failed throw cssu::Exception(OUSTR("Could not create temporary file in folder ") + destFolder + OUSTR("."), 0); } tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); destFolder = dp_misc::makeURL( m_sDownloadFolder, tempEntry ); destFolder += OUSTR("_"); ::ucbhelper::Content destFolderContent; dp_misc::create_folder( &destFolderContent, destFolder, m_updateCmdEnv.get() ); ::ucbhelper::Content sourceContent; dp_misc::create_ucb_content( &sourceContent, sDownloadURL, m_updateCmdEnv.get() ); const OUString sTitle(sourceContent.getPropertyValue( dp_misc::StrTitle::get() ).get() ); if (destFolderContent.transferContent( sourceContent, ::ucbhelper::InsertOperation_COPY, sTitle, css::ucb::NameClash::OVERWRITE )) { //the user may have cancelled the dialog because downloading took to long { ::vos::OGuard g(Application::GetSolarMutex()); if (m_stop) { return; } //all errors should be handeld by the command environment. aUpdateData.sLocalURL = destFolder + OUString( RTL_CONSTASCII_USTRINGPARAM( "/" ) ) + sTitle; } } } // ------------------------------------------------------------------------------------------------------- UpdateCommandEnv::UpdateCommandEnv( cssu::Reference< cssu::XComponentContext > const & xCtx, UpdateInstallDialog & updateDialog, ::rtl::Referenceconst & thread) : m_updateDialog( updateDialog ), m_installThread(thread), m_xContext(xCtx) { } UpdateCommandEnv::~UpdateCommandEnv() { } // XCommandEnvironment //______________________________________________________________________________ cssu::Reference UpdateCommandEnv::getInteractionHandler() throw (cssu::RuntimeException) { return this; } //______________________________________________________________________________ cssu::Reference UpdateCommandEnv::getProgressHandler() throw (cssu::RuntimeException) { return this; } // XInteractionHandler void UpdateCommandEnv::handle( cssu::Reference< css::task::XInteractionRequest> const & xRequest ) throw (cssu::RuntimeException) { cssu::Any request( xRequest->getRequest() ); OSL_ASSERT( request.getValueTypeClass() == cssu::TypeClass_EXCEPTION ); dp_misc::TRACE(OUSTR("[dp_gui_cmdenv.cxx] incoming request:\n") + ::comphelper::anyToString(request) + OUSTR("\n\n")); css::deployment::VersionException verExc; bool approve = false; bool abort = false; if (request >>= verExc) { //We must catch the version exception during the update, //because otherwise the user would be confronted with the dialogs, asking //them if they want to replace an already installed version of the same extension. //During an update we assume that we always want to replace the old version with the //new version. approve = true; } if (approve == false && abort == false) { //forward to interaction handler for main dialog. handleInteractionRequest( m_xContext, xRequest ); } else { // select: cssu::Sequence< cssu::Reference< css::task::XInteractionContinuation > > conts( xRequest->getContinuations() ); cssu::Reference< css::task::XInteractionContinuation > const * pConts = conts.getConstArray(); sal_Int32 len = conts.getLength(); for ( sal_Int32 pos = 0; pos < len; ++pos ) { if (approve) { cssu::Reference< css::task::XInteractionApprove > xInteractionApprove( pConts[ pos ], cssu::UNO_QUERY ); if (xInteractionApprove.is()) { xInteractionApprove->select(); // don't query again for ongoing continuations: approve = false; } } else if (abort) { cssu::Reference< css::task::XInteractionAbort > xInteractionAbort( pConts[ pos ], cssu::UNO_QUERY ); if (xInteractionAbort.is()) { xInteractionAbort->select(); // don't query again for ongoing continuations: abort = false; } } } } } // XProgressHandler void UpdateCommandEnv::push( cssu::Any const & /*Status*/ ) throw (cssu::RuntimeException) { } void UpdateCommandEnv::update( cssu::Any const & /*Status */) throw (cssu::RuntimeException) { } void UpdateCommandEnv::pop() throw (cssu::RuntimeException) { } } //end namespace dp_gui