/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_extensions.hxx" #include "updatecheck.hxx" #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include #include #include #ifdef WNT #ifdef _MSC_VER #pragma warning(push,1) // disable warnings within system headers //#pragma warning(disable: 4917) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #endif #include "updateprotocol.hxx" #include "updatecheckconfig.hxx" namespace awt = com::sun::star::awt ; namespace beans = com::sun::star::beans ; namespace container = com::sun::star::container ; namespace deployment = com::sun::star::deployment ; namespace frame = com::sun::star::frame ; namespace lang = com::sun::star::lang ; namespace c3s = com::sun::star::system ; namespace task = com::sun::star::task ; namespace util = com::sun::star::util ; namespace uno = com::sun::star::uno ; #define UNISTRING(s) rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(s)) #define PROPERTY_TITLE UNISTRING("BubbleHeading") #define PROPERTY_TEXT UNISTRING("BubbleText") #define PROPERTY_IMAGE UNISTRING("BubbleImageURL") #define PROPERTY_SHOW_BUBBLE UNISTRING("BubbleVisible") #define PROPERTY_CLICK_HDL UNISTRING("MenuClickHDL") #define PROPERTY_DEFAULT_TITLE UNISTRING("DefaultHeading") #define PROPERTY_DEFAULT_TEXT UNISTRING("DefaultText") #define PROPERTY_SHOW_MENUICON UNISTRING("MenuIconVisible") //------------------------------------------------------------------------------ // Returns the URL of the release note for the given position rtl::OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled) { std::vector< ReleaseNote >::const_iterator iter = rInfo.ReleaseNotes.begin(); while( iter != rInfo.ReleaseNotes.end() ) { if( pos == iter->Pos ) { if( (pos > 2) || !autoDownloadEnabled || ! (iter->URL2.getLength() > 0) ) return iter->URL; } else if( (pos == iter->Pos2) && ((1 == iter->Pos) || (2 == iter->Pos)) && autoDownloadEnabled ) return iter->URL2; ++iter; } return rtl::OUString(); } //------------------------------------------------------------------------------ namespace { static inline rtl::OUString getBuildId() { rtl::OUString aPathVal(UNISTRING("${$OOO_BASE_DIR/program/" SAL_CONFIGFILE("version") ":buildid}")); rtl::Bootstrap::expandMacros(aPathVal); return aPathVal; } //------------------------------------------------------------------------------ static inline rtl::OUString getBaseInstallation() { rtl::OUString aPathVal(UNISTRING("${$BRAND_BASE_DIR/program/" SAL_CONFIGFILE("bootstrap") ":BaseInstallation}")); rtl::Bootstrap::expandMacros(aPathVal); return aPathVal; } //------------------------------------------------------------------------------ inline bool isObsoleteUpdateInfo(const rtl::OUString& rBuildId) { return sal_True != rBuildId.equals(getBuildId()) && rBuildId.getLength() > 0; } //------------------------------------------------------------------------------ rtl::OUString getImageFromFileName(const rtl::OUString& aFile) { #ifndef WNT rtl::OUString aUnpackPath; if( osl_getExecutableFile(&aUnpackPath.pData) == osl_Process_E_None ) { sal_uInt32 lastIndex = aUnpackPath.lastIndexOf('/'); if ( lastIndex > 0 ) { aUnpackPath = aUnpackPath.copy( 0, lastIndex+1 ); aUnpackPath += UNISTRING( "unpack_update" ); } oslFileHandle hOut = NULL; oslProcess hProcess = NULL; rtl::OUString aSystemPath; osl::File::getSystemPathFromFileURL(aFile, aSystemPath); oslProcessError rc = osl_executeProcess_WithRedirectedIO( aUnpackPath.pData, // [in] Image name &aSystemPath.pData, 1, // [in] Arguments osl_Process_WAIT || osl_Process_NORMAL, // [in] Options NULL, // [in] Security NULL, // [in] Working directory NULL, 0, // [in] Environment variables &hProcess, // [out] Process handle NULL, &hOut, NULL // [out] File handles for redirected I/O ); if( osl_Process_E_None == rc ) { oslProcessInfo aInfo; aInfo.Size = sizeof(oslProcessInfo); if( osl_Process_E_None == osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo) ) { if( 0 == aInfo.Code ) { sal_Char szBuffer[4096]; sal_uInt64 nBytesRead = 0; const sal_uInt64 nBytesToRead = sizeof(szBuffer) - 1; rtl::OUString aImageName; while( osl_File_E_None == osl_readFile(hOut, szBuffer, nBytesToRead, &nBytesRead) ) { sal_Char *pc = szBuffer + nBytesRead; do { *pc = '\0'; --pc; } while( ('\n' == *pc) || ('\r' == *pc) ); aImageName += rtl::OUString(szBuffer, pc - szBuffer + 1, osl_getThreadTextEncoding()); if( nBytesRead < nBytesToRead ) break; } if( osl::FileBase::E_None == osl::FileBase::getFileURLFromSystemPath(aImageName, aImageName) ) return aImageName; } } osl_closeFile(hOut); osl_freeProcessHandle(hProcess); } } #endif return aFile; } //------------------------------------------------------------------------------ static uno::Reference< beans::XPropertySet > createMenuBarUI( const uno::Reference< uno::XComponentContext >& xContext, const uno::Reference< task::XJob >& xJob) { if( !xContext.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheckJob: empty component context" ), uno::Reference< uno::XInterface > () ); uno::Reference< lang::XMultiComponentFactory > xServiceManager(xContext->getServiceManager()); if( !xServiceManager.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheckJob: unable to obtain service manager from component context" ), uno::Reference< uno::XInterface > () ); uno::Reference< beans::XPropertySet > xMenuBarUI = uno::Reference< beans::XPropertySet > ( xServiceManager->createInstanceWithContext( UNISTRING( "com.sun.star.setup.UpdateCheckUI" ), xContext ), uno::UNO_QUERY_THROW); xMenuBarUI->setPropertyValue( PROPERTY_CLICK_HDL, uno::makeAny( xJob ) ); return xMenuBarUI; } //------------------------------------------------------------------------------ typedef sal_Bool (* OnlineCheckFunc) (); class UpdateCheckThread : public WorkerThread { public: UpdateCheckThread( osl::Condition& rCondition, const uno::Reference& xContext ); virtual void SAL_CALL join(); virtual void SAL_CALL terminate(); virtual void SAL_CALL cancel(); protected: virtual ~UpdateCheckThread(); virtual void SAL_CALL run(); virtual void SAL_CALL onTerminated(); /* Wrapper around checkForUpdates */ bool runCheck( bool & rbExtensionsChecked ); private: /* Used to avoid dialup login windows (on platforms we know how to double this) */ inline bool hasInternetConnection() const { if(m_pHasInternetConnection != NULL ) return (sal_True == m_pHasInternetConnection()); return true; } /* Creates a new instance of UpdateInformationProvider and returns this instance */ inline uno::Reference createProvider() { osl::MutexGuard aGuard(m_aMutex); m_xProvider = deployment::UpdateInformationProvider::create(m_xContext); return m_xProvider; }; /* Returns the remembered instance of UpdateInformationProvider if any */ inline uno::Reference getProvider() { osl::MutexGuard aGuard(m_aMutex); return m_xProvider; }; /* Releases the remembered instance of UpdateInformationProvider if any */ inline void clearProvider() { osl::MutexGuard aGuard(m_aMutex); m_xProvider.clear(); }; osl::Mutex m_aMutex; osl::Module m_aModule; protected: osl::Condition& m_aCondition; private: // const OnlineCheckFunc m_pHasInternetConnection; const uno::Reference m_xContext; uno::Reference m_xProvider; }; class ManualUpdateCheckThread : public UpdateCheckThread { public: ManualUpdateCheckThread( osl::Condition& rCondition, const uno::Reference& xContext ) : UpdateCheckThread(rCondition, xContext) {}; virtual void SAL_CALL run(); }; class MenuBarButtonJob : public ::cppu::WeakImplHelper1< task::XJob > { public: MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck); // XJob virtual uno::Any SAL_CALL execute(const uno::Sequence&) throw (lang::IllegalArgumentException, uno::Exception); private: rtl::Reference< UpdateCheck > m_aUpdateCheck; }; class DownloadThread : public WorkerThread { public: DownloadThread( osl::Condition& rCondition, const uno::Reference& xContext, const rtl::Reference< DownloadInteractionHandler >& rHandler, const rtl::OUString& rURL ); virtual void SAL_CALL run(); virtual void SAL_CALL cancel(); virtual void SAL_CALL suspend(); virtual void SAL_CALL onTerminated(); protected: ~DownloadThread(); private: osl::Condition& m_aCondition; const uno::Reference m_xContext; const rtl::OUString m_aURL; Download m_aDownload; }; //------------------------------------------------------------------------------ class ShutdownThread : public osl::Thread { public: ShutdownThread( const uno::Reference& xContext ); virtual void SAL_CALL run(); virtual void SAL_CALL onTerminated(); protected: ~ShutdownThread(); private: osl::Condition m_aCondition; const uno::Reference m_xContext; }; //------------------------------------------------------------------------------ UpdateCheckThread::UpdateCheckThread( osl::Condition& rCondition, const uno::Reference& xContext ) : m_aCondition(rCondition), m_pHasInternetConnection(NULL), m_xContext(xContext) { #ifdef WNT rtl::OUString aPath; if( osl_getExecutableFile(&aPath.pData) == osl_Process_E_None ) { sal_uInt32 lastIndex = aPath.lastIndexOf('/'); if ( lastIndex > 0 ) { aPath = aPath.copy( 0, lastIndex+1 ); aPath += UNISTRING( "onlinecheck" ); } if ( m_aModule.load(aPath) ) { m_pHasInternetConnection = reinterpret_cast < OnlineCheckFunc > ( m_aModule.getFunctionSymbol( UNISTRING("hasInternetConnection"))); } } #endif createSuspended(); // actually run the thread resume(); } //------------------------------------------------------------------------------ UpdateCheckThread::~UpdateCheckThread() { } //------------------------------------------------------------------------------ void SAL_CALL UpdateCheckThread::terminate() { // Cancel potentially hanging http request .. cancel(); // .. before terminating osl::Thread::terminate(); } //------------------------------------------------------------------------------ void SAL_CALL UpdateCheckThread::join() { uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider()); // do not join during an update check until #i73893# is fixed if( ! xProvider.is() ) { osl::Thread::join(); } } //------------------------------------------------------------------------------ void SAL_CALL UpdateCheckThread::cancel() { uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider()); if( xProvider.is() ) xProvider->cancel(); } //------------------------------------------------------------------------------ bool UpdateCheckThread::runCheck( bool & rbExtensionsChecked ) { bool ret = false; UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL; UpdateInfo aInfo; rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); if( checkForUpdates(aInfo, m_xContext, aController->getInteractionHandler(), createProvider()) ) { aController->setUpdateInfo(aInfo); eUIState = aController->getUIState(aInfo); ret = true; } else aController->setCheckFailedState(); // We will only look for extension updates, when there is no 'check for office updates' dialog open // and when there was no office update found if ( ( eUIState != UPDATESTATE_UPDATE_AVAIL ) && ( eUIState != UPDATESTATE_UPDATE_NO_DOWNLOAD ) && !aController->isDialogShowing() && !rbExtensionsChecked ) { bool bHasExtensionUpdates = checkForExtensionUpdates( m_xContext ); aController->setHasExtensionUpdates( bHasExtensionUpdates ); if ( bHasExtensionUpdates ) aController->setUIState( UPDATESTATE_EXT_UPD_AVAIL ); rbExtensionsChecked = true; } // joining with this thread is safe again clearProvider(); return ret; } //------------------------------------------------------------------------------ void SAL_CALL UpdateCheckThread::onTerminated() { delete this; } //------------------------------------------------------------------------------ void SAL_CALL UpdateCheckThread::run() { bool bExtensionsChecked = false; TimeValue systime; TimeValue nExtCheckTime; osl_getSystemTime( &nExtCheckTime ); osl::Condition::Result aResult = osl::Condition::result_timeout; TimeValue tv = { 10, 0 }; // Initial wait to avoid doing further time consuming tasks during start-up aResult = m_aCondition.wait(&tv); try { while( sal_True == schedule() ) { /* Use cases: * a) manual check requested from auto check thread - "last check" should not be checked (one time) * a1) manual check was requested in the middle of a running auto check, * condition is set * a2) manual check was requested while waiting for a retry, * condition is set * a3) manual check was requested while waiting for time to next * scheduled check elapsing, condition is set * a4) manual check was requested during initial wait, condition is set * b) check interval got changed, condition may be set - same sub-cases as a), * but "last check" should be honored * c) normal auto check mode, condition not set - "last check" should be honored */ // Accessing const members without synchronization rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *aController); // FIXME: remember last & offset ? sal_Int64 last = rModel->getLastChecked(); sal_Int64 offset = rModel->getCheckInterval(); rModel.clear(); // last == 0 means check immediately bool checkNow = ! (last > 0); // Reset the condition to avoid busy loops if( osl::Condition::result_ok == aResult ) { m_aCondition.reset(); aResult = osl::Condition::result_timeout; checkNow = aController->isDialogShowing(); } if( ! checkNow ) { osl_getSystemTime(&systime); // Go back to sleep until time has elapsed sal_Int64 next = last + offset; if( last + offset > systime.Seconds ) { // This can not be > 32 Bit for now .. tv.Seconds = static_cast< sal_Int32 > (next - systime.Seconds); aResult = m_aCondition.wait(&tv); continue; } } static sal_uInt8 n = 0; if( ! hasInternetConnection() || ! runCheck( bExtensionsChecked ) ) { // the extension update check should be independent from the office update check // osl_getSystemTime( &systime ); if ( nExtCheckTime.Seconds + offset < systime.Seconds ) bExtensionsChecked = false; // Increase next by 15, 60, .. minutes static const sal_Int32 nRetryInterval[] = { 900, 3600, 14400, 86400 }; if( n < sizeof(nRetryInterval) / sizeof(sal_Int32) ) ++n; tv.Seconds = nRetryInterval[n-1]; aResult = m_aCondition.wait(&tv); } else // reset retry counter { n = 0; bExtensionsChecked = false; } } } catch(const uno::Exception& e) { // Silently catch all errors OSL_TRACE( "Caught exception: %s\n thread terminated.\n", rtl::OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr() ); } } //------------------------------------------------------------------------------ void SAL_CALL ManualUpdateCheckThread::run() { bool bExtensionsChecked = false; try { runCheck( bExtensionsChecked ); m_aCondition.reset(); } catch(const uno::Exception& e) { // Silently catch all errors OSL_TRACE( "Caught exception: %s\n thread terminated.\n", rtl::OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr() ); } } //------------------------------------------------------------------------------ MenuBarButtonJob::MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck) : m_aUpdateCheck(rUpdateCheck) { }; //------------------------------------------------------------------------------ uno::Any SAL_CALL MenuBarButtonJob::execute(const uno::Sequence& ) throw (lang::IllegalArgumentException, uno::Exception) { if ( m_aUpdateCheck->shouldShowExtUpdDlg() ) m_aUpdateCheck->showExtensionDialog(); else m_aUpdateCheck->showDialog(); return uno::Any(); } //------------------------------------------------------------------------------ DownloadThread::DownloadThread(osl::Condition& rCondition, const uno::Reference& xContext, const rtl::Reference< DownloadInteractionHandler >& rHandler, const rtl::OUString& rURL) : m_aCondition(rCondition), m_xContext(xContext), m_aURL(rURL), m_aDownload(xContext, rHandler) { createSuspended(); } //------------------------------------------------------------------------------ DownloadThread::~DownloadThread() { } //------------------------------------------------------------------------------ void SAL_CALL DownloadThread::run() { #ifdef WNT CoUninitialize(); CoInitialize( NULL ); #endif while( schedule() ) { rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); rtl::OUString aLocalFile = rModel->getLocalFileName(); rtl::OUString aDownloadDest = rModel->getDownloadDestination(); // release config class for now rModel.clear(); static sal_uInt8 n = 0; if( ! m_aDownload.start(m_aURL, aLocalFile, aDownloadDest ) ) { // retry every 15s unless the dialog is not visible TimeValue tv; tv.Seconds = 15; if( ! UpdateCheck::get()->isDialogShowing() ) { // Increase next by 1, 5, 15, 60, .. minutes static const sal_Int16 nRetryInterval[] = { 60, 300, 900, 3600 }; if( n < sizeof(nRetryInterval) / sizeof(sal_Int16) ) ++n; tv.Seconds = nRetryInterval[n-1]; } m_aCondition.wait(&tv); } else { // reset wait period after successful download n=0; } } } //------------------------------------------------------------------------------ void SAL_CALL DownloadThread::cancel() { m_aDownload.stop(); resume(); rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); aController->cancelDownload(); } //------------------------------------------------------------------------------ void SAL_CALL DownloadThread::suspend() { osl::Thread::suspend(); m_aDownload.stop(); } //------------------------------------------------------------------------------ void SAL_CALL DownloadThread::onTerminated() { delete this; } //------------------------------------------------------------------------------ ShutdownThread::ShutdownThread( const uno::Reference& xContext) : m_xContext( xContext ) { create(); } //------------------------------------------------------------------------------ ShutdownThread::~ShutdownThread() { } //------------------------------------------------------------------------------ void SAL_CALL ShutdownThread::run() { TimeValue tv = { 0, 250 }; m_aCondition.wait(&tv); // Tell QuickStarter not to veto .. uno::Reference< beans::XFastPropertySet > xQuickStarter( UpdateCheck::createService(UNISTRING("com.sun.star.office.Quickstart"), m_xContext), uno::UNO_QUERY ); if (xQuickStarter.is()) xQuickStarter->setFastPropertyValue(0, uno::makeAny(false)); // Shutdown the office uno::Reference< frame::XDesktop > xDesktop( UpdateCheck::createService(UNISTRING("com.sun.star.frame.Desktop"), m_xContext), uno::UNO_QUERY); if( xDesktop.is() ) xDesktop->terminate(); } //------------------------------------------------------------------------------ void SAL_CALL ShutdownThread::onTerminated() { delete this; } //------------------------------------------------------------------------------ } // anonymous namespace //------------------------------------------------------------------------------ void UpdateCheck::initialize(const uno::Sequence< beans::NamedValue >& rValues, const uno::Reference& xContext) { osl::MutexGuard aGuard(m_aMutex); if( NOT_INITIALIZED == m_eState ) { NamedValueByNameAccess aNameAccess(rValues); UpdateCheckROModel aModel( aNameAccess ); m_xContext = xContext; rtl::OUString aUpdateEntryVersion = aModel.getUpdateEntryVersion(); aModel.getUpdateEntry(m_aUpdateInfo); bool obsoleteUpdateInfo = isObsoleteUpdateInfo(aUpdateEntryVersion); bool bContinueDownload = false; bool bDownloadAvailable = false; m_bHasExtensionUpdate = checkForPendingUpdates( xContext ); m_bShowExtUpdDlg = false; rtl::OUString aLocalFileName = aModel.getLocalFileName(); if( aLocalFileName.getLength() > 0 ) { bContinueDownload = true; // Try to get the number of bytes already on disk osl::DirectoryItem aDirectoryItem; if( osl::DirectoryItem::E_None == osl::DirectoryItem::get(aLocalFileName, aDirectoryItem) ) { osl::FileStatus aFileStatus(FileStatusMask_FileSize); if( osl::DirectoryItem::E_None == aDirectoryItem.getFileStatus(aFileStatus) ) { sal_Int64 nDownloadSize = aModel.getDownloadSize(); sal_Int64 nFileSize = aFileStatus.getFileSize(); if( nDownloadSize > 0 ) { if ( nDownloadSize <= nFileSize ) // we have already downloaded everthing { bContinueDownload = false; bDownloadAvailable = true; m_aImageName = getImageFromFileName( aLocalFileName ); } else // Calculate initial percent value. { sal_Int32 nPercent = (sal_Int32) (100 * nFileSize / nDownloadSize); getUpdateHandler()->setProgress( nPercent ); } } } } if ( bContinueDownload ) { bool downloadPaused = aModel.isDownloadPaused(); enableDownload(true, downloadPaused); setUIState(downloadPaused ? UPDATESTATE_DOWNLOAD_PAUSED : UPDATESTATE_DOWNLOADING); } } if ( !bContinueDownload ) { // We do this intentionally only if no download is in progress .. if( obsoleteUpdateInfo ) { // Bring-up release note for position 5 .. const rtl::OUString aURL(getReleaseNote(m_aUpdateInfo, 5)); if( aURL.getLength() > 0 ) showReleaseNote(aURL); // Data is outdated, probably due to installed update rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( xContext, *this ); aConfig->clearUpdateFound(); aConfig->clearLocalFileName(); m_aUpdateInfo = UpdateInfo(); // Remove outdated release notes storeReleaseNote( 1, rtl::OUString() ); storeReleaseNote( 2, rtl::OUString() ); } else { enableAutoCheck(aModel.isAutoCheckEnabled()); if ( bDownloadAvailable ) setUIState( UPDATESTATE_DOWNLOAD_AVAIL ); else setUIState(getUIState(m_aUpdateInfo)); } } } } //------------------------------------------------------------------------------ void UpdateCheck::cancel() { osl::ClearableMutexGuard aGuard(m_aMutex); WorkerThread *pThread = m_pThread; UpdateState eUIState = getUIState(m_aUpdateInfo); aGuard.clear(); if( NULL != pThread ) pThread->cancel(); setUIState(eUIState); } //------------------------------------------------------------------------------ void UpdateCheck::download() { osl::ClearableMutexGuard aGuard(m_aMutex); UpdateInfo aInfo(m_aUpdateInfo); State eState = m_eState; aGuard.clear(); if( aInfo.Sources[0].IsDirect ) { // Ignore second click of a double click if( DOWNLOADING != eState ) { shutdownThread(true); osl::ClearableMutexGuard aGuard2(m_aMutex); enableDownload(true); aGuard2.clear(); setUIState(UPDATESTATE_DOWNLOADING); } } else { showReleaseNote(aInfo.Sources[0].URL); // Display in browser } } //------------------------------------------------------------------------------ void UpdateCheck::install() { osl::MutexGuard aGuard(m_aMutex); const uno::Reference< c3s::XSystemShellExecute > xShellExecute( createService( UNISTRING( "com.sun.star.system.SystemShellExecute" ), m_xContext ), uno::UNO_QUERY ); try { // Construct install command ?? // Store release note for position 3 and 4 rtl::OUString aURL(getReleaseNote(m_aUpdateInfo, 3)); storeReleaseNote(1, aURL); aURL = getReleaseNote(m_aUpdateInfo, 4); storeReleaseNote(2, aURL); if( xShellExecute.is() ) { rtl::OUString aInstallImage(m_aImageName); osl::FileBase::getSystemPathFromFileURL(aInstallImage, aInstallImage); rtl::OUString aParameter; sal_Int32 nFlags = c3s::SystemShellExecuteFlags::DEFAULTS; #if ( defined LINUX || defined SOLARIS ) nFlags = 42; aParameter = getBaseInstallation(); if( aParameter.getLength() > 0 ) osl::FileBase::getSystemPathFromFileURL(aParameter, aParameter); aParameter += UNISTRING(" &"); #endif rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext ); rModel->clearLocalFileName(); xShellExecute->execute(aInstallImage, aParameter, nFlags); ShutdownThread *pShutdownThread = new ShutdownThread( m_xContext ); (void) pShutdownThread; } } catch(uno::Exception&) { m_aUpdateHandler->setErrorMessage( m_aUpdateHandler->getDefaultInstErrMsg() ); } } //------------------------------------------------------------------------------ void UpdateCheck::pause() { osl::ClearableMutexGuard aGuard(m_aMutex); if( NULL != m_pThread ) m_pThread->suspend(); rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); aGuard.clear(); rModel->storeDownloadPaused(true); setUIState(UPDATESTATE_DOWNLOAD_PAUSED); } //------------------------------------------------------------------------------ void UpdateCheck::resume() { osl::ClearableMutexGuard aGuard(m_aMutex); if( NULL != m_pThread ) m_pThread->resume(); rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); aGuard.clear(); rModel->storeDownloadPaused(false); setUIState(UPDATESTATE_DOWNLOADING); } //------------------------------------------------------------------------------ void UpdateCheck::closeAfterFailure() { osl::ClearableMutexGuard aGuard(m_aMutex); if ( ( m_eState == DISABLED ) || ( m_eState == CHECK_SCHEDULED ) ) { const UpdateState eUIState = getUIState( m_aUpdateInfo ); aGuard.clear(); setUIState( eUIState, true ); } } //------------------------------------------------------------------------------ void UpdateCheck::shutdownThread(bool join) { osl::ClearableMutexGuard aGuard(m_aMutex); // copy thread object pointer to stack osl::Thread *pThread = m_pThread; m_pThread = NULL; aGuard.clear(); if( NULL != pThread ) { pThread->terminate(); if( join ) { m_aCondition.set(); pThread->join(); m_aCondition.reset(); } } } //------------------------------------------------------------------------------ void UpdateCheck::enableAutoCheck(bool enable) { if( enable ) m_pThread = new UpdateCheckThread(m_aCondition, m_xContext); m_eState = enable ? CHECK_SCHEDULED : DISABLED; } //------------------------------------------------------------------------------ void UpdateCheck::enableDownload(bool enable, bool paused) { OSL_ASSERT(NULL == m_pThread); State eState = DISABLED; if( enable ) { m_pThread = new DownloadThread(m_aCondition, m_xContext, this, m_aUpdateInfo.Sources[0].URL ); if( !paused ) { eState = DOWNLOADING; m_pThread->resume(); } else eState = DOWNLOAD_PAUSED; m_eState = eState; } else { enableAutoCheck(UpdateCheckConfig::get(m_xContext)->isAutoCheckEnabled()); } } //------------------------------------------------------------------------------ bool UpdateCheck::downloadTargetExists(const rtl::OUString& rFileName) { osl::ClearableMutexGuard aGuard(m_aMutex); rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); UpdateState eUIState = UPDATESTATE_DOWNLOADING; bool cont = false; if( aUpdateHandler->isVisible() ) { cont = aUpdateHandler->showOverwriteWarning(); if( cont ) { if( osl_File_E_None != osl_removeFile(rFileName.pData) ) { // FIXME: error message cont = false; } } else eUIState = getUIState(m_aUpdateInfo); } else { m_aImageName = getImageFromFileName(rFileName); eUIState = UPDATESTATE_DOWNLOAD_AVAIL; } if( !cont ) { shutdownThread(false); enableDownload(false); aGuard.clear(); setUIState(eUIState); } return cont; } //------------------------------------------------------------------------------ bool UpdateCheck::checkDownloadDestination( const rtl::OUString& rFileName ) { osl::ClearableMutexGuard aGuard(m_aMutex); rtl::Reference< UpdateHandler > aUpdateHandler( getUpdateHandler() ); bool bReload = false; if( aUpdateHandler->isVisible() ) { bReload = aUpdateHandler->showOverwriteWarning( rFileName ); } return bReload; } //------------------------------------------------------------------------------ void UpdateCheck::downloadStalled(const rtl::OUString& rErrorMessage) { osl::ClearableMutexGuard aGuard(m_aMutex); rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); aGuard.clear(); aUpdateHandler->setErrorMessage(rErrorMessage); setUIState(UPDATESTATE_ERROR_DOWNLOADING); } //------------------------------------------------------------------------------ void UpdateCheck::downloadProgressAt(sal_Int8 nPercent) { osl::ClearableMutexGuard aGuard(m_aMutex); rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); aGuard.clear(); aUpdateHandler->setProgress(nPercent); setUIState(UPDATESTATE_DOWNLOADING); } //------------------------------------------------------------------------------ void UpdateCheck::downloadStarted(const rtl::OUString& rLocalFileName, sal_Int64 nFileSize) { if ( nFileSize > 0 ) { osl::MutexGuard aGuard(m_aMutex); rtl::Reference< UpdateCheckConfig > aModel(UpdateCheckConfig::get(m_xContext)); aModel->storeLocalFileName(rLocalFileName, nFileSize); // Bring-up release note for position 1 .. const rtl::OUString aURL(getReleaseNote(m_aUpdateInfo, 1, aModel->isAutoDownloadEnabled())); if( aURL.getLength() > 0 ) showReleaseNote(aURL); } } //------------------------------------------------------------------------------ void UpdateCheck::downloadFinished(const rtl::OUString& rLocalFileName) { osl::ClearableMutexGuard aGuard(m_aMutex); // no more retries m_pThread->terminate(); m_aImageName = getImageFromFileName(rLocalFileName); UpdateInfo aUpdateInfo(m_aUpdateInfo); aGuard.clear(); setUIState(UPDATESTATE_DOWNLOAD_AVAIL); // Bring-up release note for position 2 .. rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext ); const rtl::OUString aURL(getReleaseNote(aUpdateInfo, 2, rModel->isAutoDownloadEnabled())); if( aURL.getLength() > 0 ) showReleaseNote(aURL); } //------------------------------------------------------------------------------ void UpdateCheck::cancelDownload() { shutdownThread(true); osl::MutexGuard aGuard(m_aMutex); enableDownload(false); rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); rtl::OUString aLocalFile(rModel->getLocalFileName()); rModel->clearLocalFileName(); rModel->storeDownloadPaused(false); if( isObsoleteUpdateInfo(rModel->getUpdateEntryVersion()) ) { rModel->clearUpdateFound(); // This wasn't done during init yet .. m_aUpdateInfo = UpdateInfo(); } /*oslFileError rc =*/ osl_removeFile(aLocalFile.pData); // FIXME: error handling .. } //------------------------------------------------------------------------------ void UpdateCheck::showDialog(bool forceCheck) { osl::ResettableMutexGuard aGuard(m_aMutex); bool update_found = m_aUpdateInfo.BuildId.getLength() > 0; bool bSetUIState = ! m_aUpdateHandler.is(); UpdateState eDialogState = UPDATESTATES_COUNT; switch( m_eState ) { case DISABLED: case CHECK_SCHEDULED: if( forceCheck || ! update_found ) // Run check when forced or if we did not find an update yet { eDialogState = UPDATESTATE_CHECKING; bSetUIState = true; } else if(m_aUpdateInfo.Sources[0].IsDirect) eDialogState = UPDATESTATE_UPDATE_AVAIL; else eDialogState = UPDATESTATE_UPDATE_NO_DOWNLOAD; break; case DOWNLOADING: eDialogState = UPDATESTATE_DOWNLOADING; break; case DOWNLOAD_PAUSED: eDialogState = UPDATESTATE_DOWNLOAD_PAUSED; break; case NOT_INITIALIZED: OSL_ASSERT( false ); break; } if( bSetUIState ) { aGuard.clear(); setUIState(eDialogState, true); // suppress bubble as Dialog will be visible soon aGuard.reset(); } getUpdateHandler()->setVisible(true); // Run check in separate thread .. if( UPDATESTATE_CHECKING == eDialogState ) { if( DISABLED == m_eState ) { // destructs itself when done, not cancellable for now .. new ManualUpdateCheckThread(m_aCondition, m_xContext); } m_aCondition.set(); } } //------------------------------------------------------------------------------ void UpdateCheck::setUpdateInfo(const UpdateInfo& aInfo) { osl::ClearableMutexGuard aGuard(m_aMutex); bool bSuppressBubble = (sal_True == aInfo.BuildId.equals(m_aUpdateInfo.BuildId)); m_aUpdateInfo = aInfo; OSL_ASSERT(DISABLED == m_eState || CHECK_SCHEDULED == m_eState); // Ignore leading non direct download if we get direct ones std::vector< DownloadSource >::iterator iter = m_aUpdateInfo.Sources.begin(); while( iter != m_aUpdateInfo.Sources.end() ) { if( iter->IsDirect ) break; ++iter; } if( (iter != m_aUpdateInfo.Sources.begin()) && (iter != m_aUpdateInfo.Sources.end()) && iter->IsDirect ) { m_aUpdateInfo.Sources.erase(m_aUpdateInfo.Sources.begin(), --iter); } rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *this); OSL_ASSERT( rModel.is() ); // Decide whether to use alternate release note pos .. bool autoDownloadEnabled = rModel->isAutoDownloadEnabled(); std::vector< ReleaseNote >::iterator iter2 = m_aUpdateInfo.ReleaseNotes.begin(); while( iter2 != m_aUpdateInfo.ReleaseNotes.end() ) { if( ((1 == iter2->Pos) || (2 == iter2->Pos)) && autoDownloadEnabled && (iter2->URL2.getLength() > 0)) { iter2->URL = iter2->URL2; iter2->URL2 = rtl::OUString(); iter2->Pos = iter2->Pos2; iter2->Pos2 = 0; } ++iter2; } // do not move below store/clear .. rModel->updateLastChecked(); UpdateState eUIState; if( m_aUpdateInfo.Sources.size() > 0 ) { rModel->storeUpdateFound(aInfo, getBuildId()); if( m_aUpdateInfo.Sources[0].IsDirect ) { eUIState = UPDATESTATE_UPDATE_AVAIL; if( rModel->isAutoDownloadEnabled() ) { shutdownThread(false); eUIState = UPDATESTATE_DOWNLOADING; enableDownload(true); } } else eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD; } else { eUIState = UPDATESTATE_NO_UPDATE_AVAIL; rModel->clearUpdateFound(); } aGuard.clear(); setUIState(eUIState, bSuppressBubble); } //------------------------------------------------------------------------------ void UpdateCheck::setCheckFailedState() { setUIState(UPDATESTATE_ERROR_CHECKING); } //------------------------------------------------------------------------------ void UpdateCheck::handleMenuBarUI( rtl::Reference< UpdateHandler > rUpdateHandler, UpdateState& eState, bool suppressBubble ) { uno::Reference xMenuBarUI( m_xMenuBarUI ); if ( ( UPDATESTATE_NO_UPDATE_AVAIL == eState ) && m_bHasExtensionUpdate ) eState = UPDATESTATE_EXT_UPD_AVAIL; if ( UPDATESTATE_EXT_UPD_AVAIL == eState ) m_bShowExtUpdDlg = true; else m_bShowExtUpdDlg = false; if( xMenuBarUI.is() ) { if( UPDATESTATE_NO_UPDATE_AVAIL == eState ) { xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::makeAny(sal_False) ); } else { xMenuBarUI->setPropertyValue( PROPERTY_TITLE, uno::makeAny(rUpdateHandler->getBubbleTitle(eState)) ); xMenuBarUI->setPropertyValue( PROPERTY_TEXT, uno::makeAny(rUpdateHandler->getBubbleText(eState)) ); if( ! suppressBubble && ( ! rUpdateHandler->isVisible() || rUpdateHandler->isMinimized() ) ) xMenuBarUI->setPropertyValue( PROPERTY_SHOW_BUBBLE, uno::makeAny( sal_True ) ); if( UPDATESTATE_CHECKING != eState ) xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::makeAny(sal_True) ); } } } //------------------------------------------------------------------------------ void UpdateCheck::setUIState(UpdateState eState, bool suppressBubble) { osl::ClearableMutexGuard aGuard(m_aMutex); if( ! m_xMenuBarUI.is() && (DISABLED != m_eState) && ( m_bHasExtensionUpdate || (UPDATESTATE_NO_UPDATE_AVAIL != eState)) && (UPDATESTATE_CHECKING != eState) && (UPDATESTATE_ERROR_CHECKING != eState) ) { m_xMenuBarUI = createMenuBarUI(m_xContext, new MenuBarButtonJob(this)); } // Show bubble only when the status has changed if ( eState == m_eUpdateState ) suppressBubble = true; else m_eUpdateState = eState; rtl::Reference aUpdateHandler(getUpdateHandler()); OSL_ASSERT( aUpdateHandler.is() ); UpdateInfo aUpdateInfo(m_aUpdateInfo); rtl::OUString aImageName(m_aImageName); aGuard.clear(); handleMenuBarUI( aUpdateHandler, eState, suppressBubble ); if( (UPDATESTATE_UPDATE_AVAIL == eState) || (UPDATESTATE_DOWNLOAD_PAUSED == eState) || (UPDATESTATE_DOWNLOADING == eState) ) { uno::Reference< uno::XComponentContext > xContext(m_xContext); rtl::OUString aDownloadDestination = UpdateCheckConfig::get(xContext, this)->getDownloadDestination(); osl_getSystemPathFromFileURL(aDownloadDestination.pData, &aDownloadDestination.pData); aUpdateHandler->setDownloadPath(aDownloadDestination); } else if( UPDATESTATE_DOWNLOAD_AVAIL == eState ) { aUpdateHandler->setDownloadFile(aImageName); } aUpdateHandler->setDescription(aUpdateInfo.Description); aUpdateHandler->setNextVersion(aUpdateInfo.Version); aUpdateHandler->setState(eState); } //------------------------------------------------------------------------------ UpdateState UpdateCheck::getUIState(const UpdateInfo& rInfo) { UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL; if( rInfo.BuildId.getLength() > 0 ) { if( rInfo.Sources[0].IsDirect ) eUIState = UPDATESTATE_UPDATE_AVAIL; else eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD; } return eUIState; } //------------------------------------------------------------------------------ void UpdateCheck::showReleaseNote(const rtl::OUString& rURL) const { const uno::Reference< c3s::XSystemShellExecute > xShellExecute( createService( UNISTRING( "com.sun.star.system.SystemShellExecute" ), m_xContext ), uno::UNO_QUERY ); try { if( xShellExecute.is() ) xShellExecute->execute(rURL, rtl::OUString(), c3s::SystemShellExecuteFlags::DEFAULTS); } catch(c3s::SystemShellExecuteException&) { } } //------------------------------------------------------------------------------ bool UpdateCheck::storeReleaseNote(sal_Int8 nNum, const rtl::OUString &rURL) { osl::FileBase::RC rc; rtl::OUString aTargetDir( UpdateCheckConfig::getAllUsersDirectory() + UNISTRING( "/sun" ) ); rc = osl::Directory::createPath( aTargetDir ); rtl::OUString aFileName = UNISTRING("releasenote") + rtl::OUString::valueOf( (sal_Int32) nNum ) + UNISTRING(".url"); rtl::OUString aFilePath; rc = osl::FileBase::getAbsoluteFileURL( aTargetDir, aFileName, aFilePath ); if ( rc != osl::FileBase::E_None ) return false; rc = osl::File::remove( aFilePath ); // don't store empty release notes, but delete old ones if ( rURL.getLength() == 0 ) return true; osl::File aFile( aFilePath ); rc = aFile.open( OpenFlag_Write | OpenFlag_Create ); if ( rc != osl::FileBase::E_None ) return false; rtl::OString aLineBuf("[InternetShortcut]\r\n"); sal_uInt64 nWritten = 0; rtl::OUString aURL( rURL ); #ifdef WNT rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten ); if ( rc != osl::FileBase::E_None ) return false; aURL = UNISTRING("URL=") + rURL; #endif aLineBuf = rtl::OUStringToOString( aURL, RTL_TEXTENCODING_UTF8 ); rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten ); if ( rc != osl::FileBase::E_None ) return false; aFile.close(); return true; } //------------------------------------------------------------------------------ void UpdateCheck::showExtensionDialog() { rtl::OUString sServiceName = UNISTRING("com.sun.star.deployment.ui.PackageManagerDialog"); rtl::OUString sArguments = UNISTRING("SHOW_UPDATE_DIALOG"); uno::Reference< uno::XInterface > xService; if( ! m_xContext.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheck::showExtensionDialog(): empty component context" ), uno::Reference< uno::XInterface > () ); uno::Reference< lang::XMultiComponentFactory > xServiceManager( m_xContext->getServiceManager() ); if( !xServiceManager.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheck::showExtensionDialog(): unable to obtain service manager from component context" ), uno::Reference< uno::XInterface > () ); xService = xServiceManager->createInstanceWithContext( sServiceName, m_xContext ); uno::Reference< task::XJobExecutor > xExecuteable( xService, uno::UNO_QUERY ); if ( xExecuteable.is() ) xExecuteable->trigger( sArguments ); } //------------------------------------------------------------------------------ rtl::Reference UpdateCheck::getUpdateHandler() { osl::MutexGuard aGuard(m_aMutex); if( ! m_aUpdateHandler.is() ) m_aUpdateHandler = new UpdateHandler(m_xContext, this); return m_aUpdateHandler; } //------------------------------------------------------------------------------ uno::Reference< task::XInteractionHandler > UpdateCheck::getInteractionHandler() const { osl::MutexGuard aGuard(m_aMutex); uno::Reference< task::XInteractionHandler > xHandler; if( m_aUpdateHandler.is() && m_aUpdateHandler->isVisible() ) xHandler = m_aUpdateHandler.get(); return xHandler; } //------------------------------------------------------------------------------ uno::Reference< uno::XInterface > UpdateCheck::createService(const rtl::OUString& rServiceName, const uno::Reference& xContext) { if( !xContext.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheckConfig: empty component context" ), uno::Reference< uno::XInterface >() ); const uno::Reference< lang::XMultiComponentFactory > xServiceManager(xContext->getServiceManager()); if( !xServiceManager.is() ) throw uno::RuntimeException( UNISTRING( "UpdateCheckConfig: unable to obtain service manager from component context" ), uno::Reference< uno::XInterface >() ); return xServiceManager->createInstanceWithContext(rServiceName, xContext); } //------------------------------------------------------------------------------ bool UpdateCheck::isDialogShowing() const { osl::MutexGuard aGuard(m_aMutex); return sal_True == m_aUpdateHandler.is() && m_aUpdateHandler->isVisible(); }; //------------------------------------------------------------------------------ void UpdateCheck::autoCheckStatusChanged(bool enabled) { osl::ClearableMutexGuard aGuard(m_aMutex); if( (CHECK_SCHEDULED == m_eState) && !enabled ) shutdownThread(false); if( (DISABLED == m_eState) || (CHECK_SCHEDULED == m_eState) ) { enableAutoCheck(enabled); UpdateState eState = getUIState(m_aUpdateInfo); aGuard.clear(); setUIState(eState); } }; //------------------------------------------------------------------------------ void UpdateCheck::autoCheckIntervalChanged() { // just wake-up m_aCondition.set(); }; //------------------------------------------------------------------------------ oslInterlockedCount SAL_CALL UpdateCheck::acquire() SAL_THROW(()) { return ReferenceObject::acquire(); } //------------------------------------------------------------------------------ oslInterlockedCount SAL_CALL UpdateCheck::release() SAL_THROW(()) { return ReferenceObject::release(); }