This repository has been archived by the owner on May 10, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 445
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
560 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
254 changes: 254 additions & 0 deletions
254
Sources/Brave/WebFilters/Debug/FilterListDetailsToolsView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
// | ||
// SwiftUIView.swift | ||
// | ||
// | ||
// Created by Jacob on 2023-10-16. | ||
// | ||
|
||
import SwiftUI | ||
import BraveCore | ||
|
||
@available(iOS 16.0, *) | ||
struct FilterListDetailsToolsView: View { | ||
struct FilterListDetails { | ||
let source: CachedAdBlockEngine.Source | ||
let title: String | ||
let externalURL: String? | ||
let lazyInfo: AdBlockStats.LazyFilterListInfo? | ||
var compiledInfo: CachedAdBlockEngine.FilterListInfo? | ||
var compiledResource: CachedAdBlockEngine.ResourcesInfo? | ||
var customRules: String | ||
} | ||
|
||
@Binding var details: FilterListDetails | ||
@State var rulesError: Error? | ||
@State var isRecompiled = false | ||
@State var isCopied = false | ||
@State var isResourceCopied = false | ||
@Binding var customRules: String | ||
|
||
var isSaved: Bool { | ||
return details.customRules == customRules | ||
} | ||
|
||
var body: some View { | ||
List { | ||
if let lazyInfo = details.lazyInfo { | ||
Section("File") { | ||
VStack(alignment: .leading) { | ||
HStack { | ||
Label("Rules", systemImage: "doc.plaintext") | ||
HStack { | ||
Text("v") + Text(lazyInfo.filterListInfo.version) | ||
}.foregroundStyle(.secondary) | ||
|
||
Spacer() | ||
|
||
if isCopied { | ||
Image(systemName: "checkmark") | ||
.foregroundStyle(.green) | ||
} else { | ||
Button("Copy", systemImage: "doc.on.clipboard") { | ||
UIPasteboard.general.string = lazyInfo.filterListInfo.localFileURL.path(percentEncoded: false) | ||
isCopied = true | ||
|
||
Task { | ||
try await Task.sleep(for: .seconds(2)) | ||
isCopied = false | ||
} | ||
} | ||
.labelStyle(.iconOnly) | ||
.buttonStyle(.borderedProminent) | ||
.foregroundStyle(.white) | ||
.font(.caption) | ||
} | ||
} | ||
|
||
Text(lazyInfo.filterListInfo.localFileURL.path(percentEncoded: false)) | ||
.foregroundStyle(.secondary) | ||
.font(.caption) | ||
} | ||
} | ||
} | ||
|
||
if let compiledInfo = details.compiledInfo { | ||
Section("Engine") { | ||
HStack { | ||
Label("Version", systemImage: "hammer") | ||
HStack { | ||
Text("v") + Text(compiledInfo.version) | ||
}.foregroundStyle(.secondary) | ||
} | ||
|
||
if let version = details.compiledResource?.version { | ||
HStack { | ||
Label("Resources", systemImage: "doc.plaintext") | ||
HStack { | ||
Text("v") + Text(version) | ||
}.foregroundStyle(.secondary) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if details.lazyInfo?.filterListInfo.fileType == .text { | ||
Section { | ||
TextEditor(text: $customRules) | ||
.frame(height: 300) | ||
if let error = rulesError { | ||
Text(String(describing: error)) | ||
.foregroundStyle(.red) | ||
} | ||
} header: { | ||
HStack { | ||
Text("Additional rules") | ||
.frame(maxWidth: .infinity, alignment: .leading) | ||
if isSaved { | ||
Image(systemName: "checkmark") | ||
.foregroundStyle(.green) | ||
} else { | ||
Button("Save", systemImage: "arrow.down.doc") { | ||
saveCustomRules(for: details.source) | ||
} | ||
.buttonStyle(.borderedProminent) | ||
.foregroundStyle(.white) | ||
.font(.caption) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
.toolbar { | ||
ToolbarItem(placement: .automatic) { | ||
Button("Load", systemImage: "hammer") { | ||
guard let lazyInfo = details.lazyInfo else { return } | ||
saveCustomRules(for: details.source) | ||
isRecompiled = true | ||
|
||
Task { | ||
guard let resourcesInfo = await AdBlockStats.shared.resourcesInfo else { return } | ||
|
||
await AdBlockStats.shared.compile( | ||
filterListInfo: lazyInfo.filterListInfo, | ||
resourcesInfo: resourcesInfo, | ||
isAlwaysAggressive: lazyInfo.isAlwaysAggressive, | ||
force: true | ||
) | ||
|
||
details.compiledInfo = await AdBlockStats.shared.cachedEngines[details.source]?.filterListInfo | ||
details.compiledResource = await AdBlockStats.shared.cachedEngines[details.source]?.resourcesInfo | ||
|
||
try await Task.sleep(for: .seconds(2)) | ||
isRecompiled = false | ||
} | ||
} | ||
.buttonStyle(.borderedProminent) | ||
.foregroundStyle(.white) | ||
.font(.caption) | ||
} | ||
} | ||
.onChange(of: details.source, perform: { value in | ||
self.isCopied = false | ||
self.isRecompiled = false | ||
}) | ||
.navigationTitle(details.title) | ||
.onDisappear(perform: { | ||
saveCustomRules(for: details.source) | ||
}) | ||
} | ||
|
||
private func saveCustomRules(for source: CachedAdBlockEngine.Source) { | ||
do { | ||
if !customRules.isEmpty { | ||
_ = try AdblockEngine(rules: customRules) | ||
try source.save(additionalRules: customRules) | ||
} else { | ||
try source.deleteAdditionalRules() | ||
} | ||
|
||
details.customRules = customRules | ||
} catch { | ||
self.rulesError = error | ||
} | ||
} | ||
|
||
} | ||
|
||
#if swift(>=5.9) | ||
@available(iOS 16.0, *) | ||
#Preview { | ||
FilterListDetailsToolsView( | ||
details: .constant(FilterListDetailsToolsView.FilterListDetails( | ||
source: .adBlock, title: "AdBlock", externalURL: nil, lazyInfo: nil, | ||
compiledInfo: nil, compiledResource: nil, customRules: "" | ||
)), | ||
customRules: .constant("") | ||
) | ||
} | ||
#endif | ||
|
||
extension CachedAdBlockEngine.Source { | ||
/// The directory to which we should store our debug files into | ||
private static var debugFolderDirectory: FileManager.SearchPathDirectory { | ||
return FileManager.SearchPathDirectory.applicationSupportDirectory | ||
} | ||
|
||
/// The file to save additional rules to | ||
private var debugFolderName: String { | ||
switch self { | ||
case .adBlock: return "debug/ad-block" | ||
case .filterListURL(let uuid): return "debug/\(uuid)" | ||
case .filterList(let componentId): return "debug/\(componentId)" | ||
} | ||
} | ||
|
||
private var debugFolderURL: URL? { | ||
guard let folderURL = Self.debugFolderDirectory.url else { return nil } | ||
return folderURL.appendingPathComponent(debugFolderName) | ||
} | ||
|
||
/// Get the cache folder for this resource | ||
/// | ||
/// - Note: Returns nil if the cache folder does not exist | ||
private var createdDebugFolderURL: URL? { | ||
guard let folderURL = debugFolderURL else { return nil } | ||
|
||
if FileManager.default.fileExists(atPath: folderURL.path) { | ||
return folderURL | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
func loadAdditionalRules() throws -> String? { | ||
guard let folderURL = createdDebugFolderURL else { return nil } | ||
let fileURL = folderURL.appendingPathComponent("additional-rules", conformingTo: .text) | ||
guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil } | ||
return try String(contentsOf: fileURL, encoding: .utf8) | ||
} | ||
|
||
func save(additionalRules: String) throws { | ||
let folderURL = try getOrCreateDebugFolder() | ||
let fileURL = folderURL.appendingPathComponent("additional-rules", conformingTo: .text) | ||
try additionalRules.write(to: fileURL, atomically: true, encoding: .utf8) | ||
} | ||
|
||
func deleteAdditionalRules() throws { | ||
guard let folderURL = createdDebugFolderURL else { return } | ||
let fileURL = folderURL.appendingPathComponent("additional-rules", conformingTo: .text) | ||
guard FileManager.default.fileExists(atPath: fileURL.path) else { return } | ||
try FileManager.default.removeItem(at: fileURL) | ||
} | ||
|
||
/// Get or create a debug folder for this filter list | ||
private func getOrCreateDebugFolder() throws -> URL { | ||
guard let folderURL = FileManager.default.getOrCreateFolder( | ||
name: debugFolderName, | ||
location: Self.debugFolderDirectory | ||
) else { | ||
throw ResourceFileError.failedToCreateCacheFolder | ||
} | ||
|
||
return folderURL | ||
} | ||
} |
Oops, something went wrong.