diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/config/ConfigManager.kt b/composeApp/src/commonMain/kotlin/com/clipevery/config/ConfigManager.kt index aeecb8819..19bdde9eb 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/config/ConfigManager.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/config/ConfigManager.kt @@ -2,16 +2,16 @@ package com.clipevery.config import com.clipevery.app.AppEnv import com.clipevery.presist.OneFilePersist -import com.clipevery.utils.DeviceUtils +import com.clipevery.utils.getDeviceUtils import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch abstract class ConfigManager( private val configFilePersist: OneFilePersist, - deviceUtils: DeviceUtils, private val appEnv: AppEnv, ) { + private val deviceUtils = getDeviceUtils() var config: AppConfig diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/presist/FileInfoTree.kt b/composeApp/src/commonMain/kotlin/com/clipevery/presist/FileInfoTree.kt index 6ef9882a7..b2f183f7b 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/presist/FileInfoTree.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/presist/FileInfoTree.kt @@ -2,7 +2,7 @@ package com.clipevery.presist import com.clipevery.clip.item.ClipFile import com.clipevery.clip.item.ClipFileImpl -import com.clipevery.utils.EncryptUtils +import com.clipevery.utils.getEncryptUtils import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -73,6 +73,8 @@ class SingleFileInfoTree( class FileInfoTreeBuilder { + private val encryptUtils = getEncryptUtils() + private val tree = mutableMapOf() private var size = 0L @@ -91,9 +93,9 @@ class FileInfoTreeBuilder { fun build(path: Path): FileInfoTree { val md5 = if (md5List.isEmpty()) { - EncryptUtils.md5ByString(path.fileName.toString()) + encryptUtils.md5ByString(path.fileName.toString()) } else { - EncryptUtils.md5ByArray(md5List.toTypedArray()) + encryptUtils.md5ByArray(md5List.toTypedArray()) } return DirFileInfoTree(tree, size, md5) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt b/composeApp/src/commonMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt deleted file mode 100644 index cbeb090be..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.clipevery.serializer - -import com.clipevery.utils.EncryptUtils.base64Decode -import com.clipevery.utils.EncryptUtils.base64Encode -import com.clipevery.utils.decodePreKeyBundle -import com.clipevery.utils.encodePreKeyBundle -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.signal.libsignal.protocol.state.PreKeyBundle - -object PreKeyBundleSerializer : KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("PreKeyBundle") { - } - - override fun serialize( - encoder: Encoder, - value: PreKeyBundle, - ) { - val byteArray = encodePreKeyBundle(value) - encoder.encodeString(base64Encode(byteArray)) - } - - override fun deserialize(decoder: Decoder): PreKeyBundle { - val byteArray = base64Decode(decoder.decodeString()) - return decodePreKeyBundle(byteArray) - } -} diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/RealmInstantSerializer.kt b/composeApp/src/commonMain/kotlin/com/clipevery/serializer/RealmInstantSerializer.kt deleted file mode 100644 index 908b3daec..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/RealmInstantSerializer.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.clipevery.serializer - -import io.realm.kotlin.internal.RealmInstantImpl -import io.realm.kotlin.types.RealmInstant -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.descriptors.element -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -object RealmInstantSerializer : KSerializer { - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("RealmInstant") { - element("epochSeconds") - element("nanosecondsOfSecond") - } - - override fun deserialize(decoder: Decoder): RealmInstant { - val dec = decoder.beginStructure(descriptor) - var epochSeconds: Long = 0L - var nanosecondsOfSecond: Int = 0 - loop@ while (true) { - when (val index = dec.decodeElementIndex(descriptor)) { - 0 -> epochSeconds = dec.decodeLongElement(descriptor, index) - 1 -> nanosecondsOfSecond = dec.decodeIntElement(descriptor, index) - else -> break@loop - } - } - dec.endStructure(descriptor) - return RealmInstantImpl(epochSeconds, nanosecondsOfSecond) - } - - override fun serialize( - encoder: Encoder, - value: RealmInstant, - ) { - val compositeOutput = encoder.beginStructure(descriptor) - compositeOutput.encodeLongElement(descriptor, 0, value.epochSeconds) - compositeOutput.encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) - compositeOutput.endStructure(descriptor) - } -} diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/detail/HtmlToImageDetailView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/detail/HtmlToImageDetailView.kt index c71d297cb..4c822d31c 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/detail/HtmlToImageDetailView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/detail/HtmlToImageDetailView.kt @@ -33,7 +33,7 @@ import com.clipevery.presist.FilePersist import com.clipevery.ui.base.AsyncView import com.clipevery.ui.base.LoadImageData import com.clipevery.ui.base.loadImage -import com.clipevery.utils.FileUtils +import com.clipevery.utils.getFileUtils import java.awt.Desktop @Composable @@ -44,9 +44,10 @@ fun HtmlToImageDetailView( val current = LocalKoinApplication.current val density = LocalDensity.current val filePersist = current.koin.get() - val fileUtils = current.koin.get() val chromeService = current.koin.get() + val fileUtils = getFileUtils() + val filePath by remember(clipData.id) { mutableStateOf(clipHtml.getHtmlImagePath()) } var existFile by remember(clipData.id) { mutableStateOf(filePath.toFile().exists()) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/ClipPreviewItemView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/ClipPreviewItemView.kt index 24f6c589b..f82123e65 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/ClipPreviewItemView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/ClipPreviewItemView.kt @@ -49,7 +49,7 @@ import com.clipevery.ui.base.ToastManager import com.clipevery.ui.base.ToastStyle import com.clipevery.ui.base.starRegular import com.clipevery.ui.base.starSolid -import com.clipevery.utils.DateUtils +import com.clipevery.utils.getDateUtils import io.realm.kotlin.types.RealmInstant import kotlinx.coroutines.launch import kotlin.reflect.KClass @@ -188,12 +188,14 @@ fun ClipPreviewItemView( } } +val dateUtils = getDateUtils() + fun getDateText( createTime: RealmInstant, copywriter: Copywriter, ): String { - val date = DateUtils.convertRealmInstantToLocalDateTime(createTime) - DateUtils.getDateText(date)?.let { + val date = dateUtils.convertRealmInstantToLocalDateTime(createTime) + dateUtils.getDateText(date)?.let { return copywriter.getText(it) } ?: run { return copywriter.getDate(date) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/HtmlToImagePreviewView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/HtmlToImagePreviewView.kt index ffcd5e1cd..52f575190 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/HtmlToImagePreviewView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/HtmlToImagePreviewView.kt @@ -41,7 +41,7 @@ import com.clipevery.ui.base.AsyncView import com.clipevery.ui.base.LoadImageData import com.clipevery.ui.base.html import com.clipevery.ui.base.loadImage -import com.clipevery.utils.FileUtils +import com.clipevery.utils.getFileUtils import java.awt.Desktop @Composable @@ -50,10 +50,11 @@ fun HtmlToImagePreviewView(clipData: ClipData) { val current = LocalKoinApplication.current val density = LocalDensity.current val copywriter = current.koin.get() - val fileUtils = current.koin.get() val filePersist = current.koin.get() val chromeService = current.koin.get() + val fileUtils = getFileUtils() + val clipHtml = it as ClipHtml val filePath by remember(clipData.id) { mutableStateOf(clipHtml.getHtmlImagePath()) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleFilePreviewView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleFilePreviewView.kt index 76e4e1079..c6ac09db2 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleFilePreviewView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleFilePreviewView.kt @@ -32,7 +32,7 @@ import com.clipevery.ui.base.file import com.clipevery.ui.base.fileSlash import com.clipevery.ui.base.folder import com.clipevery.utils.FileExtUtils -import com.clipevery.utils.FileUtils +import com.clipevery.utils.getFileUtils import io.ktor.util.* import java.awt.Desktop import java.nio.file.Path @@ -41,7 +41,7 @@ import java.nio.file.Path fun SingleFilePreviewView(filePath: Path) { val current = LocalKoinApplication.current val copywriter = current.koin.get() - val fileUtils = current.koin.get() + val fileUtils = getFileUtils() val existFile by remember { mutableStateOf(filePath.toFile().exists()) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleImagePreviewView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleImagePreviewView.kt index 3abb5faf4..9c70d742f 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleImagePreviewView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/SingleImagePreviewView.kt @@ -33,7 +33,7 @@ import com.clipevery.ui.base.LoadImageData import com.clipevery.ui.base.image import com.clipevery.ui.base.imageSlash import com.clipevery.ui.base.loadImage -import com.clipevery.utils.FileUtils +import com.clipevery.utils.getFileUtils import java.awt.Desktop import java.nio.file.Path import kotlin.io.path.exists @@ -43,7 +43,7 @@ fun SingleImagePreviewView(imagePath: Path) { val current = LocalKoinApplication.current val density = LocalDensity.current val copywriter = current.koin.get() - val fileUtils = current.koin.get() + val fileUtils = getFileUtils() val existFile by remember { mutableStateOf(imagePath.exists()) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/TextPreviewView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/TextPreviewView.kt index dffd234b4..bc686d68f 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/TextPreviewView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/clip/preview/TextPreviewView.kt @@ -23,7 +23,7 @@ import com.clipevery.clip.item.ClipText import com.clipevery.dao.clip.ClipData import com.clipevery.i18n.GlobalCopywriter import com.clipevery.ui.base.feed -import com.clipevery.utils.FileUtils +import com.clipevery.utils.getFileUtils import java.awt.Desktop @Composable @@ -31,7 +31,7 @@ fun TextPreviewView(clipData: ClipData) { clipData.getClipItem()?.let { val current = LocalKoinApplication.current val copywriter = current.koin.get() - val fileUtils = current.koin.get() + val fileUtils = getFileUtils() ClipSpecificPreviewContentView(it, { Text( modifier = diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/devices/NearbyDevicesView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/devices/NearbyDevicesView.kt index a809d5a45..f7dc7362e 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/devices/NearbyDevicesView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/devices/NearbyDevicesView.kt @@ -49,7 +49,7 @@ import com.clipevery.ui.base.ClipIconButton import com.clipevery.ui.base.add import com.clipevery.ui.base.magnifying import com.clipevery.ui.base.warning -import com.clipevery.utils.JsonUtils +import com.clipevery.utils.getJsonUtils import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString @@ -229,7 +229,7 @@ fun NearbyDeviceView(syncInfo: SyncInfo) { val deviceManager = current.koin.get() val syncRuntimeInfoDao = current.koin.get() val configManager = current.koin.get() - val jsonUtils = current.koin.get() + val jsonUtils = getJsonUtils() SyncDeviceView(syncInfo = syncInfo) { ClipIconButton( radius = 18.dp, diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/NetSettingsView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/NetSettingsView.kt index 0a63d0c3a..ec7b4924d 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/NetSettingsView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/NetSettingsView.kt @@ -44,8 +44,8 @@ import com.clipevery.ui.base.network import com.clipevery.ui.base.remove import com.clipevery.ui.base.towerBroadcast import com.clipevery.ui.devices.SyncDeviceView -import com.clipevery.utils.JsonUtils -import com.clipevery.utils.NetUtils +import com.clipevery.utils.getJsonUtils +import com.clipevery.utils.getNetUtils import kotlinx.serialization.encodeToString @Composable @@ -54,8 +54,8 @@ fun NetSettingsView() { val configManager = current.koin.get() val deviceManager = current.koin.get() val copywriter = current.koin.get() - val netUtils = current.koin.get() - val jsonUtils = current.koin.get() + val netUtils = getNetUtils() + val jsonUtils = getJsonUtils() var ip: String? by remember { mutableStateOf(null) } var port: String? by remember { mutableStateOf(null) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/StoreSettingsView.kt b/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/StoreSettingsView.kt index d7802f1b9..0049e3696 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/StoreSettingsView.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/ui/settings/StoreSettingsView.kt @@ -58,8 +58,8 @@ import com.clipevery.ui.base.link import com.clipevery.ui.base.percent import com.clipevery.ui.base.trash import com.clipevery.ui.devices.measureTextWidth -import com.clipevery.utils.FileUtils import com.clipevery.utils.Quadruple +import com.clipevery.utils.getFileUtils @Composable fun StoreSettingsView() { @@ -68,7 +68,7 @@ fun StoreSettingsView() { val configManager = current.koin.get() val clipDao = current.koin.get() val copywriter = current.koin.get() - val fileUtils = current.koin.get() + val fileUtils = getFileUtils() var clipCount: Long? by remember { mutableStateOf(null) } var clipFormatSize: String? by remember { mutableStateOf(null) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/DateUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/DateUtils.kt index 91deb15bf..f2410c050 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/DateUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/DateUtils.kt @@ -1,83 +1,26 @@ package com.clipevery.utils import io.realm.kotlin.types.RealmInstant -import java.time.Instant import java.time.LocalDateTime -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.time.temporal.ChronoUnit -import java.util.Calendar import java.util.Locale -object DateUtils { +expect fun getDateUtils(): DateUtils - val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") +interface DateUtils { - fun getPrevDay(): RealmInstant { - val calendar = Calendar.getInstance() - calendar.add(Calendar.DAY_OF_MONTH, -1) - // Convert milliseconds to seconds for the epochSeconds parameter - val epochSeconds = calendar.timeInMillis / 1000 - // The nanosecondAdjustment parameter is 0 as we're not adjusting nanoseconds here - return RealmInstant.from(epochSeconds, 0) - } + fun getPrevDay(): RealmInstant - fun getRealmInstant(days: Int): RealmInstant { - val calendar = Calendar.getInstance() - calendar.add(Calendar.DAY_OF_MONTH, days) - // Convert milliseconds to seconds for the epochSeconds parameter - val epochSeconds = calendar.timeInMillis / 1000 - // The nanosecondAdjustment parameter is 0 as we're not adjusting nanoseconds here - return RealmInstant.from(epochSeconds, 0) - } + fun getRealmInstant(days: Int): RealmInstant - fun getDateText(date: LocalDateTime): String? { - val now = LocalDateTime.now() + fun getDateText(date: LocalDateTime): String? - if (date.toLocalDate().isEqual(now.toLocalDate())) { - val hour = ChronoUnit.HOURS.between(date, now) - val minutes = ChronoUnit.MINUTES.between(date, now) - val seconds = ChronoUnit.SECONDS.between(date, now) - - if (hour < 1 && minutes < 1 && seconds < 60) { - return "Just_now" - } - return "Today" - } - - val yesterday = now.minusDays(1) - if (date.toLocalDate().isEqual(yesterday.toLocalDate())) { - return "Yesterday" - } - - return null - } - - fun getYYYYMMDD(date: LocalDateTime = LocalDateTime.now()): String { - return dateFormatter.format(date) - } + fun getYYYYMMDD(date: LocalDateTime = LocalDateTime.now()): String fun getDateText( date: LocalDateTime, pattern: String, locale: Locale, - ): String { - val formatter: DateTimeFormatter = - Memoize.memoize(pattern, locale) { - DateTimeFormatter.ofPattern(pattern, locale) - }() - return formatter.format(date) - } - - fun convertRealmInstantToLocalDateTime(realmInstant: RealmInstant): LocalDateTime { - // 1. 从 RealmInstant 获取秒和纳秒 - val epochSeconds = realmInstant.epochSeconds - val nanosecondsOfSecond = realmInstant.nanosecondsOfSecond - - // 2. 使用 Instant.ofEpochSecond 创建 Instant - val instant = Instant.ofEpochSecond(epochSeconds, nanosecondsOfSecond.toLong()) + ): String - // 3. 使用系统默认的时区将 Instant 转换为 LocalDateTime - return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) - } + fun convertRealmInstantToLocalDateTime(realmInstant: RealmInstant): LocalDateTime } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/DeviceUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/DeviceUtils.kt index 841675e93..97c5b636a 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/DeviceUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/DeviceUtils.kt @@ -1,5 +1,7 @@ package com.clipevery.utils +expect fun getDeviceUtils(): DeviceUtils + interface DeviceUtils { fun createAppInstanceId(): String diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/EncryptUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/EncryptUtils.kt index 3d6d6c720..8451808dd 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/EncryptUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/EncryptUtils.kt @@ -1,98 +1,38 @@ package com.clipevery.utils -import java.io.ByteArrayOutputStream -import java.security.SecureRandom -import java.util.Base64 -import javax.crypto.Cipher -import javax.crypto.KeyGenerator import javax.crypto.SecretKey -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec -object EncryptUtils { +expect fun getEncryptUtils(): EncryptUtils - fun generateAESKey(): SecretKey { - val keyGen = KeyGenerator.getInstance("AES") - keyGen.init(256) - return keyGen.generateKey() - } +interface EncryptUtils { - fun secretKeyToString(secretKey: SecretKey): String { - val encodedKey = secretKey.encoded - return base64Encode(encodedKey) - } + fun generateAESKey(): SecretKey - fun stringToSecretKey(encodedKey: String): SecretKey { - val decodedKey = base64Decode(encodedKey) - return SecretKeySpec(decodedKey, 0, decodedKey.size, "AES") - } + fun secretKeyToString(secretKey: SecretKey): String - fun base64Encode(bytes: ByteArray): String { - return Base64.getEncoder().encodeToString(bytes) - } + fun stringToSecretKey(encodedKey: String): SecretKey - fun base64Decode(string: String): ByteArray { - return Base64.getDecoder().decode(string) - } + fun base64Encode(bytes: ByteArray): String - fun base64mimeEncode(bytes: ByteArray): String { - return Base64.getMimeEncoder().encodeToString(bytes) - } + fun base64Decode(string: String): ByteArray - fun base64mimeDecode(string: String): ByteArray { - return Base64.getMimeDecoder().decode(string) - } + fun base64mimeEncode(bytes: ByteArray): String + + fun base64mimeDecode(string: String): ByteArray fun encryptData( key: SecretKey, data: ByteArray, - ): ByteArray { - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - val ivBytes = ByteArray(cipher.blockSize) - SecureRandom().nextBytes(ivBytes) - val ivSpec = IvParameterSpec(ivBytes) - - cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec) - val encrypted = cipher.doFinal(data) - return ivBytes + encrypted - } + ): ByteArray fun decryptData( key: SecretKey, encryptedData: ByteArray, - ): ByteArray { - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - val ivBytes = encryptedData.copyOfRange(0, 16) - val actualEncryptedData = encryptedData.copyOfRange(16, encryptedData.size) - - val ivSpec = IvParameterSpec(ivBytes) - cipher.init(Cipher.DECRYPT_MODE, key, ivSpec) - - return cipher.doFinal(actualEncryptedData) - } + ): ByteArray - fun md5(bytes: ByteArray): String { - val md = java.security.MessageDigest.getInstance("MD5") - val digest = md.digest(bytes) - return digest.fold("") { str, it -> str + "%02x".format(it) } - } + fun md5(bytes: ByteArray): String - fun md5ByArray(array: Array): String { - if (array.isEmpty()) { - throw IllegalArgumentException("Array is empty") - } - if (array.size == 1) { - return array[0] - } else { - val outputStream = ByteArrayOutputStream() - array.forEach { - outputStream.write(it.toByteArray()) - } - return md5(outputStream.toByteArray()) - } - } + fun md5ByArray(array: Array): String - fun md5ByString(string: String): String { - return md5(string.toByteArray()) - } + fun md5ByString(string: String): String } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/FileUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/FileUtils.kt index 1ee2c519e..8b8e06056 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/FileUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/FileUtils.kt @@ -7,6 +7,8 @@ import io.ktor.utils.io.* import java.nio.file.Path import java.time.LocalDateTime +expect fun getFileUtils(): FileUtils + interface FileUtils { val tempDirectory: Path diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/HtmlUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/HtmlUtils.kt index 93fed6eb9..524a99362 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/HtmlUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/HtmlUtils.kt @@ -1,11 +1,11 @@ package com.clipevery.utils -import java.util.Base64 - object HtmlUtils { + private val encryptUtils = getEncryptUtils() + fun dataUrl(html: String): String { - val encodedContent = Base64.getEncoder().encodeToString(html.toByteArray()) + val encodedContent = encryptUtils.base64Encode(html.toByteArray()) return "data:text/html;charset=UTF-8;base64,$encodedContent" } } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/JsonUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/JsonUtils.kt index 4932f49bd..421148c2e 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/JsonUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/JsonUtils.kt @@ -2,6 +2,8 @@ package com.clipevery.utils import kotlinx.serialization.json.Json +expect fun getJsonUtils(): JsonUtils + interface JsonUtils { val JSON: Json diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/NetUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/NetUtils.kt index 5a0844855..816af8c3e 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/NetUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/NetUtils.kt @@ -2,6 +2,8 @@ package com.clipevery.utils import com.clipevery.dao.sync.HostInfo +expect fun getNetUtils(): NetUtils + interface NetUtils { fun getHostInfoList(): List diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/PreKeyBundleUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/PreKeyBundleUtils.kt deleted file mode 100644 index 21a24bded..000000000 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/PreKeyBundleUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.clipevery.utils - -import org.signal.libsignal.protocol.IdentityKey -import org.signal.libsignal.protocol.InvalidKeyException -import org.signal.libsignal.protocol.ecc.ECPublicKey -import org.signal.libsignal.protocol.state.PreKeyBundle -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.DataInputStream -import java.io.DataOutputStream -import java.io.IOException - -@Throws(IOException::class, InvalidKeyException::class) -fun decodePreKeyBundle(encoded: ByteArray): PreKeyBundle { - val byteStream = ByteArrayInputStream(encoded) - val dataStream = DataInputStream(byteStream) - val registrationId = dataStream.readInt() - val deviceId = dataStream.readInt() - val preKeyId = dataStream.readInt() - val preKeyPublicSize = dataStream.readInt() - val preKeyPublicBytes = ByteArray(preKeyPublicSize) - dataStream.read(preKeyPublicBytes) - val preKeyPublic = ECPublicKey(preKeyPublicBytes) - val signedPreKeyId = dataStream.readInt() - - val signedPreKeyPublicSize = dataStream.readInt() - val signedPreKeyPublicBytes = ByteArray(signedPreKeyPublicSize) - dataStream.read(signedPreKeyPublicBytes) - val signedPreKeyPublic = ECPublicKey(signedPreKeyPublicBytes) - - val signedPreKeySignatureSize = dataStream.readInt() - val signedPreKeySignatureBytes = ByteArray(signedPreKeySignatureSize) - dataStream.read(signedPreKeySignatureBytes) - - val identityKeySize = dataStream.readInt() - val identityKeyBytes = ByteArray(identityKeySize) - dataStream.read(identityKeyBytes) - val identityKey = IdentityKey(ECPublicKey(identityKeyBytes)) - - return PreKeyBundle( - registrationId, - deviceId, - preKeyId, - preKeyPublic, - signedPreKeyId, - signedPreKeyPublic, - signedPreKeySignatureBytes, - identityKey, - ) -} - -fun encodePreKeyBundle(preKeyBundle: PreKeyBundle): ByteArray { - val byteStream = ByteArrayOutputStream() - val dataStream = DataOutputStream(byteStream) - dataStream.writeInt(preKeyBundle.registrationId) - dataStream.writeInt(preKeyBundle.deviceId) - dataStream.writeInt(preKeyBundle.preKeyId) - val preKeyPublicBytes = preKeyBundle.preKey.serialize() - dataStream.writeInt(preKeyPublicBytes.size) - dataStream.write(preKeyPublicBytes) - dataStream.writeInt(preKeyBundle.signedPreKeyId) - val signedPreKeySignatureBytes = preKeyBundle.signedPreKey.serialize() - dataStream.writeInt(signedPreKeySignatureBytes.size) - dataStream.write(signedPreKeySignatureBytes) - dataStream.writeInt(preKeyBundle.signedPreKeySignature.size) - dataStream.write(preKeyBundle.signedPreKeySignature) - val identityKeyBytes = preKeyBundle.identityKey.serialize() - dataStream.writeInt(identityKeyBytes.size) - dataStream.write(identityKeyBytes) - return byteStream.toByteArray() -} diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/utils/ResourceUtils.kt b/composeApp/src/commonMain/kotlin/com/clipevery/utils/ResourceUtils.kt index a78575641..705ab76d6 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/utils/ResourceUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/utils/ResourceUtils.kt @@ -1,31 +1,10 @@ package com.clipevery.utils -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.toComposeImageBitmap import java.util.Properties -object ResourceUtils { +expect fun getResourceUtils(): ResourceUtils - fun loadImageBitmap(resourcePath: String): ImageBitmap { - // Assuming `resourcePath` is a valid path for an image file within your resources directory. - val image = - org.jetbrains.skia.Image.makeFromEncoded( - Thread.currentThread().contextClassLoader.getResourceAsStream(resourcePath) - ?.readBytes()!!, - ) - return image.toComposeImageBitmap() - } +interface ResourceUtils { - fun loadProperties(fileName: String): Properties { - val properties = Properties() - val classLoader = Thread.currentThread().contextClassLoader - classLoader.getResourceAsStream(fileName).use { inputStream -> - if (inputStream == null) { - throw IllegalArgumentException("File not found: $fileName") - } else { - properties.load(inputStream) - } - } - return properties - } + fun loadProperties(fileName: String): Properties } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/Clipevery.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/Clipevery.kt index d9b95c70d..5f5472828 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/Clipevery.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/Clipevery.kt @@ -100,17 +100,9 @@ import com.clipevery.ui.base.ToastManager import com.clipevery.ui.getTrayMouseAdapter import com.clipevery.ui.resource.ClipResourceLoader import com.clipevery.ui.resource.DesktopAbsoluteClipResourceLoader -import com.clipevery.utils.DesktopDeviceUtils -import com.clipevery.utils.DesktopFileUtils -import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.DesktopNetUtils import com.clipevery.utils.DesktopQRCodeGenerator -import com.clipevery.utils.DeviceUtils -import com.clipevery.utils.FileUtils import com.clipevery.utils.IDGenerator import com.clipevery.utils.IDGeneratorFactory -import com.clipevery.utils.JsonUtils -import com.clipevery.utils.NetUtils import com.clipevery.utils.QRCodeGenerator import com.clipevery.utils.TelnetUtils import com.clipevery.utils.ioDispatcher @@ -159,15 +151,10 @@ class Clipevery { DefaultConfigManager( get().getPersist("appConfig.json", AppFileType.USER), get(), - get(), ) } single { DesktopQRCodeGenerator(get(), get()) } single { IDGeneratorFactory(get()).createIDGenerator() } - single { DesktopJsonUtils } - single { DesktopFileUtils } - single { DesktopDeviceUtils } - single { DesktopNetUtils } single { CacheManagerImpl(get()) } single { clipLogger } single { Clipevery.logger } @@ -231,7 +218,7 @@ class Clipevery { listOf( SyncClipTaskExecutor(get(), get(), get()), DeleteClipTaskExecutor(get()), - PullFileTaskExecutor(get(), get(), get(), get(), get()), + PullFileTaskExecutor(get(), get(), get(), get()), CleanClipTaskExecutor(get(), get()), ), get(), @@ -252,7 +239,6 @@ class Clipevery { } private fun initInject() { - koinApplication.koin.get() koinApplication.koin.get() koinApplication.koin.get() koinApplication.koin.get() diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/CacheManagerImpl.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/CacheManagerImpl.kt index 62a368174..081c4dded 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/CacheManagerImpl.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/CacheManagerImpl.kt @@ -8,6 +8,7 @@ import com.clipevery.presist.FilesIndex import com.clipevery.presist.FilesIndexBuilder import com.clipevery.task.PullFileTaskExecutor import com.clipevery.utils.DateUtils +import com.clipevery.utils.getDateUtils import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache @@ -15,6 +16,8 @@ import java.util.concurrent.TimeUnit class CacheManagerImpl(private val clipDao: ClipDao) : CacheManager { + private val dateUtils: DateUtils = getDateUtils() + override val filesIndexCache: LoadingCache = CacheBuilder.newBuilder() .maximumSize(1000) @@ -26,8 +29,8 @@ class CacheManagerImpl(private val clipDao: ClipDao) : CacheManager { val clipId = key.clipId clipDao.getClipData(appInstanceId, clipId)?.let { clipData -> val dateString = - DateUtils.getYYYYMMDD( - DateUtils.convertRealmInstantToLocalDateTime(clipData.createTime), + dateUtils.getYYYYMMDD( + dateUtils.convertRealmInstantToLocalDateTime(clipData.createTime), ) val filesIndexBuilder = FilesIndexBuilder(PullFileTaskExecutor.CHUNK_SIZE) val fileItems = clipData.getClipAppearItems().filter { it is ClipFiles } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultFilesPlugin.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultFilesPlugin.kt index b531657a9..7c1a81884 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultFilesPlugin.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultFilesPlugin.kt @@ -4,12 +4,15 @@ import com.clipevery.clip.ClipPlugin import com.clipevery.clip.item.FilesClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.EncryptUtils.md5ByArray +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.MutableRealm import io.realm.kotlin.ext.toRealmList import kotlinx.serialization.encodeToString object MultFilesPlugin : ClipPlugin { + + private val encryptUtils = getEncryptUtils() + override fun pluginProcess( clipAppearItems: List, realm: MutableRealm, @@ -27,7 +30,7 @@ object MultFilesPlugin : ClipPlugin { val fileInfoMapJsonString = DesktopJsonUtils.JSON.encodeToString(fileInfoMap) val md5 = clipAppearItems.map { it as FilesClipItem }.map { it.md5 } - .toTypedArray().let { md5ByArray(it) } + .toTypedArray().let { encryptUtils.md5ByArray(it) } clipAppearItems.forEach { it.clear(realm, clearResource = false) } return FilesClipItem().apply { this.relativePathList = relativePathList diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultiImagesPlugin.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultiImagesPlugin.kt index 7e139f939..215a864fc 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultiImagesPlugin.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/plugin/MultiImagesPlugin.kt @@ -5,12 +5,15 @@ import com.clipevery.clip.item.FilesClipItem import com.clipevery.clip.item.ImagesClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.EncryptUtils.md5ByArray +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.MutableRealm import io.realm.kotlin.ext.toRealmList import kotlinx.serialization.encodeToString object MultiImagesPlugin : ClipPlugin { + + private val encryptUtils = getEncryptUtils() + override fun pluginProcess( clipAppearItems: List, realm: MutableRealm, @@ -30,7 +33,7 @@ object MultiImagesPlugin : ClipPlugin { val size = fileInfoMap.map { it.value.size }.sum() val md5 = clipAppearItems.map { it as FilesClipItem }.map { it.md5 } - .toTypedArray().let { md5ByArray(it) } + .toTypedArray().let { encryptUtils.md5ByArray(it) } clipAppearItems.forEach { it.clear(realm, clearResource = false) } return ImagesClipItem().apply { this.relativePathList = relativePathList diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FilesItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FilesItemService.kt index 808821026..259907fb0 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FilesItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FilesItemService.kt @@ -11,7 +11,7 @@ import com.clipevery.utils.DesktopFileUtils import com.clipevery.utils.DesktopFileUtils.copyPath import com.clipevery.utils.DesktopFileUtils.createClipRelativePath import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.EncryptUtils.md5ByArray +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.MutableRealm import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.toRealmList @@ -25,6 +25,8 @@ class FilesItemService(appInfo: AppInfo) : ClipItemService(appInfo) { companion object FilesItemService { const val FILE_LIST_ID = "application/x-java-file-list" + + private val encryptUtils = getEncryptUtils() } override fun getIdentifiers(): List { @@ -78,7 +80,7 @@ class FilesItemService(appInfo: AppInfo) : ClipItemService(appInfo) { val relativePathRealmList = relativePathList.toRealmList() val fileInfoTreeJsonString = DesktopJsonUtils.JSON.encodeToString(fileInfoTrees) - val md5 = md5ByArray(files.mapNotNull { fileInfoTrees[it.name]?.md5 }.toTypedArray()) + val md5 = encryptUtils.md5ByArray(files.mapNotNull { fileInfoTrees[it.name]?.md5 }.toTypedArray()) val count = fileInfoTrees.map { it.value.getCount() }.sum() val size = fileInfoTrees.map { it.value.size }.sum() diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt index 8c2f9d725..9587883bf 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt @@ -6,7 +6,7 @@ import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.HtmlClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.DesktopFileUtils -import com.clipevery.utils.EncryptUtils.md5 +import com.clipevery.utils.getEncryptUtils import io.github.oshai.kotlinlogging.KotlinLogging import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor @@ -17,6 +17,8 @@ class HtmlItemService(appInfo: AppInfo) : ClipItemService(appInfo) { companion object HtmlItemService { const val HTML_ID = "text/html" + + private val encryptUtils = getEncryptUtils() } val logger = KotlinLogging.logger {} @@ -51,7 +53,7 @@ class HtmlItemService(appInfo: AppInfo) : ClipItemService(appInfo) { if (transferData is String) { val html = extractHtml(transferData) val htmlBytes = html.toByteArray() - val md5 = md5(htmlBytes) + val md5 = encryptUtils.md5(htmlBytes) val relativePath = DesktopFileUtils.createClipRelativePath( appInstanceId = appInfo.appInstanceId, diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt index cbb05ed99..9eaf338bb 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt @@ -5,7 +5,7 @@ import com.clipevery.clip.ClipCollector import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.TextClipItem import com.clipevery.dao.clip.ClipAppearItem -import com.clipevery.utils.EncryptUtils.md5 +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable @@ -17,6 +17,8 @@ class TextItemService(appInfo: AppInfo) : ClipItemService(appInfo) { const val UNICODE_STRING = "Unicode String" const val TEXT = "text/plain" const val PLAIN_TEXT = "Plain Text" + + private val encryptUtils = getEncryptUtils() } override fun getIdentifiers(): List { @@ -48,7 +50,7 @@ class TextItemService(appInfo: AppInfo) : ClipItemService(appInfo) { ) { if (transferData is String) { val textBytes = transferData.toByteArray() - val md5 = md5(textBytes) + val md5 = encryptUtils.md5(textBytes) val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> realm.query(TextClipItem::class, "id == $0", clipItem.id).first().find()?.apply { this.text = transferData diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt index 3935b6060..91677b242 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt @@ -5,7 +5,7 @@ import com.clipevery.clip.ClipCollector import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.UrlClipItem import com.clipevery.dao.clip.ClipAppearItem -import com.clipevery.utils.EncryptUtils.md5 +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable @@ -14,6 +14,8 @@ class UrlItemService(appInfo: AppInfo) : ClipItemService(appInfo) { companion object UrlItemService { const val URL = "application/x-java-url" + + private val encryptUtils = getEncryptUtils() } override fun getIdentifiers(): List { @@ -45,7 +47,7 @@ class UrlItemService(appInfo: AppInfo) : ClipItemService(appInfo) { ) { if (transferData is String) { val urlBytes = transferData.toByteArray() - val md5 = md5(urlBytes) + val md5 = encryptUtils.md5(urlBytes) val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> realm.query(UrlClipItem::class, "id == $0", clipItem.id).first().find()?.apply { this.url = transferData diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/config/DefaultConfigManager.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/config/DefaultConfigManager.kt index 0cb55b553..ab7be3a00 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/config/DefaultConfigManager.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/config/DefaultConfigManager.kt @@ -2,16 +2,14 @@ package com.clipevery.config import com.clipevery.app.AppEnv import com.clipevery.presist.OneFilePersist -import com.clipevery.utils.DeviceUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob class DefaultConfigManager( private val configFilePersist: OneFilePersist, - deviceUtils: DeviceUtils, appEnv: AppEnv, -) : ConfigManager(configFilePersist, deviceUtils, appEnv) { +) : ConfigManager(configFilePersist, appEnv) { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt index e0483f359..ef71f61a4 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt @@ -9,8 +9,8 @@ import com.clipevery.clip.item.UrlClipItem import com.clipevery.dao.task.TaskType import com.clipevery.task.TaskExecutor import com.clipevery.task.extra.SyncExtraInfo -import com.clipevery.utils.DateUtils import com.clipevery.utils.TaskUtils.createTask +import com.clipevery.utils.getDateUtils import io.github.oshai.kotlinlogging.KotlinLogging import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm @@ -30,6 +30,8 @@ class ClipRealm( val logger = KotlinLogging.logger {} + private val dateUtils = getDateUtils() + private val taskExecutor by lazy { lazyTaskExecutor.value } override fun getMaxClipId(): Long { @@ -82,7 +84,7 @@ class ClipRealm( ClipData::class, "md5 == $0 AND createTime > $1 AND id != $2 AND clipState != $3", newClipDataMd5, - DateUtils.getPrevDay(), + dateUtils.getPrevDay(), newClipDataId, ClipState.DELETED, ) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/i18n/I18n.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/i18n/I18n.kt index b16901e0d..8946967e5 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/i18n/I18n.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/i18n/I18n.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.clipevery.config.ConfigManager -import com.clipevery.utils.DateUtils +import com.clipevery.utils.getDateUtils import io.github.oshai.kotlinlogging.KotlinLogging import java.io.FileNotFoundException import java.io.InputStreamReader @@ -69,6 +69,8 @@ open class GlobalCopywriterImpl(private val configManager: ConfigManager) : Glob class CopywriterImpl(private val language: String) : Copywriter { + private val dateUtils = getDateUtils() + private val properties: Properties = loadProperties() private var currentLanguage: String = en @@ -134,7 +136,7 @@ class CopywriterImpl(private val language: String) : Copywriter { "zh" -> "yyyy年MM月dd日" else -> "MM/dd/yyyy" } - return DateUtils.getDateText(date, pattern, locale) + return dateUtils.getDateText(date, pattern, locale) } override fun getAbridge(): String { diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/net/clientapi/DesktopSyncClientApi.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/net/clientapi/DesktopSyncClientApi.kt index 2e91c6c75..cebb350dc 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/net/clientapi/DesktopSyncClientApi.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/net/clientapi/DesktopSyncClientApi.kt @@ -4,8 +4,8 @@ import com.clipevery.dto.sync.DataContent import com.clipevery.dto.sync.RequestTrust import com.clipevery.dto.sync.SyncInfo import com.clipevery.net.ClipClient +import com.clipevery.serializer.PreKeyBundleSerializer import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.decodePreKeyBundle import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.call.* import io.ktor.http.* @@ -28,7 +28,7 @@ class DesktopSyncClientApi( if (response.status.value != 200) { return null } - return decodePreKeyBundle(response.body().data) + return PreKeyBundleSerializer.decodePreKeyBundle(response.body().data) } catch (e: Exception) { logger.error(e) { "getPreKeyBundle error" } } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/path/DesktopPathProvider.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/path/DesktopPathProvider.kt index 080ebbb73..e24255bdc 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/path/DesktopPathProvider.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/path/DesktopPathProvider.kt @@ -2,9 +2,9 @@ package com.clipevery.path import com.clipevery.app.AppEnv import com.clipevery.platform.currentPlatform -import com.clipevery.utils.DesktopFileUtils import com.clipevery.utils.FileUtils -import com.clipevery.utils.ResourceUtils +import com.clipevery.utils.getFileUtils +import com.clipevery.utils.getResourceUtils import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -13,7 +13,7 @@ object DesktopPathProvider : PathProvider { private val pathProvider = getPathProvider() - override val fileUtils: FileUtils = DesktopFileUtils + override val fileUtils: FileUtils = getFileUtils() override val clipAppPath: Path = pathProvider.clipAppPath @@ -47,13 +47,15 @@ class DevelopmentPathProvider : PathProvider { private val composeAppDir = System.getProperty("user.dir") - private val development = ResourceUtils.loadProperties("development.properties") + private val resourceUtils = getResourceUtils() + + private val development = resourceUtils.loadProperties("development.properties") override val clipAppPath: Path = getAppPath() override val clipUserPath: Path = getUserPath() - override val fileUtils: FileUtils = DesktopFileUtils + override val fileUtils: FileUtils = getFileUtils() private fun getAppPath(): Path { development.getProperty("clipAppPath")?.let { @@ -90,7 +92,7 @@ class WindowsPathProvider : PathProvider { override val clipUserPath: Path = Paths.get(userHomePath).resolve(".clipevery") - override val fileUtils: FileUtils = DesktopFileUtils + override val fileUtils: FileUtils = getFileUtils() private fun getAppPath(): Path { System.getProperty("compose.application.resources.dir")?.let { @@ -124,7 +126,7 @@ class MacosPathProvider : PathProvider { override val clipLogPath: Path = getLogPath() - override val fileUtils: FileUtils = DesktopFileUtils + override val fileUtils: FileUtils = getFileUtils() private fun getAppSupportPath(): Path { val appSupportPath = Paths.get(userHome, "Library", "Application Support", "Clipevery") @@ -163,5 +165,5 @@ class LinuxPathProvider : PathProvider { override val clipUserPath: Path get() = TODO("Not yet implemented") - override val fileUtils: FileUtils = DesktopFileUtils + override val fileUtils: FileUtils = getFileUtils() } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/PullRouting.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/PullRouting.kt index 2132ed9dd..031a54d2e 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/PullRouting.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/PullRouting.kt @@ -6,9 +6,9 @@ import com.clipevery.dto.pull.PullFileRequest import com.clipevery.dto.pull.PullFilesKey import com.clipevery.exception.StandardErrorCode import com.clipevery.sync.SyncManager -import com.clipevery.utils.FileUtils import com.clipevery.utils.failResponse import com.clipevery.utils.getAppInstanceId +import com.clipevery.utils.getFileUtils import com.clipevery.utils.successResponse import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.server.application.* @@ -25,7 +25,7 @@ fun Routing.pullRouting() { val cacheManager = koinApplication.koin.get() - val fileUtils = koinApplication.koin.get() + val fileUtils = getFileUtils() post("/pull/file") { getAppInstanceId(call).let { fromAppInstanceId -> diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt index 8a93f3d71..89e87b677 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/routing/SyncRouting.kt @@ -9,8 +9,8 @@ import com.clipevery.dto.sync.DataContent import com.clipevery.dto.sync.RequestTrust import com.clipevery.dto.sync.SyncInfo import com.clipevery.exception.StandardErrorCode +import com.clipevery.serializer.PreKeyBundleSerializer import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.encodePreKeyBundle import com.clipevery.utils.failResponse import com.clipevery.utils.getAppInstanceId import com.clipevery.utils.successResponse @@ -82,7 +82,7 @@ fun Routing.syncRouting() { identityKeyPair.publicKey, ) - val bytes = encodePreKeyBundle(preKeyBundle) + val bytes = PreKeyBundleSerializer.encodePreKeyBundle(preKeyBundle) logger.debug { "${appInfo.appInstanceId} create preKeyBundle for $appInstanceId:\n $preKeyBundle" } successResponse(call, DataContent(bytes)) } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt similarity index 73% rename from composeApp/src/commonMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt index 4088803b6..6addfddae 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/Base64ByteArraySerializer.kt @@ -1,7 +1,6 @@ package com.clipevery.serializer -import com.clipevery.utils.EncryptUtils.base64Decode -import com.clipevery.utils.EncryptUtils.base64Encode +import com.clipevery.utils.getEncryptUtils import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor @@ -10,16 +9,18 @@ import kotlinx.serialization.encoding.Encoder object Base64ByteArraySerializer : KSerializer { + private val encryptUtils = getEncryptUtils() + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ByteArray") {} override fun deserialize(decoder: Decoder): ByteArray { - return base64Decode(decoder.decodeString()) + return encryptUtils.base64Decode(decoder.decodeString()) } override fun serialize( encoder: Encoder, value: ByteArray, ) { - encoder.encodeString(base64Encode(value)) + encoder.encodeString(encryptUtils.base64Encode(value)) } } diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt similarity index 74% rename from composeApp/src/commonMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt index e04ba7494..1ff214133 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/IdentityKeySerializer.kt @@ -1,7 +1,6 @@ package com.clipevery.serializer -import com.clipevery.utils.EncryptUtils.base64Decode -import com.clipevery.utils.EncryptUtils.base64Encode +import com.clipevery.utils.getEncryptUtils import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor @@ -10,10 +9,13 @@ import kotlinx.serialization.encoding.Encoder import org.signal.libsignal.protocol.IdentityKey object IdentityKeySerializer : KSerializer { + + private val encryptUtils = getEncryptUtils() + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("IdentityKey") {} override fun deserialize(decoder: Decoder): IdentityKey { - val byteArray = base64Decode(decoder.decodeString()) + val byteArray = encryptUtils.base64Decode(decoder.decodeString()) return IdentityKey(byteArray) } @@ -21,6 +23,6 @@ object IdentityKeySerializer : KSerializer { encoder: Encoder, value: IdentityKey, ) { - encoder.encodeString(base64Encode(value.serialize())) + encoder.encodeString(encryptUtils.base64Encode(value.serialize())) } } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt new file mode 100644 index 000000000..31a886d41 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/PreKeyBundleSerializer.kt @@ -0,0 +1,99 @@ +package com.clipevery.serializer + +import com.clipevery.utils.getEncryptUtils +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.InvalidKeyException +import org.signal.libsignal.protocol.ecc.ECPublicKey +import org.signal.libsignal.protocol.state.PreKeyBundle +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException + +object PreKeyBundleSerializer : KSerializer { + + private val encryptUtils = getEncryptUtils() + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("PreKeyBundle") { + } + + override fun serialize( + encoder: Encoder, + value: PreKeyBundle, + ) { + val byteArray = encodePreKeyBundle(value) + encoder.encodeString(encryptUtils.base64Encode(byteArray)) + } + + override fun deserialize(decoder: Decoder): PreKeyBundle { + val byteArray = encryptUtils.base64Decode(decoder.decodeString()) + return decodePreKeyBundle(byteArray) + } + + @Throws(IOException::class, InvalidKeyException::class) + fun decodePreKeyBundle(encoded: ByteArray): PreKeyBundle { + val byteStream = ByteArrayInputStream(encoded) + val dataStream = DataInputStream(byteStream) + val registrationId = dataStream.readInt() + val deviceId = dataStream.readInt() + val preKeyId = dataStream.readInt() + val preKeyPublicSize = dataStream.readInt() + val preKeyPublicBytes = ByteArray(preKeyPublicSize) + dataStream.read(preKeyPublicBytes) + val preKeyPublic = ECPublicKey(preKeyPublicBytes) + val signedPreKeyId = dataStream.readInt() + + val signedPreKeyPublicSize = dataStream.readInt() + val signedPreKeyPublicBytes = ByteArray(signedPreKeyPublicSize) + dataStream.read(signedPreKeyPublicBytes) + val signedPreKeyPublic = ECPublicKey(signedPreKeyPublicBytes) + + val signedPreKeySignatureSize = dataStream.readInt() + val signedPreKeySignatureBytes = ByteArray(signedPreKeySignatureSize) + dataStream.read(signedPreKeySignatureBytes) + + val identityKeySize = dataStream.readInt() + val identityKeyBytes = ByteArray(identityKeySize) + dataStream.read(identityKeyBytes) + val identityKey = IdentityKey(ECPublicKey(identityKeyBytes)) + + return PreKeyBundle( + registrationId, + deviceId, + preKeyId, + preKeyPublic, + signedPreKeyId, + signedPreKeyPublic, + signedPreKeySignatureBytes, + identityKey, + ) + } + + fun encodePreKeyBundle(preKeyBundle: PreKeyBundle): ByteArray { + val byteStream = ByteArrayOutputStream() + val dataStream = DataOutputStream(byteStream) + dataStream.writeInt(preKeyBundle.registrationId) + dataStream.writeInt(preKeyBundle.deviceId) + dataStream.writeInt(preKeyBundle.preKeyId) + val preKeyPublicBytes = preKeyBundle.preKey.serialize() + dataStream.writeInt(preKeyPublicBytes.size) + dataStream.write(preKeyPublicBytes) + dataStream.writeInt(preKeyBundle.signedPreKeyId) + val signedPreKeySignatureBytes = preKeyBundle.signedPreKey.serialize() + dataStream.writeInt(signedPreKeySignatureBytes.size) + dataStream.write(signedPreKeySignatureBytes) + dataStream.writeInt(preKeyBundle.signedPreKeySignature.size) + dataStream.write(preKeyBundle.signedPreKeySignature) + val identityKeyBytes = preKeyBundle.identityKey.serialize() + dataStream.writeInt(identityKeyBytes.size) + dataStream.write(identityKeyBytes) + return byteStream.toByteArray() + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/TextClipItemSerializer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/TextClipItemSerializer.kt deleted file mode 100644 index 77d03dc88..000000000 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/serializer/TextClipItemSerializer.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.clipevery.serializer - -import com.clipevery.clip.item.TextClipItem -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.descriptors.element -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.encodeStructure - -object TextClipItemSerializer : KSerializer { - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("TextClipItem") { - element("identifier") - element("text") - element("favorite") - element("size") - element("md5") - element("extraInfo") - } - - @OptIn(ExperimentalSerializationApi::class) - override fun deserialize(decoder: Decoder): TextClipItem { - val dec = decoder.beginStructure(descriptor) - var identifier = "" - var text = "" - var favorite = false - var size = 0L - var md5 = "" - var extraInfo: String? = null - loop@ while (true) { - when (val index = dec.decodeElementIndex(descriptor)) { - 0 -> identifier = dec.decodeStringElement(descriptor, index) - 1 -> text = dec.decodeStringElement(descriptor, index) - 2 -> favorite = dec.decodeBooleanElement(descriptor, index) - 3 -> size = dec.decodeLongElement(descriptor, index) - 4 -> md5 = dec.decodeStringElement(descriptor, index) - 5 -> extraInfo = dec.decodeNullableSerializableElement(descriptor, index, String.serializer()) - else -> break@loop - } - } - dec.endStructure(descriptor) - return TextClipItem().apply { - this.identifier = identifier - this.text = text - this.favorite = favorite - this.size = size - this.md5 = md5 - this.extraInfo = extraInfo - } - } - - @OptIn(ExperimentalSerializationApi::class) - override fun serialize( - encoder: Encoder, - value: TextClipItem, - ) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, value.identifier) - encodeStringElement(descriptor, 1, value.text) - encodeBooleanElement(descriptor, 2, value.favorite) - encodeLongElement(descriptor, 3, value.size) - encodeStringElement(descriptor, 4, value.md5) - encodeNullableSerializableElement(descriptor, 5, String.serializer(), value.extraInfo) - } - } -} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/signal/IdentityKeyStore.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/signal/IdentityKeyStore.kt index ae6af01a1..9f21a6a90 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/signal/IdentityKeyStore.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/signal/IdentityKeyStore.kt @@ -9,11 +9,7 @@ import com.clipevery.os.windows.WindowDapiHelper import com.clipevery.path.DesktopPathProvider import com.clipevery.platform.currentPlatform import com.clipevery.presist.DesktopOneFilePersist -import com.clipevery.utils.EncryptUtils.decryptData -import com.clipevery.utils.EncryptUtils.encryptData -import com.clipevery.utils.EncryptUtils.generateAESKey -import com.clipevery.utils.EncryptUtils.secretKeyToString -import com.clipevery.utils.EncryptUtils.stringToSecretKey +import com.clipevery.utils.getEncryptUtils import io.github.oshai.kotlinlogging.KotlinLogging import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.IdentityKeyPair @@ -113,6 +109,8 @@ class MacosIdentityKeyStoreFactory( private val signalDao: SignalDao, ) : IdentityKeyStoreFactory { + private val encryptUtils = getEncryptUtils() + private val filePersist = DesktopOneFilePersist( DesktopPathProvider.resolve("signal.data", AppFileType.ENCRYPT), @@ -129,8 +127,8 @@ class MacosIdentityKeyStoreFactory( password?.let { logger.info { "Found password in keychain by $service ${appInfo.userName}" } try { - val secretKey = stringToSecretKey(it) - val decryptData = decryptData(secretKey, bytes) + val secretKey = encryptUtils.stringToSecretKey(it) + val decryptData = encryptUtils.decryptData(secretKey, bytes) val (identityKeyPair, registrationId) = readIdentityKeyPairWithRegistrationId(decryptData) return DesktopIdentityKeyStore(signalDao, identityKeyPair, registrationId) } catch (e: Exception) { @@ -154,15 +152,15 @@ class MacosIdentityKeyStoreFactory( val secretKey = password?.let { logger.info { "Found password in keychain by $service ${appInfo.userName}" } - stringToSecretKey(it) + encryptUtils.stringToSecretKey(it) } ?: run { logger.info { "Generating new password in keychain by $service ${appInfo.userName}" } - val secretKey = generateAESKey() - MacosKeychainHelper.setPassword(service, appInfo.userName, secretKeyToString(secretKey)) + val secretKey = encryptUtils.generateAESKey() + MacosKeychainHelper.setPassword(service, appInfo.userName, encryptUtils.secretKeyToString(secretKey)) secretKey } - val encryptData = encryptData(secretKey, data) + val encryptData = encryptUtils.encryptData(secretKey, data) filePersist.saveBytes(encryptData) return DesktopIdentityKeyStore(signalDao, identityKeyPair, registrationId) } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/task/CleanClipTaskExecutor.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/task/CleanClipTaskExecutor.kt index 26fe63249..52055f623 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/task/CleanClipTaskExecutor.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/task/CleanClipTaskExecutor.kt @@ -10,6 +10,7 @@ import com.clipevery.task.extra.BaseExtraInfo import com.clipevery.utils.DateUtils import com.clipevery.utils.TaskUtils import com.clipevery.utils.TaskUtils.createFailureClipTaskResult +import com.clipevery.utils.getDateUtils import io.realm.kotlin.types.RealmInstant import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -19,6 +20,8 @@ class CleanClipTaskExecutor( private val configManager: ConfigManager, ) : SingleTypeTaskExecutor { + private val dateUtils: DateUtils = getDateUtils() + override val taskType: Int = TaskType.CLEAN_CLIP_TASK private val cleanLock = Mutex() @@ -29,11 +32,11 @@ class CleanClipTaskExecutor( try { cleanLock.withLock { val imageCleanTime = CleanTime.entries[configManager.config.imageCleanTimeIndex] - val imageCleanTimeInstant = DateUtils.getRealmInstant(-imageCleanTime.days) + val imageCleanTimeInstant = dateUtils.getRealmInstant(-imageCleanTime.days) clipDao.markDeleteByCleanTime(imageCleanTimeInstant, ClipType.IMAGE) val fileCleanTime = CleanTime.entries[configManager.config.fileCleanTimeIndex] - val fileCleanTimeInstant = DateUtils.getRealmInstant(-fileCleanTime.days) + val fileCleanTimeInstant = dateUtils.getRealmInstant(-fileCleanTime.days) clipDao.markDeleteByCleanTime(fileCleanTimeInstant, ClipType.FILE) } } catch (e: Throwable) { diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/task/PullFileTaskExecutor.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/task/PullFileTaskExecutor.kt index 3a2e565b6..bcac8e5d3 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/task/PullFileTaskExecutor.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/task/PullFileTaskExecutor.kt @@ -21,6 +21,8 @@ import com.clipevery.utils.FileUtils import com.clipevery.utils.TaskUtils import com.clipevery.utils.TaskUtils.createFailureClipTaskResult import com.clipevery.utils.buildUrl +import com.clipevery.utils.getDateUtils +import com.clipevery.utils.getFileUtils import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.* import io.ktor.utils.io.* @@ -28,7 +30,6 @@ import io.ktor.utils.io.* class PullFileTaskExecutor( private val clipDao: ClipDao, private val pullFileClientApi: PullFileClientApi, - private val fileUtils: FileUtils, private val syncManager: SyncManager, private val clipboardService: ClipboardService, ) : SingleTypeTaskExecutor { @@ -38,6 +39,10 @@ class PullFileTaskExecutor( private val logger = KotlinLogging.logger {} const val CHUNK_SIZE: Long = 1024 * 1024 // 1MB + + private val dateUtils: DateUtils = getDateUtils() + + private val fileUtils: FileUtils = getFileUtils() } override val taskType: Int = TaskType.PULL_FILE_TASK @@ -49,8 +54,8 @@ class PullFileTaskExecutor( val fileItems = clipData.getClipAppearItems().filter { it is ClipFiles } val appInstanceId = clipData.appInstanceId val dateString = - DateUtils.getYYYYMMDD( - DateUtils.convertRealmInstantToLocalDateTime(clipData.createTime), + dateUtils.getYYYYMMDD( + dateUtils.convertRealmInstantToLocalDateTime(clipData.createTime), ) val clipId = clipData.clipId val filesIndexBuilder = FilesIndexBuilder(CHUNK_SIZE) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DateUtils.desktop.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DateUtils.desktop.kt new file mode 100644 index 000000000..470ca1a9e --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DateUtils.desktop.kt @@ -0,0 +1,87 @@ +package com.clipevery.utils + +import io.realm.kotlin.types.RealmInstant +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.util.Calendar +import java.util.Locale + +actual fun getDateUtils(): DateUtils { + return DesktopDateUtils +} + +object DesktopDateUtils : DateUtils { + + val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + + override fun getPrevDay(): RealmInstant { + val calendar = Calendar.getInstance() + calendar.add(Calendar.DAY_OF_MONTH, -1) + // Convert milliseconds to seconds for the epochSeconds parameter + val epochSeconds = calendar.timeInMillis / 1000 + // The nanosecondAdjustment parameter is 0 as we're not adjusting nanoseconds here + return RealmInstant.from(epochSeconds, 0) + } + + override fun getRealmInstant(days: Int): RealmInstant { + val calendar = Calendar.getInstance() + calendar.add(Calendar.DAY_OF_MONTH, days) + // Convert milliseconds to seconds for the epochSeconds parameter + val epochSeconds = calendar.timeInMillis / 1000 + // The nanosecondAdjustment parameter is 0 as we're not adjusting nanoseconds here + return RealmInstant.from(epochSeconds, 0) + } + + override fun getDateText(date: LocalDateTime): String? { + val now = LocalDateTime.now() + + if (date.toLocalDate().isEqual(now.toLocalDate())) { + val hour = ChronoUnit.HOURS.between(date, now) + val minutes = ChronoUnit.MINUTES.between(date, now) + val seconds = ChronoUnit.SECONDS.between(date, now) + + if (hour < 1 && minutes < 1 && seconds < 60) { + return "Just_now" + } + return "Today" + } + + val yesterday = now.minusDays(1) + if (date.toLocalDate().isEqual(yesterday.toLocalDate())) { + return "Yesterday" + } + + return null + } + + override fun getDateText( + date: LocalDateTime, + pattern: String, + locale: Locale, + ): String { + val formatter: DateTimeFormatter = + Memoize.memoize(pattern, locale) { + DateTimeFormatter.ofPattern(pattern, locale) + }() + return formatter.format(date) + } + + override fun getYYYYMMDD(date: LocalDateTime): String { + return dateFormatter.format(date) + } + + override fun convertRealmInstantToLocalDateTime(realmInstant: RealmInstant): LocalDateTime { + // 1. 从 RealmInstant 获取秒和纳秒 + val epochSeconds = realmInstant.epochSeconds + val nanosecondsOfSecond = realmInstant.nanosecondsOfSecond + + // 2. 使用 Instant.ofEpochSecond 创建 Instant + val instant = Instant.ofEpochSecond(epochSeconds, nanosecondsOfSecond.toLong()) + + // 3. 使用系统默认的时区将 Instant 转换为 LocalDateTime + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopDeviceUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DeviceUtils.desktop.kt similarity index 95% rename from composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopDeviceUtils.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/utils/DeviceUtils.desktop.kt index 84555e55c..1fae12d20 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopDeviceUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DeviceUtils.desktop.kt @@ -7,6 +7,10 @@ import java.io.IOException import java.io.InputStreamReader import java.util.UUID +actual fun getDeviceUtils(): DeviceUtils { + return DesktopDeviceUtils +} + object DesktopDeviceUtils : DeviceUtils { override fun createAppInstanceId(): String { diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/EncryptUtils.desktop.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/EncryptUtils.desktop.kt new file mode 100644 index 000000000..dbc66323c --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/EncryptUtils.desktop.kt @@ -0,0 +1,102 @@ +package com.clipevery.utils + +import java.io.ByteArrayOutputStream +import java.security.SecureRandom +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +actual fun getEncryptUtils(): EncryptUtils { + return DesktopEncryptUtils +} + +object DesktopEncryptUtils : EncryptUtils { + + override fun generateAESKey(): SecretKey { + val keyGen = KeyGenerator.getInstance("AES") + keyGen.init(256) + return keyGen.generateKey() + } + + override fun secretKeyToString(secretKey: SecretKey): String { + val encodedKey = secretKey.encoded + return base64Encode(encodedKey) + } + + override fun stringToSecretKey(encodedKey: String): SecretKey { + val decodedKey = base64Decode(encodedKey) + return SecretKeySpec(decodedKey, 0, decodedKey.size, "AES") + } + + override fun base64Encode(bytes: ByteArray): String { + return Base64.getEncoder().encodeToString(bytes) + } + + override fun base64Decode(string: String): ByteArray { + return Base64.getDecoder().decode(string) + } + + override fun base64mimeEncode(bytes: ByteArray): String { + return Base64.getMimeEncoder().encodeToString(bytes) + } + + override fun base64mimeDecode(string: String): ByteArray { + return Base64.getMimeDecoder().decode(string) + } + + override fun encryptData( + key: SecretKey, + data: ByteArray, + ): ByteArray { + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + val ivBytes = ByteArray(cipher.blockSize) + SecureRandom().nextBytes(ivBytes) + val ivSpec = IvParameterSpec(ivBytes) + + cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec) + val encrypted = cipher.doFinal(data) + return ivBytes + encrypted + } + + override fun decryptData( + key: SecretKey, + encryptedData: ByteArray, + ): ByteArray { + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + val ivBytes = encryptedData.copyOfRange(0, 16) + val actualEncryptedData = encryptedData.copyOfRange(16, encryptedData.size) + + val ivSpec = IvParameterSpec(ivBytes) + cipher.init(Cipher.DECRYPT_MODE, key, ivSpec) + + return cipher.doFinal(actualEncryptedData) + } + + override fun md5(bytes: ByteArray): String { + val md = java.security.MessageDigest.getInstance("MD5") + val digest = md.digest(bytes) + return digest.fold("") { str, it -> str + "%02x".format(it) } + } + + override fun md5ByArray(array: Array): String { + if (array.isEmpty()) { + throw IllegalArgumentException("Array is empty") + } + if (array.size == 1) { + return array[0] + } else { + val outputStream = ByteArrayOutputStream() + array.forEach { + outputStream.write(it.toByteArray()) + } + return md5(outputStream.toByteArray()) + } + } + + override fun md5ByString(string: String): String { + return md5(string.toByteArray()) + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopFileUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/FileUtils.desktop.kt similarity index 97% rename from composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopFileUtils.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/utils/FileUtils.desktop.kt index 9ad07a0e8..16808c3ae 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopFileUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/FileUtils.desktop.kt @@ -21,10 +21,16 @@ import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.pathString +actual fun getFileUtils(): FileUtils { + return DesktopFileUtils +} + object DesktopFileUtils : FileUtils { private val logger = KotlinLogging.logger {} + private val dateUtils = getDateUtils() + override val tempDirectory: Path = java.nio.file.Files.createTempDirectory("clipevery") private val units = arrayOf("B", "KB", "MB", "GB", "TB") @@ -64,7 +70,7 @@ object DesktopFileUtils : FileUtils { clipId: Long, fileName: String, ): String { - val dateYYYYMMDD = DateUtils.getYYYYMMDD(date) + val dateYYYYMMDD = dateUtils.getYYYYMMDD(date) return Paths.get(appInstanceId, dateYYYYMMDD, clipId.toString(), fileName).pathString } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopJsonUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/JsonUtils.desktop.kt similarity index 96% rename from composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopJsonUtils.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/utils/JsonUtils.desktop.kt index 9b215e9ae..8040b5fa5 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopJsonUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/JsonUtils.desktop.kt @@ -14,7 +14,6 @@ import com.clipevery.presist.SingleFileInfoTree import com.clipevery.serializer.Base64ByteArraySerializer import com.clipevery.serializer.IdentityKeySerializer import com.clipevery.serializer.PreKeyBundleSerializer -import com.clipevery.serializer.RealmInstantSerializer import com.clipevery.task.extra.BaseExtraInfo import com.clipevery.task.extra.PullExtraInfo import com.clipevery.task.extra.SyncExtraInfo @@ -29,6 +28,10 @@ import kotlinx.serialization.modules.subclass import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.state.PreKeyBundle +actual fun getJsonUtils(): JsonUtils { + return DesktopJsonUtils +} + object DesktopJsonUtils : JsonUtils { override val JSON: Json = @@ -45,7 +48,6 @@ object DesktopJsonUtils : JsonUtils { // use in clip data serializersModuleOf(MutableRealmIntKSerializer) serializersModuleOf(RealmAnyKSerializer) - serializersModuleOf(RealmInstantSerializer) polymorphic(RealmObject::class) { subclass(FilesClipItem::class) subclass(HtmlClipItem::class) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopNetUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/NetUtils.desktop.kt similarity index 96% rename from composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopNetUtils.kt rename to composeApp/src/desktopMain/kotlin/com/clipevery/utils/NetUtils.desktop.kt index 8beee2b08..83704c2aa 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/DesktopNetUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/NetUtils.desktop.kt @@ -5,6 +5,10 @@ import java.net.InetAddress import java.net.NetworkInterface import java.util.Collections +actual fun getNetUtils(): NetUtils { + return DesktopNetUtils +} + object DesktopNetUtils : NetUtils { override fun getHostInfoList(): List { val nets = NetworkInterface.getNetworkInterfaces() diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt index 48c69b9d2..24bdee199 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/QRCodeUtils.kt @@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.toComposeImageBitmap import com.clipevery.app.AppInfo import com.clipevery.dto.sync.SyncInfo import com.clipevery.endpoint.EndpointInfoFactory -import com.clipevery.utils.EncryptUtils.base64Encode import com.google.zxing.BarcodeFormat import com.google.zxing.qrcode.QRCodeWriter import kotlinx.serialization.encodeToString @@ -20,6 +19,8 @@ class DesktopQRCodeGenerator( private val endpointInfoFactory: EndpointInfoFactory, ) : QRCodeGenerator { + private val encryptUtils = getEncryptUtils() + private fun buildQRCode(token: CharArray): String { val endpointInfo = endpointInfoFactory.createEndpointInfo() val syncInfo = SyncInfo(appInfo, endpointInfo) @@ -63,7 +64,7 @@ class DesktopQRCodeGenerator( val saltDataStream = DataOutputStream(saltByteStream) saltDataStream.write(byteArrayRotate) saltDataStream.writeInt(token) - return base64Encode(saltByteStream.toByteArray()) + return encryptUtils.base64Encode(saltByteStream.toByteArray()) } private fun ByteArray.rotate(offset: Int): ByteArray { diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/ResourceUtils.desktop.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/ResourceUtils.desktop.kt new file mode 100644 index 000000000..c96aae31e --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/ResourceUtils.desktop.kt @@ -0,0 +1,23 @@ +package com.clipevery.utils + +import java.util.Properties + +actual fun getResourceUtils(): ResourceUtils { + return DesktopResourceUtils +} + +object DesktopResourceUtils : ResourceUtils { + + override fun loadProperties(fileName: String): Properties { + val properties = Properties() + val classLoader = Thread.currentThread().contextClassLoader + classLoader.getResourceAsStream(fileName).use { inputStream -> + if (inputStream == null) { + throw IllegalArgumentException("File not found: $fileName") + } else { + properties.load(inputStream) + } + } + return properties + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TxtRecordUtils.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TxtRecordUtils.kt index 2f6bae0a8..3b6d5bcb9 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TxtRecordUtils.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/utils/TxtRecordUtils.kt @@ -14,7 +14,7 @@ object TxtRecordUtils { val jsonString = DesktopJsonUtils.JSON.encodeToString(obj) // Convert the JSON string to a base64 encoded string - val base64Encoded = EncryptUtils.base64Encode(jsonString.toByteArray(Charsets.UTF_8)) + val base64Encoded = DesktopEncryptUtils.base64Encode(jsonString.toByteArray(Charsets.UTF_8)) // Prepare a dictionary to store the split data val txtRecordDict = mutableMapOf() @@ -38,7 +38,7 @@ object TxtRecordUtils { } // Decode the base64 string into a JSON string - val jsonString = String(EncryptUtils.base64Decode(base64Encoded), Charsets.UTF_8) + val jsonString = String(DesktopEncryptUtils.base64Decode(base64Encoded), Charsets.UTF_8) // Deserialize the object from the JSON string return DesktopJsonUtils.JSON.decodeFromString(jsonString) diff --git a/composeApp/src/desktopTest/kotlin/com/clipevery/serializer/SerializerTest.kt b/composeApp/src/desktopTest/kotlin/com/clipevery/serializer/SerializerTest.kt index d7f78f216..19599010f 100644 --- a/composeApp/src/desktopTest/kotlin/com/clipevery/serializer/SerializerTest.kt +++ b/composeApp/src/desktopTest/kotlin/com/clipevery/serializer/SerializerTest.kt @@ -7,7 +7,7 @@ import com.clipevery.dao.clip.ClipData import com.clipevery.dao.clip.ClipState import com.clipevery.dao.clip.ClipType import com.clipevery.utils.DesktopJsonUtils -import com.clipevery.utils.EncryptUtils.md5ByString +import com.clipevery.utils.getEncryptUtils import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmInstant @@ -22,11 +22,12 @@ class SerializerTest { @Test fun testClipData() { + val encryptUtils = getEncryptUtils() val textClipItem = TextClipItem().apply { this.identifier = TextItemService.TEXT this.text = "testClipData" - this.md5 = md5ByString(this.text) + this.md5 = encryptUtils.md5ByString(this.text) } val clipData =