Repository Unit Tests #50

Merged
AmineB merged 38 commits from davidoskky/ReaderForSelfoss-multiplatform:repository_tests into master 2022-09-30 11:31:55 +00:00
23 changed files with 318 additions and 139 deletions
Showing only changes of commit d2436bb976 - Show all commits

View File

@ -1,26 +1,118 @@
kind: pipeline
type: docker
name: android
name: test
steps:
- name: code-analysis
- name: AnylyseBuildTest
image: mingc/android-build-box:latest
failure: ignore
commands:
- ls -la
- echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
trigger:
event:
- push
- pull_request
- name: test
image: mingc/android-build-box:latest
---
kind: pipeline
type: docker
name: Publish
steps:
- name: createTag
image: ubuntu:latest
commands:
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- apt-get update && apt-get install -y git
- ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags
environment:
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
from_secret: giteaPass
- name: scpFiles
image: appleboy/drone-scp
settings:
host: amine-louveau.fr
username: ubuntu
key:
from_secret: privateKey
port: 22
target: /home/ubuntu/
source: version.txt
- name: deploy
image: appleboy/drone-ssh
settings:
host: amine-louveau.fr
user: ubuntu
key:
from_secret: privateKey
command_timeout: 2m
script:
- cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
trigger:
event:
- promote
target:
- production
---
kind: pipeline
type: docker
name: Release
steps:
- name: build
image: mingc/android-build-box:latest
commands:
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
- echo "---------------------------------------------------------"
- echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Sign"
- $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:
from_secret: keyAlias
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: giteaAPI
base_url: https://gitea.amine-louveau.fr
files: signed.apk
trigger:
event:
- tag

View File

@ -1,3 +1,14 @@
# V2/Multiplatform rewrite
**v1**
- The app has the same functionalities as before.
--------------------------------------------------------------------
# Old version changes
**1.7.x**
- Hiding tags with 0 articles

View File

@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)

View File

@ -93,7 +93,7 @@ android {
}
flavorDimensions.add("build")
productFlavors {
create("github") {
create("githubConfig") {
versionNameSuffix = "-github"
dimension = "build"
}
@ -101,6 +101,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android"
}

View File

@ -55,11 +55,38 @@
public *;
}
-dontwarn com.anupcowkur.reservoir.**
-dontwarn javax.annotation.**
-keep class android.support.v7.widget.SearchView { *; }
# maybe remove later ?
-keep class * extends androidx.fragment.app.Fragment
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault

View File

@ -1,11 +0,0 @@
#!/bin/bash
# NOTE: This is copy/pasted in jenkins
rm -f version.txt
printf "versionName=$1-github\nversionCode=$1" >> version.txt
# You'll need to change server as your server and define a VERSION_PATH.
scp version.txt server:$VERSION_PATH
rm version.txt

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="bou.amine.apps.readerforselfossv2.android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@ -9,7 +9,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@ -84,7 +84,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid(this@AddSourceActivity)) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)

View File

@ -14,7 +14,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
@ -115,14 +115,14 @@ class LoginActivity : AppCompatActivity(), DIAware {
binding.passwordView.error = null
// Store values at the time of the login attempt.
val url = binding.urlView.text.toString()
val login = binding.loginView.text.toString()
val password = binding.passwordView.text.toString()
val url = binding.urlView.text.toString().trim()
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid(this@LoginActivity)) {
if (url.isBaseUrlInvalid(this@LoginActivity)) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true

View File

@ -268,8 +268,8 @@ class ArticleFragment : Fragment(), DIAware {
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
AmineB marked this conversation as resolved Outdated

This is a simple api call to fetch the artcile content. There is no need to put it inside the repository. It is only used here.

This is a simple api call to fetch the artcile content. There is no need to put it inside the repository. It is only used here.

Yes, I do understand.
I suggest this edit just because it would allow to keep all information about the connectivity private within the repository and increase incapsulation.
But there is no real significant difference.

Yes, I do understand. I suggest this edit just because it would allow to keep all information about the connectivity private within the repository and increase incapsulation. But there is no real significant difference.
parser.parseUrl(url).enqueue(
object : Callback<ParsedContent> {

View File

@ -163,7 +163,7 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(ctx: Context): Boolean {
fun String.isBaseUrlInvalid(ctx: Context): Boolean {
val baseUrl = this.toHttpUrlOrNull()
var existsAndEndsWithSlash = false
if (baseUrl != null) {
@ -171,7 +171,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean {
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
}
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {

View File

@ -1,11 +0,0 @@
#!/bin/bash
# You can pass --force as first parameter to force push and tag creation.
echo "Creating tag $@"
TAG="v$@"
git tag ${TAG}
echo "Pushing tag"
git push origin ${TAG}

View File

@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.2.2")
classpath("com.android.tools.build:gradle:7.3.0")
// sonarquve
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")

View File

@ -2,7 +2,7 @@
git fetch --tags -p
BASE_VERSION="1.7"
BASE_VERSION="1"
LAST_TAG=$(git tag -l | sort -V | tail -1)
INITIAL_VERSION="${BASE_VERSION//./}$(date '+%y%m%j')"
@ -21,11 +21,11 @@ VERSION="${INITIAL_VERSION}${TODAYS_VERSION}"
PARAMS_EXCEPT_PUBLISH=$(echo $1 | sed 's/\-\-publish//')
./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH}
./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH} $@
if [[ "$@" == *'--publish'* ]]
then
./publish-version.sh ${VERSION}
./publish-version.sh ${VERSION} $@
else
echo "Did not publish. If you wanted to do so, call the script with \"--publish\" or \"--publish-local\"."
fi

View File

@ -1,3 +1 @@
A new RSS reader for <a href="http://selfoss.aditu.de/">selfoss</a>.
It connects to your selfoss instance (works only with selfoss, and can't work without it), and you'll be able to read and manage all your RSS feeds.

View File

@ -1,6 +1,6 @@
#Wed Feb 09 17:05:19 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

15
publish-version.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
# NOTE: This is copy/pasted in jenkins
rm -f version.txt
printf "versionName=$1-github\nversionCode=$1" >> version.txt
if [[ "$@" == *'--from-ci'* ]]
then
echo "File created. HANDLE IN CI"
else
# You'll need to change server as your server and define a VERSION_PATH.
scp version.txt server:$VERSION_PATH
rm version.txt
fi

View File

@ -114,6 +114,7 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
namespace = "bou.amine.apps.readerforselfossv2"
}
sqldelight {

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="bou.amine.apps.readerforselfossv2" />
<manifest />

View File

@ -105,4 +105,16 @@ class SelfossModel {
return this
}
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
}
}
}

View File

@ -48,7 +48,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
if (isNetworkAvailable()) {
fetchedItems = api.getItems(
displayedItems.type,
@ -60,23 +60,25 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} else {
if (appSettingsService.isItemCachingEnabled()) {
fetchedItems = getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
fetchedItems = SelfossModel.StatusAndData.succes(
getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
)
}
}
if (fetchedItems != null) {
items = ArrayList(fetchedItems)
if (fetchedItems.success && fetchedItems.data != null) {
items = ArrayList(fetchedItems.data!!)
sortItems()
}
return items
}
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
if (isNetworkAvailable()) {
val offset = items.size
fetchedItems = api.getItems(
@ -89,16 +91,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} // When using the db cache, we load everything the first time, so there should be nothing more to load.
if (fetchedItems != null) {
items.addAll(fetchedItems)
if (fetchedItems.success && fetchedItems.data != null) {
items.addAll(fetchedItems.data!!)
sortItems()
}
return items
}
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item>? {
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
return if (isNetworkAvailable()) {
api.getItems(
val items = api.getItems(
itemType.type,
0,
tagFilter?.tag,
@ -107,6 +109,11 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
null,
200
)
return if (items.success && items.data != null) {
items.data
} else {
emptyList()
}
} else {
emptyList()
}
@ -120,10 +127,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var success = false
if (isNetworkAvailable()) {
val response = api.stats()
if (response != null) {
badgeUnread = response.unread
badgeAll = response.total
badgeStarred = response.starred
if (response.success && response.data != null) {
badgeUnread = response.data.unread
badgeAll = response.data.total
badgeStarred = response.data.starred
success = true
}
} else {
@ -139,10 +146,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getTags(): List<SelfossModel.Tag>? {
return if (isNetworkAvailable()) {
val apiTags = api.tags()
if (apiTags != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags)
if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags.data)
}
apiTags
apiTags.data
} else {
getDBTags().map { it.toView() }
}
@ -151,7 +158,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
// TODO: Add tests
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
return if (isNetworkAvailable()) {
api.spouts()
val spouts = api.spouts()
return if (spouts.success && spouts.data != null) {
spouts.data
} else {
emptyMap() // TODO: do something here
}
} else {
throw NetworkUnavailableException()
}
@ -160,10 +172,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
return if (isNetworkAvailable()) {
val apiSources = api.sources()
if (apiSources != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources)
if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources.data)
}
apiSources
apiSources.data
} else {
ArrayList(getDBSources().map { it.toView() })
}
@ -180,7 +192,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun markAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.markAsRead(id.toString())?.isSuccess == true
api.markAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), read = true)
true
@ -199,7 +211,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unmarkAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unmarkAsRead(id.toString())?.isSuccess == true
api.unmarkAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), unread = true)
true
@ -217,7 +229,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun starrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.starr(id.toString())?.isSuccess == true
api.starr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
@ -235,7 +247,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unstarrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unstarr(id.toString())?.isSuccess == true
api.unstarr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
@ -245,7 +257,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
var success = false
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) {
success = true
for (item in items) {
markAsReadLocally(item)
@ -334,7 +346,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun updateRemote(): Boolean {
return if (isNetworkAvailable()) {
api.update()?.equals("finished") ?: false
api.update()?.equals("finished")
} else {
false
}
@ -367,8 +379,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) {
val fetchedVersion = api.version()
if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.getApiMajorVersion())
if (fetchedVersion.success && fetchedVersion.data != null && fetchedVersion.data.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
}
}
}
@ -429,7 +441,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
val allItems = getMaxItemsForBackground(ItemType.ALL)
val starredItems = getMaxItemsForBackground(ItemType.STARRED)
insertDBItems(newItems.orEmpty() + allItems.orEmpty() + starredItems.orEmpty())
insertDBItems(newItems + allItems + starredItems)
return newItems
} catch (e: Throwable) {
// We do nothing

View File

@ -10,6 +10,7 @@ import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
@ -34,7 +35,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
appSettingsService.logApiCalls(message)
}
}
level = LogLevel.ALL
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout()
@ -65,11 +66,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client = createHttpClient()
}
suspend fun login(): SelfossModel.SuccessResponse? =
client.get(url("/login")) {
suspend fun login(): SelfossModel.SuccessResponse =
maybeResponse(client.get(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun getItems(
type: String,
@ -79,8 +80,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
search: String?,
updatedSince: String?,
items: Int? = null
): List<SelfossModel.Item>? =
client.get(url("/items")) {
): SelfossModel.StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
parameter("type", type)
@ -90,74 +91,74 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
}.body()
})
suspend fun stats(): SelfossModel.Stats? =
client.get(url("/stats")) {
suspend fun stats(): SelfossModel.StatusAndData<SelfossModel.Stats> =
bodyOrFailure(client.get(url("/stats")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun tags(): List<SelfossModel.Tag>? =
client.get(url("/tags")) {
suspend fun tags(): SelfossModel.StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure(client.get(url("/tags")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun update(): String? =
client.get(url("/update")) {
suspend fun update(): SelfossModel.StatusAndData<String> =
bodyOrFailure(client.get(url("/update")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun spouts(): Map<String, SelfossModel.Spout>? =
client.get(url("/sources/spouts")) {
suspend fun spouts(): SelfossModel.StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure(client.get(url("/sources/spouts")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun sources(): ArrayList<SelfossModel.Source>? =
client.get(url("/sources/list")) {
suspend fun sources(): SelfossModel.StatusAndData<ArrayList<SelfossModel.Source>> =
bodyOrFailure(client.get(url("/sources/list")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun version(): SelfossModel.ApiVersion? =
client.get(url("/api/about")).body()
suspend fun version(): SelfossModel.StatusAndData<SelfossModel.ApiVersion> =
bodyOrFailure(client.get(url("/api/about")))
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/mark/$id")) {
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/mark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unmark/$id")) {
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unmark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun starr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/starr/$id")) {
suspend fun starr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/starr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unstarr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unstarr/$id")) {
suspend fun unstarr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unstarr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? =
client.submitForm(
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse =
maybeResponse(client.submitForm(
url = url("/mark"),
formParameters = Parameters.build {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
ids.map { append("ids[]", it) }
}
).body()
))
suspend fun createSourceForVersion(
title: String,
@ -166,12 +167,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
tags: String,
filter: String,
version: Int
): SelfossModel.SuccessResponse? =
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
): SelfossModel.SuccessResponse =
maybeResponse(
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
)
suspend fun createSource(
title: String,
@ -179,7 +182,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@ -189,7 +192,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags", tags)
append("filter", filter)
}
).body()
)
suspend fun createSource2(
title: String,
@ -197,7 +200,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@ -207,11 +210,27 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags[]", tags)
append("filter", filter)
}
).body()
)
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? =
client.delete(url("/source/$id")) {
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse =
maybeResponse(client.delete(url("/source/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse {
return if (r.status.isSuccess()) {
r.body()
} else {
SelfossModel.SuccessResponse(false)
}
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData<T> {
return if (r.status.isSuccess()) {
SelfossModel.StatusAndData.succes(r.body())
} else {
SelfossModel.StatusAndData.error()
}
}
}

14
version.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
echo "Creating tag $1"
TAG="v$1"
git tag -a ${TAG} -m ${TAG}
if [[ "$@" == *'--from-ci'* ]]
then
echo "Tag created. HANDLE IN CI"
else
echo "Pushing tag"
git push origin ${TAG}
fi