Skip to content

Add update filter lists button #24828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@ import SwiftUI
/// A view showing enabled and disabled community filter lists
struct FilterListsView: View {
private static let dateFormatter = RelativeDateTimeFormatter()
enum FilterListUpdateStatus: Equatable {
case unknown
case updating
case updated

var label: String {
switch self {
case .unknown: Strings.Shields.updateLists
case .updated: Strings.Shields.listsUpdated
case .updating: Strings.Shields.updatingLists
}
}

var braveImageName: String {
switch self {
case .unknown, .updating: "leo.refresh"
case .updated: "leo.check.circle-outline"
}
}

var forgroundColor: Color {
switch self {
case .unknown: Color(.braveBlurpleTint)
case .updated: Color(.braveSuccessLabel)
case .updating: Color(.braveDisabled)
}
}
}

@ObservedObject private var filterListStorage = FilterListStorage.shared
@ObservedObject private var customFilterListStorage = CustomFilterListStorage.shared
Expand All @@ -22,10 +50,31 @@ struct FilterListsView: View {
@State private var showingCustomFiltersSheet = false
@State private var customRules: String?
@State private var rulesError: Error?
@State private var filterListsUpdateStatus = FilterListUpdateStatus.unknown
@State private var customFilterListsUpdateStatus = FilterListUpdateStatus.unknown
@State private var customFilterListsUpdateError: Error? = nil

var body: some View {
List {
Section {
if !customFilterListStorage.filterListsURLs.isEmpty {
updateFilterListsButton(
status: customFilterListsUpdateStatus,
error: customFilterListsUpdateError
) {
customFilterListsUpdateStatus = .updating
customFilterListsUpdateError = nil
Task {
do {
try await FilterListCustomURLDownloader.shared.updateFilterLists()
customFilterListsUpdateStatus = .updated
} catch {
customFilterListsUpdateError = error
customFilterListsUpdateStatus = .unknown
}
}
}
}
externalFilterListRows
} header: {
VStack(alignment: .leading, spacing: 4) {
Expand All @@ -51,6 +100,13 @@ struct FilterListsView: View {
.listRowBackground(Color(.secondaryBraveGroupedBackground))

Section {
updateFilterListsButton(status: filterListsUpdateStatus, error: nil) {
filterListsUpdateStatus = .updating
Task {
await updateFilterLists()
filterListsUpdateStatus = .updated
}
}
defaultFilterListRows
} header: {
VStack(alignment: .leading, spacing: 4) {
Expand All @@ -71,7 +127,8 @@ struct FilterListsView: View {
)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.animation(.default, value: customFilterListStorage.filterListsURLs)
.listBackgroundColor(Color(UIColor.braveGroupedBackground))
.scrollContentBackground(.hidden)
.background(Color(UIColor.braveGroupedBackground))
.listStyle(.insetGrouped)
.navigationTitle(Strings.Shields.contentFiltering)
.toolbar {
Expand All @@ -84,6 +141,32 @@ struct FilterListsView: View {
}
}

private func updateFilterListsButton(
status: FilterListUpdateStatus,
error: Error?,
action: @escaping () -> Void
) -> some View {
VStack(alignment: .leading) {
Button(
action: action,
label: {
Label(
status.label,
braveSystemImage: status.braveImageName
)
}
)
.labelStyle(.titleAndIcon)
.foregroundStyle(status.forgroundColor)
.disabled(status == .updating)
if let error {
Text(error.localizedDescription)
.foregroundStyle(Color(.braveErrorLabel))
.font(.caption)
}
}
}

private var customFiltersAccessibilityLabel: Text {
if let customRules = customRules {
Text(customRules)
Expand Down Expand Up @@ -182,7 +265,7 @@ struct FilterListsView: View {
case .failure:
Text(Strings.Shields.filterListsDownloadFailed)
.font(.caption)
.foregroundColor(.red)
.foregroundColor(Color(.braveErrorLabel))
case .pending:
Text(Strings.Shields.filterListsDownloadPending)
.font(.caption)
Expand Down Expand Up @@ -265,6 +348,12 @@ struct FilterListsView: View {
customRules = nil
}
}

private func updateFilterLists() async {
_ = await FilterListStorage.shared.updateFilterLists()
await AdblockResourceDownloader.shared.updateResources()
await AdBlockGroupsManager.shared.compileEngines()
}
}

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,24 @@ import os
}
}

/// This will compile available data right away if it is needed
func compileAvailableEngines(
for enabledSources: [GroupedAdBlockEngine.Source],
resourcesInfo: GroupedAdBlockEngine.ResourcesInfo?
) async {
do {
let compilableFiles = compilableFiles(for: enabledSources)
try await compileAvailableEngines(
for: compilableFiles,
resourcesInfo: resourcesInfo
)
} catch {
ContentBlockerManager.log.error(
"Failed to compile engine for `\(self.cacheFolderName)`: \(String(describing: error))"
)
}
}

/// Compile an engine from all available data
private func compileAvailableEngines(
for files: [AdBlockEngineManager.FileInfo],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,18 @@ import os
}
}

/// Ensure all engines are compiled right away.
func compileEngines() async {
await GroupedAdBlockEngine.EngineType.allCases.asyncConcurrentForEach { engineType in
let enabledSources = self.sourceProvider.enabledSources(for: engineType)
let manager = self.getManager(for: engineType)
await manager.compileAvailableEngines(
for: enabledSources,
resourcesInfo: self.resourcesInfo
)
}
}

/// Get all required rule lists for the given domain
public func ruleLists(for domain: Domain) async -> Set<WKContentRuleList> {
let validBlocklistTypes = self.validBlocklistTypes(for: domain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ public actor AdblockResourceDownloader: Sendable {
}
}

/// Perform a one time force update of all the resources
public func updateResources() async {
await Self.handledResources.asyncConcurrentForEach { resource in
do {
let result = try await self.resourceDownloader.download(resource: resource, force: true)
await self.handle(
downloadResult: result,
for: resource,
allowedModes: Set(ContentBlockerManager.BlockingMode.allCases)
)
} catch {
ContentBlockerManager.log.error(
"Failed to fetch resource `\(resource.cacheFileName)`: \(error.localizedDescription)"
)
}
}
}

/// Start fetching the given resource at regular intervals
private func startFetching(resource: BraveS3Resource, every fetchInterval: TimeInterval) {
Task { @MainActor in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ import Foundation
}
}

/// Perform a one time force update of all the filster lists
func updateFilterLists() async throws {
try await CustomFilterListStorage.shared.filterListsURLs.asyncForEach { customURL in
let result = try await self.resourceDownloader.download(
resource: customURL.setting.resource,
force: true
)
self.handle(downloadResult: result, for: customURL)
}
}

/// Handle the download results of a custom filter list. This will process the download by compiling iOS rule lists and adding the rule list to the `AdBlockEngineManager`.
private func handle(
downloadResult: ResourceDownloader<DownloadResource>.DownloadResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,27 +139,6 @@ extension AdblockService {
})
}
}

/// Get a list of files for the given engine type if the path exists
@MainActor fileprivate func fileInfos(
for engineType: GroupedAdBlockEngine.EngineType
) -> [AdBlockEngineManager.FileInfo] {
return filterListCatalogEntries.compactMap { entry in
let source = entry.engineSource
guard entry.engineType == engineType || source.onlyExceptions(for: engineType) else {
return nil
}

guard
let localFileURL = installPath(forFilterListUUID: entry.uuid),
FileManager.default.fileExists(atPath: localFileURL.relativePath)
else {
return nil
}

return entry.fileInfo(for: localFileURL)
}
}
}

extension AdblockFilterListCatalogEntry {
Expand Down
42 changes: 42 additions & 0 deletions ios/brave-ios/Sources/Brave/WebFilters/FilterListStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ import Preferences
}
}

func updateFilterLists() async -> Bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will run on main, is that ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be fine. The method does this:

DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay then shit will require it always run on main then unless you use a task runner on the core side, if the @MainActor is ever removed from the parent type this will need to make sure its marked as MainActor again

return await withCheckedContinuation { continuation in
guard let adBlockService else {
continuation.resume(returning: false)
return
}

adBlockService.updateFilterLists({ updated in
guard updated else {
continuation.resume(returning: updated)
return
}
for engineType in GroupedAdBlockEngine.EngineType.allCases {
let fileInfos = adBlockService.fileInfos(for: engineType)
AdBlockGroupsManager.shared.update(fileInfos: fileInfos)
}
continuation.resume(returning: updated)
})
}
}

/// Load the filter list settings
private func loadFilterListSettings() {
allFilterListSettings = FilterListSetting.loadAllSettings(fromMemory: !persistChanges)
Expand Down Expand Up @@ -334,6 +355,27 @@ extension AdblockService {
})
}
}

/// Get a list of files for the given engine type if the path exists
@MainActor func fileInfos(
for engineType: GroupedAdBlockEngine.EngineType
) -> [AdBlockEngineManager.FileInfo] {
return filterListCatalogEntries.compactMap { entry in
let source = entry.engineSource
guard entry.engineType == engineType || source.onlyExceptions(for: engineType) else {
return nil
}

guard
let localFileURL = installPath(forFilterListUUID: entry.uuid),
FileManager.default.fileExists(atPath: localFileURL.relativePath)
else {
return nil
}

return entry.fileInfo(for: localFileURL)
}
}
}

extension FilterListSetting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ actor ResourceDownloader<Resource: DownloadResourceInterface>: Sendable {

/// Download the give resource type for the filter list and store it into the cache folder url
@discardableResult
func download(resource: Resource) async throws -> DownloadResult {
let result = try await downloadInternal(resource: resource)
func download(resource: Resource, force: Bool = false) async throws -> DownloadResult {
let result = try await downloadInternal(resource: resource, force: force)

switch result {
case .downloaded(let networkResource, let date):
Expand Down Expand Up @@ -90,8 +90,11 @@ actor ResourceDownloader<Resource: DownloadResourceInterface>: Sendable {
}
}

private func downloadInternal(resource: Resource) async throws -> DownloadResultStatus {
let etag = try? await resource.createdEtag()
private func downloadInternal(
resource: Resource,
force: Bool = false
) async throws -> DownloadResultStatus {
let etag = force ? nil : try? await resource.createdEtag()

do {
let networkResource = try await self.networkManager.downloadResource(
Expand Down
23 changes: 23 additions & 0 deletions ios/brave-ios/Sources/BraveShields/ShieldStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,29 @@ extension Strings.Shields {
comment:
"This is an error message when a user tries to enter a non-https scheme URL into the 'add custom filter list URL' input field"
)
public static let updateLists = NSLocalizedString(
"UpdateLists",
tableName: "BraveShared",
bundle: .module,
value: "Update Lists",
comment: "This is a label for a button which when pressed updates all the filter lists"
)
public static let updatingLists = NSLocalizedString(
"UpdatingLists",
tableName: "BraveShared",
bundle: .module,
value: "Updating Lists",
comment:
"This is a label on a button that updates filter lists which signifies that lista are being updated"
)
public static let listsUpdated = NSLocalizedString(
"ListsUpdated",
tableName: "BraveShared",
bundle: .module,
value: "Lists Updated",
comment:
"This is a label on a button that updates filter lists which signifies that lists have been updated"
)
}

// MARK: - Create custom filters
Expand Down
3 changes: 3 additions & 0 deletions ios/browser/api/brave_shields/adblock_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ OBJC_EXPORT
/// Listen to downloaded version changes of resources
- (void)registerResourcesChanges:(void (^)(NSString* resourcesJSON))callback;

/// Update the filter lists
- (void)updateFilterLists:(void (^)(bool))callback;

- (instancetype)init NS_UNAVAILABLE;

@end
Expand Down
4 changes: 4 additions & 0 deletions ios/browser/api/brave_shields/adblock_service.mm
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,8 @@ - (bool)isFilterListEnabledForUUID:(NSString*)uuid {
return _serviceManager->IsFilterListEnabled(base::SysNSStringToUTF8(uuid));
}

- (void)updateFilterLists:(void (^)(bool))callback {
_serviceManager->UpdateFilterLists(base::BindOnce(callback));
}

@end
Loading