summaryrefslogtreecommitdiffstats
path: root/extensions/source/update/check
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/source/update/check')
-rw-r--r--extensions/source/update/check/actionlistener.hxx39
-rw-r--r--extensions/source/update/check/download.cxx427
-rw-r--r--extensions/source/update/check/download.hxx80
-rw-r--r--extensions/source/update/check/onlinecheck.cxx49
-rw-r--r--extensions/source/update/check/onlinecheck.hxx28
-rw-r--r--extensions/source/update/check/org/openoffice/Office/Addons.xcu42
-rw-r--r--extensions/source/update/check/org/openoffice/Office/Jobs.xcu66
-rw-r--r--extensions/source/update/check/updatecheck.cxx1617
-rw-r--r--extensions/source/update/check/updatecheck.hxx189
-rw-r--r--extensions/source/update/check/updatecheckconfig.cxx660
-rw-r--r--extensions/source/update/check/updatecheckconfig.hxx205
-rw-r--r--extensions/source/update/check/updatecheckconfiglistener.hxx37
-rw-r--r--extensions/source/update/check/updatecheckjob.cxx322
-rw-r--r--extensions/source/update/check/updatehdl.cxx1281
-rw-r--r--extensions/source/update/check/updatehdl.hxx207
-rw-r--r--extensions/source/update/check/updateinfo.hxx61
-rw-r--r--extensions/source/update/check/updateprotocol.cxx317
-rw-r--r--extensions/source/update/check/updateprotocol.hxx68
-rw-r--r--extensions/source/update/check/updateprotocoltest.cxx76
-rw-r--r--extensions/source/update/check/updchk.uno.component30
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>