Procházet zdrojové kódy

optimize DataObj saving progress
split Tags object

00ll00 před 5 roky
rodič
revize
5bdaefd33e

+ 14 - 4
src/main/kotlin/bot_luo_core/bot/MultiBotHandler.kt

@@ -1,8 +1,11 @@
 package bot_luo_core.bot
 
 import bot_luo_core.cli.CmdHandler
+import bot_luo_core.data.DataObj
 import bot_luo_core.util.JsonWorker
 import bot_luo_core.util.Logger
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.runBlocking
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotFactory
 import net.mamoe.mirai.contact.*
@@ -10,7 +13,6 @@ import net.mamoe.mirai.containsFriend
 import net.mamoe.mirai.event.*
 import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.message.data.MessageChain
-import net.mamoe.mirai.message.data.content
 import net.mamoe.mirai.utils.BotConfiguration
 import org.apache.logging.log4j.Level
 
@@ -124,9 +126,11 @@ object MultiBotHandler {
             CmdHandler.call(this)
             if (this.isIntercepted) Logger.log(this, Level.INFO) else Logger.log(this, Level.DEBUG)
         }
-        GlobalEventChannel.subscribeAlways<MessagePreSendEvent> {
-            this.cancel()   //TODO 解除张口结舌之术
-        }
+//        GlobalEventChannel.subscribeAlways<MessagePreSendEvent> {
+//            this.cancel()   //TODO 解除张口结舌之术
+//        }
+
+        Runtime.getRuntime().addShutdownHook(Thread{ saveAll() })
     }
 
     /**
@@ -258,5 +262,11 @@ object MultiBotHandler {
         }
     }
 
+    fun saveAll() {
+        runBlocking {
+            DataObj.savingJobs.values.forEach { it.start(); it.join() }
+            DataObj.savingJobs.keys.forEach { it.cancelAndJoin() }
+        }
+    }
 
 }

+ 1 - 0
src/main/kotlin/bot_luo_core/cli/CmdCatalog.kt

@@ -20,6 +20,7 @@ object CmdCatalog {
             AnnounceCmd::class,
             ControlCmd::class,
             DebugCmd::class,
+            DumpFileCmd::class,
             ExecuteCmd::class,
             HelpCmd::class,
             ListCmd::class,

+ 4 - 1
src/main/kotlin/bot_luo_core/cli/CmdContext.kt

@@ -130,10 +130,13 @@ class CmdContext(
     }
 
     suspend fun uploadOutput(): MessageReceipt<Contact>? {
+        val text = getOutput().content
+        if (text.isBlank()) return null
         withBackendBot{ contact ->
             if (contact !is FileSupported) return null
-            val file = File.createTempFile("output-${this.hashCode()}",".txt")
+            val file = File.createTempFile("output-${this.hashCode().toString(16)}",".txt")
             file.deleteOnExit()
+            file.writeText(text)
             file.toExternalResource().use { resource ->
                 return  contact.sendMessageWithLog(contact.uploadFile("${this.reader.original.content.replace(' ','_')}.txt",resource))
             }

+ 1 - 1
src/main/kotlin/bot_luo_core/cli/commands/DebugCmd.kt

@@ -22,7 +22,7 @@ class DebugCmd(context: CmdContext) : Cmd(context) {
         table.th("调试信息 ——").br()
         table.tr("active_groups:").tb(Groups.activeGroupsCount)
         table.tr("active_users:").tb(Users.activeUsersCount)
-        table.tr("save_jobs:").tb(DataObj.saveJobs.size)
+        table.tr("save_jobs:").tb(DataObj.savingJobs.size)
         context.print(table.toString())
         return SUCCESS
     }

+ 70 - 100
src/main/kotlin/bot_luo_core/cli/commands/TagCmd.kt

@@ -10,22 +10,19 @@ import bot_luo_core.cli.annotation.Method
 import bot_luo_core.cli.handlers.GroupArgHandler
 import bot_luo_core.cli.handlers.TagArgHandler
 import bot_luo_core.cli.handlers.UserArgHandler
-import bot_luo_core.data.Group
-import bot_luo_core.data.Tags
-import bot_luo_core.data.Tags.BUILTIN_GROUP_TAGS
-import bot_luo_core.data.Tags.BUILTIN_USER_TAGS
-import bot_luo_core.data.Tags.addToGroupTag
-import bot_luo_core.data.Tags.addToUserTag
-import bot_luo_core.data.Tags.groupTag
-import bot_luo_core.data.Tags.readGroupTag
-import bot_luo_core.data.Tags.readUserTag
-import bot_luo_core.data.Tags.remFromGroupTag
-import bot_luo_core.data.Tags.remFromUserTag
-import bot_luo_core.data.Tags.remGroupTag
-import bot_luo_core.data.Tags.remUserTag
-import bot_luo_core.data.Tags.userTag
-import bot_luo_core.data.User
+import bot_luo_core.data.*
+import bot_luo_core.data.GroupTags.BUILTIN_GROUP_TAGS
+import bot_luo_core.data.GroupTags.addToGroupTag
+import bot_luo_core.data.GroupTags.readGroupTag
+import bot_luo_core.data.GroupTags.remFromGroupTag
+import bot_luo_core.data.GroupTags.remGroupTag
+import bot_luo_core.data.UserTags.BUILTIN_USER_TAGS
+import bot_luo_core.data.UserTags.addToUserTag
+import bot_luo_core.data.UserTags.readUserTag
+import bot_luo_core.data.UserTags.remFromUserTag
+import bot_luo_core.data.UserTags.remUserTag
 import bot_luo_core.util.TableBuilder
+import bot_luo_core.util.Tag.tree
 import bot_luo_core.util.Text.toLowercase
 import kotlin.collections.ArrayList
 
@@ -40,7 +37,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     /*  ========================  add  ========================  */
 
     @Method(name = "group-add", alias = ["ga"], pmsLevel = CmdPermissionLevel.OP, title = "添加群组标签", order = 0)
-    fun groupAdd(
+    suspend fun groupAdd(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待加入标签", handler = TagArgHandler::class, multiValued = true)
@@ -55,12 +52,14 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         table.th("群组标签添加 —— 标签($tag)").br()
 
-        for (t in tagIn) {
-            if (addToGroupTag(tag, t)) {
-                table.tr().tb(t).tb("[√] success added")
-                suc++
-            } else {
-                table.tr().tb(t).tb("[×] duplicated item or circulated tag")
+        withLockedAccessing(GroupTags) {
+            for (t in tagIn) {
+                if (addToGroupTag(tag, t)) {
+                    table.tr().tb(t).tb("[√] success added")
+                    suc++
+                } else {
+                    table.tr().tb(t).tb("[×] duplicated item or circulated tag")
+                }
             }
         }
         context.print(table.toString())
@@ -68,7 +67,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     }
 
     @Method(name = "group-add", alias = ["ga"], pmsLevel = CmdPermissionLevel.OP, title = "添加群组标签", order = 1)
-    fun groupAdd1(
+    suspend fun groupAdd1(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待加入群组", handler = GroupArgHandler::class, multiValued = true)
@@ -83,12 +82,14 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         table.th("群组标签添加 —— 标签($tag)").br()
 
-        for (g in groupIn) {
-            if (addToGroupTag(tag, g.id.toString())) {
-                table.tr(g.name).tb("(${g.id})").tb("[√] success added")
-                suc++
-            } else {
-                table.tr(g.name).tb("(${g.id})").tb("[×] duplicated item")
+        withLockedAccessing(GroupTags) {
+            for (g in groupIn) {
+                if (addToGroupTag(tag, g.id.toString())) {
+                    table.tr(g.name).tb("(${g.id})").tb("[√] success added")
+                    suc++
+                } else {
+                    table.tr(g.name).tb("(${g.id})").tb("[×] duplicated item")
+                }
             }
         }
         context.print(table.toString())
@@ -96,7 +97,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     }
 
     @Method(name = "user-add", alias = ["ua"], pmsLevel = CmdPermissionLevel.OP, title = "添加用户标签", order = 0)
-    fun userAdd(
+    suspend fun userAdd(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待加入标签", handler = TagArgHandler::class, multiValued = true)
@@ -111,12 +112,14 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         table.th("用户标签添加 —— 标签($tag)").br()
 
-        for (t in tagIn) {
-            if (addToUserTag(tag, t)) {
-                table.tr().tb(t).tb("[√] success added")
-                suc++
-            } else {
-                table.tr().tb(t).tb("[×] duplicated item or circulated tag")
+        withLockedAccessing(UserTags) {
+            for (t in tagIn) {
+                if (addToUserTag(tag, t)) {
+                    table.tr().tb(t).tb("[√] success added")
+                    suc++
+                } else {
+                    table.tr().tb(t).tb("[×] duplicated item or circulated tag")
+                }
             }
         }
         context.print(table.toString())
@@ -124,7 +127,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     }
 
     @Method(name = "user-add", alias = ["ua"], pmsLevel = CmdPermissionLevel.OP, title = "添加用户标签", order = 1)
-    fun userAdd1(
+    suspend fun userAdd1(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待加入用户", handler = UserArgHandler::class, multiValued = true)
@@ -139,12 +142,14 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         table.th("用户标签添加 —— 标签($tag)").br()
 
-        for (u in userIn) {
-            if (addToUserTag(tag, u.id.toString())) {
-                table.tr(u.name).tb("(${u.id})").tb("[√] success added")
-                suc++
-            } else {
-                table.tr(u.name).tb("(${u.id})").tb("[×] duplicated item")
+        withLockedAccessing(UserTags) {
+            for (u in userIn) {
+                if (addToUserTag(tag, u.id.toString())) {
+                    table.tr(u.name).tb("(${u.id})").tb("[√] success added")
+                    suc++
+                } else {
+                    table.tr(u.name).tb("(${u.id})").tb("[×] duplicated item")
+                }
             }
         }
         context.print(table.toString())
@@ -161,7 +166,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         if (tag == null) {
             table.th("群组标签 —— 所有标签").br()
-            for (t in groupTag.keys) {
+            for (t in GroupTags.tags.keys) {
                 table.tr(t)
             }
             for (t in BUILTIN_GROUP_TAGS) {
@@ -187,7 +192,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         val table = TableBuilder(4)
         if (tag == null) {
             table.th("用户标签 —— 所有标签").br()
-            for (t in userTag.keys) {
+            for (t in UserTags.tags.keys) {
                 table.tr(t)
             }
             for (t in BUILTIN_USER_TAGS) {
@@ -214,7 +219,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     ): CmdReceipt {
         val table = TableBuilder(1)
         table.th("展开群组标签 —— 标签($tag)")
-        tree(tag, table) { groupTag[it] ?: ArrayList() }
+        tree(tag, table) { GroupTags.tags[it] ?: ArrayList() }
         context.print(table.toString())
         return SUCCESS
     }
@@ -226,7 +231,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     ): CmdReceipt {
         val table = TableBuilder(1)
         table.th("展开用户标签 —— 标签($tag)")
-        tree(tag, table) { userTag[it] ?: ArrayList() }
+        tree(tag, table) { UserTags.tags[it] ?: ArrayList() }
         context.print(table.toString())
         return SUCCESS
     }
@@ -247,7 +252,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
             context.print("操作超时,")
             false
         }) {
-            if (remGroupTag(tag)) {
+            if (withLockedAccessing(GroupTags) { remGroupTag(tag) }) {
                 context.print("已移除标签 $tag 及其引用")
                 SUCCESS
             } else {
@@ -274,7 +279,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
                 context.print("操作超时,")
                 false
             }) {
-            if (remUserTag(tag)) {
+            if (withLockedAccessing(UserTags) { remUserTag(tag) }) {
                 context.print("已移除标签 $tag 及其引用")
                 SUCCESS
             } else {
@@ -288,7 +293,7 @@ class TagCmd(context: CmdContext) : Cmd(context) {
     }
 
     @Method(name = "group-remove", alias = ["gr"], pmsLevel = CmdPermissionLevel.OP, title = "移除群组标签", order = 1)
-    fun groupRem(
+    suspend fun groupRem(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待移除群组或标签", multiValued = true)
@@ -300,18 +305,20 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         }
         val table = TableBuilder(4)
         table.th("移除群组标签 —— 标签($tag)")
-        for (i in itemsIn) {
-            if (remFromGroupTag(tag, i.toLowercase()))
-                table.tr().tb(i).tb("[√] success removed")
-            else
-                table.tr().tb(i).tb("[×] tag or item not found")
+        withLockedAccessing(GroupTags) {
+            for (i in itemsIn) {
+                if (remFromGroupTag(tag, i.toLowercase()))
+                    table.tr().tb(i).tb("[√] success removed")
+                else
+                    table.tr().tb(i).tb("[×] tag or item not found")
+            }
         }
         context.print(table.toString())
         return SUCCESS
     }
 
     @Method(name = "group-remove", alias = ["gr"], pmsLevel = CmdPermissionLevel.OP, title = "移除群组标签", order = 1)
-    fun userRem(
+    suspend fun userRem(
         @Argument(display = "被操作标签", handler = TagArgHandler::class)
         tag: String,
         @Argument(display = "待移除用户或标签", multiValued = true)
@@ -323,52 +330,15 @@ class TagCmd(context: CmdContext) : Cmd(context) {
         }
         val table = TableBuilder(4)
         table.th("移除用户标签 —— 标签($tag)")
-        for (i in itemsIn) {
-            if (remFromUserTag(tag, i.toLowercase()))
-                table.tr().tb(i).tb("[√] success removed")
-            else
-                table.tr().tb(i).tb("[×] tag or item not found")
+        withLockedAccessing(UserTags) {
+            for (i in itemsIn) {
+                if (remFromUserTag(tag, i.toLowercase()))
+                    table.tr().tb(i).tb("[√] success removed")
+                else
+                    table.tr().tb(i).tb("[×] tag or item not found")
+            }
         }
         context.print(table.toString())
         return SUCCESS
     }
-
-    override fun onExit(receipt: CmdReceipt) {
-        if (receipt.needSave()) Tags.saveTags()
-    }
-
-    companion object {
-        private fun tree(
-            tag: String,
-            table: TableBuilder,
-            line: ArrayList<Int> = ArrayList(),
-            tagGetter: (tag: String) -> ArrayList<String>
-        ) {
-            table.tr()
-            line.forEachIndexed { index, l ->
-                if (index < line.lastIndex) {
-                    if (l > 0) table.tb("│")
-                    else table.tb("  ")
-                } else {
-                    if (l > 0) table.tb("├")
-                    else table.tb("└")
-                }
-            }
-            table.tb(tag)
-            val items = tagGetter(tag)
-            items.sortedWith { o1, o2 ->
-                var r = 0
-                if (o1.startsWith('#')) r++
-                if (o2.startsWith('#')) r--
-                r
-            }
-            items.forEachIndexed { index, i ->
-                line.add(items.lastIndex - index)
-
-                tree(i, table, line, tagGetter)
-
-                line.removeLast()
-            }
-        }
-    }
 }

+ 2 - 2
src/main/kotlin/bot_luo_core/cli/handlers/GroupArgHandler.kt

@@ -8,8 +8,8 @@ import bot_luo_core.cli.exceptions.EmptyTag
 import bot_luo_core.cli.exceptions.HandlerFatal
 import bot_luo_core.cli.exceptions.TagMultiValued
 import bot_luo_core.data.Group
+import bot_luo_core.data.GroupTags
 import bot_luo_core.data.Groups
-import bot_luo_core.data.Tags
 import kotlin.reflect.KType
 
 class GroupArgHandler: ArgHandler<Group> {
@@ -21,7 +21,7 @@ class GroupArgHandler: ArgHandler<Group> {
 
         if (reader.canRead() && reader.peek() == '#') {
             val tag = reader.readString()
-            val res = Tags.readGroupTag(tag, context) ?: throw ContextNeeded(pos, argName)
+            val res = GroupTags.readGroupTag(tag, context) ?: throw ContextNeeded(pos, argName)
             when (res.size) {
                 0 -> throw EmptyTag(pos, tag)
                 1 -> return  res[0]

+ 1 - 1
src/main/kotlin/bot_luo_core/cli/handlers/MultiGroupArgHandler.kt

@@ -33,7 +33,7 @@ class MultiGroupArgHandler: ArgHandler<ArrayList<Group>> {
 
         if (reader.canRead() && reader.peek() == '#') {
             val tag = reader.readString()
-            val res = Tags.readGroupTag(tag, context) ?: throw ContextNeeded(pos, argName)
+            val res = GroupTags.readGroupTag(tag, context) ?: throw ContextNeeded(pos, argName)
             if (res.isNotEmpty())
                 return res
             else

+ 2 - 5
src/main/kotlin/bot_luo_core/cli/handlers/MultiUserArgHandler.kt

@@ -6,12 +6,9 @@ import bot_luo_core.cli.MessageReader
 import bot_luo_core.cli.exceptions.ContextNeeded
 import bot_luo_core.cli.exceptions.EmptyTag
 import bot_luo_core.cli.exceptions.HandlerFatal
-import bot_luo_core.cli.exceptions.SyntaxError
-import bot_luo_core.data.Tags
 import bot_luo_core.data.User
+import bot_luo_core.data.UserTags
 import bot_luo_core.data.Users
-import net.mamoe.mirai.message.data.toPlainText
-import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.reflect.KType
 
@@ -37,7 +34,7 @@ class MultiUserArgHandler: ArgHandler<ArrayList<User>> {
 
         if (reader.canRead() && reader.peek() == '#') {
             val tag = reader.readString()
-            val res = Tags.readUserTag(tag, context) ?: throw ContextNeeded(pos, argName)
+            val res = UserTags.readUserTag(tag, context) ?: throw ContextNeeded(pos, argName)
             if (res.isNotEmpty())
                 return res
             else

+ 2 - 2
src/main/kotlin/bot_luo_core/cli/handlers/UserArgHandler.kt

@@ -7,8 +7,8 @@ import bot_luo_core.cli.exceptions.ContextNeeded
 import bot_luo_core.cli.exceptions.EmptyTag
 import bot_luo_core.cli.exceptions.HandlerFatal
 import bot_luo_core.cli.exceptions.TagMultiValued
-import bot_luo_core.data.Tags
 import bot_luo_core.data.User
+import bot_luo_core.data.UserTags
 import bot_luo_core.data.Users
 import kotlin.reflect.KType
 
@@ -26,7 +26,7 @@ class UserArgHandler: ArgHandler<User> {
 
         if (reader.canRead() && reader.peek() == '#') {
             val tag = reader.readString()
-            val res = Tags.readUserTag(tag, context) ?: throw ContextNeeded(pos, argName)
+            val res = UserTags.readUserTag(tag, context) ?: throw ContextNeeded(pos, argName)
             when (res.size) {
                 0 -> throw EmptyTag(pos, tag)
                 1 -> return  res[0]

+ 7 - 14
src/main/kotlin/bot_luo_core/data/DataObj.kt

@@ -6,7 +6,6 @@ import com.alibaba.fastjson.TypeReference
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.*
 import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
 import java.io.File
 
 /**
@@ -41,7 +40,6 @@ abstract class DataObj(
     val jsonObj: JSONObject
 
     private var saveJob: Job? = null
-    private val context = Dispatchers.IO
 
     /**
      * 访问数量
@@ -84,18 +82,18 @@ abstract class DataObj(
      */
     private fun launchSaveJob() {
         saveJob?.cancel()
-        saveJobs.remove(saveJob)
-        saveJob = CoroutineScope(context).launch {
+        savingJobs.remove(saveJob)
+        saveJob = scope.launch {
             try {
                 delay(autoSaveInterval)
             } catch (ignore: CancellationException) {
                 return@launch
             }
-            if (changed) save()
+            if (changed) savingJobs[saveJob]?.start()
             if (saveAndUnload) unload()
         }.apply {
-            saveJobs.add(this)
-            this.invokeOnCompletion { saveJobs.remove(this) }
+            savingJobs[this] = scope.launch(start = CoroutineStart.LAZY) {save()}
+            this.invokeOnCompletion { savingJobs.remove(this) }
         }
     }
 
@@ -171,13 +169,8 @@ abstract class DataObj(
     }
 
     companion object {
-        val saveJobs = ArrayList<Job>()
-
-//        fun saveAll() {
-//            saveJobs.forEach {
-//                it.start()
-//            }
-//        }
+        val savingJobs = HashMap<Job, Job>()
+        val scope = CoroutineScope(Dispatchers.IO)
     }
 
 }

+ 57 - 0
src/main/kotlin/bot_luo_core/data/GroupTags.kt

@@ -0,0 +1,57 @@
+package bot_luo_core.data
+
+import bot_luo_core.cli.CmdContext
+import bot_luo_core.util.Tag
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+
+object GroupTags: DataObj("data/group_tags.json", 1000L, false) {
+    override fun unload() {}
+
+    val BUILTIN_GROUP_TAGS = listOf("#this", "#none")
+
+    val tags: HashMap<String, ArrayList<String>> = getObj()?: HashMap()
+
+    private fun saveTags() = setObj(tags)
+
+    /**
+     * 从标签读取群组列表
+     *
+     * 标签为'#'开头的字符串,大小写不敏感
+     *
+     * "#none"指向私聊虚拟群组(0)
+     *
+     * @return 群组[ArrayList]?,标签无效则返回空列表,缺少环境时返回`null`
+     */
+    fun readGroupTag(tag: String, context: CmdContext?): ArrayList<Group>? {
+        val res1 = ArrayList<String>()
+        fun readGroupTag1(tag: String, result: ArrayList<String>) {
+            if (tag in BUILTIN_GROUP_TAGS) {
+                result.add(tag)
+            } else if (tags.containsKey(tag)) {
+                for (item in tags[tag]!!) {
+                    if (item !in result)
+                        if (item.startsWith('#')) {
+                            readGroupTag1(item, result)
+                        } else {
+                            result.add(item)
+                        }
+                }
+            }
+        }
+        readGroupTag1(tag.lowercase(Locale.getDefault()).trim(), res1)
+        val res = ArrayList<Group>()
+        res.addAll(res1.mapNotNull { it.toLongOrNull() }.map { Groups.readGroup(it) })
+        if (res1.contains("#this")) {
+            if (context == null) return null
+            else res.add(context.group)
+        }
+        if (res1.contains("#none")) res.add(Groups.virtualGroup)
+        return res
+    }
+    fun readGroupTag(tag: String) = readGroupTag(tag,null)
+    fun addToGroupTag(tag: String, item: String) = Tag.addToTag(tags, tag, item).apply { saveTags() }
+    fun remFromGroupTag(tag: String, item: String) = Tag.remFromTag(tags, tag, item).apply { saveTags() }
+    fun remGroupTag(tag: String) = Tag.remTag(tags, tag).apply { saveTags() }
+}

+ 0 - 191
src/main/kotlin/bot_luo_core/data/Tags.kt

@@ -1,191 +0,0 @@
-package bot_luo_core.data
-
-import bot_luo_core.cli.CmdContext
-import bot_luo_core.cli.exceptions.ContextNeeded
-import bot_luo_core.util.JsonWorker
-import java.util.*
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
-
-object Tags {
-    private const val userFilePath = "data/user_tags.json"
-    private const val groupFilePath = "data/group_tags.json"
-
-    val userTag = HashMap<String, ArrayList<String>>()
-    val groupTag = HashMap<String, ArrayList<String>>()
-
-    val BUILTIN_GROUP_TAGS = listOf("#this", "#none")
-    val BUILTIN_USER_TAGS = listOf("#this")
-
-    init {
-        loadTags()
-    }
-
-    /**
-     * 加载标签文件
-     */
-    fun loadTags() {
-        val ut: HashMap<String,ArrayList<String>> = JsonWorker.readJson(userFilePath)?: HashMap()
-        userTag.clear()
-        userTag.putAll(ut)
-
-        val gt: HashMap<String,ArrayList<String>> = JsonWorker.readJson(groupFilePath)?: HashMap()
-        userTag.clear()
-        userTag.putAll(gt)
-    }
-
-    /**
-     * 保存标签文件
-     */
-    fun saveTags() {
-        JsonWorker.writeJson(userFilePath, userTag)
-        JsonWorker.writeJson(groupFilePath, groupTag)
-    }
-
-    /**
-     * 从标签读取群组列表
-     *
-     * 标签为'#'开头的字符串,大小写不敏感
-     *
-     * "#none"指向私聊虚拟群组(0)
-     *
-     * @return 群组[ArrayList]?,标签无效则返回空列表,缺少环境时返回`null`
-     */
-    fun readGroupTag(tag: String, context: CmdContext?): ArrayList<Group>? {
-        val res1 = ArrayList<String>()
-        fun readGroupTag1(tag: String, result: ArrayList<String>) {
-            if (tag in BUILTIN_GROUP_TAGS) {
-                result.add(tag)
-            } else if (groupTag.containsKey(tag)) {
-                for (item in groupTag[tag]!!) {
-                    if (item !in result)
-                        if (item.startsWith('#')) {
-                            readGroupTag1(item, result)
-                        } else {
-                            result.add(item)
-                        }
-                }
-            }
-        }
-        readGroupTag1(tag.lowercase(Locale.getDefault()).trim(), res1)
-        val res = ArrayList<Group>()
-        res.addAll(res1.mapNotNull { it.toLongOrNull() }.map { Groups.readGroup(it) })
-        if (res1.contains("#this")) {
-            if (context == null) return null
-            else res.add(context.group)
-        }
-        if (res1.contains("#none")) res.add(Groups.virtualGroup)
-        return res
-    }
-
-    fun readGroupTag(tag: String) = readGroupTag(tag,null)
-
-    /**
-     * 从标签读取用户列表
-     *
-     * 标签为'#'开头的字符串,大小写不敏感
-     *
-     * @return 用户[ArrayList]?,标签无效则返回空列表,缺少环境时返回`null`
-     */
-    fun readUserTag(tag: String, context: CmdContext?): ArrayList<User>? {
-        val res1 = ArrayList<String>()
-        fun readUserTag1(tag: String, result: ArrayList<String>) {
-            if (tag in BUILTIN_USER_TAGS) {
-                result.add(tag)
-            } else if (userTag.containsKey(tag)) {
-                for (item in userTag[tag]!!) {
-                    if (item !in result)
-                        if (item.startsWith('#')) {
-                            readUserTag1(item, result)
-                        } else {
-                            result.add(item)
-                        }
-                }
-            }
-        }
-        readUserTag1(tag.lowercase(Locale.getDefault()).trim(), res1)
-        val res = ArrayList<User>()
-        res.addAll(res1.mapNotNull { it.toLongOrNull() }.map { Users.readUser(it) })
-        if (res1.contains("#this")) {
-            if (context == null) return null
-            else res.add(context.user)
-        }
-        return res
-    }
-
-    fun readUserTag(tag: String) = readUserTag(tag,null)
-
-    fun addToGroupTag(tag: String, item: String) = addToTag(groupTag, tag, item)
-
-    fun addToUserTag(tag: String, item: String) = addToTag(userTag, tag, item)
-
-    /**
-     * ## 向指定标签池的指定标签下添加项
-     *
-     * 如果项以'#'开头则视为标签嵌套
-     *
-     * @return 如果出现重复或标签循环嵌套则返回`false`
-     */
-    fun addToTag(tagPool: HashMap<String, ArrayList<String>>, tag: String, item: String): Boolean {
-        if (tagPool.containsKey(tag) && tagPool[tag]!!.contains(item)) return false
-        if (item.startsWith('#')) {
-            if (item == tag) return false
-            val res = ArrayList<String>()
-            getParentTags(tagPool, tag, res)
-            if (item in res) return false
-        }
-        if (!tagPool.containsKey(tag)) tagPool[tag] = ArrayList()
-        tagPool[tag]!!.add(item)
-        return true
-    }
-
-    private fun getParentTags(tagPool: HashMap<String, ArrayList<String>>, tag: String, result: ArrayList<String>) {
-        for (t in tagPool.keys) {
-            if (t in result) continue
-            if (tagPool[t]!!.contains(tag)) {
-                result.add(t)
-                getParentTags(tagPool, t, result)
-            }
-        }
-    }
-
-    fun remFromGroupTag(tag: String, item: String) = remFromTag(groupTag,tag, item)
-    fun remFromUserTag(tag: String, item: String) = remFromTag(userTag,tag, item)
-
-    /**
-     * ## 从指定标签池的指定标签下移除项
-     *
-     * @return 如果标签不存在或项不存在于标签下返回`false`
-     */
-    private fun remFromTag(tagPool: HashMap<String, ArrayList<String>>, tag: String, item: String): Boolean {
-        if (tagPool.containsKey(tag) && tagPool[tag]!!.contains(item)) {
-            tagPool[tag]!!.remove(item)
-            return true
-        }
-        return false
-    }
-
-    fun remGroupTag(tag: String) = remTag(groupTag,tag)
-    fun remUserTag(tag: String) = remTag(userTag,tag)
-
-    /**
-     * ## 移除整个标签(包括引用)
-     *
-     * @return 如果标签不存在且标签未被引用返回`false`
-     */
-    private fun remTag(tagPool: HashMap<String, ArrayList<String>>, tag: String): Boolean {
-        var res = false
-        if (tagPool.containsKey(tag)) {
-            tagPool.remove(tag)
-            res = true
-        }
-
-        for (i in tagPool.values) {
-            if (i.contains(tag)) {
-                i.remove(tag)
-                res = true
-            }
-        }
-        return res
-    }
-}

+ 55 - 0
src/main/kotlin/bot_luo_core/data/UserTags.kt

@@ -0,0 +1,55 @@
+package bot_luo_core.data
+
+import bot_luo_core.cli.CmdContext
+import bot_luo_core.util.Tag
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+
+object UserTags: DataObj("data/user_tags.json", 1000L, false)  {
+    override fun unload() {}
+
+    val BUILTIN_USER_TAGS = listOf("#this", "#any")
+
+    val tags: HashMap<String, ArrayList<String>> = getObj() ?: HashMap()
+
+    private fun saveTags() = setObj(tags)
+
+    /**
+     * 从标签读取用户列表
+     *
+     * 标签为'#'开头的字符串,大小写不敏感
+     *
+     * @return 用户[ArrayList]?,标签无效则返回空列表,缺少环境时返回`null`
+     */
+    fun readUserTag(tag: String, context: CmdContext?): ArrayList<User>? {
+        val res1 = ArrayList<String>()
+        fun readUserTag1(tag: String, result: ArrayList<String>) {
+            if (tag in BUILTIN_USER_TAGS) {
+                result.add(tag)
+            } else if (tags.containsKey(tag)) {
+                for (item in tags[tag]!!) {
+                    if (item !in result)
+                        if (item.startsWith('#')) {
+                            readUserTag1(item, result)
+                        } else {
+                            result.add(item)
+                        }
+                }
+            }
+        }
+        readUserTag1(tag.lowercase(Locale.getDefault()).trim(), res1)
+        val res = ArrayList<User>()
+        res.addAll(res1.mapNotNull { it.toLongOrNull() }.map { Users.readUser(it) })
+        if (res1.contains("#this")) {
+            if (context == null) return null
+            else res.add(context.user)
+        }
+        if (res1.contains("#any")) res.add(Users.virtualUser)
+        return res
+    }
+    fun readUserTag(tag: String) = readUserTag(tag, null)
+    fun addToUserTag(tag: String, item: String) = Tag.addToTag(tags, tag, item).apply { saveTags() }
+    fun remFromUserTag(tag: String, item: String) = Tag.remFromTag(tags, tag, item).apply { saveTags() }
+    fun remUserTag(tag: String) = Tag.remTag(tags, tag).apply { saveTags() }
+}

+ 100 - 0
src/main/kotlin/bot_luo_core/util/Tag.kt

@@ -0,0 +1,100 @@
+package bot_luo_core.util
+
+object Tag {
+    /**
+     * ## 向指定标签池的指定标签下添加项
+     *
+     * 如果项以'#'开头则视为标签嵌套
+     *
+     * @return 如果出现重复或标签循环嵌套则返回`false`
+     */
+    fun addToTag(tagPool: HashMap<String, ArrayList<String>>, tag: String, item: String): Boolean {
+        if (tagPool.containsKey(tag) && tagPool[tag]!!.contains(item)) return false
+        if (item.startsWith('#')) {
+            if (item == tag) return false
+            val res = ArrayList<String>()
+            getParentTags(tagPool, tag, res)
+            if (item in res) return false
+        }
+        if (!tagPool.containsKey(tag)) tagPool[tag] = ArrayList()
+        tagPool[tag]!!.add(item)
+        return true
+    }
+
+    private fun getParentTags(tagPool: HashMap<String, ArrayList<String>>, tag: String, result: ArrayList<String>) {
+        for (t in tagPool.keys) {
+            if (t in result) continue
+            if (tagPool[t]!!.contains(tag)) {
+                result.add(t)
+                getParentTags(tagPool, t, result)
+            }
+        }
+    }
+
+    /**
+     * ## 从指定标签池的指定标签下移除项
+     *
+     * @return 如果标签不存在或项不存在于标签下返回`false`
+     */
+    fun remFromTag(tagPool: HashMap<String, ArrayList<String>>, tag: String, item: String): Boolean {
+        if (tagPool.containsKey(tag) && tagPool[tag]!!.contains(item)) {
+            tagPool[tag]!!.remove(item)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * ## 移除整个标签(包括引用)
+     *
+     * @return 如果标签不存在且标签未被引用返回`false`
+     */
+    fun remTag(tagPool: HashMap<String, ArrayList<String>>, tag: String): Boolean {
+        var res = false
+        if (tagPool.containsKey(tag)) {
+            tagPool.remove(tag)
+            res = true
+        }
+
+        for (i in tagPool.values) {
+            if (i.contains(tag)) {
+                i.remove(tag)
+                res = true
+            }
+        }
+        return res
+    }
+
+    fun tree(
+        tag: String,
+        table: TableBuilder,
+        line: ArrayList<Int> = ArrayList(),
+        tagGetter: (tag: String) -> ArrayList<String>
+    ) {
+        table.tr()
+        line.forEachIndexed { index, l ->
+            if (index < line.lastIndex) {
+                if (l > 0) table.tb("│")
+                else table.tb("  ")
+            } else {
+                if (l > 0) table.tb("├")
+                else table.tb("└")
+            }
+        }
+        table.tb(tag)
+        val items = tagGetter(tag)
+        items.sortedWith { o1, o2 ->
+            var r = 0
+            if (o1.startsWith('#')) r++
+            if (o2.startsWith('#')) r--
+            r
+        }
+        items.forEachIndexed { index, i ->
+            line.add(items.lastIndex - index)
+
+            tree(i, table, line, tagGetter)
+
+            line.removeLast()
+        }
+    }
+}