/* -*- 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/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 MAX_ITEMS_PER_PAGE 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, const AdditionsItem& rCurrentItem)
{
    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);
    Bitmap aBmp = aGraphic.GetBitmap();
    Size aBmpSize = aBmp.GetSizePixel();
    Size aThumbSize(rCurrentItem.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 = rCurrentItem.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->DrawBitmap(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
    rCurrentItem.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;
 
    m_pAdditionsDialog->m_aAdditionsItems.push_back(std::make_unique<AdditionsItem>(
        m_pAdditionsDialog->m_xContentBox.get(), m_pAdditionsDialog, additionInfo));
 
    AdditionsItem& rCurrentItem = *m_pAdditionsDialog->m_aAdditionsItems.back();
 
    LoadImage(aPreviewFile, rCurrentItem);
    m_pAdditionsDialog->m_nCurrentListItemCount++;
 
    if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
    {
        if (m_pAdditionsDialog->m_nCurrentListItemCount
            != m_pAdditionsDialog->m_aAllExtensionsVector.size())
            m_pAdditionsDialog->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_xContentBox(m_xBuilder->weld_box(u"contentBox"_ustr))
    , m_xButtonShowMore(m_xBuilder->weld_button(u"buttonShowMore"_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_xButtonShowMore->connect_clicked(LINK(this, AdditionsDialog, ShowMoreHdl));
    m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
 
    m_nMaxItemCount = MAX_ITEMS_PER_PAGE; // Dialog initialization item count
    m_nCurrentListItemCount = 0; // First, there is no item on the list.
 
    OUString sTag = sAdditionsTag;
    OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
    if (!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);
        }
        set_title(sDialogTitle);
    }
    else
    {
        set_title(titlePrefix);
        sTag = "allextensions"; // Means empty parameter
    }
 
    OUString sEncodedURLPart = INetURLObject::encode(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 : m_aAdditionsItems)
    {
        item->m_xContainer->hide();
    }
    m_aAdditionsItems.clear();
}
 
void AdditionsDialog::RefreshUI()
{
    if (m_pSearchThread.is())
        m_pSearchThread->StopExecution();
    ClearList();
    m_nCurrentListItemCount = 0;
    m_nMaxItemCount = MAX_ITEMS_PER_PAGE;
    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::Box* pParentBox, AdditionsDialog* pParentDialog,
                             const AdditionInfo& additionInfo)
    : m_xBuilder(Application::CreateBuilder(pParentBox, 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_pParentDialog(pParentDialog)
    , m_sDownloadURL(u""_ustr)
    , m_sExtensionID(u""_ustr)
{
    SolarMutexGuard aGuard;
 
    // AdditionsItem set location
    pParentBox->reorder_child(m_xContainer.get(), 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_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();
    response(RET_CLOSE);
}
 
IMPL_LINK_NOARG(AdditionsDialog, ShowMoreHdl, weld::Button&, void)
{
    m_xButtonShowMore->set_visible(false);
    m_nMaxItemCount += MAX_ITEMS_PER_PAGE;
    if (m_pSearchThread.is())
        m_pSearchThread->StopExecution();
    m_pSearchThread = new SearchAndParseThread(this, false);
    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.