From 4d0cf0382f5285517559dc0bf88eada5efb04fd7 Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Sun, 25 Feb 2024 22:32:07 +0800 Subject: [PATCH] :hammer: Refactor the pasteboard reading logic (#385) --- .../kotlin/com/clipevery/clip/ClipPlugin.kt | 0 .../kotlin/com/clipevery/dao/clip/ClipDao.kt | 8 +- .../com/clipevery/clip/ClipCollector.kt | 83 ++++++++++++------- .../com/clipevery/clip/ClipItemService.kt | 26 ++++-- .../clip/DesktopTransferableConsumer.kt | 67 +++++++++++---- .../clipevery/clip/service/FileItemService.kt | 33 ++++++-- .../clipevery/clip/service/HtmlItemService.kt | 44 ++++++---- .../clip/service/ImageItemService.kt | 35 +++++--- .../clipevery/clip/service/TextItemService.kt | 47 +++++------ .../clipevery/clip/service/UrlItemService.kt | 32 +++++-- .../com/clipevery/dao/clip/ClipRealm.kt | 69 ++++++++++++--- .../os/macos/MacosClipboardService.kt | 6 +- .../os/windows/WindowsClipboardService.kt | 4 +- 13 files changed, 322 insertions(+), 132 deletions(-) rename composeApp/src/{desktopMain => commonMain}/kotlin/com/clipevery/clip/ClipPlugin.kt (100%) diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipPlugin.kt b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipPlugin.kt similarity index 100% rename from composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipPlugin.kt rename to composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipPlugin.kt diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/dao/clip/ClipDao.kt b/composeApp/src/commonMain/kotlin/com/clipevery/dao/clip/ClipDao.kt index 4d6a700e0..0c0a8ad6f 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/dao/clip/ClipDao.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/dao/clip/ClipDao.kt @@ -1,5 +1,7 @@ package com.clipevery.dao.clip +import com.clipevery.clip.ClipPlugin +import io.realm.kotlin.MutableRealm import io.realm.kotlin.query.RealmResults import io.realm.kotlin.types.RealmInstant import org.mongodb.kbson.ObjectId @@ -8,13 +10,17 @@ interface ClipDao { fun getMaxClipId(): Int - fun createClipData(clipData: ClipData) + fun createClipData(clipData: ClipData): ObjectId fun deleteClipData(id: ObjectId) fun getClipData(appInstanceId: String? = null, limit: Int): RealmResults + fun releaseClipData(id: ObjectId, clipPlugins: List) + + fun updateClipItem(update: (MutableRealm) -> Unit) + fun getClipDataGreaterThan(appInstanceId: String? = null, createTime: RealmInstant): RealmResults diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipCollector.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipCollector.kt index 0a4081cc1..5436355cd 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipCollector.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipCollector.kt @@ -5,10 +5,14 @@ import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.dao.clip.ClipContent import com.clipevery.dao.clip.ClipDao import com.clipevery.dao.clip.ClipData +import com.clipevery.dao.clip.ClipType import io.github.oshai.kotlinlogging.KotlinLogging +import io.realm.kotlin.MutableRealm import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject +import org.mongodb.kbson.ObjectId import kotlin.reflect.KClass class ClipCollector( @@ -17,55 +21,74 @@ class ClipCollector( private val clipDao: ClipDao, private val clipPlugins: List) { - val logger = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} - private val collectors: Array, ClipAppearItem>> = Array(itemCount) { mutableMapOf() } + private val preCollectors: Array, ClipAppearItem>> = Array(itemCount) { mutableMapOf() } - fun needCollectionItem(itemIndex: Int, kclass: KClass): Boolean { - return !collectors[itemIndex].contains(kclass) + private val updateCollectors: Array>> = Array(itemCount) { mutableSetOf() } + + private var existError = false + + fun needPreCollectionItem(itemIndex: Int, kclass: KClass): Boolean { + return !preCollectors[itemIndex].contains(kclass) } - fun collectItem(itemIndex: Int, kclass: KClass, clipItem: ClipAppearItem) { - collectors[itemIndex][kclass] = clipItem + fun needUpdateCollectItem(itemIndex: Int, kclass: KClass): Boolean { + return !updateCollectors[itemIndex].contains(kclass) } - fun collectError(clipId: Int, itemIndex: Int, error: Exception) { - logger.error(error) { "Failed to collect item $itemIndex of clip $clipId" } + fun preCollectItem(itemIndex: Int, kclass: KClass, clipItem: ClipAppearItem) { + preCollectors[itemIndex][kclass] = clipItem } - fun completeCollect(clipId: Int) { - // if there are no collectors, return - if (collectors.isEmpty()) { - return + fun updateCollectItem(itemIndex: Int, kclass: KClass, update: (ClipAppearItem, MutableRealm) -> Unit) { + preCollectors[itemIndex][kclass]?.let{ + val updateClipItem: (MutableRealm) -> Unit = { realm -> + update(it, realm) + } + clipDao.updateClipItem(updateClipItem) } + } - var clipAppearItems: List = collectors.flatMap { it.values } + fun collectError(clipId: Int, itemIndex: Int, error: Exception) { + logger.error(error) { "Failed to collect item $itemIndex of clip $clipId" } + existError = true + } - for (clipPlugin in clipPlugins) { - clipAppearItems = clipPlugin.pluginProcess(clipAppearItems) + fun createPreClipData(clipId: Int): ObjectId? { + val collector = preCollectors.filter { it.isNotEmpty() } + if (collector.isEmpty()) { + return null } + val clipAppearItems: List = preCollectors.flatMap { it.values } - assert(clipAppearItems.isNotEmpty()) - - val firstItem: ClipAppearItem = clipAppearItems.first() - - val remainingItems: List = clipAppearItems.drop(1) - - val clipAppearContent: RealmAny = RealmAny.create(firstItem as RealmObject) - - val clipContent = ClipContent(remainingItems.map { RealmAny.create(it as RealmObject) }.toRealmList()) + val clipContent = ClipContent(clipAppearItems.map { RealmAny.create(it as RealmObject) }.toRealmList()) val clipData = ClipData().apply { this.clipId = clipId - this.clipAppearContent = clipAppearContent this.clipContent = clipContent - this.clipType = firstItem.getClipType() - this.clipSearchContent = firstItem.getSearchContent() - this.md5 = firstItem.md5 + this.clipType = ClipType.INVALID + this.md5 = "" this.appInstanceId = appInfo.appInstanceId - this.preCreate = false + this.createTime = RealmInstant.now() + this.preCreate = true } + return clipDao.createClipData(clipData) + } - clipDao.createClipData(clipData) + fun completeCollect(id: ObjectId) { + if (existError || preCollectors.isEmpty()) { + try { + clipDao.deleteClipData(id) + } catch (e: Exception) { + logger.error(e) { "Failed to delete clip $id" } + } + } else { + try { + clipDao.releaseClipData(id, clipPlugins) + } catch (e: Exception) { + logger.error(e) { "Failed to release clip $id" } + } + } } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipItemService.kt index 9019ce967..d88eeac82 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/ClipItemService.kt @@ -7,7 +7,15 @@ interface ClipItemService { fun getIdentifiers(): List - fun createClipItem( + fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) + + fun loadRepresentation( clipId: Int, itemIndex: Int, dataFlavor: DataFlavor, @@ -17,19 +25,19 @@ interface ClipItemService { ) { try { val transferData = transferable.getTransferData(dataFlavor) - doCreateClipItem(transferData, clipId, itemIndex, dataFlavor, dataFlavorMap, transferable, clipCollector) + doLoadRepresentation(transferData, clipId, itemIndex, dataFlavor, dataFlavorMap, transferable, clipCollector) } catch (e: Exception) { collectError(e, clipId, itemIndex, clipCollector) } } - fun doCreateClipItem(transferData: Any, - clipId: Int, - itemIndex: Int, - dataFlavor: DataFlavor, - dataFlavorMap: Map>, - transferable: Transferable, - clipCollector: ClipCollector) + fun doLoadRepresentation(transferData: Any, + clipId: Int, + itemIndex: Int, + dataFlavor: DataFlavor, + dataFlavorMap: Map>, + transferable: Transferable, + clipCollector: ClipCollector) fun collectError(error: Exception, clipId: Int, diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/DesktopTransferableConsumer.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/DesktopTransferableConsumer.kt index e654d8b02..36cf59669 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/DesktopTransferableConsumer.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/DesktopTransferableConsumer.kt @@ -40,23 +40,62 @@ open class DesktopTransferableConsumer(private val appInfo: AppInfo, val clipCollector = ClipCollector(dataFlavorMap.size, appInfo, clipDao, clipPlugins) try { - var itemIndex = 0 - for (entry in dataFlavorMap) { - val identifier = entry.key - val flavors = entry.value - logger.info { "itemIndex: $itemIndex Transferable flavor: $identifier" } - for (flavor in flavors) { - clipItemServiceMap[identifier]?.let { clipItemService -> - if (clipCollector.needCollectionItem(itemIndex, clipItemService::class)) { - clipItemService.createClipItem(clipId, itemIndex, flavor, dataFlavorMap, transferable, clipCollector) - } - } - } - itemIndex ++ + preCollect(clipId, dataFlavorMap, transferable, clipCollector) + clipCollector.createPreClipData(clipId)?.let { + updateClipData(clipId, dataFlavorMap, transferable, clipCollector) + clipCollector.completeCollect(it) } - clipCollector.completeCollect(clipId) } catch (e: Exception) { logger.error(e) { "Failed to consume transferable" } } } + + private fun preCollect(clipId: Int, + dataFlavorMap: Map>, + transferable: Transferable, + clipCollector: ClipCollector) { + var itemIndex = 0 + for (entry in dataFlavorMap) { + val identifier = entry.key + val flavors = entry.value + logger.info { "itemIndex: $itemIndex Transferable flavor: $identifier" } + for (flavor in flavors) { + if (clipItemServiceMap[identifier]?.let { clipItemService -> + if (clipCollector.needPreCollectionItem(itemIndex, clipItemService::class)) { + clipItemService.createPreClipItem(clipId, itemIndex, identifier, transferable, clipCollector) + false + } else { + true + } + } == true) { + break + } + } + itemIndex ++ + } + } + + private fun updateClipData(clipId: Int, + dataFlavorMap: Map>, + transferable: Transferable, + clipCollector: ClipCollector) { + var itemIndex = 0 + for (entry in dataFlavorMap) { + val identifier = entry.key + val flavors = entry.value + for (flavor in flavors) { + if (clipItemServiceMap[identifier]?.let { clipItemService -> + if (clipCollector.needUpdateCollectItem(itemIndex, clipItemService::class)) { + clipItemService.loadRepresentation(clipId, itemIndex, flavor, dataFlavorMap, transferable, clipCollector) + false + } else { + true + } + } == true) { + break + } + } + itemIndex ++ + } + } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FileItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FileItemService.kt index f0bfad021..944c5f721 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FileItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/FileItemService.kt @@ -8,6 +8,7 @@ import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.DesktopFileUtils import com.clipevery.utils.DesktopFileUtils.copyFile import com.clipevery.utils.DesktopFileUtils.createClipRelativePath +import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable import java.io.File @@ -23,7 +24,21 @@ class FileItemService: ClipItemService { return listOf(FILE_LIST_ID) } - override fun doCreateClipItem( + override fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) { + FileClipItem().apply { + this.identifier = identifier + }.let { + clipCollector.preCollectItem(itemIndex, this::class, it) + } + } + + override fun doLoadRepresentation( transferData: Any, clipId: Int, itemIndex: Int, @@ -32,21 +47,25 @@ class FileItemService: ClipItemService { transferable: Transferable, clipCollector: ClipCollector ) { + + if (transferData is List<*>) { val files = transferData.filterIsInstance() if (files.size == 1) { - var clipItem: ClipAppearItem? = null val fileName = files[0].name val relativePath = createClipRelativePath(clipId, fileName) val filePath = DesktopFileUtils.createClipPath(relativePath, isFile = true, AppFileType.FILE) if (copyFile(files[0].toPath(), filePath)) { - clipItem = FileClipItem().apply { - this.identifier = dataFlavor.humanPresentableName - this.relativePath = relativePath - this.md5 = DesktopFileUtils.getFileMd5(filePath) + val md5 = DesktopFileUtils.getFileMd5(filePath) + + val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> + realm.query(FileClipItem::class).query("id == $0", clipItem.id).first().find()?.apply { + this.relativePath = relativePath + this.md5 = md5 + } } + clipCollector.updateCollectItem(itemIndex, this::class, update) } - clipItem?.let { clipCollector.collectItem(itemIndex, this::class, it) } } else if (files.size > 1) { // todo multi files } 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 5b449acb2..4226ddbbd 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/HtmlItemService.kt @@ -5,6 +5,7 @@ import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.HtmlClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.md5ByString +import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable @@ -19,23 +20,36 @@ class HtmlItemService: ClipItemService { return listOf(HTML_ID) } - override fun doCreateClipItem(transferData: Any, - clipId: Int, - itemIndex: Int, - dataFlavor: DataFlavor, - dataFlavorMap: Map>, - transferable: Transferable, - clipCollector: ClipCollector) { - var clipItem: ClipAppearItem? = null + override fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) { + HtmlClipItem().apply { + this.identifier = identifier + }.let { + clipCollector.preCollectItem(itemIndex, this::class, it) + } + } + + override fun doLoadRepresentation(transferData: Any, + clipId: Int, + itemIndex: Int, + dataFlavor: DataFlavor, + dataFlavorMap: Map>, + transferable: Transferable, + clipCollector: ClipCollector) { if (transferData is String) { - clipItem = HtmlClipItem().apply { - identifier = dataFlavor.humanPresentableName - html = transferData - md5 = md5ByString(html) + val md5 = md5ByString(transferData) + val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> + realm.query(HtmlClipItem::class).query("id == $0", clipItem.id).first().find()?.apply { + this.html = transferData + this.md5 = md5 + } } + clipCollector.updateCollectItem(itemIndex, this::class, update) } - clipItem?.let { clipCollector.collectItem(itemIndex, this::class, it) } } - - } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/ImageItemService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/ImageItemService.kt index 3cffd97d4..647c18304 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/ImageItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/ImageItemService.kt @@ -11,6 +11,7 @@ import com.clipevery.utils.DesktopFileUtils.createClipRelativePath import com.clipevery.utils.DesktopFileUtils.createRandomFileName import com.clipevery.utils.DesktopFileUtils.getExtFromFileName import com.clipevery.utils.DesktopFileUtils.getFileMd5 +import io.realm.kotlin.MutableRealm import org.jsoup.Jsoup import java.awt.Image import java.awt.datatransfer.DataFlavor @@ -32,7 +33,21 @@ class ImageItemService: ClipItemService { return listOf(IMAGE_ID) } - override fun doCreateClipItem( + override fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) { + ImageClipItem().apply { + this.identifier = identifier + }.let { + clipCollector.preCollectItem(itemIndex, this::class, it) + } + } + + override fun doLoadRepresentation( transferData: Any, clipId: Int, itemIndex: Int, @@ -41,7 +56,6 @@ class ImageItemService: ClipItemService { transferable: Transferable, clipCollector: ClipCollector ) { - var clipItem: ClipAppearItem? = null if (transferData is Image) { val image: BufferedImage = toBufferedImage(transferData) var name = tryGetImageName(dataFlavorMap, transferable) ?: createRandomFileName(ext = "png") @@ -52,15 +66,16 @@ class ImageItemService: ClipItemService { val relativePath = createClipRelativePath(clipId, name) val imagePath = createClipPath(relativePath, isFile = true, AppFileType.IMAGE) if (writeImage(image, ext, imagePath)) { - clipItem = ImageClipItem().apply { - this.identifier = dataFlavor.humanPresentableName - this.relativePath = relativePath - this.md5 = getFileMd5(imagePath) + val md5 = getFileMd5(imagePath) + val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> + realm.query(ImageClipItem::class).query("id == $0", clipItem.id).first().find()?.apply { + this.relativePath = relativePath + this.md5 = md5 + } } + clipCollector.updateCollectItem(itemIndex, this::class, update) } } - clipItem?.let { clipCollector.collectItem(itemIndex, this::class, it) } - } private fun writeImage(image: BufferedImage, ext: String, imagePath: Path): Boolean { @@ -102,7 +117,7 @@ class ImageItemService: ClipItemService { return getLastPathSegment(src) } - fun getLastPathSegment(urlString: String): String? { + private fun getLastPathSegment(urlString: String): String? { try { val url = URL(urlString) var path: String = url.getPath() @@ -116,7 +131,7 @@ class ImageItemService: ClipItemService { } } - fun toBufferedImage(img: Image): BufferedImage { + private fun toBufferedImage(img: Image): BufferedImage { if (img is BufferedImage) { return img } 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 e7494d90e..a11eaa7f9 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/TextItemService.kt @@ -3,12 +3,11 @@ package com.clipevery.clip.service import com.clipevery.clip.ClipCollector import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.TextClipItem -import com.clipevery.clip.item.UrlClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.md5ByString +import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable -import java.net.URL class TextItemService: ClipItemService { @@ -23,7 +22,21 @@ class TextItemService: ClipItemService { return listOf(UNICODE_STRING, TEXT, PLAIN_TEXT) } - override fun doCreateClipItem( + override fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) { + TextClipItem().apply { + this.identifier = identifier + }.let { + clipCollector.preCollectItem(itemIndex, this::class, it) + } + } + + override fun doLoadRepresentation( transferData: Any, clipId: Int, itemIndex: Int, @@ -32,31 +45,15 @@ class TextItemService: ClipItemService { transferable: Transferable, clipCollector: ClipCollector ) { - var clipItem: ClipAppearItem? = null if (transferData is String) { - getURL(transferData)?.let { - clipItem = UrlClipItem().apply { - identifier = dataFlavor.humanPresentableName - url = transferData - md5 = md5ByString(url) - } - } ?: run { - clipItem = TextClipItem().apply { - identifier = dataFlavor.humanPresentableName - text = transferData - md5 = md5ByString(text) + val md5 = md5ByString(transferData) + val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> + realm.query(TextClipItem::class).query("id == $0", clipItem.id).first().find()?.apply { + this.text = transferData + this.md5 = md5 } } - } - clipItem?.let { clipCollector.collectItem(itemIndex, this::class, it) } - } - - private fun getURL(str: String): String? { - return try { - URL(str) - str - } catch (e: Exception) { - null + clipCollector.updateCollectItem(itemIndex, this::class, update) } } } \ No newline at end of file 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 46650209f..3a2392239 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/clip/service/UrlItemService.kt @@ -5,6 +5,7 @@ import com.clipevery.clip.ClipItemService import com.clipevery.clip.item.UrlClipItem import com.clipevery.dao.clip.ClipAppearItem import com.clipevery.utils.md5ByString +import io.realm.kotlin.MutableRealm import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.Transferable @@ -18,7 +19,21 @@ class UrlItemService: ClipItemService { return listOf(URL) } - override fun doCreateClipItem( + override fun createPreClipItem( + clipId: Int, + itemIndex: Int, + identifier: String, + transferable: Transferable, + clipCollector: ClipCollector + ) { + UrlClipItem().apply { + this.identifier = identifier + }.let { + clipCollector.preCollectItem(itemIndex, this::class, it) + } + } + + override fun doLoadRepresentation( transferData: Any, clipId: Int, itemIndex: Int, @@ -27,16 +42,15 @@ class UrlItemService: ClipItemService { transferable: Transferable, clipCollector: ClipCollector ) { - var clipItem: ClipAppearItem? = null if (transferData is String) { - clipItem = UrlClipItem().apply { - identifier = dataFlavor.humanPresentableName - url = transferData - md5 = md5ByString(url) + val md5 = md5ByString(transferData) + val update: (ClipAppearItem, MutableRealm) -> Unit = { clipItem, realm -> + realm.query(UrlClipItem::class).query("id == $0", clipItem.id).first().find()?.apply { + this.url = transferData + this.md5 = md5 + } } - } - clipItem?.let { - clipCollector.collectItem(itemIndex, this::class, it) + clipCollector.updateCollectItem(itemIndex, this::class, update) } } } \ No newline at end of file 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 623779cc9..78227390e 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/dao/clip/ClipRealm.kt @@ -1,16 +1,18 @@ package com.clipevery.dao.clip +import com.clipevery.clip.ClipPlugin import com.clipevery.utils.DateUtils import io.github.oshai.kotlinlogging.KotlinLogging import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.Sort +import io.realm.kotlin.types.RealmAny import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject import org.mongodb.kbson.ObjectId -class ClipRealm(private val realm: Realm): ClipDao { +class ClipRealm(private val realm: Realm) : ClipDao { private val logger = KotlinLogging.logger {} @@ -18,17 +20,11 @@ class ClipRealm(private val realm: Realm): ClipDao { return realm.query(ClipData::class).sort("clipId", Sort.DESCENDING).first().find()?.clipId ?: 0 } - override fun createClipData(clipData: ClipData) { + override fun createClipData(clipData: ClipData): ObjectId { realm.writeBlocking { copyToRealm(clipData) } - doDeleteClipData { - query(ClipData::class, "md5 == $0", clipData.md5) - .query("createTime > $0", DateUtils.getPrevDay()) - .query("clipId != $0", clipData.clipId) - .query("appInstanceId != $0", clipData.appInstanceId) - .find().toList() - } + return clipData.id } override fun deleteClipData(id: ObjectId) { @@ -47,7 +43,7 @@ class ClipRealm(private val realm: Realm): ClipDao { } val clipAppearContent = clipData.clipAppearContent val clipAppearItem = ClipContent.getClipItem(clipAppearContent) - val clipAppearItems = clipData.clipContent?.clipAppearItems?.mapNotNull{ anyValue -> + val clipAppearItems = clipData.clipContent?.clipAppearItems?.mapNotNull { anyValue -> ClipContent.getClipItem(anyValue) } delete(clipData) @@ -72,6 +68,59 @@ class ClipRealm(private val realm: Realm): ClipDao { return query.sort("createTime", Sort.DESCENDING).limit(limit).find() } + override fun releaseClipData(id: ObjectId, clipPlugins: List) { + var md5: String? = null + realm.writeBlocking { + query(ClipData::class).query("id == $0", id).first().find()?.let { clipData -> + clipData.clipContent?.let { clipContent -> + var clipAppearItems = clipContent.clipAppearItems.mapNotNull { anyValue -> + ClipContent.getClipItem(anyValue) + } + + assert(clipAppearItems.isNotEmpty()) + + for (clipPlugin in clipPlugins) { + clipAppearItems = clipPlugin.pluginProcess(clipAppearItems) + } + + val firstItem: ClipAppearItem = clipAppearItems.first() + + md5 = firstItem.md5 + + val remainingItems: List = clipAppearItems.drop(1) + + val clipAppearContent: RealmAny = RealmAny.create(firstItem as RealmObject) + + clipContent.clipAppearItems.clear() + + clipContent.clipAppearItems.addAll(remainingItems.map { RealmAny.create(it as RealmObject) }) + + clipData.clipAppearContent = clipAppearContent + clipData.clipContent = clipContent + clipData.clipType = firstItem.getClipType() + clipData.clipSearchContent = firstItem.getSearchContent() + clipData.md5 = firstItem.md5 + clipData.preCreate = false + } + } + } + + md5?.let { + doDeleteClipData { + query(ClipData::class, "md5 == $0", it) + .query("createTime > $0", DateUtils.getPrevDay()) + .query("id != $0", id) + .find().toList() + } + } + } + + override fun updateClipItem(update: (MutableRealm) -> Unit) { + realm.writeBlocking { + update(this) + } + } + override fun getClipDataGreaterThan( appInstanceId: String?, createTime: RealmInstant diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt index 951df630c..84ecb7b2f 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt @@ -4,6 +4,7 @@ import com.clipevery.clip.ClipboardService import com.clipevery.clip.TransferableConsumer import com.clipevery.os.macos.api.MacosApi import com.clipevery.utils.cpuDispatcher +import com.clipevery.utils.ioDispatcher import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -11,6 +12,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import java.awt.Toolkit import java.awt.datatransfer.Clipboard @@ -33,7 +35,9 @@ class MacosClipboardService(override val clipConsumer: TransferableConsumer): Cl changeCount = currentChangeCount val contents = systemClipboard.getContents(null) contents?.let { - clipConsumer.consume(it) + withContext(ioDispatcher) { + clipConsumer.consume(it) + } } } } diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt index c620b3b66..71b318d0d 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt @@ -85,7 +85,9 @@ class WindowsClipboardService private fun onChange() { val contents: Transferable? = systemClipboard.getContents(null) contents?.let { - clipConsumer.consume(it) + CoroutineScope(ioDispatcher).launch { + clipConsumer.consume(it) + } } }