Skip to content

Commit

Permalink
Revert "Drop support for openHAB 1 (#3725)"
Browse files Browse the repository at this point in the history
This reverts commit 7fb4ec6.
  • Loading branch information
mueller-ma authored Aug 28, 2024
1 parent 9030ef1 commit 23edb08
Show file tree
Hide file tree
Showing 20 changed files with 615 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.openhab.habdroid.model.CloudMessage
import org.openhab.habdroid.model.CloudNotificationAction
import org.openhab.habdroid.model.CloudNotificationId
import org.openhab.habdroid.model.toCloudNotificationAction
import org.openhab.habdroid.model.toIconResource
import org.openhab.habdroid.model.toOH2IconResource
import org.openhab.habdroid.util.map
import org.openhab.habdroid.util.toJsonArrayOrNull

Expand Down Expand Up @@ -58,7 +58,7 @@ class FcmMessageListenerService : FirebaseMessagingService() {
// timestamp, so use the (undocumented) google.sent_time as a time reference
// in that case. If that also isn't present, don't show time at all.
createdTimestamp = data["timestamp"]?.toLong() ?: message.sentTime,
icon = data["icon"].toIconResource(),
icon = data["icon"].toOH2IconResource(),
tag = data["tag"],
actions = actions,
onClickAction = data["on-click"]?.let { CloudNotificationAction("", it) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import org.openhab.habdroid.model.DefaultSitemap
import org.openhab.habdroid.model.ServerConfiguration
import org.openhab.habdroid.model.ServerPath
import org.openhab.habdroid.model.putIconResource
import org.openhab.habdroid.model.toIconResource
import org.openhab.habdroid.model.toOH2IconResource
import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
import org.openhab.habdroid.ui.preference.PreferencesActivity
import org.openhab.habdroid.util.PrefKeys
Expand Down Expand Up @@ -107,7 +107,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() {
val widgetPrefs = ItemUpdateWidget.getPrefsForWidget(context, id)
val icon = widgetPrefs.getStringOrNull(PreferencesActivity.ITEM_UPDATE_WIDGET_ICON)
widgetPrefs.edit {
putIconResource(PreferencesActivity.ITEM_UPDATE_WIDGET_ICON, icon.toIconResource())
putIconResource(PreferencesActivity.ITEM_UPDATE_WIDGET_ICON, icon.toOH2IconResource())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fun JSONObject.toCloudMessage(): CloudMessage? {
title = payload?.optString("title").orEmpty(),
message = payload?.optString("message", "") ?: getString("message"),
createdTimestamp = created,
icon = (payload?.optStringOrNull("icon") ?: optStringOrNull("icon")).toIconResource(),
icon = (payload?.optStringOrNull("icon") ?: optStringOrNull("icon")).toOH2IconResource(),
tag = tag,
actions = payload?.optJSONArray("actions")?.map { it.toCloudNotificationAction() }?.filterNotNull(),
onClickAction = payload?.optStringOrNull("on-click")?.let { CloudNotificationAction("", it) },
Expand Down
23 changes: 17 additions & 6 deletions mobile/src/main/java/org/openhab/habdroid/model/IconResource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.openhab.habdroid.util.getStringOrNull
@Parcelize
data class IconResource internal constructor(
internal val icon: String,
internal val isOh2: Boolean,
internal val customState: String
) : Parcelable {
fun toUrl(context: Context, includeState: Boolean): String {
Expand All @@ -41,6 +42,10 @@ data class IconResource internal constructor(

@VisibleForTesting
fun toUrl(includeState: Boolean, iconFormat: IconFormat, desiredSizePixels: Int): String {
if (!isOh2) {
return "images/$icon.png"
}

var iconSource = "oh"
var iconSet = "classic"
var iconName = "none"
Expand Down Expand Up @@ -114,7 +119,7 @@ data class IconResource internal constructor(
}

fun withCustomState(state: String): IconResource {
return IconResource(icon, state)
return IconResource(icon, isOh2, state)
}

companion object {
Expand All @@ -127,8 +132,9 @@ fun SharedPreferences.getIconResource(key: String): IconResource? {
return try {
val obj = JSONObject(iconString)
val icon = obj.getString("icon")
val isOh2 = obj.getInt("ohversion") == 2
val customState = obj.optString("state")
IconResource(icon, customState)
IconResource(icon, isOh2, customState)
} catch (e: JSONException) {
null
}
Expand All @@ -140,6 +146,7 @@ fun SharedPreferences.Editor.putIconResource(key: String, icon: IconResource?):
} else {
val iconString = JSONObject()
.put("icon", icon.icon)
.put("ohversion", if (icon.isOh2) 2 else 1)
.put("state", icon.customState)
.toString()
putString(key, iconString)
Expand All @@ -150,11 +157,15 @@ fun SharedPreferences.Editor.putIconResource(key: String, icon: IconResource?):
@VisibleForTesting
fun String.isNoneIcon() = "(oh:([a-z]+:)?)?none".toRegex().matches(this)

fun String?.toIconResource(): IconResource? {
return if (isNullOrEmpty() || isNoneIcon()) null else IconResource(this, "")
fun String?.toOH1IconResource(): IconResource? {
return if (isNullOrEmpty() || isNoneIcon()) null else IconResource(this, false, "")
}

fun String?.toOH2IconResource(): IconResource? {
return if (isNullOrEmpty() || isNoneIcon()) null else IconResource(this, true, "")
}

internal fun String?.toWidgetIconResource(
internal fun String?.toOH2WidgetIconResource(
item: Item?,
type: Widget.Type,
hasMappings: Boolean,
Expand Down Expand Up @@ -195,7 +206,7 @@ internal fun String?.toWidgetIconResource(
else -> item.state.asString
}

return IconResource(this, iconState.orEmpty())
return IconResource(this, true, iconState.orEmpty())
}

enum class IconFormat {
Expand Down
43 changes: 43 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/model/Item.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import kotlinx.parcelize.Parcelize
import org.json.JSONException
import org.json.JSONObject
import org.openhab.habdroid.R
import org.openhab.habdroid.util.forEach
import org.openhab.habdroid.util.map
import org.openhab.habdroid.util.mapString
import org.openhab.habdroid.util.optFloatOrNull
import org.openhab.habdroid.util.optStringOrNull
import org.w3c.dom.Node

@Parcelize
data class Item internal constructor(
Expand Down Expand Up @@ -243,6 +245,47 @@ data class Item internal constructor(
}
}

fun Node.toItem(): Item? {
var name: String? = null
var state: String? = null
var link: String? = null
var type = Item.Type.None
var groupType = Item.Type.None
childNodes.forEach { node ->
when (node.nodeName) {
"type" -> type = node.textContent.toItemType()
"groupType" -> groupType = node.textContent.toItemType()
"name" -> name = node.textContent
"state" -> state = node.textContent
"link" -> link = node.textContent
}
}

val finalName = name ?: return null
if (state == "Uninitialized" || state == "Undefined") {
state = null
}

return Item(
name = finalName,
rawLabel = finalName,
category = null,
type = type,
groupType = groupType,
link = link,
readOnly = false,
members = emptyList(),
options = null,
state = state.toParsedState(),
tags = emptyList(),
groupNames = emptyList(),
minimum = null,
maximum = null,
step = null,
linkToMore = null
)
}

@Throws(JSONException::class)
fun JSONObject.toItem(): Item {
val name = getString("name")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ fun JSONObject.toLabeledValue(valueKey: String, labelKey: String): LabeledValue
val value = getString(valueKey)
val valueRelease = optStringOrNull("releaseCommand")
val label = optString(labelKey, value)
val icon = optStringOrNull("icon")?.toIconResource()
val icon = optStringOrNull("icon")?.toOH2IconResource()
return LabeledValue(value, valueRelease, label, icon, optInt("row"), optInt("column"))
}
24 changes: 23 additions & 1 deletion mobile/src/main/java/org/openhab/habdroid/model/LinkedPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package org.openhab.habdroid.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.json.JSONObject
import org.openhab.habdroid.util.forEach
import org.openhab.habdroid.util.optStringOrNull
import org.w3c.dom.Node

/**
* This is a class to hold information about openHAB linked page.
Expand All @@ -41,6 +43,26 @@ data class LinkedPage(
}
}

fun Node.toLinkedPage(): LinkedPage? {
var id: String? = null
var title: String? = null
var icon: String? = null
var link: String? = null

childNodes.forEach { node ->
when (node.nodeName) {
"id" -> id = node.textContent
"title" -> title = node.textContent
"icon" -> icon = node.textContent
"link" -> link = node.textContent
}
}

val finalId = id ?: return null
val finalLink = link ?: return null
return LinkedPage.build(finalId, title, icon.toOH1IconResource(), finalLink)
}

fun JSONObject?.toLinkedPage(): LinkedPage? {
if (this == null) {
return null
Expand All @@ -49,7 +71,7 @@ fun JSONObject?.toLinkedPage(): LinkedPage? {
return LinkedPage.build(
getString("id"),
optStringOrNull("title"),
icon.toIconResource(),
icon.toOH2IconResource(),
getString("link")
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,26 @@ package org.openhab.habdroid.model

import android.os.Parcelable
import android.util.Log
import java.io.IOException
import java.io.StringReader
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
import kotlinx.parcelize.Parcelize
import okhttp3.Request
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.openhab.habdroid.core.connection.Connection
import org.openhab.habdroid.util.HttpClient
import org.xml.sax.InputSource
import org.xml.sax.SAXException

@Parcelize
data class ServerProperties(val flags: Int, val sitemaps: List<Sitemap>) : Parcelable {
fun hasJsonApi(): Boolean {
return flags and SERVER_FLAG_JSON_REST_API != 0
}

fun hasSseSupport(): Boolean {
return flags and SERVER_FLAG_SSE_SUPPORT != 0
}
Expand All @@ -40,6 +50,7 @@ data class ServerProperties(val flags: Int, val sitemaps: List<Sitemap>) : Parce
companion object {
private val TAG = ServerProperties::class.java.simpleName

const val SERVER_FLAG_JSON_REST_API = 1 shl 0
const val SERVER_FLAG_SSE_SUPPORT = 1 shl 1
const val SERVER_FLAG_ICON_FORMAT_SUPPORT = 1 shl 2
const val SERVER_FLAG_CHART_SCALING_SUPPORT = 1 shl 3
Expand Down Expand Up @@ -76,7 +87,12 @@ data class ServerProperties(val flags: Int, val sitemaps: List<Sitemap>) : Parce
val result = client.get("rest/").asText()
try {
val resultJson = JSONObject(result.response)
var flags = (SERVER_FLAG_ICON_FORMAT_SUPPORT or SERVER_FLAG_CHART_SCALING_SUPPORT)
// If this succeeded, we're talking to OH2
var flags = (
SERVER_FLAG_JSON_REST_API
or SERVER_FLAG_ICON_FORMAT_SUPPORT
or SERVER_FLAG_CHART_SCALING_SUPPORT
)
try {
val version = resultJson.getString("version").toInt()
Log.i(TAG, "Server has rest api version $version")
Expand Down Expand Up @@ -114,21 +130,48 @@ data class ServerProperties(val flags: Int, val sitemaps: List<Sitemap>) : Parce

FlagsSuccess(flags)
} catch (e: JSONException) {
FlagsFailure(result.request, 200, e)
if (result.response.startsWith("<?xml")) {
// We're talking to an OH1 instance
FlagsSuccess(0)
} else {
FlagsFailure(result.request, 200, e)
}
}
} catch (e: HttpClient.HttpException) {
FlagsFailure(e.request, e.statusCode, e)
}

private suspend fun fetchSitemaps(client: HttpClient, flags: Int): PropsResult = try {
val result = client.get("rest/sitemaps").asText()
val sitemaps = loadSitemapsFromJson(result.response)
// OH1 returns XML, later versions return JSON
val sitemaps = if (flags and SERVER_FLAG_JSON_REST_API != 0) {
loadSitemapsFromJson(result.response)
} else {
loadSitemapsFromXml(result.response)
}

Log.d(TAG, "Server returned sitemaps: $sitemaps")
PropsSuccess(ServerProperties(flags, sitemaps))
} catch (e: HttpClient.HttpException) {
PropsFailure(e.request, e.statusCode, e)
}

private fun loadSitemapsFromXml(response: String): List<Sitemap> {
val dbf = DocumentBuilderFactory.newInstance()
try {
val builder = dbf.newDocumentBuilder()
val sitemapsXml = builder.parse(InputSource(StringReader(response)))
return sitemapsXml.toSitemapList()
} catch (e: ParserConfigurationException) {
Log.e(TAG, "Failed parsing sitemap XML", e)
} catch (e: SAXException) {
Log.e(TAG, "Failed parsing sitemap XML", e)
} catch (e: IOException) {
Log.e(TAG, "Failed parsing sitemap XML", e)
}
return emptyList()
}

private fun loadSitemapsFromJson(response: String): List<Sitemap> {
return try {
val jsonArray = JSONArray(response)
Expand Down
35 changes: 34 additions & 1 deletion mobile/src/main/java/org/openhab/habdroid/model/Sitemap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import kotlinx.parcelize.Parcelize
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.openhab.habdroid.util.forEach
import org.openhab.habdroid.util.optStringOrNull
import org.w3c.dom.Document
import org.w3c.dom.Node

@Parcelize
data class Sitemap internal constructor(
Expand All @@ -29,13 +32,43 @@ data class Sitemap internal constructor(
val homepageLink: String
) : Parcelable

fun Node.toSitemap(): Sitemap? {
var label: String? = null
var name: String? = null
var icon: String? = null
var homepageLink: String? = null

childNodes.forEach { node ->
when (node.nodeName) {
"name" -> name = node.textContent
"label" -> label = node.textContent
"icon" -> icon = node.textContent
"homepage" ->
node.childNodes.forEach { pageNode ->
if (pageNode.nodeName == "link") {
homepageLink = pageNode.textContent
}
}
}
}

val finalName = name ?: return null
val finalLink = homepageLink ?: return null
return Sitemap(finalName, label ?: finalName, icon.toOH1IconResource(), finalLink)
}

fun JSONObject.toSitemap(): Sitemap? {
val name = optStringOrNull("name") ?: return null
val homepageLink = optJSONObject("homepage")?.optStringOrNull("link") ?: return null
val label = optStringOrNull("label")
val icon = optStringOrNull("icon")

return Sitemap(name, label ?: name, icon.toIconResource(), homepageLink)
return Sitemap(name, label ?: name, icon.toOH2IconResource(), homepageLink)
}

fun Document.toSitemapList(): List<Sitemap> {
val sitemapNodes = getElementsByTagName("sitemap")
return (0 until sitemapNodes.length).mapNotNull { index -> sitemapNodes.item(index).toSitemap() }
}

fun JSONArray.toSitemapList(): List<Sitemap> {
Expand Down
Loading

0 comments on commit 23edb08

Please sign in to comment.