Skip to content

Commit c817abc

Browse files
committed
Abstraction and document comments
1 parent de3df50 commit c817abc

17 files changed

Lines changed: 654 additions & 408 deletions
Lines changed: 41 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,81 @@
11
package io.github.edsuns.adfilter
22

33
import android.content.Context
4-
import android.net.Uri
54
import android.webkit.WebResourceRequest
6-
import android.webkit.WebResourceResponse
75
import android.webkit.WebView
8-
import androidx.lifecycle.MutableLiveData
9-
import androidx.work.WorkInfo
6+
import android.webkit.WebViewClient
107
import io.github.edsuns.adblockclient.ResourceType
11-
import io.github.edsuns.adfilter.script.ElementHiding
12-
import io.github.edsuns.adfilter.script.ScriptInjection
13-
import io.github.edsuns.adfilter.script.Scriptlet
14-
import io.github.edsuns.adfilter.util.None
15-
import kotlinx.coroutines.Dispatchers
16-
import kotlinx.coroutines.runBlocking
17-
import kotlinx.coroutines.withContext
18-
import java.io.File
8+
import io.github.edsuns.adfilter.impl.AdFilterImpl
199

2010
/**
2111
* Created by Edsuns@qq.com on 2020/10/24.
2212
*/
23-
class AdFilter internal constructor(appContext: Context) {
13+
interface AdFilter {
2414

25-
private val detector: Detector = Detector()
26-
internal val binaryDataStore: BinaryDataStore =
27-
BinaryDataStore(File(appContext.filesDir, FILE_STORE_DIR))
28-
private val filterDataLoader: FilterDataLoader = FilterDataLoader(detector, binaryDataStore)
29-
private val elementHiding: ElementHiding = ElementHiding(detector)
30-
private val scriptlet: Scriptlet = Scriptlet(detector)
31-
val customFilter = filterDataLoader.getCustomFilter()
32-
val viewModel = FilterViewModel(appContext, filterDataLoader)
15+
/**
16+
* View model of AdFilter.
17+
*/
18+
val viewModel: FilterViewModel
3319

20+
/**
21+
* Whether any filters have been added since the app was installed.
22+
*/
3423
val hasInstallation: Boolean
35-
get() = viewModel.sharedPreferences.hasInstallation
36-
37-
init {
38-
viewModel.isEnabled.observeForever { enable ->
39-
if (enable) {
40-
viewModel.filters.value?.values?.forEach {
41-
if (it.isEnabled && it.hasDownloaded()) {
42-
viewModel.enableFilter(it)
43-
}
44-
}
45-
filterDataLoader.load(FilterDataLoader.ID_CUSTOM)
46-
} else {
47-
filterDataLoader.unloadAll()
48-
filterDataLoader.unloadCustomFilter()
49-
}
50-
viewModel.updateEnabledFilterCount()
51-
viewModel.sharedPreferences.isEnabled = enable
52-
// notify onDirty
53-
(viewModel.onDirty as MutableLiveData).value = None.Value
54-
}
55-
viewModel.workInfo.observeForever { list -> processWorkInfo(list) }
56-
}
57-
58-
private fun processWorkInfo(workInfoList: List<WorkInfo>) {
59-
workInfoList.forEach { workInfo ->
60-
val filterId = viewModel.downloadFilterIdMap[workInfo.id.toString()]
61-
viewModel.filters.value?.get(filterId)?.let {
62-
updateFilter(it, workInfo)
63-
}
64-
}
65-
}
66-
67-
private fun updateFilter(filter: Filter, workInfo: WorkInfo) {
68-
val state = workInfo.state
69-
val isInstallation = workInfo.tags.contains(TAG_INSTALLATION)
70-
var downloadState = filter.downloadState
71-
if (isInstallation) {
72-
downloadState =
73-
when (state) {
74-
WorkInfo.State.RUNNING -> DownloadState.INSTALLING
75-
WorkInfo.State.SUCCEEDED -> {
76-
val alreadyUpToDate =
77-
workInfo.outputData.getBoolean(KEY_ALREADY_UP_TO_DATE, false)
78-
if (!alreadyUpToDate) {
79-
filter.filtersCount =
80-
workInfo.outputData.getInt(KEY_FILTERS_COUNT, 0)
81-
workInfo.outputData.getString(KEY_RAW_CHECKSUM)
82-
?.let { filter.checksum = it }
83-
if (filter.isEnabled || !filter.hasDownloaded()) {
84-
viewModel.enableFilter(filter)
85-
}
86-
}
87-
if (filter.name.isBlank()) {
88-
workInfo.outputData.getString(KEY_FILTER_NAME)
89-
?.let { filter.name = it }
90-
}
91-
filter.updateTime = System.currentTimeMillis()
92-
DownloadState.SUCCESS
93-
}
94-
WorkInfo.State.FAILED -> DownloadState.FAILED
95-
WorkInfo.State.CANCELLED -> DownloadState.CANCELLED
96-
else -> downloadState
97-
}
98-
} else {
99-
downloadState = when (state) {
100-
WorkInfo.State.ENQUEUED -> DownloadState.ENQUEUED
101-
WorkInfo.State.RUNNING -> DownloadState.DOWNLOADING
102-
WorkInfo.State.FAILED -> DownloadState.FAILED
103-
else -> downloadState
104-
}
105-
}
106-
if (state.isFinished) {
107-
viewModel.downloadFilterIdMap.remove(workInfo.id.toString())
108-
// save shared preferences
109-
viewModel.sharedPreferences.downloadFilterIdMap = viewModel.downloadFilterIdMap
110-
// notify download work removed
111-
(viewModel.workToFilterMap as MutableLiveData).postValue(viewModel.downloadFilterIdMap)
112-
}
113-
if (downloadState != filter.downloadState) {
114-
filter.downloadState = downloadState
115-
viewModel.flushFilter()
116-
}
117-
}
11824

11925
/**
120-
* Notify the application of a resource request and allow the application to return the data.
121-
*
122-
* If the return value is null, the WebView will continue to load the resource as usual.
123-
* Otherwise, the return response and data will be used.
124-
*
125-
* NOTE: This method is called on a thread other than the UI thread so clients should exercise
126-
* caution when accessing private data or the view system.
26+
* Custom filter.
12727
*/
128-
fun shouldIntercept(
129-
webView: WebView,
130-
request: WebResourceRequest
131-
): FilterResult = runBlocking {
132-
val url = request.url.toString()
133-
if (request.isForMainFrame) {
134-
return@runBlocking FilterResult(null, url, null)
135-
}
136-
137-
val documentUrl = withContext(Dispatchers.Main) { webView.url }
138-
?: return@runBlocking FilterResult(null, url, null)
139-
140-
val resourceType = ResourceType.from(request)
28+
val customFilter: CustomFilter
14129

142-
val result = shouldIntercept(url, documentUrl, resourceType)
143-
if (result.shouldBlock && resourceType.isVisibleResource()) {
144-
elementHiding.elemhideBlockedResource(webView, url)
145-
}
146-
147-
return@runBlocking result
148-
}
30+
/**
31+
* Call this function when [WebViewClient.shouldInterceptRequest],
32+
* and use [FilterResult.resourceResponse] as return value.
33+
*/
34+
fun shouldIntercept(webView: WebView, request: WebResourceRequest): FilterResult
14935

36+
/**
37+
* @param url url of the request
38+
* @param documentUrl the origin of the request
39+
* @param resourceType [ResourceType]
40+
*/
15041
fun shouldIntercept(
15142
url: String,
15243
documentUrl: String = url,
15344
resourceType: ResourceType? = null
154-
): FilterResult {
155-
val type = resourceType ?: ResourceType.from(Uri.parse(url)) ?: ResourceType.UNKNOWN
156-
val rule = detector.shouldBlock(url, documentUrl, type)
157-
158-
return if (rule != null) {
159-
FilterResult(rule, url, WebResourceResponse(null, null, null))
160-
} else {
161-
FilterResult(null, url, null)
162-
}
163-
}
164-
165-
private fun ResourceType.isVisibleResource(): Boolean =
166-
this === ResourceType.IMAGE || this === ResourceType.MEDIA || this === ResourceType.SUBDOCUMENT
45+
): FilterResult
16746

168-
fun setupWebView(webView: WebView) {
169-
webView.addJavascriptInterface(elementHiding, ScriptInjection.bridgeNameFor(elementHiding))
170-
webView.addJavascriptInterface(scriptlet, ScriptInjection.bridgeNameFor(scriptlet))
171-
}
47+
/**
48+
* Call this function when [WebViewClient.onPageStarted], it will run the filter script.
49+
*/
50+
fun performScript(webView: WebView?, url: String?)
17251

173-
fun performScript(webView: WebView?, url: String?) {
174-
if (viewModel.isEnabled.value == true) {
175-
elementHiding.perform(webView, url)
176-
scriptlet.perform(webView, url)
177-
}
178-
}
52+
/**
53+
* Call this function when [webView] is created to setup filter on it.
54+
*/
55+
fun setupWebView(webView: WebView)
17956

18057
companion object {
18158
@Volatile
18259
private var instance: AdFilter? = null
18360

61+
/**
62+
* @return [AdFilter] singleton (if it is not instantiated, an exception is thrown)
63+
*/
18464
fun get(): AdFilter =
18565
instance ?: throw RuntimeException("Should call create() before get()")
18666

67+
/**
68+
* @return [AdFilter] singleton (if it is not instantiated, singleton will be created)
69+
*/
18770
fun get(context: Context): AdFilter = instance ?: synchronized(this) {
18871
// keep application context rather than any other context to avoid memory leak
189-
instance = instance ?: AdFilter(context.applicationContext)
72+
instance = instance ?: AdFilterImpl(context.applicationContext)
19073
instance!!
19174
}
19275

76+
/**
77+
* Instantiate [AdFilter] singleton.
78+
*/
19379
fun create(context: Context): AdFilter = get(context)
19480
}
19581
}

ad-filter/src/main/java/io/github/edsuns/adfilter/Constants.kt

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,51 @@
11
package io.github.edsuns.adfilter
22

3-
import io.github.edsuns.adfilter.util.RuleIterator
4-
53
/**
64
* Created by Edsuns@qq.com on 2021/1/24.
75
*/
8-
class CustomFilter internal constructor(
9-
private val filterDataLoader: FilterDataLoader,
10-
data: String? = null
11-
) : RuleIterator(data) {
12-
13-
fun flush() {
14-
val blacklistStr = dataBuilder.toString()
15-
if (blacklistStr.isNotBlank()) {
16-
val rawData = blacklistStr.toByteArray()
17-
filterDataLoader.loadCustomFilter(rawData)
18-
}
19-
}
6+
interface CustomFilter : Iterator<String> {
7+
/**
8+
* Get specified line of rule.
9+
*/
10+
fun get(line: Int): String
11+
12+
/**
13+
* @return true if custom contains the [rule]
14+
*/
15+
fun contains(rule: String): Boolean
16+
17+
/**
18+
* Add a new rule.
19+
*/
20+
fun append(rule: String)
21+
22+
/**
23+
* @return true if [rule] is a comment (starts with `! `)
24+
*/
25+
fun isComment(rule: String): Boolean
26+
27+
/**
28+
* Make the existing rule as a comment.
29+
*/
30+
fun comment(rule: String)
31+
32+
/**
33+
* Make the existing comment as a rule.
34+
*/
35+
fun uncomment(rule: String)
36+
37+
/**
38+
* Remove specified rule from custom filter.
39+
*/
40+
fun remove(rule: String)
41+
42+
/**
43+
* @return the number of rules in custom filter
44+
*/
45+
fun size(): Int
46+
47+
/**
48+
* Save the changes.
49+
*/
50+
fun flush()
2051
}

0 commit comments

Comments
 (0)