Configurable response timeouts, resolves #31

This commit is contained in:
Aidan Follestad 2018-12-02 13:36:46 -08:00
commit 62ef385b65
15 changed files with 220 additions and 40 deletions

View file

@ -22,12 +22,14 @@ import com.afollestad.nocknock.viewcomponents.ext.conceal
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
import com.afollestad.nocknock.viewcomponents.ext.onLayout import com.afollestad.nocknock.viewcomponents.ext.onLayout
import com.afollestad.nocknock.viewcomponents.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import com.afollestad.nocknock.viewcomponents.ext.textAsInt
import com.afollestad.nocknock.viewcomponents.ext.trimmedText import com.afollestad.nocknock.viewcomponents.ext.trimmedText
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
import kotlinx.android.synthetic.main.activity_addsite.doneBtn import kotlinx.android.synthetic.main.activity_addsite.doneBtn
import kotlinx.android.synthetic.main.activity_addsite.inputName import kotlinx.android.synthetic.main.activity_addsite.inputName
import kotlinx.android.synthetic.main.activity_addsite.inputUrl import kotlinx.android.synthetic.main.activity_addsite.inputUrl
import kotlinx.android.synthetic.main.activity_addsite.loadingProgress import kotlinx.android.synthetic.main.activity_addsite.loadingProgress
import kotlinx.android.synthetic.main.activity_addsite.responseTimeoutInput
import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode import kotlinx.android.synthetic.main.activity_addsite.responseValidationMode
import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchTerm
import kotlinx.android.synthetic.main.activity_addsite.rootView import kotlinx.android.synthetic.main.activity_addsite.rootView
@ -99,6 +101,7 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView {
val checkInterval = checkIntervalLayout.getSelectedCheckInterval() val checkInterval = checkIntervalLayout.getSelectedCheckInterval()
val validationMode = val validationMode =
responseValidationMode.selectedItemPosition.indexToValidationMode() responseValidationMode.selectedItemPosition.indexToValidationMode()
val defaultTimeout = getString(R.string.response_timeout_default).toInt()
isClosing = true isClosing = true
presenter.commit( presenter.commit(
@ -106,7 +109,8 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView {
url = inputUrl.trimmedText(), url = inputUrl.trimmedText(),
checkInterval = checkInterval, checkInterval = checkInterval,
validationMode = validationMode, validationMode = validationMode,
validationContent = validationMode.validationContent() validationContent = validationMode.validationContent(),
networkTimeout = responseTimeoutInput.textAsInt(defaultValue = defaultTimeout)
) )
} }
} }
@ -165,6 +169,11 @@ class AddSiteActivity : AppCompatActivity(), AddSiteView {
null null
} }
) )
responseTimeoutInput.error = if (errors.networkTimeout != null) {
getString(errors.networkTimeout!!)
} else {
null
}
} }
override fun onSiteAdded() { override fun onSiteAdded() {

View file

@ -27,11 +27,12 @@ data class InputErrors(
var url: Int? = null, var url: Int? = null,
var checkInterval: Int? = null, var checkInterval: Int? = null,
var termSearch: Int? = null, var termSearch: Int? = null,
var javaScript: Int? = null var javaScript: Int? = null,
var networkTimeout: Int? = null
) { ) {
@CheckResult fun any(): Boolean { @CheckResult fun any(): Boolean {
return name != null || url != null || checkInterval != null || return name != null || url != null || checkInterval != null ||
termSearch != null || javaScript != null termSearch != null || javaScript != null || networkTimeout != null
} }
} }
@ -52,7 +53,8 @@ interface AddSitePresenter {
url: String, url: String,
checkInterval: Long, checkInterval: Long,
validationMode: ValidationMode, validationMode: ValidationMode,
validationContent: String? validationContent: String?,
networkTimeout: Int
) )
fun dropView() fun dropView()
@ -106,7 +108,8 @@ class RealAddSitePresenter @Inject constructor(
url: String, url: String,
checkInterval: Long, checkInterval: Long,
validationMode: ValidationMode, validationMode: ValidationMode,
validationContent: String? validationContent: String?,
networkTimeout: Int
) { ) {
val inputErrors = InputErrors() val inputErrors = InputErrors()
@ -126,6 +129,9 @@ class RealAddSitePresenter @Inject constructor(
} else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) { } else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) {
inputErrors.javaScript = R.string.please_enter_javaScript inputErrors.javaScript = R.string.please_enter_javaScript
} }
if (networkTimeout <= 0) {
inputErrors.networkTimeout = R.string.please_enter_networkTimeout
}
if (inputErrors.any()) { if (inputErrors.any()) {
view?.setInputErrors(inputErrors) view?.setInputErrors(inputErrors)
@ -138,7 +144,8 @@ class RealAddSitePresenter @Inject constructor(
status = WAITING, status = WAITING,
checkInterval = checkInterval, checkInterval = checkInterval,
validationMode = validationMode, validationMode = validationMode,
validationContent = validationContent validationContent = validationContent,
networkTimeout = networkTimeout
) )
with(view!!) { with(view!!) {

View file

@ -33,6 +33,7 @@ import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
import com.afollestad.nocknock.viewcomponents.ext.onScroll import com.afollestad.nocknock.viewcomponents.ext.onScroll
import com.afollestad.nocknock.viewcomponents.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import com.afollestad.nocknock.viewcomponents.ext.textAsInt
import com.afollestad.nocknock.viewcomponents.ext.trimmedText import com.afollestad.nocknock.viewcomponents.ext.trimmedText
import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout
import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton import kotlinx.android.synthetic.main.activity_viewsite.disableChecksButton
@ -41,6 +42,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.iconStatus
import kotlinx.android.synthetic.main.activity_viewsite.inputName import kotlinx.android.synthetic.main.activity_viewsite.inputName
import kotlinx.android.synthetic.main.activity_viewsite.inputUrl import kotlinx.android.synthetic.main.activity_viewsite.inputUrl
import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress import kotlinx.android.synthetic.main.activity_viewsite.loadingProgress
import kotlinx.android.synthetic.main.activity_viewsite.responseTimeoutInput
import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode import kotlinx.android.synthetic.main.activity_viewsite.responseValidationMode
import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm import kotlinx.android.synthetic.main.activity_viewsite.responseValidationSearchTerm
import kotlinx.android.synthetic.main.activity_viewsite.rootView import kotlinx.android.synthetic.main.activity_viewsite.rootView
@ -113,13 +115,15 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
val checkInterval = checkIntervalLayout.getSelectedCheckInterval() val checkInterval = checkIntervalLayout.getSelectedCheckInterval()
val validationMode = val validationMode =
responseValidationMode.selectedItemPosition.indexToValidationMode() responseValidationMode.selectedItemPosition.indexToValidationMode()
val defaultTimeout = getString(R.string.response_timeout_default).toInt()
presenter.commit( presenter.commit(
name = inputName.trimmedText(), name = inputName.trimmedText(),
url = inputUrl.trimmedText(), url = inputUrl.trimmedText(),
checkInterval = checkInterval, checkInterval = checkInterval,
validationMode = validationMode, validationMode = validationMode,
validationContent = validationMode.validationContent() validationContent = validationMode.validationContent(),
networkTimeout = responseTimeoutInput.textAsInt(defaultValue = defaultTimeout)
) )
} }
@ -189,6 +193,8 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
} }
} }
responseTimeoutInput.setText(model.networkTimeout.toString())
disableChecksButton.showOrHide(!this.disabled) disableChecksButton.showOrHide(!this.disabled)
doneBtn.setText( doneBtn.setText(
if (this.disabled) R.string.renable_and_save_changes if (this.disabled) R.string.renable_and_save_changes
@ -228,6 +234,11 @@ class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
null null
} }
) )
responseTimeoutInput.error = if (errors.networkTimeout != null) {
getString(errors.networkTimeout!!)
} else {
null
}
} }
override fun scopeWhileAttached( override fun scopeWhileAttached(

View file

@ -34,11 +34,12 @@ data class InputErrors(
var url: Int? = null, var url: Int? = null,
var checkInterval: Int? = null, var checkInterval: Int? = null,
var termSearch: Int? = null, var termSearch: Int? = null,
var javaScript: Int? = null var javaScript: Int? = null,
var networkTimeout: Int? = null
) { ) {
@CheckResult fun any(): Boolean { @CheckResult fun any(): Boolean {
return name != null || url != null || checkInterval != null || return name != null || url != null || checkInterval != null ||
termSearch != null || javaScript != null termSearch != null || javaScript != null || networkTimeout != null
} }
} }
@ -66,7 +67,8 @@ interface ViewSitePresenter {
url: String, url: String,
checkInterval: Long, checkInterval: Long,
validationMode: ValidationMode, validationMode: ValidationMode,
validationContent: String? validationContent: String?,
networkTimeout: Int
) )
fun checkNow() fun checkNow()
@ -151,7 +153,8 @@ class RealViewSitePresenter @Inject constructor(
url: String, url: String,
checkInterval: Long, checkInterval: Long,
validationMode: ValidationMode, validationMode: ValidationMode,
validationContent: String? validationContent: String?,
networkTimeout: Int
) { ) {
val inputErrors = InputErrors() val inputErrors = InputErrors()
@ -171,6 +174,9 @@ class RealViewSitePresenter @Inject constructor(
} else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) { } else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) {
inputErrors.javaScript = R.string.please_enter_javaScript inputErrors.javaScript = R.string.please_enter_javaScript
} }
if (networkTimeout <= 0) {
inputErrors.networkTimeout = R.string.please_enter_networkTimeout
}
if (inputErrors.any()) { if (inputErrors.any()) {
view?.setInputErrors(inputErrors) view?.setInputErrors(inputErrors)
@ -184,7 +190,8 @@ class RealViewSitePresenter @Inject constructor(
checkInterval = checkInterval, checkInterval = checkInterval,
validationMode = validationMode, validationMode = validationMode,
validationContent = validationContent, validationContent = validationContent,
disabled = false disabled = false,
networkTimeout = networkTimeout
) )
with(view!!) { with(view!!) {

View file

@ -100,6 +100,28 @@
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
/> />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:text="@string/response_timeout"
style="@style/NockText.SectionHeader"
/>
<EditText
android:id="@+id/responseTimeoutInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="-4dp"
android:layout_marginStart="-4dp"
android:hint="@string/response_timeout_default"
android:inputType="number"
android:maxLength="8"
android:textColor="#FFFFFF"
tools:ignore="Autofill,HardcodedText,LabelFor"
style="@style/NockText.Body.Light"
/>
<TextView <TextView
android:id="@+id/responseValidationLabel" android:id="@+id/responseValidationLabel"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -122,6 +122,28 @@
android:layout_marginTop="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset"
/> />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_inset"
android:text="@string/response_timeout"
style="@style/NockText.SectionHeader"
/>
<EditText
android:id="@+id/responseTimeoutInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="-4dp"
android:layout_marginStart="-4dp"
android:hint="@string/response_timeout_default"
android:inputType="number"
android:maxLength="8"
android:textColor="#FFFFFF"
tools:ignore="Autofill,HardcodedText,LabelFor"
style="@style/NockText.Body.Light"
/>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"

View file

@ -25,6 +25,7 @@
<string name="please_enter_check_interval">Please input a check interval.</string> <string name="please_enter_check_interval">Please input a check interval.</string>
<string name="please_enter_search_term">Please input a search term.</string> <string name="please_enter_search_term">Please input a search term.</string>
<string name="please_enter_javaScript">Please input a validation script.</string> <string name="please_enter_javaScript">Please input a validation script.</string>
<string name="please_enter_networkTimeout">Please enter a network timeout greater than 0.</string>
<string name="options">Options</string> <string name="options">Options</string>
<string name="already_checking_sites">Already checking sites!</string> <string name="already_checking_sites">Already checking sites!</string>
@ -49,6 +50,9 @@
<string name="disable">Disable</string> <string name="disable">Disable</string>
<string name="renable_and_save_changes">Enable Checks &amp; Save Changes</string> <string name="renable_and_save_changes">Enable Checks &amp; Save Changes</string>
<string name="response_timeout">Network Response Timeout</string>
<string name="response_timeout_default">10000</string>
<string name="refresh_status">Refresh Status</string> <string name="refresh_status">Refresh Status</string>
<string name="warning_http_url"> <string name="warning_http_url">

View file

@ -113,7 +113,8 @@ class AddSitePresenterTest {
"https://test.com", "https://test.com",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -131,7 +132,8 @@ class AddSitePresenterTest {
"", "",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -149,7 +151,8 @@ class AddSitePresenterTest {
"ftp://hello.com", "ftp://hello.com",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -167,7 +170,8 @@ class AddSitePresenterTest {
"https://hello.com", "https://hello.com",
-1, -1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -185,7 +189,8 @@ class AddSitePresenterTest {
"https://hello.com", "https://hello.com",
1, 1,
TERM_SEARCH, TERM_SEARCH,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -197,13 +202,33 @@ class AddSitePresenterTest {
assertThat(errors.termSearch).isEqualTo(R.string.please_enter_search_term) assertThat(errors.termSearch).isEqualTo(R.string.please_enter_search_term)
} }
@Test fun commit_networkTimeout_error() {
presenter.commit(
"Testing",
"https://hello.com",
1,
STATUS_CODE,
null,
0
)
val inputErrorsCaptor = argumentCaptor<InputErrors>()
verify(view).setInputErrors(inputErrorsCaptor.capture())
verify(checkStatusManager, never())
.scheduleCheck(any(), any(), any(), any())
val errors = inputErrorsCaptor.firstValue
assertThat(errors.networkTimeout).isEqualTo(R.string.please_enter_networkTimeout)
}
@Test fun commit_javaScript_error() { @Test fun commit_javaScript_error() {
presenter.commit( presenter.commit(
"Testing", "Testing",
"https://hello.com", "https://hello.com",
1, 1,
JAVASCRIPT, JAVASCRIPT,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -221,7 +246,8 @@ class AddSitePresenterTest {
"https://hello.com", "https://hello.com",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val modelCaptor = argumentCaptor<ServerModel>() val modelCaptor = argumentCaptor<ServerModel>()

View file

@ -156,7 +156,8 @@ class ViewSitePresenterTest {
"https://test.com", "https://test.com",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -174,7 +175,8 @@ class ViewSitePresenterTest {
"", "",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -192,7 +194,8 @@ class ViewSitePresenterTest {
"ftp://hello.com", "ftp://hello.com",
1, 1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -210,7 +213,8 @@ class ViewSitePresenterTest {
"https://hello.com", "https://hello.com",
-1, -1,
STATUS_CODE, STATUS_CODE,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -228,7 +232,8 @@ class ViewSitePresenterTest {
"https://hello.com", "https://hello.com",
1, 1,
TERM_SEARCH, TERM_SEARCH,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -246,7 +251,8 @@ class ViewSitePresenterTest {
"https://hello.com", "https://hello.com",
1, 1,
JAVASCRIPT, JAVASCRIPT,
null null,
60000
) )
val inputErrorsCaptor = argumentCaptor<InputErrors>() val inputErrorsCaptor = argumentCaptor<InputErrors>()
@ -258,6 +264,25 @@ class ViewSitePresenterTest {
assertThat(errors.javaScript).isEqualTo(R.string.please_enter_javaScript) assertThat(errors.javaScript).isEqualTo(R.string.please_enter_javaScript)
} }
@Test fun commit_networkTimeout_error() {
presenter.commit(
"Testing",
"https://hello.com",
1,
STATUS_CODE,
null,
0
)
val inputErrorsCaptor = argumentCaptor<InputErrors>()
verify(view).setInputErrors(inputErrorsCaptor.capture())
verify(checkStatusManager, never())
.scheduleCheck(any(), any(), any(), any())
val errors = inputErrorsCaptor.firstValue
assertThat(errors.networkTimeout).isEqualTo(R.string.please_enter_networkTimeout)
}
@Test fun commit_success() = runBlocking { @Test fun commit_success() = runBlocking {
val name = "Testing" val name = "Testing"
val url = "https://hello.com" val url = "https://hello.com"
@ -274,7 +299,8 @@ class ViewSitePresenterTest {
url, url,
checkInterval, checkInterval,
validationMode, validationMode,
validationContent validationContent,
60000
) )
val modelCaptor = argumentCaptor<ServerModel>() val modelCaptor = argumentCaptor<ServerModel>()
@ -358,4 +384,4 @@ class ViewSitePresenterTest {
on { getAction() } doReturn action on { getAction() } doReturn action
} }
} }
} }

View file

@ -27,7 +27,8 @@ data class ServerModel(
val reason: String? = null, val reason: String? = null,
val validationMode: ValidationMode, val validationMode: ValidationMode,
val validationContent: String? = null, val validationContent: String? = null,
val disabled: Boolean = false val disabled: Boolean = false,
val networkTimeout: Int = 0
) : IdProvider { ) : IdProvider {
companion object { companion object {
@ -42,6 +43,7 @@ data class ServerModel(
const val COLUMN_VALIDATION_MODE = "validation_mode" const val COLUMN_VALIDATION_MODE = "validation_mode"
const val COLUMN_VALIDATION_CONTENT = "validation_content" const val COLUMN_VALIDATION_CONTENT = "validation_content"
const val COLUMN_DISABLED = "disabled" const val COLUMN_DISABLED = "disabled"
const val COLUMN_NETWORK_TIMEOUT = "network_timeout"
const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC, $COLUMN_DISABLED DESC" const val DEFAULT_SORT_ORDER = "$COLUMN_NAME ASC, $COLUMN_DISABLED DESC"
@ -58,7 +60,8 @@ data class ServerModel(
cursor.getColumnIndex(COLUMN_VALIDATION_MODE) cursor.getColumnIndex(COLUMN_VALIDATION_MODE)
).toValidationMode(), ).toValidationMode(),
validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)), validationContent = cursor.getString(cursor.getColumnIndex(COLUMN_VALIDATION_CONTENT)),
disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1 disabled = cursor.getInt(cursor.getColumnIndex(COLUMN_DISABLED)) == 1,
networkTimeout = cursor.getInt(cursor.getColumnIndex(COLUMN_NETWORK_TIMEOUT))
) )
} }
} }
@ -81,5 +84,6 @@ data class ServerModel(
put(COLUMN_VALIDATION_MODE, validationMode.value) put(COLUMN_VALIDATION_MODE, validationMode.value)
put(COLUMN_VALIDATION_CONTENT, validationContent) put(COLUMN_VALIDATION_CONTENT, validationContent)
put(COLUMN_DISABLED, disabled) put(COLUMN_DISABLED, disabled)
put(COLUMN_NETWORK_TIMEOUT, networkTimeout)
} }
} }

View file

@ -21,7 +21,9 @@ private const val SQL_CREATE_ENTRIES =
"${ServerModel.COLUMN_REASON} TEXT," + "${ServerModel.COLUMN_REASON} TEXT," +
"${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," + "${ServerModel.COLUMN_VALIDATION_MODE} INTEGER," +
"${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT," + "${ServerModel.COLUMN_VALIDATION_CONTENT} TEXT," +
"${ServerModel.COLUMN_DISABLED} INTEGER)" "${ServerModel.COLUMN_DISABLED} INTEGER," +
"${ServerModel.COLUMN_NETWORK_TIMEOUT} INTEGER" +
")"
private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${ServerModel.TABLE_NAME}"
@ -30,7 +32,7 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, DATABASE_VERSION context, DATABASE_NAME, null, DATABASE_VERSION
) { ) {
companion object { companion object {
const val DATABASE_VERSION = 2 const val DATABASE_VERSION = 3
const val DATABASE_NAME = "ServerModels.db" const val DATABASE_NAME = "ServerModels.db"
} }
@ -43,8 +45,12 @@ class ServerModelDbHelper(context: Context) : SQLiteOpenHelper(
oldVersion: Int, oldVersion: Int,
newVersion: Int newVersion: Int
) { ) {
db.execSQL(SQL_DELETE_ENTRIES) if (newVersion == 3 && oldVersion == 2) {
onCreate(db) db.execSQL(
"ALTER TABLE ${ServerModel.TABLE_NAME} " +
"ADD COLUMN ${ServerModel.COLUMN_NETWORK_TIMEOUT} INTEGER DEFAULT 10000"
)
}
} }
override fun onDowngrade( override fun onDowngrade(

View file

@ -19,7 +19,9 @@ import com.afollestad.nocknock.utilities.providers.StringProvider
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jetbrains.annotations.TestOnly
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber.d as log import timber.log.Timber.d as log
@ -29,6 +31,8 @@ data class CheckResult(
val response: Response? = null val response: Response? = null
) )
typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface CheckStatusManager { interface CheckStatusManager {
@ -55,6 +59,12 @@ class RealCheckStatusManager @Inject constructor(
private val siteStore: ServerModelStore private val siteStore: ServerModelStore
) : CheckStatusManager { ) : CheckStatusManager {
private var clientTimeoutChanger: ClientTimeoutChanger = { client, timeout ->
client.newBuilder()
.callTimeout(timeout.toLong(), MILLISECONDS)
.build()
}
override suspend fun ensureScheduledChecks() { override suspend fun ensureScheduledChecks() {
val sites = siteStore.get() val sites = siteStore.get()
if (sites.isEmpty()) { if (sites.isEmpty()) {
@ -121,6 +131,7 @@ class RealCheckStatusManager @Inject constructor(
override suspend fun performCheck(site: ServerModel): CheckResult { override suspend fun performCheck(site: ServerModel): CheckResult {
check(site.id != 0) { "Cannot schedule checks for jobs with no ID." } check(site.id != 0) { "Cannot schedule checks for jobs with no ID." }
check(site.networkTimeout > 0) { "Network timeout not set for site ${site.id}" }
log("performCheck(${site.id}) - GET ${site.url}") log("performCheck(${site.id}) - GET ${site.url}")
val request = Request.Builder() val request = Request.Builder()
@ -129,8 +140,10 @@ class RealCheckStatusManager @Inject constructor(
.build() .build()
return try { return try {
val response = okHttpClient.newCall(request) val client = clientTimeoutChanger(okHttpClient, site.networkTimeout)
val response = client.newCall(request)
.execute() .execute()
if (response.isSuccessful || response.code() == 401) { if (response.isSuccessful || response.code() == 401) {
log("performCheck(${site.id}) = Successful") log("performCheck(${site.id}) = Successful")
CheckResult( CheckResult(
@ -164,4 +177,8 @@ class RealCheckStatusManager @Inject constructor(
private fun jobForSite(site: ServerModel) = private fun jobForSite(site: ServerModel) =
jobScheduler.allPendingJobs jobScheduler.allPendingJobs
.firstOrNull { job -> job.id == site.id } .firstOrNull { job -> job.id == site.id }
@TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) {
this.clientTimeoutChanger = changer
}
} }

View file

@ -55,7 +55,12 @@ class CheckStatusManagerTest {
bundleProvider, bundleProvider,
jobInfoProvider, jobInfoProvider,
store store
) ).apply {
setClientTimeoutChanger { _, timeout ->
whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout)
return@setClientTimeoutChanger okHttpClient
}
}
@Test fun ensureScheduledChecks_noEnabledSites() = runBlocking { @Test fun ensureScheduledChecks_noEnabledSites() = runBlocking {
val model1 = fakeModel().copy(disabled = true) val model1 = fakeModel().copy(disabled = true)
@ -234,6 +239,8 @@ class CheckStatusManagerTest {
reason = null reason = null
) )
) )
assertThat(okHttpClient.callTimeoutMillis())
.isEqualTo(model1.networkTimeout)
} }
@Test fun performCheck_401_butStillSuccess() = runBlocking { @Test fun performCheck_401_butStillSuccess() = runBlocking {
@ -280,7 +287,8 @@ class CheckStatusManagerTest {
id = 1, id = 1,
name = "Wakanda Forever", name = "Wakanda Forever",
url = "https://www.wakanda.gov", url = "https://www.wakanda.gov",
validationMode = STATUS_CODE validationMode = STATUS_CODE,
networkTimeout = 60000
) )
private fun fakeJob(id: Int): JobInfo { private fun fakeJob(id: Int): JobInfo {

View file

@ -24,7 +24,13 @@ fun Long.timeString() = when {
"${ceil((this.toFloat() / DAY.toFloat()).toDouble()).toInt()}d" "${ceil((this.toFloat() / DAY.toFloat()).toDouble()).toInt()}d"
this >= HOUR -> this >= HOUR ->
"${ceil((this.toFloat() / HOUR.toFloat()).toDouble()).toInt()}h" "${ceil((this.toFloat() / HOUR.toFloat()).toDouble()).toInt()}h"
this >= MINUTE -> this >= MINUTE -> {
"${ceil((this.toFloat() / MINUTE.toFloat()).toDouble()).toInt()}m" val result = "${ceil((this.toFloat() / MINUTE.toFloat()).toDouble()).toInt()}m"
if (result == "60m") {
"1h"
} else {
result
}
}
else -> "<1m" else -> "<1m"
} }

View file

@ -9,9 +9,14 @@ import android.widget.TextView
fun TextView.trimmedText() = text.toString().trim() fun TextView.trimmedText() = text.toString().trim()
fun TextView.textAsLong(): Long { fun TextView.textAsInt(defaultValue: Int = 0): Int {
val text = trimmedText() val text = trimmedText()
return if (text.isEmpty()) 0L else text.toLong() return if (text.isEmpty()) defaultValue else text.toInt()
}
fun TextView.textAsLong(defaultValue: Long = 0L): Long {
val text = trimmedText()
return if (text.isEmpty()) defaultValue else text.toLong()
} }
///** @author https://stackoverflow.com/a/53296137/309644 */ ///** @author https://stackoverflow.com/a/53296137/309644 */