Skip to content

feat: Add timestamps to clipboard updates#18

Merged
Dr1mH4X merged 5 commits into
mainfrom
dev
Mar 18, 2026
Merged

feat: Add timestamps to clipboard updates#18
Dr1mH4X merged 5 commits into
mainfrom
dev

Conversation

@Dr1mH4X

@Dr1mH4X Dr1mH4X commented Mar 18, 2026

Copy link
Copy Markdown
Owner

Add timestamps to clipboard updates

Include Unix timestamp in clipboard payloads and peer messages.
Make clipboard setter ignore incoming updates older than the last seen
timestamp to prevent applying stale clipboard contents.
When LAN beacon send fails, attempt to rebind a new UDP socket and set
broadcast before retrying.
close #13

Summary by Sourcery

添加剪贴板时间戳,以防止应用过期的剪贴板内容,并在 UDP 发送失败时提升局域网广播信标的鲁棒性。

New Features:

  • 在剪贴板更新负载和局域网对等端的剪贴板消息中加入 Unix 时间戳。

Bug Fixes:

  • 在剪贴板设置器中忽略早于最近一次已应用时间戳的传入剪贴板更新,以避免回退到过期数据。
  • 当局域网广播信标发送操作失败时,尝试重新绑定并重新配置 UDP 广播套接字,从而提高对瞬时网络错误的恢复能力。
Original summary in English

Summary by Sourcery

Add clipboard timestamps to prevent applying stale clipboard contents and improve LAN beacon robustness when UDP sends fail.

New Features:

  • Include Unix timestamps in clipboard update payloads and LAN peer clipboard messages.

Bug Fixes:

  • Ignore incoming clipboard updates older than the last applied timestamp in the clipboard setter to avoid reverting to stale data.
  • Attempt to rebind and reconfigure the UDP broadcast socket when LAN beacon send operations fail, increasing resilience to transient network errors.

Summary by Sourcery

为剪贴板添加时间戳用于新鲜度追踪,并改进局域网发现广播和剪贴板应用逻辑的健壮性。

New Features:

  • 在剪贴板更新负载和局域网节点剪贴板消息中附加 Unix 时间戳,以追踪剪贴板数据的生成时间。

Bug Fixes:

  • 在剪贴板设置器中通过追踪上一次已应用的内容,防止应用过期或重复的剪贴板文本和图像更新。
  • 当发送操作失败时,通过重新绑定并重新配置 UDP 套接字以及记录重新绑定结果,提高局域网广播信标发送的鲁棒性。
Original summary in English

Summary by Sourcery

Add clipboard timestamps for freshness tracking and improve robustness of LAN discovery beacons and clipboard application logic.

New Features:

  • Attach Unix timestamps to clipboard update payloads and LAN peer clipboard messages to track when clipboard data was generated.

Bug Fixes:

  • Prevent applying stale or duplicate clipboard text and image updates in the clipboard setter by tracking the last applied content.
  • Increase resilience of LAN beacon broadcasting by rebinding and reconfiguring the UDP socket when send operations fail and logging rebind outcomes.

新功能:

  • 在剪贴板更新负载和局域网节点的剪贴板消息中附加 Unix 时间戳,以指示剪贴板数据的生成时间。

错误修复:

  • 通过忽略早于上次已应用时间戳的负载,或内容未变化的负载,防止应用过期或重复的文本和图像剪贴板更新。
  • 在发送操作失败时,通过重新绑定和重新配置 UDP 广播套接字来提高局域网信标的弹性,并记录重新绑定的结果。
Original summary in English

Summary by Sourcery

为剪贴板添加时间戳用于新鲜度追踪,并改进局域网发现广播和剪贴板应用逻辑的健壮性。

New Features:

  • 在剪贴板更新负载和局域网节点剪贴板消息中附加 Unix 时间戳,以追踪剪贴板数据的生成时间。

Bug Fixes:

  • 在剪贴板设置器中通过追踪上一次已应用的内容,防止应用过期或重复的剪贴板文本和图像更新。
  • 当发送操作失败时,通过重新绑定并重新配置 UDP 套接字以及记录重新绑定结果,提高局域网广播信标发送的鲁棒性。
Original summary in English

Summary by Sourcery

Add clipboard timestamps for freshness tracking and improve robustness of LAN discovery beacons and clipboard application logic.

New Features:

  • Attach Unix timestamps to clipboard update payloads and LAN peer clipboard messages to track when clipboard data was generated.

Bug Fixes:

  • Prevent applying stale or duplicate clipboard text and image updates in the clipboard setter by tracking the last applied content.
  • Increase resilience of LAN beacon broadcasting by rebinding and reconfiguring the UDP socket when send operations fail and logging rebind outcomes.

新功能:

  • 为剪贴板更新载荷和局域网节点的剪贴板消息附加 Unix 时间戳,以跟踪剪贴板数据的新鲜度。

错误修复:

  • 在剪贴板设置器中防止重新应用相同的剪贴板文本或图像数据,以避免冗余更新。
  • 当局域网信标发送操作失败时,尝试重新绑定并重新配置 UDP 广播套接字,从而提高局域网发现的健壮性。
Original summary in English

Summary by Sourcery

为剪贴板添加时间戳用于新鲜度追踪,并改进局域网发现广播和剪贴板应用逻辑的健壮性。

New Features:

  • 在剪贴板更新负载和局域网节点剪贴板消息中附加 Unix 时间戳,以追踪剪贴板数据的生成时间。

Bug Fixes:

  • 在剪贴板设置器中通过追踪上一次已应用的内容,防止应用过期或重复的剪贴板文本和图像更新。
  • 当发送操作失败时,通过重新绑定并重新配置 UDP 套接字以及记录重新绑定结果,提高局域网广播信标发送的鲁棒性。
Original summary in English

Summary by Sourcery

Add clipboard timestamps for freshness tracking and improve robustness of LAN discovery beacons and clipboard application logic.

New Features:

  • Attach Unix timestamps to clipboard update payloads and LAN peer clipboard messages to track when clipboard data was generated.

Bug Fixes:

  • Prevent applying stale or duplicate clipboard text and image updates in the clipboard setter by tracking the last applied content.
  • Increase resilience of LAN beacon broadcasting by rebinding and reconfiguring the UDP socket when send operations fail and logging rebind outcomes.

新功能:

  • 在剪贴板更新负载和局域网节点的剪贴板消息中附加 Unix 时间戳,以指示剪贴板数据的生成时间。

错误修复:

  • 通过忽略早于上次已应用时间戳的负载,或内容未变化的负载,防止应用过期或重复的文本和图像剪贴板更新。
  • 在发送操作失败时,通过重新绑定和重新配置 UDP 广播套接字来提高局域网信标的弹性,并记录重新绑定的结果。
Original summary in English

Summary by Sourcery

为剪贴板添加时间戳用于新鲜度追踪,并改进局域网发现广播和剪贴板应用逻辑的健壮性。

New Features:

  • 在剪贴板更新负载和局域网节点剪贴板消息中附加 Unix 时间戳,以追踪剪贴板数据的生成时间。

Bug Fixes:

  • 在剪贴板设置器中通过追踪上一次已应用的内容,防止应用过期或重复的剪贴板文本和图像更新。
  • 当发送操作失败时,通过重新绑定并重新配置 UDP 套接字以及记录重新绑定结果,提高局域网广播信标发送的鲁棒性。
Original summary in English

Summary by Sourcery

Add clipboard timestamps for freshness tracking and improve robustness of LAN discovery beacons and clipboard application logic.

New Features:

  • Attach Unix timestamps to clipboard update payloads and LAN peer clipboard messages to track when clipboard data was generated.

Bug Fixes:

  • Prevent applying stale or duplicate clipboard text and image updates in the clipboard setter by tracking the last applied content.
  • Increase resilience of LAN beacon broadcasting by rebinding and reconfiguring the UDP socket when send operations fail and logging rebind outcomes.

Copilot AI review requested due to automatic review settings March 18, 2026 05:58
@sourcery-ai

sourcery-ai Bot commented Mar 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

为与剪贴板相关的负载和局域网对等消息添加 Unix 时间戳,在剪贴板 setter 中引入去重与新鲜度检查,并通过在发送失败时重新绑定 UDP 套接字并添加适当日志,使局域网 beacon 广播器更加健壮。

局域网 beacon 广播器 UDP 发送失败与重绑定的时序图

sequenceDiagram
    participant LanBeaconBroadcaster
    participant UdpSocket
    participant EventChannel

    LanBeaconBroadcaster->>UdpSocket: bind(INADDR_ANY, port=0)
    UdpSocket-->>LanBeaconBroadcaster: Ok(socket)
    LanBeaconBroadcaster->>UdpSocket: set_broadcast(true)

    loop periodic_beacon
        LanBeaconBroadcaster->>UdpSocket: send_to(beacon_payload, broadcast_addr)
        alt send_ok
            UdpSocket-->>LanBeaconBroadcaster: Ok(bytes_sent)
        else send_error
            UdpSocket-->>LanBeaconBroadcaster: Err(e)
            LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Warn, LAN beacon send failed (attempting rebind))

            LanBeaconBroadcaster->>UdpSocket: bind(INADDR_ANY, port=0)
            alt rebind_ok
                UdpSocket-->>LanBeaconBroadcaster: Ok(new_socket)
                LanBeaconBroadcaster->>UdpSocket: new_socket.set_broadcast(true)
                alt set_broadcast_ok
                    UdpSocket-->>LanBeaconBroadcaster: Ok()
                    LanBeaconBroadcaster->>LanBeaconBroadcaster: socket = new_socket
                    LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Info, LAN beacon rebind successful)
                else set_broadcast_error
                    UdpSocket-->>LanBeaconBroadcaster: Err(err)
                    LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Error, LAN beacon rebind set_broadcast failed)
                end
            else rebind_error
                UdpSocket-->>LanBeaconBroadcaster: Err(err)
                LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Error, LAN beacon rebind failed)
            end
        end
    end
Loading

更新后的剪贴板消息类型类图

classDiagram
    class ClipboardUpdatePayload {
        +String content_type
        +String data
        +String sender_device_id
        +u64 timestamp
    }

    class ClipboardBroadcastPayload {
        +String content_type
        +String data
        +u64 timestamp
    }

    class ClipboardBroadcast {
        +String source_device_id
        +ClipboardBroadcastPayload payload
    }

    class PeerMessage {
    }

    class PeerMessageClipboardVariant {
        +String content_type
        +String data
        +u64 timestamp
    }

    ClipboardBroadcast o-- ClipboardBroadcastPayload
    PeerMessage <|-- PeerMessageClipboardVariant
Loading

文件级变更

Change Details Files
为剪贴板更新负载和局域网对等剪贴板消息添加时间戳,以追踪新鲜度。
  • 扩展 ClipboardUpdatePayload 和 ClipboardBroadcastPayload 结构体,增加一个 u64 类型的 timestamp 字段。
  • 在发送文本和图片剪贴板更新时,通过新的 current_timestamp() 辅助方法填充 timestamp。
  • 通过更新 PeerMessage::Clipboard 及其发送/接收逻辑,将 timestamp 在局域网对等消息中向前透传。
src-tauri/src/runtime/messages.rs
src-tauri/src/runtime/clipboard.rs
src-tauri/src/runtime/lan/protocol.rs
src-tauri/src/runtime/lan/peer.rs
防止过期或重复的剪贴板更新在本地被应用。
  • 在剪贴板 setter 循环中跟踪上一次应用的文本和图片剪贴板数据。
  • 当传入负载的数据与上一次应用的值相同时,短路处理逻辑,避免冗余的剪贴板写入。
src-tauri/src/runtime/clipboard.rs
通过在 UDP 发送失败时重新绑定套接字并添加诊断信息,提高局域网 beacon 的健壮性。
  • 在 beacon 广播器中将 UDP 套接字改为可变,以便在出错后可以被替换。
  • 在发送失败时,尝试绑定一个新的 UDP 套接字、为其设置广播标志,并在成功时将其替换为当前套接字。
  • 发出 warn/info/error 级别的 RuntimeLogEvent 日志条目,用于描述发送失败、重绑定成功,以及重绑定或 set_broadcast 出错的情况。
src-tauri/src/runtime/lan/discovery.rs

相对于关联 Issue 的评估

Issue Objective Addressed Explanation
#13 通过在需要时重新绑定套接字,确保局域网对等发现/运行时能够正确处理网络接口变化(例如 Wi‑Fi ↔ 有线、睡眠/唤醒),以避免 beacon/连接卡死。
#13 为设备间同时剪贴板更新实现冲突解决策略(例如使用时间戳),以避免无限同步循环或过期剪贴板覆盖。
#13 通过增加兼容的解析/格式转换或优雅降级为纯文本,改善不同操作系统间(例如 macOS → Windows)富文本/HTML 的跨平台剪贴板处理。 该 PR 仅在剪贴板消息中添加了时间戳,在剪贴板 setter 中跳过重复的文本/图片负载,并改进了 UDP beacon 套接字的重绑定逻辑。它没有对富文本/HTML 格式或跨平台剪贴板解析/转换做任何改动。

可能关联的 Issue


Tips and commands

Interacting with Sourcery

  • 触发新评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在回复某条评审评论时让 Sourcery 创建 issue。你也可以回复该评论并写上 @sourcery-ai issue,从该评论生成 issue。
  • 生成 pull request 标题: 在 pull request 标题中的任意位置写上 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文的任意位置写上 @sourcery-ai summary,即可在该位置生成 PR 摘要。也可以在 pull request 中评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,这会很有用。
  • 忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 评审。特别适用于你想从一次全新的评审开始——别忘了随后评论 @sourcery-ai review 来触发新的评审!

Customizing Your Experience

访问你的 dashboard 以:

  • 启用或停用评审特性,例如 Sourcery 自动生成的 pull request 摘要、审阅者指南等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审说明。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Adds Unix timestamps to clipboard-related payloads and LAN peer messages, introduces de-duplication and freshness checks in the clipboard setter, and makes the LAN beacon broadcaster more robust by rebinding the UDP socket on send failures with appropriate logging.

Sequence diagram for LAN beacon broadcaster UDP send failure and rebind

sequenceDiagram
    participant LanBeaconBroadcaster
    participant UdpSocket
    participant EventChannel

    LanBeaconBroadcaster->>UdpSocket: bind(INADDR_ANY, port=0)
    UdpSocket-->>LanBeaconBroadcaster: Ok(socket)
    LanBeaconBroadcaster->>UdpSocket: set_broadcast(true)

    loop periodic_beacon
        LanBeaconBroadcaster->>UdpSocket: send_to(beacon_payload, broadcast_addr)
        alt send_ok
            UdpSocket-->>LanBeaconBroadcaster: Ok(bytes_sent)
        else send_error
            UdpSocket-->>LanBeaconBroadcaster: Err(e)
            LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Warn, LAN beacon send failed (attempting rebind))

            LanBeaconBroadcaster->>UdpSocket: bind(INADDR_ANY, port=0)
            alt rebind_ok
                UdpSocket-->>LanBeaconBroadcaster: Ok(new_socket)
                LanBeaconBroadcaster->>UdpSocket: new_socket.set_broadcast(true)
                alt set_broadcast_ok
                    UdpSocket-->>LanBeaconBroadcaster: Ok()
                    LanBeaconBroadcaster->>LanBeaconBroadcaster: socket = new_socket
                    LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Info, LAN beacon rebind successful)
                else set_broadcast_error
                    UdpSocket-->>LanBeaconBroadcaster: Err(err)
                    LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Error, LAN beacon rebind set_broadcast failed)
                end
            else rebind_error
                UdpSocket-->>LanBeaconBroadcaster: Err(err)
                LanBeaconBroadcaster->>EventChannel: RuntimeLogEvent(Level_Error, LAN beacon rebind failed)
            end
        end
    end
Loading

Updated class diagram for clipboard message types

classDiagram
    class ClipboardUpdatePayload {
        +String content_type
        +String data
        +String sender_device_id
        +u64 timestamp
    }

    class ClipboardBroadcastPayload {
        +String content_type
        +String data
        +u64 timestamp
    }

    class ClipboardBroadcast {
        +String source_device_id
        +ClipboardBroadcastPayload payload
    }

    class PeerMessage {
    }

    class PeerMessageClipboardVariant {
        +String content_type
        +String data
        +u64 timestamp
    }

    ClipboardBroadcast o-- ClipboardBroadcastPayload
    PeerMessage <|-- PeerMessageClipboardVariant
Loading

File-Level Changes

Change Details Files
Add timestamps to clipboard update payloads and LAN peer clipboard messages to track freshness.
  • Extend ClipboardUpdatePayload and ClipboardBroadcastPayload structs to carry a u64 timestamp field.
  • Populate timestamp using a new current_timestamp() helper when emitting text and image clipboard updates.
  • Forward the timestamp through LAN peer messages by updating PeerMessage::Clipboard and its send/receive handling.
src-tauri/src/runtime/messages.rs
src-tauri/src/runtime/clipboard.rs
src-tauri/src/runtime/lan/protocol.rs
src-tauri/src/runtime/lan/peer.rs
Prevent stale or duplicate clipboard updates from being applied locally.
  • Track last applied text and image clipboard data in the clipboard setter loop.
  • Short‑circuit processing when an incoming payload has identical data to the last applied value to avoid redundant clipboard writes.
src-tauri/src/runtime/clipboard.rs
Improve LAN beacon robustness by rebinding UDP socket on send failures and adding diagnostics.
  • Make the UDP socket mutable in the beacon broadcaster so it can be replaced after errors.
  • On send failure, attempt to bind a new UDP socket, set broadcast on it, and swap it in if successful.
  • Emit warn/info/error RuntimeLogEvent entries describing send failures, rebind success, and rebind failures or set_broadcast errors.
src-tauri/src/runtime/lan/discovery.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#13 Ensure LAN peer discovery/runtime correctly handles network interface changes (e.g., Wi‑Fi ↔ wired, sleep/wake) by rebinding sockets as needed to avoid beacon/connection hangs.
#13 Implement a conflict‑resolution strategy for simultaneous clipboard updates between devices (e.g., using timestamps) to avoid infinite sync loops or stale clipboard overwrites.
#13 Improve cross‑platform clipboard handling for rich text/HTML between different OSes (e.g., macOS → Windows) by adding compatible parsing/format conversion or graceful degradation to plain text. The PR only adds timestamps to clipboard messages, skips duplicate text/image payloads in the clipboard setter, and improves UDP beacon socket rebinding. It does not introduce any changes related to rich text/HTML formats or cross‑platform clipboard parsing/conversion.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • 使用 SystemTime::now().duration_since(UNIX_EPOCH).unwrap() 时,如果系统时钟被设置在 Unix 纪元之前,就可能发生 panic;建议显式处理 Err 分支并回退到一个安全的默认值,而不是直接 unwrap。
  • 在 LAN beacon 重新绑定的路径中,UdpSocket::bindset_broadcast(true) 失败会被静默忽略;建议记录这些错误日志,这样在生产环境中就能观察到 beacon 重新绑定相关的问题。
给 AI Agent 的提示
请根据本次代码审查中的评论进行修改:

## 总体评论
- 使用 `SystemTime::now().duration_since(UNIX_EPOCH).unwrap()` 时,如果系统时钟被设置在 Unix 纪元之前,就可能发生 panic;建议显式处理 `Err` 分支并回退到一个安全的默认值,而不是直接 unwrap。
- 在 LAN beacon 重新绑定的路径中,`UdpSocket::bind``set_broadcast(true)` 失败会被静默忽略;建议记录这些错误日志,这样在生产环境中就能观察到 beacon 重新绑定相关的问题。

## 逐条评论

### 评论 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="202-205" />
<code_context>
             _ = cancel.cancelled() => break,
             maybe_payload = rx.recv() => {
                 if let Some(payload) = maybe_payload {
+                    if payload.timestamp < last_timestamp {
+                        continue;
+                    }
+                    last_timestamp = payload.timestamp;
                     match payload.content_type.as_str() {
                         CONTENT_TYPE_TEXT => {
</code_context>
<issue_to_address>
**issue (bug_risk):** 依赖基于墙上时间(wall-clock)的时间戳来做排序,在系统时钟被回拨时可能会丢弃有效的更新。

`SystemTime::now()` 并不保证单调递增,可能会往回跳或向前跳(例如 NTP 同步、手动修改时间),因此这种比较有可能错误地跳过更新的剪贴板内容。对于本地排序,更推荐使用单调递增的序列(例如一个计数器,或者用 `Instant` 映射到一个只增不减的值)。如果这个时间戳是用于跨设备排序,还需要在设计上考虑不同机器之间的时钟偏差。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的审查有帮助,请考虑分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Using SystemTime::now().duration_since(UNIX_EPOCH).unwrap() can panic if the system clock is set before the Unix epoch; consider handling the Err case explicitly and falling back to a safe default instead of unwrapping.
  • In the LAN beacon rebind path, failures of UdpSocket::bind or set_broadcast(true) are silently ignored; consider logging these errors so issues with beacon rebinding are observable in production.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `SystemTime::now().duration_since(UNIX_EPOCH).unwrap()` can panic if the system clock is set before the Unix epoch; consider handling the `Err` case explicitly and falling back to a safe default instead of unwrapping.
- In the LAN beacon rebind path, failures of `UdpSocket::bind` or `set_broadcast(true)` are silently ignored; consider logging these errors so issues with beacon rebinding are observable in production.

## Individual Comments

### Comment 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="202-205" />
<code_context>
             _ = cancel.cancelled() => break,
             maybe_payload = rx.recv() => {
                 if let Some(payload) = maybe_payload {
+                    if payload.timestamp < last_timestamp {
+                        continue;
+                    }
+                    last_timestamp = payload.timestamp;
                     match payload.content_type.as_str() {
                         CONTENT_TYPE_TEXT => {
</code_context>
<issue_to_address>
**issue (bug_risk):** Relying on wall-clock timestamps for ordering can drop valid updates when the system clock moves backwards.

`SystemTime::now()` is not guaranteed to be monotonic and can move backward or jump forward (e.g., NTP, manual clock changes), so this comparison can incorrectly skip newer clipboard updates. For local ordering, prefer a monotonic sequence (e.g., a counter or `Instant` mapped to an increasing value). If the timestamp is intended for cross-device ordering, you’ll also need to account for clock skew between machines in the design.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src-tauri/src/runtime/clipboard.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a timestamp field to clipboard-related payloads/messages, uses it to ignore stale inbound clipboard updates, and improves LAN discovery robustness by rebinding the UDP broadcast socket after send failures.

Changes:

  • Added timestamp to clipboard update/broadcast payloads and LAN PeerMessage::Clipboard.
  • Introduced stale-update filtering in the clipboard setter using last_timestamp.
  • On LAN beacon send failure, attempt to rebind a new UDP socket with broadcast enabled.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src-tauri/src/runtime/messages.rs Adds timestamp fields to clipboard payload structs used for JSON messaging.
src-tauri/src/runtime/lan/protocol.rs Extends LAN peer protocol clipboard message to carry a timestamp.
src-tauri/src/runtime/lan/peer.rs Propagates timestamp through LAN peer send/receive clipboard relays.
src-tauri/src/runtime/lan/discovery.rs Rebinds UDP socket on beacon send failures to recover from network changes.
src-tauri/src/runtime/clipboard.rs Generates timestamps for outbound updates and drops inbound updates older than the last seen timestamp.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src-tauri/src/runtime/clipboard.rs Outdated
Comment on lines +90 to +93
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
Comment thread src-tauri/src/runtime/clipboard.rs Outdated
Comment on lines +196 to +205
let mut last_timestamp = 0;
loop {
tokio::select! {
_ = cancel.cancelled() => break,
maybe_payload = rx.recv() => {
if let Some(payload) = maybe_payload {
if payload.timestamp < last_timestamp {
continue;
}
last_timestamp = payload.timestamp;
Comment on lines 137 to 151
if let Err(e) = socket.send_to(&packet, broadcast_addr).await {
let _ = events
.send(RuntimeEvent::Log(RuntimeLogEvent::new(
Level::Warn,
format!("LAN beacon send failed: {}", e),
format!("LAN beacon send failed (attempting rebind): {}", e),
)))
.await;

// Attempt to rebind on network error
if let Ok(new_socket) = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await {
if new_socket.set_broadcast(true).is_ok() {
socket = new_socket;
}
}
}
Comment on lines 38 to 43
pub struct ClipboardUpdatePayload {
pub content_type: String,
pub data: String,
pub sender_device_id: String,
pub timestamp: u64,
}
pub struct ClipboardBroadcastPayload {
pub content_type: String,
pub data: String,
pub timestamp: u64,
@@ -100,6 +100,7 @@ pub enum PeerMessage {
Clipboard {
content_type: String,
data: String,
@Dr1mH4X

Dr1mH4X commented Mar 18, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • 描述中提到要忽略早于最后一次已应用时间戳的剪贴板更新,但 start_clipboard_setter 目前只按内容(last_text / last_image_data)去重,从不比较时间戳;建议分别为每种内容类型记录最后一次应用的时间戳,并跳过时间戳小于或等于该值的更新,而不是(或不仅仅是)基于内容做去重。
  • 时间戳生成逻辑(SystemTime::now().duration_since(UNIX_EPOCH)...as_secs())在多个地方被重复使用;建议抽取成一个小的辅助函数,以保持行为一致并减少重复代码。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- The description mentions ignoring incoming clipboard updates older than the last applied timestamp, but `start_clipboard_setter` only deduplicates by content (`last_text` / `last_image_data`) and never compares timestamps; consider tracking the last applied timestamp per content type and skipping updates with lower or equal timestamps instead of (or in addition to) content-based deduplication.
- The timestamp generation logic (`SystemTime::now().duration_since(UNIX_EPOCH)...as_secs()`) is duplicated in multiple places; consider extracting this into a small helper function to keep the behavior consistent and reduce repetition.

## Individual Comments

### Comment 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="196-200" />
<code_context>
     events: mpsc::Sender<RuntimeEvent>,
     cancel: CancellationToken,
 ) {
+    let mut last_text = String::new();
+    let mut last_image_data = String::new();
     loop {
</code_context>
<issue_to_address>
**issue (bug_risk):** The `last_text`/`last_image_data` dedup logic will skip applying empty-string payloads forever.

Since `last_text` and `last_image_data` start as `String::new()`, the first empty-string payload is treated as a duplicate and skipped, and all later empty-string updates are also ignored. If an empty clipboard value should be propagated, initialize these as `Option<String>` (or similar) and treat `None` as “no previous value,” or track first use with a separate flag so an initial empty value isn’t deduped.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的代码评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点选 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The description mentions ignoring incoming clipboard updates older than the last applied timestamp, but start_clipboard_setter only deduplicates by content (last_text / last_image_data) and never compares timestamps; consider tracking the last applied timestamp per content type and skipping updates with lower or equal timestamps instead of (or in addition to) content-based deduplication.
  • The timestamp generation logic (SystemTime::now().duration_since(UNIX_EPOCH)...as_secs()) is duplicated in multiple places; consider extracting this into a small helper function to keep the behavior consistent and reduce repetition.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The description mentions ignoring incoming clipboard updates older than the last applied timestamp, but `start_clipboard_setter` only deduplicates by content (`last_text` / `last_image_data`) and never compares timestamps; consider tracking the last applied timestamp per content type and skipping updates with lower or equal timestamps instead of (or in addition to) content-based deduplication.
- The timestamp generation logic (`SystemTime::now().duration_since(UNIX_EPOCH)...as_secs()`) is duplicated in multiple places; consider extracting this into a small helper function to keep the behavior consistent and reduce repetition.

## Individual Comments

### Comment 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="196-200" />
<code_context>
     events: mpsc::Sender<RuntimeEvent>,
     cancel: CancellationToken,
 ) {
+    let mut last_text = String::new();
+    let mut last_image_data = String::new();
     loop {
</code_context>
<issue_to_address>
**issue (bug_risk):** The `last_text`/`last_image_data` dedup logic will skip applying empty-string payloads forever.

Since `last_text` and `last_image_data` start as `String::new()`, the first empty-string payload is treated as a duplicate and skipped, and all later empty-string updates are also ignored. If an empty clipboard value should be propagated, initialize these as `Option<String>` (or similar) and treat `None` as “no previous value,” or track first use with a separate flag so an initial empty value isn’t deduped.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src-tauri/src/runtime/clipboard.rs Outdated
@Dr1mH4X

Dr1mH4X commented Mar 18, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些总体反馈:

  • start_clipboard_setter 中的时间戳检查(payload.timestamp <= last_*_ts)配合以秒为粒度的 current_timestamp(),会导致在同一秒内发生的两次不同剪贴板更新共享同一个时间戳,即使数据不同,后一次更新也会被丢弃;建议使用更高精度的时间戳,或者调整条件,仅在时间戳和数据都相同时才跳过。
  • start_clipboard_setter 中新的去重状态(last_textlast_image_data 以及时间戳)在所有发送方之间是全局共享的;如果期望不同设备能够互相覆盖对方的剪贴板内容,你可能需要按发送方分别追踪“最新性”,或者重新考虑使用单一全局的 last_*,以避免产生出乎意料的行为。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The `start_clipboard_setter` timestamp check (`payload.timestamp <= last_*_ts`) with second-level `current_timestamp()` means two different clipboard updates within the same second will share the same timestamp and the latter will be dropped even if the data is different; consider using a higher-resolution timestamp or adjusting the condition to only skip when both timestamp and data match.
- The new deduplication state in `start_clipboard_setter` (`last_text`, `last_image_data`, and timestamps) is maintained globally across all senders; if different devices are expected to be able to overwrite each other’s clipboard contents, you may want to track recency per sender or reconsider using a single global `last_*` to avoid surprising behavior.

## Individual Comments

### Comment 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="201-204" />
<code_context>
                 if let Some(payload) = maybe_payload {
                     match payload.content_type.as_str() {
                         CONTENT_TYPE_TEXT => {
+                            if payload.timestamp <= last_text_ts || Some(&payload.data) == last_text.as_ref() {
+                                continue;
+                            }
+                            last_text_ts = payload.timestamp;
+                            last_text = Some(payload.data.clone());
                             if let Err(err) = set_text(&payload.data, disable_flag.clone()).await {
</code_context>
<issue_to_address>
**issue:** Relying on remote wall-clock timestamps can drop valid clipboard updates when clocks are skewed or out of order.

Using `payload.timestamp <= last_text_ts` as a hard gate assumes loosely synchronized, strictly increasing wall clocks across devices. With clock skew, a newer clipboard value from a lagging device can have a smaller timestamp and be dropped even when `payload.data` has changed.

If the intent is mainly to break feedback loops, consider instead:
- Comparing only data (and possibly sender id), or
- Using a per-device monotonic sequence, or
- Treating the timestamp as a hint (e.g., require both `timestamp < last_text_ts` and equal content before skipping).

In its current form, legitimate cross-device updates can be lost when clocks differ.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/runtime/clipboard.rs" line_range="310-314" />
<code_context>
     }
 }

+fn current_timestamp() -> u64 {
+    std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap_or_default()
+        .as_secs()
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Second-level timestamp granularity might be too coarse for disambiguating rapid clipboard changes.

`current_timestamp()` truncates to whole seconds via `as_secs()`. Multiple clipboard updates within the same second will get identical timestamps, so with the `<= last_*_ts` checks, later updates can be incorrectly treated as stale and skipped. Consider using millisecond precision (e.g. `as_millis()`) or a `(secs, nanos)` pair to avoid these collisions.

Suggested implementation:

```rust
fn current_timestamp() -> u64 {
    // Milliseconds since UNIX_EPOCH; higher precision to disambiguate rapid clipboard changes
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}

```

To fully adopt millisecond precision, you should:
1. Ensure all timestamp variables that store the result of `current_timestamp()` (e.g. `last_text_ts`, `last_image_ts`, payload `timestamp` fields) are consistently treated as “milliseconds since UNIX_EPOCH`.
2. Verify that any comparisons like `if payload.timestamp <= last_*_ts` still work as intended (they should, but comments/docs may still mention "seconds" and should be updated).
3. If timestamps are serialized (e.g. sent over IPC/JSON), confirm the consumer side expects milliseconds, and update any corresponding types/docs there as well.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得这些评论有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据这些反馈改进后续的代码审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The start_clipboard_setter timestamp check (payload.timestamp <= last_*_ts) with second-level current_timestamp() means two different clipboard updates within the same second will share the same timestamp and the latter will be dropped even if the data is different; consider using a higher-resolution timestamp or adjusting the condition to only skip when both timestamp and data match.
  • The new deduplication state in start_clipboard_setter (last_text, last_image_data, and timestamps) is maintained globally across all senders; if different devices are expected to be able to overwrite each other’s clipboard contents, you may want to track recency per sender or reconsider using a single global last_* to avoid surprising behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `start_clipboard_setter` timestamp check (`payload.timestamp <= last_*_ts`) with second-level `current_timestamp()` means two different clipboard updates within the same second will share the same timestamp and the latter will be dropped even if the data is different; consider using a higher-resolution timestamp or adjusting the condition to only skip when both timestamp and data match.
- The new deduplication state in `start_clipboard_setter` (`last_text`, `last_image_data`, and timestamps) is maintained globally across all senders; if different devices are expected to be able to overwrite each other’s clipboard contents, you may want to track recency per sender or reconsider using a single global `last_*` to avoid surprising behavior.

## Individual Comments

### Comment 1
<location path="src-tauri/src/runtime/clipboard.rs" line_range="201-204" />
<code_context>
                 if let Some(payload) = maybe_payload {
                     match payload.content_type.as_str() {
                         CONTENT_TYPE_TEXT => {
+                            if payload.timestamp <= last_text_ts || Some(&payload.data) == last_text.as_ref() {
+                                continue;
+                            }
+                            last_text_ts = payload.timestamp;
+                            last_text = Some(payload.data.clone());
                             if let Err(err) = set_text(&payload.data, disable_flag.clone()).await {
</code_context>
<issue_to_address>
**issue:** Relying on remote wall-clock timestamps can drop valid clipboard updates when clocks are skewed or out of order.

Using `payload.timestamp <= last_text_ts` as a hard gate assumes loosely synchronized, strictly increasing wall clocks across devices. With clock skew, a newer clipboard value from a lagging device can have a smaller timestamp and be dropped even when `payload.data` has changed.

If the intent is mainly to break feedback loops, consider instead:
- Comparing only data (and possibly sender id), or
- Using a per-device monotonic sequence, or
- Treating the timestamp as a hint (e.g., require both `timestamp < last_text_ts` and equal content before skipping).

In its current form, legitimate cross-device updates can be lost when clocks differ.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/runtime/clipboard.rs" line_range="310-314" />
<code_context>
     }
 }

+fn current_timestamp() -> u64 {
+    std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap_or_default()
+        .as_secs()
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Second-level timestamp granularity might be too coarse for disambiguating rapid clipboard changes.

`current_timestamp()` truncates to whole seconds via `as_secs()`. Multiple clipboard updates within the same second will get identical timestamps, so with the `<= last_*_ts` checks, later updates can be incorrectly treated as stale and skipped. Consider using millisecond precision (e.g. `as_millis()`) or a `(secs, nanos)` pair to avoid these collisions.

Suggested implementation:

```rust
fn current_timestamp() -> u64 {
    // Milliseconds since UNIX_EPOCH; higher precision to disambiguate rapid clipboard changes
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}

```

To fully adopt millisecond precision, you should:
1. Ensure all timestamp variables that store the result of `current_timestamp()` (e.g. `last_text_ts`, `last_image_ts`, payload `timestamp` fields) are consistently treated as “milliseconds since UNIX_EPOCH`.
2. Verify that any comparisons like `if payload.timestamp <= last_*_ts` still work as intended (they should, but comments/docs may still mention "seconds" and should be updated).
3. If timestamps are serialized (e.g. sent over IPC/JSON), confirm the consumer side expects milliseconds, and update any corresponding types/docs there as well.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src-tauri/src/runtime/clipboard.rs
Comment thread src-tauri/src/runtime/clipboard.rs Outdated
@Dr1mH4X

Dr1mH4X commented Mar 18, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我给出了一些总体反馈:

  • 描述中提到要忽略早于上次已应用时间戳的剪贴板更新,但 start_clipboard_setter 目前只按重复的 data 来过滤;建议按内容类型记录 last_timestamp,并跳过所有 timestamp <= last_timestamp 的负载,这样才能完整实现预期行为。
  • current_timestamp 使用 SystemTime::now() 并将 as_millis() 转为 u64;如果你需要在多设备之间依赖时间戳的顺序,建议记录或规范化时间源(例如,明确使用自纪元起的毫秒值,并处理潜在的时钟偏移或回绕问题),以避免出现微妙的排序问题。
提供给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The description mentions ignoring clipboard updates older than the last applied timestamp, but `start_clipboard_setter` currently only filters by duplicate `data`; consider tracking `last_timestamp` per content type and skipping payloads with `timestamp <= last_timestamp` to fully implement the intended behavior.
- `current_timestamp` uses `SystemTime::now()` and casts `as_millis()` to `u64`; if you rely on timestamp ordering across devices, consider documenting or normalizing the time source (e.g., clearly using milliseconds-since-epoch and handling potential clock skew or wraparound) to avoid subtle ordering issues.

Sourcery 对开源项目永久免费——如果你喜欢我们的评审,请考虑分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've left some high level feedback:

  • The description mentions ignoring clipboard updates older than the last applied timestamp, but start_clipboard_setter currently only filters by duplicate data; consider tracking last_timestamp per content type and skipping payloads with timestamp <= last_timestamp to fully implement the intended behavior.
  • current_timestamp uses SystemTime::now() and casts as_millis() to u64; if you rely on timestamp ordering across devices, consider documenting or normalizing the time source (e.g., clearly using milliseconds-since-epoch and handling potential clock skew or wraparound) to avoid subtle ordering issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The description mentions ignoring clipboard updates older than the last applied timestamp, but `start_clipboard_setter` currently only filters by duplicate `data`; consider tracking `last_timestamp` per content type and skipping payloads with `timestamp <= last_timestamp` to fully implement the intended behavior.
- `current_timestamp` uses `SystemTime::now()` and casts `as_millis()` to `u64`; if you rely on timestamp ordering across devices, consider documenting or normalizing the time source (e.g., clearly using milliseconds-since-epoch and handling potential clock skew or wraparound) to avoid subtle ordering issues.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Dr1mH4X Dr1mH4X merged commit 0948a0d into main Mar 18, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in RustSyncCV Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[fix] 网络断开与重连,并发冲突处理与剪贴板数据丢失或格式异常处理

2 participants