Newer
Older
minerva / Userland / Applications / BrowserSettings / ContentFilterSettingsWidget.cpp
@minerva minerva on 13 Jul 5 KB Initial commit
/*
 * Copyright (c) 2022, Maciej Zygmanowski <sppmacd@pm.me>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include "ContentFilterSettingsWidget.h"

#include <AK/NonnullRefPtr.h>
#include <AK/String.h>
#include <Applications/BrowserSettings/Defaults.h>
#include <LibConfig/Client.h>
#include <LibCore/StandardPaths.h>
#include <LibGUI/CheckBox.h>
#include <LibGUI/Event.h>
#include <LibGUI/Forward.h>
#include <LibGUI/InputBox.h>
#include <LibGUI/ListView.h>
#include <LibGUI/Menu.h>

ErrorOr<String> DomainListModel::filter_list_file_path() const
{
    return String::formatted("{}/BrowserContentFilters.txt", Core::StandardPaths::config_directory());
}

ErrorOr<void> DomainListModel::load()
{
    // FIXME: This should be somewhat shared with Browser.
    auto file = TRY(Core::File::open(TRY(filter_list_file_path()), Core::File::OpenMode::Read));
    auto content_filter_list = TRY(Core::InputBufferedFile::create(move(file)));
    auto buffer = TRY(ByteBuffer::create_uninitialized(4096));

    m_domain_list.clear_with_capacity();

    while (TRY(content_filter_list->can_read_line())) {
        auto line = TRY(content_filter_list->read_line(buffer));
        if (line.is_empty())
            continue;

        auto pattern = TRY(String::from_utf8(line));
        TRY(m_domain_list.try_append(move(pattern)));
    }

    return {};
}

ErrorOr<void> DomainListModel::save()
{
    if (!m_was_modified)
        return {};
    m_was_modified = false;

    StringBuilder builder;
    for (auto const& domain : m_domain_list)
        TRY(builder.try_appendff("{}\n", domain));

    auto file = TRY(Core::File::open(TRY(filter_list_file_path()), Core::File::OpenMode::Write));
    TRY(file->write_until_depleted(TRY(builder.to_byte_buffer()).bytes()));
    return {};
}

void DomainListModel::add_domain(String name)
{
    begin_insert_rows({}, m_domain_list.size(), m_domain_list.size());
    m_domain_list.append(move(name));
    end_insert_rows();
    m_was_modified = true;
    did_update(UpdateFlag::DontInvalidateIndices);
}

void DomainListModel::delete_domain(size_t index)
{
    begin_delete_rows({}, index, index);
    m_domain_list.remove(index);
    end_delete_rows();
    m_was_modified = true;
    did_update(UpdateFlag::DontInvalidateIndices);
}

void DomainListModel::reset_default_values()
{
    // FIXME: This probably should not be hardcoded.
    static constexpr Array default_domain_list {
        "207.net"sv,
        "247realmedia.com"sv,
        "2o7.net"sv,
        "adbrite.com"sv,
        "admob.com"sv,
        "adthis.com"sv,
        "advertising.com"sv,
        "aquantive.com"sv,
        "atwola.com"sv,
        "channelintelligence.com"sv,
        "doubleclick.com"sv,
        "doubleclick.net"sv,
        "esomniture.com"sv,
        "google-analytics.com"sv,
        "googleadservices.com"sv,
        "googlesyndication.com"sv,
        "gravity.com"sv,
        "hitbox.com"sv,
        "intellitxt.com"sv,
        "nielsen-online.com"sv,
        "omniture.com"sv,
        "quantcast.com"sv,
        "quantserve.com"sv,
        "scorecardresearch.com"sv,
    };

    m_domain_list.clear_with_capacity();
    for (auto domain : default_domain_list)
        m_domain_list.append(String::from_utf8(domain).release_value_but_fixme_should_propagate_errors());

    m_was_modified = true;
    did_update(UpdateFlag::InvalidateAllIndices);
}

namespace BrowserSettings {

ErrorOr<void> ContentFilterSettingsWidget::initialize()
{
    auto domain_list_model = TRY(try_make_ref_counted<DomainListModel>());
    TRY(domain_list_model->load());

    set_domain_list_model(move(domain_list_model));

    m_enable_content_filtering_checkbox = find_descendant_of_type_named<GUI::CheckBox>("enable_content_filtering_checkbox");
    m_enable_content_filtering_checkbox->set_checked(Config::read_bool("Browser"sv, "Preferences"sv, "EnableContentFilters"sv, Browser::default_enable_content_filters), GUI::AllowCallback::No);
    m_enable_content_filtering_checkbox->on_checked = [this](auto) {
        set_modified(true);
    };

    m_domain_list_view = find_descendant_of_type_named<GUI::ListView>("domain_list_view");
    m_domain_list_view->set_model(m_domain_list_model);
    m_domain_list_view->on_context_menu_request = [this](GUI::ModelIndex const& index, GUI::ContextMenuEvent const& event) {
        m_domain_list_view->set_cursor(index, GUI::AbstractView::SelectionUpdate::Set);
        m_entry_context_menu->popup(event.screen_position());
    };

    m_add_new_domain_button = find_descendant_of_type_named<GUI::Button>("add_new_domain_button");
    m_add_new_domain_button->on_click = [this](unsigned) {
        String text;

        if (GUI::InputBox::show(window(), text, "Enter a domain:"sv, "Add Content Filter"sv, GUI::InputType::NonemptyText) == GUI::Dialog::ExecResult::OK) {
            m_domain_list_model->add_domain(move(text));
            set_modified(true);
        }
    };

    auto delete_action = GUI::CommonActions::make_delete_action([this](GUI::Action const&) {
        if (!m_domain_list_view->selection().is_empty()) {
            m_domain_list_model->delete_domain(m_domain_list_view->selection().first().row());
            set_modified(true);
        }
    });
    m_entry_context_menu = GUI::Menu::construct();
    m_entry_context_menu->add_action(delete_action);

    return {};
}

void ContentFilterSettingsWidget::set_domain_list_model(NonnullRefPtr<DomainListModel> domain_list_model)
{
    m_domain_list_model = move(domain_list_model);
}

void ContentFilterSettingsWidget::apply_settings()
{
    m_domain_list_model->save().release_value_but_fixme_should_propagate_errors();
    Config::write_bool("Browser"sv, "Preferences"sv, "EnableContentFilters"sv, m_enable_content_filtering_checkbox->is_checked());
}

void ContentFilterSettingsWidget::reset_default_values()
{
    m_domain_list_model->reset_default_values();
    m_enable_content_filtering_checkbox->set_checked(Browser::default_enable_content_filters);
}

}