Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package com.crosspaste.paste
import com.crosspaste.Database
import com.crosspaste.config.CommonConfigManager
import com.crosspaste.db.paste.PasteDao
import com.crosspaste.db.sync.SyncRuntimeInfoDao
import com.crosspaste.notification.MessageType
import com.crosspaste.notification.NotificationManager
import com.crosspaste.paste.item.PasteFiles
import com.crosspaste.paste.item.PasteItem
import com.crosspaste.paste.item.PasteItemProperties
import com.crosspaste.paste.item.PasteItemReader
import com.crosspaste.paste.item.bindItem
import com.crosspaste.paste.plugin.process.DiscardOversizedNonFilePlugin
import com.crosspaste.paste.plugin.process.PasteProcessPlugin
import com.crosspaste.path.UserDataPathProvider
import com.crosspaste.presist.FilesIndex
Expand Down Expand Up @@ -36,10 +40,12 @@ class PasteReleaseService(
private val commonConfigManager: CommonConfigManager,
private val currentPaste: CurrentPaste,
private val database: Database,
private val notificationManager: NotificationManager,
private val pasteDao: PasteDao,
private val pasteItemReader: PasteItemReader,
private val pasteProcessPlugins: List<PasteProcessPlugin>,
private val searchContentService: SearchContentService,
private val syncRuntimeInfoDao: SyncRuntimeInfoDao,
private val taskSubmitter: TaskSubmitter,
private val userDataPathProvider: UserDataPathProvider,
) {
Expand Down Expand Up @@ -194,11 +200,47 @@ class PasteReleaseService(
return newPasteData
}

/**
* Remote pastes bypass the plugin pipeline (they are persisted as-is), so the
* non-file size limit is enforced here. The whole paste is skipped instead of
* degrading flavors: the remote PasteData's hash/pasteType/searchContent are
* already assembled, and dropping individual items would break the
* hash-content consistency that markDeleteSameHash dedup relies on. The
* sender keeps the full data, so nothing is lost.
*/
private suspend fun discardOversizedRemoteNonFilePaste(pasteData: PasteData): Boolean {
val maxSize = fileUtils.bytesSize(commonConfigManager.getCurrentConfig().maxNonFilePasteSize)
val oversized =
pasteData.getPasteAppearItems().any {
DiscardOversizedNonFilePlugin.isNonFilePasteType(it.getPasteType()) && it.size > maxSize
}
if (!oversized) {
return false
}
logger.info {
"Discard oversized remote non-file paste from ${pasteData.appInstanceId}: " +
"size=${pasteData.size}, limit=$maxSize"
}
val deviceName =
syncRuntimeInfoDao
.getSyncRuntimeInfo(pasteData.appInstanceId)
?.getDeviceDisplayName()
?: pasteData.appInstanceId
notificationManager.sendNotification(
title = { it.getText("remote_non_file_paste_discarded_too_large", deviceName) },
messageType = MessageType.Warning,
)
return true
}

suspend fun releaseRemotePasteData(
pasteData: PasteData,
tryWritePasteboard: (PasteData) -> Unit,
): Result<Unit> {
return runCatching {
if (discardOversizedRemoteNonFilePaste(pasteData)) {
return@runCatching
}
val remotePasteDataId = pasteData.id
val isFileType = pasteData.isFileType()
val existIconFile: Boolean? =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.crosspaste.paste.plugin.process

import com.crosspaste.config.CommonConfigManager
import com.crosspaste.notification.MessageType
import com.crosspaste.notification.NotificationManager
import com.crosspaste.paste.PasteType
import com.crosspaste.paste.item.PasteCoordinate
import com.crosspaste.paste.item.PasteItem
import com.crosspaste.utils.getFileUtils
import io.github.oshai.kotlinlogging.KotlinLogging

/**
* Discards non-file paste items larger than [com.crosspaste.config.AppConfig.maxNonFilePasteSize].
*
* Only non-file carrier types (text/html/rtf/url/color) are subject to the limit;
* file and image pastes store their payload on disk and are governed by
* maxSyncFileSize instead. Oversized items are silently dropped (flavor
* degradation); a notification is pushed only when every item is discarded —
* the resulting empty list makes PasteReleaseService mark-delete the paste row.
*/
class DiscardOversizedNonFilePlugin(
private val configManager: CommonConfigManager,
private val notificationManager: NotificationManager,
) : PasteProcessPlugin {

companion object {
private val NON_FILE_PASTE_TYPES =
setOf(
PasteType.TEXT_TYPE,
PasteType.HTML_TYPE,
PasteType.RTF_TYPE,
PasteType.URL_TYPE,
PasteType.COLOR_TYPE,
)

private val fileUtils = getFileUtils()

fun isNonFilePasteType(pasteType: PasteType): Boolean = pasteType in NON_FILE_PASTE_TYPES
}

private val logger = KotlinLogging.logger {}

override fun process(
pasteCoordinate: PasteCoordinate,
pasteItems: List<PasteItem>,
source: String?,
): List<PasteItem> {
val maxSize = fileUtils.bytesSize(configManager.getCurrentConfig().maxNonFilePasteSize)
val retainedItems =
pasteItems.filter { pasteItem ->
val oversized =
isNonFilePasteType(pasteItem.getPasteType()) && pasteItem.size > maxSize
if (oversized) {
logger.info {
"Discard oversized ${pasteItem.getPasteType().name} paste item: " +
"size=${pasteItem.size}, limit=$maxSize, id=${pasteCoordinate.id}"
}
}
!oversized
}
if (retainedItems.isEmpty() && pasteItems.isNotEmpty()) {
notificationManager.sendNotification(
title = { it.getText("non_file_paste_discarded_too_large") },
messageType = MessageType.Warning,
)
}
return retainedItems
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.composables.icons.materialsymbols.rounded.Content_paste
import com.composables.icons.materialsymbols.rounded.Music_note
import com.composables.icons.materialsymbols.rounded.Skip_next
import com.composables.icons.materialsymbols.rounded.Stacks
import com.composables.icons.materialsymbols.rounded.Title
import com.crosspaste.config.CommonConfigManager
import com.crosspaste.paste.PasteboardService
import com.crosspaste.ui.LocalThemeExtState
Expand Down Expand Up @@ -90,6 +91,19 @@ fun PasteboardSettingsContentView(extContent: @Composable () -> Unit = {}) {
}
},
)
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
SettingListItem(
title = "max_non_file_paste_size",
subtitle = "max_non_file_paste_size_desc",
icon = IconData(MaterialSymbols.Rounded.Title, themeExt.cyanIconColor),
trailingContent = {
Counter(defaultValue = config.maxNonFilePasteSize, unit = "MB", rule = {
it > 0
}) { currentMaxNonFilePasteSize ->
configManager.updateConfig("maxNonFilePasteSize", currentMaxNonFilePasteSize)
}
},
)
extContent()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.crosspaste.paste.getDesktopPasteboardService
import com.crosspaste.paste.item.DefaultPasteItemReader
import com.crosspaste.paste.item.PasteItemReader
import com.crosspaste.paste.item.UpdatePasteItemHelper
import com.crosspaste.paste.plugin.process.DiscardOversizedNonFilePlugin
import com.crosspaste.paste.plugin.process.DistinctPlugin
import com.crosspaste.paste.plugin.process.FileToUrlPlugin
import com.crosspaste.paste.plugin.process.FilesToImagesPlugin
Expand Down Expand Up @@ -165,11 +166,13 @@ fun desktopPasteComponentModule(headless: Boolean): Module =
commonConfigManager = get(),
currentPaste = get(),
database = get(),
notificationManager = get(),
pasteDao = get(),
pasteItemReader = get(),
pasteProcessPlugins =
listOf(
RemoveInvalidPlugin,
DiscardOversizedNonFilePlugin(get(), get()),
DistinctPlugin(get()),
GenerateTextPlugin(get()),
GenerateUrlPlugin,
Expand All @@ -181,6 +184,7 @@ fun desktopPasteComponentModule(headless: Boolean): Module =
SortPlugin,
),
searchContentService = get(),
syncRuntimeInfoDao = get(),
taskSubmitter = get(),
userDataPathProvider = get(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ data class DesktopAppConfig(
override val maxBackupFileSize: Long = 32,
override val enabledSyncFileSizeLimit: Boolean = true,
override val maxSyncFileSize: Long = 512,
// MB
override val maxNonFilePasteSize: Long = 8,
override val useDefaultStoragePath: Boolean = true,
override val storagePath: String = "",
override val enableSoundEffect: Boolean = true,
Expand Down Expand Up @@ -141,6 +143,7 @@ data class DesktopAppConfig(
enabledSyncFileSizeLimit
},
maxSyncFileSize = if (key == "maxSyncFileSize") toLong(value) else maxSyncFileSize,
maxNonFilePasteSize = if (key == "maxNonFilePasteSize") toLong(value) else maxNonFilePasteSize,
useDefaultStoragePath = if (key == "useDefaultStoragePath") toBoolean(value) else useDefaultStoragePath,
storagePath = if (key == "storagePath") toString(value) else storagePath,
enableSoundEffect = if (key == "enableSoundEffect") toBoolean(value) else enableSoundEffect,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.crosspaste.ui.settings

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
Expand Down Expand Up @@ -32,8 +32,8 @@ actual fun SettingListItem(
ListItem(
modifier =
Modifier
.height(
if (isSmallItem) xxxxLarge else huge,
.heightIn(
min = if (isSmallItem) xxxxLarge else huge,
).then(
if (onClick != null) Modifier.clickable { onClick() } else Modifier,
),
Expand Down Expand Up @@ -78,8 +78,8 @@ actual fun SettingListItem(
ListItem(
modifier =
Modifier
.height(
if (isSmallItem) xxxxLarge else huge,
.heightIn(
min = if (isSmallItem) xxxxLarge else huge,
).then(
if (onClick != null) Modifier.clickable { onClick() } else Modifier,
),
Expand Down Expand Up @@ -117,8 +117,8 @@ actual fun SettingListItem(
ListItem(
modifier =
Modifier
.height(
if (isSmallItem) xxxxLarge else huge,
.heightIn(
min = if (isSmallItem) xxxxLarge else huge,
).then(
if (onClick != null) Modifier.clickable { onClick() } else Modifier,
),
Expand Down Expand Up @@ -152,8 +152,8 @@ actual fun SettingListSwitchItem(
val isSmallItem = LocalSmallSettingItemState.current
ListItem(
modifier =
Modifier.height(
if (isSmallItem) xxxxLarge else huge,
Modifier.heightIn(
min = if (isSmallItem) xxxxLarge else huge,
),
headlineContent = { Text(copywriter.getText(title), style = MaterialTheme.typography.bodyMedium) },
supportingContent =
Expand Down
4 changes: 4 additions & 0 deletions app/src/desktopMain/resources/i18n/de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ local_version_low_desc=Aktuelle Softwareversion zu niedrig, Upgrade empfohlen.
manual_clear=Manuelle Bereinigung
manual_proxy_configuration=Manuelle Proxy-Konfiguration
max_back_up_file_size=Maximale Backup-Dateigröße
max_non_file_paste_size=Maximale Größe für Nicht-Datei-Einträge
max_non_file_paste_size_desc=Dateien und Bilder sind nicht betroffen
max_sync_file_size=Maximale Sync-Dateigröße
maximum_storage=Maximaler Speicher
mcp_claude_config_hint=Führen Sie diesen Befehl im Terminal aus, um CrossPaste zu Claude Code hinzuzufügen:
Expand Down Expand Up @@ -169,6 +171,7 @@ no_data_found=Keine Daten gefunden
no_data_import=Kein Datenimport
no_new_version_available=Keine neue Version verfügbar
new_version_available=Eine neue Version ist verfügbar
non_file_paste_discarded_too_large=Text-, HTML-, Rich-Text- oder Link-Inhalt ist zu groß und wurde nicht gespeichert
update_available=Eine neue Version ist verfügbar
update_available_dialog_desc=Eine neuere Version von CrossPaste ist bereit. Jetzt aktualisieren, um herunterzuladen und neu zu starten, oder später.
update_now=Jetzt aktualisieren
Expand Down Expand Up @@ -265,6 +268,7 @@ relative_seconds_ago=vor %s Sekunden
relative_weeks_ago=vor %s Wochen
relative_years_ago=vor %s Jahren
remote=Remote
remote_non_file_paste_discarded_too_large=Text-, HTML-, Rich-Text- oder Link-Inhalt von %s ist zu groß und wurde nicht synchronisiert
remote_version_low_desc=Softwareversion des verbundenen Geräts zu niedrig, Upgrade empfohlen.
remove=Entfernen
remove_blacklist=Von der Blacklist entfernen
Expand Down
4 changes: 4 additions & 0 deletions app/src/desktopMain/resources/i18n/en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ local_version_low_desc=Current software version is too low, upgrading is recomme
manual_clear=Manual clear
manual_proxy_configuration=Manual proxy configuration
max_back_up_file_size=Max back up file size
max_non_file_paste_size=Max non-file paste size
max_non_file_paste_size_desc=Files and images are not limited
max_sync_file_size=Max sync file size
maximum_storage=Maximum Storage
mcp_claude_config_hint=Run this command in your terminal to add CrossPaste to Claude Code:
Expand Down Expand Up @@ -169,6 +171,7 @@ no_data_found=No data found
no_data_import=No data import
no_new_version_available=No new version available
new_version_available=A new version is available
non_file_paste_discarded_too_large=Text, HTML, rich text, or link content is too large and was not recorded
update_available=A new version is available
update_available_dialog_desc=A newer version of CrossPaste is ready. Update now to download and restart, or do it later.
update_now=Update now
Expand Down Expand Up @@ -265,6 +268,7 @@ relative_seconds_ago=%s seconds ago
relative_weeks_ago=%s weeks ago
relative_years_ago=%s years ago
remote=Remote
remote_non_file_paste_discarded_too_large=Text, HTML, rich text, or link content from %s is too large and was not synced
remote_version_low_desc=Connected device software version is too low, upgrading is recommended.
remove=Remove
remove_blacklist=Remove from blacklist
Expand Down
4 changes: 4 additions & 0 deletions app/src/desktopMain/resources/i18n/es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ local_version_low_desc=La versión actual de software es demasiado baja, se reco
manual_clear=Limpieza manual
manual_proxy_configuration=Configuración manual de proxy
max_back_up_file_size=Tamaño máximo de archivo backup
max_non_file_paste_size=Tamaño máximo de elementos que no son archivos
max_non_file_paste_size_desc=Los archivos e imágenes no están limitados
max_sync_file_size=Tamaño máximo de archivo de sincronización
maximum_storage=Almacenamiento máximo
mcp_claude_config_hint=Ejecute este comando en su terminal para añadir CrossPaste a Claude Code:
Expand Down Expand Up @@ -169,6 +171,7 @@ no_data_found=No se encontraron datos
no_data_import=No hay datos para importar
no_new_version_available=No hay una nueva versión disponible
new_version_available=Hay una nueva versión disponible
non_file_paste_discarded_too_large=El contenido de texto, HTML, texto enriquecido o enlace es demasiado grande y no se registró
update_available=Hay una nueva versión disponible
update_available_dialog_desc=Hay una versión más reciente de CrossPaste lista. Actualiza ahora para descargar y reiniciar, o hazlo más tarde.
update_now=Actualizar ahora
Expand Down Expand Up @@ -265,6 +268,7 @@ relative_seconds_ago=hace %s segundos
relative_weeks_ago=hace %s semanas
relative_years_ago=hace %s años
remote=Remoto
remote_non_file_paste_discarded_too_large=El contenido de texto, HTML, texto enriquecido o enlace de %s es demasiado grande y no se sincronizó
remote_version_low_desc=La versión de software del dispositivo conectado es demasiado baja, se recomienda actualizar.
remove=Eliminar
remove_blacklist=Quitar de la lista negra
Expand Down
4 changes: 4 additions & 0 deletions app/src/desktopMain/resources/i18n/fa.properties
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ local_version_low_desc=نسخه نرم‌افزار فعلی بیش از حد ق
manual_clear=پاک‌سازی دستی
manual_proxy_configuration=پیکربندی دستی پروکسی
max_back_up_file_size=حداکثر اندازه فایل پشتیبان
max_non_file_paste_size=حداکثر اندازه موارد غیرفایلی
max_non_file_paste_size_desc=فایل‌ها و تصاویر محدود نمی‌شوند
max_sync_file_size=حداکثر اندازه فایل برای همگام‌سازی
maximum_storage=حداکثر فضای ذخیره‌سازی
mcp_claude_config_hint=این دستور را در ترمینال اجرا کنید تا CrossPaste به Claude Code اضافه شود:
Expand Down Expand Up @@ -169,6 +171,7 @@ no_data_found=داده‌ای یافت نشد
no_data_import=داده‌ای برای درون‌ریزی نیست
no_new_version_available=نسخه جدیدی در دسترس نیست
new_version_available=نسخه جدیدی در دسترس است
non_file_paste_discarded_too_large=محتوای متن / HTML / متن غنی / پیوند بسیار بزرگ است و ثبت نشد
update_available=نسخه جدیدی در دسترس است
update_available_dialog_desc=نسخه جدیدتری از CrossPaste آماده است. اکنون به‌روزرسانی کنید تا دانلود و راه‌اندازی مجدد شود، یا بعداً انجام دهید.
update_now=اکنون به‌روزرسانی کن
Expand Down Expand Up @@ -265,6 +268,7 @@ relative_seconds_ago=%s ثانیه پیش
relative_weeks_ago=%s هفته پیش
relative_years_ago=%s سال پیش
remote=ریموت
remote_non_file_paste_discarded_too_large=محتوای متن / HTML / متن غنی / پیوند از %s بسیار بزرگ است و همگام‌سازی نشد
remote_version_low_desc=نسخه نرم‌افزار دستگاه متصل بیش از حد قدیمی است، ارتقا توصیه می‌شود.
remove=حذف
remove_blacklist=حذف از لیست سیاه
Expand Down
Loading