Skip to content

Commit 5ea125a

Browse files
committed
Merge branch 'release/5.6.1'
2 parents 1f1b98c + 99b313e commit 5ea125a

File tree

12 files changed

+188
-92
lines changed

12 files changed

+188
-92
lines changed

.travis.yml

Lines changed: 0 additions & 48 deletions
This file was deleted.

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ apply plugin: 'kotlin-kapt'
66
apply from: '../versioning.gradle'
77

88
ext {
9-
VERSION_NAME = "5.6.0"
9+
VERSION_NAME = "5.6.1"
1010
USE_ORCHESTRATOR = project.hasProperty('orchestrator') ? project.property('orchestrator') : false
1111
}
1212

@@ -78,7 +78,7 @@ android {
7878

7979
ext {
8080
supportLibrary = "27.1.1"
81-
architectureComponents = "1.0.0"
81+
architectureComponents = "1.1.0"
8282
architectureComponentsExtensions = "1.1.1"
8383
androidKtx = "0.3"
8484
dagger = "2.14.1"

app/src/androidTest/java/com/duckduckgo/app/httpsupgrade/db/HttpsUpgradeDomainDAOTest.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,44 +51,44 @@ class HttpsUpgradeDomainDaoTest {
5151

5252
@Test
5353
fun whenExactMatchDomainAddedAndThenAllDeletedThenDoesNotContainExactMatchDomain() {
54-
dao.insertAll(HttpsUpgradeDomain(exactMatchDomain))
54+
dao.insertAll(listOf(HttpsUpgradeDomain(exactMatchDomain)))
5555
dao.deleteAll()
5656
assertFalse(dao.hasDomain(exactMatchDomain))
5757
}
5858

5959
@Test
6060
fun whenWildcardDomainInsertedModelThenDoesNotContainParentOfWildcardDomain() {
61-
dao.insertAll(HttpsUpgradeDomain(wildcardDomain))
61+
dao.insertAll(listOf(HttpsUpgradeDomain(wildcardDomain)))
6262
assertFalse(dao.hasDomain(parentOfWildcardDomain))
6363
}
6464

6565
@Test
6666
fun whenOtherWildcardDomainInsertedThenModelDoesNotContainExampleWildcardDomain() {
67-
dao.insertAll(HttpsUpgradeDomain(otherWildcardDomain))
67+
dao.insertAll(listOf(HttpsUpgradeDomain(otherWildcardDomain)))
6868
assertFalse(dao.hasDomain(exampleWildcardDomain))
6969
}
7070

7171
@Test
7272
fun whenWildcardDomainInsertedThenModelDoesNotContainExactMatchDomain() {
73-
dao.insertAll(HttpsUpgradeDomain(wildcardDomain))
73+
dao.insertAll(listOf(HttpsUpgradeDomain(wildcardDomain)))
7474
assertFalse(dao.hasDomain(exactMatchDomain))
7575
}
7676

7777
@Test
7878
fun whenWildcardDomainInsertedThenModelContainsExampleWildcardDomain() {
79-
dao.insertAll(HttpsUpgradeDomain(wildcardDomain))
79+
dao.insertAll(listOf(HttpsUpgradeDomain(wildcardDomain)))
8080
assertTrue(dao.hasDomain("*.$parentOfWildcardDomain"))
8181
}
8282

8383
@Test
8484
fun whenExactMatchDomainInsertedThenModelDoesNotContainOtherDomain() {
85-
dao.insertAll(HttpsUpgradeDomain(exactMatchDomain))
85+
dao.insertAll(listOf(HttpsUpgradeDomain(exactMatchDomain)))
8686
assertFalse(dao.hasDomain(otherDomain))
8787
}
8888

8989
@Test
9090
fun whenExactMatchDomainIsInsertedThenModelContainsExactMatchDomain() {
91-
dao.insertAll(HttpsUpgradeDomain(exactMatchDomain))
91+
dao.insertAll(listOf(HttpsUpgradeDomain(exactMatchDomain)))
9292
assertTrue(dao.hasDomain(exactMatchDomain))
9393
}
9494

@@ -104,7 +104,7 @@ class HttpsUpgradeDomainDaoTest {
104104

105105
@Test
106106
fun whenModelContainsTwoItemsThenCountIsTwo() {
107-
dao.insertAll(HttpsUpgradeDomain(exactMatchDomain), HttpsUpgradeDomain(otherDomain))
107+
dao.insertAll(listOf(HttpsUpgradeDomain(exactMatchDomain), HttpsUpgradeDomain(otherDomain)))
108108
assertEquals(2, dao.count())
109109
}
110110

app/src/androidTest/java/com/duckduckgo/app/httpsupgrade/db/HttpsUpgradePerformanceTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class HttpsUpgraderPerformanceTest {
101101
private fun ingest(size: Int): Array<String> {
102102
val start = System.currentTimeMillis()
103103
for (i in 0 .. size) {
104-
dao.insertAll(HttpsUpgradeDomain("domain$i.com"))
104+
dao.insertAll(listOf(HttpsUpgradeDomain("domain$i.com")))
105105
}
106106

107107
val testDomains = Array(size, {

app/src/main/java/com/duckduckgo/app/autocomplete/api/AutoCompleteApi.kt

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@
1616

1717
package com.duckduckgo.app.autocomplete.api
1818

19-
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
2019
import com.duckduckgo.app.global.UriString
2120
import io.reactivex.Observable
2221
import javax.inject.Inject
2322

2423

25-
open class AutoCompleteApi @Inject constructor(
26-
private val autoCompleteService: AutoCompleteService,
27-
private val queryUrlConverter: QueryUrlConverter
28-
) {
24+
open class AutoCompleteApi @Inject constructor(private val autoCompleteService: AutoCompleteService) {
2925

3026
fun autoComplete(query: String): Observable<AutoCompleteApi.AutoCompleteResult> {
3127

@@ -34,16 +30,18 @@ open class AutoCompleteApi @Inject constructor(
3430
}
3531

3632
return autoCompleteService.autoComplete(query)
37-
.flatMapIterable { it -> it }
38-
.map { AutoCompleteSuggestion(it.phrase, UriString.isWebUrl(it.phrase)) }
39-
.toList()
40-
.onErrorReturn { emptyList() }
41-
.map { AutoCompleteResult(query = query, suggestions = it) }
42-
.toObservable()
33+
.flatMapIterable { it -> it }
34+
.map { AutoCompleteSuggestion(it.phrase, UriString.isWebUrl(it.phrase)) }
35+
.toList()
36+
.onErrorReturn { emptyList() }
37+
.map { AutoCompleteResult(query = query, suggestions = it) }
38+
.toObservable()
4339
}
4440

45-
data class AutoCompleteResult(val query: String,
46-
val suggestions: List<AutoCompleteSuggestion>)
41+
data class AutoCompleteResult(
42+
val query: String,
43+
val suggestions: List<AutoCompleteSuggestion>
44+
)
4745

4846
data class AutoCompleteSuggestion(val phrase: String, val isUrl: Boolean)
4947

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,6 @@ class BrowserTabFragment : Fragment(), FindListener {
341341

342342
if (shouldUpdateOmnibarTextInput(viewState, viewState.omnibarText)) {
343343
omnibarTextInput.setText(viewState.omnibarText)
344-
345-
// ensures caret sits at the end of the query
346-
omnibarTextInput.post {
347-
omnibarTextInput?.let {
348-
it.setSelection(it.text.length)
349-
}
350-
}
351344
appBarLayout.setExpanded(true, true)
352345
}
353346

app/src/main/java/com/duckduckgo/app/httpsupgrade/api/HttpsUpgradeListDownloader.kt

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,76 @@ package com.duckduckgo.app.httpsupgrade.api
1818

1919
import com.duckduckgo.app.global.api.isCached
2020
import com.duckduckgo.app.global.db.AppDatabase
21+
import com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDbWriteStatusStore
2122
import com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDomainDao
2223
import io.reactivex.Completable
2324
import timber.log.Timber
2425
import java.io.IOException
2526
import javax.inject.Inject
2627

28+
/**
29+
* For performance reasons, we can't insert all the HTTPS rows in a single transaction as it blocks other DB writes from happening for a while
30+
* To avoid the performance problem of a single transaction, we can chunk the data and perform many smaller transactions.
31+
* However, if we don't use a single transaction, there is the risk the process could die or exception be thrown while iterating through the HTTPS inserts, leaving the DB in a bad state.
32+
* The network cache could therefore deliver the cached value and we could skip writing to the DB thinking we'd already successfully written it all.
33+
*
34+
* To counter this, we need an external mechanism to track whether the write completed successfully or not.
35+
* Using this, we can detect if the write failed when we next download the data. We'll only skip the DB inserts if cache returns the list and the
36+
* the eTag received in the response headers matches the eTag we stored on last successful write, indicating that it finished writing successfully.
37+
* @see HttpsUpgradeDbWriteStatusStore for how this status flag is stored.
38+
*/
2739
class HttpsUpgradeListDownloader @Inject constructor(
28-
private val service: HttpsUpgradeListService,
29-
private val database: AppDatabase,
30-
private val httpsUpgradeDao: HttpsUpgradeDomainDao) {
40+
private val service: HttpsUpgradeListService,
41+
private val database: AppDatabase,
42+
private val httpsUpgradeDao: HttpsUpgradeDomainDao,
43+
private val dbWriteStatusStore: HttpsUpgradeDbWriteStatusStore
44+
) {
3145

32-
fun downloadList(): Completable {
46+
fun downloadList(chunkSize: Int = INSERTION_CHUNK_SIZE): Completable {
3347

3448
Timber.d("Downloading HTTPS Upgrade data")
3549

3650
return Completable.fromAction {
3751

3852
val call = service.https()
3953
val response = call.execute()
54+
val eTag = response.headers().get(HEADER_ETAG)
4055

41-
if (response.isCached && httpsUpgradeDao.count() != 0) {
56+
if (response.isCached && dbWriteStatusStore.isMatchingETag(eTag)) {
4257
Timber.d("HTTPS data already cached and stored")
4358
return@fromAction
4459
}
4560

4661
if (response.isSuccessful) {
4762
Timber.d("Updating HTTPS upgrade list from server")
48-
val domains = response.body()!!.toTypedArray()
49-
database.runInTransaction {
50-
httpsUpgradeDao.deleteAll()
51-
httpsUpgradeDao.insertAll(*domains)
63+
64+
val domains = response.body() ?: throw IllegalStateException("Failed to obtain HTTPS upgrade list")
65+
66+
val startTime = System.currentTimeMillis()
67+
68+
httpsUpgradeDao.deleteAll()
69+
Timber.v("Took ${System.currentTimeMillis() - startTime}ms to delete existing records")
70+
71+
val chunks = domains.chunked(chunkSize)
72+
Timber.i("Received ${domains.size} HTTPS domains; chunking by $chunkSize into ${chunks.size} separate DB transactions")
73+
74+
chunks.forEach {
75+
database.runInTransaction {
76+
httpsUpgradeDao.insertAll(it)
77+
}
5278
}
79+
80+
dbWriteStatusStore.saveETag(eTag)
81+
Timber.i("Successfully wrote HTTPS data; took ${System.currentTimeMillis() - startTime}ms")
82+
5383
} else {
5484
throw IOException("Status: ${response.code()} - ${response.errorBody()?.string()}")
5585
}
5686
}
5787
}
88+
89+
companion object {
90+
private const val INSERTION_CHUNK_SIZE = 1_000
91+
private const val HEADER_ETAG = "etag"
92+
}
5893
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2018 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.httpsupgrade.db
18+
19+
import android.content.Context
20+
import android.content.SharedPreferences
21+
import android.support.annotation.VisibleForTesting
22+
import androidx.core.content.edit
23+
import javax.inject.Inject
24+
25+
interface HttpsUpgradeDbWriteStatusStore {
26+
27+
fun saveETag(eTag: String?)
28+
fun isMatchingETag(eTag: String?): Boolean
29+
}
30+
31+
class HttpsUpgradeDbWriteStatusSharedPreferences @Inject constructor(private val context: Context) : HttpsUpgradeDbWriteStatusStore {
32+
33+
override fun saveETag(eTag: String?) {
34+
preferences.edit { putString(KEY_ETAG_OF_LAST_FULL_WRITE, eTag) }
35+
}
36+
37+
override fun isMatchingETag(eTag: String?): Boolean {
38+
val existingETag = preferences.getString(KEY_ETAG_OF_LAST_FULL_WRITE, null)
39+
return eTag == existingETag
40+
}
41+
42+
private val preferences: SharedPreferences
43+
get() = context.getSharedPreferences(FILENAME, Context.MODE_PRIVATE)
44+
45+
companion object {
46+
47+
@VisibleForTesting
48+
const val FILENAME = "com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDbWriteStatus"
49+
const val KEY_ETAG_OF_LAST_FULL_WRITE = "KEY_ETAG_OF_LAST_FULL_WRITE"
50+
}
51+
}

app/src/main/java/com/duckduckgo/app/httpsupgrade/db/HttpsUpgradeDomainDao.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ import android.arch.persistence.room.Dao
2020
import android.arch.persistence.room.Insert
2121
import android.arch.persistence.room.OnConflictStrategy
2222
import android.arch.persistence.room.Query
23-
import org.intellij.lang.annotations.Language
2423

2524
@Dao
2625
interface HttpsUpgradeDomainDao {
2726

2827
@Insert(onConflict = OnConflictStrategy.REPLACE)
29-
fun insertAll(vararg domains: HttpsUpgradeDomain)
28+
fun insertAll(domains: List<HttpsUpgradeDomain>)
3029

31-
@Language("RoomSql")
3230
@Query("select count(1) > 0 from https_upgrade_domain where domain = :domain")
3331
fun hasDomain(domain: String) : Boolean
3432

app/src/main/java/com/duckduckgo/app/httpsupgrade/di/HttpsUpgraderModule.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616

1717
package com.duckduckgo.app.httpsupgrade.di
1818

19+
import android.content.Context
1920
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
2021
import com.duckduckgo.app.httpsupgrade.HttpsUpgraderImpl
22+
import com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDbWriteStatusSharedPreferences
23+
import com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDbWriteStatusStore
2124
import com.duckduckgo.app.httpsupgrade.db.HttpsUpgradeDomainDao
2225
import dagger.Module
2326
import dagger.Provides
27+
import javax.inject.Singleton
2428

2529
@Module
2630
class HttpsUpgraderModule {
@@ -29,4 +33,10 @@ class HttpsUpgraderModule {
2933
fun httpsUpgrader(dao: HttpsUpgradeDomainDao): HttpsUpgrader {
3034
return HttpsUpgraderImpl(dao)
3135
}
36+
37+
@Provides
38+
@Singleton
39+
fun httpsUpgradeDbWriteStatusStore(context: Context): HttpsUpgradeDbWriteStatusStore {
40+
return HttpsUpgradeDbWriteStatusSharedPreferences(context)
41+
}
3242
}

0 commit comments

Comments
 (0)