add TestColumnMeta

This commit is contained in:
tateisu 2021-06-28 03:25:21 +09:00
parent 5e418a22c4
commit 92b39a464a
11 changed files with 205 additions and 208 deletions

View File

@ -539,13 +539,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
return Pair(result, null)
}
if (!client.sendRequest(
result,
tmpOkhttpClient = App1.ok_http_client_media_viewer
) {
if (!client.sendRequest(result, tmpOkhttpClient = App1.ok_http_client_media_viewer) {
request
}
) return Pair(result, null)
}) return Pair(result, null)
if (client.isApiCancelled) return Pair(null, null)
@ -637,15 +633,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
internal class DownloadHistory(val time: Long, val url: String)
private fun download(ta: TootAttachmentLike) {
val permissionCheck = ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
preparePermission()
return
}
if (!checkPermission()) return
val downLoadManager: DownloadManager = systemService(this)
?: error("missing DownloadManager system service")
@ -808,37 +796,30 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
private fun preparePermission() {
private fun checkPermission(): Boolean {
val permissionCheck = ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) return true
if (Build.VERSION.SDK_INT >= 23) {
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE
this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE
)
} else {
showToast(true, R.string.missing_permission_to_access_media)
}
return false
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray,
) {
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
var bNotGranted = false
var i = 0
val ie = permissions.size
while (i < ie) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
bNotGranted = true
}
++i
}
if (bNotGranted) {
showToast(true, R.string.missing_permission_to_access_media)
} else {
download(mediaList[idx])
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == PERMISSION_REQUEST_CODE) {
when (permissions.indices.all { grantResults[it] == PackageManager.PERMISSION_GRANTED }) {
false -> showToast(true, R.string.missing_permission_to_access_media)
else -> download(mediaList[idx])
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

View File

@ -19,7 +19,6 @@ import java.util.*
class ActMutedPseudoAccount : AppCompatActivity() {
companion object {
private val log = LogCategory("ActMutedPseudoAccount")
}

View File

@ -19,7 +19,6 @@ import java.util.*
class ActMutedWord : AppCompatActivity() {
companion object {
private val log = LogCategory("ActMutedWord")
}

View File

@ -1,6 +1,6 @@
package jp.juggler.subwaytooter
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
@ -29,9 +29,6 @@ import jp.juggler.subwaytooter.view.MyEditText
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
import kotlinx.coroutines.Job
import okhttp3.Request
import okhttp3.internal.closeQuietly
import ru.gildor.coroutines.okhttp.await
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
@ -45,113 +42,56 @@ class ActPost : AppCompatActivity(),
var refActPost: WeakReference<ActPost>? = null
internal const val EXTRA_POSTED_ACCT = "posted_acct"
internal const val EXTRA_POSTED_STATUS_ID = "posted_status_id"
internal const val EXTRA_POSTED_REPLY_ID = "posted_reply_id"
internal const val EXTRA_POSTED_REDRAFT_ID = "posted_redraft_id"
internal const val EXTRA_MULTI_WINDOW = "multiWindow"
const val EXTRA_POSTED_ACCT = "posted_acct"
const val EXTRA_POSTED_STATUS_ID = "posted_status_id"
const val EXTRA_POSTED_REPLY_ID = "posted_reply_id"
const val EXTRA_POSTED_REDRAFT_ID = "posted_redraft_id"
const val EXTRA_MULTI_WINDOW = "multiWindow"
internal const val KEY_ACCOUNT_DB_ID = "account_db_id"
internal const val KEY_REPLY_STATUS = "reply_status"
internal const val KEY_REDRAFT_STATUS = "redraft_status"
internal const val KEY_INITIAL_TEXT = "initial_text"
internal const val KEY_SHARED_INTENT = "sent_intent"
internal const val KEY_QUOTE = "quote"
internal const val KEY_SCHEDULED_STATUS = "scheduled_status"
const val KEY_ACCOUNT_DB_ID = "account_db_id"
const val KEY_REPLY_STATUS = "reply_status"
const val KEY_REDRAFT_STATUS = "redraft_status"
const val KEY_INITIAL_TEXT = "initial_text"
const val KEY_SHARED_INTENT = "sent_intent"
const val KEY_QUOTE = "quote"
const val KEY_SCHEDULED_STATUS = "scheduled_status"
internal const val KEY_ATTACHMENT_LIST = "attachment_list"
internal const val KEY_IN_REPLY_TO_ID = "in_reply_to_id"
internal const val KEY_IN_REPLY_TO_TEXT = "in_reply_to_text"
internal const val KEY_IN_REPLY_TO_IMAGE = "in_reply_to_image"
const val KEY_ATTACHMENT_LIST = "attachment_list"
const val KEY_IN_REPLY_TO_ID = "in_reply_to_id"
const val KEY_IN_REPLY_TO_TEXT = "in_reply_to_text"
const val KEY_IN_REPLY_TO_IMAGE = "in_reply_to_image"
const val STATE_ALL = "all"
/////////////////////////////////////////////////
fun createIntent(
activity: Activity,
context: Context,
accountDbId: Long,
multiWindowMode: Boolean,
// 再編集する投稿。アカウントと同一のタンスであること
redraftStatus: TootStatus? = null,
// 返信対象の投稿。同一タンス上に同期済みであること
replyStatus: TootStatus? = null,
//初期テキスト
initialText: String? = null,
// 外部アプリから共有されたインテント
sharedIntent: Intent? = null,
// 返信ではなく引用トゥートを作成する
quote: Boolean = false,
//(Mastodon) 予約投稿の編集
scheduledStatus: TootScheduled? = null,
) = Intent(activity, ActPost::class.java).apply {
) = Intent(context, ActPost::class.java).apply {
putExtra(EXTRA_MULTI_WINDOW, multiWindowMode)
putExtra(KEY_ACCOUNT_DB_ID, accountDbId)
if (redraftStatus != null) {
putExtra(KEY_REDRAFT_STATUS, redraftStatus.json.toString())
}
if (replyStatus != null) {
putExtra(KEY_REPLY_STATUS, replyStatus.json.toString())
initialText?.let { putExtra(KEY_INITIAL_TEXT, it) }
redraftStatus?.let { putExtra(KEY_REDRAFT_STATUS, it.json.toString()) }
replyStatus?.let {
putExtra(KEY_REPLY_STATUS, it.json.toString())
putExtra(KEY_QUOTE, quote)
}
if (initialText != null) {
putExtra(KEY_INITIAL_TEXT, initialText)
}
if (sharedIntent != null) {
putExtra(KEY_SHARED_INTENT, sharedIntent)
}
if (scheduledStatus != null) {
putExtra(KEY_SCHEDULED_STATUS, scheduledStatus.src.toString())
}
}
internal suspend fun checkExist(url: String?): Boolean {
if (url?.isEmpty() != false) return false
try {
val request = Request.Builder().url(url).build()
val call = App1.ok_http_client.newCall(request)
val response = call.await()
try {
if (response.isSuccessful) return true
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
} finally {
response.closeQuietly()
}
} catch (ex: Throwable) {
log.trace(ex)
}
return false
}
fun Double?.finiteOrZero(): Double = if (this?.isFinite() == true) this else 0.0
// poll type string to spinner index
fun String?.toPollTypeIndex() = when (this) {
"mastodon" -> 1
"friendsNico" -> 2
else -> 0
}
fun Int?.toPollTypeString() = when (this) {
1 -> "mastodon"
2 -> "friendsNico"
else -> ""
sharedIntent?.let { putExtra(KEY_SHARED_INTENT, it) }
scheduledStatus?.let { putExtra(KEY_SCHEDULED_STATUS, it.src.toString()) }
}
}
@ -180,7 +120,7 @@ class ActPost : AppCompatActivity(),
lateinit var etExpireMinutes: EditText
lateinit var tvCharCount: TextView
internal lateinit var handler: Handler
lateinit var handler: Handler
lateinit var formRoot: ActPostRootLinearLayout
lateinit var llReply: View
@ -192,37 +132,37 @@ class ActPost : AppCompatActivity(),
lateinit var ibSchedule: ImageButton
lateinit var ibScheduleReset: ImageButton
internal lateinit var pref: SharedPreferences
internal lateinit var appState: AppState
lateinit var pref: SharedPreferences
lateinit var appState: AppState
lateinit var attachmentUploader: AttachmentUploader
lateinit var attachmentPicker: AttachmentPicker
lateinit var completionHelper: CompletionHelper
var density: Float = 0f
///////////////////////////////////////////////////
// SavedAccount.acctAscii => FeaturedTagCache
val featuredTagCache = ConcurrentHashMap<String, FeaturedTagCache>()
// background job
var jobFeaturedTag: WeakReference<Job>? = null
var jobMaxCharCount: WeakReference<Job>? = null
///////////////////////////////////////////////////
var states = ActPostStates()
internal var account: SavedAccount? = null
var accountList: ArrayList<SavedAccount> = ArrayList()
var account: SavedAccount? = null
var attachmentList = ArrayList<PostAttachment>()
var isPostComplete: Boolean = false
internal var density: Float = 0f
var accountList: ArrayList<SavedAccount> = ArrayList()
var scheduledStatus: TootScheduled? = null
// key is SavedAccount.acctAscii
val featuredTagCache = ConcurrentHashMap<String, FeaturedTagCache>()
var jobFeaturedTag: WeakReference<Job>? = null
var jobMaxCharCount: WeakReference<Job>? = null
// カスタムサムネイルを指定する添付メディア
var paThumbnailTarget: PostAttachment? = null
val scrollListener: ViewTreeObserver.OnScrollChangedListener =
ViewTreeObserver.OnScrollChangedListener { completionHelper.onScrollChanged() }
/////////////////////////////////////////////////////////////////////
val isMultiWindowPost: Boolean
get() = intent.getBooleanExtra(EXTRA_MULTI_WINDOW, false)
@ -338,8 +278,10 @@ class ActPost : AppCompatActivity(),
}
override fun onBackPressed() {
saveDraft()
super.onBackPressed()
// 戻るボタンを押したときとonPauseで2回保存することになるが、
// 同じ内容はDB上は重複しないはず…
saveDraft()
}
override fun onClick(v: View) {
@ -495,11 +437,9 @@ class ActPost : AppCompatActivity(),
)
tvCharCount = findViewById(R.id.tvCharCount)
llReply = findViewById(R.id.llReply)
tvReplyTo = findViewById(R.id.tvReplyTo)
ivReply = findViewById(R.id.ivReply)
tvSchedule = findViewById(R.id.tvSchedule)
ibSchedule = findViewById(R.id.ibSchedule)
ibScheduleReset = findViewById(R.id.ibScheduleReset)
@ -532,12 +472,8 @@ class ActPost : AppCompatActivity(),
})
val textWatcher: TextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
updateTextCount()
}
@ -549,6 +485,9 @@ class ActPost : AppCompatActivity(),
et.addTextChangedListener(textWatcher)
}
val scrollListener: ViewTreeObserver.OnScrollChangedListener =
ViewTreeObserver.OnScrollChangedListener { completionHelper.onScrollChanged() }
scrollView.viewTreeObserver.addOnScrollChangedListener(scrollListener)
etContent.contentMineTypeArray = AttachmentUploader.acceptableMimeTypes.toTypedArray()

View File

@ -1,8 +1,6 @@
package jp.juggler.subwaytooter
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.ActPost.Companion.toPollTypeIndex
import jp.juggler.subwaytooter.ActPost.Companion.toPollTypeString
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootParser
@ -14,6 +12,8 @@ import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.*
import kotlinx.coroutines.isActive
import okhttp3.Request
import ru.gildor.coroutines.okhttp.await
private val log = LogCategory("ActPostDrafts")
@ -30,7 +30,6 @@ private const val DRAFT_REPLY_ID = "reply_id"
private const val DRAFT_REPLY_TEXT = "reply_text"
private const val DRAFT_REPLY_IMAGE = "reply_image"
private const val DRAFT_REPLY_URL = "reply_url"
private const val DRAFT_IS_ENQUETE = "is_enquete"
private const val DRAFT_POLL_TYPE = "poll_type"
private const val DRAFT_POLL_MULTIPLE = "poll_multiple"
private const val DRAFT_POLL_HIDE_TOTALS = "poll_hide_totals"
@ -39,6 +38,34 @@ private const val DRAFT_POLL_EXPIRE_HOUR = "poll_expire_hour"
private const val DRAFT_POLL_EXPIRE_MINUTE = "poll_expire_minute"
private const val DRAFT_ENQUETE_ITEMS = "enquete_items"
private const val DRAFT_QUOTE = "quotedRenote" // 歴史的な理由で名前がMisskey用になってる
private const val DRAFT_IS_ENQUETE = "is_enquete" // deprecated. old draft may use this.
// poll type string to spinner index
private fun String?.toPollTypeIndex() = when (this) {
"mastodon" -> 1
"friendsNico" -> 2
else -> 0
}
private fun Int?.toPollTypeString() = when (this) {
1 -> "mastodon"
2 -> "friendsNico"
else -> ""
}
private suspend fun checkExist(url: String?): Boolean {
if (url?.isEmpty() != false) return false
try {
val request = Request.Builder().url(url).build()
App1.ok_http_client.newCall(request).await().use { response ->
if (response.isSuccessful) return true
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
}
} catch (ex: Throwable) {
log.trace(ex)
}
return false
}
fun ActPost.saveDraft() {
val content = etContent.text.toString()
@ -93,9 +120,6 @@ fun ActPost.saveDraft() {
states.visibility?.id?.toString()?.let { json.put(DRAFT_VISIBILITY, it) }
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
// deprecated. but still used in old draft.
// json.put(DRAFT_IS_ENQUETE, isEnquete)
PostDraft.save(System.currentTimeMillis(), json)
} catch (ex: Throwable) {
log.trace(ex)
@ -177,7 +201,7 @@ fun ActPost.restoreDraft(draft: JsonObject) {
while (it.hasNext()) {
if (isTaskCancelled()) return@runWithProgress null
val ta = TootAttachment.decodeJson(it.next())
if (ActPost.checkExist(ta.url)) continue
if (checkExist(ta.url)) continue
it.remove()
isSomeAttachmentRemoved = true
// 本文からURLを除去する

View File

@ -1,9 +1,10 @@
package jp.juggler.subwaytooter
import jp.juggler.subwaytooter.ActPost.Companion.finiteOrZero
import jp.juggler.util.notEmpty
import jp.juggler.util.vg
private fun Double?.finiteOrZero(): Double = if (this?.isFinite() == true) this else 0.0
fun ActPost.showPoll() {
val i = spPollType.selectedItemPosition
llEnquete.vg(i != 0)

View File

@ -72,7 +72,7 @@ fun ActMain.openActPostImpl(
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost(pref)
val intent = ActPost.createIntent(
activity = this,
context = this,
accountDbId = accountDbId,
redraftStatus = redraftStatus,
replyStatus = replyStatus,

View File

@ -82,15 +82,15 @@ class PostDraft {
try {
// make hash
val sb = StringBuilder()
json.keys.toMutableList().apply { sort() }.forEach { k ->
val v = json[k]?.toString() ?: "(null)"
sb.append("&")
sb.append(k)
sb.append("=")
sb.append(v)
}
val hash = sb.toString().digestSHA256Hex()
val hash = StringBuilder().also { sb ->
json.keys.sorted().forEach { k ->
val v = json[k]?.toString() ?: "(null)"
sb.append("&")
sb.append(k)
sb.append("=")
sb.append(v)
}
}.toString().digestSHA256Hex()
// save to db
App1.database.replace(table, null, ContentValues().apply {
@ -113,16 +113,14 @@ class PostDraft {
}
}
} catch (ex: Throwable) {
log.trace(ex)
log.e(ex, "hasDraft failed.")
log.trace(ex, "hasDraft failed.")
}
return false
}
fun createCursor(): Cursor? {
try {
return App1.database.query(
return try {
App1.database.query(
table,
null,
null,
@ -132,33 +130,29 @@ class PostDraft {
"$COL_TIME_SAVE desc"
)
} catch (ex: Throwable) {
log.trace(ex)
log.e(ex, "createCursor failed.")
log.trace(ex, "createCursor failed.")
null
}
return null
}
fun loadFromCursor(cursor: Cursor, colIdxArg: ColIdx?, position: Int): PostDraft? {
val colIdx = colIdxArg ?: ColIdx(cursor)
if (!cursor.moveToPosition(position)) {
return if (!cursor.moveToPosition(position)) {
log.d("loadFromCursor: move failed. position=$position")
return null
null
} else {
PostDraft().also { dst ->
val colIdx = colIdxArg ?: ColIdx(cursor)
dst.id = cursor.getLong(colIdx.idx_id)
dst.time_save = cursor.getLong(colIdx.idx_time_save)
dst.hash = cursor.getString(colIdx.idx_hash)
dst.json = try {
cursor.getString(colIdx.idx_json).decodeJsonObject()
} catch (ex: Throwable) {
log.trace(ex)
JsonObject()
}
}
}
val dst = PostDraft()
dst.id = cursor.getLong(colIdx.idx_id)
dst.time_save = cursor.getLong(colIdx.idx_time_save)
try {
dst.json = cursor.getString(colIdx.idx_json).decodeJsonObject()
} catch (ex: Throwable) {
log.trace(ex)
dst.json = JsonObject()
}
dst.hash = cursor.getString(colIdx.idx_hash)
return dst
}
}
}

View File

@ -347,7 +347,7 @@ class SavedAccount(
const val table = "access_info"
private val columnList = ColumnMeta.List(table)
val columnList = ColumnMeta.List(table)
private val COL_ID = ColumnMeta(columnList, 0, BaseColumns._ID, "INTEGER PRIMARY KEY", primary = true)
private val COL_HOST = ColumnMeta(columnList, 0, "h", "text not null")

View File

@ -75,15 +75,20 @@ class ColumnMeta(
fun createParams(): String =
sorted().joinToString(",") { "${it.name} ${it.typeSpec}" }
val maxVersion: Int
get() = this.maxOfOrNull{ it.version} ?: 0
fun addColumnsSql(oldVersion: Int, newVersion: Int) =
sorted()
.filter { oldVersion < it.version && newVersion >= it.version }
.map { "alter table $table add column ${it.name} ${it.typeSpec}" }
fun addColumns(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
for (column in this) {
if (oldVersion < column.version && newVersion >= column.version) {
val sql = "alter table $table add column ${column.name} ${column.typeSpec}"
try {
db.execSQL(sql)
} catch (ex: Throwable) {
log.trace(ex, "addColumns $table ${column.name} failed. $sql")
}
for (sql in addColumnsSql(oldVersion, newVersion)) {
try {
db.execSQL(sql)
} catch (ex: Throwable) {
log.trace(ex, "addColumns failed. $sql")
}
}
}
@ -91,9 +96,13 @@ class ColumnMeta(
// テーブル作成時のソート
override fun compareTo(other: ColumnMeta): Int {
val ia = if (this.primary) 1 else 0
val ib = if (other.primary) 1 else 0
return ia.compareTo(ib).notZero() ?: name.compareTo(other.name)
// プライマリキーを先頭にする
val ia = if (this.primary) -1 else 0
val ib = if (other.primary) -1 else 0
ia.compareTo(ib).notZero()?.let{ return it}
// 残りはカラム名順
return name.compareTo(other.name)
}
override fun toString(): String = name

View File

@ -0,0 +1,51 @@
package jp.juggler.subwaytooter
import jp.juggler.subwaytooter.table.SavedAccount
import org.junit.Assert.assertEquals
import org.junit.Test
class TestColumnMeta {
@Test
fun test1() {
val columnList = SavedAccount.columnList
val actual = columnList.createParams()
val expect =
"_id INTEGER PRIMARY KEY,a text not null,confirm_boost integer default 1,confirm_favourite integer default 1,confirm_follow integer default 1,confirm_follow_locked integer default 1,confirm_post integer default 1,confirm_reaction integer default 1,confirm_unboost integer default 1,confirm_unfavourite integer default 1,confirm_unfollow integer default 1,d text,default_sensitive integer default 0,default_text text default '',dont_hide_nsfw integer default 0,dont_show_timeout integer default 0,expand_cw integer default 0,h text not null,image_max_megabytes text default null,image_resize text default null,is_misskey integer default 0,last_notification_error text,last_push_endpoint text,last_subscription_error text,max_toot_chars integer default 0,movie_max_megabytes text default null,notification_boost integer default 1,notification_favourite integer default 1,notification_follow integer default 1,notification_follow_request integer default 1,notification_mention integer default 1,notification_post integer default 1,notification_reaction integer default 1,notification_server text default '',notification_vote integer default 1,push_policy text default null,register_key text default '',register_time integer default 0,sound_uri text default '',t text not null,u text not null,visibility text"
assertEquals("SavedAccount createParams()", expect, actual)
}
@Test
fun test2() {
val columnList = SavedAccount.columnList
val expectMap = mapOf(
2 to "alter table access_info add column notification_boost integer default 1;alter table access_info add column notification_favourite integer default 1;alter table access_info add column notification_follow integer default 1;alter table access_info add column notification_mention integer default 1",
10 to "alter table access_info add column confirm_follow integer default 1;alter table access_info add column confirm_follow_locked integer default 1;alter table access_info add column confirm_post integer default 1;alter table access_info add column confirm_unfollow integer default 1",
13 to "alter table access_info add column notification_server text default ''",
14 to "alter table access_info add column register_key text default '';alter table access_info add column register_time integer default 0",
16 to "alter table access_info add column sound_uri text default ''",
18 to "alter table access_info add column dont_show_timeout integer default 0",
23 to "alter table access_info add column confirm_favourite integer default 1",
24 to "alter table access_info add column confirm_unboost integer default 1;alter table access_info add column confirm_unfavourite integer default 1",
27 to "alter table access_info add column default_text text default ''",
28 to "alter table access_info add column is_misskey integer default 0",
33 to "alter table access_info add column notification_reaction integer default 1;alter table access_info add column notification_vote integer default 1",
38 to "alter table access_info add column default_sensitive integer default 0;alter table access_info add column expand_cw integer default 0",
39 to "alter table access_info add column max_toot_chars integer default 0",
42 to "alter table access_info add column last_notification_error text",
44 to "alter table access_info add column notification_follow_request integer default 1",
45 to "alter table access_info add column last_subscription_error text",
46 to "alter table access_info add column last_push_endpoint text",
56 to "alter table access_info add column d text",
57 to "alter table access_info add column notification_post integer default 1",
59 to "alter table access_info add column image_max_megabytes text default null;alter table access_info add column image_resize text default null;alter table access_info add column movie_max_megabytes text default null",
60 to "alter table access_info add column push_policy text default null",
61 to "alter table access_info add column confirm_reaction integer default 1",
)
for (newVersion in 1..expectMap.maxOf { it.key }) {
val actualSql = columnList.addColumnsSql(newVersion - 1, newVersion).joinToString(";")
val expectSql = expectMap[newVersion] ?: ""
assertEquals("SavedAccount v$newVersion", expectSql, actualSql)
}
}
}