more split...

This commit is contained in:
tateisu 2021-06-24 13:49:27 +09:00
parent 9395774a48
commit 3eccf01edd
27 changed files with 566 additions and 743 deletions

View File

@ -14,6 +14,8 @@ import org.jetbrains.anko.backgroundDrawable
import kotlin.math.abs
import kotlin.math.min
private val log = LogCategory("ActMainColumns")
// スマホモードなら現在のカラムを、タブレットモードなら-1Lを返す
// (カラム一覧画面のデフォルト選択位置に使われる)
val ActMain.currentColumn: Int
@ -348,13 +350,13 @@ fun ActMain.scrollToColumn(index: Int, smoothScroll: Boolean = true) {
// スマホはスムーススクロール基本ありだがたまにしない
{ env ->
ActMain.log.d("ipLastColumnPos beforeScroll=${env.pager.currentItem}")
log.d("ipLastColumnPos beforeScroll=${env.pager.currentItem}")
env.pager.setCurrentItem(index, smoothScroll)
},
// タブレットでスムーススクロールさせると頻繁にオーバーランするので絶対しない
{ env ->
ActMain.log.d("ipLastColumnPos beforeScroll=${env.visibleColumnsIndices.first}")
log.d("ipLastColumnPos beforeScroll=${env.visibleColumnsIndices.first}")
env.tabletPager.scrollToPosition(index)
}
)
@ -371,7 +373,7 @@ fun ActMain.resizeColumnWidth(views: TabletViews) {
columnWMinDp = iv
}
} catch (ex: Throwable) {
ActMain.log.trace(ex)
log.trace(ex)
}
}

View File

@ -6,10 +6,7 @@ import android.util.JsonReader
import android.view.WindowManager
import androidx.annotation.WorkerThread
import jp.juggler.subwaytooter.notification.PollingWorker
import jp.juggler.util.launchMain
import jp.juggler.util.runOnMainLooper
import jp.juggler.util.runWithProgress
import jp.juggler.util.showToast
import jp.juggler.util.*
import kotlinx.coroutines.delay
import org.apache.commons.io.IOUtils
import java.io.File
@ -19,6 +16,8 @@ import java.io.InputStreamReader
import java.util.ArrayList
import java.util.zip.ZipInputStream
private val log = LogCategory("ActMainImportAppData")
@WorkerThread
fun ActMain.importAppData(uri: Uri) {
launchMain {
@ -72,7 +71,7 @@ fun ActMain.importAppData(uri: Uri) {
PollingWorker.queueAppDataImportBefore(this@importAppData)
while (PollingWorker.mBusyAppDataImportBefore.get()) {
delay(1000L)
ActMain.log.d("syncing polling task...")
log.d("syncing polling task...")
}
// データを読み込む
@ -109,7 +108,7 @@ fun ActMain.importAppData(uri: Uri) {
}
}
} catch (ex: Throwable) {
ActMain.log.trace(ex)
log.trace(ex)
if (zipEntryCount != 0) {
showToast(ex, "importAppData failed.")
}

View File

@ -376,7 +376,7 @@ class ActPost : AppCompatActivity(),
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
val rv = super.onKeyUp(keyCode, event)
if (event?.isCtrlPressed == true) {
ActMain.log.d("onKeyUp code=$keyCode rv=$rv")
log.d("onKeyUp code=$keyCode rv=$rv")
when (keyCode) {
KeyEvent.KEYCODE_T -> btnPost.performClick()
}

View File

@ -177,6 +177,10 @@ object AppDataExporter {
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
if (name == null) {
reader.skipValue()
continue
}
if (BaseColumns._ID == name) {
old_id = reader.nextLong()
@ -191,9 +195,9 @@ object AppDataExporter {
}
// リアルタイム通知に関連する項目は読み飛ばす
if (SavedAccount.COL_NOTIFICATION_TAG == name ||
SavedAccount.COL_REGISTER_KEY == name ||
SavedAccount.COL_REGISTER_TIME == name
if (SavedAccount.COL_NOTIFICATION_TAG.name == name ||
SavedAccount.COL_REGISTER_KEY.name == name ||
SavedAccount.COL_REGISTER_TIME.name == name
) {
reader.skipValue()
continue

View File

@ -700,7 +700,7 @@ fun ItemViewHolder.showScheduled(item: TootScheduled) {
tvContentWarning.text = decodedSpoilerText
spoilerInvalidator.register(decodedSpoilerText)
val cwShown = ContentWarning.isShown(item.uri, accessInfo.expand_cw)
showContent(cwShown)
setContentVisibility(cwShown)
}
else -> {

View File

@ -179,7 +179,8 @@ private fun ItemViewHolder.showSpoilerTextAndContent(status: TootStatus) {
}
}
private fun ItemViewHolder.setContentVisibility(shown: Boolean) {
// 予約投稿でも使う
fun ItemViewHolder.setContentVisibility(shown: Boolean) {
llContents.visibility = if (shown) View.VISIBLE else View.GONE
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
statusShowing?.let { status ->

View File

@ -1,11 +1,7 @@
package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.b2i
import jp.juggler.util.JsonObject
import jp.juggler.util.LogCategory
import jp.juggler.util.asciiPattern
import jp.juggler.util.groupEx
import jp.juggler.util.*
import java.util.*
class TootList(parser: TootParser, src: JsonObject) : TimelineItem(), Comparable<TootList> {

View File

@ -44,121 +44,281 @@ class TaskRunner(
val notificationManager = pollingWorker.notificationManager
val pref = pollingWorker.pref
val threadList = LinkedList<AccountRunner>()
val errorInstance = ArrayList<String>()
private fun createErrorNotification(instanceList: ArrayList<String>) {
if (instanceList.isEmpty()) return
// 通知タップ時のPendingIntent
val clickIntent = Intent(context, ActCallback::class.java)
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val clickPi = PendingIntent.getActivity(
context,
3,
clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
)
val builder = if (Build.VERSION.SDK_INT >= 26) {
// Android 8 から、通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
val channel = NotificationHelper.createNotificationChannel(
context,
"ErrorNotification",
"Error",
null,
2 /* NotificationManager.IMPORTANCE_LOW */
)
NotificationCompat.Builder(context, channel.id)
} else {
NotificationCompat.Builder(context, "not_used")
}
builder
.setContentIntent(clickPi)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
.setColor(
ContextCompat.getColor(
context,
R.color.Light_colorAccent
)
) // ここは常に白テーマの色を使う
.setWhen(System.currentTimeMillis())
.setGroup(context.packageName + ":" + "Error")
val header = context.getString(R.string.error_notification_title)
val summary = context.getString(R.string.error_notification_summary)
builder
.setContentTitle(header)
.setContentText(summary + ": " + instanceList[0])
val style = NotificationCompat.InboxStyle()
.setBigContentTitle(header)
.setSummaryText(summary)
for (i in 0..4) {
if (i >= instanceList.size) break
style.addLine(instanceList[i])
}
builder.setStyle(style)
notificationManager.notify(PollingWorker.NOTIFICATION_ID_ERROR, builder.build())
}
private fun NotificationData.getNotificationLine(): String {
val name = when (PrefB.bpShowAcctInSystemNotification(pref)) {
false -> notification.accountRef?.decoded_display_name
true -> {
val acctPretty = notification.accountRef?.get()?.acct?.pretty
if (acctPretty?.isNotEmpty() == true) {
"@$acctPretty"
} else {
null
}
}
} ?: "?"
return "- " + when (notification.type) {
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY,
->
context.getString(R.string.display_name_replied_by, name)
TootNotification.TYPE_RENOTE,
TootNotification.TYPE_REBLOG,
->
context.getString(R.string.display_name_boosted_by, name)
TootNotification.TYPE_QUOTE ->
context.getString(R.string.display_name_quoted_by, name)
TootNotification.TYPE_STATUS ->
context.getString(R.string.display_name_posted_by, name)
TootNotification.TYPE_FOLLOW ->
context.getString(R.string.display_name_followed_by, name)
TootNotification.TYPE_UNFOLLOW ->
context.getString(R.string.display_name_unfollowed_by, name)
TootNotification.TYPE_FAVOURITE ->
context.getString(R.string.display_name_favourited_by, name)
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION,
->
context.getString(R.string.display_name_reaction_by, name)
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY,
->
context.getString(R.string.display_name_voted_by, name)
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
->
context.getString(R.string.display_name_follow_request_by, name)
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY ->
context.getString(R.string.display_name_follow_request_accepted_by, name)
TootNotification.TYPE_POLL ->
context.getString(R.string.end_of_polling_from, name)
else -> "?"
}
}
private fun deleteCacheData(dbId: Long?) {
if (dbId != null) {
log.d("Notification clear! db_id=$dbId")
SavedAccount.loadAccount(context, dbId) ?: return
NotificationCache.deleteCache(dbId)
}
}
private fun beforePolling(): Boolean {
// タスクによってはポーリング前にすることがある
@Suppress("NON_EXHAUSTIVE_WHEN")
when (taskId) {
TaskId.BootCompleted ->
NotificationTracking.resetPostAll()
TaskId.PackageReplaced ->
NotificationTracking.resetPostAll()
TaskId.DataInjected ->
pollingWorker.processInjectedData(job.injectedAccounts)
TaskId.ResetTrackingState ->
NotificationTracking.resetTrackingState(taskData.long(PollingWorker.EXTRA_DB_ID))
// プッシュ通知が届いた
TaskId.FcmMessage -> {
var bDone = false
val tag = taskData.string(PollingWorker.EXTRA_TAG)
if (tag != null) {
if (tag.startsWith("acct<>")) {
val acct = tag.substring(6)
val sa = SavedAccount.loadAccountByAcct(context, acct)
if (sa != null) {
NotificationCache.resetLastLoad(sa.db_id)
job.injectedAccounts.add(sa.db_id)
bDone = true
}
}
if (!bDone) {
for (sa in SavedAccount.loadByTag(context, tag)) {
NotificationCache.resetLastLoad(sa.db_id)
job.injectedAccounts.add(sa.db_id)
bDone = true
}
}
}
if (!bDone) {
// タグにマッチする情報がなかった場合、全部読み直す
NotificationCache.resetLastLoad()
}
}
TaskId.Clear -> {
deleteCacheData(taskData.long(PollingWorker.EXTRA_DB_ID))
}
TaskId.NotificationDelete -> {
val dbId = taskData.long(PollingWorker.EXTRA_DB_ID)
val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID)
log.d("Notification deleted! db_id=$dbId,type=$type,id=$id")
if (dbId != null) {
NotificationTracking.updateRead(dbId, typeName)
}
return false
}
TaskId.NotificationClick -> {
val dbId = taskData.long(PollingWorker.EXTRA_DB_ID)
val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID).notEmpty()
log.d("Notification clicked! db_id=$dbId,type=$type,id=$id")
if (dbId != null) {
// 通知をキャンセル
val notificationTag = when (typeName) {
"" -> "$dbId/_"
else -> "$dbId/$typeName"
}
if (id != null) {
val itemTag = "$notificationTag/$id"
notificationManager.cancel(itemTag, PollingWorker.NOTIFICATION_ID)
} else {
notificationManager.cancel(
notificationTag,
PollingWorker.NOTIFICATION_ID
)
}
// DB更新処理
NotificationTracking.updateRead(dbId, typeName)
}
return false
}
}
return true
}
private suspend fun prepareInstallId() {
// インストールIDを生成する
// インストールID生成時にSavedAccountテーブルを操作することがあるので
// アカウントリストの取得より先に行う
if (job.installId == null) {
PollingWorker.workerStatus = "make install id"
job.installId = PollingWorker.prepareInstallId(context, job)
}
}
private suspend fun startForAccount(sa: SavedAccount) {
if (sa.isPseudo) return
threadList.add(AccountRunner(sa).apply { start() })
}
private suspend fun waitAllAccounts() {
while (true) {
// 同じホスト名が重複しないようにSetに集める
val liveSet = TreeSet<Host>()
for (t in threadList) {
if (!t.isActive) continue
if (job.isJobCancelled) t.cancel()
liveSet.add(t.account.apiHost)
}
if (liveSet.isEmpty()) break
PollingWorker.workerStatus =
"waiting ${liveSet.joinToString(", ") { it.pretty }}"
delay(if (job.isJobCancelled) 100L else 1000L)
}
}
suspend fun runTask() {
workerStatus = "start task $taskId"
coroutineScope {
try {
// タスクによってはポーリング前にすることがある
@Suppress("NON_EXHAUSTIVE_WHEN")
when (taskId) {
if (!beforePolling()) return@coroutineScope
TaskId.BootCompleted ->
NotificationTracking.resetPostAll()
TaskId.PackageReplaced ->
NotificationTracking.resetPostAll()
TaskId.DataInjected ->
pollingWorker.processInjectedData(job.injectedAccounts)
TaskId.ResetTrackingState ->
NotificationTracking.resetTrackingState(taskData.long(PollingWorker.EXTRA_DB_ID))
// プッシュ通知が届いた
TaskId.FcmMessage -> {
var bDone = false
val tag = taskData.string(PollingWorker.EXTRA_TAG)
if (tag != null) {
if (tag.startsWith("acct<>")) {
val acct = tag.substring(6)
val sa = SavedAccount.loadAccountByAcct(context, acct)
if (sa != null) {
NotificationCache.resetLastLoad(sa.db_id)
job.injectedAccounts.add(sa.db_id)
bDone = true
}
}
if (!bDone) {
for (sa in SavedAccount.loadByTag(context, tag)) {
NotificationCache.resetLastLoad(sa.db_id)
job.injectedAccounts.add(sa.db_id)
bDone = true
}
}
}
if (!bDone) {
// タグにマッチする情報がなかった場合、全部読み直す
NotificationCache.resetLastLoad()
}
}
TaskId.Clear -> {
deleteCacheData(taskData.long(PollingWorker.EXTRA_DB_ID))
}
TaskId.NotificationDelete -> {
val dbId = taskData.long(PollingWorker.EXTRA_DB_ID)
val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID)
log.d("Notification deleted! db_id=$dbId,type=$type,id=$id")
if (dbId != null) {
NotificationTracking.updateRead(dbId, typeName)
}
return@coroutineScope
}
TaskId.NotificationClick -> {
val dbId = taskData.long(PollingWorker.EXTRA_DB_ID)
val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID).notEmpty()
log.d("Notification clicked! db_id=$dbId,type=$type,id=$id")
if (dbId != null) {
// 通知をキャンセル
val notificationTag = when (typeName) {
"" -> "$dbId/_"
else -> "$dbId/$typeName"
}
if (id != null) {
val itemTag = "$notificationTag/$id"
notificationManager.cancel(itemTag, PollingWorker.NOTIFICATION_ID)
} else {
notificationManager.cancel(
notificationTag,
PollingWorker.NOTIFICATION_ID
)
}
// DB更新処理
NotificationTracking.updateRead(dbId, typeName)
}
return@coroutineScope
}
}
// インストールIDを生成する
// インストールID生成時にSavedAccountテーブルを操作することがあるので
// アカウントリストの取得より先に行う
if (job.installId == null) {
PollingWorker.workerStatus = "make install id"
job.installId = PollingWorker.prepareInstallId(context, job)
}
prepareInstallId()
// アカウント別に処理スレッドを作る
PollingWorker.workerStatus = "create account threads"
val threadList = LinkedList<AccountRunner>()
suspend fun startForAccount(sa: SavedAccount) {
if (sa.isPseudo) return
threadList.add(AccountRunner(sa).apply { start() })
}
if (job.injectedAccounts.isNotEmpty()) {
// 更新対象アカウントが限られているなら、そのdb_idだけ処理する
job.injectedAccounts.forEach { dbId ->
@ -169,19 +329,7 @@ class TaskRunner(
SavedAccount.loadAccountList(context).forEach { startForAccount(it) }
}
while (true) {
// 同じホスト名が重複しないようにSetに集める
val liveSet = TreeSet<Host>()
for (t in threadList) {
if (!t.isActive) continue
if (job.isJobCancelled) t.cancel()
liveSet.add(t.account.apiHost)
}
if (liveSet.isEmpty()) break
PollingWorker.workerStatus =
"waiting ${liveSet.joinToString(", ") { it.pretty }}"
delay(if (job.isJobCancelled) 100L else 1000L)
}
waitAllAccounts()
synchronized(errorInstance) {
createErrorNotification(errorInstance)
@ -254,6 +402,15 @@ class TaskRunner(
private val favMuteSet: HashSet<Acct> get() = job.favMuteSet
private val onError: (TootApiResult) -> Unit = { result ->
val sv = result.error
if (sv?.contains("Timeout") == true && !account.dont_show_timeout) {
synchronized(errorInstance) {
if (!errorInstance.any { it == sv }) errorInstance.add(sv)
}
}
}
fun cancel() {
try {
currentCall?.get()?.cancel()
@ -270,15 +427,6 @@ class TaskRunner(
}
}
private val onError: (TootApiResult) -> Unit = { result ->
val sv = result.error
if (sv?.contains("Timeout") == true && !account.dont_show_timeout) {
synchronized(errorInstance) {
if (!errorInstance.any { it == sv }) errorInstance.add(sv)
}
}
}
private suspend fun runSuspend() {
try {
// 疑似アカウントはチェック対象外
@ -708,140 +856,4 @@ class TaskRunner(
}
}
}
private fun createErrorNotification(instanceList: ArrayList<String>) {
if (instanceList.isEmpty()) return
// 通知タップ時のPendingIntent
val clickIntent = Intent(context, ActCallback::class.java)
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val clickPi = PendingIntent.getActivity(
context,
3,
clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
)
val builder = if (Build.VERSION.SDK_INT >= 26) {
// Android 8 から、通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
val channel = NotificationHelper.createNotificationChannel(
context,
"ErrorNotification",
"Error",
null,
2 /* NotificationManager.IMPORTANCE_LOW */
)
NotificationCompat.Builder(context, channel.id)
} else {
NotificationCompat.Builder(context, "not_used")
}
builder
.setContentIntent(clickPi)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
.setColor(
ContextCompat.getColor(
context,
R.color.Light_colorAccent
)
) // ここは常に白テーマの色を使う
.setWhen(System.currentTimeMillis())
.setGroup(context.packageName + ":" + "Error")
val header = context.getString(R.string.error_notification_title)
val summary = context.getString(R.string.error_notification_summary)
builder
.setContentTitle(header)
.setContentText(summary + ": " + instanceList[0])
val style = NotificationCompat.InboxStyle()
.setBigContentTitle(header)
.setSummaryText(summary)
for (i in 0..4) {
if (i >= instanceList.size) break
style.addLine(instanceList[i])
}
builder.setStyle(style)
notificationManager.notify(PollingWorker.NOTIFICATION_ID_ERROR, builder.build())
}
private fun NotificationData.getNotificationLine(): String {
val name = when (PrefB.bpShowAcctInSystemNotification(pref)) {
false -> notification.accountRef?.decoded_display_name
true -> {
val acctPretty = notification.accountRef?.get()?.acct?.pretty
if (acctPretty?.isNotEmpty() == true) {
"@$acctPretty"
} else {
null
}
}
} ?: "?"
return "- " + when (notification.type) {
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY,
->
context.getString(R.string.display_name_replied_by, name)
TootNotification.TYPE_RENOTE,
TootNotification.TYPE_REBLOG,
->
context.getString(R.string.display_name_boosted_by, name)
TootNotification.TYPE_QUOTE ->
context.getString(R.string.display_name_quoted_by, name)
TootNotification.TYPE_STATUS ->
context.getString(R.string.display_name_posted_by, name)
TootNotification.TYPE_FOLLOW ->
context.getString(R.string.display_name_followed_by, name)
TootNotification.TYPE_UNFOLLOW ->
context.getString(R.string.display_name_unfollowed_by, name)
TootNotification.TYPE_FAVOURITE ->
context.getString(R.string.display_name_favourited_by, name)
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION,
->
context.getString(R.string.display_name_reaction_by, name)
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY,
->
context.getString(R.string.display_name_voted_by, name)
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
->
context.getString(R.string.display_name_follow_request_by, name)
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY ->
context.getString(R.string.display_name_follow_request_accepted_by, name)
TootNotification.TYPE_POLL ->
context.getString(R.string.end_of_polling_from, name)
else -> "?"
}
}
private fun deleteCacheData(dbId: Long?) {
if (dbId != null) {
log.d("Notification clear! db_id=$dbId")
SavedAccount.loadAccount(context, dbId) ?: return
NotificationCache.deleteCache(dbId)
}
}
}

View File

@ -7,6 +7,7 @@ import java.util.ArrayList
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
object AcctSet : TableCompanion {

View File

@ -3,10 +3,7 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.util.JsonObject
import jp.juggler.util.LogCategory
import jp.juggler.util.getString
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.*
object ClientInfo : TableCompanion {
private val log = LogCategory("ClientInfo")

View File

@ -6,6 +6,8 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.b2i
import jp.juggler.util.getInt
object ContentWarning : TableCompanion {

View File

@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
object FavMute : TableCompanion {

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.table
import android.database.sqlite.SQLiteDatabase
import jp.juggler.util.TableCompanion
object LogData : TableCompanion {
// private const val TAG = "SubwayTooter"

View File

@ -5,6 +5,8 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.b2i
import jp.juggler.util.getInt
object MediaShown : TableCompanion {

View File

@ -8,6 +8,7 @@ import java.util.HashSet
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
object MutedApp : TableCompanion {

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.WordTrieTree
object MutedWord : TableCompanion {

View File

@ -7,6 +7,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.putMayNull
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.getLong
import jp.juggler.util.minComparable
import java.util.concurrent.ConcurrentHashMap

View File

@ -5,10 +5,7 @@ import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.provider.BaseColumns
import jp.juggler.subwaytooter.App1
import jp.juggler.util.JsonObject
import jp.juggler.util.LogCategory
import jp.juggler.util.digestSHA256Hex
import jp.juggler.util.decodeJsonObject
import jp.juggler.util.*
class PostDraft {

View File

@ -15,6 +15,7 @@ import jp.juggler.subwaytooter.notification.PollingWorker
import jp.juggler.subwaytooter.util.LinkHelper
import jp.juggler.util.*
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.max
class SavedAccount(
@ -195,7 +196,7 @@ class SavedAccount(
this.token_info = token_info
val cv = ContentValues()
cv.put(COL_TOKEN, token_info.toString())
COL_TOKEN.putTo(cv, token_info.toString())
App1.database.update(table, cv, "$COL_ID=?", arrayOf(db_id.toString()))
}
@ -204,40 +205,40 @@ class SavedAccount(
if (db_id == INVALID_DB_ID) error("saveSetting: missing db_id")
val cv = ContentValues()
cv.put(COL_VISIBILITY, visibility.id.toString())
cv.put(COL_CONFIRM_BOOST, confirm_boost.b2i())
cv.put(COL_CONFIRM_FAVOURITE, confirm_favourite.b2i())
cv.put(COL_CONFIRM_UNBOOST, confirm_unboost.b2i())
cv.put(COL_CONFIRM_UNFAVOURITE, confirm_unfavourite.b2i())
COL_VISIBILITY.putTo(cv, visibility.id.toString())
COL_CONFIRM_BOOST.putTo(cv, confirm_boost.b2i())
COL_CONFIRM_FAVOURITE.putTo(cv, confirm_favourite.b2i())
COL_CONFIRM_UNBOOST.putTo(cv, confirm_unboost.b2i())
COL_CONFIRM_UNFAVOURITE.putTo(cv, confirm_unfavourite.b2i())
cv.put(COL_DONT_HIDE_NSFW, dont_hide_nsfw.b2i())
cv.put(COL_DONT_SHOW_TIMEOUT, dont_show_timeout.b2i())
cv.put(COL_NOTIFICATION_MENTION, notification_mention.b2i())
cv.put(COL_NOTIFICATION_BOOST, notification_boost.b2i())
cv.put(COL_NOTIFICATION_FAVOURITE, notification_favourite.b2i())
cv.put(COL_NOTIFICATION_FOLLOW, notification_follow.b2i())
cv.put(COL_NOTIFICATION_FOLLOW_REQUEST, notification_follow_request.b2i())
cv.put(COL_NOTIFICATION_REACTION, notification_reaction.b2i())
cv.put(COL_NOTIFICATION_VOTE, notification_vote.b2i())
cv.put(COL_NOTIFICATION_POST, notification_post.b2i())
COL_DONT_HIDE_NSFW.putTo(cv, dont_hide_nsfw.b2i())
COL_DONT_SHOW_TIMEOUT.putTo(cv, dont_show_timeout.b2i())
COL_NOTIFICATION_MENTION.putTo(cv, notification_mention.b2i())
COL_NOTIFICATION_BOOST.putTo(cv, notification_boost.b2i())
COL_NOTIFICATION_FAVOURITE.putTo(cv, notification_favourite.b2i())
COL_NOTIFICATION_FOLLOW.putTo(cv, notification_follow.b2i())
COL_NOTIFICATION_FOLLOW_REQUEST.putTo(cv, notification_follow_request.b2i())
COL_NOTIFICATION_REACTION.putTo(cv, notification_reaction.b2i())
COL_NOTIFICATION_VOTE.putTo(cv, notification_vote.b2i())
COL_NOTIFICATION_POST.putTo(cv, notification_post.b2i())
cv.put(COL_CONFIRM_FOLLOW, confirm_follow.b2i())
cv.put(COL_CONFIRM_FOLLOW_LOCKED, confirm_follow_locked.b2i())
cv.put(COL_CONFIRM_UNFOLLOW, confirm_unfollow.b2i())
cv.put(COL_CONFIRM_POST, confirm_post.b2i())
cv.put(COL_CONFIRM_REACTION, confirm_reaction.b2i())
COL_CONFIRM_FOLLOW.putTo(cv, confirm_follow.b2i())
COL_CONFIRM_FOLLOW_LOCKED.putTo(cv, confirm_follow_locked.b2i())
COL_CONFIRM_UNFOLLOW.putTo(cv, confirm_unfollow.b2i())
COL_CONFIRM_POST.putTo(cv, confirm_post.b2i())
COL_CONFIRM_REACTION.putTo(cv, confirm_reaction.b2i())
cv.put(COL_SOUND_URI, sound_uri)
cv.put(COL_DEFAULT_TEXT, default_text)
COL_SOUND_URI.putTo(cv, sound_uri)
COL_DEFAULT_TEXT.putTo(cv, default_text)
cv.put(COL_DEFAULT_SENSITIVE, default_sensitive.b2i())
cv.put(COL_EXPAND_CW, expand_cw.b2i())
cv.put(COL_MAX_TOOT_CHARS, max_toot_chars)
COL_DEFAULT_SENSITIVE.putTo(cv, default_sensitive.b2i())
COL_EXPAND_CW.putTo(cv, expand_cw.b2i())
COL_MAX_TOOT_CHARS.putTo(cv, max_toot_chars)
cv.putOrNull(COL_IMAGE_RESIZE, image_resize)
cv.putOrNull(COL_IMAGE_MAX_MEGABYTES, image_max_megabytes)
cv.putOrNull(COL_MOVIE_MAX_MEGABYTES, movie_max_megabytes)
cv.putOrNull(COL_PUSH_POLICY, push_policy)
COL_IMAGE_RESIZE.putTo(cv, image_resize)
COL_IMAGE_MAX_MEGABYTES.putTo(cv, image_max_megabytes)
COL_MOVIE_MAX_MEGABYTES.putTo(cv, movie_max_megabytes)
COL_PUSH_POLICY.putTo(cv, push_policy)
// UIからは更新しない
// notification_tag
@ -346,70 +347,74 @@ class SavedAccount(
const val table = "access_info"
private const val COL_ID = BaseColumns._ID
private const val COL_HOST = "h"
private const val COL_DOMAIN = "d"
private const val COL_USER = "u"
private const val COL_ACCOUNT = "a"
private const val COL_TOKEN = "t"
private val columnList = ColumnMeta.List(table)
private const val COL_VISIBILITY = "visibility"
private const val COL_CONFIRM_BOOST = "confirm_boost"
private const val COL_DONT_HIDE_NSFW = "dont_hide_nsfw"
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")
private val COL_DOMAIN = ColumnMeta(columnList, 56, "d", "text")
private val COL_USER = ColumnMeta(columnList, 0, "u", "text not null")
private val COL_ACCOUNT = ColumnMeta(columnList, 0, "a", "text not null")
private val COL_TOKEN = ColumnMeta(columnList, 0, "t", "text not null")
private const val COL_NOTIFICATION_MENTION = "notification_mention" // スキーマ2
private const val COL_NOTIFICATION_BOOST = "notification_boost" // スキーマ2
private const val COL_NOTIFICATION_FAVOURITE = "notification_favourite" // スキーマ2
private const val COL_NOTIFICATION_FOLLOW = "notification_follow" // スキーマ2
private const val COL_NOTIFICATION_FOLLOW_REQUEST = "notification_follow_request" // スキーマ44
private const val COL_NOTIFICATION_REACTION = "notification_reaction" // スキーマ33
private const val COL_NOTIFICATION_VOTE = "notification_vote" // スキーマ33
private const val COL_NOTIFICATION_POST = "notification_post" // スキーマ57
private val COL_VISIBILITY = ColumnMeta(columnList, 0, "visibility", "text")
private val COL_CONFIRM_BOOST = ColumnMeta(columnList, 0, "confirm_boost", ColumnMeta.TS_TRUE)
private val COL_DONT_HIDE_NSFW = ColumnMeta(columnList, 0, "dont_hide_nsfw", ColumnMeta.TS_ZERO)
private const val COL_CONFIRM_FOLLOW = "confirm_follow" // スキーマ10
private const val COL_CONFIRM_FOLLOW_LOCKED = "confirm_follow_locked" // スキーマ10
private const val COL_CONFIRM_UNFOLLOW = "confirm_unfollow" // スキーマ10
private const val COL_CONFIRM_POST = "confirm_post" // スキーマ10
private const val COL_CONFIRM_FAVOURITE = "confirm_favourite" // スキーマ23
private const val COL_CONFIRM_UNBOOST = "confirm_unboost" // スキーマ24
private const val COL_CONFIRM_UNFAVOURITE = "confirm_unfavourite" // スキーマ24
private const val COL_CONFIRM_REACTION = "confirm_reaction" // スキーマ61
private val COL_NOTIFICATION_MENTION = ColumnMeta(columnList, 2, "notification_mention", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_BOOST = ColumnMeta(columnList, 2, "notification_boost", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FAVOURITE = ColumnMeta(columnList, 2, "notification_favourite", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FOLLOW = ColumnMeta(columnList, 2, "notification_follow", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_FOLLOW_REQUEST =
ColumnMeta(columnList, 44, "notification_follow_request", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_REACTION = ColumnMeta(columnList, 33, "notification_reaction", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_VOTE = ColumnMeta(columnList, 33, "notification_vote", ColumnMeta.TS_TRUE)
private val COL_NOTIFICATION_POST = ColumnMeta(columnList, 57, "notification_post", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW = ColumnMeta(columnList, 10, "confirm_follow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FOLLOW_LOCKED = ColumnMeta(columnList, 10, "confirm_follow_locked", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFOLLOW = ColumnMeta(columnList, 10, "confirm_unfollow", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_POST = ColumnMeta(columnList, 10, "confirm_post", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_FAVOURITE = ColumnMeta(columnList, 23, "confirm_favourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNBOOST = ColumnMeta(columnList, 24, "confirm_unboost", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_UNFAVOURITE = ColumnMeta(columnList, 24, "confirm_unfavourite", ColumnMeta.TS_TRUE)
private val COL_CONFIRM_REACTION = ColumnMeta(columnList, 61, "confirm_reaction", ColumnMeta.TS_TRUE)
// スキーマ13から
const val COL_NOTIFICATION_TAG = "notification_server"
val COL_NOTIFICATION_TAG = ColumnMeta(columnList, 13, "notification_server", ColumnMeta.TS_EMPTY)
// スキーマ14から
const val COL_REGISTER_KEY = "register_key"
const val COL_REGISTER_TIME = "register_time"
val COL_REGISTER_KEY = ColumnMeta(columnList, 14, "register_key", ColumnMeta.TS_EMPTY)
val COL_REGISTER_TIME = ColumnMeta(columnList, 14, "register_time", ColumnMeta.TS_ZERO)
// スキーマ16から
private const val COL_SOUND_URI = "sound_uri"
private val COL_SOUND_URI = ColumnMeta(columnList, 16, "sound_uri", ColumnMeta.TS_EMPTY)
// スキーマ18から
private const val COL_DONT_SHOW_TIMEOUT = "dont_show_timeout"
private val COL_DONT_SHOW_TIMEOUT = ColumnMeta(columnList, 18, "dont_show_timeout", ColumnMeta.TS_ZERO)
// スキーマ27から
private const val COL_DEFAULT_TEXT = "default_text"
private val COL_DEFAULT_TEXT = ColumnMeta(columnList, 27, "default_text", ColumnMeta.TS_EMPTY)
// スキーマ28から
private const val COL_MISSKEY_VERSION = "is_misskey" // カラム名がおかしいのは、昔はboolean扱いだったから
private val COL_MISSKEY_VERSION = ColumnMeta(columnList, 28, "is_misskey", ColumnMeta.TS_ZERO)
// カラム名がおかしいのは、昔はboolean扱いだったから
// 0: not misskey
// 1: old(v10) misskey
// 11: misskey v11
private const val COL_DEFAULT_SENSITIVE = "default_sensitive"
private const val COL_EXPAND_CW = "expand_cw"
private const val COL_MAX_TOOT_CHARS = "max_toot_chars"
private val COL_DEFAULT_SENSITIVE = ColumnMeta(columnList, 38, "default_sensitive", ColumnMeta.TS_ZERO)
private val COL_EXPAND_CW = ColumnMeta(columnList, 38, "expand_cw", ColumnMeta.TS_ZERO)
private val COL_MAX_TOOT_CHARS = ColumnMeta(columnList, 39, "max_toot_chars", ColumnMeta.TS_ZERO)
private const val COL_LAST_NOTIFICATION_ERROR = "last_notification_error" // スキーマ42
private const val COL_LAST_SUBSCRIPTION_ERROR = "last_subscription_error" // スキーマ45
private const val COL_LAST_PUSH_ENDPOINT = "last_push_endpoint" // スキーマ46
private val COL_LAST_NOTIFICATION_ERROR = ColumnMeta(columnList, 42, "last_notification_error", "text")
private val COL_LAST_SUBSCRIPTION_ERROR = ColumnMeta(columnList, 45, "last_subscription_error", "text")
private val COL_LAST_PUSH_ENDPOINT = ColumnMeta(columnList, 46, "last_push_endpoint", "text")
private const val COL_IMAGE_RESIZE = "image_resize" // スキーマ59
private const val COL_IMAGE_MAX_MEGABYTES = "image_max_megabytes" // スキーマ59
private const val COL_MOVIE_MAX_MEGABYTES = "movie_max_megabytes" // スキーマ59
private val COL_IMAGE_RESIZE = ColumnMeta(columnList, 59, "image_resize", "text default null")
private val COL_IMAGE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "image_max_megabytes", "text default null")
private val COL_MOVIE_MAX_MEGABYTES = ColumnMeta(columnList, 59, "movie_max_megabytes", "text default null")
private const val COL_PUSH_POLICY = "push_policy" // スキーマ60
private val COL_PUSH_POLICY = ColumnMeta(columnList, 60, "push_policy", "text default null")
/////////////////////////////////
// login information
@ -425,299 +430,13 @@ class SavedAccount(
}
override fun onDBCreate(db: SQLiteDatabase) {
db.execSQL(
"""create table if not exists $table
($COL_ID INTEGER PRIMARY KEY
,$COL_USER text not null
,$COL_HOST text not null
,$COL_ACCOUNT text not null
,$COL_TOKEN text not null
,$COL_VISIBILITY text
,$COL_CONFIRM_BOOST integer default 1
,$COL_DONT_HIDE_NSFW integer default 0
,$COL_NOTIFICATION_MENTION integer default 1
,$COL_NOTIFICATION_BOOST integer default 1
,$COL_NOTIFICATION_FAVOURITE integer default 1
,$COL_NOTIFICATION_FOLLOW integer default 1
,$COL_CONFIRM_FOLLOW integer default 1
,$COL_CONFIRM_FOLLOW_LOCKED integer default 1
,$COL_CONFIRM_UNFOLLOW integer default 1
,$COL_CONFIRM_POST integer default 1
,$COL_NOTIFICATION_TAG text default ''
,$COL_REGISTER_KEY text default ''
,$COL_REGISTER_TIME integer default 0
,$COL_SOUND_URI text default ''
,$COL_DONT_SHOW_TIMEOUT integer default 0
,$COL_CONFIRM_FAVOURITE integer default 1
,$COL_CONFIRM_UNBOOST integer default 1
,$COL_CONFIRM_UNFAVOURITE integer default 1
,$COL_DEFAULT_TEXT text default ''
,$COL_MISSKEY_VERSION integer default 0
,$COL_NOTIFICATION_REACTION integer default 1
,$COL_NOTIFICATION_VOTE integer default 1
,$COL_DEFAULT_SENSITIVE integer default 0
,$COL_EXPAND_CW integer default 0
,$COL_MAX_TOOT_CHARS integer default 0
,$COL_LAST_NOTIFICATION_ERROR text
,$COL_NOTIFICATION_FOLLOW_REQUEST integer default 1
,$COL_LAST_SUBSCRIPTION_ERROR text
,$COL_LAST_PUSH_ENDPOINT text
,$COL_DOMAIN text
,$COL_NOTIFICATION_POST integer default 1
,$COL_IMAGE_RESIZE text default null
,$COL_IMAGE_MAX_MEGABYTES text default null
,$COL_MOVIE_MAX_MEGABYTES text default null
,$COL_PUSH_POLICY text default null
,$COL_CONFIRM_REACTION integer default 1
)""".trimIndent()
)
db.execSQL("create table if not exists $table (${columnList.createParams()})")
db.execSQL("create index if not exists ${table}_user on $table(u)")
db.execSQL("create index if not exists ${table}_host on $table(h,u)")
}
override fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
fun isUpgraded(n: Int, block: () -> Unit) {
if (oldVersion < n && newVersion >= n) block()
}
isUpgraded(2) {
try {
db.execSQL("alter table $table add column notification_mention integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column notification_boost integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column notification_favourite integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column notification_follow integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(10) {
try {
db.execSQL("alter table $table add column $COL_CONFIRM_FOLLOW integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_CONFIRM_FOLLOW_LOCKED integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_CONFIRM_UNFOLLOW integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_CONFIRM_POST integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(13) {
try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_TAG text default ''")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(14) {
try {
db.execSQL("alter table $table add column $COL_REGISTER_KEY text default ''")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_REGISTER_TIME integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(16) {
try {
db.execSQL("alter table $table add column $COL_SOUND_URI text default ''")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(18) {
try {
db.execSQL("alter table $table add column $COL_DONT_SHOW_TIMEOUT integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(23) {
try {
db.execSQL("alter table $table add column $COL_CONFIRM_FAVOURITE integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(24) {
try {
db.execSQL("alter table $table add column $COL_CONFIRM_UNFAVOURITE integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_CONFIRM_UNBOOST integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(27) {
try {
db.execSQL("alter table $table add column $COL_DEFAULT_TEXT text default ''")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(28) {
try {
db.execSQL("alter table $table add column $COL_MISSKEY_VERSION integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(33) {
try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_REACTION integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_VOTE integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(38) {
try {
db.execSQL("alter table $table add column $COL_DEFAULT_SENSITIVE integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_EXPAND_CW integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(39) {
try {
db.execSQL("alter table $table add column $COL_MAX_TOOT_CHARS integer default 0")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(42) {
try {
db.execSQL("alter table $table add column $COL_LAST_NOTIFICATION_ERROR text")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(44) {
try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_FOLLOW_REQUEST integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(45) {
try {
db.execSQL("alter table $table add column $COL_LAST_SUBSCRIPTION_ERROR text")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(46) {
try {
db.execSQL("alter table $table add column $COL_LAST_PUSH_ENDPOINT text")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(56) {
try {
db.execSQL("alter table $table add column $COL_DOMAIN text")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(57) {
try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_POST integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(59) {
try {
db.execSQL("alter table $table add column $COL_IMAGE_RESIZE text default null")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_IMAGE_MAX_MEGABYTES text default null")
} catch (ex: Throwable) {
log.trace(ex)
}
try {
db.execSQL("alter table $table add column $COL_MOVIE_MAX_MEGABYTES text default null")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(60) {
try {
db.execSQL("alter table $table add column $COL_PUSH_POLICY text default null")
} catch (ex: Throwable) {
log.trace(ex)
}
}
isUpgraded(61) {
try {
db.execSQL("alter table $table add column $COL_CONFIRM_REACTION integer default 1")
} catch (ex: Throwable) {
log.trace(ex)
}
}
columnList.addColumns(db, oldVersion, newVersion)
}
val defaultResizeConfig = ResizeConfig(ResizeType.LongSide, 1280)
@ -777,12 +496,12 @@ class SavedAccount(
): Long {
try {
val cv = ContentValues()
cv.put(COL_USER, acct)
cv.put(COL_HOST, host)
cv.putOrNull(COL_DOMAIN, domain)
cv.put(COL_ACCOUNT, account.toString())
cv.put(COL_TOKEN, token.toString())
cv.put(COL_MISSKEY_VERSION, misskeyVersion)
COL_USER.putTo(cv, acct)
COL_HOST.putTo(cv, host)
COL_DOMAIN.putTo(cv, domain)
COL_ACCOUNT.putTo(cv, account.toString())
COL_TOKEN.putTo(cv, token.toString())
COL_MISSKEY_VERSION.putTo(cv, misskeyVersion)
return App1.database.insert(table, null, cv)
} catch (ex: Throwable) {
log.trace(ex)
@ -794,8 +513,8 @@ class SavedAccount(
fun clearRegistrationCache() {
val cv = ContentValues()
cv.put(COL_REGISTER_KEY, REGISTER_KEY_UNREGISTERED)
cv.put(COL_REGISTER_TIME, 0L)
COL_REGISTER_KEY.putTo(cv, REGISTER_KEY_UNREGISTERED)
COL_REGISTER_TIME.putTo(cv, 0L)
App1.database.update(table, cv, null, null)
}
@ -1019,7 +738,7 @@ class SavedAccount(
null
).use { cursor ->
while (cursor.moveToNext()) {
list.add(cursor.getLong(COL_ID))
list.add(COL_ID.getLong(cursor))
}
}
} catch (ex: Throwable) {
@ -1106,7 +825,7 @@ class SavedAccount(
if (ta != null) {
this.loginAccount = ta
val cv = ContentValues()
cv.put(COL_ACCOUNT, result.jsonObject.toString())
COL_ACCOUNT.putTo(cv, result.jsonObject.toString())
App1.database.update(table, cv, "$COL_ID=?", arrayOf(db_id.toString()))
PollingWorker.queueUpdateNotification(context)
}
@ -1119,40 +838,27 @@ class SavedAccount(
}
}
fun updateNotificationError(text: String?) {
this.lastNotificationError = text
private fun updateSingleString(col: ColumnMeta, value: String?) {
if (db_id != INVALID_DB_ID) {
val cv = ContentValues()
when (text) {
null -> cv.putNull(COL_LAST_NOTIFICATION_ERROR)
else -> cv.put(COL_LAST_NOTIFICATION_ERROR, text)
}
col.putTo(cv, value)
App1.database.update(table, cv, "$COL_ID=?", arrayOf(db_id.toString()))
}
}
fun updateNotificationError(text: String?) {
this.lastNotificationError = text
updateSingleString(COL_LAST_NOTIFICATION_ERROR, text)
}
fun updateSubscriptionError(text: String?) {
this.last_subscription_error = text
if (db_id != INVALID_DB_ID) {
val cv = ContentValues()
when (text) {
null -> cv.putNull(COL_LAST_SUBSCRIPTION_ERROR)
else -> cv.put(COL_LAST_SUBSCRIPTION_ERROR, text)
}
App1.database.update(table, cv, "$COL_ID=?", arrayOf(db_id.toString()))
}
updateSingleString(COL_LAST_SUBSCRIPTION_ERROR, text)
}
fun updateLastPushEndpoint(text: String?) {
this.last_push_endpoint = text
if (db_id != INVALID_DB_ID) {
val cv = ContentValues()
when (text) {
null -> cv.putNull(COL_LAST_PUSH_ENDPOINT)
else -> cv.put(COL_LAST_PUSH_ENDPOINT, text)
}
App1.database.update(table, cv, "$COL_ID=?", arrayOf(db_id.toString()))
}
updateSingleString(COL_LAST_PUSH_ENDPOINT, text)
}
override fun equals(other: Any?): Boolean =

View File

@ -6,6 +6,7 @@ import android.provider.BaseColumns
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import jp.juggler.util.getString
object SubscriptionServerKey : TableCompanion {

View File

@ -1,27 +0,0 @@
package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
// SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる
// boolean to integer
fun Boolean.b2i() = if (this) 1 else 0
// integer to boolean
fun Int.i2b() = this != 0
fun Cursor.getBoolean(keyIdx: Int) =
getInt(keyIdx).i2b()
fun Cursor.getBoolean(key: String) =
getBoolean(getColumnIndex(key))
interface TableCompanion {
fun onDBCreate(db: SQLiteDatabase)
fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int)
}
fun ContentValues.putOrNull(key: String, value: String?) =
if (value == null) putNull(key) else put(key, value)

View File

@ -7,6 +7,7 @@ import java.util.ArrayList
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
object TagSet : TableCompanion {

View File

@ -15,8 +15,8 @@ import android.provider.BaseColumns
import com.caverock.androidsvg.SVG
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.table.TableCompanion
import jp.juggler.util.LogCategory
import jp.juggler.util.TableCompanion
import kotlinx.coroutines.channels.Channel
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference

View File

@ -1,5 +1,6 @@
package jp.juggler.util
import org.intellij.lang.annotations.Language
import java.util.regex.Pattern
/*
@ -32,10 +33,10 @@ http://userguide.icu-project.org/strings/regexp
*/
fun String.asciiPattern(flags: Int = 0): Pattern =
fun @receiver:Language("RegExp") String.asciiPattern(flags: Int = 0): Pattern =
Pattern.compile(this.asciiPatternString(), flags)
fun String.asciiPatternString(): String {
fun @receiver:Language("RegExp") String.asciiPatternString(): String {
val dst = StringBuilder()
dst.ensureCapacity(this.length)
var escaped = false

View File

@ -1,7 +1,11 @@
package jp.juggler.subwaytooter
package jp.juggler.util
import jp.juggler.subwaytooter.BuildConfig
import android.os.SystemClock
private val log = LogCategory("Benchmark")
val benchmarkLimitDefault = if (BuildConfig.DEBUG) 10L else 100L
fun <T : Any?> benchmark(
caption: String,
@ -11,6 +15,6 @@ fun <T : Any?> benchmark(
val start = SystemClock.elapsedRealtime()
val rv = block()
val duration = SystemClock.elapsedRealtime() - start
if (duration >= limit) ActMain.log.w("benchmark: ${duration}ms : $caption")
if (duration >= limit) log.w("benchmark: ${duration}ms : $caption")
return rv
}

View File

@ -0,0 +1,149 @@
package jp.juggler.util
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
/////////////////////////////////////////////////////////////
// SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる
// boolean to integer
fun Boolean.b2i() = if (this) 1 else 0
// integer to boolean
fun Int.i2b() = this != 0
fun Cursor.getBoolean(keyIdx: Int) =
getInt(keyIdx).i2b()
fun Cursor.getBoolean(key: String) =
getBoolean(getColumnIndex(key))
fun Cursor.getInt(key: String) =
getInt(getColumnIndex(key))
fun Cursor.getIntOrNull(idx: Int) =
if (isNull(idx)) null else getInt(idx)
fun Cursor.getIntOrNull(key: String) =
getIntOrNull(getColumnIndex(key))
fun Cursor.getLong(key: String) =
getLong(getColumnIndex(key))
//fun Cursor.getLongOrNull(idx:Int) =
// if(isNull(idx)) null else getLong(idx)
//fun Cursor.getLongOrNull(key:String) =
// getLongOrNull(getColumnIndex(key))
fun Cursor.getString(key: String): String =
getString(getColumnIndex(key))
fun Cursor.getStringOrNull(keyIdx: Int) =
if (isNull(keyIdx)) null else getString(keyIdx)
fun Cursor.getStringOrNull(key: String) =
getStringOrNull(getColumnIndex(key))
fun ContentValues.putOrNull(key: String, value: String?) =
if (value == null) putNull(key) else put(key, value)
/////////////////////////////////////////////////////////////
interface TableCompanion {
fun onDBCreate(db: SQLiteDatabase)
fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int)
}
class ColumnMeta(
list: List,
val version: Int,
val name: String,
val typeSpec: String,
val primary: Boolean = false,
) : Comparable<ColumnMeta> {
companion object {
private val log = LogCategory("ColumnMeta")
const val TS_EMPTY = "text default ''"
const val TS_ZERO = "integer default 0"
const val TS_TRUE = "integer default 1"
}
class List(val table: String) : ArrayList<ColumnMeta>() {
fun createParams(): String =
sorted().joinToString(",") { "${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")
}
}
}
}
}
// テーブル作成時のソート
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)
}
override fun toString(): String = name
override fun hashCode(): Int = name.hashCode()
override fun equals(other: Any?): Boolean = when (other) {
is ColumnMeta -> name == other.name
else -> name == other
}
init {
list.add(this)
}
@Suppress("unused")
fun putNullTo(cv: ContentValues) = cv.putNull(name)
fun putTo(cv: ContentValues, v: Boolean?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: String?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Byte?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Short?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Int?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Long?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Float?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: Double?) = cv.put(name, v)
fun putTo(cv: ContentValues, v: ByteArray?) = cv.put(name, v)
fun getIndex(cursor: Cursor) = cursor.getColumnIndex(name)
fun getLong(cursor: Cursor) = cursor.getLong(getIndex(cursor))
}
fun ContentValues.putNull(key: ColumnMeta) = putNull(key.name)
fun ContentValues.put(key: ColumnMeta, v: Boolean?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: String?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Byte?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Short?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Int?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Long?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Float?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Double?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: ByteArray?) = put(key.name, v)
fun Cursor.getInt(key: ColumnMeta) = getInt(getColumnIndex(key.name))
fun Cursor.getBoolean(key: ColumnMeta) = getBoolean(getColumnIndex(key.name))
fun Cursor.getLong(key: ColumnMeta) = getLong(getColumnIndex(key.name))
@Suppress("unused")
fun Cursor.getIntOrNull(key: ColumnMeta) = getIntOrNull(getColumnIndex(key.name))
fun Cursor.getString(key: ColumnMeta): String = getString(getColumnIndex(key.name))
fun Cursor.getStringOrNull(key: ColumnMeta): String? {
val idx = key.getIndex(this)
return if (isNull(idx)) null else getString(idx)
}

View File

@ -1,30 +0,0 @@
package jp.juggler.util
import android.database.Cursor
fun Cursor.getInt(key: String) =
getInt(getColumnIndex(key))
fun Cursor.getIntOrNull(idx: Int) =
if (isNull(idx)) null else getInt(idx)
fun Cursor.getIntOrNull(key: String) =
getIntOrNull(getColumnIndex(key))
fun Cursor.getLong(key: String) =
getLong(getColumnIndex(key))
//fun Cursor.getLongOrNull(idx:Int) =
// if(isNull(idx)) null else getLong(idx)
//fun Cursor.getLongOrNull(key:String) =
// getLongOrNull(getColumnIndex(key))
fun Cursor.getString(key: String): String =
getString(getColumnIndex(key))
fun Cursor.getStringOrNull(keyIdx: Int) =
if (isNull(keyIdx)) null else getString(keyIdx)
fun Cursor.getStringOrNull(key: String) =
getStringOrNull(getColumnIndex(key))