Skip to content

Commit b51e603

Browse files
committed
添加功能(移动端) 原生通知支持和文件传输功能,重构相关服务
1 parent 7a6e4c2 commit b51e603

53 files changed

Lines changed: 1279 additions & 1662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Core/Core.csproj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@
8080
<DependentUpon>PluginDetail.axaml</DependentUpon>
8181
<SubType>Code</SubType>
8282
</Compile>
83-
<Compile Update="UI\DeviceCommunication\DeviceDiscoveryPage.axaml.cs">
84-
<DependentUpon>DeviceDiscoveryPage.axaml</DependentUpon>
85-
<SubType>Code</SubType>
86-
</Compile>
8783
<Compile Update="UI\DeviceCommunication\DeviceCommunicationPage.axaml.cs">
8884
<DependentUpon>DeviceCommunicationPage.axaml</DependentUpon>
8985
<SubType>Code</SubType>
@@ -96,7 +92,6 @@
9692

9793
<ItemGroup>
9894
<AdditionalFiles Include="UI\UiControls\Plugin\PluginDetail.axaml" />
99-
<AdditionalFiles Include="UI\DeviceCommunication\DeviceDiscoveryPage.axaml" />
10095
<AdditionalFiles Include="UI\DeviceCommunication\DeviceCommunicationPage.axaml" />
10196
<AdditionalFiles Include="UI\DeviceCommunication\FontIcon.axaml" />
10297
<AdditionalFiles Include="UI\DeviceCommunication\DeviceCommunicationIcons.axaml" />

Core/Services/DeviceCommunication/Application/FileTransferPayloadHandler.cs

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.IO.Pipelines;
22
using Core.Services.DeviceCommunication.Messages.Chat;
33
using Core.Services.DeviceCommunication.Sessions;
4+
using Kitopia.DeviceCommunication.Diagnostics;
45

56
namespace Core.Services.DeviceCommunication.Application;
67

@@ -19,21 +20,23 @@ public FileTransferPayloadHandler(
1920

2021
public async ValueTask HandleAsync(FileChatMessage message, PipeReader payload, CancellationToken cancellationToken)
2122
{
23+
DeviceCommunicationDiagnostics.Info("FilePayloadHandler",
24+
$"HandleAsync ChannelId={message.ChannelId} Length={message.Length}");
25+
2226
if (!_fileTransferSessionStore.TryGet(message.ChannelId, out var session) ||
2327
session.State != FileTransferState.Accepted ||
24-
string.IsNullOrWhiteSpace(session.SavePath))
28+
(string.IsNullOrWhiteSpace(session.SavePath) && session.OpenWriteStreamAsync is null))
2529
{
30+
DeviceCommunicationDiagnostics.Warning("FilePayloadHandler",
31+
$"session missing ChartId={message.ChannelId} found={(session != null)} state={session?.State} savePath={session?.SavePath}");
2632
await DrainPayloadAsync(message, payload, cancellationToken);
2733
return;
2834
}
2935

30-
var totalBytes = Math.Max(0L, message.Length ?? 0L);
31-
var directory = Path.GetDirectoryName(session.SavePath);
32-
if (!string.IsNullOrWhiteSpace(directory))
33-
{
34-
Directory.CreateDirectory(directory);
35-
}
36+
DeviceCommunicationDiagnostics.Info("FilePayloadHandler",
37+
$"writing SavePath={session.SavePath} expectedBytes={message.Length}");
3638

39+
var totalBytes = Math.Max(0L, message.Length ?? 0L);
3740
long receivedBytes = 0;
3841
long lastReportedBytes = 0;
3942
const int progressStepBytes = 1024 * 1024;
@@ -69,17 +72,14 @@ await _incomingMessageSink.PublishEventAsync(
6972

7073
try
7174
{
72-
await using var fileStream = new FileStream(
73-
session.SavePath,
74-
FileMode.Create,
75-
FileAccess.Write,
76-
FileShare.None,
77-
64 * 1024,
78-
useAsync: true);
75+
await using var fileStream = await OpenTargetStreamAsync(session, cancellationToken);
7976

8077
await using var progressStream = new ProgressReportingWriteStream(fileStream, ReportProgressAsync);
8178
await payload.CopyToAsync(progressStream, cancellationToken);
8279

80+
DeviceCommunicationDiagnostics.Info("FilePayloadHandler",
81+
$"copy done ChannelId={message.ChannelId} receivedBytes={receivedBytes} fileLength={GetStreamLengthForLog(fileStream, receivedBytes)}");
82+
8383
if (receivedBytes > 0 && receivedBytes != lastReportedBytes)
8484
{
8585
var finalProgressTotal = totalBytes > 0 ? totalBytes : receivedBytes;
@@ -116,6 +116,8 @@ await _incomingMessageSink.PublishEventAsync(
116116
}
117117
catch (OperationCanceledException)
118118
{
119+
DeviceCommunicationDiagnostics.Info("FilePayloadHandler",
120+
$"cancelled ChannelId={message.ChannelId} receivedBytes={receivedBytes}");
119121
_fileTransferSessionStore.TryRemove(message.ChannelId, out _);
120122
await _incomingMessageSink.PublishEventAsync(
121123
new FileTransferUpdatedEvent(
@@ -131,8 +133,10 @@ await _incomingMessageSink.PublishEventAsync(
131133
CancellationToken.None);
132134
throw;
133135
}
134-
catch
136+
catch (Exception ex)
135137
{
138+
DeviceCommunicationDiagnostics.Error("FilePayloadHandler",
139+
$"receive failed ChannelId={message.ChannelId} receivedBytes={receivedBytes} SavePath={session.SavePath}", ex);
136140
_fileTransferSessionStore.TryRemove(message.ChannelId, out _);
137141
await _incomingMessageSink.PublishEventAsync(
138142
new FileTransferUpdatedEvent(
@@ -150,6 +154,46 @@ await _incomingMessageSink.PublishEventAsync(
150154
}
151155
}
152156

157+
private static async ValueTask<Stream> OpenTargetStreamAsync(
158+
FileTransferSession session,
159+
CancellationToken cancellationToken)
160+
{
161+
if (session.OpenWriteStreamAsync is not null)
162+
{
163+
var stream = await session.OpenWriteStreamAsync(cancellationToken);
164+
if (stream.CanSeek)
165+
{
166+
stream.SetLength(0);
167+
}
168+
169+
return stream;
170+
}
171+
172+
if (string.IsNullOrWhiteSpace(session.SavePath))
173+
{
174+
throw new InvalidOperationException("Missing file save target.");
175+
}
176+
177+
var directory = Path.GetDirectoryName(session.SavePath);
178+
if (!string.IsNullOrWhiteSpace(directory))
179+
{
180+
Directory.CreateDirectory(directory);
181+
}
182+
183+
return new FileStream(
184+
session.SavePath,
185+
FileMode.Create,
186+
FileAccess.Write,
187+
FileShare.None,
188+
64 * 1024,
189+
useAsync: true);
190+
}
191+
192+
private static long GetStreamLengthForLog(Stream stream, long fallback)
193+
{
194+
return stream.CanSeek ? stream.Length : fallback;
195+
}
196+
153197
private async ValueTask DrainPayloadAsync(
154198
FileChatMessage message,
155199
PipeReader payload,

Core/Services/DeviceCommunication/Application/IMessageAppService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ public interface IMessageAppService
1515
ValueTask SendFileChatAsync(string deviceId, FileChatMessage message, Stream stream, CancellationToken cancellationToken = default);
1616
ValueTask SendImageChatAsync(string deviceId, ImageChatMessage message, Stream stream, CancellationToken cancellationToken = default);
1717
ValueTask AcceptFileAsync(string deviceId, Guid transferId, string savePath, CancellationToken cancellationToken = default);
18+
ValueTask AcceptFileAsync(
19+
string deviceId,
20+
Guid transferId,
21+
string saveTarget,
22+
Func<CancellationToken, ValueTask<Stream>> openWriteStreamAsync,
23+
CancellationToken cancellationToken = default);
1824
ValueTask RejectFileAsync(string deviceId, Guid transferId, string reason, CancellationToken cancellationToken = default);
1925
ValueTask CancelTransferAsync(string deviceId, Guid transferId, string reason, CancellationToken cancellationToken = default);
2026
ValueTask SendClipboardTextAsync(string deviceId, TextClipboardMessage message, CancellationToken cancellationToken = default);

Core/Services/DeviceCommunication/Application/MessageAppService.cs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,50 @@ public ValueTask AcceptFileAsync(
108108
throw new InvalidOperationException("invalid_save_path");
109109
}
110110

111-
var fileName = Path.GetFileName(savePath);
111+
return AcceptFileCoreAsync(
112+
deviceId,
113+
transferId,
114+
savePath,
115+
OpenLocalFileWriteStreamAsync,
116+
cancellationToken);
117+
}
118+
119+
public ValueTask AcceptFileAsync(
120+
string deviceId,
121+
Guid transferId,
122+
string saveTarget,
123+
Func<CancellationToken, ValueTask<Stream>> openWriteStreamAsync,
124+
CancellationToken cancellationToken = default) {
125+
if (string.IsNullOrWhiteSpace(saveTarget)) {
126+
throw new InvalidOperationException("invalid_save_path");
127+
}
128+
129+
ArgumentNullException.ThrowIfNull(openWriteStreamAsync);
130+
131+
return AcceptFileCoreAsync(
132+
deviceId,
133+
transferId,
134+
saveTarget,
135+
(_, token) => openWriteStreamAsync(token),
136+
cancellationToken);
137+
}
138+
139+
private ValueTask AcceptFileCoreAsync(
140+
string deviceId,
141+
Guid transferId,
142+
string saveTarget,
143+
Func<string, CancellationToken, ValueTask<Stream>> openWriteStreamAsync,
144+
CancellationToken cancellationToken) {
145+
var fileName = Path.GetFileName(saveTarget);
112146
var session = new FileTransferSession {
113147
ConversationId = deviceId,
114148
TransferId = transferId,
115149
FileName = string.IsNullOrWhiteSpace(fileName) ? transferId.ToString("D") : fileName,
116150
SizeBytes = 0,
117151
ContentType = "application/octet-stream",
118152
State = FileTransferState.Accepted,
119-
SavePath = savePath
153+
SavePath = saveTarget,
154+
OpenWriteStreamAsync = token => openWriteStreamAsync(saveTarget, token)
120155
};
121156

122157
if (!_fileTransferSessionStore.TryAdd(session)) {
@@ -128,6 +163,22 @@ public ValueTask AcceptFileAsync(
128163
return SendCoreAsync(deviceId, message, cancellationToken);
129164
}
130165

166+
private static ValueTask<Stream> OpenLocalFileWriteStreamAsync(string path, CancellationToken cancellationToken) {
167+
_ = cancellationToken;
168+
var directory = Path.GetDirectoryName(path);
169+
if (!string.IsNullOrWhiteSpace(directory)) {
170+
Directory.CreateDirectory(directory);
171+
}
172+
173+
return new ValueTask<Stream>(new FileStream(
174+
path,
175+
FileMode.Create,
176+
FileAccess.Write,
177+
FileShare.None,
178+
64 * 1024,
179+
useAsync: true));
180+
}
181+
131182
public ValueTask RejectFileAsync(
132183
string deviceId,
133184
Guid transferId,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace Core.Services.DeviceCommunication.Platform;
8+
9+
public readonly record struct ChatDisplayContext(bool IsMainWindowActive, bool IsChatPageOpen);
10+
11+
public sealed record ChatFileSaveTarget(
12+
string DisplayPath,
13+
string? LocalPath,
14+
Func<CancellationToken, ValueTask<Stream>> OpenWriteAsync)
15+
{
16+
public static ChatFileSaveTarget FromLocalPath(string path)
17+
{
18+
ArgumentException.ThrowIfNullOrWhiteSpace(path);
19+
20+
return new ChatFileSaveTarget(
21+
path,
22+
path,
23+
_ => new ValueTask<Stream>(new FileStream(
24+
path,
25+
FileMode.Create,
26+
FileAccess.Write,
27+
FileShare.None,
28+
64 * 1024,
29+
useAsync: true)));
30+
}
31+
}
32+
33+
public interface IChatPlatformService
34+
{
35+
Task<IReadOnlyList<string>> PickFilesToSendAsync();
36+
Task<ChatFileSaveTarget?> PickSaveTargetAsync(string suggestedFileName);
37+
bool CanOpenFile { get; }
38+
void OpenFile(string path);
39+
Task CopyTextToClipboardAsync(string text);
40+
Task<string?> PromptTextAsync(string title, string prompt, string? initialValue);
41+
ChatDisplayContext GetDisplayContext(string? selectedConversationId);
42+
}

Core/Services/DeviceCommunication/Protocol/ProtocolSession.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Net;
33
using System.Text.Json;
44
using Core.Services.DeviceCommunication.Routing;
5+
using Kitopia.DeviceCommunication.Diagnostics;
56
using SharedProtocolFrame = Kitopia.DeviceCommunication.Protocol.ProtocolFrame;
67
using SharedProtocolFrameHeader = Kitopia.DeviceCommunication.Protocol.ProtocolFrameHeader;
78
using SharedLocalDataPipeIo = Kitopia.DeviceCommunication.Transport.LocalDataPipeIo;
@@ -58,6 +59,8 @@ public async ValueTask HandleAsync(
5859
}
5960

6061
var scopedPayloadReader = SharedProtocolFrame.CreatePayloadReader(payloadReader, header.PayloadLength);
62+
DeviceCommunicationDiagnostics.Debug("ProtocolSession",
63+
$"frame Route={envelope.Route} Command={envelope.Command} ChannelId={envelope.ChannelId} EnvLen={header.EnvelopeLength} PayloadLen={header.PayloadLength}");
6164
var context = new MessageContext(protocol, remoteEndPoint, string.Empty);
6265
await _dispatcher.DispatchAsync(context, envelope, scopedPayloadReader, cancellationToken);
6366
}

Core/Services/DeviceCommunication/Sessions/FileTransferSession.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ public sealed class FileTransferSession
1919
public FileTransferState State { get; set; }
2020
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
2121
public string? SavePath { get; set; }
22+
public Func<CancellationToken, ValueTask<Stream>>? OpenWriteStreamAsync { get; set; }
2223
}

0 commit comments

Comments
 (0)