diff options
author | Yusuf Keten <ketenyusuf@gmail.com> | 2020-07-07 03:22:01 +0300 |
---|---|---|
committer | Muhammet Kara <muhammet.kara@collabora.com> | 2020-07-10 12:39:56 +0200 |
commit | daefcf77bd99d9dae9aaa08695c40c2a99d753d7 (patch) | |
tree | d726171e8865a18c6c769fe72546aa393d84cbe3 /cui | |
parent | c54c8fedeaff84e1c92147ae929d6beada95bcaf (diff) |
tdf#133026: Tight integration of extensions - Adding thread structure
- Add new class - SearchAndParseThread.
- Add new label to show progress such as not found, loading, searching, etc.
- Add cache to prevent unnecessary data transfers.
Change-Id: Ieb5dbdabc3c9b5224e89be959728ca9b6d5c63b9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/98226
Tested-by: Jenkins
Reviewed-by: Muhammet Kara <muhammet.kara@collabora.com>
Diffstat (limited to 'cui')
-rw-r--r-- | cui/inc/strings.hrc | 3 | ||||
-rw-r--r-- | cui/source/dialogs/AdditionsDialog.cxx | 301 | ||||
-rw-r--r-- | cui/source/inc/AdditionsDialog.hxx | 30 | ||||
-rw-r--r-- | cui/uiconfig/ui/additionsdialog.ui | 47 |
4 files changed, 323 insertions, 58 deletions
diff --git a/cui/inc/strings.hrc b/cui/inc/strings.hrc index d54a0f9cd370..be9c3f7d3a3e 100644 --- a/cui/inc/strings.hrc +++ b/cui/inc/strings.hrc @@ -395,6 +395,9 @@ #define RID_SVXSTR_ADDITIONS_INSTALLBUTTON NC_("RID_SVXSTR_ADDITIONS_INSTALLBUTTON", "Install") #define RID_SVXSTR_ADDITIONS_LICENCE NC_("RID_SVXSTR_ADDITIONS_LICENCE", "License:") #define RID_SVXSTR_ADDITIONS_REQUIREDVERSION NC_("RID_SVXSTR_ADDITIONS_REQUIREDVERSION","Required version: >=") +#define RID_SVXSTR_ADDITIONS_SEARCHING NC_("RID_SVXSTR_ADDITIONS_SEARCHING", "Searching...") +#define RID_SVXSTR_ADDITIONS_LOADING NC_("RID_SVXSTR_ADDITIONS_LOADING", "Loading...") +#define RID_SVXSTR_ADDITIONS_NORESULTS NC_("ID_SVXSTR_ADDITIONS_NORESULTS", "No results found") #endif diff --git a/cui/source/dialogs/AdditionsDialog.cxx b/cui/source/dialogs/AdditionsDialog.cxx index 5ed8ad97e46c..ba49e07b9365 100644 --- a/cui/source/dialogs/AdditionsDialog.cxx +++ b/cui/source/dialogs/AdditionsDialog.cxx @@ -7,23 +7,41 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ +#include <config_folders.h> #include <AdditionsDialog.hxx> + #include <rtl/ustrbuf.hxx> #include <unordered_set> #include <sal/log.hxx> -#include <tools/stream.hxx> #include <strings.hrc> #include <com/sun/star/uno/XComponentContext.hpp> #include <com/sun/star/inspection/PropertyLineElement.hpp> #include <com/sun/star/graphic/GraphicProvider.hpp> #include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/strbuf.hxx> #include <tools/debug.hxx> #include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> +#include <tools/stream.hxx> #include <comphelper/processfactory.hxx> #include <comphelper/string.hxx> #include <dialmgr.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/mnemonic.hxx> + +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/xml/sax/XParser.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <ucbhelper/content.hxx> +#include <comphelper/simplefileaccessinteraction.hxx> #include <curl/curl.h> #include <orcus/json_document_tree.hpp> @@ -40,14 +58,25 @@ using ::com::sun::star::graphic::XGraphicProvider; using ::com::sun::star::uno::Sequence; using ::com::sun::star::beans::PropertyValue; using ::com::sun::star::graphic::XGraphic; +using namespace com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::beans; + +#ifdef UNX +const char kUserAgent[] = "LibreOffice AdditionsDownloader/1.0 (Linux)"; +#else +const char kUserAgent[] = "LibreOffice AdditionsDownloader/1.0 (unknown platform)"; +#endif namespace { struct AdditionInfo { + OUString sExtensionID; OUString sName; OUString sAuthorName; - OUString sPreviewURL; + OUString sExtensionURL; OUString sScreenshotURL; OUString sIntroduction; OUString sDescription; @@ -72,6 +101,18 @@ size_t WriteCallback(void* ptr, size_t size, size_t nmemb, void* userp) return real_size; } +// Callback to get the response data from server to a file. +size_t WriteCallbackFile(void* ptr, size_t size, size_t nmemb, void* userp) +{ + if (!userp) + return 0; + + SvStream* response = static_cast<SvStream*>(userp); + size_t real_size = size * nmemb; + response->WriteBytes(ptr, real_size); + return real_size; +} + // Gets the content of the given URL and returns as a standard string std::string curlGet(const OString& rURL) { @@ -104,6 +145,36 @@ std::string curlGet(const OString& rURL) return response_body; } +// Downloads and saves the file at the given rURL to a local path (sFileURL) +void curlDownload(const OString& rURL, const OUString& sFileURL) +{ + CURL* curl = curl_easy_init(); + SvFileStream aFile(sFileURL, StreamMode::WRITE); + + if (!curl) + return; + + curl_easy_setopt(curl, CURLOPT_URL, rURL.getStr()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallbackFile); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(&aFile)); + + CURLcode cc = curl_easy_perform(curl); + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code != 200) + { + SAL_WARN("cui.dialogs", "Download failed. Error code: " << http_code); + } + + if (cc != CURLE_OK) + { + SAL_WARN("cui.dialogs", "curl error: " << cc); + } +} + void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions) { orcus::json::document_tree aJsonDoc; @@ -130,6 +201,8 @@ void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdd try { AdditionInfo aNewAddition = { + OStringToOUString(OString(arrayElement.child("id").string_value().get()), + RTL_TEXTENCODING_UTF8), OStringToOUString(OString(arrayElement.child("name").string_value().get()), RTL_TEXTENCODING_UTF8), OStringToOUString(OString(arrayElement.child("author").string_value().get()), @@ -189,71 +262,197 @@ void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdd } } } + +bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile) +{ + uno::Reference<ucb::XSimpleFileAccess3> xFileAccess + = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + Reference<XComponentContext> xContext(::comphelper::getProcessComponentContext()); + + // copy the images to the user's additions folder + OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER + "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros(userFolder); + userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/"; + + OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName()); + OString aPreviewURL = OUStringToOString(aAdditionInfo.sScreenshotURL, RTL_TEXTENCODING_UTF8); + + try + { + osl::Directory::createPath(userFolder); + + if (!xFileAccess->exists(userFolder + aPreviewFile)) + curlDownload(aPreviewURL, userFolder + aPreviewFile); + } + catch (const uno::Exception&) + { + return false; + } + sPreviewFile = userFolder + aPreviewFile; + return true; } -AdditionsDialog::AdditionsDialog(weld::Window* pParent) - : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog") - , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch")) - , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear")) - , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow")) - , m_xContentGrid(m_xBuilder->weld_container("contentGrid")) +} // End of the anonymous namespace + +SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const OUString& rURL, + const bool& isFirstLoading) + : Thread("cuiAdditionsSearchThread") + , m_pAdditionsDialog(pDialog) + , m_aURL(rURL) + , m_bExecute(true) + , m_bIsFirstLoading(isFirstLoading) { - fillGrid(); } -AdditionsDialog::~AdditionsDialog() {} +SearchAndParseThread::~SearchAndParseThread() {} -void AdditionsDialog::fillGrid() +void SearchAndParseThread::execute() { - // TODO - Temporary URL - OString rURL = "https://yusufketen.com/extensionTest.json"; + //m_pAdditionsDialog->ClearSearchResults(); + OUString sProgress; + if (m_bIsFirstLoading) + sProgress = CuiResId(RID_SVXSTR_ADDITIONS_LOADING); + else + sProgress = CuiResId(RID_SVXSTR_ADDITIONS_SEARCHING); + + m_pAdditionsDialog->SetProgress(sProgress); + + uno::Reference<ucb::XSimpleFileAccess3> xFileAccess + = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + OString rURL = OUStringToOString(m_aURL, RTL_TEXTENCODING_UTF8); std::string sResponse = curlGet(rURL); + std::vector<AdditionInfo> additionInfos; + parseResponse(sResponse, additionInfos); - sal_Int32 i = 0; - for (const auto& additionInfo : additionInfos) + if (additionInfos.empty()) { - m_aAdditionsItems.emplace_back(m_xContentGrid.get()); - AdditionsItem& aCurrentItem = m_aAdditionsItems.back(); - - sal_Int32 nGridPositionY = i++; - aCurrentItem.m_xContainer->set_grid_left_attach(0); - aCurrentItem.m_xContainer->set_grid_top_attach(nGridPositionY); - - aCurrentItem.m_xLinkButtonName->set_label(additionInfo.sName); - aCurrentItem.m_xLinkButtonName->set_uri(additionInfo.sPreviewURL); - aCurrentItem.m_xLabelDescription->set_label(additionInfo.sIntroduction); - aCurrentItem.m_xLabelAuthor->set_label(additionInfo.sAuthorName); - aCurrentItem.m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON)); - OUString sLicenseString = CuiResId(RID_SVXSTR_ADDITIONS_LICENCE) + additionInfo.sLicense; - aCurrentItem.m_xLabelLicense->set_label(sLicenseString); - OUString sVersionString - = CuiResId(RID_SVXSTR_ADDITIONS_REQUIREDVERSION) + additionInfo.sCompatibleVersion; - aCurrentItem.m_xLabelVersion->set_label(sVersionString); - aCurrentItem.m_xLinkButtonComments->set_label(additionInfo.sCommentNumber); - aCurrentItem.m_xLinkButtonComments->set_uri(additionInfo.sCommentURL); - aCurrentItem.m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber); - - Reference<XGraphic> xGraphic; - try + sProgress = CuiResId(RID_SVXSTR_ADDITIONS_NORESULTS); + m_pAdditionsDialog->SetProgress(sProgress); + return; + } + else + { + //Get Preview Files + sal_Int32 i = 0; + for (const auto& additionInfo : additionInfos) { - Reference<XComponentContext> xContext(::comphelper::getProcessComponentContext()); - Reference<XGraphicProvider> xGraphicProvider(GraphicProvider::create(xContext)); + if (!m_bExecute) + return; + + OUString aPreviewFile; + bool bResult = getPreviewFile(additionInfo, aPreviewFile); + + if (!bResult) + { + SAL_INFO("cui.dialogs", + "Couldn't get the preview file. Skipping: " << aPreviewFile); + continue; + } + + SolarMutexGuard aGuard; + m_pAdditionsDialog->m_aAdditionsItems.emplace_back( + m_pAdditionsDialog->m_xContentGrid.get()); + AdditionsItem& aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back(); + + sal_Int32 nGridPositionY = i++; + aCurrentItem.m_xContainer->set_grid_left_attach(0); + aCurrentItem.m_xContainer->set_grid_top_attach(nGridPositionY); + + aCurrentItem.m_xLinkButtonName->set_label(additionInfo.sName); + aCurrentItem.m_xLinkButtonName->set_uri(additionInfo.sExtensionURL); + aCurrentItem.m_xLabelDescription->set_label(additionInfo.sIntroduction); + aCurrentItem.m_xLabelAuthor->set_label(additionInfo.sAuthorName); + aCurrentItem.m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON)); + OUString sLicenseString + = CuiResId(RID_SVXSTR_ADDITIONS_LICENCE) + additionInfo.sLicense; + aCurrentItem.m_xLabelLicense->set_label(sLicenseString); + OUString sVersionString + = CuiResId(RID_SVXSTR_ADDITIONS_REQUIREDVERSION) + additionInfo.sCompatibleVersion; + aCurrentItem.m_xLabelVersion->set_label(sVersionString); + aCurrentItem.m_xLinkButtonComments->set_label(additionInfo.sCommentNumber); + aCurrentItem.m_xLinkButtonComments->set_uri(additionInfo.sCommentURL); + aCurrentItem.m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber); + + GraphicFilter aFilter; + Graphic aGraphic; + + INetURLObject aURLObj(aPreviewFile); + + // This block may be added according to need + /*OUString aAdditionsSetting = additionInfo.sSlug + + ";" + additionInfo.sName + + ";" + additionInfo.sPreviewURL + + ";" + additionInfo.sHeaderURL + + ";" + additionInfo.sFooterURL + + ";" + additionInfo.sTextColor; + + m_pAdditionsDialog->AddPersonaSetting( aPersonaSetting ); + */ + + // for VCL to be able to create bitmaps / do visual changes in the thread + aFilter.ImportGraphic(aGraphic, aURLObj); + BitmapEx aBmp = aGraphic.GetBitmapEx(); + + ScopedVclPtr<VirtualDevice> xVirDev + = aCurrentItem.m_xImageScreenshot->create_virtual_device(); + xVirDev->SetOutputSizePixel(aBmp.GetSizePixel()); + xVirDev->DrawBitmapEx(Point(0, 0), aBmp); + + aCurrentItem.m_xImageScreenshot->set_image(xVirDev.get()); + xVirDev.disposeAndClear(); + } + } - Sequence<PropertyValue> aMediaProperties(1); - aMediaProperties[0].Name = "URL"; - aMediaProperties[0].Value <<= additionInfo.sScreenshotURL; + if (!m_bExecute) + return; - xGraphic = Reference<XGraphic>(xGraphicProvider->queryGraphic(aMediaProperties), - css::uno::UNO_SET_THROW); - } - catch (const Exception&) - { - DBG_UNHANDLED_EXCEPTION("cui.dialogs"); - } + SolarMutexGuard aGuard; + sProgress.clear(); + m_pAdditionsDialog->SetProgress(sProgress); +} + +AdditionsDialog::AdditionsDialog(weld::Window* pParent) + : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog") + , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch")) + , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear")) + , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow")) + , m_xContentGrid(m_xBuilder->weld_container("contentGrid")) + , m_xLabelProgress(m_xBuilder->weld_label("labelProgress")) +{ + // TODO - Temporary URL + OString rURL = "https://yusufketen.com/extensionTest.json"; + + m_pSearchThread + = new SearchAndParseThread(this, OStringToOUString(rURL, RTL_TEXTENCODING_UTF8), true); + m_pSearchThread->launch(); + // fillGrid(); +} - aCurrentItem.m_xImageScreenshot->set_image(xGraphic); +AdditionsDialog::~AdditionsDialog() +{ + if (m_pSearchThread.is()) + { + // Release the solar mutex, so the thread is not affected by the race + // when it's after the m_bExecute check but before taking the solar + // mutex. + SolarMutexReleaser aReleaser; + m_pSearchThread->join(); + } +} + +void AdditionsDialog::SetProgress(const OUString& rProgress) +{ + if (rProgress.isEmpty()) + m_xLabelProgress->hide(); + else + { + SolarMutexGuard aGuard; + m_xLabelProgress->show(); + m_xLabelProgress->set_label(rProgress); + m_xDialog->resize_to_request(); //TODO } } diff --git a/cui/source/inc/AdditionsDialog.hxx b/cui/source/inc/AdditionsDialog.hxx index ee252e0ea79e..8b3211bb3d1a 100644 --- a/cui/source/inc/AdditionsDialog.hxx +++ b/cui/source/inc/AdditionsDialog.hxx @@ -11,6 +11,8 @@ #pragma once #include <vcl/svapp.hxx> +#include <salhelper/thread.hxx> +#include <rtl/ref.hxx> #include <vcl/weld.hxx> struct AdditionsItem @@ -50,10 +52,14 @@ struct AdditionsItem std::unique_ptr<weld::Image> m_xImageDownloadNumber; std::unique_ptr<weld::Label> m_xLabelDownloadNumber; }; +class SearchAndParseThread; class AdditionsDialog : public weld::GenericDialogController { private: + // void fillGrid(); + +public: std::unique_ptr<weld::Entry> m_xEntrySearch; std::unique_ptr<weld::MenuButton> m_xMenuButtonSettings; std::vector<AdditionsItem> m_aAdditionsItems; @@ -61,11 +67,31 @@ private: std::unique_ptr<weld::ScrolledWindow> m_xContentWindow; std::unique_ptr<weld::Container> m_xContentGrid; - void fillGrid(); + std::unique_ptr<weld::Label> m_xLabelProgress; + ::rtl::Reference<SearchAndParseThread> m_pSearchThread; -public: AdditionsDialog(weld::Window* pParent); ~AdditionsDialog() override; + + void SetProgress(const OUString& rProgress); +}; + +class SearchAndParseThread : public salhelper::Thread +{ +private: + AdditionsDialog* m_pAdditionsDialog; + OUString m_aURL; + std::atomic<bool> m_bExecute; + bool m_bIsFirstLoading; + + virtual ~SearchAndParseThread() override; + virtual void execute() override; + +public: + SearchAndParseThread(AdditionsDialog* pDialog, const OUString& rURL, + const bool& bIsFirstLoading); + + void StopExecution() { m_bExecute = false; } }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cui/uiconfig/ui/additionsdialog.ui b/cui/uiconfig/ui/additionsdialog.ui index 1eb7736220a1..0cd02d98e8cb 100644 --- a/cui/uiconfig/ui/additionsdialog.ui +++ b/cui/uiconfig/ui/additionsdialog.ui @@ -26,17 +26,54 @@ <property name="can_focus">False</property> <property name="layout_style">end</property> <child> + <object class="GtkBox"> + <property name="width_request">300</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">5</property> + <child> + <placeholder/> + </child> + <child> + <object class="GtkLabel" id="labelProgress"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes" context="additionsdialog|ProgressLabel">Progress Label</property> + <child internal-child="accessible"> + <object class="AtkObject" id="labelProgress-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|ProgressLabel">ProgressLabel</property> + <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|ProgressLabel">This label shows that the progress of the operations such as loading extensions, not found, etc.</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> <object class="GtkButton" id="buttonClose"> <property name="label">gtk-close</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> + <property name="valign">end</property> <property name="use_stock">True</property> </object> <packing> <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> + <property name="fill">False</property> + <property name="position">1</property> </packing> </child> </object> @@ -67,8 +104,8 @@ <property name="primary_icon_sensitive">False</property> <child internal-child="accessible"> <object class="AtkObject" id="entrySearch-atkobject"> - <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|entrySearch">searchEntry</property> - <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|entrySearch">searchEntry</property> + <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|searchEntry">searchEntry</property> + <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|searchEntry">searchEntry</property> </object> </child> </object> @@ -95,7 +132,7 @@ </child> <child internal-child="accessible"> <object class="AtkObject" id="buttonGear-atkobject"> - <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|buttonGear">Gear Menu</property> + <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|buttonGear">Gear Menu</property> <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|buttonGear">Contains commands to modify settings of the additions list such as sorting type or view type.</property> </object> </child> |