add TestColumnMeta
This commit is contained in:
parent
5e418a22c4
commit
92b39a464a
|
@ -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)
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.util.*
|
|||
class ActMutedPseudoAccount : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LogCategory("ActMutedPseudoAccount")
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.util.*
|
|||
class ActMutedWord : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LogCategory("ActMutedWord")
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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を除去する
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue