Skip to content

Commit 3862cc5

Browse files
committed
fix(windows): import remote clipboard history
1 parent d8e6059 commit 3862cc5

5 files changed

Lines changed: 94 additions & 10 deletions

File tree

clipSync-windows/ClipSync.WPF.Tests/ProtocolTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ public void CreateDeviceListMessage()
7474
Assert.Equal("device_list", obj["type"]?.Value<string>());
7575
}
7676

77+
[Fact]
78+
public void CreateDeviceUnregisterMessage_IncludesDeviceId()
79+
{
80+
// Act
81+
string message = Protocol.CreateDeviceUnregisterMessage("dev-123");
82+
83+
// Assert
84+
var obj = JObject.Parse(message);
85+
Assert.Equal("device_unregister", obj["type"]?.Value<string>());
86+
Assert.Equal("dev-123", obj["payload"]?["device_id"]?.Value<string>());
87+
}
88+
7789
[Fact]
7890
public void Deserialize_ValidMessage()
7991
{

clipSync-windows/ClipSync.WPF/Core/SyncEngine.cs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class SyncEngine : IDisposable
2020

2121
public event Action<string>? ConnectionStateChanged;
2222
public event Action<Network.ClipboardItem>? ClipboardItemReceived;
23+
public event Action? ClipboardHistoryUpdated;
2324
public event Action<string>? ErrorOccurred;
2425
public event Action<List<Network.Device>>? DeviceListUpdated;
2526

@@ -156,7 +157,7 @@ private async void OnWebSocketMessage(string message)
156157
_heartbeatTimer?.OnHeartbeatAck();
157158
break;
158159
case "clipboard_history":
159-
HandleClipboardHistory(wsMessage);
160+
await HandleClipboardHistoryAsync(wsMessage);
160161
break;
161162
case "device_list_response":
162163
HandleDeviceListResponse(wsMessage);
@@ -278,13 +279,54 @@ private async Task HandleClipboardSync(WebSocketMessage message)
278279

279280
if (_database != null)
280281
{
281-
await _database.InsertClipboardItemAsync(item);
282+
_ = await _database.InsertClipboardItemAsync(item);
282283
}
283284
}
284285

285-
private void HandleClipboardHistory(WebSocketMessage message)
286+
private async Task HandleClipboardHistoryAsync(WebSocketMessage message)
286287
{
287-
// Handle clipboard history response if needed
288+
var itemsArray = message.Payload?.Property("items")?.Value;
289+
if (itemsArray == null)
290+
{
291+
AppLogger.Warn("SyncEngine", "收到 clipboard_history,但缺少 items 字段");
292+
return;
293+
}
294+
295+
var importedCount = 0;
296+
foreach (var itemToken in itemsArray)
297+
{
298+
var item = new Network.ClipboardItem
299+
{
300+
Id = GetLong(itemToken, "id"),
301+
ContentType = GetString(itemToken, "content_type", "text"),
302+
Content = GetString(itemToken, "content"),
303+
Format = GetString(itemToken, "format"),
304+
Size = GetLong(itemToken, "size"),
305+
Checksum = GetString(itemToken, "checksum"),
306+
SourceDeviceId = GetString(itemToken, "source_device_id"),
307+
SourceDeviceName = GetString(itemToken, "source_device_name", "Unknown device"),
308+
CreatedAt = GetLong(itemToken, "created_at")
309+
};
310+
311+
if (string.IsNullOrEmpty(item.Content) || string.IsNullOrEmpty(item.Checksum))
312+
{
313+
continue;
314+
}
315+
316+
if (_database != null && await _database.InsertClipboardItemAsync(item))
317+
{
318+
importedCount++;
319+
}
320+
}
321+
322+
var total = GetLong(message.Payload, "total");
323+
var hasMore = GetBool(message.Payload, "has_more");
324+
AppLogger.Info("SyncEngine", $"收到剪贴板历史: imported={importedCount}, total={total}, has_more={hasMore}");
325+
326+
if (importedCount > 0)
327+
{
328+
ClipboardHistoryUpdated?.Invoke();
329+
}
288330
}
289331

290332
private void HandleDeviceListResponse(WebSocketMessage message)
@@ -479,7 +521,7 @@ private async Task SaveClipboardItemAsync(ClipboardChangedEventArgs args, string
479521
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
480522
};
481523

482-
await _database.InsertClipboardItemAsync(item);
524+
_ = await _database.InsertClipboardItemAsync(item);
483525
}
484526

485527
private async Task PublishClipboardChangedEventAsync(ClipboardChangedEventArgs args)

clipSync-windows/ClipSync.WPF/MainWindow.xaml.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public MainWindow(SettingsManager settingsManager, SyncEngine syncEngine, System
6363
_syncEngine.ErrorOccurred += OnErrorOccurred;
6464
_syncEngine.DeviceListUpdated += OnDeviceListUpdated;
6565
_syncEngine.ClipboardItemReceived += OnClipboardItemReceived;
66+
_syncEngine.ClipboardHistoryUpdated += OnClipboardHistoryUpdated;
6667

6768
ShowLoginContent();
6869
}
@@ -491,6 +492,12 @@ private async void OnClipboardItemReceived(Network.ClipboardItem item)
491492
await LoadHistoryAsync();
492493
}
493494

495+
private async void OnClipboardHistoryUpdated()
496+
{
497+
AppLogger.Info("MainWindow", "远端剪贴板历史已导入,准备刷新历史列表");
498+
await LoadHistoryAsync();
499+
}
500+
494501
private void ShowMainContent()
495502
{
496503
_isLoggedIn = true;

clipSync-windows/ClipSync.WPF/Network/Protocol.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ public static string CreateDeviceListMessage()
158158
return Serialize(WebSocketMessage.Create("device_list", new JObject()));
159159
}
160160

161+
public static string CreateDeviceUnregisterMessage(string deviceId)
162+
{
163+
var payload = new JObject
164+
{
165+
["device_id"] = deviceId
166+
};
167+
return Serialize(WebSocketMessage.Create("device_unregister", payload));
168+
}
169+
161170
public static string CreatePongMessage()
162171
{
163172
return Serialize(WebSocketMessage.Create("pong", new JObject()));

clipSync-windows/ClipSync.WPF/Storage/LocalDatabase.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ CREATE INDEX IF NOT EXISTS idx_clipboard_created_at
5757
});
5858
}
5959

60-
public async Task InsertClipboardItemAsync(Network.ClipboardItem item)
60+
public async Task<bool> InsertClipboardItemAsync(Network.ClipboardItem item)
6161
{
6262
await EnsureInitializedAsync();
6363

64-
await Task.Run(() =>
64+
return await Task.Run(() =>
6565
{
6666
using var connection = new SqliteConnection($"Data Source={_dbPath}");
6767
connection.Open();
@@ -70,8 +70,15 @@ await Task.Run(() =>
7070
command.CommandText = @"
7171
INSERT INTO clipboard_history
7272
(content_type, content, format, size, checksum, source_device_id, source_device_name, created_at)
73-
VALUES
74-
(@contentType, @content, @format, @size, @checksum, @sourceDeviceId, @sourceDeviceName, @createdAt)";
73+
SELECT
74+
@contentType, @content, @format, @size, @checksum, @sourceDeviceId, @sourceDeviceName, @createdAt
75+
WHERE NOT EXISTS (
76+
SELECT 1
77+
FROM clipboard_history
78+
WHERE checksum = @checksum
79+
AND source_device_id = @sourceDeviceId
80+
AND created_at = @createdAt
81+
)";
7582
command.Parameters.AddWithValue("@contentType", item.ContentType);
7683
command.Parameters.AddWithValue("@content", item.Content);
7784
command.Parameters.AddWithValue("@format", item.Format);
@@ -80,7 +87,12 @@ INSERT INTO clipboard_history
8087
command.Parameters.AddWithValue("@sourceDeviceId", item.SourceDeviceId);
8188
command.Parameters.AddWithValue("@sourceDeviceName", item.SourceDeviceName);
8289
command.Parameters.AddWithValue("@createdAt", item.CreatedAt);
83-
command.ExecuteNonQuery();
90+
var inserted = command.ExecuteNonQuery() > 0;
91+
92+
if (!inserted)
93+
{
94+
return false;
95+
}
8496

8597
// Keep only last 50 items
8698
var deleteCommand = connection.CreateCommand();
@@ -92,6 +104,8 @@ ORDER BY created_at DESC
92104
LIMIT 50
93105
)";
94106
deleteCommand.ExecuteNonQuery();
107+
108+
return true;
95109
});
96110
}
97111

0 commit comments

Comments
 (0)