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
= com::sun::star::beans
;
57 namespace deployment
= com::sun::star::deployment
;
58 namespace frame
= com::sun::star::frame
;
59 namespace lang
= com::sun::star::lang
;
60 namespace c3s
= com::sun::star::system
;
61 namespace task
= com::sun::star::task
;
62 namespace uno
= com::sun::star::uno
;
64 constexpr OUStringLiteral PROPERTY_TITLE
= u
"BubbleHeading";
65 constexpr OUStringLiteral PROPERTY_TEXT
= u
"BubbleText";
66 constexpr OUStringLiteral PROPERTY_SHOW_BUBBLE
= u
"BubbleVisible";
67 constexpr OUStringLiteral PROPERTY_CLICK_HDL
= u
"MenuClickHDL";
68 constexpr OUString PROPERTY_SHOW_MENUICON
= u
"MenuIconVisible"_ustr
;
70 // Returns the URL of the release note for the given position
71 OUString
getReleaseNote(const UpdateInfo
& rInfo
, sal_uInt8 pos
, bool autoDownloadEnabled
)
73 for (auto const& elem
: rInfo
.ReleaseNotes
)
77 if( (pos
> 2) || !autoDownloadEnabled
|| elem
.URL2
.isEmpty() )
80 else if( (pos
== elem
.Pos2
) && ((1 == elem
.Pos
) || (2 == elem
.Pos
)) && autoDownloadEnabled
)
93 OUString
aPathVal("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE("version") ":buildid}");
94 rtl::Bootstrap::expandMacros(aPathVal
);
99 bool isObsoleteUpdateInfo(std::u16string_view rBuildId
)
101 return rBuildId
!= getBuildId() && !rBuildId
.empty();
105 OUString
getImageFromFileName(const OUString
& aFile
)
108 OUString aUnpackPath
;
109 if( osl_getExecutableFile(&aUnpackPath
.pData
) == osl_Process_E_None
)
111 sal_uInt32 lastIndex
= aUnpackPath
.lastIndexOf('/');
114 aUnpackPath
= OUString::Concat(aUnpackPath
.subView( 0, lastIndex
+1 )) +
118 oslFileHandle hOut
= nullptr;
119 oslProcess hProcess
= nullptr;
121 OUString aSystemPath
;
122 osl::File::getSystemPathFromFileURL(aFile
, aSystemPath
);
124 oslProcessError rc
= osl_executeProcess_WithRedirectedIO(
125 aUnpackPath
.pData
, // [in] Image name
126 &aSystemPath
.pData
, 1, // [in] Arguments
127 osl_Process_WAIT
| osl_Process_NORMAL
, // [in] Options
128 nullptr, // [in] Security
129 nullptr, // [in] Working directory
130 nullptr, 0, // [in] Environment variables
131 &hProcess
, // [out] Process handle
132 nullptr, &hOut
, nullptr // [out] File handles for redirected I/O
135 if( osl_Process_E_None
== rc
)
137 // Create a guard to ensure correct cleanup in its dtor in any case
138 comphelper::ScopeGuard
g([hOut
, hProcess
] () {
140 osl_freeProcessHandle(hProcess
);
143 oslProcessInfo aInfo
;
144 aInfo
.Size
= sizeof(oslProcessInfo
);
146 if( osl_Process_E_None
== osl_getProcessInfo(hProcess
, osl_Process_EXITCODE
, &aInfo
) )
148 if( 0 == aInfo
.Code
)
151 sal_uInt64 nBytesRead
= 0;
152 const sal_uInt64 nBytesToRead
= sizeof(szBuffer
) - 1;
155 while( osl_File_E_None
== osl_readFile(hOut
, szBuffer
, nBytesToRead
, &nBytesRead
) )
157 char *pc
= szBuffer
+ nBytesRead
;
162 while( ('\n' == *pc
) || ('\r' == *pc
) );
164 aImageName
+= OUString(szBuffer
, pc
- szBuffer
+ 1, osl_getThreadTextEncoding());
166 if( nBytesRead
< nBytesToRead
)
170 if( osl::FileBase::E_None
== osl::FileBase::getFileURLFromSystemPath(aImageName
, aImageName
) )
182 uno::Reference
< beans::XPropertySet
> createMenuBarUI(
183 const uno::Reference
< uno::XComponentContext
>& xContext
,
184 const uno::Reference
< task::XJob
>& xJob
)
187 throw uno::RuntimeException(
188 "UpdateCheckJob: empty component context", uno::Reference
< uno::XInterface
> () );
190 uno::Reference
< lang::XMultiComponentFactory
> xServiceManager(xContext
->getServiceManager());
191 if( !xServiceManager
.is() )
192 throw uno::RuntimeException(
193 "UpdateCheckJob: unable to obtain service manager from component context", uno::Reference
< uno::XInterface
> () );
195 uno::Reference
< beans::XPropertySet
> xMenuBarUI(
196 xServiceManager
->createInstanceWithContext( "com.sun.star.setup.UpdateCheckUI", xContext
),
197 uno::UNO_QUERY_THROW
);
199 xMenuBarUI
->setPropertyValue( PROPERTY_CLICK_HDL
, uno::Any( xJob
) );
205 typedef sal_Bool (* OnlineCheckFunc
) ();
207 class UpdateCheckThread
: public WorkerThread
211 UpdateCheckThread( osl::Condition
& rCondition
,
212 const uno::Reference
<uno::XComponentContext
>& xContext
,
213 rtl::Reference
<UpdateCheck
> const & controller
);
215 virtual void SAL_CALL
join() override
;
216 virtual void SAL_CALL
terminate() override
;
217 virtual void cancel() override
;
219 void cancelAsSoonAsPossible();
222 virtual ~UpdateCheckThread() override
;
224 virtual void SAL_CALL
run() override
;
225 virtual void SAL_CALL
onTerminated() override
;
227 /* Wrapper around checkForUpdates */
228 bool runCheck( bool & rbExtensionsChecked
);
232 /* Used to avoid dialup login windows (on platforms we know how to double this) */
233 static bool hasInternetConnection()
236 return WNT_hasInternetConnection();
242 /* Creates a new instance of UpdateInformationProvider and returns this instance */
243 uno::Reference
<deployment::XUpdateInformationProvider
> createProvider()
245 osl::MutexGuard
aGuard(m_aMutex
);
246 m_xProvider
= deployment::UpdateInformationProvider::create(m_xContext
);
250 /* Returns the remembered instance of UpdateInformationProvider if any */
251 uno::Reference
<deployment::XUpdateInformationProvider
> getProvider()
252 { osl::MutexGuard
aGuard(m_aMutex
); return m_xProvider
; };
254 /* Releases the remembered instance of UpdateInformationProvider if any */
256 { osl::MutexGuard
aGuard(m_aMutex
); m_xProvider
.clear(); };
261 osl::Condition
& m_aCondition
;
264 const uno::Reference
<uno::XComponentContext
> m_xContext
;
265 uno::Reference
<deployment::XUpdateInformationProvider
> m_xProvider
;
266 rtl::Reference
<UpdateCheck
> m_controller
;
267 bool m_cancelAsSoonAsPossible
;
271 class ManualUpdateCheckThread
: public UpdateCheckThread
274 ManualUpdateCheckThread( osl::Condition
& rCondition
, const uno::Reference
<uno::XComponentContext
>& xContext
) :
275 UpdateCheckThread(rCondition
, xContext
, {}) {};
277 virtual void SAL_CALL
run() override
;
281 class MenuBarButtonJob
: public ::cppu::WeakImplHelper
< task::XJob
>
284 explicit MenuBarButtonJob(const rtl::Reference
< UpdateCheck
>& rUpdateCheck
);
287 virtual uno::Any SAL_CALL
execute(const uno::Sequence
<beans::NamedValue
>&) override
;
290 rtl::Reference
< UpdateCheck
> m_aUpdateCheck
;
293 class DownloadThread
: public WorkerThread
297 osl::Condition
& rCondition
,
298 const uno::Reference
<uno::XComponentContext
>& xContext
,
299 const rtl::Reference
< DownloadInteractionHandler
>& rHandler
,
300 const OUString
& rURL
);
302 virtual void SAL_CALL
run() override
;
303 virtual void cancel() override
;
304 virtual void SAL_CALL
suspend() override
;
305 virtual void SAL_CALL
onTerminated() override
;
308 virtual ~DownloadThread() override
;
311 osl::Condition
& m_aCondition
;
312 const uno::Reference
<uno::XComponentContext
> m_xContext
;
313 const OUString m_aURL
;
314 Download m_aDownload
;
319 UpdateCheckThread::UpdateCheckThread( osl::Condition
& rCondition
,
320 const uno::Reference
<uno::XComponentContext
>& xContext
,
321 rtl::Reference
<UpdateCheck
> const & controller
) :
322 m_aCondition(rCondition
),
323 m_xContext(xContext
),
324 m_controller(controller
),
325 m_cancelAsSoonAsPossible(false)
329 // actually run the thread
334 UpdateCheckThread::~UpdateCheckThread()
340 UpdateCheckThread::terminate()
342 // Cancel potentially hanging http request ..
344 // .. before terminating
345 osl::Thread::terminate();
350 UpdateCheckThread::join()
352 uno::Reference
< deployment::XUpdateInformationProvider
> xProvider(getProvider());
354 // do not join during an update check until #i73893# is fixed
355 if( ! xProvider
.is() )
363 UpdateCheckThread::cancel()
365 uno::Reference
< deployment::XUpdateInformationProvider
> xProvider(getProvider());
371 void UpdateCheckThread::cancelAsSoonAsPossible() {
373 osl::MutexGuard
g(m_aMutex
);
374 m_cancelAsSoonAsPossible
= true;
380 UpdateCheckThread::runCheck( bool & rbExtensionsChecked
)
383 UpdateState eUIState
= UPDATESTATE_NO_UPDATE_AVAIL
;
386 rtl::Reference
< UpdateCheck
> aController(UpdateCheck::get());
388 if( checkForUpdates(aInfo
, m_xContext
, aController
->getInteractionHandler(), createProvider()) )
390 aController
->setUpdateInfo(aInfo
);
391 eUIState
= UpdateCheck::getUIState(aInfo
);
395 aController
->setCheckFailedState();
397 // We will only look for extension updates, when there is no 'check for office updates' dialog open
398 // and when there was no office update found
399 if ( ( eUIState
!= UPDATESTATE_UPDATE_AVAIL
) &&
400 ( eUIState
!= UPDATESTATE_UPDATE_NO_DOWNLOAD
) &&
401 !aController
->isDialogShowing() &&
402 !rbExtensionsChecked
)
404 bool bHasExtensionUpdates
= checkForExtensionUpdates( m_xContext
);
405 aController
->setHasExtensionUpdates( bHasExtensionUpdates
);
406 if ( bHasExtensionUpdates
)
407 aController
->setUIState( UPDATESTATE_EXT_UPD_AVAIL
);
408 rbExtensionsChecked
= true;
411 // joining with this thread is safe again
418 UpdateCheckThread::onTerminated()
425 UpdateCheckThread::run()
427 osl_setThreadName("UpdateCheckThread");
430 TimeValue nExtCheckTime
;
431 osl_getSystemTime( &nExtCheckTime
);
433 osl::Condition::Result aResult
= osl::Condition::result_timeout
;
434 TimeValue tv
= { 10, 0 };
436 // Initial wait to avoid doing further time consuming tasks during start-up
437 aResult
= m_aCondition
.wait(&tv
);
439 osl::MutexGuard
g(m_aMutex
);
440 if (m_cancelAsSoonAsPossible
) {
446 bool bExtensionsChecked
= false;
451 * a) manual check requested from auto check thread - "last check" should not be checked (one time)
452 * a1) manual check was requested in the middle of a running auto check,
454 * a2) manual check was requested while waiting for a retry,
456 * a3) manual check was requested while waiting for time to next
457 * scheduled check elapsing, condition is set
458 * a4) manual check was requested during initial wait, condition is set
459 * b) check interval got changed, condition may be set - same sub-cases as a),
460 * but "last check" should be honored
461 * c) normal auto check mode, condition not set - "last check" should be honored
464 // Accessing const members without synchronization
465 rtl::Reference
< UpdateCheck
> aController(UpdateCheck::get());
466 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
, *aController
);
468 // FIXME: remember last & offset ?
469 sal_Int64 last
= rModel
->getLastChecked();
470 sal_Int64 offset
= rModel
->getCheckInterval();
474 // last == 0 means check immediately
475 bool checkNow
= last
<= 0;
477 // Reset the condition to avoid busy loops
478 if( osl::Condition::result_ok
== aResult
)
480 m_aCondition
.reset();
481 aResult
= osl::Condition::result_timeout
;
482 checkNow
= aController
->isDialogShowing();
487 osl_getSystemTime(&systime
);
489 // Go back to sleep until time has elapsed
490 sal_Int64 next
= last
+ offset
;
491 if( last
+ offset
> systime
.Seconds
)
493 // This can not be > 32 Bit for now ..
494 tv
.Seconds
= static_cast< sal_Int32
> (next
- systime
.Seconds
);
495 aResult
= m_aCondition
.wait(&tv
);
497 osl::MutexGuard
g(m_aMutex
);
498 if (m_cancelAsSoonAsPossible
) {
506 static sal_uInt8 n
= 0;
508 if( ! hasInternetConnection() || ! runCheck( bExtensionsChecked
) )
510 // the extension update check should be independent from the office update check
512 osl_getSystemTime( &systime
);
513 if ( nExtCheckTime
.Seconds
+ offset
< systime
.Seconds
)
514 bExtensionsChecked
= false;
516 // Increase next by 15, 60, .. minutes
517 static const sal_Int32 nRetryInterval
[] = { 900, 3600, 14400, 86400 };
519 if( n
< std::size(nRetryInterval
) )
522 tv
.Seconds
= nRetryInterval
[n
-1];
523 aResult
= m_aCondition
.wait(&tv
);
525 osl::MutexGuard
g(m_aMutex
);
526 if (m_cancelAsSoonAsPossible
) {
531 else // reset retry counter
534 bExtensionsChecked
= false;
539 catch(const uno::Exception
&) {
540 // Silently catch all errors
541 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
545 if (m_controller
.is()) {
546 m_controller
->notifyUpdateCheckFinished();
552 ManualUpdateCheckThread::run()
555 bool bExtensionsChecked
= false;
556 runCheck( bExtensionsChecked
);
557 m_aCondition
.reset();
559 catch(const uno::Exception
&) {
560 // Silently catch all errors
561 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
566 MenuBarButtonJob::MenuBarButtonJob(const rtl::Reference
< UpdateCheck
>& rUpdateCheck
) :
567 m_aUpdateCheck(rUpdateCheck
)
573 MenuBarButtonJob::execute(const uno::Sequence
<beans::NamedValue
>& )
575 if ( m_aUpdateCheck
->shouldShowExtUpdDlg() )
576 m_aUpdateCheck
->showExtensionDialog();
578 m_aUpdateCheck
->showDialog();
584 DownloadThread::DownloadThread(osl::Condition
& rCondition
,
585 const uno::Reference
<uno::XComponentContext
>& xContext
,
586 const rtl::Reference
< DownloadInteractionHandler
>& rHandler
,
587 const OUString
& rURL
) :
588 m_aCondition(rCondition
),
589 m_xContext(xContext
),
591 m_aDownload(xContext
, rHandler
)
597 DownloadThread::~DownloadThread()
603 DownloadThread::run()
605 osl_setThreadName("DownloadThread");
608 int nNbCallCoInitializeExForReinit
= 0;
609 // for SystemShellExecute
610 o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED
, nNbCallCoInitializeExForReinit
);
615 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
);
617 OUString aLocalFile
= rModel
->getLocalFileName();
618 OUString aDownloadDest
= rModel
->getDownloadDestination();
620 // release config class for now
623 static sal_uInt8 n
= 0;
624 if( ! m_aDownload
.start(m_aURL
, aLocalFile
, aDownloadDest
) )
626 // retry every 15s unless the dialog is not visible
629 if( ! UpdateCheck::get()->isDialogShowing() )
631 // Increase next by 1, 5, 15, 60, .. minutes
632 static const sal_Int16 nRetryInterval
[] = { 60, 300, 900, 3600 };
634 if( n
< std::size(nRetryInterval
) )
637 tv
.Seconds
= nRetryInterval
[n
-1];
639 m_aCondition
.wait(&tv
);
643 // reset wait period after successful download
648 o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED
, nNbCallCoInitializeExForReinit
);
653 void DownloadThread::cancel()
658 rtl::Reference
< UpdateCheck
> aController(UpdateCheck::get());
659 aController
->cancelDownload();
663 void SAL_CALL
DownloadThread::suspend()
665 osl::Thread::suspend();
670 void SAL_CALL
DownloadThread::onTerminated()
676 } // anonymous namespace
678 UpdateCheck::UpdateCheck()
679 : m_eState(NOT_INITIALIZED
)
680 , m_eUpdateState(UPDATESTATES_COUNT
)
682 , m_bHasExtensionUpdate(false)
683 , m_bShowExtUpdDlg(false)
684 , m_updateCheckRunning(false)
688 UpdateCheck::~UpdateCheck() {}
691 UpdateCheck::initialize(const uno::Sequence
< beans::NamedValue
>& rValues
,
692 const uno::Reference
<uno::XComponentContext
>& xContext
)
694 std::scoped_lock
aGuard(m_aMutex
);
696 if( NOT_INITIALIZED
== m_eState
)
698 NamedValueByNameAccess
aNameAccess(rValues
);
699 UpdateCheckROModel
aModel( aNameAccess
);
700 m_xContext
= xContext
;
702 OUString aUpdateEntryVersion
= aModel
.getUpdateEntryVersion();
704 aModel
.getUpdateEntry(m_aUpdateInfo
);
706 bool obsoleteUpdateInfo
= isObsoleteUpdateInfo(aUpdateEntryVersion
);
707 bool bContinueDownload
= false;
708 bool bDownloadAvailable
= false;
710 m_bHasExtensionUpdate
= checkForPendingUpdates( xContext
);
711 m_bShowExtUpdDlg
= false;
713 OUString aLocalFileName
= aModel
.getLocalFileName();
715 if( !aLocalFileName
.isEmpty() )
717 bContinueDownload
= true;
719 // Try to get the number of bytes already on disk
720 osl::DirectoryItem aDirectoryItem
;
721 if( osl::DirectoryItem::E_None
== osl::DirectoryItem::get(aLocalFileName
, aDirectoryItem
) )
723 osl::FileStatus
aFileStatus(osl_FileStatus_Mask_FileSize
);
724 if( osl::DirectoryItem::E_None
== aDirectoryItem
.getFileStatus(aFileStatus
) )
726 sal_Int64 nDownloadSize
= aModel
.getDownloadSize();
727 sal_Int64 nFileSize
= aFileStatus
.getFileSize();
729 if( nDownloadSize
> 0 )
731 if ( nDownloadSize
<= nFileSize
) // we have already downloaded everything
733 bContinueDownload
= false;
734 bDownloadAvailable
= true;
735 m_aImageName
= getImageFromFileName( aLocalFileName
);
737 else // Calculate initial percent value.
739 sal_Int32 nPercent
= static_cast<sal_Int32
>(100 * nFileSize
/ nDownloadSize
);
740 getUpdateHandler()->setProgress( nPercent
);
746 if ( bContinueDownload
)
748 bool downloadPaused
= aModel
.isDownloadPaused();
750 enableDownload(true, downloadPaused
);
751 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
752 setUIState(downloadPaused
? UPDATESTATE_DOWNLOAD_PAUSED
: UPDATESTATE_DOWNLOADING
);
756 if ( !bContinueDownload
)
758 // We do this intentionally only if no download is in progress ..
759 if( obsoleteUpdateInfo
)
761 // Bring-up release note for position 5 ..
762 const OUString
aURL(getReleaseNote(m_aUpdateInfo
, 5));
763 if( !aURL
.isEmpty() )
764 showReleaseNote(aURL
);
766 // Data is outdated, probably due to installed update
767 rtl::Reference
< UpdateCheckConfig
> aConfig
= UpdateCheckConfig::get( xContext
, *this );
768 aConfig
->clearUpdateFound();
769 aConfig
->clearLocalFileName();
772 m_aUpdateInfo
= UpdateInfo();
773 // Remove outdated release notes
774 storeReleaseNote( 1, OUString() );
775 storeReleaseNote( 2, OUString() );
779 enableAutoCheck(aModel
.isAutoCheckEnabled());
780 if ( bDownloadAvailable
)
781 setUIState( UPDATESTATE_DOWNLOAD_AVAIL
);
784 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
785 setUIState(getUIState(m_aUpdateInfo
));
794 UpdateCheck::cancel()
796 std::unique_lock
aGuard(m_aMutex
);
798 WorkerThread
*pThread
= m_pThread
;
799 UpdateState eUIState
= getUIState(m_aUpdateInfo
);
803 if( nullptr != pThread
)
806 setUIState(eUIState
);
811 UpdateCheck::download()
813 std::unique_lock
aGuard(m_aMutex
);
814 UpdateInfo
aInfo(m_aUpdateInfo
);
815 State eState
= m_eState
;
818 if (aInfo
.Sources
.empty())
820 SAL_WARN("extensions.update", "download called without source");
824 if( aInfo
.Sources
[0].IsDirect
)
826 // Ignore second click of a double click
827 if( DOWNLOADING
!= eState
)
829 shutdownThread(true);
832 std::scoped_lock
aGuard2(m_aMutex
);
833 enableDownload(true);
835 setUIState(UPDATESTATE_DOWNLOADING
);
840 showReleaseNote(aInfo
.Sources
[0].URL
); // Display in browser
848 std::unique_lock
aGuard(m_aMutex
);
850 if( nullptr != m_pThread
)
851 m_pThread
->suspend();
853 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
);
856 rModel
->storeDownloadPaused(true);
857 setUIState(UPDATESTATE_DOWNLOAD_PAUSED
);
862 UpdateCheck::resume()
864 std::unique_lock
aGuard(m_aMutex
);
866 if( nullptr != m_pThread
)
869 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
);
872 rModel
->storeDownloadPaused(false);
873 setUIState(UPDATESTATE_DOWNLOADING
);
878 UpdateCheck::closeAfterFailure()
880 std::unique_lock
aGuard(m_aMutex
);
882 if ( ( m_eState
== DISABLED
) || ( m_eState
== CHECK_SCHEDULED
) )
884 const UpdateState eUIState
= getUIState( m_aUpdateInfo
);
886 setUIState( eUIState
, true );
890 void UpdateCheck::notifyUpdateCheckFinished() {
891 std::scoped_lock
l(m_aMutex
);
892 m_updateCheckRunning
= false;
893 m_updateCheckFinished
.notify_all();
896 void UpdateCheck::waitForUpdateCheckFinished() {
897 UpdateCheckThread
* thread
;
899 std::scoped_lock
l(m_aMutex
);
900 thread
= dynamic_cast<UpdateCheckThread
*>(m_pThread
);
902 if (thread
!= nullptr) {
903 thread
->cancelAsSoonAsPossible();
906 std::unique_lock
lock(m_aMutex
);
907 if (!m_updateCheckRunning
) {
910 m_updateCheckFinished
.wait(lock
);
915 UpdateCheck::shutdownThread(bool join
)
917 std::unique_lock
aGuard(m_aMutex
);
919 // copy thread object pointer to stack
920 osl::Thread
*pThread
= m_pThread
;
924 if( nullptr != pThread
)
926 pThread
->terminate();
931 m_aCondition
.reset();
938 UpdateCheck::enableAutoCheck(bool enable
)
942 m_updateCheckRunning
= true;
943 m_pThread
= new UpdateCheckThread(m_aCondition
, m_xContext
, this);
946 m_eState
= enable
? CHECK_SCHEDULED
: DISABLED
;
951 UpdateCheck::enableDownload(bool enable
, bool paused
)
953 OSL_ASSERT(nullptr == m_pThread
);
957 m_pThread
= new DownloadThread(m_aCondition
, m_xContext
, this, m_aUpdateInfo
.Sources
[0].URL
);
958 State eState
= DISABLED
;
961 eState
= DOWNLOADING
;
965 eState
= DOWNLOAD_PAUSED
;
970 enableAutoCheck(UpdateCheckConfig::get(m_xContext
)->isAutoCheckEnabled());
977 UpdateCheck::downloadTargetExists(const OUString
& rFileName
)
979 std::unique_lock
aGuard(m_aMutex
);
981 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
982 UpdateState eUIState
= UPDATESTATE_DOWNLOADING
;
986 if( aUpdateHandler
->isVisible() )
988 cont
= aUpdateHandler
->showOverwriteWarning();
991 if( osl_File_E_None
!= osl_removeFile(rFileName
.pData
) )
993 // FIXME: error message
998 eUIState
= getUIState(m_aUpdateInfo
);
1002 m_aImageName
= getImageFromFileName(rFileName
);
1003 eUIState
= UPDATESTATE_DOWNLOAD_AVAIL
;
1008 shutdownThread(false);
1009 enableDownload(false);
1012 setUIState(eUIState
);
1019 bool UpdateCheck::checkDownloadDestination( const OUString
& rFileName
)
1021 std::scoped_lock
aGuard(m_aMutex
);
1023 rtl::Reference
< UpdateHandler
> aUpdateHandler( getUpdateHandler() );
1025 bool bReload
= false;
1027 if( aUpdateHandler
->isVisible() )
1029 bReload
= aUpdateHandler
->showOverwriteWarning( rFileName
);
1037 UpdateCheck::downloadStalled(const OUString
& rErrorMessage
)
1039 std::unique_lock
aGuard(m_aMutex
);
1040 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
1043 aUpdateHandler
->setErrorMessage(rErrorMessage
);
1044 setUIState(UPDATESTATE_ERROR_DOWNLOADING
);
1049 UpdateCheck::downloadProgressAt(sal_Int8 nPercent
)
1051 std::unique_lock
aGuard(m_aMutex
);
1052 rtl::Reference
< UpdateHandler
> aUpdateHandler(getUpdateHandler());
1055 aUpdateHandler
->setProgress(nPercent
);
1056 setUIState(UPDATESTATE_DOWNLOADING
);
1061 UpdateCheck::downloadStarted(const OUString
& rLocalFileName
, sal_Int64 nFileSize
)
1063 if ( nFileSize
> 0 )
1065 std::scoped_lock
aGuard(m_aMutex
);
1067 rtl::Reference
< UpdateCheckConfig
> aModel(UpdateCheckConfig::get(m_xContext
));
1068 aModel
->storeLocalFileName(rLocalFileName
, nFileSize
);
1070 // Bring-up release note for position 1 ..
1071 const OUString
aURL(getReleaseNote(m_aUpdateInfo
, 1, aModel
->isAutoDownloadEnabled()));
1072 if( !aURL
.isEmpty() )
1073 showReleaseNote(aURL
);
1079 UpdateCheck::downloadFinished(const OUString
& rLocalFileName
)
1081 std::unique_lock
aGuard(m_aMutex
);
1084 m_pThread
->terminate();
1086 m_aImageName
= getImageFromFileName(rLocalFileName
);
1087 UpdateInfo
aUpdateInfo(m_aUpdateInfo
);
1090 setUIState(UPDATESTATE_DOWNLOAD_AVAIL
);
1092 // Bring-up release note for position 2 ..
1093 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get( m_xContext
);
1094 const OUString
aURL(getReleaseNote(aUpdateInfo
, 2, rModel
->isAutoDownloadEnabled()));
1095 if( !aURL
.isEmpty() )
1096 showReleaseNote(aURL
);
1101 UpdateCheck::cancelDownload()
1103 shutdownThread(true);
1105 std::scoped_lock
aGuard(m_aMutex
);
1106 enableDownload(false);
1108 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
);
1110 OUString
aLocalFile(rModel
->getLocalFileName());
1111 rModel
->clearLocalFileName();
1112 rModel
->storeDownloadPaused(false);
1114 if( isObsoleteUpdateInfo(rModel
->getUpdateEntryVersion()) )
1116 rModel
->clearUpdateFound(); // This wasn't done during init yet ..
1117 m_aUpdateInfo
= UpdateInfo();
1120 /*oslFileError rc =*/ osl_removeFile(aLocalFile
.pData
);
1121 // FIXME: error handling ..
1127 UpdateCheck::showDialog(bool forceCheck
)
1129 std::unique_lock
aGuard(m_aMutex
);
1131 bool update_found
= !m_aUpdateInfo
.BuildId
.isEmpty();
1132 bool bSetUIState
= ! m_aUpdateHandler
.is();
1134 UpdateState eDialogState
= UPDATESTATES_COUNT
;
1139 case CHECK_SCHEDULED
:
1140 if( forceCheck
|| ! update_found
) // Run check when forced or if we did not find an update yet
1142 eDialogState
= UPDATESTATE_CHECKING
;
1145 else if(m_aUpdateInfo
.Sources
[0].IsDirect
)
1146 eDialogState
= UPDATESTATE_UPDATE_AVAIL
;
1148 eDialogState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1152 eDialogState
= UPDATESTATE_DOWNLOADING
;
1155 case DOWNLOAD_PAUSED
:
1156 eDialogState
= UPDATESTATE_DOWNLOAD_PAUSED
;
1159 case NOT_INITIALIZED
:
1160 OSL_ASSERT( false );
1167 setUIState(eDialogState
, true); // suppress bubble as Dialog will be visible soon
1171 getUpdateHandler()->setVisible();
1173 // Run check in separate thread ..
1174 if( UPDATESTATE_CHECKING
== eDialogState
)
1176 if( DISABLED
== m_eState
)
1178 // destructs itself when done, not cancellable for now ..
1179 new ManualUpdateCheckThread(m_aCondition
, m_xContext
);
1188 UpdateCheck::setUpdateInfo(const UpdateInfo
& aInfo
)
1190 std::unique_lock
aGuard(m_aMutex
);
1192 bool bSuppressBubble
= aInfo
.BuildId
== m_aUpdateInfo
.BuildId
;
1193 m_aUpdateInfo
= aInfo
;
1195 OSL_ASSERT(DISABLED
== m_eState
|| CHECK_SCHEDULED
== m_eState
);
1197 // Ignore leading non direct download if we get direct ones
1198 std::vector
< DownloadSource
>::iterator iter
= std::find_if(m_aUpdateInfo
.Sources
.begin(), m_aUpdateInfo
.Sources
.end(),
1199 [](const DownloadSource
& rSource
) { return rSource
.IsDirect
; });
1201 if( (iter
!= m_aUpdateInfo
.Sources
.begin()) &&
1202 (iter
!= m_aUpdateInfo
.Sources
.end()) &&
1205 m_aUpdateInfo
.Sources
.erase(m_aUpdateInfo
.Sources
.begin(), --iter
);
1208 rtl::Reference
< UpdateCheckConfig
> rModel
= UpdateCheckConfig::get(m_xContext
, *this);
1209 OSL_ASSERT( rModel
.is() );
1211 // Decide whether to use alternate release note pos ..
1212 bool autoDownloadEnabled
= rModel
->isAutoDownloadEnabled();
1214 for (auto & elem
: m_aUpdateInfo
.ReleaseNotes
)
1216 if( ((1 == elem
.Pos
) || (2 == elem
.Pos
)) && autoDownloadEnabled
&& !elem
.URL2
.isEmpty())
1218 elem
.URL
= elem
.URL2
;
1220 elem
.Pos
= elem
.Pos2
;
1225 // do not move below store/clear ..
1226 rModel
->updateLastChecked();
1228 UpdateState eUIState
;
1229 if( !m_aUpdateInfo
.Sources
.empty() )
1231 rModel
->storeUpdateFound(aInfo
, getBuildId());
1233 if( m_aUpdateInfo
.Sources
[0].IsDirect
)
1235 eUIState
= UPDATESTATE_UPDATE_AVAIL
;
1237 if( rModel
->isAutoDownloadEnabled() )
1239 shutdownThread(false);
1240 eUIState
= UPDATESTATE_DOWNLOADING
;
1241 enableDownload(true);
1245 eUIState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1249 eUIState
= UPDATESTATE_NO_UPDATE_AVAIL
;
1250 rModel
->clearUpdateFound();
1254 setUIState(eUIState
, bSuppressBubble
);
1259 UpdateCheck::setCheckFailedState()
1261 setUIState(UPDATESTATE_ERROR_CHECKING
);
1265 void UpdateCheck::handleMenuBarUI( const rtl::Reference
< UpdateHandler
>& rUpdateHandler
,
1266 UpdateState
& eState
,
1267 bool suppressBubble
)
1269 uno::Reference
<beans::XPropertySet
> xMenuBarUI( m_xMenuBarUI
);
1271 if ( ( UPDATESTATE_NO_UPDATE_AVAIL
== eState
) && m_bHasExtensionUpdate
)
1272 eState
= UPDATESTATE_EXT_UPD_AVAIL
;
1274 if ( UPDATESTATE_EXT_UPD_AVAIL
== eState
)
1275 m_bShowExtUpdDlg
= true;
1277 m_bShowExtUpdDlg
= false;
1279 if( xMenuBarUI
.is() )
1281 if( UPDATESTATE_NO_UPDATE_AVAIL
== eState
)
1283 xMenuBarUI
->setPropertyValue( PROPERTY_SHOW_MENUICON
, uno::Any(false) );
1287 xMenuBarUI
->setPropertyValue( PROPERTY_TITLE
, uno::Any(rUpdateHandler
->getBubbleTitle(eState
)) );
1288 xMenuBarUI
->setPropertyValue( PROPERTY_TEXT
, uno::Any(rUpdateHandler
->getBubbleText(eState
)) );
1290 if( ! suppressBubble
&& ( ! rUpdateHandler
->isVisible() || rUpdateHandler
->isMinimized() ) )
1291 xMenuBarUI
->setPropertyValue( PROPERTY_SHOW_BUBBLE
, uno::Any( true ) );
1293 if( UPDATESTATE_CHECKING
!= eState
)
1294 xMenuBarUI
->setPropertyValue( PROPERTY_SHOW_MENUICON
, uno::Any(true) );
1300 void UpdateCheck::setUIState(UpdateState eState
, bool suppressBubble
)
1302 std::unique_lock
aGuard(m_aMutex
);
1304 if( ! m_xMenuBarUI
.is() &&
1305 (DISABLED
!= m_eState
) &&
1306 ( m_bHasExtensionUpdate
|| (UPDATESTATE_NO_UPDATE_AVAIL
!= eState
)) &&
1307 (UPDATESTATE_CHECKING
!= eState
) &&
1308 (UPDATESTATE_ERROR_CHECKING
!= eState
)
1311 m_xMenuBarUI
= createMenuBarUI(m_xContext
, new MenuBarButtonJob(this));
1314 // Show bubble only when the status has changed
1315 if ( eState
== m_eUpdateState
)
1316 suppressBubble
= true;
1318 m_eUpdateState
= eState
;
1320 rtl::Reference
<UpdateHandler
> aUpdateHandler(getUpdateHandler());
1321 OSL_ASSERT( aUpdateHandler
.is() );
1323 UpdateInfo
aUpdateInfo(m_aUpdateInfo
);
1324 OUString
aImageName(m_aImageName
);
1328 handleMenuBarUI( aUpdateHandler
, eState
, suppressBubble
);
1330 if( (UPDATESTATE_UPDATE_AVAIL
== eState
)
1331 || (UPDATESTATE_DOWNLOAD_PAUSED
== eState
)
1332 || (UPDATESTATE_DOWNLOADING
== eState
) )
1334 uno::Reference
< uno::XComponentContext
> xContext(m_xContext
);
1336 OUString aDownloadDestination
=
1337 UpdateCheckConfig::get(xContext
, this)->getDownloadDestination();
1339 osl_getSystemPathFromFileURL(aDownloadDestination
.pData
, &aDownloadDestination
.pData
);
1341 aUpdateHandler
->setDownloadPath(aDownloadDestination
);
1343 else if( UPDATESTATE_DOWNLOAD_AVAIL
== eState
)
1345 aUpdateHandler
->setDownloadFile(aImageName
);
1348 aUpdateHandler
->setDescription(aUpdateInfo
.Description
);
1349 aUpdateHandler
->setNextVersion(aUpdateInfo
.Version
);
1350 aUpdateHandler
->setState(eState
);
1355 UpdateCheck::getUIState(const UpdateInfo
& rInfo
)
1357 UpdateState eUIState
= UPDATESTATE_NO_UPDATE_AVAIL
;
1359 if( !rInfo
.BuildId
.isEmpty() )
1361 if( rInfo
.Sources
[0].IsDirect
)
1362 eUIState
= UPDATESTATE_UPDATE_AVAIL
;
1364 eUIState
= UPDATESTATE_UPDATE_NO_DOWNLOAD
;
1372 UpdateCheck::showReleaseNote(const OUString
& rURL
) const
1374 const uno::Reference
< c3s::XSystemShellExecute
> xShellExecute(
1375 c3s::SystemShellExecute::create( m_xContext
) );
1378 xShellExecute
->execute(rURL
, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY
);
1379 } catch(const c3s::SystemShellExecuteException
&) {
1385 UpdateCheck::storeReleaseNote(sal_Int8 nNum
, const OUString
&rURL
)
1387 osl::FileBase::RC rc
;
1388 OUString
aTargetDir( UpdateCheckConfig::getAllUsersDirectory() + "/sun" );
1390 osl::Directory::createPath( aTargetDir
);
1392 OUString aFileName
= "releasenote" +
1393 OUString::number( nNum
) +
1397 rc
= osl::FileBase::getAbsoluteFileURL( aTargetDir
, aFileName
, aFilePath
);
1398 if ( rc
!= osl::FileBase::E_None
) return false;
1400 osl::File::remove( aFilePath
);
1402 // don't store empty release notes, but delete old ones
1403 if ( rURL
.isEmpty() )
1406 osl::File
aFile( aFilePath
);
1407 rc
= aFile
.open( osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
1408 if ( rc
!= osl::FileBase::E_None
) return false;
1410 OString
aLineBuf("[InternetShortcut]\r\n"_ostr
);
1411 sal_uInt64 nWritten
= 0;
1413 OUString
aURL( rURL
);
1415 rc
= aFile
.write( aLineBuf
.getStr(), aLineBuf
.getLength(), nWritten
);
1416 if ( rc
!= osl::FileBase::E_None
) return false;
1417 aURL
= "URL=" + rURL
;
1419 aLineBuf
= OUStringToOString( aURL
, RTL_TEXTENCODING_UTF8
);
1420 rc
= aFile
.write( aLineBuf
.getStr(), aLineBuf
.getLength(), nWritten
);
1421 if ( rc
!= osl::FileBase::E_None
) return false;
1428 void UpdateCheck::showExtensionDialog()
1430 uno::Reference
< uno::XInterface
> xService
;
1432 if( ! m_xContext
.is() )
1433 throw uno::RuntimeException(
1434 "UpdateCheck::showExtensionDialog(): empty component context", uno::Reference
< uno::XInterface
> () );
1436 uno::Reference
< lang::XMultiComponentFactory
> xServiceManager( m_xContext
->getServiceManager() );
1437 if( !xServiceManager
.is() )
1438 throw uno::RuntimeException(
1439 "UpdateCheck::showExtensionDialog(): unable to obtain service manager from component context", uno::Reference
< uno::XInterface
> () );
1441 xService
= xServiceManager
->createInstanceWithContext( "com.sun.star.deployment.ui.PackageManagerDialog", m_xContext
);
1442 uno::Reference
< task::XJobExecutor
> xExecutable( xService
, uno::UNO_QUERY
);
1443 if ( xExecutable
.is() )
1444 xExecutable
->trigger( "SHOW_UPDATE_DIALOG" );
1448 rtl::Reference
<UpdateHandler
>
1449 UpdateCheck::getUpdateHandler()
1451 std::scoped_lock
aGuard(m_aMutex
);
1453 if( ! m_aUpdateHandler
.is() )
1454 m_aUpdateHandler
= new UpdateHandler(m_xContext
, this);
1456 return m_aUpdateHandler
;
1460 uno::Reference
< task::XInteractionHandler
>
1461 UpdateCheck::getInteractionHandler() const
1463 std::scoped_lock
aGuard(m_aMutex
);
1465 uno::Reference
< task::XInteractionHandler
> xHandler
;
1467 if( m_aUpdateHandler
.is() && m_aUpdateHandler
->isVisible() )
1468 xHandler
= m_aUpdateHandler
.get();
1475 UpdateCheck::isDialogShowing() const
1477 std::scoped_lock
aGuard(m_aMutex
);
1478 return m_aUpdateHandler
.is() && m_aUpdateHandler
->isVisible();
1483 UpdateCheck::autoCheckStatusChanged(bool enabled
)
1485 std::unique_lock
aGuard(m_aMutex
);
1487 if( (CHECK_SCHEDULED
== m_eState
) && !enabled
)
1488 shutdownThread(false);
1490 if( (DISABLED
== m_eState
) || (CHECK_SCHEDULED
== m_eState
) )
1492 enableAutoCheck(enabled
);
1493 UpdateState eState
= getUIState(m_aUpdateInfo
);
1501 UpdateCheck::autoCheckIntervalChanged()
1507 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */