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 .
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>
48 #include <o3tl/safeCoInitUninit.hxx>
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
)
76 if( (pos
> 2) || !autoDownloadEnabled
|| elem
.URL2
.isEmpty() )
79 else if( (pos
== elem
.Pos2
) && ((1 == elem
.Pos
) || (2 == elem
.Pos
)) && autoDownloadEnabled
)
92 OUString
aPathVal(u
"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE("version") ":buildid}"_ustr
);
93 rtl::Bootstrap::expandMacros(aPathVal
);
98 bool isObsoleteUpdateInfo(std::u16string_view rBuildId
)
100 return rBuildId
!= getBuildId() && !rBuildId
.empty();
104 OUString
getImageFromFileName(const OUString
& aFile
)
107 OUString aUnpackPath
;
108 if( osl_getExecutableFile(&aUnpackPath
.pData
) == osl_Process_E_None
)
110 sal_uInt32 lastIndex
= aUnpackPath
.lastIndexOf('/');
113 aUnpackPath
= OUString::Concat(aUnpackPath
.subView( 0, lastIndex
+1 )) +
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
] () {
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
)
150 sal_uInt64 nBytesRead
= 0;
151 const sal_uInt64 nBytesToRead
= sizeof(szBuffer
) - 1;
154 while( osl_File_E_None
== osl_readFile(hOut
, szBuffer
, nBytesToRead
, &nBytesRead
) )
156 char *pc
= szBuffer
+ nBytesRead
;
161 while( ('\n' == *pc
) || ('\r' == *pc
) );
163 aImageName
+= OUString(szBuffer
, pc
- szBuffer
+ 1, osl_getThreadTextEncoding());
165 if( nBytesRead
< nBytesToRead
)
169 if( osl::FileBase::E_None
== osl::FileBase::getFileURLFromSystemPath(aImageName
, aImageName
) )
181 uno::Reference
< beans::XPropertySet
> createMenuBarUI(
182 const uno::Reference
< uno::XComponentContext
>& xContext
,
183 const uno::Reference
< task::XJob
>& xJob
)
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
) );
204 typedef sal_Bool (* OnlineCheckFunc
) ();
206 class UpdateCheckThread
: public WorkerThread
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();
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
);
231 /* Used to avoid dialup login windows (on platforms we know how to double this) */
232 static bool hasInternetConnection()
235 return WNT_hasInternetConnection();
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
);
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 */
255 { osl::MutexGuard
aGuard(m_aMutex
); m_xProvider
.clear(); };
260 osl::Condition
& m_aCondition
;
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
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
>
283 explicit MenuBarButtonJob(const rtl::Reference
< UpdateCheck
>& rUpdateCheck
);
286 virtual uno::Any SAL_CALL
execute(const uno::Sequence
<beans::NamedValue
>&) override
;
289 rtl::Reference
< UpdateCheck
> m_aUpdateCheck
;
292 class DownloadThread
: public WorkerThread
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
;
307 virtual ~DownloadThread() override
;
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)
328 // actually run the thread
333 UpdateCheckThread::~UpdateCheckThread()
339 UpdateCheckThread::terminate()
341 // Cancel potentially hanging http request ..
343 // .. before terminating
344 osl::Thread::terminate();
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() )
362 UpdateCheckThread::cancel()
364 uno::Reference
< deployment::XUpdateInformationProvider
> xProvider(getProvider());
370 void UpdateCheckThread::cancelAsSoonAsPossible() {
372 osl::MutexGuard
g(m_aMutex
);
373 m_cancelAsSoonAsPossible
= true;
379 UpdateCheckThread::runCheck( bool & rbExtensionsChecked
)
382 UpdateState eUIState
= UPDATESTATE_NO_UPDATE_AVAIL
;
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
);
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
417 UpdateCheckThread::onTerminated()
424 UpdateCheckThread::run()
426 osl_setThreadName("UpdateCheckThread");
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
) {
445 bool bExtensionsChecked
= false;
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,
453 * a2) manual check was requested while waiting for a retry,
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();
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();
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
) {
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
) )
521 tv
.Seconds
= nRetryInterval
[n
-1];
522 aResult
= m_aCondition
.wait(&tv
);
524 osl::MutexGuard
g(m_aMutex
);
525 if (m_cancelAsSoonAsPossible
) {
530 else // reset retry counter
533 bExtensionsChecked
= false;
538 catch(const uno::Exception
&) {
539 // Silently catch all errors
540 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
544 if (m_controller
.is()) {
545 m_controller
->notifyUpdateCheckFinished();
551 ManualUpdateCheckThread::run()
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
)
572 MenuBarButtonJob::execute(const uno::Sequence
<beans::NamedValue
>& )
574 if ( m_aUpdateCheck
->shouldShowExtUpdDlg() )
575 m_aUpdateCheck
->showExtensionDialog();
577 m_aUpdateCheck
->showDialog();
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
),
590 m_aDownload(xContext
, rHandler
)
596 DownloadThread::~DownloadThread()
602 DownloadThread::run()
604 osl_setThreadName("DownloadThread");
607 int nNbCallCoInitializeExForReinit
= 0;
608 // for SystemShellExecute
609 o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED
, nNbCallCoInitializeExForReinit
);
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
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
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
) )
636 tv
.Seconds
= nRetryInterval
[n
-1];
638 m_aCondition
.wait(&tv
);
642 // reset wait period after successful download
647 o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED
, nNbCallCoInitializeExForReinit
);
652 void DownloadThread::cancel()
657 rtl::Reference
< UpdateCheck
> aController(UpdateCheck::get());
658 aController
->cancelDownload();
662 void SAL_CALL
DownloadThread::suspend()
664 osl::Thread::suspend();
669 void SAL_CALL
DownloadThread::onTerminated()
675 } // anonymous namespace
677 UpdateCheck::UpdateCheck()
678 : m_eState(NOT_INITIALIZED
)
679 , m_eUpdateState(UPDATESTATES_COUNT
)
681 , m_bHasExtensionUpdate(false)
682 , m_bShowExtUpdDlg(false)
683 , m_updateCheckRunning(false)
687 UpdateCheck::~UpdateCheck() {}
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() );
778 enableAutoCheck(aModel
.isAutoCheckEnabled());
779 if ( bDownloadAvailable
)
780 setUIState( UPDATESTATE_DOWNLOAD_AVAIL
);
783 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
784 setUIState(getUIState(m_aUpdateInfo
));
793 UpdateCheck::cancel()
795 std::unique_lock
aGuard(m_aMutex
);
797 WorkerThread
*pThread
= m_pThread
;
798 UpdateState eUIState
= getUIState(m_aUpdateInfo
);
802 if( nullptr != pThread
)
805 setUIState(eUIState
);
810 UpdateCheck::download()
812 std::unique_lock
aGuard(m_aMutex
);
813 UpdateInfo
aInfo(m_aUpdateInfo
);
814 State eState
= m_eState
;
817 if (aInfo
.Sources
.empty())
819 SAL_WARN("extensions.update", "download called without source");
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
);
839 showReleaseNote(aInfo
.Sources
[0].URL
); // Display in browser
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
);
855 rModel
->storeDownloadPaused(true);
856 setUIState(UPDATESTATE_DOWNLOAD_PAUSED
);
861 UpdateCheck::resume()
863 std::unique_lock
aGuard(m_aMutex
);
865 if( nullptr != m_pThread
)
868 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
);
871 rModel
->storeDownloadPaused(false);
872 setUIState(UPDATESTATE_DOWNLOADING
);
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
);
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();
905 std::unique_lock
lock(m_aMutex
);
906 if (!m_updateCheckRunning
) {
909 m_updateCheckFinished
.wait(lock
);
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
;
923 if( nullptr != pThread
)
925 pThread
->terminate();
930 m_aCondition
.reset();
937 UpdateCheck::enableAutoCheck(bool enable
)
941 m_updateCheckRunning
= true;
942 m_pThread
= new UpdateCheckThread(m_aCondition
, m_xContext
, this);
945 m_eState
= enable
? CHECK_SCHEDULED
: DISABLED
;
950 UpdateCheck::enableDownload(bool enable
, bool paused
)
952 OSL_ASSERT(nullptr == m_pThread
);
956 m_pThread
= new DownloadThread(m_aCondition
, m_xContext
, this, m_aUpdateInfo
.Sources
[0].URL
);
957 State eState
= DISABLED
;
960 eState
= DOWNLOADING
;
964 eState
= DOWNLOAD_PAUSED
;
969 enableAutoCheck(UpdateCheckConfig::get(m_xContext
)->isAutoCheckEnabled());
976 UpdateCheck::downloadTargetExists(const OUString
& rFileName
)
978 std::unique_lock
aGuard(m_aMutex
);
980 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
981 UpdateState eUIState
= UPDATESTATE_DOWNLOADING
;
985 if( aUpdateHandler
->isVisible() )
987 cont
= aUpdateHandler
->showOverwriteWarning();
990 if( osl_File_E_None
!= osl_removeFile(rFileName
.pData
) )
992 // FIXME: error message
997 eUIState
= getUIState(m_aUpdateInfo
);
1001 m_aImageName
= getImageFromFileName(rFileName
);
1002 eUIState
= UPDATESTATE_DOWNLOAD_AVAIL
;
1007 shutdownThread(false);
1008 enableDownload(false);
1011 setUIState(eUIState
);
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
);
1036 UpdateCheck::downloadStalled(const OUString
& rErrorMessage
)
1038 std::unique_lock
aGuard(m_aMutex
);
1039 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
1042 aUpdateHandler
->setErrorMessage(rErrorMessage
);
1043 setUIState(UPDATESTATE_ERROR_DOWNLOADING
);
1048 UpdateCheck::downloadProgressAt(sal_Int8 nPercent
)
1050 std::unique_lock
aGuard(m_aMutex
);
1051 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
1054 aUpdateHandler
->setProgress(nPercent
);
1055 setUIState(UPDATESTATE_DOWNLOADING
);
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
);
1078 UpdateCheck::downloadFinished(const OUString
& rLocalFileName
)
1080 std::unique_lock
aGuard(m_aMutex
);
1083 m_pThread
->terminate();
1085 m_aImageName
= getImageFromFileName(rLocalFileName
);
1086 UpdateInfo
aUpdateInfo(m_aUpdateInfo
);
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
);
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 ..
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
;
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
;
1144 else if(m_aUpdateInfo
.Sources
[0].IsDirect
)
1145 eDialogState
= UPDATESTATE_UPDATE_AVAIL
;
1147 eDialogState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1151 eDialogState
= UPDATESTATE_DOWNLOADING
;
1154 case DOWNLOAD_PAUSED
:
1155 eDialogState
= UPDATESTATE_DOWNLOAD_PAUSED
;
1158 case NOT_INITIALIZED
:
1159 OSL_ASSERT( false );
1166 setUIState(eDialogState
, true); // suppress bubble as Dialog will be visible soon
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
);
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()) &&
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
;
1218 elem
.Pos
= elem
.Pos2
;
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);
1243 eUIState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1247 eUIState
= UPDATESTATE_NO_UPDATE_AVAIL
;
1248 rModel
->clearUpdateFound();
1252 setUIState(eUIState
, bSuppressBubble
);
1255 bool UpdateCheck::hasOfficeUpdate() const
1257 std::unique_lock
aGuard(m_aMutex
);
1258 return m_aUpdateInfo
.BuildId
.getLength() > 0;
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;
1280 m_bShowExtUpdDlg
= false;
1282 if( xMenuBarUI
.is() )
1284 if( UPDATESTATE_NO_UPDATE_AVAIL
== eState
)
1286 xMenuBarUI
->setPropertyValue( PROPERTY_SHOW_MENUICON
, uno::Any(false) );
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;
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
);
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
);
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
;
1367 eUIState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1375 UpdateCheck::showReleaseNote(const OUString
& rURL
) const
1377 const uno::Reference
< c3s::XSystemShellExecute
> xShellExecute(
1378 c3s::SystemShellExecute::create( m_xContext
) );
1381 xShellExecute
->execute(rURL
, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY
);
1382 } catch(const c3s::SystemShellExecuteException
&) {
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
) +
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() )
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
);
1418 rc
= aFile
.write( aLineBuf
.getStr(), aLineBuf
.getLength(), nWritten
);
1419 if ( rc
!= osl::FileBase::E_None
) return false;
1420 aURL
= "URL=" + rURL
;
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;
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();
1478 UpdateCheck::isDialogShowing() const
1480 std::scoped_lock
aGuard(m_aMutex
);
1481 return m_aUpdateHandler
.is() && m_aUpdateHandler
->isVisible();
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
);
1504 UpdateCheck::autoCheckIntervalChanged()
1510 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */