tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / extensions / source / update / check / updatecheck.cxx
blob97aa043d3f872c0f7b02f7fb989da109e0e0f50c
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 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <comphelper/scopeguard.hxx>
25 #include <config_folders.h>
27 #include "updatecheck.hxx"
29 #include <cppuhelper/implbase.hxx>
30 #include <com/sun/star/beans/XFastPropertySet.hpp>
31 #include <com/sun/star/deployment/UpdateInformationProvider.hpp>
32 #include <com/sun/star/frame/Desktop.hpp>
33 #include <com/sun/star/office/Quickstart.hpp>
34 #include <com/sun/star/system/SystemShellExecute.hpp>
35 #include <com/sun/star/system/SystemShellExecuteException.hpp>
36 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
37 #include <com/sun/star/task/XJob.hpp>
38 #include <com/sun/star/task/XJobExecutor.hpp>
40 #include <rtl/bootstrap.hxx>
41 #include <osl/process.h>
42 #include <osl/file.hxx>
43 #include <sal/macros.h>
44 #include <sal/log.hxx>
45 #include <comphelper/diagnose_ex.hxx>
47 #ifdef _WIN32
48 #include <o3tl/safeCoInitUninit.hxx>
49 #include <objbase.h>
50 #endif
52 #include "onlinecheck.hxx"
53 #include "updateprotocol.hxx"
54 #include "updatecheckconfig.hxx"
56 namespace beans = css::beans ;
57 namespace deployment = css::deployment ;
58 namespace lang = css::lang ;
59 namespace c3s = css::system ;
60 namespace task = css::task ;
61 namespace uno = css::uno ;
63 constexpr OUStringLiteral PROPERTY_TITLE = u"BubbleHeading";
64 constexpr OUStringLiteral PROPERTY_TEXT = u"BubbleText";
65 constexpr OUStringLiteral PROPERTY_SHOW_BUBBLE = u"BubbleVisible";
66 constexpr OUStringLiteral PROPERTY_CLICK_HDL = u"MenuClickHDL";
67 constexpr OUString PROPERTY_SHOW_MENUICON = u"MenuIconVisible"_ustr;
69 // Returns the URL of the release note for the given position
70 OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled)
72 for (auto const& elem : rInfo.ReleaseNotes)
74 if( pos == elem.Pos )
76 if( (pos > 2) || !autoDownloadEnabled || elem.URL2.isEmpty() )
77 return elem.URL;
79 else if( (pos == elem.Pos2) && ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled )
80 return elem.URL2;
83 return OUString();
87 namespace
90 OUString getBuildId()
92 OUString aPathVal(u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"_ustr);
93 rtl::Bootstrap::expandMacros(aPathVal);
94 return aPathVal;
98 bool isObsoleteUpdateInfo(std::u16string_view rBuildId)
100 return rBuildId != getBuildId() && !rBuildId.empty();
104 OUString getImageFromFileName(const OUString& aFile)
106 #ifndef _WIN32
107 OUString aUnpackPath;
108 if( osl_getExecutableFile(&aUnpackPath.pData) == osl_Process_E_None )
110 sal_uInt32 lastIndex = aUnpackPath.lastIndexOf('/');
111 if ( lastIndex > 0 )
113 aUnpackPath = OUString::Concat(aUnpackPath.subView( 0, lastIndex+1 )) +
114 "unpack_update";
117 oslFileHandle hOut = nullptr;
118 oslProcess hProcess = nullptr;
120 OUString aSystemPath;
121 osl::File::getSystemPathFromFileURL(aFile, aSystemPath);
123 oslProcessError rc = osl_executeProcess_WithRedirectedIO(
124 aUnpackPath.pData, // [in] Image name
125 &aSystemPath.pData, 1, // [in] Arguments
126 osl_Process_WAIT | osl_Process_NORMAL, // [in] Options
127 nullptr, // [in] Security
128 nullptr, // [in] Working directory
129 nullptr, 0, // [in] Environment variables
130 &hProcess, // [out] Process handle
131 nullptr, &hOut, nullptr // [out] File handles for redirected I/O
134 if( osl_Process_E_None == rc )
136 // Create a guard to ensure correct cleanup in its dtor in any case
137 comphelper::ScopeGuard g([hOut, hProcess] () {
138 osl_closeFile(hOut);
139 osl_freeProcessHandle(hProcess);
142 oslProcessInfo aInfo;
143 aInfo.Size = sizeof(oslProcessInfo);
145 if( osl_Process_E_None == osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo) )
147 if( 0 == aInfo.Code )
149 char szBuffer[4096];
150 sal_uInt64 nBytesRead = 0;
151 const sal_uInt64 nBytesToRead = sizeof(szBuffer) - 1;
153 OUString aImageName;
154 while( osl_File_E_None == osl_readFile(hOut, szBuffer, nBytesToRead, &nBytesRead) )
156 char *pc = szBuffer + nBytesRead;
159 *pc = '\0'; --pc;
161 while( ('\n' == *pc) || ('\r' == *pc) );
163 aImageName += OUString(szBuffer, pc - szBuffer + 1, osl_getThreadTextEncoding());
165 if( nBytesRead < nBytesToRead )
166 break;
169 if( osl::FileBase::E_None == osl::FileBase::getFileURLFromSystemPath(aImageName, aImageName) )
170 return aImageName;
175 #endif
177 return aFile;
181 uno::Reference< beans::XPropertySet > createMenuBarUI(
182 const uno::Reference< uno::XComponentContext >& xContext,
183 const uno::Reference< task::XJob >& xJob)
185 if( !xContext.is() )
186 throw uno::RuntimeException(
187 u"UpdateCheckJob: empty component context"_ustr, uno::Reference< uno::XInterface > () );
189 uno::Reference< lang::XMultiComponentFactory > xServiceManager(xContext->getServiceManager());
190 if( !xServiceManager.is() )
191 throw uno::RuntimeException(
192 u"UpdateCheckJob: unable to obtain service manager from component context"_ustr, uno::Reference< uno::XInterface > () );
194 uno::Reference< beans::XPropertySet > xMenuBarUI(
195 xServiceManager->createInstanceWithContext( u"com.sun.star.setup.UpdateCheckUI"_ustr, xContext ),
196 uno::UNO_QUERY_THROW);
198 xMenuBarUI->setPropertyValue( PROPERTY_CLICK_HDL, uno::Any( xJob ) );
200 return xMenuBarUI;
204 typedef sal_Bool (* OnlineCheckFunc) ();
206 class UpdateCheckThread : public WorkerThread
209 public:
210 UpdateCheckThread( osl::Condition& rCondition,
211 const uno::Reference<uno::XComponentContext>& xContext,
212 rtl::Reference<UpdateCheck> const & controller );
214 virtual void SAL_CALL join() override;
215 virtual void SAL_CALL terminate() override;
216 virtual void cancel() override;
218 void cancelAsSoonAsPossible();
220 protected:
221 virtual ~UpdateCheckThread() override;
223 virtual void SAL_CALL run() override;
224 virtual void SAL_CALL onTerminated() override;
226 /* Wrapper around checkForUpdates */
227 bool runCheck( bool & rbExtensionsChecked );
229 private:
231 /* Used to avoid dialup login windows (on platforms we know how to double this) */
232 static bool hasInternetConnection()
234 #ifdef _WIN32
235 return WNT_hasInternetConnection();
236 #else
237 return true;
238 #endif
241 /* Creates a new instance of UpdateInformationProvider and returns this instance */
242 uno::Reference<deployment::XUpdateInformationProvider> createProvider()
244 osl::MutexGuard aGuard(m_aMutex);
245 m_xProvider = deployment::UpdateInformationProvider::create(m_xContext);
246 return m_xProvider;
249 /* Returns the remembered instance of UpdateInformationProvider if any */
250 uno::Reference<deployment::XUpdateInformationProvider> getProvider()
251 { osl::MutexGuard aGuard(m_aMutex); return m_xProvider; };
253 /* Releases the remembered instance of UpdateInformationProvider if any */
254 void clearProvider()
255 { osl::MutexGuard aGuard(m_aMutex); m_xProvider.clear(); };
257 osl::Mutex m_aMutex;
259 protected:
260 osl::Condition& m_aCondition;
262 private:
263 const uno::Reference<uno::XComponentContext> m_xContext;
264 uno::Reference<deployment::XUpdateInformationProvider> m_xProvider;
265 rtl::Reference<UpdateCheck> m_controller;
266 bool m_cancelAsSoonAsPossible;
270 class ManualUpdateCheckThread : public UpdateCheckThread
272 public:
273 ManualUpdateCheckThread( osl::Condition& rCondition, const uno::Reference<uno::XComponentContext>& xContext ) :
274 UpdateCheckThread(rCondition, xContext, {}) {};
276 virtual void SAL_CALL run() override;
280 class MenuBarButtonJob : public ::cppu::WeakImplHelper< task::XJob >
282 public:
283 explicit MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck);
285 // XJob
286 virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override;
288 private:
289 rtl::Reference< UpdateCheck > m_aUpdateCheck;
292 class DownloadThread : public WorkerThread
294 public:
295 DownloadThread(
296 osl::Condition& rCondition,
297 const uno::Reference<uno::XComponentContext>& xContext,
298 const rtl::Reference< DownloadInteractionHandler >& rHandler,
299 const OUString& rURL );
301 virtual void SAL_CALL run() override;
302 virtual void cancel() override;
303 virtual void SAL_CALL suspend() override;
304 virtual void SAL_CALL onTerminated() override;
306 protected:
307 virtual ~DownloadThread() override;
309 private:
310 osl::Condition& m_aCondition;
311 const uno::Reference<uno::XComponentContext> m_xContext;
312 const OUString m_aURL;
313 Download m_aDownload;
318 UpdateCheckThread::UpdateCheckThread( osl::Condition& rCondition,
319 const uno::Reference<uno::XComponentContext>& xContext,
320 rtl::Reference<UpdateCheck> const & controller ) :
321 m_aCondition(rCondition),
322 m_xContext(xContext),
323 m_controller(controller),
324 m_cancelAsSoonAsPossible(false)
326 createSuspended();
328 // actually run the thread
329 resume();
333 UpdateCheckThread::~UpdateCheckThread()
338 void SAL_CALL
339 UpdateCheckThread::terminate()
341 // Cancel potentially hanging http request ..
342 cancel();
343 // .. before terminating
344 osl::Thread::terminate();
348 void SAL_CALL
349 UpdateCheckThread::join()
351 uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider());
353 // do not join during an update check until #i73893# is fixed
354 if( ! xProvider.is() )
356 osl::Thread::join();
361 void
362 UpdateCheckThread::cancel()
364 uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider());
366 if( xProvider.is() )
367 xProvider->cancel();
370 void UpdateCheckThread::cancelAsSoonAsPossible() {
372 osl::MutexGuard g(m_aMutex);
373 m_cancelAsSoonAsPossible = true;
375 m_aCondition.set();
378 bool
379 UpdateCheckThread::runCheck( bool & rbExtensionsChecked )
381 bool ret = false;
382 UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL;
384 UpdateInfo aInfo;
385 rtl::Reference< UpdateCheck > aController(UpdateCheck::get());
387 if( checkForUpdates(aInfo, m_xContext, aController->getInteractionHandler(), createProvider()) )
389 aController->setUpdateInfo(aInfo);
390 eUIState = UpdateCheck::getUIState(aInfo);
391 ret = true;
393 else
394 aController->setCheckFailedState();
396 // We will only look for extension updates, when there is no 'check for office updates' dialog open
397 // and when there was no office update found
398 if ( ( eUIState != UPDATESTATE_UPDATE_AVAIL ) &&
399 ( eUIState != UPDATESTATE_UPDATE_NO_DOWNLOAD ) &&
400 !aController->isDialogShowing() &&
401 !rbExtensionsChecked )
403 bool bHasExtensionUpdates = checkForExtensionUpdates( m_xContext );
404 aController->setHasExtensionUpdates( bHasExtensionUpdates );
405 if ( bHasExtensionUpdates )
406 aController->setUIState( UPDATESTATE_EXT_UPD_AVAIL );
407 rbExtensionsChecked = true;
410 // joining with this thread is safe again
411 clearProvider();
412 return ret;
416 void SAL_CALL
417 UpdateCheckThread::onTerminated()
419 delete this;
423 void SAL_CALL
424 UpdateCheckThread::run()
426 osl_setThreadName("UpdateCheckThread");
428 TimeValue systime;
429 TimeValue nExtCheckTime;
430 osl_getSystemTime( &nExtCheckTime );
432 osl::Condition::Result aResult = osl::Condition::result_timeout;
433 TimeValue tv = { 10, 0 };
435 // Initial wait to avoid doing further time consuming tasks during start-up
436 aResult = m_aCondition.wait(&tv);
438 osl::MutexGuard g(m_aMutex);
439 if (m_cancelAsSoonAsPossible) {
440 goto done;
444 try {
445 bool bExtensionsChecked = false;
447 while( schedule() )
449 /* Use cases:
450 * a) manual check requested from auto check thread - "last check" should not be checked (one time)
451 * a1) manual check was requested in the middle of a running auto check,
452 * condition is set
453 * a2) manual check was requested while waiting for a retry,
454 * condition is set
455 * a3) manual check was requested while waiting for time to next
456 * scheduled check elapsing, condition is set
457 * a4) manual check was requested during initial wait, condition is set
458 * b) check interval got changed, condition may be set - same sub-cases as a),
459 * but "last check" should be honored
460 * c) normal auto check mode, condition not set - "last check" should be honored
463 // Accessing const members without synchronization
464 rtl::Reference< UpdateCheck > aController(UpdateCheck::get());
465 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *aController);
467 // FIXME: remember last & offset ?
468 sal_Int64 last = rModel->getLastChecked();
469 sal_Int64 offset = rModel->getCheckInterval();
471 rModel.clear();
473 // last == 0 means check immediately
474 bool checkNow = last <= 0;
476 // Reset the condition to avoid busy loops
477 if( osl::Condition::result_ok == aResult )
479 m_aCondition.reset();
480 aResult = osl::Condition::result_timeout;
481 checkNow = aController->isDialogShowing();
484 if( ! checkNow )
486 osl_getSystemTime(&systime);
488 // Go back to sleep until time has elapsed
489 sal_Int64 next = last + offset;
490 if( last + offset > systime.Seconds )
492 // This can not be > 32 Bit for now ..
493 tv.Seconds = static_cast< sal_Int32 > (next - systime.Seconds);
494 aResult = m_aCondition.wait(&tv);
496 osl::MutexGuard g(m_aMutex);
497 if (m_cancelAsSoonAsPossible) {
498 goto done;
501 continue;
505 static sal_uInt8 n = 0;
507 if( ! hasInternetConnection() || ! runCheck( bExtensionsChecked ) )
509 // the extension update check should be independent from the office update check
511 osl_getSystemTime( &systime );
512 if ( nExtCheckTime.Seconds + offset < systime.Seconds )
513 bExtensionsChecked = false;
515 // Increase next by 15, 60, .. minutes
516 static const sal_Int32 nRetryInterval[] = { 900, 3600, 14400, 86400 };
518 if( n < std::size(nRetryInterval) )
519 ++n;
521 tv.Seconds = nRetryInterval[n-1];
522 aResult = m_aCondition.wait(&tv);
524 osl::MutexGuard g(m_aMutex);
525 if (m_cancelAsSoonAsPossible) {
526 goto done;
530 else // reset retry counter
532 n = 0;
533 bExtensionsChecked = false;
538 catch(const uno::Exception&) {
539 // Silently catch all errors
540 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
543 done:
544 if (m_controller.is()) {
545 m_controller->notifyUpdateCheckFinished();
550 void SAL_CALL
551 ManualUpdateCheckThread::run()
553 try {
554 bool bExtensionsChecked = false;
555 runCheck( bExtensionsChecked );
556 m_aCondition.reset();
558 catch(const uno::Exception&) {
559 // Silently catch all errors
560 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
565 MenuBarButtonJob::MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck) :
566 m_aUpdateCheck(rUpdateCheck)
571 uno::Any SAL_CALL
572 MenuBarButtonJob::execute(const uno::Sequence<beans::NamedValue>& )
574 if ( m_aUpdateCheck->shouldShowExtUpdDlg() )
575 m_aUpdateCheck->showExtensionDialog();
576 else
577 m_aUpdateCheck->showDialog();
579 return uno::Any();
583 DownloadThread::DownloadThread(osl::Condition& rCondition,
584 const uno::Reference<uno::XComponentContext>& xContext,
585 const rtl::Reference< DownloadInteractionHandler >& rHandler,
586 const OUString& rURL) :
587 m_aCondition(rCondition),
588 m_xContext(xContext),
589 m_aURL(rURL),
590 m_aDownload(xContext, rHandler)
592 createSuspended();
596 DownloadThread::~DownloadThread()
601 void SAL_CALL
602 DownloadThread::run()
604 osl_setThreadName("DownloadThread");
606 #ifdef _WIN32
607 int nNbCallCoInitializeExForReinit = 0;
608 // for SystemShellExecute
609 o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit);
610 #endif
612 while( schedule() )
614 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext);
616 OUString aLocalFile = rModel->getLocalFileName();
617 OUString aDownloadDest = rModel->getDownloadDestination();
619 // release config class for now
620 rModel.clear();
622 static sal_uInt8 n = 0;
623 if( ! m_aDownload.start(m_aURL, aLocalFile, aDownloadDest ) )
625 // retry every 15s unless the dialog is not visible
626 TimeValue tv(15, 0);
628 if( ! UpdateCheck::get()->isDialogShowing() )
630 // Increase next by 1, 5, 15, 60, .. minutes
631 static const sal_Int16 nRetryInterval[] = { 60, 300, 900, 3600 };
633 if( n < std::size(nRetryInterval) )
634 ++n;
636 tv.Seconds = nRetryInterval[n-1];
638 m_aCondition.wait(&tv);
640 else
642 // reset wait period after successful download
643 n=0;
646 #ifdef _WIN32
647 o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit);
648 #endif
652 void DownloadThread::cancel()
654 m_aDownload.stop();
655 resume();
657 rtl::Reference< UpdateCheck > aController(UpdateCheck::get());
658 aController->cancelDownload();
662 void SAL_CALL DownloadThread::suspend()
664 osl::Thread::suspend();
665 m_aDownload.stop();
669 void SAL_CALL DownloadThread::onTerminated()
671 delete this;
675 } // anonymous namespace
677 UpdateCheck::UpdateCheck()
678 : m_eState(NOT_INITIALIZED)
679 , m_eUpdateState(UPDATESTATES_COUNT)
680 , m_pThread(nullptr)
681 , m_bHasExtensionUpdate(false)
682 , m_bShowExtUpdDlg(false)
683 , m_updateCheckRunning(false)
687 UpdateCheck::~UpdateCheck() {}
689 void
690 UpdateCheck::initialize(const uno::Sequence< beans::NamedValue >& rValues,
691 const uno::Reference<uno::XComponentContext>& xContext)
693 std::scoped_lock aGuard(m_aMutex);
695 if( NOT_INITIALIZED == m_eState )
697 NamedValueByNameAccess aNameAccess(rValues);
698 UpdateCheckROModel aModel( aNameAccess );
699 m_xContext = xContext;
701 OUString aUpdateEntryVersion = aModel.getUpdateEntryVersion();
703 aModel.getUpdateEntry(m_aUpdateInfo);
705 bool obsoleteUpdateInfo = isObsoleteUpdateInfo(aUpdateEntryVersion);
706 bool bContinueDownload = false;
707 bool bDownloadAvailable = false;
709 m_bHasExtensionUpdate = checkForPendingUpdates( xContext );
710 m_bShowExtUpdDlg = false;
712 OUString aLocalFileName = aModel.getLocalFileName();
714 if( !aLocalFileName.isEmpty() )
716 bContinueDownload = true;
718 // Try to get the number of bytes already on disk
719 osl::DirectoryItem aDirectoryItem;
720 if( osl::DirectoryItem::E_None == osl::DirectoryItem::get(aLocalFileName, aDirectoryItem) )
722 osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileSize);
723 if( osl::DirectoryItem::E_None == aDirectoryItem.getFileStatus(aFileStatus) )
725 sal_Int64 nDownloadSize = aModel.getDownloadSize();
726 sal_Int64 nFileSize = aFileStatus.getFileSize();
728 if( nDownloadSize > 0 )
730 if ( nDownloadSize <= nFileSize ) // we have already downloaded everything
732 bContinueDownload = false;
733 bDownloadAvailable = true;
734 m_aImageName = getImageFromFileName( aLocalFileName );
736 else // Calculate initial percent value.
738 sal_Int32 nPercent = static_cast<sal_Int32>(100 * nFileSize / nDownloadSize);
739 getUpdateHandler()->setProgress( nPercent );
745 if ( bContinueDownload )
747 bool downloadPaused = aModel.isDownloadPaused();
749 enableDownload(true, downloadPaused);
750 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
751 setUIState(downloadPaused ? UPDATESTATE_DOWNLOAD_PAUSED : UPDATESTATE_DOWNLOADING);
755 if ( !bContinueDownload )
757 // We do this intentionally only if no download is in progress ..
758 if( obsoleteUpdateInfo )
760 // Bring-up release note for position 5 ..
761 const OUString aURL(getReleaseNote(m_aUpdateInfo, 5));
762 if( !aURL.isEmpty() )
763 showReleaseNote(aURL);
765 // Data is outdated, probably due to installed update
766 rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( xContext, *this );
767 aConfig->clearUpdateFound();
768 aConfig->clearLocalFileName();
771 m_aUpdateInfo = UpdateInfo();
772 // Remove outdated release notes
773 storeReleaseNote( 1, OUString() );
774 storeReleaseNote( 2, OUString() );
776 else
778 enableAutoCheck(aModel.isAutoCheckEnabled());
779 if ( bDownloadAvailable )
780 setUIState( UPDATESTATE_DOWNLOAD_AVAIL );
781 else
783 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
784 setUIState(getUIState(m_aUpdateInfo));
792 void
793 UpdateCheck::cancel()
795 std::unique_lock aGuard(m_aMutex);
797 WorkerThread *pThread = m_pThread;
798 UpdateState eUIState = getUIState(m_aUpdateInfo);
800 aGuard.unlock();
802 if( nullptr != pThread )
803 pThread->cancel();
805 setUIState(eUIState);
809 void
810 UpdateCheck::download()
812 std::unique_lock aGuard(m_aMutex);
813 UpdateInfo aInfo(m_aUpdateInfo);
814 State eState = m_eState;
815 aGuard.unlock();
817 if (aInfo.Sources.empty())
819 SAL_WARN("extensions.update", "download called without source");
820 return;
823 if( aInfo.Sources[0].IsDirect )
825 // Ignore second click of a double click
826 if( DOWNLOADING != eState )
828 shutdownThread(true);
831 std::scoped_lock aGuard2(m_aMutex);
832 enableDownload(true);
834 setUIState(UPDATESTATE_DOWNLOADING);
837 else
839 showReleaseNote(aInfo.Sources[0].URL); // Display in browser
844 void
845 UpdateCheck::pause()
847 std::unique_lock aGuard(m_aMutex);
849 if( nullptr != m_pThread )
850 m_pThread->suspend();
852 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext);
853 aGuard.unlock();
855 rModel->storeDownloadPaused(true);
856 setUIState(UPDATESTATE_DOWNLOAD_PAUSED);
860 void
861 UpdateCheck::resume()
863 std::unique_lock aGuard(m_aMutex);
865 if( nullptr != m_pThread )
866 m_pThread->resume();
868 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext);
869 aGuard.unlock();
871 rModel->storeDownloadPaused(false);
872 setUIState(UPDATESTATE_DOWNLOADING);
876 void
877 UpdateCheck::closeAfterFailure()
879 std::unique_lock aGuard(m_aMutex);
881 if ( ( m_eState == DISABLED ) || ( m_eState == CHECK_SCHEDULED ) )
883 const UpdateState eUIState = getUIState( m_aUpdateInfo );
884 aGuard.unlock();
885 setUIState( eUIState, true );
889 void UpdateCheck::notifyUpdateCheckFinished() {
890 std::scoped_lock l(m_aMutex);
891 m_updateCheckRunning = false;
892 m_updateCheckFinished.notify_all();
895 void UpdateCheck::waitForUpdateCheckFinished() {
896 UpdateCheckThread * thread;
898 std::scoped_lock l(m_aMutex);
899 thread = dynamic_cast<UpdateCheckThread *>(m_pThread);
901 if (thread != nullptr) {
902 thread->cancelAsSoonAsPossible();
904 for (;;) {
905 std::unique_lock lock(m_aMutex);
906 if (!m_updateCheckRunning) {
907 return;
909 m_updateCheckFinished.wait(lock);
913 void
914 UpdateCheck::shutdownThread(bool join)
916 std::unique_lock aGuard(m_aMutex);
918 // copy thread object pointer to stack
919 osl::Thread *pThread = m_pThread;
920 m_pThread = nullptr;
921 aGuard.unlock();
923 if( nullptr != pThread )
925 pThread->terminate();
926 if( join )
928 m_aCondition.set();
929 pThread->join();
930 m_aCondition.reset();
936 void
937 UpdateCheck::enableAutoCheck(bool enable)
939 if( enable )
941 m_updateCheckRunning = true;
942 m_pThread = new UpdateCheckThread(m_aCondition, m_xContext, this);
945 m_eState = enable ? CHECK_SCHEDULED : DISABLED;
949 void
950 UpdateCheck::enableDownload(bool enable, bool paused)
952 OSL_ASSERT(nullptr == m_pThread);
954 if( enable )
956 m_pThread = new DownloadThread(m_aCondition, m_xContext, this, m_aUpdateInfo.Sources[0].URL );
957 State eState = DISABLED;
958 if( !paused )
960 eState = DOWNLOADING;
961 m_pThread->resume();
963 else
964 eState = DOWNLOAD_PAUSED;
966 m_eState = eState;
968 else {
969 enableAutoCheck(UpdateCheckConfig::get(m_xContext)->isAutoCheckEnabled());
975 bool
976 UpdateCheck::downloadTargetExists(const OUString& rFileName)
978 std::unique_lock aGuard(m_aMutex);
980 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
981 UpdateState eUIState = UPDATESTATE_DOWNLOADING;
983 bool cont = false;
985 if( aUpdateHandler->isVisible() )
987 cont = aUpdateHandler->showOverwriteWarning();
988 if( cont )
990 if( osl_File_E_None != osl_removeFile(rFileName.pData) )
992 // FIXME: error message
993 cont = false;
996 else
997 eUIState = getUIState(m_aUpdateInfo);
999 else
1001 m_aImageName = getImageFromFileName(rFileName);
1002 eUIState = UPDATESTATE_DOWNLOAD_AVAIL;
1005 if( !cont )
1007 shutdownThread(false);
1008 enableDownload(false);
1010 aGuard.unlock();
1011 setUIState(eUIState);
1014 return cont;
1018 bool UpdateCheck::checkDownloadDestination( const OUString& rFileName )
1020 std::scoped_lock aGuard(m_aMutex);
1022 rtl::Reference< UpdateHandler > aUpdateHandler( getUpdateHandler() );
1024 bool bReload = false;
1026 if( aUpdateHandler->isVisible() )
1028 bReload = aUpdateHandler->showOverwriteWarning( rFileName );
1031 return bReload;
1035 void
1036 UpdateCheck::downloadStalled(const OUString& rErrorMessage)
1038 std::unique_lock aGuard(m_aMutex);
1039 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
1040 aGuard.unlock();
1042 aUpdateHandler->setErrorMessage(rErrorMessage);
1043 setUIState(UPDATESTATE_ERROR_DOWNLOADING);
1047 void
1048 UpdateCheck::downloadProgressAt(sal_Int8 nPercent)
1050 std::unique_lock aGuard(m_aMutex);
1051 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
1052 aGuard.unlock();
1054 aUpdateHandler->setProgress(nPercent);
1055 setUIState(UPDATESTATE_DOWNLOADING);
1059 void
1060 UpdateCheck::downloadStarted(const OUString& rLocalFileName, sal_Int64 nFileSize)
1062 if ( nFileSize > 0 )
1064 std::scoped_lock aGuard(m_aMutex);
1066 rtl::Reference< UpdateCheckConfig > aModel(UpdateCheckConfig::get(m_xContext));
1067 aModel->storeLocalFileName(rLocalFileName, nFileSize);
1069 // Bring-up release note for position 1 ..
1070 const OUString aURL(getReleaseNote(m_aUpdateInfo, 1, aModel->isAutoDownloadEnabled()));
1071 if( !aURL.isEmpty() )
1072 showReleaseNote(aURL);
1077 void
1078 UpdateCheck::downloadFinished(const OUString& rLocalFileName)
1080 std::unique_lock aGuard(m_aMutex);
1082 // no more retries
1083 m_pThread->terminate();
1085 m_aImageName = getImageFromFileName(rLocalFileName);
1086 UpdateInfo aUpdateInfo(m_aUpdateInfo);
1088 aGuard.unlock();
1089 setUIState(UPDATESTATE_DOWNLOAD_AVAIL);
1091 // Bring-up release note for position 2 ..
1092 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext );
1093 const OUString aURL(getReleaseNote(aUpdateInfo, 2, rModel->isAutoDownloadEnabled()));
1094 if( !aURL.isEmpty() )
1095 showReleaseNote(aURL);
1099 void
1100 UpdateCheck::cancelDownload()
1102 shutdownThread(true);
1104 std::scoped_lock aGuard(m_aMutex);
1105 enableDownload(false);
1107 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext);
1109 OUString aLocalFile(rModel->getLocalFileName());
1110 rModel->clearLocalFileName();
1111 rModel->storeDownloadPaused(false);
1113 if( isObsoleteUpdateInfo(rModel->getUpdateEntryVersion()) )
1115 rModel->clearUpdateFound(); // This wasn't done during init yet ..
1116 m_aUpdateInfo = UpdateInfo();
1119 /*oslFileError rc =*/ osl_removeFile(aLocalFile.pData);
1120 // FIXME: error handling ..
1125 void
1126 UpdateCheck::showDialog(bool forceCheck)
1128 std::unique_lock aGuard(m_aMutex);
1130 bool update_found = !m_aUpdateInfo.BuildId.isEmpty();
1131 bool bSetUIState = ! m_aUpdateHandler.is();
1133 UpdateState eDialogState = UPDATESTATES_COUNT;
1135 switch( m_eState )
1137 case DISABLED:
1138 case CHECK_SCHEDULED:
1139 if( forceCheck || ! update_found ) // Run check when forced or if we did not find an update yet
1141 eDialogState = UPDATESTATE_CHECKING;
1142 bSetUIState = true;
1144 else if(m_aUpdateInfo.Sources[0].IsDirect)
1145 eDialogState = UPDATESTATE_UPDATE_AVAIL;
1146 else
1147 eDialogState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1148 break;
1150 case DOWNLOADING:
1151 eDialogState = UPDATESTATE_DOWNLOADING;
1152 break;
1154 case DOWNLOAD_PAUSED:
1155 eDialogState = UPDATESTATE_DOWNLOAD_PAUSED;
1156 break;
1158 case NOT_INITIALIZED:
1159 OSL_ASSERT( false );
1160 break;
1163 if( bSetUIState )
1165 aGuard.unlock();
1166 setUIState(eDialogState, true); // suppress bubble as Dialog will be visible soon
1167 aGuard.lock();
1170 getUpdateHandler()->setVisible();
1172 // Run check in separate thread ..
1173 if( UPDATESTATE_CHECKING == eDialogState )
1175 if( DISABLED == m_eState )
1177 // destructs itself when done, not cancellable for now ..
1178 new ManualUpdateCheckThread(m_aCondition, m_xContext);
1181 m_aCondition.set();
1185 void
1186 UpdateCheck::setUpdateInfo(const UpdateInfo& aInfo)
1188 std::unique_lock aGuard(m_aMutex);
1190 bool bSuppressBubble = aInfo.BuildId == m_aUpdateInfo.BuildId;
1191 m_aUpdateInfo = aInfo;
1193 OSL_ASSERT(DISABLED == m_eState || CHECK_SCHEDULED == m_eState);
1195 // Ignore leading non direct download if we get direct ones
1196 std::vector< DownloadSource >::iterator iter = std::find_if(m_aUpdateInfo.Sources.begin(), m_aUpdateInfo.Sources.end(),
1197 [](const DownloadSource& rSource) { return rSource.IsDirect; });
1199 if( (iter != m_aUpdateInfo.Sources.begin()) &&
1200 (iter != m_aUpdateInfo.Sources.end()) &&
1201 iter->IsDirect )
1203 m_aUpdateInfo.Sources.erase(m_aUpdateInfo.Sources.begin(), --iter);
1206 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *this);
1207 OSL_ASSERT( rModel.is() );
1209 // Decide whether to use alternate release note pos ..
1210 bool autoDownloadEnabled = rModel->isAutoDownloadEnabled();
1212 for (auto & elem : m_aUpdateInfo.ReleaseNotes)
1214 if( ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled && !elem.URL2.isEmpty())
1216 elem.URL = elem.URL2;
1217 elem.URL2.clear();
1218 elem.Pos = elem.Pos2;
1219 elem.Pos2 = 0;
1223 // do not move below store/clear ..
1224 rModel->updateLastChecked();
1226 UpdateState eUIState;
1227 if( !m_aUpdateInfo.Sources.empty() )
1229 rModel->storeUpdateFound(aInfo, getBuildId());
1231 if( m_aUpdateInfo.Sources[0].IsDirect )
1233 eUIState = UPDATESTATE_UPDATE_AVAIL;
1235 if( rModel->isAutoDownloadEnabled() )
1237 shutdownThread(false);
1238 eUIState = UPDATESTATE_DOWNLOADING;
1239 enableDownload(true);
1242 else
1243 eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1245 else
1247 eUIState = UPDATESTATE_NO_UPDATE_AVAIL;
1248 rModel->clearUpdateFound();
1251 aGuard.unlock();
1252 setUIState(eUIState, bSuppressBubble);
1255 bool UpdateCheck::hasOfficeUpdate() const
1257 std::unique_lock aGuard(m_aMutex);
1258 return m_aUpdateInfo.BuildId.getLength() > 0;
1261 void
1262 UpdateCheck::setCheckFailedState()
1264 setUIState(UPDATESTATE_ERROR_CHECKING);
1268 void UpdateCheck::handleMenuBarUI( const rtl::Reference< UpdateHandler >& rUpdateHandler,
1269 UpdateState& eState,
1270 bool suppressBubble )
1272 uno::Reference<beans::XPropertySet> xMenuBarUI( m_xMenuBarUI );
1274 if ( ( UPDATESTATE_NO_UPDATE_AVAIL == eState ) && m_bHasExtensionUpdate )
1275 eState = UPDATESTATE_EXT_UPD_AVAIL;
1277 if ( UPDATESTATE_EXT_UPD_AVAIL == eState )
1278 m_bShowExtUpdDlg = true;
1279 else
1280 m_bShowExtUpdDlg = false;
1282 if( xMenuBarUI.is() )
1284 if( UPDATESTATE_NO_UPDATE_AVAIL == eState )
1286 xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(false) );
1288 else
1290 xMenuBarUI->setPropertyValue( PROPERTY_TITLE, uno::Any(rUpdateHandler->getBubbleTitle(eState)) );
1291 xMenuBarUI->setPropertyValue( PROPERTY_TEXT, uno::Any(rUpdateHandler->getBubbleText(eState)) );
1293 if( ! suppressBubble && ( ! rUpdateHandler->isVisible() || rUpdateHandler->isMinimized() ) )
1294 xMenuBarUI->setPropertyValue( PROPERTY_SHOW_BUBBLE, uno::Any( true ) );
1296 if( UPDATESTATE_CHECKING != eState )
1297 xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(true) );
1303 void UpdateCheck::setUIState(UpdateState eState, bool suppressBubble)
1305 std::unique_lock aGuard(m_aMutex);
1307 if( ! m_xMenuBarUI.is() &&
1308 (DISABLED != m_eState) &&
1309 ( m_bHasExtensionUpdate || (UPDATESTATE_NO_UPDATE_AVAIL != eState)) &&
1310 (UPDATESTATE_CHECKING != eState) &&
1311 (UPDATESTATE_ERROR_CHECKING != eState)
1314 m_xMenuBarUI = createMenuBarUI(m_xContext, new MenuBarButtonJob(this));
1317 // Show bubble only when the status has changed
1318 if ( eState == m_eUpdateState )
1319 suppressBubble = true;
1320 else
1321 m_eUpdateState = eState;
1323 rtl::Reference<UpdateHandler> aUpdateHandler(getUpdateHandler());
1324 OSL_ASSERT( aUpdateHandler.is() );
1326 UpdateInfo aUpdateInfo(m_aUpdateInfo);
1327 OUString aImageName(m_aImageName);
1329 aGuard.unlock();
1331 handleMenuBarUI( aUpdateHandler, eState, suppressBubble );
1333 if( (UPDATESTATE_UPDATE_AVAIL == eState)
1334 || (UPDATESTATE_DOWNLOAD_PAUSED == eState)
1335 || (UPDATESTATE_DOWNLOADING == eState) )
1337 uno::Reference< uno::XComponentContext > xContext(m_xContext);
1339 OUString aDownloadDestination =
1340 UpdateCheckConfig::get(xContext, this)->getDownloadDestination();
1342 osl_getSystemPathFromFileURL(aDownloadDestination.pData, &aDownloadDestination.pData);
1344 aUpdateHandler->setDownloadPath(aDownloadDestination);
1346 else if( UPDATESTATE_DOWNLOAD_AVAIL == eState )
1348 aUpdateHandler->setDownloadFile(aImageName);
1351 aUpdateHandler->setDescription(aUpdateInfo.Description);
1352 aUpdateHandler->setNextVersion(aUpdateInfo.Version);
1353 aUpdateHandler->setState(eState);
1357 UpdateState
1358 UpdateCheck::getUIState(const UpdateInfo& rInfo)
1360 UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL;
1362 if( !rInfo.BuildId.isEmpty() )
1364 if( rInfo.Sources[0].IsDirect )
1365 eUIState = UPDATESTATE_UPDATE_AVAIL;
1366 else
1367 eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1370 return eUIState;
1374 void
1375 UpdateCheck::showReleaseNote(const OUString& rURL) const
1377 const uno::Reference< c3s::XSystemShellExecute > xShellExecute(
1378 c3s::SystemShellExecute::create( m_xContext ) );
1380 try {
1381 xShellExecute->execute(rURL, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY);
1382 } catch(const c3s::SystemShellExecuteException&) {
1387 bool
1388 UpdateCheck::storeReleaseNote(sal_Int8 nNum, const OUString &rURL)
1390 osl::FileBase::RC rc;
1391 OUString aTargetDir( UpdateCheckConfig::getAllUsersDirectory() + "/sun" );
1393 osl::Directory::createPath( aTargetDir );
1395 OUString aFileName = "releasenote" +
1396 OUString::number( nNum ) +
1397 ".url";
1399 OUString aFilePath;
1400 rc = osl::FileBase::getAbsoluteFileURL( aTargetDir, aFileName, aFilePath );
1401 if ( rc != osl::FileBase::E_None ) return false;
1403 osl::File::remove( aFilePath );
1405 // don't store empty release notes, but delete old ones
1406 if ( rURL.isEmpty() )
1407 return true;
1409 osl::File aFile( aFilePath );
1410 rc = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
1411 if ( rc != osl::FileBase::E_None ) return false;
1413 OString aLineBuf("[InternetShortcut]\r\n"_ostr);
1414 sal_uInt64 nWritten = 0;
1416 OUString aURL( rURL );
1417 #ifdef _WIN32
1418 rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten );
1419 if ( rc != osl::FileBase::E_None ) return false;
1420 aURL = "URL=" + rURL;
1421 #endif
1422 aLineBuf = OUStringToOString( aURL, RTL_TEXTENCODING_UTF8 );
1423 rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten );
1424 if ( rc != osl::FileBase::E_None ) return false;
1426 aFile.close();
1427 return true;
1431 void UpdateCheck::showExtensionDialog()
1433 uno::Reference< uno::XInterface > xService;
1435 if( ! m_xContext.is() )
1436 throw uno::RuntimeException(
1437 u"UpdateCheck::showExtensionDialog(): empty component context"_ustr, uno::Reference< uno::XInterface > () );
1439 uno::Reference< lang::XMultiComponentFactory > xServiceManager( m_xContext->getServiceManager() );
1440 if( !xServiceManager.is() )
1441 throw uno::RuntimeException(
1442 u"UpdateCheck::showExtensionDialog(): unable to obtain service manager from component context"_ustr, uno::Reference< uno::XInterface > () );
1444 xService = xServiceManager->createInstanceWithContext( u"com.sun.star.deployment.ui.PackageManagerDialog"_ustr, m_xContext );
1445 uno::Reference< task::XJobExecutor > xExecutable( xService, uno::UNO_QUERY );
1446 if ( xExecutable.is() )
1447 xExecutable->trigger( u"SHOW_UPDATE_DIALOG"_ustr );
1451 rtl::Reference<UpdateHandler>
1452 UpdateCheck::getUpdateHandler()
1454 std::scoped_lock aGuard(m_aMutex);
1456 if( ! m_aUpdateHandler.is() )
1457 m_aUpdateHandler = new UpdateHandler(m_xContext, this);
1459 return m_aUpdateHandler;
1463 uno::Reference< task::XInteractionHandler >
1464 UpdateCheck::getInteractionHandler() const
1466 std::scoped_lock aGuard(m_aMutex);
1468 uno::Reference< task::XInteractionHandler > xHandler;
1470 if( m_aUpdateHandler.is() && m_aUpdateHandler->isVisible() )
1471 xHandler = m_aUpdateHandler.get();
1473 return xHandler;
1477 bool
1478 UpdateCheck::isDialogShowing() const
1480 std::scoped_lock aGuard(m_aMutex);
1481 return m_aUpdateHandler.is() && m_aUpdateHandler->isVisible();
1485 void
1486 UpdateCheck::autoCheckStatusChanged(bool enabled)
1488 std::unique_lock aGuard(m_aMutex);
1490 if( (CHECK_SCHEDULED == m_eState) && !enabled )
1491 shutdownThread(false);
1493 if( (DISABLED == m_eState) || (CHECK_SCHEDULED == m_eState) )
1495 enableAutoCheck(enabled);
1496 UpdateState eState = getUIState(m_aUpdateInfo);
1497 aGuard.unlock();
1498 setUIState(eState);
1503 void
1504 UpdateCheck::autoCheckIntervalChanged()
1506 // just wake-up
1507 m_aCondition.set();
1510 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */