diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt b/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt index f6cc03792b..2989aa8da3 100644 --- a/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt +++ b/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt @@ -69,6 +69,9 @@ class SpinnerApplicationContext : ApplicationContext() { "megaphones" to DatabaseConfig(db = MegaphoneDatabase.getInstance(this).sqlCipherDatabase), "localmetrics" to DatabaseConfig(db = LocalMetricsDatabase.getInstance(this).sqlCipherDatabase), "logs" to DatabaseConfig(db = LogDatabase.getInstance(this).sqlCipherDatabase), + ), + linkedMapOf( + StorageServicePlugin.PATH to StorageServicePlugin() ) ) diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/StorageServicePlugin.kt b/app/src/spinner/java/org/thoughtcrime/securesms/StorageServicePlugin.kt new file mode 100644 index 0000000000..4b059fd62b --- /dev/null +++ b/app/src/spinner/java/org/thoughtcrime/securesms/StorageServicePlugin.kt @@ -0,0 +1,58 @@ +package org.thoughtcrime.securesms + +import org.signal.spinner.Plugin +import org.signal.spinner.PluginResult +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore + +class StorageServicePlugin : Plugin { + override val name: String = "Storage" + override val path: String = PATH + + override fun get(): PluginResult { + val columns = listOf("Type", "Data") + val rows = mutableListOf>() + + val manager = ApplicationDependencies.getSignalServiceAccountManager() + val storageServiceKey = SignalStore.storageService().orCreateStorageKey + val storageManifestVersion = manager.storageManifestVersion + val manifest = manager.getStorageManifestIfDifferentVersion(storageServiceKey, storageManifestVersion - 1).get() + val signalStorageRecords = manager.readStorageRecords(storageServiceKey, manifest.storageIds) + + for (record in signalStorageRecords) { + val row = mutableListOf() + + if (record.account.isPresent) { + row += "Account" + row += record.account.get().toProto().toString() + } else if (record.contact.isPresent) { + row += "Contact" + row += record.contact.get().toProto().toString() + } else if (record.groupV1.isPresent) { + row += "GV1" + row += record.groupV1.get().toProto().toString() + } else if (record.groupV2.isPresent) { + row += "GV2" + row += record.groupV2.get().toProto().toString() + } else if (record.storyDistributionList.isPresent) { + row += "Distribution List" + row += record.storyDistributionList.get().toProto().toString() + } else { + row += "Unknown" + row += "" + } + rows += row + } + + rows.sortBy { it.first() } + + return PluginResult.TableResult( + columns = columns, + rows = rows + ) + } + + companion object { + const val PATH = "/storage" + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java index c8719b237a..82c91b8dba 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java @@ -268,7 +268,7 @@ public final class SignalAccountRecord implements SignalRecord { return proto.getSubscriptionManuallyCancelled(); } - AccountRecord toProto() { + public AccountRecord toProto() { return proto; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java index b54f595a69..bcdf535dc2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java @@ -191,7 +191,7 @@ public final class SignalContactRecord implements SignalRecord { return proto.getHideStory(); } - ContactRecord toProto() { + public ContactRecord toProto() { return proto; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV1Record.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV1Record.java index 3a5fee9f46..a1e4fb01ba 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV1Record.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV1Record.java @@ -114,7 +114,7 @@ public final class SignalGroupV1Record implements SignalRecord { return proto.getMutedUntilTimestamp(); } - GroupV1Record toProto() { + public GroupV1Record toProto() { return proto; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV2Record.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV2Record.java index 0a6f6d04c1..3b23ed6d36 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV2Record.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalGroupV2Record.java @@ -140,7 +140,7 @@ public final class SignalGroupV2Record implements SignalRecord { return proto.getHideStory(); } - GroupV2Record toProto() { + public GroupV2Record toProto() { return proto; } diff --git a/spinner/app/src/main/java/org/signal/spinnertest/MainActivity.kt b/spinner/app/src/main/java/org/signal/spinnertest/MainActivity.kt index 2995151fa0..40fee9d01f 100644 --- a/spinner/app/src/main/java/org/signal/spinnertest/MainActivity.kt +++ b/spinner/app/src/main/java/org/signal/spinnertest/MainActivity.kt @@ -23,7 +23,8 @@ class MainActivity : AppCompatActivity() { "Name" to "${Build.MODEL} (API ${Build.VERSION.SDK_INT})", "Package" to packageName ), - mapOf("main" to Spinner.DatabaseConfig(db = db)) + mapOf("main" to Spinner.DatabaseConfig(db = db)), + emptyMap() ) } diff --git a/spinner/lib/src/main/assets/partials/prefix.hbs b/spinner/lib/src/main/assets/partials/prefix.hbs index 728a2c698b..fbc0f2eff2 100644 --- a/spinner/lib/src/main/assets/partials/prefix.hbs +++ b/spinner/lib/src/main/assets/partials/prefix.hbs @@ -23,4 +23,7 @@
  • Browse
  • Query
  • Recent
  • + {{#each plugins}} +
  • {{name}}
  • + {{/each}} \ No newline at end of file diff --git a/spinner/lib/src/main/assets/plugin.hbs b/spinner/lib/src/main/assets/plugin.hbs new file mode 100644 index 0000000000..91169668e8 --- /dev/null +++ b/spinner/lib/src/main/assets/plugin.hbs @@ -0,0 +1,28 @@ + + {{> partials/head title=activePlugin.name }} + + + {{> partials/prefix}} + + {{#if (eq "table" pluginResult.type)}} +

    Data

    + {{pluginResult.rowCount}} row(s).
    +
    + + + {{#each pluginResult.columns}} + + {{/each}} + + {{#each pluginResult.rows}} + + {{#each this}} + + {{/each}} + + {{/each}} +
    {{this}}
    {{{this}}}
    + {{/if}} + {{> partials/suffix }} + + diff --git a/spinner/lib/src/main/java/org/signal/spinner/Plugin.kt b/spinner/lib/src/main/java/org/signal/spinner/Plugin.kt new file mode 100644 index 0000000000..8c7e9068d9 --- /dev/null +++ b/spinner/lib/src/main/java/org/signal/spinner/Plugin.kt @@ -0,0 +1,7 @@ +package org.signal.spinner + +interface Plugin { + fun get(): PluginResult + val name: String + val path: String +} diff --git a/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt b/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt new file mode 100644 index 0000000000..2999821bf6 --- /dev/null +++ b/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt @@ -0,0 +1,9 @@ +package org.signal.spinner + +sealed class PluginResult(val type: String) { + data class TableResult( + val columns: List, + val rows: List>, + val rowCount: Int = rows.size, + ) : PluginResult("table") +} diff --git a/spinner/lib/src/main/java/org/signal/spinner/Spinner.kt b/spinner/lib/src/main/java/org/signal/spinner/Spinner.kt index 0c2420c8e1..5f23a8c42d 100644 --- a/spinner/lib/src/main/java/org/signal/spinner/Spinner.kt +++ b/spinner/lib/src/main/java/org/signal/spinner/Spinner.kt @@ -18,9 +18,9 @@ object Spinner { private lateinit var server: SpinnerServer - fun init(application: Application, deviceInfo: Map, databases: Map) { + fun init(application: Application, deviceInfo: Map, databases: Map, plugins: Map) { try { - server = SpinnerServer(application, deviceInfo, databases) + server = SpinnerServer(application, deviceInfo, databases, plugins) server.start() } catch (e: IOException) { Log.w(TAG, "Spinner server hit IO exception!", e) diff --git a/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt b/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt index 800a79b2e2..f4ec0e680e 100644 --- a/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt +++ b/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt @@ -29,7 +29,8 @@ import kotlin.math.min internal class SpinnerServer( private val application: Application, deviceInfo: Map, - private val databases: Map + private val databases: Map, + private val plugins: Map ) : NanoHTTPD(5000) { companion object { @@ -66,7 +67,14 @@ internal class SpinnerServer( session.method == Method.GET && session.uri == "/query" -> getQuery(dbParam, dbConfig.db) session.method == Method.POST && session.uri == "/query" -> postQuery(dbParam, dbConfig, session) session.method == Method.GET && session.uri == "/recent" -> getRecent(dbParam, dbConfig.db) - else -> newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, "Not found") + else -> { + val plugin = plugins[session.uri] + if (plugin != null && session.method == Method.GET) { + getPlugin(dbParam, plugin) + } else { + newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, "Not found") + } + } } } catch (t: Throwable) { Log.e(TAG, t) @@ -93,6 +101,7 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), tables = db.getTables().toTableInfo(), indices = db.getIndexes().toIndexInfo(), triggers = db.getTriggers().toTriggerInfo(), @@ -109,6 +118,7 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), tableNames = db.getTableNames() ) ) @@ -140,6 +150,7 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), tableNames = dbConfig.db.getTableNames(), table = table, queryResult = queryResult, @@ -163,6 +174,7 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), query = "" ) ) @@ -184,6 +196,7 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), recentSql = queries?.reversed() ) ) @@ -202,12 +215,28 @@ internal class SpinnerServer( deviceInfo = deviceInfo, database = dbName, databases = databases.keys.toList(), + plugins = plugins.values.toList(), query = rawQuery, queryResult = dbConfig.db.query(query).toQueryResult(queryStartTime = startTime, columnTransformers = dbConfig.columnTransformers) ) ) } + private fun getPlugin(dbName: String, plugin: Plugin): Response { + return renderTemplate( + "plugin", + PluginPageModel( + environment = environment, + deviceInfo = deviceInfo, + database = dbName, + databases = databases.keys.toList(), + plugins = plugins.values.toList(), + activePlugin = plugin, + pluginResult = plugin.get() + ) + ) + } + private fun internalError(throwable: Throwable): Response { val stackTrace = ExceptionUtil.convertThrowableToString(throwable) .split("\n") @@ -361,6 +390,7 @@ internal class SpinnerServer( val deviceInfo: Map val database: String val databases: List + val plugins: List } data class OverviewPageModel( @@ -368,6 +398,7 @@ internal class SpinnerServer( override val deviceInfo: Map, override val database: String, override val databases: List, + override val plugins: List, val tables: List, val indices: List, val triggers: List, @@ -379,6 +410,7 @@ internal class SpinnerServer( override val deviceInfo: Map, override val database: String, override val databases: List, + override val plugins: List, val tableNames: List, val table: String? = null, val queryResult: QueryResult? = null, @@ -390,6 +422,7 @@ internal class SpinnerServer( override val deviceInfo: Map, override val database: String, override val databases: List, + override val plugins: List, val query: String = "", val queryResult: QueryResult? = null ) : PrefixPageData @@ -399,9 +432,20 @@ internal class SpinnerServer( override val deviceInfo: Map, override val database: String, override val databases: List, + override val plugins: List, val recentSql: List? ) : PrefixPageData + data class PluginPageModel( + override val environment: String, + override val deviceInfo: Map, + override val database: String, + override val databases: List, + override val plugins: List, + val activePlugin: Plugin, + val pluginResult: PluginResult + ) : PrefixPageData + data class QueryResult( val columns: List, val rows: List>,