@@ -6,61 +6,62 @@ import com.github.kotlintelegrambot.entities.ChatId
6
6
import com.github.kotlintelegrambot.entities.ChatId.Companion.fromChannelUsername
7
7
import com.github.kotlintelegrambot.entities.ChatId.Companion.fromId
8
8
import com.github.kotlintelegrambot.entities.TelegramFile.ByByteArray
9
+ import kotlinx.serialization.KSerializer
10
+ import kotlinx.serialization.builtins.MapSerializer
9
11
import kotlinx.serialization.cbor.Cbor
10
- import kotlinx.serialization.decodeFromByteArray
11
- import kotlinx.serialization.encodeToByteArray
12
+ import kotlinx.serialization.serializer
12
13
import java.io.Closeable
14
+ import java.io.Serializable
13
15
import java.lang.Runtime.getRuntime
14
16
import java.util.concurrent.ConcurrentHashMap
15
17
import java.util.concurrent.Executors.newSingleThreadExecutor
16
- import java.util.concurrent.Future
17
18
18
19
/* *
19
- * Immutable nosql database in your Telegram channel.
20
+ * Immutable NoSQL database in your Telegram channel.
20
21
* @param K key value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
21
22
* @param V storable value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
22
23
* Also see [Telegram Bot API limits](https://core.telegram.org/bots/faq#handling-media)
23
24
*/
24
- class TelegramStorage <K , V > @Deprecated(
25
- " This method had to be made public because of weak JVM generics. Don't use it" ,
26
- ReplaceWith (
27
- " newTelegramStorage<K, V>(bot, channel)" ,
28
- " com.github.demidko.telegram.TelegramStorage.Constructors.newTelegramStorage"
29
- )
30
- ) constructor(
25
+ class TelegramStorage <K , V >(
31
26
private val bot : Bot ,
32
27
private val channel : ChatId ,
33
- private val keyToTelegramFileId : MutableMap <K , String >
28
+ keySerializer : KSerializer <K >,
29
+ private val valueSerializer : KSerializer <V >,
34
30
) : Closeable {
35
31
36
- @Suppress(" Deprecation" )
37
32
companion object Constructors {
38
33
/* *
39
- * Immutable nosql database in your Telegram channel.
34
+ * Immutable NoSQL database in your Telegram channel.
40
35
* @param K key value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
41
36
* @param V storable value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
42
37
* Also see [Telegram Bot API limits](https://core.telegram.org/bots/faq#handling-media)
43
38
* @param botToken Telegram bot token. Must be admin of the [channelName]
44
39
* @param channelName Telegram channel name. Do not change the channel description or files!
45
40
*/
46
- inline fun <reified K , V > newTelegramStorage (botToken : String , channelName : String ): TelegramStorage <K , V > {
47
- return newTelegramStorage(bot { token = botToken }, fromChannelUsername(channelName))
41
+ inline fun <reified K , reified V > TelegramStorage (
42
+ botToken : String ,
43
+ channelName : String
44
+ ): TelegramStorage <K , V > {
45
+ return TelegramStorage (bot { token = botToken }, fromChannelUsername(channelName))
48
46
}
49
47
50
48
/* *
51
- * Immutable nosql database in your Telegram channel.
49
+ * Immutable NoSQL database in your Telegram channel. .
52
50
* @param K key value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
53
51
* @param V storable value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
54
52
* Also see [Telegram Bot API limits](https://core.telegram.org/bots/faq#handling-media)
55
53
* @param botToken Telegram bot token. Must be admin of the [channelId]
56
54
* @param channelId Telegram channel ID. Do not change the channel description or files!
57
55
*/
58
- inline fun <reified K , V > newTelegramStorage (botToken : String , channelId : Long ): TelegramStorage <K , V > {
59
- return newTelegramStorage(bot { token = botToken }, fromId(channelId))
56
+ inline fun <reified K , reified V > TelegramStorage (
57
+ botToken : String ,
58
+ channelId : Long
59
+ ): TelegramStorage <K , V > {
60
+ return TelegramStorage (bot { token = botToken }, fromId(channelId))
60
61
}
61
62
62
63
/* *
63
- * Immutable nosql database in your Telegram channel.
64
+ * Immutable NoSQL database in your Telegram channel.
64
65
* @param K key value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
65
66
* @param V storable value type. Should be [basic](https://kotlinlang.org/docs/basic-types.html) or annotated with [Serializable].
66
67
* Also see [Telegram Bot API limits](https://core.telegram.org/bots/faq#handling-media)
@@ -69,14 +70,13 @@ class TelegramStorage<K, V> @Deprecated(
69
70
* @param channel Telegram channel. Use [fromId] or [fromChannelUsername]. The [bot] must be admin of this channel.
70
71
* Do not change the channel description or files!
71
72
*/
72
- inline fun <reified K , V > newTelegramStorage (bot : Bot , channel : ChatId ): TelegramStorage <K , V > {
73
- val fileId = bot.getChat(channel).get().description ? : return TelegramStorage (bot, channel, ConcurrentHashMap ())
74
- val bytes = bot.downloadFileBytes(fileId) ? : return TelegramStorage (bot, channel, ConcurrentHashMap ())
75
- val map = Cbor .decodeFromByteArray<Map <K , String >>(bytes)
76
- return TelegramStorage (bot, channel, ConcurrentHashMap (map))
73
+ inline fun <reified K , reified V > TelegramStorage (bot : Bot , channel : ChatId ): TelegramStorage <K , V > {
74
+ return TelegramStorage (bot, channel, serializer<K >(), serializer<V >())
77
75
}
78
76
}
79
77
78
+ private val keystoreSerializer = MapSerializer (keySerializer, serializer<String >())
79
+
80
80
/* *
81
81
* Single thread to safe execution order
82
82
*/
@@ -85,57 +85,63 @@ class TelegramStorage<K, V> @Deprecated(
85
85
/* *
86
86
* Shutdown handler to save [keyToTelegramFileId] to [channel]
87
87
*/
88
- private val onShutdown = Thread {
89
- atomicExecutor.submit {
90
- val map = keyToTelegramFileId.toMap()
91
- val telegramFile = Cbor .encodeToByteArray(map).let (::ByByteArray )
92
- val fileId = bot.sendDocument(channel, telegramFile).first?.body()?.result?.document?.fileId!!
93
- val isSuccessfully = bot.setChatDescription(channel, fileId).first?.isSuccessful!!
94
- check(isSuccessfully) { " Can't save file id $fileId " }
95
- }.get()
96
- }.apply (getRuntime()::addShutdownHook)
97
-
98
- val size get() = keyToTelegramFileId.size
88
+ private val shutdownHook = Thread (::close).apply (getRuntime()::addShutdownHook)
99
89
100
- fun remove (k : K ): Future <* >? {
101
- return atomicExecutor.submit {
102
- keyToTelegramFileId.remove(k)
103
- }
90
+ private val keyToTelegramFileId: ConcurrentHashMap <K , String > = run {
91
+ val fileId = bot.getChat(channel).get().description ? : return @run ConcurrentHashMap ()
92
+ val bytes = bot.downloadFileBytes(fileId) ? : return @run ConcurrentHashMap ()
93
+ val keys = Cbor .decodeFromByteArray(keystoreSerializer, bytes)
94
+ ConcurrentHashMap (keys)
104
95
}
105
96
97
+ val size get() = keyToTelegramFileId.size
98
+
106
99
val keys get() = keyToTelegramFileId.keys
107
100
108
- fun setBinaryFuture (k : K , v : ByteArray ): Future <* >? {
109
- return atomicExecutor.submit {
110
- val telegramFile = ByByteArray (v)
111
- keyToTelegramFileId[k] = bot.sendDocument(channel, telegramFile).first?.body()?.result?.document?.fileId!!
101
+ fun remove (k : K ) {
102
+ val removeFuture = atomicExecutor.submit {
103
+ keyToTelegramFileId.remove(k)
112
104
}
105
+ checkNotNull(removeFuture).get()
113
106
}
114
107
115
- fun setBinary (k : K , v : ByteArray ) {
116
- checkNotNull(setBinaryFuture(k, v)).get()
108
+ fun clear () {
109
+ val destructionFuture = atomicExecutor.submit {
110
+ keyToTelegramFileId.clear()
111
+ bot.setChatDescription(channel, " " )
112
+ }
113
+ checkNotNull(destructionFuture).get()
117
114
}
118
115
119
- inline operator fun <reified V1 : V > get (k : K ): V1 ? {
120
- val bytes = getBinary(k) ? : return null
121
- return Cbor .decodeFromByteArray<V1 >(bytes)
116
+ fun isEmpty (): Boolean {
117
+ return keyToTelegramFileId.isEmpty()
122
118
}
123
119
124
- inline fun <reified V1 : V > setFuture (k : K , v : V1 ): Future <* >? {
125
- return setBinaryFuture(k, Cbor .encodeToByteArray(v))
120
+ operator fun get (k : K ): V ? {
121
+ val bytes = keyToTelegramFileId[k]?.let (bot::downloadFileBytes) ? : return null
122
+ return Cbor .decodeFromByteArray(valueSerializer, bytes)
126
123
}
127
124
128
- inline operator fun <reified V1 : V > set (k : K , v : V1 ) {
129
- checkNotNull(setFuture(k, v)).get()
125
+ operator fun set (k : K , v : V ) {
126
+ val setValueFuture = atomicExecutor.submit {
127
+ val telegramFile = ByByteArray (Cbor .encodeToByteArray(valueSerializer, v))
128
+ keyToTelegramFileId[k] = bot.sendDocument(channel, telegramFile).first?.body()?.result?.document?.fileId!!
129
+ }
130
+ checkNotNull(setValueFuture).get()
130
131
}
131
132
132
- fun getBinary (k : K ): ByteArray? {
133
- return keyToTelegramFileId[k]?. let (bot::downloadFileBytes )
133
+ fun containsKey (k : K ): Boolean {
134
+ return keyToTelegramFileId.containsKey(k )
134
135
}
135
136
136
137
override fun close () {
137
- onShutdown.start()
138
- onShutdown.join()
139
- getRuntime().removeShutdownHook(onShutdown)
138
+ val shutdownFuture = atomicExecutor.submit {
139
+ val telegramFile = Cbor .encodeToByteArray(keystoreSerializer, keyToTelegramFileId).let (::ByByteArray )
140
+ val fileId = bot.sendDocument(channel, telegramFile).first?.body()?.result?.document?.fileId!!
141
+ val isSuccessfully = bot.setChatDescription(channel, fileId).first?.isSuccessful!!
142
+ check(isSuccessfully) { " Can't save file id $fileId " }
143
+ }
144
+ checkNotNull(shutdownFuture).get()
145
+ getRuntime().removeShutdownHook(shutdownHook)
140
146
}
141
147
}
0 commit comments