/* -*- 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/.
 *
 */
 
#include <sal/config.h>
 
#include <algorithm>
#include <cmath>
 
#include <config_folders.h>
 
#include <AdditionsDialog.hxx>
#include <dialmgr.hxx>
#include <strings.hrc>
 
#include <o3tl/test_info.hxx>
#include <sal/log.hxx>
 
#include <com/sun/star/graphic/GraphicProvider.hpp>
#include <com/sun/star/graphic/XGraphicProvider.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <tools/urlobj.hxx>
#include <tools/stream.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/processfactory.hxx>
#include <vcl/virdev.hxx>
#include <vcl/svapp.hxx>
#include <vcl/graphicfilter.hxx>
#include <cppuhelper/exc_hlp.hxx>
 
#include <com/sun/star/util/SearchFlags.hpp>
#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <unotools/textsearch.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <ucbhelper/content.hxx>
 
#include <com/sun/star/deployment/DeploymentException.hpp>
#include <com/sun/star/deployment/ExtensionManager.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/ucb/CommandAbortedException.hpp>
#include <com/sun/star/ucb/CommandFailedException.hpp>
 
#include <com/sun/star/task/XInteractionApprove.hpp>
 
#include <orcus/json_document_tree.hpp>
#include <orcus/json_parser.hpp>
#include <orcus/config.hpp>
 
#include <bitmaps.hlst>
 
#define PAGE_SIZE 30
 
using namespace css;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::Sequence;
 
using namespace com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::beans;
 
namespace
{
// Gets the content of the given URL and returns as a standard string
std::string ucbGet(const OUString& rURL, const css::uno::Reference<css::awt::XWindow>& xParentWin)
{
    try
    {
        auto const s
            = utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ, xParentWin, false);
        if (!s)
        {
            SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed");
            return {};
        }
        std::string response_body;
        do
        {
            char buf[4096];
            auto const n = s->ReadBytes(buf, sizeof buf);
            response_body.append(buf, n);
        } while (s->good());
        if (s->bad())
        {
            SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError());
            return {};
        }
        return response_body;
    }
    catch (css::uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
        return {};
    }
}
 
// Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName)
void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName)
{
    try
    {
        ucbhelper::Content(sFolderURL, {}, comphelper::getProcessComponentContext())
            .transferContent(ucbhelper::Content(rURL, {}, comphelper::getProcessComponentContext()),
                             ucbhelper::InsertOperation::Copy, fileName,
                             css::ucb::NameClash::OVERWRITE);
    }
    catch (css::uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
    }
}
 
void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
{
    orcus::json::document_tree aJsonDoc;
    orcus::json_config aConfig;
 
    if (rResponse.empty())
        return;
 
    try
    {
        aJsonDoc.load(rResponse, aConfig);
    }
    catch (const orcus::parse_error&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API");
        return;
    }
 
    auto aDocumentRoot = aJsonDoc.get_document_root();
    if (aDocumentRoot.type() != orcus::json::node_t::object)
    {
        SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse);
        return;
    }
 
    auto resultsArray = aDocumentRoot.child("extension");
 
    for (size_t i = 0; i < resultsArray.child_count(); ++i)
    {
        auto arrayElement = resultsArray.child(i);
 
        try
        {
            AdditionInfo aNewAddition = {
                OStringToOUString(arrayElement.child("id").string_value(), RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("name").string_value(), RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("author").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("url").string_value(), RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("screenshotURL").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("extensionIntroduction").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("extensionDescription").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(
                    arrayElement.child("releases").child(0).child("compatibility").string_value(),
                    RTL_TEXTENCODING_UTF8),
                OStringToOUString(
                    arrayElement.child("releases").child(0).child("releaseName").string_value(),
                    RTL_TEXTENCODING_UTF8),
                OStringToOUString(
                    arrayElement.child("releases").child(0).child("license").string_value(),
                    RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("commentNumber").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("commentURL").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("rating").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(arrayElement.child("downloadNumber").string_value(),
                                  RTL_TEXTENCODING_UTF8),
                OStringToOUString(
                    arrayElement.child("releases").child(0).child("downloadURL").string_value(),
                    RTL_TEXTENCODING_UTF8)
            };
 
            aAdditions.push_back(aNewAddition);
        }
        catch (orcus::json::document_error& e)
        {
            // This usually happens when one of the values is null (type() == orcus::json::node_t::null)
            // TODO: Allow null values in additions.
            SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what());
        }
    }
}
 
bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
{
    uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
        = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
 
    // copy the images to the user's additions folder
    OUString userFolder = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
                          "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"_ustr;
    rtl::Bootstrap::expandMacros(userFolder);
    userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
 
    OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
    OUString aPreviewURL = aAdditionInfo.sScreenshotURL;
 
    try
    {
        osl::Directory::createPath(userFolder);
 
        if (!xFileAccess->exists(userFolder + aPreviewFile))
            ucbDownload(aPreviewURL, userFolder, aPreviewFile);
    }
    catch (const uno::Exception&)
    {
        return false;
    }
    sPreviewFile = userFolder + aPreviewFile;
    return true;
}
 
void LoadImage(std::u16string_view rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem)
{
    const sal_Int8 Margin = 6;
 
    SolarMutexGuard aGuard;
 
    GraphicFilter aFilter;
    Graphic aGraphic;
 
    INetURLObject aURLObj(rPreviewFile);
 
    // for VCL to be able to create bitmaps / do visual changes in the thread
    aFilter.ImportGraphic(aGraphic, aURLObj);
    BitmapEx aBmp = aGraphic.GetBitmapEx();
    Size aBmpSize = aBmp.GetSizePixel();
    Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request());
    if (!aBmp.IsEmpty())
    {
        double aScale;
        if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin)
        {
            aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin);
            aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
        }
        else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin)
        {
            aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin);
            aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
        };
        aBmpSize = aBmp.GetSizePixel();
    }
 
    ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device();
    xVirDev->SetOutputSizePixel(aThumbSize);
    //white background since images come with a white border
    xVirDev->SetBackground(Wallpaper(COL_WHITE));
    xVirDev->Erase();
    xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
    pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get());
    xVirDev.disposeAndClear();
}
 
} // End of the anonymous namespace
 
SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const bool isFirstLoading)
    : Thread("cuiAdditionsSearchThread")
    , m_pAdditionsDialog(pDialog)
    , m_bExecute(true)
    , m_bIsFirstLoading(isFirstLoading)
{
    // if we are running a UITest, e.g. UITest_sw_options then
    // don't attempt to downloading anything
    m_bUITest = o3tl::IsRunningUITest();
}
 
SearchAndParseThread::~SearchAndParseThread() {}
 
void SearchAndParseThread::Append(AdditionInfo& additionInfo)
{
    if (!m_bExecute)
        return;
    OUString aPreviewFile;
    bool bResult
        = !m_bUITest && getPreviewFile(additionInfo, aPreviewFile); // info vector json data
 
    if (!bResult)
    {
        SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile);
        return;
    }
 
    SolarMutexGuard aGuard;
 
    auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(),
                                                   m_pAdditionsDialog, additionInfo);
    m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem);
    std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
 
    LoadImage(aPreviewFile, aCurrentItem);
    m_pAdditionsDialog->m_nCurrentListItemCount++;
 
    if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
    {
        if (m_pAdditionsDialog->m_nCurrentListItemCount
            != m_pAdditionsDialog->m_aAllExtensionsVector.size())
            aCurrentItem->m_xButtonShowMore->set_visible(true);
    }
}
 
void SearchAndParseThread::Search()
{
    m_pAdditionsDialog->m_searchOptions.searchString
        = m_pAdditionsDialog->m_xEntrySearch->get_text();
    utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
 
    size_t nIteration = 0;
    for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector)
    {
        if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
            break;
 
        OUString sExtensionName = rInfo.sName;
        OUString sExtensionDescription = rInfo.sDescription;
 
        if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty()
            && !textSearch.searchForward(sExtensionName)
            && !textSearch.searchForward(sExtensionDescription))
        {
            continue;
        }
        else
        {
            if (nIteration >= m_pAdditionsDialog->m_nCurrentListItemCount)
                Append(rInfo);
            nIteration++;
        }
    }
    CheckInstalledExtensions();
}
 
void SearchAndParseThread::CheckInstalledExtensions()
{
    const uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages
        = m_pAdditionsDialog->getInstalledExtensions();
 
    if (!xAllPackages.hasElements())
        return;
 
    OUString currentExtensionName;
 
    for (auto& package : xAllPackages)
    {
        for (auto& extensionVersion : package)
        {
            if (extensionVersion.is())
            {
                currentExtensionName = extensionVersion->getName();
                if (currentExtensionName.isEmpty())
                    continue;
 
                m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName;
                utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
 
                for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems)
                {
                    OUString sExtensionDownloadURL = rInfo->m_sDownloadURL;
 
                    if (!textSearch.searchForward(sExtensionDownloadURL))
                    {
                        continue;
                    }
                    else
                    {
                        SolarMutexGuard aGuard;
                        rInfo->m_xButtonInstall->set_sensitive(false);
                        rInfo->m_xButtonInstall->set_label(
                            CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
                    }
                }
            }
        }
    }
}
 
void SearchAndParseThread::execute()
{
    OUString sProgress;
    if (m_bIsFirstLoading)
        sProgress = CuiResId(RID_CUISTR_ADDITIONS_LOADING);
    else
        sProgress = CuiResId(RID_CUISTR_ADDITIONS_SEARCHING);
 
    m_pAdditionsDialog->SetProgress(
        sProgress); // Loading or searching according to being first call or not
 
    if (m_bIsFirstLoading)
    {
        const auto pDialog = m_pAdditionsDialog->getDialog();
        std::string sResponse = !m_bUITest ? ucbGet(m_pAdditionsDialog->m_sURL,
                                                    pDialog ? pDialog->GetXWindow() : nullptr)
                                           : "";
        parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector);
        std::sort(m_pAdditionsDialog->m_aAllExtensionsVector.begin(),
                  m_pAdditionsDialog->m_aAllExtensionsVector.end(),
                  AdditionsDialog::sortByDownload);
        Search();
    }
    else // Searching
    {
        Search();
    }
 
    if (!m_bExecute)
        return;
 
    SolarMutexGuard aGuard;
    sProgress.clear();
    m_pAdditionsDialog->SetProgress(sProgress);
}
 
AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag)
    : GenericDialogController(pParent, u"cui/ui/additionsdialog.ui"_ustr, u"AdditionsDialog"_ustr)
    , m_aSearchDataTimer("AdditionsDialog SearchDataTimer")
    , m_xEntrySearch(m_xBuilder->weld_entry(u"entrySearch"_ustr))
    , m_xButtonClose(m_xBuilder->weld_button(u"buttonClose"_ustr))
    , m_xContentWindow(m_xBuilder->weld_scrolled_window(u"contentWindow"_ustr))
    , m_xContentGrid(m_xBuilder->weld_container(u"contentGrid"_ustr))
    , m_xLabelProgress(m_xBuilder->weld_label(u"labelProgress"_ustr))
    , m_xGearBtn(m_xBuilder->weld_menu_button(u"buttonGear"_ustr))
{
    m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl));
    m_xGearBtn->set_item_active(u"gear_sort_voting"_ustr, true);
 
    m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl));
    m_aSearchDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT);
 
    m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
    m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
    m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
 
    m_sTag = sAdditionsTag;
    m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
    m_nCurrentListItemCount = 0; // First, there is no item on the list.
 
    OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
    if (!m_sTag.isEmpty())
    { // tdf#142564 localize extension category names
        OUString sDialogTitle = u""_ustr;
        if (sAdditionsTag == "Templates")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_TEMPLATES);
        }
        else if (sAdditionsTag == "Dictionary")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_DICTIONARY);
        }
        else if (sAdditionsTag == "Gallery")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_GALLERY);
        }
        else if (sAdditionsTag == "Icons")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_ICONS);
        }
        else if (sAdditionsTag == "Color Palette")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_PALETTES);
        }
        else if (sAdditionsTag == "Themes")
        {
            sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_THEMES);
        }
        this->set_title(sDialogTitle);
    }
    else
    {
        this->set_title(titlePrefix);
        m_sTag = "allextensions"; // Means empty parameter
    }
 
    OUString sEncodedURLPart = INetURLObject::encode(m_sTag, INetURLObject::PART_PCHAR,
                                                     INetURLObject::EncodeMechanism::All);
 
    //FIXME: Temporary URL - v0 is not using actual api
    m_sURL = "https://extensions.libreoffice.org/api/v0/" + sEncodedURLPart + ".json";
 
    m_xExtensionManager
        = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
 
    //Initialize search util
    m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
    m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
    m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
                                   | css::util::SearchFlags::REG_NOT_ENDOFLINE);
    m_pSearchThread = new SearchAndParseThread(this, true);
    m_pSearchThread->launch();
}
 
AdditionsDialog::~AdditionsDialog()
{
    if (m_pSearchThread.is())
    {
        m_pSearchThread->StopExecution();
        // 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();
    }
}
 
uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>>
AdditionsDialog::getInstalledExtensions()
{
    uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages;
 
    try
    {
        xAllPackages = m_xExtensionManager->getAllExtensions(
            uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>());
    }
    catch (const deployment::DeploymentException&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
    }
    catch (const ucb::CommandFailedException&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
    }
    catch (const ucb::CommandAbortedException&)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
    }
    catch (const lang::IllegalArgumentException& e)
    {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
    }
    return xAllPackages;
}
 
void AdditionsDialog::SetProgress(const OUString& rProgress)
{
    if (rProgress.isEmpty())
    {
        m_xLabelProgress->hide();
        m_xButtonClose->set_sensitive(true);
    }
    else
    {
        SolarMutexGuard aGuard;
        m_xLabelProgress->show();
        m_xLabelProgress->set_label(rProgress);
        m_xDialog->resize_to_request(); //TODO
    }
}
 
void AdditionsDialog::ClearList()
{
    // for VCL to be able to destroy bitmaps
    SolarMutexGuard aGuard;
 
    for (auto& item : this->m_aAdditionsItems)
    {
        item->m_xContainer->hide();
    }
    this->m_aAdditionsItems.clear();
}
 
void AdditionsDialog::RefreshUI()
{
    if (m_pSearchThread.is())
        m_pSearchThread->StopExecution();
    ClearList();
    m_nCurrentListItemCount = 0;
    m_nMaxItemCount = PAGE_SIZE;
    m_pSearchThread = new SearchAndParseThread(this, false);
    m_pSearchThread->launch();
}
 
bool AdditionsDialog::sortByRating(const AdditionInfo& a, const AdditionInfo& b)
{
    return a.sRating.toDouble() > b.sRating.toDouble();
}
 
bool AdditionsDialog::sortByComment(const AdditionInfo& a, const AdditionInfo& b)
{
    return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
}
 
bool AdditionsDialog::sortByDownload(const AdditionInfo& a, const AdditionInfo& b)
{
    return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
}
 
AdditionsItem::AdditionsItem(weld::Widget* pParent, AdditionsDialog* pParentDialog,
                             const AdditionInfo& additionInfo)
    : m_xBuilder(Application::CreateBuilder(pParent, u"cui/ui/additionsfragment.ui"_ustr))
    , m_xContainer(m_xBuilder->weld_widget(u"additionsEntry"_ustr))
    , m_xImageScreenshot(m_xBuilder->weld_image(u"imageScreenshot"_ustr))
    , m_xButtonInstall(m_xBuilder->weld_button(u"buttonInstall"_ustr))
    , m_xLinkButtonWebsite(m_xBuilder->weld_link_button(u"btnWebsite"_ustr))
    , m_xLabelName(m_xBuilder->weld_label(u"lbName"_ustr))
    , m_xLabelAuthor(m_xBuilder->weld_label(u"labelAuthor"_ustr))
    , m_xLabelDescription(m_xBuilder->weld_label(u"labelDescription"_ustr))
    , m_xLabelLicense(m_xBuilder->weld_label(u"lbLicenseText"_ustr))
    , m_xLabelVersion(m_xBuilder->weld_label(u"lbVersionText"_ustr))
    , m_xLinkButtonComments(m_xBuilder->weld_link_button(u"linkButtonComments"_ustr))
    , m_xImageVoting1(m_xBuilder->weld_image(u"imageVoting1"_ustr))
    , m_xImageVoting2(m_xBuilder->weld_image(u"imageVoting2"_ustr))
    , m_xImageVoting3(m_xBuilder->weld_image(u"imageVoting3"_ustr))
    , m_xImageVoting4(m_xBuilder->weld_image(u"imageVoting4"_ustr))
    , m_xImageVoting5(m_xBuilder->weld_image(u"imageVoting5"_ustr))
    , m_xLabelDownloadNumber(m_xBuilder->weld_label(u"labelDownloadNumber"_ustr))
    , m_xButtonShowMore(m_xBuilder->weld_button(u"buttonShowMore"_ustr))
    , m_pParentDialog(pParentDialog)
    , m_sDownloadURL(u""_ustr)
    , m_sExtensionID(u""_ustr)
{
    SolarMutexGuard aGuard;
 
    // AdditionsItem set location
    m_xContainer->set_grid_left_attach(0);
    m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
 
    // Set maximum length of the extension title
    OUString sExtensionName;
    const sal_Int32 maxExtensionNameLength = 30;
 
    if (additionInfo.sName.getLength() > maxExtensionNameLength)
    {
        std::u16string_view sShortName = additionInfo.sName.subView(0, maxExtensionNameLength - 3);
        sExtensionName = OUString::Concat(sShortName) + "...";
    }
    else
    {
        sExtensionName = additionInfo.sName;
    }
 
    m_xLabelName->set_label(sExtensionName);
 
    double aExtensionRating = additionInfo.sRating.toDouble();
    switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
    {
        case 5:
            m_xImageVoting5->set_from_icon_name(RID_SVXBMP_STARS_FULL);
            [[fallthrough]];
        case 4:
            m_xImageVoting4->set_from_icon_name(RID_SVXBMP_STARS_FULL);
            [[fallthrough]];
        case 3:
            m_xImageVoting3->set_from_icon_name(RID_SVXBMP_STARS_FULL);
            [[fallthrough]];
        case 2:
            m_xImageVoting2->set_from_icon_name(RID_SVXBMP_STARS_FULL);
            [[fallthrough]];
        case 1:
            m_xImageVoting1->set_from_icon_name(RID_SVXBMP_STARS_FULL);
            break;
    }
 
    m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
    m_xLabelDescription->set_label(additionInfo.sIntroduction);
 
    if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
        m_xLabelAuthor->set_label(additionInfo.sAuthorName);
 
    m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
    m_xLabelLicense->set_label(additionInfo.sLicense);
    m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
    m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
    m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
    m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
    m_pParentDialog = pParentDialog;
    m_sDownloadURL = additionInfo.sDownloadURL;
    m_sExtensionID = additionInfo.sExtensionID;
 
    m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
    m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
}
 
bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
{
    uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
        = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
 
    // copy the extensions' files to the user's additions folder
    OUString userFolder = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
                          "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"_ustr;
    rtl::Bootstrap::expandMacros(userFolder);
    userFolder += "/user/additions/" + m_sExtensionID + "/";
 
    OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName());
    OUString aExtensionsURL = m_sDownloadURL;
 
    try
    {
        osl::Directory::createPath(userFolder);
 
        if (!xFileAccess->exists(userFolder + aExtensionsFile))
            ucbDownload(aExtensionsURL, userFolder, aExtensionsFile);
    }
    catch (const uno::Exception&)
    {
        return false;
    }
    sExtensionFile = userFolder + aExtensionsFile;
    return true;
}
 
IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
 
IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void)
{
    m_aSearchDataTimer.Start();
}
 
IMPL_LINK_NOARG(AdditionsDialog, FocusOut_Impl, weld::Widget&, void)
{
    if (m_aSearchDataTimer.IsActive())
    {
        m_aSearchDataTimer.Stop();
        m_aSearchDataTimer.Invoke();
    }
}
 
IMPL_LINK_NOARG(AdditionsDialog, CloseButtonHdl, weld::Button&, void)
{
    if (m_pSearchThread.is())
        m_pSearchThread->StopExecution();
    this->response(RET_CLOSE);
}
 
IMPL_LINK_NOARG(AdditionsItem, ShowMoreHdl, weld::Button&, void)
{
    this->m_xButtonShowMore->set_visible(false);
    m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
    if (m_pParentDialog->m_pSearchThread.is())
        m_pParentDialog->m_pSearchThread->StopExecution();
    m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
    m_pParentDialog->m_pSearchThread->launch();
}
 
IMPL_LINK_NOARG(AdditionsItem, InstallHdl, weld::Button&, void)
{
    m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLING));
    m_xButtonInstall->set_sensitive(false);
    OUString aExtensionFile;
    bool bResult = getExtensionFile(aExtensionFile); // info vector json data
 
    if (!bResult)
    {
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
 
        SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
        return;
    }
 
    rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv();
    uno::Reference<task::XAbortChannel> xAbortChannel;
    try
    {
        m_pParentDialog->m_xExtensionManager->addExtension(aExtensionFile,
                                                           uno::Sequence<beans::NamedValue>(),
                                                           u"user"_ustr, xAbortChannel, pCmdEnv);
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
    }
    catch (const ucb::CommandFailedException)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
    }
    catch (const ucb::CommandAbortedException)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
    }
    catch (const deployment::DeploymentException)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
    }
    catch (const lang::IllegalArgumentException)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
    }
    catch (const css::uno::Exception)
    {
        TOOLS_WARN_EXCEPTION("cui.dialogs", "");
        m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
        m_xButtonInstall->set_sensitive(true);
    }
}
 
// TmpRepositoryCommandEnv
 
TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() {}
 
TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv() {}
// XCommandEnvironment
 
uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler()
{
    return this;
}
 
uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; }
 
// XInteractionHandler
void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest)
{
    OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
 
    bool approve = true;
 
    // select:
    for (const auto& cont : xRequest->getContinuations())
    {
        if (approve)
        {
            uno::Reference<task::XInteractionApprove> xInteractionApprove(cont, uno::UNO_QUERY);
            if (xInteractionApprove.is())
            {
                xInteractionApprove->select();
                // don't query again for ongoing continuations:
                approve = false;
            }
        }
    }
}
 
// XProgressHandler
void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
 
void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
 
void TmpRepositoryCommandEnv::pop() {}
 
IMPL_LINK(AdditionsDialog, GearHdl, const OUString&, rIdent, void)
{
    if (rIdent == "gear_sort_voting")
    {
        std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
    }
    else if (rIdent == "gear_sort_comments")
    {
        std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
    }
    else if (rIdent == "gear_sort_downloads")
    {
        std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
    }
    // After the sorting, UI will be refreshed to update extension list.
    RefreshUI();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'ImportGraphic' is required to be utilized.

V1053 Calling the 'getDialog' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'AdditionsDialog.cxx:482', 'weld.hxx:2691', 'weld.hxx:2708'.