diff options
Diffstat (limited to '')
20 files changed, 5801 insertions, 0 deletions
diff --git a/extensions/source/update/check/actionlistener.hxx b/extensions/source/update/check/actionlistener.hxx new file mode 100644 index 000000000..8fdfd1565 --- /dev/null +++ b/extensions/source/update/check/actionlistener.hxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <salhelper/simplereferenceobject.hxx> + +class IActionListener : public virtual salhelper::SimpleReferenceObject +{ + public: + + virtual void cancel() = 0; + virtual void download() = 0; + virtual void install() = 0; + virtual void pause() = 0; + virtual void resume() = 0; + virtual void closeAfterFailure() = 0; + +protected: + virtual ~IActionListener() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/download.cxx b/extensions/source/update/check/download.cxx new file mode 100644 index 000000000..ba371bdee --- /dev/null +++ b/extensions/source/update/check/download.cxx @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <curl/curl.h> + +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <osl/file.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include "download.hxx" + +namespace beans = com::sun::star::beans ; +namespace container = com::sun::star::container ; +namespace lang = com::sun::star::lang ; +namespace uno = com::sun::star::uno ; + +namespace { + +struct OutData +{ + rtl::Reference< DownloadInteractionHandler >Handler; + OUString File; + OUString DestinationDir; + oslFileHandle FileHandle; + sal_uInt64 Offset; + osl::Condition& StopCondition; + CURL *curl; + + explicit OutData(osl::Condition& rCondition) : FileHandle(nullptr), Offset(0), StopCondition(rCondition), curl(nullptr) {}; +}; + +} + +static void openFile( OutData& out ) +{ + char * effective_url; + curl_easy_getinfo(out.curl, CURLINFO_EFFECTIVE_URL, &effective_url); + + curl_off_t nDownloadSize; + curl_easy_getinfo(out.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize); + + OString aURL(effective_url); + + // ensure no trailing '/' + sal_Int32 nLen = aURL.getLength(); + while( (nLen > 0) && ('/' == aURL[nLen-1]) ) + aURL = aURL.copy(0, --nLen); + + // extract file name last '/' + sal_Int32 nIndex = aURL.lastIndexOf('/'); + if( nIndex > 0 ) + { + out.File = out.DestinationDir + + OStringToOUString(aURL.subView(nIndex), RTL_TEXTENCODING_UTF8); + + oslFileError rc; + + // Give the user an overwrite warning if the target file exists + const sal_Int32 openFlags = osl_File_OpenFlag_Write | osl_File_OpenFlag_Create; + do + { + rc = osl_openFile(out.File.pData, &out.FileHandle, openFlags); + + if( osl_File_E_EXIST == rc && ! out.Handler->downloadTargetExists(out.File) ) + { + out.StopCondition.set(); + break; + } + + } while( osl_File_E_EXIST == rc ); + + if( osl_File_E_None == rc ) + out.Handler->downloadStarted(out.File, static_cast<sal_Int64>(nDownloadSize)); + } +} + + +static OString +getStringValue(const uno::Reference< container::XNameAccess >& xNameAccess, const OUString& aName) +{ + OSL_ASSERT(xNameAccess->hasByName(aName)); + uno::Any aValue = xNameAccess->getByName(aName); + + return OUStringToOString(aValue.get<OUString>(), RTL_TEXTENCODING_UTF8); +} + + +static sal_Int32 +getInt32Value(const uno::Reference< container::XNameAccess >& xNameAccess, + const OUString& aName) +{ + OSL_ASSERT(xNameAccess->hasByName(aName)); + uno::Any aValue = xNameAccess->getByName(aName); + + sal_Int32 n = -1; + aValue >>= n; + return n; +} + + +static size_t +write_function( void *ptr, size_t size, size_t nmemb, void *stream ) +{ + OutData *out = static_cast < OutData * > (stream); + + if( nullptr == out->FileHandle ) + openFile(*out); + + sal_uInt64 nBytesWritten = 0; + + if( nullptr != out->FileHandle ) + osl_writeFile(out->FileHandle, ptr, size * nmemb, &nBytesWritten); + + return static_cast<size_t>(nBytesWritten); +} + + +static int +progress_callback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, SAL_UNUSED_PARAMETER curl_off_t, SAL_UNUSED_PARAMETER curl_off_t) +{ + OutData *out = static_cast < OutData * > (clientp); + + assert(out); + + if (out && !out->StopCondition.check()) + { + float fPercent = 0; + if ( dltotal + out->Offset ) + fPercent = (dlnow + out->Offset) * 100 / (dltotal + out->Offset); + if( fPercent < 0 ) + fPercent = 0; + + // Do not report progress for redirection replies + long nCode; + curl_easy_getinfo(out->curl, CURLINFO_RESPONSE_CODE, &nCode); + if( (nCode != 302) && (nCode != 303) && (dltotal > 0) ) + out->Handler->downloadProgressAt(static_cast<sal_Int8>(fPercent)); + + return 0; + } + + // If stop condition is set, return non 0 value to abort + return -1; +} + + +void +Download::getProxyForURL(std::u16string_view rURL, OString& rHost, sal_Int32& rPort) const +{ + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get( m_xContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Inet/Settings"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< container::XNameAccess > xNameAccess( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + OSL_ASSERT(xNameAccess->hasByName("ooInetProxyType")); + uno::Any aValue = xNameAccess->getByName("ooInetProxyType"); + + sal_Int32 nProxyType = aValue.get< sal_Int32 >(); + if( 0 != nProxyType ) // type 0 means "direct connection to the internet + { + if( o3tl::starts_with(rURL, u"http:") ) + { + rHost = getStringValue(xNameAccess, "ooInetHTTPProxyName"); + rPort = getInt32Value(xNameAccess, "ooInetHTTPProxyPort"); + } + else if( o3tl::starts_with(rURL, u"https:") ) + { + rHost = getStringValue(xNameAccess, "ooInetHTTPSProxyName"); + rPort = getInt32Value(xNameAccess, "ooInetHTTPSProxyPort"); + } + else if( o3tl::starts_with(rURL, u"ftp:") ) + { + rHost = getStringValue(xNameAccess, "ooInetFTPProxyName"); + rPort = getInt32Value(xNameAccess, "ooInetFTPProxyPort"); + } + } +} + + +static bool curl_run(std::u16string_view rURL, OutData& out, const OString& aProxyHost, sal_Int32 nProxyPort) +{ + /* Need to investigate further whether it is necessary to call + * curl_global_init or not - leave it for now (as the ftp UCB content + * provider does as well). + */ + + CURL * pCURL = curl_easy_init(); + bool ret = false; + + if( nullptr != pCURL ) + { + out.curl = pCURL; + + OString aURL(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8)); + (void)curl_easy_setopt(pCURL, CURLOPT_URL, aURL.getStr()); + + // abort on http errors + (void)curl_easy_setopt(pCURL, CURLOPT_FAILONERROR, 1); + + // enable redirection + (void)curl_easy_setopt(pCURL, CURLOPT_FOLLOWLOCATION, 1); + // only allow redirect to https:// +#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85) + curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS_STR, "https"); +#else + curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); +#endif + + // write function + (void)curl_easy_setopt(pCURL, CURLOPT_WRITEDATA, &out); + (void)curl_easy_setopt(pCURL, CURLOPT_WRITEFUNCTION, &write_function); + + // progress handler - Condition::check unfortunately is not defined const + (void)curl_easy_setopt(pCURL, CURLOPT_NOPROGRESS, 0); + (void)curl_easy_setopt(pCURL, CURLOPT_XFERINFOFUNCTION, &progress_callback); + (void)curl_easy_setopt(pCURL, CURLOPT_PROGRESSDATA, &out); + + // proxy + (void)curl_easy_setopt(pCURL, CURLOPT_PROXY, aProxyHost.getStr()); + (void)curl_easy_setopt(pCURL, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + if( -1 != nProxyPort ) + (void)curl_easy_setopt(pCURL, CURLOPT_PROXYPORT, nProxyPort); + + if( out.Offset > 0 ) + { + // curl_off_t offset = nOffset; libcurl seems to be compiled with large + // file support (and we not) .. + sal_Int64 offset = static_cast<sal_Int64>(out.Offset); + (void)curl_easy_setopt(pCURL, CURLOPT_RESUME_FROM_LARGE, offset); + } + + CURLcode cc = curl_easy_perform(pCURL); + + // treat zero byte downloads as errors + if( nullptr == out.FileHandle ) + openFile(out); + + if( CURLE_OK == cc ) + { + out.Handler->downloadFinished(out.File); + ret = true; + } + + if ( CURLE_PARTIAL_FILE == cc ) + { + // this sometimes happens, when a user throws away his user data, but has already + // completed the download of an update. + curl_off_t nDownloadSize; + curl_easy_getinfo( pCURL, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize ); + if ( -1 == nDownloadSize ) + { + out.Handler->downloadFinished(out.File); + ret = true; + } + } + + // Avoid target file being removed + else if( (CURLE_ABORTED_BY_CALLBACK == cc) || out.StopCondition.check() ) + ret = true; + + // Only report errors when not stopped + else + { + OString aMessage("Unknown error"); + + const char * error_message = curl_easy_strerror(cc); + if( nullptr != error_message ) + aMessage = error_message; + + if ( CURLE_HTTP_RETURNED_ERROR == cc ) + { + long nError; + curl_easy_getinfo( pCURL, CURLINFO_RESPONSE_CODE, &nError ); + + if ( 403 == nError ) + aMessage += " 403: Access denied!"; + else if ( 404 == nError ) + aMessage += " 404: File not found!"; + else if ( 416 == nError ) + { + // we got this error probably, because we already downloaded the file + out.Handler->downloadFinished(out.File); + ret = true; + } + else + { + aMessage += ":error code = " + OString::number( nError ) + " !"; + } + } + if ( !ret ) + out.Handler->downloadStalled( OStringToOUString(aMessage, RTL_TEXTENCODING_UTF8) ); + } + + curl_easy_cleanup(pCURL); + } + + return ret; +} + + +bool +Download::start(const OUString& rURL, const OUString& rFile, const OUString& rDestinationDir) +{ + OSL_ASSERT( m_aHandler.is() ); + + OutData out(m_aCondition); + OUString aFile( rFile ); + + // when rFile is empty, there is no remembered file name. If there is already a file with the + // same name ask the user if she wants to resume a download or restart the download + if ( aFile.isEmpty() ) + { + // GetFileName() + OUString aURL( rURL ); + // ensure no trailing '/' + sal_Int32 nLen = aURL.getLength(); + while( (nLen > 0) && ('/' == aURL[ nLen-1 ]) ) + aURL = aURL.copy( 0, --nLen ); + + // extract file name last '/' + sal_Int32 nIndex = aURL.lastIndexOf('/'); + aFile = rDestinationDir + aURL.subView( nIndex ); + + // check for existing file + oslFileError rc = osl_openFile( aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + osl_closeFile(out.FileHandle); + out.FileHandle = nullptr; + + if( osl_File_E_EXIST == rc ) + { + if ( m_aHandler->checkDownloadDestination( aURL.copy( nIndex+1 ) ) ) + { + osl_removeFile( aFile.pData ); + aFile.clear(); + } + else + m_aHandler->downloadStarted( aFile, 0 ); + } + else + { + osl_removeFile( aFile.pData ); + aFile.clear(); + } + } + + out.File = aFile; + out.DestinationDir = rDestinationDir; + out.Handler = m_aHandler; + + if( !aFile.isEmpty() ) + { + oslFileError rc = osl_openFile(aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write); + + if( osl_File_E_None == rc ) + { + // Set file pointer to the end of the file on resume + if( osl_File_E_None == osl_setFilePos(out.FileHandle, osl_Pos_End, 0) ) + { + osl_getFilePos(out.FileHandle, &out.Offset); + } + } + else if( osl_File_E_NOENT == rc ) // file has been deleted meanwhile .. + out.File.clear(); + } + + OString aProxyHost; + sal_Int32 nProxyPort = -1; + getProxyForURL(rURL, aProxyHost, nProxyPort); + + bool ret = curl_run(rURL, out, aProxyHost, nProxyPort); + + if( nullptr != out.FileHandle ) + { + osl_syncFile(out.FileHandle); + osl_closeFile(out.FileHandle); + +// #i90930# Don't remove already downloaded bits, when curl_run reports an error +// because later calls might be successful +// if( ! ret ) +// osl_removeFile(out.File.pData); + } + + m_aCondition.reset(); + return ret; +} + + +void +Download::stop() +{ + m_aCondition.set(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/download.hxx b/extensions/source/update/check/download.hxx new file mode 100644 index 000000000..12a11ddde --- /dev/null +++ b/extensions/source/update/check/download.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <osl/conditn.hxx> +#include <salhelper/simplereferenceobject.hxx> + +struct DownloadInteractionHandler : public virtual salhelper::SimpleReferenceObject +{ + virtual bool checkDownloadDestination(const OUString& rFileName) = 0; + + // called if the destination file already exists, but resume is false + virtual bool downloadTargetExists(const OUString& rFileName) = 0; + + // called when curl reports an error + virtual void downloadStalled(const OUString& rErrorMessage) = 0; + + // progress handler + virtual void downloadProgressAt(sal_Int8 nPercent) = 0; + + // called on first progress notification + virtual void downloadStarted(const OUString& rFileName, sal_Int64 nFileSize) = 0; + + // called when download has been finished + virtual void downloadFinished(const OUString& rFileName) = 0; + +protected: + virtual ~DownloadInteractionHandler() override {} +}; + +class Download +{ +public: + Download(const css::uno::Reference<css::uno::XComponentContext>& xContext, + const rtl::Reference<DownloadInteractionHandler>& rHandler) + : m_xContext(xContext) + , m_aHandler(rHandler){}; + + // returns true when the content of rURL was successfully written to rLocalFile + bool start(const OUString& rURL, const OUString& rFile, const OUString& rDestinationDir); + + // stops the download after the next write operation + void stop(); + +protected: + // Determines the appropriate proxy settings for the given URL. Returns true if a proxy should be used + void getProxyForURL(std::u16string_view rURL, OString& rHost, sal_Int32& rPort) const; + +private: + osl::Condition m_aCondition; + const css::uno::Reference<css::uno::XComponentContext>& m_xContext; + const rtl::Reference<DownloadInteractionHandler> m_aHandler; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/onlinecheck.cxx b/extensions/source/update/check/onlinecheck.cxx new file mode 100644 index 000000000..af6ade879 --- /dev/null +++ b/extensions/source/update/check/onlinecheck.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <sal/macros.h> + +#include "onlinecheck.hxx" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wininet.h> + +// #i71984 +extern "C" bool WNT_hasInternetConnection() +{ + DWORD dwFlags; + WCHAR szConnectionName[1024]; + + __try { + bool fIsConnected = InternetGetConnectedStateExW( + &dwFlags, + szConnectionName, + SAL_N_ELEMENTS(szConnectionName), + 0 ); + + return fIsConnected; + + } __except( EXCEPTION_EXECUTE_HANDLER ) { + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/onlinecheck.hxx b/extensions/source/update/check/onlinecheck.hxx new file mode 100644 index 000000000..fcfedcf48 --- /dev/null +++ b/extensions/source/update/check/onlinecheck.hxx @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#if defined(_WIN32) +extern "C" bool WNT_hasInternetConnection(); +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/org/openoffice/Office/Addons.xcu b/extensions/source/update/check/org/openoffice/Office/Addons.xcu new file mode 100644 index 000000000..29aa55e47 --- /dev/null +++ b/extensions/source/update/check/org/openoffice/Office/Addons.xcu @@ -0,0 +1,42 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . +--> +<oor:component-data oor:name="Addons" oor:package="org.openoffice.Office" xmlns:install="http://openoffice.org/2004/installation" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <node oor:name="AddonUI" install:module="onlineupdate"> + <node oor:name="OfficeHelp"> + <node oor:name="UpdateCheckJob" oor:op="replace"> + <prop oor:name="URL" oor:type="xs:string"> + <value>vnd.sun.star.job:alias=UpdateCheck</value> + </prop> + <prop oor:name="ImageIdentifier" oor:type="xs:string"> + <value/> + </prop> + <prop oor:name="Title" oor:type="xs:string"> + <value xml:lang="en-US">~Check for Updates...</value> + </prop> + <prop oor:name="Target" oor:type="xs:string"> + <value>_self</value> + </prop> + <prop oor:name="Context" oor:type="xs:string"> + <value/> + </prop> + </node> + </node> + </node> +</oor:component-data> + diff --git a/extensions/source/update/check/org/openoffice/Office/Jobs.xcu b/extensions/source/update/check/org/openoffice/Office/Jobs.xcu new file mode 100644 index 000000000..58db40480 --- /dev/null +++ b/extensions/source/update/check/org/openoffice/Office/Jobs.xcu @@ -0,0 +1,66 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . +--> +<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office" xmlns:install="http://openoffice.org/2004/installation" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <node oor:name="Jobs" install:module="onlineupdate"> + <node oor:name="UpdateCheck" oor:op="replace"> + <prop oor:name="Service"> + <value>com.sun.star.setup.UpdateCheck</value> + </prop> + <node oor:name="Arguments"> + <prop oor:name="AutoCheckEnabled" oor:type="xs:boolean" oor:op="replace"> + <value>true</value> + </prop> + <prop oor:name="LastCheck" oor:type="xs:long" oor:op="replace"> + <value>0</value> + </prop> + <prop oor:name="CheckInterval" oor:type="xs:long" oor:op="replace"> + <!-- + Every Day = 86400 + Every Week = 604800 + Every Month = 2592000 + --> + <value>604800</value> + </prop> + <prop oor:name="DownloadDestination" oor:type="xs:string" oor:op="replace"> + <value></value> + </prop> + <prop oor:name="AutoDownloadEnabled" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + <prop oor:name="DownloadSupported" oor:type="xs:boolean" oor:op="replace"> + <value>true</value> + </prop> + <prop oor:name="DownloadPaused" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + <prop oor:name="ExtendedUserAgent" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + </node> + </node> + </node> + <node oor:name="Events" install:module="onlineupdate"> + <node oor:name="onFirstVisibleTask" oor:op="fuse"> + <node oor:name="JobList"> + <node oor:name="UpdateCheck" oor:op="replace"/> + </node> + </node> + </node> +</oor:component-data> + diff --git a/extensions/source/update/check/updatecheck.cxx b/extensions/source/update/check/updatecheck.cxx new file mode 100644 index 000000000..8669f890e --- /dev/null +++ b/extensions/source/update/check/updatecheck.cxx @@ -0,0 +1,1617 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <comphelper/scopeguard.hxx> +#include <config_folders.h> + +#include "updatecheck.hxx" + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/office/Quickstart.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteException.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> + +#include <rtl/bootstrap.hxx> +#include <osl/process.h> +#include <osl/file.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> + +#ifdef _WIN32 +#include <o3tl/safeCoInitUninit.hxx> +#include <objbase.h> +#endif + +#include "onlinecheck.hxx" +#include "updateprotocol.hxx" +#include "updatecheckconfig.hxx" + +namespace beans = com::sun::star::beans ; +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 uno = com::sun::star::uno ; + +constexpr OUStringLiteral PROPERTY_TITLE = u"BubbleHeading"; +constexpr OUStringLiteral PROPERTY_TEXT = u"BubbleText"; +constexpr OUStringLiteral PROPERTY_SHOW_BUBBLE = u"BubbleVisible"; +constexpr OUStringLiteral PROPERTY_CLICK_HDL = u"MenuClickHDL"; +constexpr OUStringLiteral PROPERTY_SHOW_MENUICON = u"MenuIconVisible"; + +// Returns the URL of the release note for the given position +OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled) +{ + for (auto const& elem : rInfo.ReleaseNotes) + { + if( pos == elem.Pos ) + { + if( (pos > 2) || !autoDownloadEnabled || elem.URL2.isEmpty() ) + return elem.URL; + } + else if( (pos == elem.Pos2) && ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled ) + return elem.URL2; + } + + return OUString(); +} + + +namespace +{ + +OUString getBuildId() +{ + OUString aPathVal("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"); + rtl::Bootstrap::expandMacros(aPathVal); + return aPathVal; +} + + +#if (defined LINUX || defined __sun) +OUString getBaseInstallation() +{ + OUString aPathVal("$BRAND_BASE_DIR"); + rtl::Bootstrap::expandMacros(aPathVal); + return aPathVal; +} +#endif + + +bool isObsoleteUpdateInfo(std::u16string_view rBuildId) +{ + return rBuildId != getBuildId() && !rBuildId.empty(); +} + + +OUString getImageFromFileName(const OUString& aFile) +{ +#ifndef _WIN32 + OUString aUnpackPath; + if( osl_getExecutableFile(&aUnpackPath.pData) == osl_Process_E_None ) + { + sal_uInt32 lastIndex = aUnpackPath.lastIndexOf('/'); + if ( lastIndex > 0 ) + { + aUnpackPath = OUString::Concat(aUnpackPath.subView( 0, lastIndex+1 )) + + "unpack_update"; + } + + oslFileHandle hOut = nullptr; + oslProcess hProcess = nullptr; + + 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 + nullptr, // [in] Security + nullptr, // [in] Working directory + nullptr, 0, // [in] Environment variables + &hProcess, // [out] Process handle + nullptr, &hOut, nullptr // [out] File handles for redirected I/O + ); + + if( osl_Process_E_None == rc ) + { + // Create a guard to ensure correct cleanup in its dtor in any case + comphelper::ScopeGuard g([hOut, hProcess] () { + osl_closeFile(hOut); + osl_freeProcessHandle(hProcess); + }); + + oslProcessInfo aInfo; + aInfo.Size = sizeof(oslProcessInfo); + + if( osl_Process_E_None == osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo) ) + { + if( 0 == aInfo.Code ) + { + char szBuffer[4096]; + sal_uInt64 nBytesRead = 0; + const sal_uInt64 nBytesToRead = sizeof(szBuffer) - 1; + + OUString aImageName; + while( osl_File_E_None == osl_readFile(hOut, szBuffer, nBytesToRead, &nBytesRead) ) + { + char *pc = szBuffer + nBytesRead; + do + { + *pc = '\0'; --pc; + } + while( ('\n' == *pc) || ('\r' == *pc) ); + + aImageName += OUString(szBuffer, pc - szBuffer + 1, osl_getThreadTextEncoding()); + + if( nBytesRead < nBytesToRead ) + break; + } + + if( osl::FileBase::E_None == osl::FileBase::getFileURLFromSystemPath(aImageName, aImageName) ) + return aImageName; + } + } + } + } +#endif + + return aFile; +} + + +uno::Reference< beans::XPropertySet > createMenuBarUI( + const uno::Reference< uno::XComponentContext >& xContext, + const uno::Reference< task::XJob >& xJob) +{ + if( !xContext.is() ) + throw uno::RuntimeException( + "UpdateCheckJob: empty component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager(xContext->getServiceManager()); + if( !xServiceManager.is() ) + throw uno::RuntimeException( + "UpdateCheckJob: unable to obtain service manager from component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< beans::XPropertySet > xMenuBarUI( + xServiceManager->createInstanceWithContext( "com.sun.star.setup.UpdateCheckUI", xContext ), + uno::UNO_QUERY_THROW); + + xMenuBarUI->setPropertyValue( PROPERTY_CLICK_HDL, uno::Any( xJob ) ); + + return xMenuBarUI; +} + + +typedef sal_Bool (* OnlineCheckFunc) (); + +class UpdateCheckThread : public WorkerThread +{ + +public: + UpdateCheckThread( osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + rtl::Reference<UpdateCheck> const & controller ); + + virtual void SAL_CALL join() override; + virtual void SAL_CALL terminate() override; + virtual void cancel() override; + + void cancelAsSoonAsPossible(); + +protected: + virtual ~UpdateCheckThread() override; + + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + + /* Wrapper around checkForUpdates */ + bool runCheck( bool & rbExtensionsChecked ); + +private: + + /* Used to avoid dialup login windows (on platforms we know how to double this) */ + static bool hasInternetConnection() + { +#ifdef _WIN32 + return WNT_hasInternetConnection(); +#else + return true; +#endif + } + + /* Creates a new instance of UpdateInformationProvider and returns this instance */ + uno::Reference<deployment::XUpdateInformationProvider> createProvider() + { + osl::MutexGuard aGuard(m_aMutex); + m_xProvider = deployment::UpdateInformationProvider::create(m_xContext); + return m_xProvider; + }; + + /* Returns the remembered instance of UpdateInformationProvider if any */ + uno::Reference<deployment::XUpdateInformationProvider> getProvider() + { osl::MutexGuard aGuard(m_aMutex); return m_xProvider; }; + + /* Releases the remembered instance of UpdateInformationProvider if any */ + void clearProvider() + { osl::MutexGuard aGuard(m_aMutex); m_xProvider.clear(); }; + + osl::Mutex m_aMutex; + +protected: + osl::Condition& m_aCondition; + +private: + const uno::Reference<uno::XComponentContext> m_xContext; + uno::Reference<deployment::XUpdateInformationProvider> m_xProvider; + rtl::Reference<UpdateCheck> m_controller; + bool m_cancelAsSoonAsPossible; +}; + + +class ManualUpdateCheckThread : public UpdateCheckThread +{ +public: + ManualUpdateCheckThread( osl::Condition& rCondition, const uno::Reference<uno::XComponentContext>& xContext ) : + UpdateCheckThread(rCondition, xContext, {}) {}; + + virtual void SAL_CALL run() override; +}; + + +class MenuBarButtonJob : public ::cppu::WeakImplHelper< task::XJob > +{ +public: + explicit MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck); + + // XJob + virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override; + +private: + rtl::Reference< UpdateCheck > m_aUpdateCheck; +}; + +class DownloadThread : public WorkerThread +{ +public: + DownloadThread( + osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + const rtl::Reference< DownloadInteractionHandler >& rHandler, + const OUString& rURL ); + + virtual void SAL_CALL run() override; + virtual void cancel() override; + virtual void SAL_CALL suspend() override; + virtual void SAL_CALL onTerminated() override; + +protected: + virtual ~DownloadThread() override; + +private: + osl::Condition& m_aCondition; + const uno::Reference<uno::XComponentContext> m_xContext; + const OUString m_aURL; + Download m_aDownload; +}; + + +class ShutdownThread : public osl::Thread +{ +public: + explicit ShutdownThread(const uno::Reference<uno::XComponentContext>& xContext); + + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + +protected: + virtual ~ShutdownThread() override; + +private: + osl::Condition m_aCondition; + const uno::Reference<uno::XComponentContext> m_xContext; +}; + + +UpdateCheckThread::UpdateCheckThread( osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + rtl::Reference<UpdateCheck> const & controller ) : + m_aCondition(rCondition), + m_xContext(xContext), + m_controller(controller), + m_cancelAsSoonAsPossible(false) +{ + 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 +UpdateCheckThread::cancel() +{ + uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider()); + + if( xProvider.is() ) + xProvider->cancel(); +} + +void UpdateCheckThread::cancelAsSoonAsPossible() { + { + osl::MutexGuard g(m_aMutex); + m_cancelAsSoonAsPossible = true; + } + m_aCondition.set(); +} + +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 = UpdateCheck::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() +{ + osl_setThreadName("UpdateCheckThread"); + + 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); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + + try { + bool bExtensionsChecked = false; + + while( 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); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + 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 < SAL_N_ELEMENTS(nRetryInterval) ) + ++n; + + tv.Seconds = nRetryInterval[n-1]; + aResult = m_aCondition.wait(&tv); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + } + else // reset retry counter + { + n = 0; + bExtensionsChecked = false; + } + } + } + + catch(const uno::Exception&) { + // Silently catch all errors + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" ); + } + +done: + if (m_controller.is()) { + m_controller->notifyUpdateCheckFinished(); + } +} + + +void SAL_CALL +ManualUpdateCheckThread::run() +{ + try { + bool bExtensionsChecked = false; + runCheck( bExtensionsChecked ); + m_aCondition.reset(); + } + catch(const uno::Exception&) { + // Silently catch all errors + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" ); + } +} + + +MenuBarButtonJob::MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck) : + m_aUpdateCheck(rUpdateCheck) +{ +}; + + +uno::Any SAL_CALL +MenuBarButtonJob::execute(const uno::Sequence<beans::NamedValue>& ) +{ + if ( m_aUpdateCheck->shouldShowExtUpdDlg() ) + m_aUpdateCheck->showExtensionDialog(); + else + m_aUpdateCheck->showDialog(); + + return uno::Any(); +} + + +DownloadThread::DownloadThread(osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + const rtl::Reference< DownloadInteractionHandler >& rHandler, + const OUString& rURL) : + m_aCondition(rCondition), + m_xContext(xContext), + m_aURL(rURL), + m_aDownload(xContext, rHandler) +{ + createSuspended(); +} + + +DownloadThread::~DownloadThread() +{ +} + + +void SAL_CALL +DownloadThread::run() +{ + osl_setThreadName("DownloadThread"); + +#ifdef _WIN32 + int nNbCallCoInitializeExForReinit = 0; + // for SystemShellExecute + o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit); +#endif + + while( schedule() ) + { + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + + OUString aLocalFile = rModel->getLocalFileName(); + 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 < SAL_N_ELEMENTS(nRetryInterval) ) + ++n; + + tv.Seconds = nRetryInterval[n-1]; + } + m_aCondition.wait(&tv); + } + else + { + // reset wait period after successful download + n=0; + } + } +#ifdef _WIN32 + o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit); +#endif +} + + +void 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<uno::XComponentContext>& xContext) : + m_xContext( xContext ) +{ + create(); +} + + +ShutdownThread::~ShutdownThread() +{ +} + + +void SAL_CALL +ShutdownThread::run() +{ + osl_setThreadName("ShutdownThread"); + + TimeValue tv = { 0, 250 }; + + m_aCondition.wait(&tv); + + // Tell QuickStarter not to veto .. + uno::Reference< css::beans::XFastPropertySet > xQuickStarter = css::office::Quickstart::createDefault(m_xContext); + + xQuickStarter->setFastPropertyValue(0, uno::Any(false)); + + // Shutdown the office + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(m_xContext); + + xDesktop->terminate(); +} + + +void SAL_CALL ShutdownThread::onTerminated() +{ + delete this; +} + + +} // anonymous namespace + +UpdateCheck::UpdateCheck() + : m_eState(NOT_INITIALIZED) + , m_eUpdateState(UPDATESTATES_COUNT) + , m_pThread(nullptr) + , m_bHasExtensionUpdate(false) + , m_bShowExtUpdDlg(false) + , m_updateCheckRunning(false) +{ +} + +UpdateCheck::~UpdateCheck() {} + +void +UpdateCheck::initialize(const uno::Sequence< beans::NamedValue >& rValues, + const uno::Reference<uno::XComponentContext>& xContext) +{ + std::scoped_lock aGuard(m_aMutex); + + if( NOT_INITIALIZED == m_eState ) + { + NamedValueByNameAccess aNameAccess(rValues); + UpdateCheckROModel aModel( aNameAccess ); + m_xContext = xContext; + + 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; + + OUString aLocalFileName = aModel.getLocalFileName(); + + if( !aLocalFileName.isEmpty() ) + { + 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(osl_FileStatus_Mask_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 everything + { + bContinueDownload = false; + bDownloadAvailable = true; + m_aImageName = getImageFromFileName( aLocalFileName ); + } + else // Calculate initial percent value. + { + sal_Int32 nPercent = static_cast<sal_Int32>(100 * nFileSize / nDownloadSize); + getUpdateHandler()->setProgress( nPercent ); + } + } + } + } + + if ( bContinueDownload ) + { + bool downloadPaused = aModel.isDownloadPaused(); + + enableDownload(true, downloadPaused); + // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex + 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 OUString aURL(getReleaseNote(m_aUpdateInfo, 5)); + if( !aURL.isEmpty() ) + 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, OUString() ); + storeReleaseNote( 2, OUString() ); + } + else + { + enableAutoCheck(aModel.isAutoCheckEnabled()); + if ( bDownloadAvailable ) + setUIState( UPDATESTATE_DOWNLOAD_AVAIL ); + else + { + // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex + setUIState(getUIState(m_aUpdateInfo)); + } + } + } + } +} + + +void +UpdateCheck::cancel() +{ + std::unique_lock aGuard(m_aMutex); + + WorkerThread *pThread = m_pThread; + UpdateState eUIState = getUIState(m_aUpdateInfo); + + aGuard.unlock(); + + if( nullptr != pThread ) + pThread->cancel(); + + setUIState(eUIState); +} + + +void +UpdateCheck::download() +{ + std::unique_lock aGuard(m_aMutex); + UpdateInfo aInfo(m_aUpdateInfo); + State eState = m_eState; + aGuard.unlock(); + + if (aInfo.Sources.empty()) + { + SAL_WARN("extensions.update", "download called without source"); + return; + } + + if( aInfo.Sources[0].IsDirect ) + { + // Ignore second click of a double click + if( DOWNLOADING != eState ) + { + shutdownThread(true); + + { + std::scoped_lock aGuard2(m_aMutex); + enableDownload(true); + } + setUIState(UPDATESTATE_DOWNLOADING); + } + } + else + { + showReleaseNote(aInfo.Sources[0].URL); // Display in browser + } +} + + +void +UpdateCheck::install() +{ + std::scoped_lock aGuard(m_aMutex); + + const uno::Reference< c3s::XSystemShellExecute > xShellExecute = c3s::SystemShellExecute::create( m_xContext ); + + try { + // Construct install command ?? + + // Store release note for position 3 and 4 + OUString aURL(getReleaseNote(m_aUpdateInfo, 3)); + storeReleaseNote(1, aURL); + + aURL = getReleaseNote(m_aUpdateInfo, 4); + storeReleaseNote(2, aURL); + + OUString aInstallImage(m_aImageName); + osl::FileBase::getSystemPathFromFileURL(aInstallImage, aInstallImage); + + sal_Int32 nFlags; +#if (defined LINUX || defined __sun) + nFlags = 42; + OUString aParameter = getBaseInstallation(); + if( !aParameter.isEmpty() ) + osl::FileBase::getSystemPathFromFileURL(aParameter, aParameter); + + aParameter += " &"; +#else + nFlags = c3s::SystemShellExecuteFlags::DEFAULTS; + OUString const aParameter; +#endif + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext ); + rModel->clearLocalFileName(); + + xShellExecute->execute(aInstallImage, aParameter, nFlags); + new ShutdownThread( m_xContext ); + } catch(const uno::Exception&) { + m_aUpdateHandler->setErrorMessage( m_aUpdateHandler->getDefaultInstErrMsg() ); + } +} + + +void +UpdateCheck::pause() +{ + std::unique_lock aGuard(m_aMutex); + + if( nullptr != m_pThread ) + m_pThread->suspend(); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + aGuard.unlock(); + + rModel->storeDownloadPaused(true); + setUIState(UPDATESTATE_DOWNLOAD_PAUSED); +} + + +void +UpdateCheck::resume() +{ + std::unique_lock aGuard(m_aMutex); + + if( nullptr != m_pThread ) + m_pThread->resume(); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + aGuard.unlock(); + + rModel->storeDownloadPaused(false); + setUIState(UPDATESTATE_DOWNLOADING); +} + + +void +UpdateCheck::closeAfterFailure() +{ + std::unique_lock aGuard(m_aMutex); + + if ( ( m_eState == DISABLED ) || ( m_eState == CHECK_SCHEDULED ) ) + { + const UpdateState eUIState = getUIState( m_aUpdateInfo ); + aGuard.unlock(); + setUIState( eUIState, true ); + } +} + +void UpdateCheck::notifyUpdateCheckFinished() { + std::scoped_lock l(m_aMutex); + m_updateCheckRunning = false; + m_updateCheckFinished.notify_all(); +} + +void UpdateCheck::waitForUpdateCheckFinished() { + UpdateCheckThread * thread; + { + std::scoped_lock l(m_aMutex); + thread = dynamic_cast<UpdateCheckThread *>(m_pThread); + } + if (thread != nullptr) { + thread->cancelAsSoonAsPossible(); + } + for (;;) { + std::unique_lock lock(m_aMutex); + if (!m_updateCheckRunning) { + return; + } + m_updateCheckFinished.wait(lock); + } +} + +void +UpdateCheck::shutdownThread(bool join) +{ + std::unique_lock aGuard(m_aMutex); + + // copy thread object pointer to stack + osl::Thread *pThread = m_pThread; + m_pThread = nullptr; + aGuard.unlock(); + + if( nullptr != pThread ) + { + pThread->terminate(); + if( join ) + { + m_aCondition.set(); + pThread->join(); + m_aCondition.reset(); + } + } +} + + +void +UpdateCheck::enableAutoCheck(bool enable) +{ + if( enable ) + { + m_updateCheckRunning = true; + m_pThread = new UpdateCheckThread(m_aCondition, m_xContext, this); + } + + m_eState = enable ? CHECK_SCHEDULED : DISABLED; +} + + +void +UpdateCheck::enableDownload(bool enable, bool paused) +{ + OSL_ASSERT(nullptr == m_pThread); + + if( enable ) + { + m_pThread = new DownloadThread(m_aCondition, m_xContext, this, m_aUpdateInfo.Sources[0].URL ); + State eState = DISABLED; + 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 OUString& rFileName) +{ + std::unique_lock 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.unlock(); + setUIState(eUIState); + } + + return cont; +} + + +bool UpdateCheck::checkDownloadDestination( const OUString& rFileName ) +{ + std::scoped_lock aGuard(m_aMutex); + + rtl::Reference< UpdateHandler > aUpdateHandler( getUpdateHandler() ); + + bool bReload = false; + + if( aUpdateHandler->isVisible() ) + { + bReload = aUpdateHandler->showOverwriteWarning( rFileName ); + } + + return bReload; +} + + +void +UpdateCheck::downloadStalled(const OUString& rErrorMessage) +{ + std::unique_lock aGuard(m_aMutex); + rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); + aGuard.unlock(); + + aUpdateHandler->setErrorMessage(rErrorMessage); + setUIState(UPDATESTATE_ERROR_DOWNLOADING); +} + + +void +UpdateCheck::downloadProgressAt(sal_Int8 nPercent) +{ + std::unique_lock aGuard(m_aMutex); + rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); + aGuard.unlock(); + + aUpdateHandler->setProgress(nPercent); + setUIState(UPDATESTATE_DOWNLOADING); +} + + +void +UpdateCheck::downloadStarted(const OUString& rLocalFileName, sal_Int64 nFileSize) +{ + if ( nFileSize > 0 ) + { + std::scoped_lock aGuard(m_aMutex); + + rtl::Reference< UpdateCheckConfig > aModel(UpdateCheckConfig::get(m_xContext)); + aModel->storeLocalFileName(rLocalFileName, nFileSize); + + // Bring-up release note for position 1 .. + const OUString aURL(getReleaseNote(m_aUpdateInfo, 1, aModel->isAutoDownloadEnabled())); + if( !aURL.isEmpty() ) + showReleaseNote(aURL); + } +} + + +void +UpdateCheck::downloadFinished(const OUString& rLocalFileName) +{ + std::unique_lock aGuard(m_aMutex); + + // no more retries + m_pThread->terminate(); + + m_aImageName = getImageFromFileName(rLocalFileName); + UpdateInfo aUpdateInfo(m_aUpdateInfo); + + aGuard.unlock(); + setUIState(UPDATESTATE_DOWNLOAD_AVAIL); + + // Bring-up release note for position 2 .. + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext ); + const OUString aURL(getReleaseNote(aUpdateInfo, 2, rModel->isAutoDownloadEnabled())); + if( !aURL.isEmpty() ) + showReleaseNote(aURL); +} + + +void +UpdateCheck::cancelDownload() +{ + shutdownThread(true); + + std::scoped_lock aGuard(m_aMutex); + enableDownload(false); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + + 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) +{ + std::unique_lock aGuard(m_aMutex); + + bool update_found = !m_aUpdateInfo.BuildId.isEmpty(); + 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.unlock(); + setUIState(eDialogState, true); // suppress bubble as Dialog will be visible soon + aGuard.lock(); + } + + getUpdateHandler()->setVisible(); + + // 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) +{ + std::unique_lock aGuard(m_aMutex); + + bool bSuppressBubble = aInfo.BuildId == 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 = std::find_if(m_aUpdateInfo.Sources.begin(), m_aUpdateInfo.Sources.end(), + [](const DownloadSource& rSource) { return rSource.IsDirect; }); + + 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(); + + for (auto & elem : m_aUpdateInfo.ReleaseNotes) + { + if( ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled && !elem.URL2.isEmpty()) + { + elem.URL = elem.URL2; + elem.URL2.clear(); + elem.Pos = elem.Pos2; + elem.Pos2 = 0; + } + } + + // do not move below store/clear .. + rModel->updateLastChecked(); + + UpdateState eUIState; + if( !m_aUpdateInfo.Sources.empty() ) + { + 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.unlock(); + setUIState(eUIState, bSuppressBubble); +} + + +void +UpdateCheck::setCheckFailedState() +{ + setUIState(UPDATESTATE_ERROR_CHECKING); +} + + +void UpdateCheck::handleMenuBarUI( const rtl::Reference< UpdateHandler >& rUpdateHandler, + UpdateState& eState, + bool suppressBubble ) +{ + uno::Reference<beans::XPropertySet> 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::Any(false) ); + } + else + { + xMenuBarUI->setPropertyValue( PROPERTY_TITLE, uno::Any(rUpdateHandler->getBubbleTitle(eState)) ); + xMenuBarUI->setPropertyValue( PROPERTY_TEXT, uno::Any(rUpdateHandler->getBubbleText(eState)) ); + + if( ! suppressBubble && ( ! rUpdateHandler->isVisible() || rUpdateHandler->isMinimized() ) ) + xMenuBarUI->setPropertyValue( PROPERTY_SHOW_BUBBLE, uno::Any( true ) ); + + if( UPDATESTATE_CHECKING != eState ) + xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(true) ); + } + } +} + + +void UpdateCheck::setUIState(UpdateState eState, bool suppressBubble) +{ + std::unique_lock 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<UpdateHandler> aUpdateHandler(getUpdateHandler()); + OSL_ASSERT( aUpdateHandler.is() ); + + UpdateInfo aUpdateInfo(m_aUpdateInfo); + OUString aImageName(m_aImageName); + + aGuard.unlock(); + + handleMenuBarUI( aUpdateHandler, eState, suppressBubble ); + + if( (UPDATESTATE_UPDATE_AVAIL == eState) + || (UPDATESTATE_DOWNLOAD_PAUSED == eState) + || (UPDATESTATE_DOWNLOADING == eState) ) + { + uno::Reference< uno::XComponentContext > xContext(m_xContext); + + 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.isEmpty() ) + { + if( rInfo.Sources[0].IsDirect ) + eUIState = UPDATESTATE_UPDATE_AVAIL; + else + eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD; + } + + return eUIState; +} + + +void +UpdateCheck::showReleaseNote(const OUString& rURL) const +{ + const uno::Reference< c3s::XSystemShellExecute > xShellExecute( + c3s::SystemShellExecute::create( m_xContext ) ); + + try { + xShellExecute->execute(rURL, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY); + } catch(const c3s::SystemShellExecuteException&) { + } +} + + +bool +UpdateCheck::storeReleaseNote(sal_Int8 nNum, const OUString &rURL) +{ + osl::FileBase::RC rc; + OUString aTargetDir( UpdateCheckConfig::getAllUsersDirectory() + "/sun" ); + + osl::Directory::createPath( aTargetDir ); + + OUString aFileName = "releasenote" + + OUString::number( nNum ) + + ".url"; + + OUString aFilePath; + rc = osl::FileBase::getAbsoluteFileURL( aTargetDir, aFileName, aFilePath ); + if ( rc != osl::FileBase::E_None ) return false; + + osl::File::remove( aFilePath ); + + // don't store empty release notes, but delete old ones + if ( rURL.isEmpty() ) + return true; + + osl::File aFile( aFilePath ); + rc = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + if ( rc != osl::FileBase::E_None ) return false; + + OString aLineBuf("[InternetShortcut]\r\n"); + sal_uInt64 nWritten = 0; + + OUString aURL( rURL ); +#ifdef _WIN32 + rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten ); + if ( rc != osl::FileBase::E_None ) return false; + aURL = "URL=" + rURL; +#endif + aLineBuf = 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() +{ + uno::Reference< uno::XInterface > xService; + + if( ! m_xContext.is() ) + throw uno::RuntimeException( + "UpdateCheck::showExtensionDialog(): empty component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager( m_xContext->getServiceManager() ); + if( !xServiceManager.is() ) + throw uno::RuntimeException( + "UpdateCheck::showExtensionDialog(): unable to obtain service manager from component context", uno::Reference< uno::XInterface > () ); + + xService = xServiceManager->createInstanceWithContext( "com.sun.star.deployment.ui.PackageManagerDialog", m_xContext ); + uno::Reference< task::XJobExecutor > xExecutable( xService, uno::UNO_QUERY ); + if ( xExecutable.is() ) + xExecutable->trigger( "SHOW_UPDATE_DIALOG" ); +} + + +rtl::Reference<UpdateHandler> +UpdateCheck::getUpdateHandler() +{ + std::scoped_lock aGuard(m_aMutex); + + if( ! m_aUpdateHandler.is() ) + m_aUpdateHandler = new UpdateHandler(m_xContext, this); + + return m_aUpdateHandler; +} + + +uno::Reference< task::XInteractionHandler > +UpdateCheck::getInteractionHandler() const +{ + std::scoped_lock aGuard(m_aMutex); + + uno::Reference< task::XInteractionHandler > xHandler; + + if( m_aUpdateHandler.is() && m_aUpdateHandler->isVisible() ) + xHandler = m_aUpdateHandler.get(); + + return xHandler; +} + + +bool +UpdateCheck::isDialogShowing() const +{ + std::scoped_lock aGuard(m_aMutex); + return m_aUpdateHandler.is() && m_aUpdateHandler->isVisible(); +}; + + +void +UpdateCheck::autoCheckStatusChanged(bool enabled) +{ + std::unique_lock 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.unlock(); + setUIState(eState); + } +}; + + +void +UpdateCheck::autoCheckIntervalChanged() +{ + // just wake-up + m_aCondition.set(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheck.hxx b/extensions/source/update/check/updatecheck.hxx new file mode 100644 index 000000000..546616f57 --- /dev/null +++ b/extensions/source/update/check/updatecheck.hxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <condition_variable> +#include <mutex> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <osl/conditn.hxx> +#include <osl/thread.hxx> +#include <rtl/instance.hxx> + +#include "updateinfo.hxx" +#include "updatecheckconfiglistener.hxx" +#include "actionlistener.hxx" +#include "updatehdl.hxx" +#include "download.hxx" + + +class UpdateCheck; + +class UpdateCheckInitData { + +public: + inline rtl::Reference< UpdateCheck > operator() () const; +}; + +class WorkerThread : public osl::Thread +{ +public: + virtual void cancel() = 0; +}; + +class UpdateCheck : + public UpdateCheckConfigListener, + public IActionListener, + public DownloadInteractionHandler, + public rtl::StaticWithInit< rtl::Reference< UpdateCheck >, UpdateCheckInitData > +{ + UpdateCheck(); + + virtual ~UpdateCheck() override; + +public: + operator rtl::Reference< UpdateCheckConfigListener > () + { return static_cast< UpdateCheckConfigListener * > (this); } + + void initialize(const css::uno::Sequence<css::beans::NamedValue>& rValues, + const css::uno::Reference<css::uno::XComponentContext>& xContext); + + // Update internal update info member + void setUpdateInfo(const UpdateInfo& aInfo); + + /* This method turns on the menubar icon, triggers the bubble window or + * updates the dialog text when appropriate + */ + void setUIState(UpdateState eState, bool suppressBubble = false); + + // Returns the UI state that matches rInfo best + static UpdateState getUIState(const UpdateInfo& rInfo); + + // Check for updates failed + void setCheckFailedState(); + + // Executes the update check dialog for manual checks and downloads interaction + void showDialog(bool forceCheck = false); + + // Returns true if the update dialog is currently showing + bool isDialogShowing() const; + bool shouldShowExtUpdDlg() const { return ( m_bShowExtUpdDlg && m_bHasExtensionUpdate ); } + void showExtensionDialog(); + void setHasExtensionUpdates( bool bHasUpdates ) { m_bHasExtensionUpdate = bHasUpdates; } + bool hasOfficeUpdate() const { return (m_aUpdateInfo.BuildId.getLength() > 0); } + + // DownloadInteractionHandler + virtual bool downloadTargetExists(const OUString& rFileName) override; + virtual void downloadStalled(const OUString& rErrorMessage) override; + virtual void downloadProgressAt(sal_Int8 nProcent) override; + virtual void downloadStarted(const OUString& rLocalFileName, sal_Int64 nFileSize) override; + virtual void downloadFinished(const OUString& rLocalFileName) override; + // checks if the download target already exists and asks user what to do next + virtual bool checkDownloadDestination( const OUString& rFile ) override; + + // Cancels the download action (and resumes checking if enabled) + void cancelDownload(); + + // Returns the XInteractionHandler of the UpdateHandler instance if present (and visible) + css::uno::Reference< css::task::XInteractionHandler > getInteractionHandler() const; + + // UpdateCheckConfigListener + virtual void autoCheckStatusChanged(bool enabled) override; + virtual void autoCheckIntervalChanged() override; + + // IActionListener + void cancel() override; + void download() override; + void install() override; + void pause() override; + void resume() override; + void closeAfterFailure() override; + + void notifyUpdateCheckFinished(); + + void waitForUpdateCheckFinished(); + +private: + + // Schedules or cancels next automatic check for updates + void enableAutoCheck(bool enable); + + // Starts/resumes or stops a download + void enableDownload(bool enable, bool paused=false); + + // Shuts down the currently running thread + void shutdownThread(bool join); + + // Returns the update handler instance + rtl::Reference<UpdateHandler> getUpdateHandler(); + + // Open the given URL in a browser + void showReleaseNote(const OUString& rURL) const; + + // stores the release note url on disk to be used by setup app + static bool storeReleaseNote(sal_Int8 nNum, const OUString &rURL); + + /* This method turns on the menubar icon and triggers the bubble window + */ + void handleMenuBarUI( const rtl::Reference< UpdateHandler >& rUpdateHandler, + UpdateState& eState, bool suppressBubble ); + enum State { + NOT_INITIALIZED, + DISABLED, + CHECK_SCHEDULED, + DOWNLOADING, + DOWNLOAD_PAUSED + }; + + State m_eState; + UpdateState m_eUpdateState; + + mutable std::recursive_mutex m_aMutex; + WorkerThread *m_pThread; + osl::Condition m_aCondition; + + UpdateInfo m_aUpdateInfo; + OUString m_aImageName; + bool m_bHasExtensionUpdate; + bool m_bShowExtUpdDlg; + + rtl::Reference<UpdateHandler> m_aUpdateHandler; + css::uno::Reference<css::beans::XPropertySet> m_xMenuBarUI; + css::uno::Reference<css::uno::XComponentContext> m_xContext; + + bool m_updateCheckRunning = false; + std::condition_variable_any m_updateCheckFinished; + + friend class UpdateCheckInitData; +}; + +inline rtl::Reference< UpdateCheck > +UpdateCheckInitData::operator() () const +{ + return rtl::Reference< UpdateCheck > (new UpdateCheck()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfig.cxx b/extensions/source/update/check/updatecheckconfig.cxx new file mode 100644 index 000000000..e728d91e7 --- /dev/null +++ b/extensions/source/update/check/updatecheckconfig.cxx @@ -0,0 +1,660 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "updatecheckconfig.hxx" +#include "updatecheck.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/security.hxx> +#include <osl/time.h> +#include <osl/file.hxx> +#include <sal/macros.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#ifdef _WIN32 +#include <objbase.h> +#include <shlobj.h> +#endif + +namespace container = com::sun::star::container ; +namespace beans = com::sun::star::beans ; +namespace lang = com::sun::star::lang ; +namespace util = com::sun::star::util ; +namespace uno = com::sun::star::uno ; + +#define LAST_CHECK "LastCheck" +#define UPDATE_VERSION "UpdateVersion" +#define UPDATE_BUILDID "UpdateBuildId" +#define UPDATE_DESCRIPTION "UpdateDescription" +#define DOWNLOAD_URL "DownloadURL" +#define IS_DIRECT_DOWNLOAD "IsDirectDownload" +#define OLD_VERSION "UpdateFoundFor" +#define AUTOCHECK_ENABLED "AutoCheckEnabled" +#define AUTODOWNLOAD_ENABLED "AutoDownloadEnabled" +#define CHECK_INTERVAL "CheckInterval" +#define LOCAL_FILE "LocalFile" +#define DOWNLOAD_SIZE "DownloadSize" +#define DOWNLOAD_PAUSED "DownloadPaused" +#define DOWNLOAD_DESTINATION "DownloadDestination" +#define RELEASE_NOTE "ReleaseNote" + +#define PROPERTY_VERSION "Version" + +const char * const aUpdateEntryProperties[] = { + UPDATE_VERSION, + UPDATE_BUILDID, + UPDATE_DESCRIPTION, + DOWNLOAD_URL, + IS_DIRECT_DOWNLOAD, + RELEASE_NOTE"1", + RELEASE_NOTE"2", + RELEASE_NOTE"3", + RELEASE_NOTE"4", + RELEASE_NOTE"5", + OLD_VERSION +}; + +const sal_uInt32 nUpdateEntryProperties = SAL_N_ELEMENTS(aUpdateEntryProperties); + +css::uno::Any NamedValueByNameAccess::getValue(const char * pName) +{ + const sal_Int32 nLen = m_rValues.getLength(); + for( sal_Int32 n=0; n < nLen; ++n ) + { + if( m_rValues[n].Name.equalsAscii( pName ) ) + return m_rValues[n].Value; + } + return css::uno::Any(); +} + +bool +UpdateCheckROModel::isAutoCheckEnabled() const +{ + return m_aNameAccess.getValue(AUTOCHECK_ENABLED).get<bool>(); +} + +bool +UpdateCheckROModel::isDownloadPaused() const +{ + return m_aNameAccess.getValue(DOWNLOAD_PAUSED).get<bool>(); +} + +OUString +UpdateCheckROModel::getStringValue(const char * pStr) const +{ + uno::Any aAny( m_aNameAccess.getValue(pStr) ); + OUString aRet; + + aAny >>= aRet; + + return aRet; +} + +OUString UpdateCheckROModel::getLocalFileName() const +{ + return getStringValue(LOCAL_FILE); +}; + +sal_Int64 UpdateCheckROModel::getDownloadSize() const +{ + uno::Any aAny( m_aNameAccess.getValue(DOWNLOAD_SIZE) ); + sal_Int64 nRet = -1; + + aAny >>= nRet; + return nRet; +}; + +OUString +UpdateCheckROModel::getUpdateEntryVersion() const +{ + return getStringValue(OLD_VERSION); +} + +void +UpdateCheckROModel::getUpdateEntry(UpdateInfo& rInfo) const +{ + rInfo.BuildId = getStringValue(UPDATE_BUILDID); + rInfo.Version = getStringValue(UPDATE_VERSION); + rInfo.Description = getStringValue(UPDATE_DESCRIPTION); + + bool isDirectDownload = false; + m_aNameAccess.getValue(IS_DIRECT_DOWNLOAD) >>= isDirectDownload; + + rInfo.Sources.push_back( DownloadSource( isDirectDownload, getStringValue(DOWNLOAD_URL) ) ); + + for(sal_Int32 n=1; n < 6; ++n ) + { + OUString aUStr = getStringValue( + OString(OString::Concat(RELEASE_NOTE) + OString::number(n)).getStr()); + if( !aUStr.isEmpty() ) + rInfo.ReleaseNotes.push_back(ReleaseNote(static_cast<sal_Int8>(n), aUStr)); + } +} + +OUString UpdateCheckConfig::getDownloadsDirectory() +{ + OUString aRet; + +#ifdef _WIN32 + PWSTR szPath; + + if (SHGetKnownFolderPath(FOLDERID_Downloads, 0, nullptr, &szPath) == S_OK) + { + aRet = o3tl::toU(szPath); + CoTaskMemFree(szPath); + osl::FileBase::getFileURLFromSystemPath( aRet, aRet ); + } +#else + // This should become a desktop specific setting in some system backend .. + OUString aHomeDir; + osl::Security().getHomeDir( aHomeDir ); + aRet = aHomeDir + "/Desktop"; + + // Set path to home directory when there is no /Desktop directory + osl::Directory aDocumentsDir( aRet ); + if( osl::FileBase::E_None != aDocumentsDir.open() ) + aRet = aHomeDir; +#endif + + return aRet; +} + +OUString UpdateCheckConfig::getAllUsersDirectory() +{ + OUString aRet; + +#ifdef _WIN32 + WCHAR szPath[MAX_PATH]; + + if (TRUE == SHGetSpecialFolderPathW(nullptr, szPath, CSIDL_COMMON_DOCUMENTS, true)) + { + aRet = o3tl::toU(szPath); + osl::FileBase::getFileURLFromSystemPath( aRet, aRet ); + } +#else + osl::FileBase::getTempDirURL(aRet); +#endif + + return aRet; +} + +UpdateCheckConfig::UpdateCheckConfig( const uno::Reference<container::XNameContainer>& xContainer, + const uno::Reference<container::XNameContainer>& xAvailableUpdates, + const uno::Reference<container::XNameContainer>& xIgnoredUpdates, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener ) : + m_xContainer( xContainer ), + m_xAvailableUpdates( xAvailableUpdates ), + m_xIgnoredUpdates( xIgnoredUpdates ), + m_rListener( rListener ) +{} + +UpdateCheckConfig::~UpdateCheckConfig() +{} + +::rtl::Reference< UpdateCheckConfig > +UpdateCheckConfig::get( + const uno::Reference<uno::XComponentContext>& xContext, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get( xContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Office.Jobs/Jobs/UpdateCheck/Arguments"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< container::XNameContainer > xContainer( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + aProperty.Value <<= OUString("/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates"); + aArgumentList = { uno::Any(aProperty) }; + uno::Reference< container::XNameContainer > xIgnoredExt( xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), uno::UNO_QUERY_THROW ); + + aProperty.Value <<= OUString("/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/AvailableUpdates"); + aArgumentList = { uno::Any(aProperty) }; + uno::Reference< container::XNameContainer > xUpdateAvail( xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), uno::UNO_QUERY_THROW ); + + return new UpdateCheckConfig( xContainer, xUpdateAvail, xIgnoredExt, rListener ); +} + +bool +UpdateCheckConfig::isAutoCheckEnabled() const +{ + bool nValue = false; + const_cast < UpdateCheckConfig *> (this)->getByName( AUTOCHECK_ENABLED ) >>= nValue; + return nValue; +} + +bool +UpdateCheckConfig::isAutoDownloadEnabled() const +{ + bool nValue = false; + const_cast < UpdateCheckConfig *> (this)->getByName( AUTODOWNLOAD_ENABLED ) >>= nValue; + return nValue; +} + +OUString +UpdateCheckConfig::getUpdateEntryVersion() const +{ + OUString aValue; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( OLD_VERSION ) >>= aValue; + + return aValue; +} + +sal_Int64 +UpdateCheckConfig::getLastChecked() const +{ + sal_Int64 nValue = 0; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( LAST_CHECK ) >>= nValue; + + return nValue; +} + +sal_Int64 +UpdateCheckConfig::getCheckInterval() const +{ + sal_Int64 nValue = 0; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( CHECK_INTERVAL ) >>= nValue; + + return nValue; +} + +OUString +UpdateCheckConfig::getLocalFileName() const +{ + OUString aName = LOCAL_FILE; + OUString aRet; + + if( m_xContainer->hasByName(aName) ) + m_xContainer->getByName(aName) >>= aRet; + + return aRet; +} + +OUString +UpdateCheckConfig::getDownloadDestination() const +{ + OUString aRet; + + const_cast <UpdateCheckConfig *> (this)->getByName(DOWNLOAD_DESTINATION) >>= aRet; + + return aRet; +} + +void +UpdateCheckConfig::storeLocalFileName(const OUString& rLocalFileName, sal_Int64 nFileSize) +{ + const sal_uInt8 nItems = 2; + const OUString aNameList[nItems] = { OUString(LOCAL_FILE), OUString(DOWNLOAD_SIZE) }; + const uno::Any aValueList[nItems] = { uno::Any(rLocalFileName), uno::Any(nFileSize) }; + + for( sal_uInt8 i=0; i < nItems; ++i ) + { + if( m_xContainer->hasByName(aNameList[i]) ) + m_xContainer->replaceByName(aNameList[i], aValueList[i]); + else + m_xContainer->insertByName(aNameList[i], aValueList[i]); + } + + commitChanges(); +} + +void +UpdateCheckConfig::clearLocalFileName() +{ + const sal_uInt8 nItems = 2; + const OUString aNameList[nItems] = { OUString(LOCAL_FILE), OUString(DOWNLOAD_SIZE) }; + + for(const auto & i : aNameList) + { + if( m_xContainer->hasByName(i) ) + m_xContainer->removeByName(i); + } + + commitChanges(); +} + +void +UpdateCheckConfig::storeDownloadPaused(bool paused) +{ + replaceByName(DOWNLOAD_PAUSED , uno::Any(paused)); + commitChanges(); +} + +void +UpdateCheckConfig::updateLastChecked() +{ + TimeValue systime; + osl_getSystemTime(&systime); + + sal_Int64 lastCheck = systime.Seconds; + + replaceByName(LAST_CHECK, uno::Any(lastCheck)); +} + +void +UpdateCheckConfig::storeUpdateFound( const UpdateInfo& rInfo, const OUString& aCurrentBuild) + +{ + bool autoDownloadEnabled = isAutoDownloadEnabled(); + + uno::Any aValues[nUpdateEntryProperties] = + { + uno::Any(rInfo.Version), + uno::Any(rInfo.BuildId), + uno::Any(rInfo.Description), + uno::Any(rInfo.Sources[0].URL), + uno::Any(rInfo.Sources[0].IsDirect), + uno::Any(getReleaseNote(rInfo, 1, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 2, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 3, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 4, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 5, autoDownloadEnabled) ), + uno::Any(aCurrentBuild) + }; + + OUString aName; + for( sal_uInt32 n=0; n < nUpdateEntryProperties; ++n ) + { + aName = OUString::createFromAscii(aUpdateEntryProperties[n]); + + if( m_xContainer->hasByName(aName) ) + m_xContainer->replaceByName(aName, aValues[n]); + else + m_xContainer->insertByName(aName,aValues[n]); + } + + commitChanges(); +} + +void +UpdateCheckConfig::clearUpdateFound() +{ + OUString aName; + + for(const char* aUpdateEntryProperty : aUpdateEntryProperties) + { + aName = OUString::createFromAscii(aUpdateEntryProperty); + + try { + if( m_xContainer->hasByName(aName) ) + m_xContainer->removeByName(aName); + } catch(const lang::WrappedTargetException& ) { + // Can not remove value, probably in share layer + OSL_ASSERT(false); + m_xContainer->replaceByName(aName, uno::Any(OUString())); + } + } + + /* As we have removed UpdateVersionFound from the shared configuration + * existing entries in the user layer do not have a oor operation and + * thus are completely ignored (which also means they can not be removed). + */ + + commitChanges(); +} + +uno::Type SAL_CALL +UpdateCheckConfig::getElementType() +{ + return m_xContainer->getElementType(); +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasElements() +{ + return m_xContainer->hasElements(); +} + +uno::Any SAL_CALL +UpdateCheckConfig::getByName( const OUString& aName ) +{ + uno::Any aValue = m_xContainer->getByName( aName ); + + // Provide dynamic default value + if( aName == DOWNLOAD_DESTINATION ) + { + OUString aStr; + aValue >>= aStr; + + if( aStr.isEmpty() ) + aValue <<= getDownloadsDirectory(); + } + return aValue; +} + +uno::Sequence< OUString > SAL_CALL +UpdateCheckConfig::getElementNames() +{ + return m_xContainer->getElementNames(); +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasByName( const OUString& aName ) +{ + return m_xContainer->hasByName( aName ); +} + +void SAL_CALL +UpdateCheckConfig::replaceByName( const OUString& aName, const uno::Any& aElement ) +{ + return m_xContainer->replaceByName( aName, aElement ); +} + +// XChangesBatch + +void SAL_CALL +UpdateCheckConfig::commitChanges() +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + util::ChangesSet aChangesSet = xChangesBatch->getPendingChanges(); + xChangesBatch->commitChanges(); + + if( m_rListener.is() ) + { + const sal_Int32 nChanges = aChangesSet.getLength(); + OUString aString; + + for( sal_Int32 i=0; i<nChanges; ++i ) + { + aChangesSet[i].Accessor >>= aString; + if( aString.endsWith(AUTOCHECK_ENABLED "']") ) + { + bool bEnabled = false; + aChangesSet[i].Element >>= bEnabled; + m_rListener->autoCheckStatusChanged(bEnabled); + } + else if( aString.endsWith(CHECK_INTERVAL "']") ) + { + m_rListener->autoCheckIntervalChanged(); + } + } + } + } + + xChangesBatch.set( m_xAvailableUpdates, uno::UNO_QUERY ); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + xChangesBatch->commitChanges(); + } + xChangesBatch.set( m_xIgnoredUpdates, uno::UNO_QUERY ); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + xChangesBatch->commitChanges(); + } +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasPendingChanges( ) +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() ) + return xChangesBatch->hasPendingChanges(); + + return false; +} + +uno::Sequence< util::ElementChange > SAL_CALL +UpdateCheckConfig::getPendingChanges( ) +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() ) + return xChangesBatch->getPendingChanges(); + + return uno::Sequence< util::ElementChange >(); +} + +bool UpdateCheckConfig::storeExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ) +{ + bool bNotify = true; + + if ( m_xAvailableUpdates->hasByName( rExtensionName ) ) + uno::Reference< beans::XPropertySet >( m_xAvailableUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->setPropertyValue( PROPERTY_VERSION, uno::Any( rVersion ) ); + else + { + uno::Reference< beans::XPropertySet > elem( uno::Reference< lang::XSingleServiceFactory >( m_xAvailableUpdates, uno::UNO_QUERY_THROW )->createInstance(), uno::UNO_QUERY_THROW ); + elem->setPropertyValue( PROPERTY_VERSION, uno::Any( rVersion ) ); + m_xAvailableUpdates->insertByName( rExtensionName, uno::Any( elem ) ); + } + + if ( m_xIgnoredUpdates->hasByName( rExtensionName ) ) + { + OUString aIgnoredVersion; + uno::Any aValue( uno::Reference< beans::XPropertySet >( m_xIgnoredUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue >>= aIgnoredVersion; + if ( aIgnoredVersion.isEmpty() ) // no version means ignore all updates + bNotify = false; + else if ( aIgnoredVersion == rVersion ) // the user wanted to ignore this update + bNotify = false; + } + + commitChanges(); + + return bNotify; +} + +bool UpdateCheckConfig::checkExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ) +{ + if ( m_xAvailableUpdates->hasByName( rExtensionName ) ) + { + OUString aStoredVersion; + uno::Any aValue( uno::Reference< beans::XPropertySet >( m_xAvailableUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue >>= aStoredVersion; + + if ( m_xIgnoredUpdates->hasByName( rExtensionName ) ) + { + OUString aIgnoredVersion; + uno::Any aValue2( uno::Reference< beans::XPropertySet >( m_xIgnoredUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue2 >>= aIgnoredVersion; + if ( aIgnoredVersion.isEmpty() ) // no version means ignore all updates + return false; + else if ( aIgnoredVersion == aStoredVersion ) // the user wanted to ignore this update + return false; + // TODO: else delete ignored entry? + } + if ( isVersionGreater( rVersion, aStoredVersion ) ) + return true; + else + { + m_xAvailableUpdates->removeByName( rExtensionName ); + commitChanges(); + } + } + + return false; +} + +OUString UpdateCheckConfig::getSubVersion( const OUString& rVersion, + sal_Int32 *nIndex ) +{ + while ( *nIndex < rVersion.getLength() && rVersion[*nIndex] == '0') + { + ++*nIndex; + } + + return rVersion.getToken( 0, '.', *nIndex ); +} + +/// checks if the second version string is greater than the first one +bool UpdateCheckConfig::isVersionGreater( const OUString& rVersion1, + const OUString& rVersion2 ) +{ + for ( sal_Int32 i1 = 0, i2 = 0; i1 >= 0 || i2 >= 0; ) + { + OUString sSub1( getSubVersion( rVersion1, &i1 ) ); + OUString sSub2( getSubVersion( rVersion2, &i2 ) ); + + if ( sSub1.getLength() < sSub2.getLength() ) { + return true; + } else if ( sSub1.getLength() > sSub2.getLength() ) { + return false; + } else if ( sSub1 < sSub2 ) { + return true; + } else if ( sSub1 > sSub2 ) { + return false; + } + } + return false; +} + +OUString SAL_CALL +UpdateCheckConfig::getImplementationName() +{ + return "vnd.sun.UpdateCheckConfig"; +} + +sal_Bool SAL_CALL +UpdateCheckConfig::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +uno::Sequence< OUString > SAL_CALL +UpdateCheckConfig::getSupportedServiceNames() +{ + return { "com.sun.star.setup.UpdateCheckConfig" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateCheckConfig_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(UpdateCheckConfig::get(context, *UpdateCheck::get()).get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfig.hxx b/extensions/source/update/check/updatecheckconfig.hxx new file mode 100644 index 000000000..a9836c624 --- /dev/null +++ b/extensions/source/update/check/updatecheckconfig.hxx @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <rtl/ref.hxx> + +#include "updatecheckconfiglistener.hxx" +#include "updateinfo.hxx" + +/* This helper class provides by name access to a sequence of named values */ +class NamedValueByNameAccess +{ + const css::uno::Sequence< css::beans::NamedValue >& m_rValues; + +public: + explicit NamedValueByNameAccess( + const css::uno::Sequence< css::beans::NamedValue >& rValues) : + m_rValues(rValues) {} ; + + css::uno::Any getValue(const char * pName); +}; + + +/* This class encapsulates the configuration item actually used for storing the state + * the update check is actually in. + */ +class UpdateCheckROModel +{ +public: + explicit UpdateCheckROModel(NamedValueByNameAccess& aNameAccess) : m_aNameAccess(aNameAccess) {}; + + bool isAutoCheckEnabled() const; + bool isDownloadPaused() const; + OUString getLocalFileName() const; + sal_Int64 getDownloadSize() const; + + OUString getUpdateEntryVersion() const; + void getUpdateEntry(UpdateInfo& rInfo) const; + +private: + + OUString getStringValue(const char *) const; + + NamedValueByNameAccess& m_aNameAccess; +}; + + +/* This class implements the non published UNO service com.sun.star.setup.UpdateCheckConfig, + * which primary use is to be able to track changes done in the Tools -> Options page of this + * component, as this is not supported by the OOo configuration for extendable groups. + */ + +class UpdateCheckConfig : public ::cppu::WeakImplHelper< + css::container::XNameReplace, + css::util::XChangesBatch, + css::lang::XServiceInfo > +{ + UpdateCheckConfig( const css::uno::Reference< css::container::XNameContainer >& xContainer, + const css::uno::Reference< css::container::XNameContainer >& xAvailableUpdates, + const css::uno::Reference< css::container::XNameContainer >& xIgnoredUpdates, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener ); + + virtual ~UpdateCheckConfig() override; + +public: + + static ::rtl::Reference< UpdateCheckConfig > get( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener = ::rtl::Reference< UpdateCheckConfigListener >()); + + // Should really implement ROModel... + bool isAutoCheckEnabled() const; + bool isAutoDownloadEnabled() const; + OUString getUpdateEntryVersion() const; + + /* Updates the timestamp of last check, but does not commit the change + * as either clearUpdateFound() or setUpdateFound() are expected to get + * called next. + */ + void updateLastChecked(); + + /* Returns the date of the last successful check in seconds since 1970 */ + sal_Int64 getLastChecked() const; + + /* Returns configured check interval in seconds */ + sal_Int64 getCheckInterval() const; + + /* Reset values of previously remembered update + */ + void clearUpdateFound(); + + /* Stores the specified data of an available update + */ + void storeUpdateFound(const UpdateInfo& rInfo, const OUString& aCurrentBuild); + + // Returns the local file name of a started download + OUString getLocalFileName() const; + + // Returns the local file name of a started download + OUString getDownloadDestination() const; + + // stores the local file name of a just started download + void storeLocalFileName(const OUString& rFileName, sal_Int64 nFileSize); + + // Removes the local file name of a download + void clearLocalFileName(); + + // Stores the bool value for manually paused downloads + void storeDownloadPaused(bool paused); + + // Returns the directory for downloaded files + static OUString getDownloadsDirectory(); + + // Returns a directory accessible for all users + static OUString getAllUsersDirectory(); + + // store and retrieve information about extensions + bool storeExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ); + bool checkExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ); + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + + // XChangesBatch + virtual void SAL_CALL commitChanges( ) override; + virtual sal_Bool SAL_CALL hasPendingChanges( ) override; + virtual css::uno::Sequence< css::util::ElementChange > SAL_CALL getPendingChanges( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +private: + + static OUString getSubVersion( const OUString& rVersion, sal_Int32 *nIndex ); + static bool isVersionGreater( const OUString& rVersion1, const OUString& rVersion2 ); + + const css::uno::Reference< css::container::XNameContainer > m_xContainer; + const css::uno::Reference< css::container::XNameContainer > m_xAvailableUpdates; + const css::uno::Reference< css::container::XNameContainer > m_xIgnoredUpdates; + const ::rtl::Reference< UpdateCheckConfigListener > m_rListener; +}; + +/// @throws css::uno::RuntimeException +template <typename T> +T getValue( const css::uno::Sequence< css::beans::NamedValue >& rNamedValues, const char * pszName ) +{ + for( css::beans::NamedValue const & nv : rNamedValues ) + { + // Unfortunately gcc-3.3 does not like Any.get<T>(); + if( nv.Name.equalsAscii( pszName ) ) + { + T value = T(); + if( ! (nv.Value >>= value) ) + throw css::uno::RuntimeException( + OUString( + cppu_Any_extraction_failure_msg( + &nv.Value, + ::cppu::getTypeFavourUnsigned(&value).getTypeLibType() ), + SAL_NO_ACQUIRE ), + css::uno::Reference< css::uno::XInterface >() ); + + return value; + } + } + + return T(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfiglistener.hxx b/extensions/source/update/check/updatecheckconfiglistener.hxx new file mode 100644 index 000000000..903200f68 --- /dev/null +++ b/extensions/source/update/check/updatecheckconfiglistener.hxx @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <salhelper/simplereferenceobject.hxx> + +/* This interface should be implemented by classes acting + * as controller (as in the MVC pattern). + */ + +struct UpdateCheckConfigListener : public virtual salhelper::SimpleReferenceObject +{ + virtual void autoCheckStatusChanged(bool enabled) = 0; + virtual void autoCheckIntervalChanged() = 0; + +protected: + virtual ~UpdateCheckConfigListener() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckjob.cxx b/extensions/source/update/check/updatecheckjob.cxx new file mode 100644 index 000000000..82d2f7439 --- /dev/null +++ b/extensions/source/update/check/updatecheckjob.cxx @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "updatecheck.hxx" +#include "updatecheckconfig.hxx" +#include "updatehdl.hxx" +#include "updateprotocol.hxx" + +#include <memory> +#include <mutex> +#include <utility> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/task/XJob.hpp> + +namespace beans = com::sun::star::beans ; +namespace frame = com::sun::star::frame ; +namespace lang = com::sun::star::lang ; +namespace task = com::sun::star::task ; +namespace uno = com::sun::star::uno ; + +namespace +{ + +class InitUpdateCheckJobThread : public osl::Thread +{ +public: + InitUpdateCheckJobThread( const uno::Reference< uno::XComponentContext > &xContext, + const uno::Sequence< beans::NamedValue > &xParameters, + bool bShowDialog ); + + virtual void SAL_CALL run() override; + + void setTerminating(); + +private: + osl::Condition m_aCondition; + uno::Reference<uno::XComponentContext> m_xContext; + uno::Sequence<beans::NamedValue> m_xParameters; + bool m_bShowDialog; + bool m_bTerminating; + + std::mutex m_mutex; + rtl::Reference<UpdateCheck> m_controller; +}; + +class UpdateCheckJob : + public ::cppu::WeakImplHelper< task::XJob, lang::XServiceInfo, frame::XTerminateListener > +{ + virtual ~UpdateCheckJob() override; + +public: + + UpdateCheckJob( + css::uno::Reference<css::uno::XComponentContext> const & context, + css::uno::Reference<css::frame::XDesktop2> const & desktop): + m_xContext(context), m_xDesktop(desktop) + {} + + // XJob + virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( lang::EventObject const & evt ) override; + virtual void SAL_CALL notifyTermination( lang::EventObject const & evt ) override; + +private: + uno::Reference<uno::XComponentContext> m_xContext; + uno::Reference< frame::XDesktop2 > m_xDesktop; + std::unique_ptr< InitUpdateCheckJobThread > m_pInitThread; + + void handleExtensionUpdates( const uno::Sequence< beans::NamedValue > &rListProp ); + void terminateAndJoinThread(); +}; + +InitUpdateCheckJobThread::InitUpdateCheckJobThread( + const uno::Reference< uno::XComponentContext > &xContext, + const uno::Sequence< beans::NamedValue > &xParameters, + bool bShowDialog ) : + m_xContext( xContext ), + m_xParameters( xParameters ), + m_bShowDialog( bShowDialog ), + m_bTerminating( false ) +{ + create(); +} + + +void SAL_CALL InitUpdateCheckJobThread::run() +{ + osl_setThreadName("InitUpdateCheckJobThread"); + + if (!m_bShowDialog) { + TimeValue tv = { 25, 0 }; + m_aCondition.wait( &tv ); + if ( m_bTerminating ) + return; + } + + try { + rtl::Reference< UpdateCheck > aController( UpdateCheck::get() ); + // At least for the automatic ("onFirstVisibleTask", i.e., !m_bShowDialog) check, wait for + // m_controller during setTerminating, to prevent m_controller from still having threads + // running during exit (ideally, we would make sure that all threads are joined before exit, + // but the UpdateCheck logic is rather convoluted, so play it safe for now and only address + // the automatic update check that is known to cause issues during `make check`, not the + // manually triggered update check scenario): + if (!m_bShowDialog) { + std::scoped_lock l(m_mutex); + m_controller = aController; + } + aController->initialize( m_xParameters, m_xContext ); + + if ( m_bShowDialog ) + aController->showDialog( true ); + } catch (const uno::Exception &) { + // fdo#64962 - don't bring the app down on some unexpected exception. + TOOLS_WARN_EXCEPTION("extensions.update", "Caught init update exception, thread terminated" ); + { + std::scoped_lock l(m_mutex); + m_controller.clear(); + } + } +} + +void InitUpdateCheckJobThread::setTerminating() { + m_bTerminating = true; + m_aCondition.set(); + rtl::Reference<UpdateCheck> controller; + { + std::scoped_lock l(m_mutex); + std::swap(controller, m_controller); + } + if (controller.is()) { + controller->waitForUpdateCheckFinished(); + } +} + +UpdateCheckJob::~UpdateCheckJob() +{ +} + +uno::Any +UpdateCheckJob::execute(const uno::Sequence<beans::NamedValue>& namedValues) +{ + for ( sal_Int32 n=namedValues.getLength(); n-- > 0; ) + { + if ( namedValues[ n ].Name == "DynamicData" ) + { + uno::Sequence<beans::NamedValue> aListProp; + if ( namedValues[n].Value >>= aListProp ) + { + for ( sal_Int32 i=aListProp.getLength(); i-- > 0; ) + { + if ( aListProp[ i ].Name == "updateList" ) + { + handleExtensionUpdates( aListProp ); + return uno::Any(); + } + } + } + } + } + + uno::Sequence<beans::NamedValue> aConfig = + getValue< uno::Sequence<beans::NamedValue> > (namedValues, "JobConfig"); + + /* Determine the way we got invoked here - + * see Developers Guide Chapter "4.7.2 Jobs" to understand the magic + */ + + uno::Sequence<beans::NamedValue> aEnvironment = + getValue< uno::Sequence<beans::NamedValue> > (namedValues, "Environment"); + + OUString aEventName = getValue< OUString > (aEnvironment, "EventName"); + + m_pInitThread.reset( + new InitUpdateCheckJobThread( + m_xContext, aConfig, + aEventName != "onFirstVisibleTask")); + + return uno::Any(); +} + + +void UpdateCheckJob::handleExtensionUpdates( const uno::Sequence< beans::NamedValue > &rListProp ) +{ + try { + uno::Sequence< uno::Sequence< OUString > > aList = + getValue< uno::Sequence< uno::Sequence< OUString > > > ( rListProp, "updateList" ); + bool bPrepareOnly = getValue< bool > ( rListProp, "prepareOnly" ); + + // we will first store any new found updates and then check, if there are any + // pending updates. + storeExtensionUpdateInfos( m_xContext, aList ); + + if ( bPrepareOnly ) + return; + + bool bHasUpdates = checkForPendingUpdates( m_xContext ); + + rtl::Reference<UpdateCheck> aController( UpdateCheck::get() ); + if ( ! aController.is() ) + return; + + aController->setHasExtensionUpdates( bHasUpdates ); + + if ( ! aController->hasOfficeUpdate() ) + { + if ( bHasUpdates ) + aController->setUIState( UPDATESTATE_EXT_UPD_AVAIL, true ); + else + aController->setUIState( UPDATESTATE_NO_UPDATE_AVAIL, true ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated"); + } +} + + +OUString SAL_CALL +UpdateCheckJob::getImplementationName() +{ + return "vnd.sun.UpdateCheck"; +} + + +uno::Sequence< OUString > SAL_CALL +UpdateCheckJob::getSupportedServiceNames() +{ + return { "com.sun.star.setup.UpdateCheck" }; +} + +sal_Bool SAL_CALL +UpdateCheckJob::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + + +// XEventListener +void SAL_CALL UpdateCheckJob::disposing( lang::EventObject const & rEvt ) +{ + bool shutDown = ( rEvt.Source == m_xDesktop ); + + if ( shutDown && m_xDesktop.is() ) + { + terminateAndJoinThread(); + m_xDesktop->removeTerminateListener( this ); + m_xDesktop.clear(); + } +} + + +// XTerminateListener +void SAL_CALL UpdateCheckJob::queryTermination( lang::EventObject const & ) +{ +} + +void UpdateCheckJob::terminateAndJoinThread() +{ + if (m_pInitThread != nullptr) + { + m_pInitThread->setTerminating(); + m_pInitThread->join(); + m_pInitThread.reset(); + } +} + +void SAL_CALL UpdateCheckJob::notifyTermination( lang::EventObject const & ) +{ + terminateAndJoinThread(); +} + +} // anonymous namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateCheckJob_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + css::uno::Reference<css::frame::XDesktop2> desktop( + css::frame::Desktop::create(context)); + rtl::Reference<UpdateCheckJob> job(new UpdateCheckJob(context, desktop)); + desktop->addTerminateListener(job); + return cppu::acquire(job.get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatehdl.cxx b/extensions/source/update/check/updatehdl.cxx new file mode 100644 index 000000000..24fb96bda --- /dev/null +++ b/extensions/source/update/check/updatehdl.cxx @@ -0,0 +1,1281 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstddef> + +#include "updatehdl.hxx" +#include <helpids.h> + +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/Sequence.h> + +#include <com/sun/star/awt/ActionEvent.hpp> +#include <com/sun/star/awt/PushButtonType.hpp> +#include <com/sun/star/awt/UnoControlDialog.hpp> +#include <com/sun/star/awt/VclWindowPeerAttribute.hpp> +#include <com/sun/star/awt/WindowAttribute.hpp> +#include <com/sun/star/awt/XButton.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/awt/XMessageBox.hpp> +#include <com/sun/star/awt/XAnimation.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/awt/XVclContainer.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XWindow2.hpp> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> + +#include <com/sun/star/container/XNameContainer.hpp> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/InteractionRequestStringResolver.hpp> + +#include <strings.hrc> +#include <unotools/resmgr.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> + +constexpr OUStringLiteral COMMAND_CLOSE = u"close"; + +constexpr OUStringLiteral CTRL_THROBBER = u"throbber"; +constexpr OUStringLiteral CTRL_PROGRESS = u"progress"; + +constexpr OUStringLiteral TEXT_STATUS = u"text_status"; +constexpr OUStringLiteral TEXT_PERCENT = u"text_percent"; +constexpr OUStringLiteral TEXT_DESCRIPTION = u"text_description"; + +constexpr OUStringLiteral FIXED_LINE_MODEL = u"com.sun.star.awt.UnoControlFixedLineModel"; +constexpr OUStringLiteral FIXED_TEXT_MODEL = u"com.sun.star.awt.UnoControlFixedTextModel"; +constexpr OUStringLiteral EDIT_FIELD_MODEL = u"com.sun.star.awt.UnoControlEditModel"; +constexpr OUStringLiteral BUTTON_MODEL = u"com.sun.star.awt.UnoControlButtonModel"; +constexpr OUStringLiteral GROUP_BOX_MODEL = u"com.sun.star.awt.UnoControlGroupBoxModel"; + +using namespace com::sun::star; + + +UpdateHandler::UpdateHandler( const uno::Reference< uno::XComponentContext > & rxContext, + const rtl::Reference< IActionListener > & rxActionListener ) : + mxContext( rxContext ), + mxActionListener( rxActionListener ), + meCurState( UPDATESTATES_COUNT ), + meLastState( UPDATESTATES_COUNT ), + mnPercent( 0 ), + mnLastCtrlState( -1 ), + mbDownloadBtnHasDots( false ), + mbVisible( false ), + mbStringsLoaded( false ), + mbMinimized( false ), + mbListenerAdded(false), + mbShowsMessageBox(false) +{ +} + + +UpdateHandler::~UpdateHandler() +{ + mxContext = nullptr; + mxUpdDlg = nullptr; + mxInteractionHdl = nullptr; + mxActionListener = nullptr; +} + + +void UpdateHandler::enableControls( short nCtrlState ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( nCtrlState == mnLastCtrlState ) + return; + + // the help button should always be the last button in the + // enum list and must never be disabled + for ( int i=0; i<HELP_BUTTON; i++ ) + { + short nCurStateVal = static_cast<short>(nCtrlState >> i); + short nOldStateVal = static_cast<short>(mnLastCtrlState >> i); + if ( ( nCurStateVal & 0x01 ) != ( nOldStateVal & 0x01 ) ) + { + bool bEnableControl = ( ( nCurStateVal & 0x01 ) == 0x01 ); + setControlProperty( msButtonIDs[i], "Enabled", uno::Any( bEnableControl ) ); + } + } + + mnLastCtrlState = nCtrlState; +} + + +void UpdateHandler::setDownloadBtnLabel( bool bAppendDots ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( mbDownloadBtnHasDots != bAppendDots ) + { + OUString aLabel( msDownload ); + + if ( bAppendDots ) + aLabel += "..."; + + setControlProperty( msButtonIDs[DOWNLOAD_BUTTON], "Label", uno::Any( aLabel ) ); + setControlProperty( msButtonIDs[DOWNLOAD_BUTTON], "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DOWNLOAD2 )) ); + + mbDownloadBtnHasDots = bAppendDots; + } +} + + +void UpdateHandler::setState( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + meCurState = eState; + + if ( mxUpdDlg.is() && mbVisible ) + updateState( meCurState ); +} + + +bool UpdateHandler::isVisible() const +{ + if ( !mxUpdDlg.is() ) return false; + + uno::Reference< awt::XWindow2 > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + return xWindow->isVisible(); + else + return false; +} + + +void UpdateHandler::setVisible( bool bVisible ) +{ + osl::MutexGuard aGuard( maMutex ); + + mbVisible = bVisible; + + if ( bVisible ) + { + if ( !mxUpdDlg.is() ) + createDialog(); + + // this should never happen, but if it happens we better return here + if ( !mxUpdDlg.is() ) + return; + + updateState( meCurState ); + + uno::Reference< awt::XWindow > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + xWindow->setVisible( bVisible ); + + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + { + xTopWindow->toFront(); + if ( !mbListenerAdded ) + { + xTopWindow->addTopWindowListener( this ); + mbListenerAdded = true; + } + } + } + else if ( mxUpdDlg.is() ) + { + uno::Reference< awt::XWindow > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + xWindow->setVisible( bVisible ); + } +} + + +void UpdateHandler::setProgress( sal_Int32 nPercent ) +{ + if ( nPercent > 100 ) + nPercent = 100; + else if ( nPercent < 0 ) + nPercent = 0; + + if ( nPercent != mnPercent ) + { + osl::MutexGuard aGuard( maMutex ); + + mnPercent = nPercent; + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( nPercent ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + } +} + + +void UpdateHandler::setErrorMessage( const OUString& rErrorMsg ) +{ + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( rErrorMsg ) ); +} + + +void UpdateHandler::setDownloadFile( std::u16string_view rFilePath ) +{ + std::size_t nLast = rFilePath.rfind( '/' ); + if ( nLast != std::u16string_view::npos ) + { + msDownloadFile = rFilePath.substr( nLast+1 ); + const OUString aDownloadURL(rFilePath.substr( 0, nLast )); + osl::FileBase::getSystemPathFromFileURL( aDownloadURL, msDownloadPath ); + } +} + + +OUString UpdateHandler::getBubbleText( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + OUString sText; + sal_Int32 nIndex = static_cast<sal_Int32>(eState); + + loadStrings(); + + if ( ( UPDATESTATE_UPDATE_AVAIL <= nIndex ) && ( nIndex < UPDATESTATES_COUNT ) ) + sText = substVariables( msBubbleTexts[ nIndex - UPDATESTATE_UPDATE_AVAIL ] ); + + return sText; +} + + +OUString UpdateHandler::getBubbleTitle( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + OUString sText; + sal_Int32 nIndex = static_cast<sal_Int32>(eState); + + loadStrings(); + + if ( ( UPDATESTATE_UPDATE_AVAIL <= nIndex ) && ( nIndex < UPDATESTATES_COUNT ) ) + sText = substVariables( msBubbleTitles[ nIndex - UPDATESTATE_UPDATE_AVAIL] ); + + return sText; +} + + +OUString UpdateHandler::getDefaultInstErrMsg() +{ + osl::MutexGuard aGuard( maMutex ); + + loadStrings(); + + return substVariables( msInstallError ); +} + +// XActionListener + +void SAL_CALL UpdateHandler::disposing( const lang::EventObject& rEvt ) +{ + if ( rEvt.Source == mxUpdDlg ) + mxUpdDlg.clear(); +} + + +void SAL_CALL UpdateHandler::actionPerformed( awt::ActionEvent const & rEvent ) +{ + DialogControls eButton = BUTTON_COUNT; + for ( int i = 0; i < BUTTON_COUNT; i++ ) + { + if ( rEvent.ActionCommand == msButtonIDs[i] ) + { + eButton = static_cast<DialogControls>(i); + break; + } + } + + if ( rEvent.ActionCommand == COMMAND_CLOSE ) + { + if ( ( mnLastCtrlState & ( 1 << CLOSE_BUTTON ) ) == ( 1 << CLOSE_BUTTON ) ) + eButton = CLOSE_BUTTON; + else + eButton = CANCEL_BUTTON; + } + + switch ( eButton ) { + case CANCEL_BUTTON: + { + bool bCancel = true; + + if ( ( meCurState == UPDATESTATE_DOWNLOADING ) || + ( meCurState == UPDATESTATE_DOWNLOAD_PAUSED ) || + ( meCurState == UPDATESTATE_ERROR_DOWNLOADING ) ) + bCancel = showWarning( msCancelMessage ); + + if ( bCancel ) + { + mxActionListener->cancel(); + setVisible( false ); + } + break; + } + case CLOSE_BUTTON: + setVisible( false ); + if ( meCurState == UPDATESTATE_ERROR_CHECKING ) + mxActionListener->closeAfterFailure(); + break; + case DOWNLOAD_BUTTON: + mxActionListener->download(); + break; + case INSTALL_BUTTON: + if ( showWarning( msInstallMessage ) ) + mxActionListener->install(); + break; + case PAUSE_BUTTON: + mxActionListener->pause(); + break; + case RESUME_BUTTON: + mxActionListener->resume(); + break; + case HELP_BUTTON: + break; + default: + OSL_FAIL( "UpdateHandler::actionPerformed: unknown command!" ); + } +} + +// XTopWindowListener + +void SAL_CALL UpdateHandler::windowOpened( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowClosing( const lang::EventObject& e ) +{ + awt::ActionEvent aActionEvt; + aActionEvt.ActionCommand = COMMAND_CLOSE; + aActionEvt.Source = e.Source; + + actionPerformed( aActionEvt ); +} + + +void SAL_CALL UpdateHandler::windowClosed( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowMinimized( const lang::EventObject& ) +{ + mbMinimized = true; +} + + +void SAL_CALL UpdateHandler::windowNormalized( const lang::EventObject& ) +{ + mbMinimized = false; +} + + +void SAL_CALL UpdateHandler::windowActivated( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowDeactivated( const lang::EventObject& ) +{ +} + +// XInteractionHandler + +void SAL_CALL UpdateHandler::handle( uno::Reference< task::XInteractionRequest > const & rRequest) +{ + if ( !mxInteractionHdl.is() ) + { + if( !mxContext.is() ) + throw uno::RuntimeException( "UpdateHandler:: empty component context", *this ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager(mxContext->getServiceManager()); + + if( !xServiceManager.is() ) + throw uno::RuntimeException( "UpdateHandler: unable to obtain service manager from component context", *this ); + + mxInteractionHdl.set( + task::InteractionHandler::createWithParent(mxContext, nullptr), + uno::UNO_QUERY_THROW); + } + uno::Reference< task::XInteractionRequestStringResolver > xStrResolver = + task::InteractionRequestStringResolver::create( mxContext ); + beans::Optional< OUString > aErrorText = xStrResolver->getStringFromInformationalRequest( rRequest ); + if ( aErrorText.IsPresent ) + { + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( aErrorText.Value ) ); + + uno::Sequence< uno::Reference< task::XInteractionContinuation > > xContinuations = rRequest->getContinuations(); + if ( xContinuations.getLength() == 1 ) + { + if ( meCurState == UPDATESTATE_CHECKING ) + setState( UPDATESTATE_ERROR_CHECKING ); + else if ( meCurState == UPDATESTATE_DOWNLOADING ) + setState( UPDATESTATE_ERROR_DOWNLOADING ); + + xContinuations[0]->select(); + } + else + mxInteractionHdl->handle( rRequest ); + } + else + mxInteractionHdl->handle( rRequest ); +} + + +// XTerminateListener + +void SAL_CALL UpdateHandler::queryTermination( const lang::EventObject& ) +{ + if ( mbShowsMessageBox ) + { + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + xTopWindow->toFront(); + + throw frame::TerminationVetoException( + "The office cannot be closed while displaying a warning!", + static_cast<frame::XTerminateListener*>(this)); + } + else + setVisible( false ); +} + + +void SAL_CALL UpdateHandler::notifyTermination( const lang::EventObject& ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( mxUpdDlg.is() ) + { + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + xTopWindow->removeTopWindowListener( this ); + + uno::Reference< lang::XComponent > xComponent( mxUpdDlg, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + mxUpdDlg.clear(); + } +} + + +void UpdateHandler::updateState( UpdateState eState ) +{ + if ( meLastState == eState ) + return; + + OUString sText; + + switch ( eState ) + { + case UPDATESTATE_CHECKING: + showControls( (1<<CANCEL_BUTTON) + (1<<THROBBER_CTRL) ); + enableControls( 1<<CANCEL_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msChecking) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( OUString() ) ); + focusControl( CANCEL_BUTTON ); + break; + case UPDATESTATE_ERROR_CHECKING: + showControls( 0 ); + enableControls( 1 << CLOSE_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msCheckingError) ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_UPDATE_AVAIL: + showControls( 0 ); + enableControls( ( 1 << CLOSE_BUTTON ) + ( 1 << DOWNLOAD_BUTTON ) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msUpdFound) ) ); + + sText = substVariables(msDownloadWarning); + if ( !msDescriptionMsg.isEmpty() ) + sText += "\n\n" + msDescriptionMsg; + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( sText ) ); + + setDownloadBtnLabel( false ); + focusControl( DOWNLOAD_BUTTON ); + break; + case UPDATESTATE_UPDATE_NO_DOWNLOAD: + showControls( 0 ); + enableControls( ( 1 << CLOSE_BUTTON ) + ( 1 << DOWNLOAD_BUTTON ) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msUpdFound) ) ); + + sText = substVariables(msDownloadNotAvail); + if ( !msDescriptionMsg.isEmpty() ) + sText += "\n\n" + msDescriptionMsg; + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( sText ) ); + + setDownloadBtnLabel( true ); + focusControl( DOWNLOAD_BUTTON ); + break; + case UPDATESTATE_NO_UPDATE_AVAIL: + case UPDATESTATE_EXT_UPD_AVAIL: // will only be set, when there are no office updates avail + showControls( 0 ); + enableControls( 1 << CLOSE_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msNoUpdFound) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( OUString() ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOADING: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloading) ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadWarning) ) ); + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( mnPercent ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOAD_PAUSED: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) + (1<<RESUME_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloadPause) ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadWarning) ) ); + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( mnPercent ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_ERROR_DOWNLOADING: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloadError) ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOAD_AVAIL: + showControls( 0 ); + enableControls( (1<<CLOSE_BUTTON) + (1<<INSTALL_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msReady2Install) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadDescr) ) ); + focusControl( INSTALL_BUTTON ); + break; + case UPDATESTATE_AUTO_START: + case UPDATESTATES_COUNT: + //do nothing, only count! + break; + } + + meLastState = eState; +} + +OUString UpdateHandler::loadString(const std::locale& rLocale, + TranslateId pResourceId) +{ + return Translate::get(pResourceId, rLocale); +} + +OUString UpdateHandler::substVariables( const OUString &rSource ) const +{ + return rSource + .replaceAll( "%NEXTVERSION", msNextVersion ) + .replaceAll( "%DOWNLOAD_PATH", msDownloadPath ) + .replaceAll( "%FILE_NAME", msDownloadFile ) + .replaceAll( "%PERCENT", OUString::number( mnPercent ) ); +} + +void UpdateHandler::loadStrings() +{ + if ( mbStringsLoaded ) + return; + else + mbStringsLoaded = true; + + std::locale loc = Translate::Create("pcr"); + + msChecking = loadString( loc, RID_UPDATE_STR_CHECKING ); + msCheckingError = loadString( loc, RID_UPDATE_STR_CHECKING_ERR ); + msNoUpdFound = loadString( loc, RID_UPDATE_STR_NO_UPD_FOUND ); + + msUpdFound = loadString( loc, RID_UPDATE_STR_UPD_FOUND ); + setFullVersion( msUpdFound ); + + msDlgTitle = loadString( loc, RID_UPDATE_STR_DLG_TITLE ); + msDownloadPause = loadString( loc, RID_UPDATE_STR_DOWNLOAD_PAUSE ); + msDownloadError = loadString( loc, RID_UPDATE_STR_DOWNLOAD_ERR ); + msDownloadWarning = loadString( loc, RID_UPDATE_STR_DOWNLOAD_WARN ); + msDownloadDescr = loadString( loc, RID_UPDATE_STR_DOWNLOAD_DESCR ); + msDownloadNotAvail = loadString( loc, RID_UPDATE_STR_DOWNLOAD_UNAVAIL ); + msDownloading = loadString( loc, RID_UPDATE_STR_DOWNLOADING ); + msReady2Install = loadString( loc, RID_UPDATE_STR_READY_INSTALL ); + msCancelMessage = loadString( loc, RID_UPDATE_STR_CANCEL_DOWNLOAD ); + msInstallMessage = loadString( loc, RID_UPDATE_STR_BEGIN_INSTALL ); + msInstallError = loadString( loc, RID_UPDATE_STR_INSTALL_ERROR ); + msOverwriteWarning = loadString( loc, RID_UPDATE_STR_OVERWRITE_WARNING ); + msPercent = loadString( loc, RID_UPDATE_STR_PERCENT ); + msReloadWarning = loadString( loc, RID_UPDATE_STR_RELOAD_WARNING ); + msReloadReload = loadString( loc, RID_UPDATE_STR_RELOAD_RELOAD ); + msReloadContinue = loadString( loc, RID_UPDATE_STR_RELOAD_CONTINUE ); + + msStatusFL = loadString( loc, RID_UPDATE_FT_STATUS ); + msDescription = loadString( loc, RID_UPDATE_FT_DESCRIPTION ); + + msClose = loadString( loc, RID_UPDATE_BTN_CLOSE ); + msDownload = loadString( loc, RID_UPDATE_BTN_DOWNLOAD ); + msInstall = loadString( loc, RID_UPDATE_BTN_INSTALL ); + msPauseBtn = loadString( loc, RID_UPDATE_BTN_PAUSE ); + msResumeBtn = loadString( loc, RID_UPDATE_BTN_RESUME ); + msCancelBtn = loadString( loc, RID_UPDATE_BTN_CANCEL ); + + std::pair<TranslateId, TranslateId> RID_UPDATE_BUBBLE[] = + { + { RID_UPDATE_BUBBLE_UPDATE_AVAIL, RID_UPDATE_BUBBLE_T_UPDATE_AVAIL }, + { RID_UPDATE_BUBBLE_UPDATE_NO_DOWN, RID_UPDATE_BUBBLE_T_UPDATE_NO_DOWN }, + { RID_UPDATE_BUBBLE_AUTO_START, RID_UPDATE_BUBBLE_T_AUTO_START }, + { RID_UPDATE_BUBBLE_DOWNLOADING, RID_UPDATE_BUBBLE_T_DOWNLOADING }, + { RID_UPDATE_BUBBLE_DOWNLOAD_PAUSED, RID_UPDATE_BUBBLE_T_DOWNLOAD_PAUSED }, + { RID_UPDATE_BUBBLE_ERROR_DOWNLOADING, RID_UPDATE_BUBBLE_T_ERROR_DOWNLOADING }, + { RID_UPDATE_BUBBLE_DOWNLOAD_AVAIL, RID_UPDATE_BUBBLE_T_DOWNLOAD_AVAIL }, + { RID_UPDATE_BUBBLE_EXT_UPD_AVAIL, RID_UPDATE_BUBBLE_T_EXT_UPD_AVAIL } + }; + + static_assert(SAL_N_ELEMENTS(RID_UPDATE_BUBBLE) == UPDATESTATES_COUNT - UPDATESTATE_UPDATE_AVAIL, "mismatch"); + + // all update states before UPDATESTATE_UPDATE_AVAIL don't have a bubble + // so we can ignore them + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UPDATE_BUBBLE); ++i) + { + msBubbleTexts[i] = loadString(loc, RID_UPDATE_BUBBLE[i].first); + msBubbleTitles[i] = loadString(loc, RID_UPDATE_BUBBLE[i].second); + } + + for ( int i=0; i < BUTTON_COUNT; i++ ) + { + msButtonIDs[ i ] = "BUTTON_" + OUString::number( i ); + } +} + + +void UpdateHandler::startThrobber( bool bStart ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + uno::Reference< awt::XAnimation > xThrobber( xContainer->getControl( CTRL_THROBBER ), uno::UNO_QUERY ); + + if ( xThrobber.is() ) + { + if ( bStart ) + xThrobber->startAnimation(); + else + xThrobber->stopAnimation(); + } + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( CTRL_THROBBER ), uno::UNO_QUERY ); + if (xWindow.is() ) + xWindow->setVisible( bStart ); +} + + +void UpdateHandler::setControlProperty( const OUString &rCtrlName, + const OUString &rPropName, + const uno::Any &rPropValue ) +{ + if ( !mxUpdDlg.is() ) return; + + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + uno::Reference< awt::XControl > xControl( xContainer->getControl( rCtrlName ), uno::UNO_SET_THROW ); + uno::Reference< awt::XControlModel > xControlModel( xControl->getModel(), uno::UNO_SET_THROW ); + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY_THROW ); + + try { + xPropSet->setPropertyValue( rPropName, rPropValue ); + } + catch( const beans::UnknownPropertyException& ) + { + TOOLS_WARN_EXCEPTION( "extensions.update", "UpdateHandler::setControlProperty" ); + } +} + + +void UpdateHandler::showControl( const OUString &rCtrlName, bool bShow ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + + if ( !xContainer.is() ) + { + OSL_FAIL( "UpdateHandler::showControl: could not get control container!" ); + return; + } + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( rCtrlName ), uno::UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setVisible( bShow ); +} + + +void UpdateHandler::focusControl( DialogControls eID ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + + if ( !xContainer.is() ) + { + OSL_FAIL( "UpdateHandler::focusControl: could not get control container!" ); + return; + } + + OSL_ENSURE( (eID < BUTTON_COUNT), "UpdateHandler::focusControl: id too big!" ); + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( msButtonIDs[static_cast<short>(eID)] ), uno::UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setFocus(); +} + + +void UpdateHandler::insertControlModel( uno::Reference< awt::XControlModel > const & rxDialogModel, + OUString const & rServiceName, + OUString const & rControlName, + awt::Rectangle const & rPosSize, + uno::Sequence< beans::NamedValue > const & rProps ) +{ + uno::Reference< lang::XMultiServiceFactory > xFactory (rxDialogModel, uno::UNO_QUERY_THROW); + uno::Reference< awt::XControlModel > xModel (xFactory->createInstance (rServiceName), uno::UNO_QUERY_THROW); + uno::Reference< beans::XPropertySet > xPropSet (xModel, uno::UNO_QUERY_THROW); + + for (beans::NamedValue const & prop : rProps) + { + xPropSet->setPropertyValue (prop.Name, prop.Value); + } + + // @see awt/UnoControlDialogElement.idl + xPropSet->setPropertyValue( "Name", uno::Any (rControlName) ); + xPropSet->setPropertyValue( "PositionX", uno::Any (rPosSize.X) ); + xPropSet->setPropertyValue( "PositionY", uno::Any (rPosSize.Y) ); + xPropSet->setPropertyValue( "Height", uno::Any (rPosSize.Height) ); + xPropSet->setPropertyValue( "Width", uno::Any (rPosSize.Width) ); + + // insert by Name into DialogModel container + uno::Reference< container::XNameContainer > xContainer (rxDialogModel, uno::UNO_QUERY_THROW); + xContainer->insertByName( rControlName, uno::Any (uno::Reference< uno::XInterface >(xModel, uno::UNO_QUERY))); +} + + +void UpdateHandler::setFullVersion( OUString& rString ) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider( + css::configuration::theDefaultProvider::get( mxContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Setup/Product"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< uno::XInterface > xConfigAccess = xConfigurationProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", + aArgumentList ); + + uno::Reference< container::XNameAccess > xNameAccess( xConfigAccess, uno::UNO_QUERY_THROW ); + + OUString aProductVersion; + xNameAccess->getByName("ooSetupVersion") >>= aProductVersion; + OUString aProductFullVersion; + xNameAccess->getByName("ooSetupVersionAboutBox") >>= aProductFullVersion; + rString = rString.replaceFirst( aProductVersion, aProductFullVersion ); +} + + +bool UpdateHandler::showWarning( const OUString &rWarningText ) const +{ + bool bRet = false; + + uno::Reference< awt::XControl > xControl( mxUpdDlg, uno::UNO_QUERY ); + if ( !xControl.is() ) return bRet; + + uno::Reference< awt::XWindowPeer > xPeer = xControl->getPeer(); + if ( !xPeer.is() ) return bRet; + + uno::Reference< awt::XToolkit > xToolkit = xPeer->getToolkit(); + if ( !xToolkit.is() ) return bRet; + + awt::WindowDescriptor aDescriptor; + + sal_Int32 nWindowAttributes = awt::WindowAttribute::BORDER | awt::WindowAttribute::MOVEABLE | awt::WindowAttribute::CLOSEABLE; + nWindowAttributes |= awt::VclWindowPeerAttribute::YES_NO; + nWindowAttributes |= awt::VclWindowPeerAttribute::DEF_NO; + + aDescriptor.Type = awt::WindowClass_MODALTOP; + aDescriptor.WindowServiceName = "warningbox"; + aDescriptor.ParentIndex = -1; + aDescriptor.Parent = xPeer; + aDescriptor.Bounds = awt::Rectangle( 10, 10, 250, 150 ); + aDescriptor.WindowAttributes = nWindowAttributes; + + uno::Reference< awt::XMessageBox > xMsgBox( xToolkit->createWindow( aDescriptor ), uno::UNO_QUERY ); + if ( xMsgBox.is() ) + { + mbShowsMessageBox = true; + sal_Int16 nRet; + // xMsgBox->setCaptionText( msCancelTitle ); + xMsgBox->setMessageText( rWarningText ); + nRet = xMsgBox->execute(); + if ( nRet == 2 ) // RET_YES == 2 + bRet = true; + mbShowsMessageBox = false; + } + + uno::Reference< lang::XComponent > xComponent( xMsgBox, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + return bRet; +} + + +bool UpdateHandler::showWarning( const OUString &rWarningText, + const OUString &rBtnText_1, + const OUString &rBtnText_2 ) const +{ + bool bRet = false; + + uno::Reference< awt::XControl > xControl( mxUpdDlg, uno::UNO_QUERY ); + if ( !xControl.is() ) return bRet; + + uno::Reference< awt::XWindowPeer > xPeer = xControl->getPeer(); + if ( !xPeer.is() ) return bRet; + + uno::Reference< awt::XToolkit > xToolkit = xPeer->getToolkit(); + if ( !xToolkit.is() ) return bRet; + + awt::WindowDescriptor aDescriptor; + + sal_Int32 nWindowAttributes = awt::WindowAttribute::BORDER | awt::WindowAttribute::MOVEABLE | awt::WindowAttribute::CLOSEABLE; + nWindowAttributes |= awt::VclWindowPeerAttribute::YES_NO; + nWindowAttributes |= awt::VclWindowPeerAttribute::DEF_NO; + + aDescriptor.Type = awt::WindowClass_MODALTOP; + aDescriptor.WindowServiceName = "warningbox"; + aDescriptor.ParentIndex = -1; + aDescriptor.Parent = xPeer; + aDescriptor.Bounds = awt::Rectangle( 10, 10, 250, 150 ); + aDescriptor.WindowAttributes = nWindowAttributes; + + uno::Reference< awt::XMessageBox > xMsgBox( xToolkit->createWindow( aDescriptor ), uno::UNO_QUERY ); + if ( xMsgBox.is() ) + { + uno::Reference< awt::XVclContainer > xMsgBoxCtrls( xMsgBox, uno::UNO_QUERY ); + if ( xMsgBoxCtrls.is() ) + { + uno::Sequence< uno::Reference< awt::XWindow > > xChildren = xMsgBoxCtrls->getWindows(); + + for ( uno::Reference< awt::XWindow > const & child : std::as_const(xChildren) ) + { + uno::Reference< awt::XVclWindowPeer > xMsgBoxCtrl( child, uno::UNO_QUERY ); + if ( xMsgBoxCtrl.is() ) + { + bool bIsDefault = true; + uno::Any aValue = xMsgBoxCtrl->getProperty( "DefaultButton" ); + aValue >>= bIsDefault; + if ( bIsDefault ) + xMsgBoxCtrl->setProperty( "Text", uno::Any( rBtnText_1 ) ); + else + xMsgBoxCtrl->setProperty( "Text", uno::Any( rBtnText_2 ) ); + } + } + } + + sal_Int16 nRet; + // xMsgBox->setCaptionText( msCancelTitle ); + mbShowsMessageBox = true; + xMsgBox->setMessageText( rWarningText ); + nRet = xMsgBox->execute(); + if ( nRet == 2 ) // RET_YES == 2 + bRet = true; + + mbShowsMessageBox = false; + } + + uno::Reference< lang::XComponent > xComponent( xMsgBox, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + return bRet; +} + + +bool UpdateHandler::showOverwriteWarning( std::u16string_view rFileName ) const +{ + return showWarning( + (msReloadWarning + .replaceAll( "%FILENAME", rFileName ) + .replaceAll( "%DOWNLOAD_PATH", msDownloadPath )), + msReloadContinue, msReloadReload ); +} + + +bool UpdateHandler::showOverwriteWarning() const +{ + return showWarning( msOverwriteWarning ); +} + + +#define BUTTON_HEIGHT 14 +#define BUTTON_WIDTH 50 +#define BUTTON_X_OFFSET 7 +#define BUTTON_Y_OFFSET 3 +#define LABEL_HEIGHT 10 + +#define DIALOG_WIDTH 300 +#define DIALOG_BORDER 5 +#define INNER_BORDER 3 +#define TEXT_OFFSET 1 +#define BOX_HEIGHT1 ( LABEL_HEIGHT + 3*BUTTON_HEIGHT + 2*BUTTON_Y_OFFSET + 2*INNER_BORDER ) +#define BOX_HEIGHT2 50 +#define EDIT_WIDTH ( DIALOG_WIDTH - 2 * DIALOG_BORDER ) +#define BOX1_BTN_X ( DIALOG_BORDER + EDIT_WIDTH - BUTTON_WIDTH - INNER_BORDER ) +#define BOX1_BTN_Y ( DIALOG_BORDER + LABEL_HEIGHT + INNER_BORDER) +#define THROBBER_WIDTH 16 +#define THROBBER_HEIGHT 16 +#define THROBBER_X_POS ( DIALOG_BORDER + 8 ) +#define THROBBER_Y_POS ( DIALOG_BORDER + 23 ) +#define BUTTON_BAR_HEIGHT 24 +#define LABEL_OFFSET ( LABEL_HEIGHT + 4 ) +#define DIALOG_HEIGHT ( BOX_HEIGHT1 + BOX_HEIGHT2 + LABEL_OFFSET + BUTTON_BAR_HEIGHT + 3 * DIALOG_BORDER ) +#define LABEL_Y_POS ( 2 * DIALOG_BORDER + BOX_HEIGHT1 ) +#define EDIT2_Y_POS ( LABEL_Y_POS + LABEL_HEIGHT ) +#define BUTTON_BAR_Y_POS ( EDIT2_Y_POS + DIALOG_BORDER + BOX_HEIGHT2 ) +#define BUTTON_Y_POS ( BUTTON_BAR_Y_POS + 8 ) +#define CLOSE_BTN_X ( DIALOG_WIDTH - DIALOG_BORDER - BUTTON_WIDTH ) +#define INSTALL_BTN_X ( CLOSE_BTN_X - 2 * BUTTON_X_OFFSET - BUTTON_WIDTH ) +#define DOWNLOAD_BTN_X ( INSTALL_BTN_X - BUTTON_X_OFFSET - BUTTON_WIDTH ) +#define PROGRESS_WIDTH 80 +#define PROGRESS_HEIGHT 10 +#define PROGRESS_X_POS ( DIALOG_BORDER + 8 ) +#define PROGRESS_Y_POS ( DIALOG_BORDER + 2*LABEL_OFFSET ) + + +void UpdateHandler::showControls( short nControls ) +{ + // The buttons from CANCEL_BUTTON to RESUME_BUTTON will be shown or + // hidden on demand + short nShiftMe; + for ( int i = 0; i <= int(RESUME_BUTTON); i++ ) + { + nShiftMe = static_cast<short>(nControls >> i); + showControl( msButtonIDs[i], static_cast<bool>(nShiftMe & 0x01) ); + } + + nShiftMe = static_cast<short>(nControls >> THROBBER_CTRL); + startThrobber( static_cast<bool>(nShiftMe & 0x01) ); + + nShiftMe = static_cast<short>(nControls >> PROGRESS_CTRL); + showControl( CTRL_PROGRESS, static_cast<bool>(nShiftMe & 0x01) ); + showControl( TEXT_PERCENT, static_cast<bool>(nShiftMe & 0x01) ); + + // Status text needs to be smaller, when there are buttons at the right side of the dialog + if ( ( nControls & ( (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ) ) != 0 ) + setControlProperty( TEXT_STATUS, "Width", uno::Any( sal_Int32(EDIT_WIDTH - BUTTON_WIDTH - 2*INNER_BORDER - TEXT_OFFSET ) ) ); + else + setControlProperty( TEXT_STATUS, "Width", uno::Any( sal_Int32(EDIT_WIDTH - 2*TEXT_OFFSET ) ) ); + + // Status text needs to be taller, when we show the progress bar + if ( ( nControls & ( 1<<PROGRESS_CTRL ) ) != 0 ) + setControlProperty( TEXT_STATUS, "Height", uno::Any( sal_Int32(LABEL_HEIGHT) ) ); + else + setControlProperty( TEXT_STATUS, "Height", uno::Any( sal_Int32(BOX_HEIGHT1 - 4*TEXT_OFFSET - LABEL_HEIGHT ) ) ); +} + + +void UpdateHandler::createDialog() +{ + if ( !mxContext.is() ) + { + OSL_ASSERT( false ); + return; + } + + if( mxContext.is() ) + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( mxContext ); + xDesktop->addTerminateListener( this ); + } + + loadStrings(); + + uno::Reference< lang::XMultiComponentFactory > xFactory( mxContext->getServiceManager(), uno::UNO_SET_THROW ); + uno::Reference< awt::XControlModel > xControlModel( xFactory->createInstanceWithContext( + "com.sun.star.awt.UnoControlDialogModel", + mxContext), uno::UNO_QUERY_THROW ); + { + // @see awt/UnoControlDialogModel.idl + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY_THROW ); + + xPropSet->setPropertyValue( "Title", uno::Any( msDlgTitle ) ); + xPropSet->setPropertyValue( "Closeable", uno::Any( true ) ); + xPropSet->setPropertyValue( "Enabled", uno::Any( true ) ); + xPropSet->setPropertyValue( "Moveable", uno::Any( true ) ); + xPropSet->setPropertyValue( "Sizeable", uno::Any( true ) ); + xPropSet->setPropertyValue( "DesktopAsParent", uno::Any( true ) ); + xPropSet->setPropertyValue( "PositionX", uno::Any(sal_Int32( 100 )) ); + xPropSet->setPropertyValue( "PositionY", uno::Any(sal_Int32( 100 )) ); + xPropSet->setPropertyValue( "Width", uno::Any(sal_Int32( DIALOG_WIDTH )) ); + xPropSet->setPropertyValue( "Height", uno::Any(sal_Int32( DIALOG_HEIGHT )) ); + xPropSet->setPropertyValue( "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DLG )) ); + } + { // Label (fixed text) <status> + uno::Sequence< beans::NamedValue > aProps { { "Label", uno::Any( msStatusFL ) } }; + + insertControlModel( xControlModel, FIXED_TEXT_MODEL, "fixedLineStatus", + awt::Rectangle( DIALOG_BORDER+1, DIALOG_BORDER, EDIT_WIDTH-2, LABEL_HEIGHT ), + aProps ); + } + { // box around <status> text + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, GROUP_BOX_MODEL, "StatusBox", + awt::Rectangle( DIALOG_BORDER, DIALOG_BORDER + LABEL_HEIGHT, EDIT_WIDTH, BOX_HEIGHT1 - LABEL_HEIGHT ), + aProps ); + } + { // Text (multiline edit) <status> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( substVariables(msChecking) ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "MultiLine", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + { "AutoVScroll", uno::Any( true ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_STATUS )) } + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_STATUS, + awt::Rectangle( DIALOG_BORDER + TEXT_OFFSET, + DIALOG_BORDER + LABEL_HEIGHT + TEXT_OFFSET, + EDIT_WIDTH - 2*TEXT_OFFSET, + BOX_HEIGHT1 - 4*TEXT_OFFSET - LABEL_HEIGHT ), + aProps ); + } + { // Text (edit) <percent> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( substVariables(msPercent) ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_PERCENT, + awt::Rectangle( PROGRESS_X_POS + PROGRESS_WIDTH + DIALOG_BORDER, + PROGRESS_Y_POS, + EDIT_WIDTH - PROGRESS_WIDTH - BUTTON_WIDTH - 2*DIALOG_BORDER, + LABEL_HEIGHT ), + aProps ); + } + { // pause button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msPauseBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_PAUSE )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[PAUSE_BUTTON], + awt::Rectangle( BOX1_BTN_X, BOX1_BTN_Y, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // resume button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msResumeBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_RESUME )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[RESUME_BUTTON], + awt::Rectangle( BOX1_BTN_X, + BOX1_BTN_Y + BUTTON_Y_OFFSET + BUTTON_HEIGHT, + BUTTON_WIDTH, + BUTTON_HEIGHT ), + aProps ); + } + { // abort button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msCancelBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_CANCEL )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[CANCEL_BUTTON], + awt::Rectangle( BOX1_BTN_X, + BOX1_BTN_Y + (2*(BUTTON_HEIGHT+BUTTON_Y_OFFSET)), + BUTTON_WIDTH, + BUTTON_HEIGHT ), + aProps ); + } + { // Label (FixedText) <description> + uno::Sequence< beans::NamedValue > aProps { { "Label", uno::Any( msDescription ) } }; + + insertControlModel( xControlModel, FIXED_TEXT_MODEL, "fixedTextDescription", + awt::Rectangle( DIALOG_BORDER+1, LABEL_Y_POS, EDIT_WIDTH-2, LABEL_HEIGHT ), + aProps ); + } + { // box around <description> text + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, GROUP_BOX_MODEL, "DescriptionBox", + awt::Rectangle( DIALOG_BORDER, EDIT2_Y_POS, EDIT_WIDTH, BOX_HEIGHT2 ), + aProps ); + } + { // Text (MultiLineEdit) <description> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( OUString() ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "MultiLine", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + { "AutoVScroll", uno::Any( true ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DESCRIPTION )) } + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_DESCRIPTION, + awt::Rectangle( DIALOG_BORDER + TEXT_OFFSET, + EDIT2_Y_POS + 2*TEXT_OFFSET, + EDIT_WIDTH - 3*TEXT_OFFSET, + BOX_HEIGHT2 - 3*TEXT_OFFSET ), + aProps ); + } + { // @see awt/UnoControlFixedLineModel.idl + uno::Sequence< beans::NamedValue > aProps { { "Orientation", uno::Any( sal_Int32( 0 ) ) } }; + + insertControlModel( xControlModel, FIXED_LINE_MODEL, "fixedLine", + awt::Rectangle( 0, BUTTON_BAR_Y_POS, DIALOG_WIDTH, 5 ), + aProps ); + } + { // close button // @see awt/UnoControlButtonModel.idl + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + // [property] short PushButtonType + // with own "ButtonActionListener" + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + // with default ActionListener => endDialog(). + // setProperty( aProps, 2, "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_CANCEL) ) ); + // [property] string Label // only if PushButtonType_STANDARD + { "Label", uno::Any( msClose ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_CLOSE )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[ CLOSE_BUTTON ], + awt::Rectangle( CLOSE_BTN_X, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // install button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msInstall ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_INSTALL )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[INSTALL_BUTTON], + awt::Rectangle( INSTALL_BTN_X, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // download button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msDownload ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DOWNLOAD )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[DOWNLOAD_BUTTON], + awt::Rectangle( DOWNLOAD_BTN_X, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // help button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_HELP) ) } + }; + + insertControlModel( xControlModel, BUTTON_MODEL, msButtonIDs[HELP_BUTTON], + awt::Rectangle( DIALOG_BORDER, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // @see awt/UnoControlThrobberModel.idl + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, "com.sun.star.awt.SpinningProgressControlModel", CTRL_THROBBER, + awt::Rectangle( THROBBER_X_POS, THROBBER_Y_POS, THROBBER_WIDTH, THROBBER_HEIGHT), + aProps ); + } + { // @see awt/UnoControlProgressBarModel.idl + uno::Sequence< beans::NamedValue > aProps + { + { "Enabled", uno::Any( true ) }, + { "ProgressValue", uno::Any( sal_Int32( 0 ) ) }, + { "ProgressValueMax", uno::Any( sal_Int32( 100 ) ) }, + { "ProgressValueMin", uno::Any( sal_Int32( 0 ) ) }, + }; + insertControlModel( xControlModel, "com.sun.star.awt.UnoControlProgressBarModel", CTRL_PROGRESS, + awt::Rectangle( PROGRESS_X_POS, PROGRESS_Y_POS, PROGRESS_WIDTH, PROGRESS_HEIGHT ), + aProps); + } + + uno::Reference< awt::XUnoControlDialog > xControl = awt::UnoControlDialog::create( mxContext ); + xControl->setModel( xControlModel ); + + if ( !mbVisible ) + { + xControl->setVisible( false ); + } + + xControl->createPeer( nullptr, nullptr ); + { + for ( int i = 0; i < HELP_BUTTON; i++ ) + { + uno::Reference< awt::XButton > xButton ( xControl->getControl( msButtonIDs[i] ), uno::UNO_QUERY); + if (xButton.is()) + { + xButton->setActionCommand( msButtonIDs[i] ); + xButton->addActionListener( this ); + } + } + } + + mxUpdDlg.set( xControl, uno::UNO_QUERY_THROW ); + mnLastCtrlState = -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatehdl.hxx b/extensions/source/update/check/updatehdl.hxx new file mode 100644 index 000000000..297cf730c --- /dev/null +++ b/extensions/source/update/check/updatehdl.hxx @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <osl/mutex.hxx> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/XActionListener.hpp> +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/awt/XDialog.hpp> +#include <com/sun/star/awt/XTopWindowListener.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <cppuhelper/implbase.hxx> +#include <unotools/resmgr.hxx> +#include <rtl/ref.hxx> + +#include "actionlistener.hxx" + +enum DialogControls +{ + CANCEL_BUTTON = 0, + PAUSE_BUTTON, + RESUME_BUTTON, + INSTALL_BUTTON, + DOWNLOAD_BUTTON, + CLOSE_BUTTON, + HELP_BUTTON, + BUTTON_COUNT, + THROBBER_CTRL, + PROGRESS_CTRL +}; + +enum UpdateState { + UPDATESTATE_CHECKING = 0, + UPDATESTATE_ERROR_CHECKING, + UPDATESTATE_NO_UPDATE_AVAIL, + UPDATESTATE_UPDATE_AVAIL, + UPDATESTATE_UPDATE_NO_DOWNLOAD, + UPDATESTATE_AUTO_START, + UPDATESTATE_DOWNLOADING, + UPDATESTATE_DOWNLOAD_PAUSED, + UPDATESTATE_ERROR_DOWNLOADING, + UPDATESTATE_DOWNLOAD_AVAIL, + UPDATESTATE_EXT_UPD_AVAIL, + UPDATESTATES_COUNT +}; + +class UpdateHandler : public cppu::WeakImplHelper< css::awt::XActionListener, + css::awt::XTopWindowListener, + css::task::XInteractionHandler, + css::frame::XTerminateListener > +{ +private: + css::uno::Reference< css::uno::XComponentContext > mxContext; + css::uno::Reference< css::awt::XDialog > mxUpdDlg; + css::uno::Reference< css::task::XInteractionHandler > mxInteractionHdl; + rtl::Reference< IActionListener > mxActionListener; + + UpdateState meCurState; + UpdateState meLastState; + sal_Int32 mnPercent; + short mnLastCtrlState; + bool mbDownloadBtnHasDots; + bool mbVisible; + bool mbStringsLoaded; + bool mbMinimized; + bool mbListenerAdded; + mutable bool mbShowsMessageBox; + + osl::Mutex maMutex; + + OUString msNextVersion; + OUString msDownloadPath; + OUString msDownloadFile; + OUString msDescriptionMsg; + OUString msChecking; // RID_UPDATE_STR_CHECKING + OUString msCheckingError; // RID_UPDATE_STR_CHECKING_ERR + OUString msNoUpdFound; // RID_UPDATE_STR_NO_UPD_FOUND + OUString msUpdFound; // RID_UPDATE_STR_UPD_FOUND + OUString msDlgTitle; // RID_UPDATE_STR_DLG_TITLE + OUString msDownloadPause; // RID_UPDATE_STR_DOWNLOAD_PAUSE + OUString msDownloadError; // RID_UPDATE_STR_DOWNLOAD_ERR + OUString msDownloadWarning; // RID_UPDATE_STR_DOWNLOAD_WARN + OUString msDownloadDescr; // RID_UPDATE_STR_DOWNLOAD_WARN + OUString msDownloadNotAvail; // RID_UPDATE_STR_DOWNLOAD_UNAVAIL + OUString msDownloading; // RID_UPDATE_STR_DOWNLOADING + OUString msReady2Install; // RID_UPDATE_STR_READY_INSTALL + OUString msCancelMessage; // RID_UPDATE_STR_CANCEL_DOWNLOAD + OUString msInstallMessage; // RID_UPDATE_STR_BEGIN_INSTALL + OUString msInstallError; // RID_UPDATE_STR_INSTALL_ERROR + OUString msOverwriteWarning; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msPercent; // RID_UPDATE_STR_PERCENT + OUString msReloadWarning; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msReloadReload; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msReloadContinue; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msStatusFL; // RID_UPDATE_FT_STATUS + OUString msDescription; // RID_UPDATE_FT_DESCRIPTION + OUString msClose; // RID_UPDATE_BTN_CLOSE + OUString msDownload; // RID_UPDATE_BTN_DOWNLOAD + OUString msInstall; // RID_UPDATE_BTN_INSTALL + OUString msPauseBtn; // RID_UPDATE_BTN_PAUSE + OUString msResumeBtn; // RID_UPDATE_BTN_RESUME + OUString msCancelBtn; // RID_UPDATE_BTN_CANCEL + OUString msButtonIDs[ BUTTON_COUNT ]; + OUString msBubbleTexts[ UPDATESTATES_COUNT ]; + OUString msBubbleTitles[ UPDATESTATES_COUNT ]; + + void createDialog(); + void updateState( UpdateState eNewState ); + void startThrobber( bool bStart = true ); + void setControlProperty( const OUString &rCtrlName, + const OUString &rPropName, + const css::uno::Any &rPropValue ); + void showControl( const OUString &rCtrlName, bool bShow = true ); + void showControls( short nControls ); + void focusControl( DialogControls eID ); + void enableControls( short nCtrlState ); + void setDownloadBtnLabel( bool bAppendDots ); + void loadStrings(); + static OUString loadString(const std::locale& rLocale, + TranslateId pResourceId); + OUString substVariables( const OUString &rSource ) const; + static void insertControlModel( css::uno::Reference< css::awt::XControlModel > const & rxDialogModel, + OUString const & rServiceName, + OUString const & rControlName, + css::awt::Rectangle const & rPosSize, + css::uno::Sequence< css::beans::NamedValue > const & rProps ); + + void setFullVersion( OUString& rString ); + +public: + UpdateHandler( const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const rtl::Reference< IActionListener > & rxActionListener ); + virtual ~UpdateHandler() override; + UpdateHandler(const UpdateHandler&) = delete; + UpdateHandler& operator=(const UpdateHandler&) = delete; + + bool isVisible() const; + bool isMinimized() const { return mbMinimized; } + void setVisible( bool bVisible = true ); + void setProgress( sal_Int32 nPercent ); + void setNextVersion( const OUString &rNextVersion ) { msNextVersion = rNextVersion; } + void setDownloadPath( const OUString &rPath ) { msDownloadPath = rPath; } + void setDownloadFile( std::u16string_view rPath ); + void setErrorMessage( const OUString &rErrorMsg ); + void setDescription( const OUString &rDescription ){ msDescriptionMsg = rDescription; } + + void setState( UpdateState eState ); + OUString getBubbleText( UpdateState eState ); + OUString getBubbleTitle( UpdateState eState ); + OUString getDefaultInstErrMsg(); + bool showWarning( const OUString &rWarning ) const; + bool showWarning( const OUString &rWarning, const OUString& rBtnText_1, const OUString& rBtnText_2 ) const; + bool showOverwriteWarning( std::u16string_view rFileName ) const; + bool showOverwriteWarning() const; + + // Allows runtime exceptions to be thrown by const methods + operator css::uno::Reference< css::uno::XInterface > () const + { return const_cast< cppu::OWeakObject * > (static_cast< cppu::OWeakObject const * > (this)); }; + + // XActionListener + virtual void SAL_CALL disposing( const css::lang::EventObject &rObj ) override; + virtual void SAL_CALL actionPerformed( css::awt::ActionEvent const & rEvent) override; + + // XTopWindowListener + virtual void SAL_CALL windowOpened( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowClosing( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowClosed( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowMinimized( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowNormalized( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowActivated( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowDeactivated( const css::lang::EventObject& e ) override; + + // XInteractionHandler + virtual void SAL_CALL handle( const css::uno::Reference< css::task::XInteractionRequest >& Request ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( const css::lang::EventObject& e ) override; + virtual void SAL_CALL notifyTermination( const css::lang::EventObject& e ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateinfo.hxx b/extensions/source/update/check/updateinfo.hxx new file mode 100644 index 000000000..79387b358 --- /dev/null +++ b/extensions/source/update/check/updateinfo.hxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <vector> + +struct DownloadSource +{ + bool IsDirect; + OUString URL; + + DownloadSource(bool bIsDirect, const OUString& aURL) : IsDirect(bIsDirect), URL(aURL) {}; + DownloadSource(const DownloadSource& ds) : IsDirect(ds.IsDirect), URL(ds.URL) {}; + + DownloadSource & operator=( const DownloadSource & ds ) { IsDirect = ds.IsDirect; URL = ds.URL; return *this; }; +}; + +struct ReleaseNote +{ + sal_uInt8 Pos; + OUString URL; + sal_uInt8 Pos2; + OUString URL2; + + ReleaseNote(sal_uInt8 pos, const OUString& aURL) : Pos(pos), URL(aURL), Pos2(0), URL2() {}; + + ReleaseNote(const ReleaseNote& rn) :Pos(rn.Pos), URL(rn.URL), Pos2(rn.Pos2), URL2(rn.URL2) {}; + ReleaseNote & operator=( const ReleaseNote& rn) { Pos=rn.Pos; URL=rn.URL; Pos2=rn.Pos2; URL2=rn.URL2; return *this; }; +}; + +struct UpdateInfo +{ + OUString BuildId; + OUString Version; + OUString Description; + std::vector< DownloadSource > Sources; + std::vector< ReleaseNote > ReleaseNotes; +}; + +// Returns the URL of the release note for the given position +OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled=false); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocol.cxx b/extensions/source/update/check/updateprotocol.cxx new file mode 100644 index 000000000..db8319c79 --- /dev/null +++ b/extensions/source/update/check/updateprotocol.cxx @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_folders.h> + +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathException.hpp> + +#include "updateprotocol.hxx" +#include "updatecheckconfig.hxx" + +#include <com/sun/star/deployment/UpdateInformationEntry.hpp> +#include <com/sun/star/deployment/XPackageInformationProvider.hpp> + + +#include <rtl/ref.hxx> +#include <rtl/bootstrap.hxx> +#include <osl/diagnose.h> + +namespace container = css::container ; +namespace deployment = css::deployment ; +namespace uno = css::uno ; +namespace task = css::task ; +namespace xml = css::xml ; + + +static bool +getBootstrapData( + uno::Sequence< OUString > & rRepositoryList, + OUString & rGitID, + OUString & rInstallSetID) +{ + rGitID = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"; + rtl::Bootstrap::expandMacros( rGitID ); + if ( rGitID.isEmpty() ) + return false; + + rInstallSetID = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateID}"; + rtl::Bootstrap::expandMacros( rInstallSetID ); + if ( rInstallSetID.isEmpty() ) + return false; + + OUString aValue( "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateURL}" ); + rtl::Bootstrap::expandMacros( aValue ); + + if( !aValue.isEmpty() ) + { + rRepositoryList = { aValue }; + } + + return true; +} + + +// Returns 'true' if successfully connected to the update server +bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + uno::Reference< uno::XComponentContext > const & rxContext, + uno::Reference< task::XInteractionHandler > const & rxInteractionHandler, + const uno::Reference< deployment::XUpdateInformationProvider >& rUpdateInfoProvider) +{ + OUString myArch; + OUString myOS; + + rtl::Bootstrap::get("_OS", myOS); + rtl::Bootstrap::get("_ARCH", myArch); + + uno::Sequence< OUString > aRepositoryList; + OUString aGitID; + OUString aInstallSetID; + + if( ! ( getBootstrapData(aRepositoryList, aGitID, aInstallSetID) && (aRepositoryList.getLength() > 0) ) ) + return false; + + return checkForUpdates( o_rUpdateInfo, rxContext, rxInteractionHandler, rUpdateInfoProvider, + myOS, myArch, + aRepositoryList, aGitID, aInstallSetID ); +} + +bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const uno::Reference< uno::XComponentContext > & rxContext, + const uno::Reference< task::XInteractionHandler > & rxInteractionHandler, + const uno::Reference< deployment::XUpdateInformationProvider >& rUpdateInfoProvider, + std::u16string_view rOS, + std::u16string_view rArch, + const uno::Sequence< OUString > &rRepositoryList, + std::u16string_view rGitID, + const OUString &rInstallSetID ) +{ + if( !rxContext.is() ) + throw uno::RuntimeException( "checkForUpdates: empty component context" ); + + OSL_ASSERT( rxContext->getServiceManager().is() ); + + // XPath implementation + uno::Reference< xml::xpath::XXPathAPI > xXPath = xml::xpath::XPathAPI::create(rxContext); + + xXPath->registerNS( "inst", "http://update.libreoffice.org/description" ); + + if( rxInteractionHandler.is() ) + rUpdateInfoProvider->setInteractionHandler(rxInteractionHandler); + + try + { + uno::Reference< container::XEnumeration > aUpdateInfoEnumeration = + rUpdateInfoProvider->getUpdateInformationEnumeration( rRepositoryList, rInstallSetID ); + + if ( !aUpdateInfoEnumeration.is() ) + return false; // something went wrong .. + + OUString aXPathExpression = + OUString::Concat("/child::inst:description[inst:os=\'")+ + rOS + + "\' and inst:arch=\'"+ + rArch + + "\' and inst:gitid!=\'"+ + rGitID + + "\']"; + + + while( aUpdateInfoEnumeration->hasMoreElements() ) + { + deployment::UpdateInformationEntry aEntry; + + if( aUpdateInfoEnumeration->nextElement() >>= aEntry ) + { + uno::Reference< xml::dom::XNode > xNode( aEntry.UpdateDocument ); + uno::Reference< xml::dom::XNodeList > xNodeList; + try { + xNodeList = xXPath->selectNodeList(xNode, aXPathExpression + + "/inst:update/attribute::src"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + sal_Int32 i, imax = xNodeList->getLength(); + for( i = 0; i < imax; ++i ) + { + uno::Reference< xml::dom::XNode > xNode2( xNodeList->item(i) ); + + if( xNode2.is() ) + { + uno::Reference< xml::dom::XElement > xParent(xNode2->getParentNode(), uno::UNO_QUERY_THROW); + OUString aType = xParent->getAttribute("type"); + bool bIsDirect = !aType.equalsIgnoreAsciiCase("text/html"); + + o_rUpdateInfo.Sources.push_back( DownloadSource(bIsDirect, xNode2->getNodeValue()) ); + } + } + + uno::Reference< xml::dom::XNode > xNode2; + try { + xNode2 = xXPath->selectSingleNode(xNode, aXPathExpression + + "/inst:version/text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + if( xNode2.is() ) + o_rUpdateInfo.Version = xNode2->getNodeValue(); + + try { + xNode2 = xXPath->selectSingleNode(xNode, aXPathExpression + + "/inst:buildid/text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + if( xNode2.is() ) + o_rUpdateInfo.BuildId = xNode2->getNodeValue(); + + o_rUpdateInfo.Description = aEntry.Description; + + // Release Notes + try { + xNodeList = xXPath->selectNodeList(xNode, aXPathExpression + + "/inst:relnote"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + imax = xNodeList->getLength(); + for( i = 0; i < imax; ++i ) + { + uno::Reference< xml::dom::XElement > xRelNote(xNodeList->item(i), uno::UNO_QUERY); + if( xRelNote.is() ) + { + sal_Int32 pos = xRelNote->getAttribute("pos").toInt32(); + + ReleaseNote aRelNote(static_cast<sal_uInt8>(pos), xRelNote->getAttribute("src")); + + if( xRelNote->hasAttribute("src2") ) + { + pos = xRelNote->getAttribute("pos2").toInt32(); + aRelNote.Pos2 = static_cast<sal_Int8>(pos); + aRelNote.URL2 = xRelNote->getAttribute("src2"); + } + + o_rUpdateInfo.ReleaseNotes.push_back(aRelNote); + } + } + + if( !o_rUpdateInfo.Sources.empty() ) + return true; + } + } + } + catch( ... ) + { + return false; + } + + return true; +} + + +bool storeExtensionUpdateInfos( const uno::Reference< uno::XComponentContext > & rxContext, + const uno::Sequence< uno::Sequence< OUString > > &rUpdateInfos ) +{ + bool bNotify = false; + + if ( rUpdateInfos.hasElements() ) + { + rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( rxContext ); + + for ( sal_Int32 i = rUpdateInfos.getLength() - 1; i >= 0; i-- ) + { + bNotify |= aConfig->storeExtensionVersion( rUpdateInfos[i][0], rUpdateInfos[i][1] ); + } + } + return bNotify; +} + + +// Returns 'true' if there are updates for any extension + +bool checkForExtensionUpdates( const uno::Reference< uno::XComponentContext > & rxContext ) +{ + uno::Sequence< uno::Sequence< OUString > > aUpdateList; + + uno::Reference< deployment::XPackageInformationProvider > xInfoProvider; + try + { + uno::Any aValue( rxContext->getValueByName( + "/singletons/com.sun.star.deployment.PackageInformationProvider" ) ); + OSL_VERIFY( aValue >>= xInfoProvider ); + } + catch( const uno::Exception& ) + { + OSL_FAIL( "checkForExtensionUpdates: could not create the PackageInformationProvider!" ); + } + + if ( !xInfoProvider.is() ) return false; + + aUpdateList = xInfoProvider->isUpdateAvailable( OUString() ); + bool bNotify = storeExtensionUpdateInfos( rxContext, aUpdateList ); + + return bNotify; +} + + +// Returns 'true' if there are any pending updates for any extension (offline check) + +bool checkForPendingUpdates( const uno::Reference< uno::XComponentContext > & rxContext ) +{ + uno::Sequence< uno::Sequence< OUString > > aExtensionList; + uno::Reference< deployment::XPackageInformationProvider > xInfoProvider; + try + { + uno::Any aValue( rxContext->getValueByName( + "/singletons/com.sun.star.deployment.PackageInformationProvider" ) ); + OSL_VERIFY( aValue >>= xInfoProvider ); + } + catch( const uno::Exception& ) + { + OSL_FAIL( "checkForExtensionUpdates: could not create the PackageInformationProvider!" ); + } + + if ( !xInfoProvider.is() ) return false; + + bool bPendingUpdateFound = false; + + aExtensionList = xInfoProvider->getExtensionList(); + if ( aExtensionList.hasElements() ) + { + rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( rxContext ); + + for ( sal_Int32 i = aExtensionList.getLength() - 1; i >= 0; i-- ) + { + bPendingUpdateFound = aConfig->checkExtensionVersion( aExtensionList[i][0], aExtensionList[i][1] ); + if ( bPendingUpdateFound ) + break; + } + } + + return bPendingUpdateFound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocol.hxx b/extensions/source/update/check/updateprotocol.hxx new file mode 100644 index 000000000..4dedeb0d6 --- /dev/null +++ b/extensions/source/update/check/updateprotocol.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> + +#include "updateinfo.hxx" + +// Returns 'true' if successfully connected to the update server +bool checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::task::XInteractionHandler >& rxInteractionHandler, + const css::uno::Reference< css::deployment::XUpdateInformationProvider >& rxProvider +); + +// The same as above, that does not read the info from bootstrap +SAL_DLLPUBLIC_EXPORT bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const css::uno::Reference< css::task::XInteractionHandler > & rxInteractionHandler, + const css::uno::Reference< css::deployment::XUpdateInformationProvider >& rUpdateInfoProvider, + std::u16string_view rOS, + std::u16string_view rArch, + const css::uno::Sequence< OUString > &rRepositoryList, + std::u16string_view rGitID, + const OUString &rInstallID +); + +// Returns 'true' if there are updates for any extension +bool checkForExtensionUpdates( + const css::uno::Reference< css::uno::XComponentContext >& rxContext +); + +bool checkForPendingUpdates( + const css::uno::Reference< css::uno::XComponentContext >& rxContext +); + +bool storeExtensionUpdateInfos( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Sequence< OUString > > &rUpdateInfos +); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocoltest.cxx b/extensions/source/update/check/updateprotocoltest.cxx new file mode 100644 index 000000000..070f930af --- /dev/null +++ b/extensions/source/update/check/updateprotocoltest.cxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/bootstrap.hxx> + +#include "updateprotocol.hxx" +#include <com/sun/star/ucb/UniversalContentBroker.hpp> + +#include <sal/main.h> +#include <osl/process.h> +#include <stdio.h> +#include "sal/log.hxx" + +namespace task = ::com::sun::star::task; +namespace uno = ::com::sun::star::uno; + + +SAL_IMPLEMENT_MAIN() +{ + (void) argv; + (void) argc; + + if( osl_getCommandArgCount() != 0 ) + { + fprintf(stderr, "Usage: updateprotocoltest\n"); + return -1; + } + + // create the initial component context + uno::Reference< uno::XComponentContext > rComponentContext = cppu::defaultBootstrap_InitialComponentContext(); + + // initialize UCB (for backwards compatibility, in case some code still uses + // plain createInstance w/o args directly to obtain an instance): + css::ucb::UniversalContentBroker::create(rComponentContext); + + + OUString aURL; + OUString aVersion; + + try + { + if( checkForUpdates(rComponentContext, uno::Reference< task::XInteractionHandler > (), aURL, aVersion) ) + { + SAL_INFO("extensions.update", "Update found: " << aVersion << " on " << aURL); + } + else + { + SAL_INFO("extensions.update", "no updates found" ); + } + } + catch( ... ) + { + SAL_INFO("extensions.update", "unhandled exception caught" ); + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updchk.uno.component b/extensions/source/update/check/updchk.uno.component new file mode 100644 index 000000000..f147e3065 --- /dev/null +++ b/extensions/source/update/check/updchk.uno.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="vnd.sun.UpdateCheck" + constructor="extensions_update_UpdateCheckJob_get_implementation"> + <service name="com.sun.star.setup.UpdateCheck"/> + </implementation> + <implementation name="vnd.sun.UpdateCheckConfig" + constructor="extensions_update_UpdateCheckConfig_get_implementation"> + <service name="com.sun.star.setup.UpdateCheckConfig"/> + </implementation> +</component> |