Avoid potential negative array index access to cached text.
[LibreOffice.git] / extensions / source / update / check / updatecheck.cxx
blob26b83f9d83f74da2bb592bafe73f1178cd1426db
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 = 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)
75 if( pos == elem.Pos )
77 if( (pos > 2) || !autoDownloadEnabled || elem.URL2.isEmpty() )
78 return elem.URL;
80 else if( (pos == elem.Pos2) && ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled )
81 return elem.URL2;
84 return OUString();
88 namespace
91 OUString getBuildId()
93 OUString aPathVal("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
94 rtl::Bootstrap::expandMacros(aPathVal);
95 return aPathVal;
99 bool isObsoleteUpdateInfo(std::u16string_view rBuildId)
101 return rBuildId != getBuildId() && !rBuildId.empty();
105 OUString getImageFromFileName(const OUString& aFile)
107 #ifndef _WIN32
108 OUString aUnpackPath;
109 if( osl_getExecutableFile(&aUnpackPath.pData) == osl_Process_E_None )
111 sal_uInt32 lastIndex = aUnpackPath.lastIndexOf('/');
112 if ( lastIndex > 0 )
114 aUnpackPath = OUString::Concat(aUnpackPath.subView( 0, lastIndex+1 )) +
115 "unpack_update";
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] () {
139 osl_closeFile(hOut);
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 )
150 char szBuffer[4096];
151 sal_uInt64 nBytesRead = 0;
152 const sal_uInt64 nBytesToRead = sizeof(szBuffer) - 1;
154 OUString aImageName;
155 while( osl_File_E_None == osl_readFile(hOut, szBuffer, nBytesToRead, &nBytesRead) )
157 char *pc = szBuffer + nBytesRead;
160 *pc = '\0'; --pc;
162 while( ('\n' == *pc) || ('\r' == *pc) );
164 aImageName += OUString(szBuffer, pc - szBuffer + 1, osl_getThreadTextEncoding());
166 if( nBytesRead < nBytesToRead )
167 break;
170 if( osl::FileBase::E_None == osl::FileBase::getFileURLFromSystemPath(aImageName, aImageName) )
171 return aImageName;
176 #endif
178 return aFile;
182 uno::Reference< beans::XPropertySet > createMenuBarUI(
183 const uno::Reference< uno::XComponentContext >& xContext,
184 const uno::Reference< task::XJob >& xJob)
186 if( !xContext.is() )
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 ) );
201 return xMenuBarUI;
205 typedef sal_Bool (* OnlineCheckFunc) ();
207 class UpdateCheckThread : public WorkerThread
210 public:
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();
221 protected:
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 );
230 private:
232 /* Used to avoid dialup login windows (on platforms we know how to double this) */
233 static bool hasInternetConnection()
235 #ifdef _WIN32
236 return WNT_hasInternetConnection();
237 #else
238 return true;
239 #endif
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);
247 return m_xProvider;
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 */
255 void clearProvider()
256 { osl::MutexGuard aGuard(m_aMutex); m_xProvider.clear(); };
258 osl::Mutex m_aMutex;
260 protected:
261 osl::Condition& m_aCondition;
263 private:
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
273 public:
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 >
283 public:
284 explicit MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck);
286 // XJob
287 virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override;
289 private:
290 rtl::Reference< UpdateCheck > m_aUpdateCheck;
293 class DownloadThread : public WorkerThread
295 public:
296 DownloadThread(
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;
307 protected:
308 virtual ~DownloadThread() override;
310 private:
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)
327 createSuspended();
329 // actually run the thread
330 resume();
334 UpdateCheckThread::~UpdateCheckThread()
339 void SAL_CALL
340 UpdateCheckThread::terminate()
342 // Cancel potentially hanging http request ..
343 cancel();
344 // .. before terminating
345 osl::Thread::terminate();
349 void SAL_CALL
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() )
357 osl::Thread::join();
362 void
363 UpdateCheckThread::cancel()
365 uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider());
367 if( xProvider.is() )
368 xProvider->cancel();
371 void UpdateCheckThread::cancelAsSoonAsPossible() {
373 osl::MutexGuard g(m_aMutex);
374 m_cancelAsSoonAsPossible = true;
376 m_aCondition.set();
379 bool
380 UpdateCheckThread::runCheck( bool & rbExtensionsChecked )
382 bool ret = false;
383 UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL;
385 UpdateInfo aInfo;
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);
392 ret = true;
394 else
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
412 clearProvider();
413 return ret;
417 void SAL_CALL
418 UpdateCheckThread::onTerminated()
420 delete this;
424 void SAL_CALL
425 UpdateCheckThread::run()
427 osl_setThreadName("UpdateCheckThread");
429 TimeValue systime;
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) {
441 goto done;
445 try {
446 bool bExtensionsChecked = false;
448 while( schedule() )
450 /* Use cases:
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,
453 * condition is set
454 * a2) manual check was requested while waiting for a retry,
455 * condition is set
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();
472 rModel.clear();
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();
485 if( ! checkNow )
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) {
499 goto done;
502 continue;
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) )
520 ++n;
522 tv.Seconds = nRetryInterval[n-1];
523 aResult = m_aCondition.wait(&tv);
525 osl::MutexGuard g(m_aMutex);
526 if (m_cancelAsSoonAsPossible) {
527 goto done;
531 else // reset retry counter
533 n = 0;
534 bExtensionsChecked = false;
539 catch(const uno::Exception&) {
540 // Silently catch all errors
541 TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" );
544 done:
545 if (m_controller.is()) {
546 m_controller->notifyUpdateCheckFinished();
551 void SAL_CALL
552 ManualUpdateCheckThread::run()
554 try {
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)
572 uno::Any SAL_CALL
573 MenuBarButtonJob::execute(const uno::Sequence<beans::NamedValue>& )
575 if ( m_aUpdateCheck->shouldShowExtUpdDlg() )
576 m_aUpdateCheck->showExtensionDialog();
577 else
578 m_aUpdateCheck->showDialog();
580 return uno::Any();
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),
590 m_aURL(rURL),
591 m_aDownload(xContext, rHandler)
593 createSuspended();
597 DownloadThread::~DownloadThread()
602 void SAL_CALL
603 DownloadThread::run()
605 osl_setThreadName("DownloadThread");
607 #ifdef _WIN32
608 int nNbCallCoInitializeExForReinit = 0;
609 // for SystemShellExecute
610 o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit);
611 #endif
613 while( schedule() )
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
621 rModel.clear();
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
627 TimeValue tv(15, 0);
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) )
635 ++n;
637 tv.Seconds = nRetryInterval[n-1];
639 m_aCondition.wait(&tv);
641 else
643 // reset wait period after successful download
644 n=0;
647 #ifdef _WIN32
648 o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit);
649 #endif
653 void DownloadThread::cancel()
655 m_aDownload.stop();
656 resume();
658 rtl::Reference< UpdateCheck > aController(UpdateCheck::get());
659 aController->cancelDownload();
663 void SAL_CALL DownloadThread::suspend()
665 osl::Thread::suspend();
666 m_aDownload.stop();
670 void SAL_CALL DownloadThread::onTerminated()
672 delete this;
676 } // anonymous namespace
678 UpdateCheck::UpdateCheck()
679 : m_eState(NOT_INITIALIZED)
680 , m_eUpdateState(UPDATESTATES_COUNT)
681 , m_pThread(nullptr)
682 , m_bHasExtensionUpdate(false)
683 , m_bShowExtUpdDlg(false)
684 , m_updateCheckRunning(false)
688 UpdateCheck::~UpdateCheck() {}
690 void
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() );
777 else
779 enableAutoCheck(aModel.isAutoCheckEnabled());
780 if ( bDownloadAvailable )
781 setUIState( UPDATESTATE_DOWNLOAD_AVAIL );
782 else
784 // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex
785 setUIState(getUIState(m_aUpdateInfo));
793 void
794 UpdateCheck::cancel()
796 std::unique_lock aGuard(m_aMutex);
798 WorkerThread *pThread = m_pThread;
799 UpdateState eUIState = getUIState(m_aUpdateInfo);
801 aGuard.unlock();
803 if( nullptr != pThread )
804 pThread->cancel();
806 setUIState(eUIState);
810 void
811 UpdateCheck::download()
813 std::unique_lock aGuard(m_aMutex);
814 UpdateInfo aInfo(m_aUpdateInfo);
815 State eState = m_eState;
816 aGuard.unlock();
818 if (aInfo.Sources.empty())
820 SAL_WARN("extensions.update", "download called without source");
821 return;
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);
838 else
840 showReleaseNote(aInfo.Sources[0].URL); // Display in browser
845 void
846 UpdateCheck::pause()
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);
854 aGuard.unlock();
856 rModel->storeDownloadPaused(true);
857 setUIState(UPDATESTATE_DOWNLOAD_PAUSED);
861 void
862 UpdateCheck::resume()
864 std::unique_lock aGuard(m_aMutex);
866 if( nullptr != m_pThread )
867 m_pThread->resume();
869 rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext);
870 aGuard.unlock();
872 rModel->storeDownloadPaused(false);
873 setUIState(UPDATESTATE_DOWNLOADING);
877 void
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 );
885 aGuard.unlock();
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();
905 for (;;) {
906 std::unique_lock lock(m_aMutex);
907 if (!m_updateCheckRunning) {
908 return;
910 m_updateCheckFinished.wait(lock);
914 void
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;
921 m_pThread = nullptr;
922 aGuard.unlock();
924 if( nullptr != pThread )
926 pThread->terminate();
927 if( join )
929 m_aCondition.set();
930 pThread->join();
931 m_aCondition.reset();
937 void
938 UpdateCheck::enableAutoCheck(bool enable)
940 if( enable )
942 m_updateCheckRunning = true;
943 m_pThread = new UpdateCheckThread(m_aCondition, m_xContext, this);
946 m_eState = enable ? CHECK_SCHEDULED : DISABLED;
950 void
951 UpdateCheck::enableDownload(bool enable, bool paused)
953 OSL_ASSERT(nullptr == m_pThread);
955 if( enable )
957 m_pThread = new DownloadThread(m_aCondition, m_xContext, this, m_aUpdateInfo.Sources[0].URL );
958 State eState = DISABLED;
959 if( !paused )
961 eState = DOWNLOADING;
962 m_pThread->resume();
964 else
965 eState = DOWNLOAD_PAUSED;
967 m_eState = eState;
969 else {
970 enableAutoCheck(UpdateCheckConfig::get(m_xContext)->isAutoCheckEnabled());
976 bool
977 UpdateCheck::downloadTargetExists(const OUString& rFileName)
979 std::unique_lock aGuard(m_aMutex);
981 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
982 UpdateState eUIState = UPDATESTATE_DOWNLOADING;
984 bool cont = false;
986 if( aUpdateHandler->isVisible() )
988 cont = aUpdateHandler->showOverwriteWarning();
989 if( cont )
991 if( osl_File_E_None != osl_removeFile(rFileName.pData) )
993 // FIXME: error message
994 cont = false;
997 else
998 eUIState = getUIState(m_aUpdateInfo);
1000 else
1002 m_aImageName = getImageFromFileName(rFileName);
1003 eUIState = UPDATESTATE_DOWNLOAD_AVAIL;
1006 if( !cont )
1008 shutdownThread(false);
1009 enableDownload(false);
1011 aGuard.unlock();
1012 setUIState(eUIState);
1015 return cont;
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 );
1032 return bReload;
1036 void
1037 UpdateCheck::downloadStalled(const OUString& rErrorMessage)
1039 std::unique_lock aGuard(m_aMutex);
1040 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
1041 aGuard.unlock();
1043 aUpdateHandler->setErrorMessage(rErrorMessage);
1044 setUIState(UPDATESTATE_ERROR_DOWNLOADING);
1048 void
1049 UpdateCheck::downloadProgressAt(sal_Int8 nPercent)
1051 std::unique_lock aGuard(m_aMutex);
1052 rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler());
1053 aGuard.unlock();
1055 aUpdateHandler->setProgress(nPercent);
1056 setUIState(UPDATESTATE_DOWNLOADING);
1060 void
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);
1078 void
1079 UpdateCheck::downloadFinished(const OUString& rLocalFileName)
1081 std::unique_lock aGuard(m_aMutex);
1083 // no more retries
1084 m_pThread->terminate();
1086 m_aImageName = getImageFromFileName(rLocalFileName);
1087 UpdateInfo aUpdateInfo(m_aUpdateInfo);
1089 aGuard.unlock();
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);
1100 void
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 ..
1126 void
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;
1136 switch( m_eState )
1138 case DISABLED:
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;
1143 bSetUIState = true;
1145 else if(m_aUpdateInfo.Sources[0].IsDirect)
1146 eDialogState = UPDATESTATE_UPDATE_AVAIL;
1147 else
1148 eDialogState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1149 break;
1151 case DOWNLOADING:
1152 eDialogState = UPDATESTATE_DOWNLOADING;
1153 break;
1155 case DOWNLOAD_PAUSED:
1156 eDialogState = UPDATESTATE_DOWNLOAD_PAUSED;
1157 break;
1159 case NOT_INITIALIZED:
1160 OSL_ASSERT( false );
1161 break;
1164 if( bSetUIState )
1166 aGuard.unlock();
1167 setUIState(eDialogState, true); // suppress bubble as Dialog will be visible soon
1168 aGuard.lock();
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);
1182 m_aCondition.set();
1187 void
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()) &&
1203 iter->IsDirect )
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;
1219 elem.URL2.clear();
1220 elem.Pos = elem.Pos2;
1221 elem.Pos2 = 0;
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);
1244 else
1245 eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1247 else
1249 eUIState = UPDATESTATE_NO_UPDATE_AVAIL;
1250 rModel->clearUpdateFound();
1253 aGuard.unlock();
1254 setUIState(eUIState, bSuppressBubble);
1258 void
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;
1276 else
1277 m_bShowExtUpdDlg = false;
1279 if( xMenuBarUI.is() )
1281 if( UPDATESTATE_NO_UPDATE_AVAIL == eState )
1283 xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(false) );
1285 else
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;
1317 else
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);
1326 aGuard.unlock();
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);
1354 UpdateState
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;
1363 else
1364 eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD;
1367 return eUIState;
1371 void
1372 UpdateCheck::showReleaseNote(const OUString& rURL) const
1374 const uno::Reference< c3s::XSystemShellExecute > xShellExecute(
1375 c3s::SystemShellExecute::create( m_xContext ) );
1377 try {
1378 xShellExecute->execute(rURL, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY);
1379 } catch(const c3s::SystemShellExecuteException&) {
1384 bool
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 ) +
1394 ".url";
1396 OUString aFilePath;
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() )
1404 return true;
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 );
1414 #ifdef _WIN32
1415 rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten );
1416 if ( rc != osl::FileBase::E_None ) return false;
1417 aURL = "URL=" + rURL;
1418 #endif
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;
1423 aFile.close();
1424 return true;
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();
1470 return xHandler;
1474 bool
1475 UpdateCheck::isDialogShowing() const
1477 std::scoped_lock aGuard(m_aMutex);
1478 return m_aUpdateHandler.is() && m_aUpdateHandler->isVisible();
1482 void
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);
1494 aGuard.unlock();
1495 setUIState(eState);
1500 void
1501 UpdateCheck::autoCheckIntervalChanged()
1503 // just wake-up
1504 m_aCondition.set();
1507 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */