feat: add batched Rust-backed upload task
CI / format (push) Has been cancelled
CI / test (push) Has been cancelled
CI / packaging (push) Has been cancelled

This commit is contained in:
Tien Do Nam
2026-07-15 18:18:58 +02:00
parent 3bb7627646
commit e9acf98be3
15 changed files with 722 additions and 129 deletions
@@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:localsend_app/isolate/isolate.dart';
@@ -6,8 +5,8 @@ import 'package:localsend_app/isolate/model/device.dart';
import 'package:localsend_app/isolate/src/isolate/child/main.dart';
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
import 'package:localsend_app/isolate/src/task/upload/http_upload.dart';
import 'package:localsend_app/isolate/util/stream.dart';
import 'package:localsend_app/rust/api/http.dart';
import 'package:localsend_app/util/native/channel/android_channel.dart';
import 'package:localsend_app/util/rust.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:typed_isolates/typed_isolates.dart';
@@ -42,6 +41,39 @@ class HttpUploadTask implements BaseHttpUploadTask {
});
}
class HttpUploadFile {
final String remoteFileToken;
final String fileId;
final String? filePath;
final List<int>? fileBytes;
final int fileSize;
HttpUploadFile({
required this.remoteFileToken,
required this.fileId,
required this.filePath,
required this.fileBytes,
required this.fileSize,
});
}
/// Uploads a list of files as one isolate task.
///
/// This task is intended to replace the file scheduling loop in the parent
/// isolate. Files are uploaded sequentially and progress is reported across
/// the complete list.
class HttpUploadFilesTask implements BaseHttpUploadTask {
final String? remoteSessionId;
final List<HttpUploadFile> files;
final Device device;
HttpUploadFilesTask({
required this.remoteSessionId,
required this.files,
required this.device,
});
}
class HttpUploadCancelTask implements BaseHttpUploadTask {
final int taskId;
@@ -61,8 +93,6 @@ abstract class UriContentStreamResolver {
Stream<Uint8List> resolve(Uri uri);
}
UriContentStreamResolver? _uriContentStreamResolver;
Future<void> setupHttpUploadIsolate(
Stream<SendToIsolateData<IsolateTask<BaseHttpUploadTask>>> receiveFromMain,
void Function(IsolateTaskStreamResult<double>) sendToMain,
@@ -74,17 +104,33 @@ Future<void> setupHttpUploadIsolate(
sendToMain: sendToMain,
initialData: initialData,
handler: (ref, task) async {
final HttpUploadTask uploadTask;
final String? remoteSessionId;
final List<HttpUploadFile> files;
final Device device;
switch (task.data) {
case HttpUploadSetContentStreamResolverTask task:
final rootIsolateToken = ref.read(syncProvider).rootIsolateToken;
task.resolver.init(
rootIsolateToken: rootIsolateToken,
);
_uriContentStreamResolver = task.resolver;
return;
case HttpUploadTask task:
uploadTask = task;
remoteSessionId = task.remoteSessionId;
files = [
HttpUploadFile(
remoteFileToken: task.remoteFileToken,
fileId: task.fileId,
filePath: task.filePath,
fileBytes: task.fileBytes,
fileSize: task.fileSize,
),
];
device = task.device;
break;
case HttpUploadFilesTask task:
remoteSessionId = task.remoteSessionId;
files = task.files;
device = task.device;
break;
case HttpUploadCancelTask task:
final cancelToken = ref.read(_cancelTokenProvider)[task.taskId];
@@ -93,37 +139,42 @@ Future<void> setupHttpUploadIsolate(
return;
}
final Stream<List<int>>? fileStream = uploadTask.filePath != null
? _uriContentStreamResolver != null && uploadTask.filePath!.startsWith('content://')
? _uriContentStreamResolver!.resolve(Uri.parse(uploadTask.filePath!))
: File(uploadTask.filePath!).openRead()
: null;
final (streamController, subscription) = fileStream?.digested() ?? (null, null);
try {
final cancelToken = createCancellationToken();
ref.read(_cancelTokenProvider).putIfAbsent(task.id, () => cancelToken);
final totalSize = files.fold<int>(0, (sum, file) => sum + file.fileSize);
var completedSize = 0;
await ref
.read(httpUploadProvider)
.upload(
stream: streamController?.stream ?? Stream.fromIterable([uploadTask.fileBytes!]),
contentLength: uploadTask.fileSize,
target: uploadTask.device,
remoteSessionId: uploadTask.remoteSessionId,
fileId: uploadTask.fileId,
token: uploadTask.remoteFileToken,
onSendProgress: (progress) {
sendToMain(
IsolateTaskStreamResult.event(
id: task.id,
data: progress,
),
);
},
cancelToken: cancelToken,
);
for (final file in files) {
final filePath = file.filePath;
final isContentUri = filePath?.startsWith('content://') ?? false;
final fileDescriptor = isContentUri ? await getFileDescriptorAndroid(uri: filePath!) : null;
await ref
.read(httpUploadProvider)
.upload(
stream: filePath == null && file.fileBytes != null ? Stream.value(file.fileBytes!) : null,
path: !isContentUri ? filePath : null,
fileDescriptor: fileDescriptor,
contentLength: file.fileSize,
target: device,
remoteSessionId: remoteSessionId,
fileId: file.fileId,
token: file.remoteFileToken,
onSendProgress: (progress) {
final totalProgress = totalSize == 0 ? 1.0 : (completedSize + progress * file.fileSize) / totalSize;
sendToMain(
IsolateTaskStreamResult.event(
id: task.id,
data: totalProgress,
),
);
},
cancelToken: cancelToken,
);
completedSize += file.fileSize;
}
sendToMain(
IsolateTaskStreamResult.done(
@@ -138,13 +189,7 @@ Future<void> setupHttpUploadIsolate(
),
);
} finally {
// Close the stream if it is still open
// ignore: unawaited_futures
streamController?.close();
// Cancel the subscription if it is still open
// ignore: unawaited_futures
subscription?.cancel();
ref.read(_cancelTokenProvider).remove(task.id);
}
},
);
@@ -172,6 +172,42 @@ class IsolateHttpUploadAction extends ReduxActionWithResult<IsolateController, P
}
}
class IsolateHttpUploadFilesAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, IsolateHttpUploadActionResult> {
final int isolateIndex;
final String? remoteSessionId;
final List<HttpUploadFile> files;
final Device device;
IsolateHttpUploadFilesAction({
required this.isolateIndex,
required this.remoteSessionId,
required this.files,
required this.device,
});
@override
(ParentIsolateState, IsolateHttpUploadActionResult) reduce() {
final connection = state.httpUpload[isolateIndex];
final taskId = IdProvider.instance.getNextId();
final progress = connection.sendWrappedTaskAndListenStream(
task: HttpUploadFilesTask(
remoteSessionId: remoteSessionId,
files: files,
device: device,
),
taskId: taskId,
);
return (
state,
IsolateHttpUploadActionResult(
taskId: taskId,
progress: progress,
),
);
}
}
class IsolateHttpUploadCancelAction extends ReduxAction<IsolateController, ParentIsolateState> {
final int isolateIndex;
final int taskId;
@@ -16,7 +16,9 @@ class HttpUploadService {
HttpUploadService(this._client);
Future<void> upload({
required Stream<List<int>> stream,
required Stream<List<int>>? stream,
required String? path,
required int? fileDescriptor,
required int contentLength,
required Device target,
required String? remoteSessionId,
@@ -25,25 +27,29 @@ class HttpUploadService {
required void Function(double progress) onSendProgress,
required RsCancellationToken cancelToken,
}) async {
final (sink, receiver) = await createStream();
final (sink, receiver) = stream != null ? await createStream() : (null, null);
final uploadFuture = _client.upload(
protocol: target.getProtocolType(),
ip: target.ip!,
port: target.port,
publicKey: null,
sessionId: remoteSessionId ?? '',
fileId: fileId,
token: token,
binary: receiver,
cancelToken: cancelToken,
);
final uploadFuture = _client
.upload(
protocol: target.getProtocolType(),
ip: target.ip!,
port: target.port,
publicKey: null,
sessionId: remoteSessionId ?? '',
fileId: fileId,
token: token,
binary: receiver,
path: path,
fileDescriptor: fileDescriptor,
contentLength: BigInt.from(contentLength),
cancelToken: cancelToken,
)
.forEach(onSendProgress);
try {
int sent = 0;
await for (final chunk in stream) {
await for (final chunk in stream ?? const Stream<List<int>>.empty()) {
try {
await sink.add(data: chunk);
await sink!.add(data: chunk);
} catch (_) {
// The Rust side dropped the receiver, i.e. the upload request already
// ended (e.g. rejected by the receiver or cancelled).
@@ -51,10 +57,8 @@ class HttpUploadService {
await uploadFuture;
rethrow;
}
sent += chunk.length;
onSendProgress(sent / contentLength);
}
sink.close();
sink?.close();
} catch (e) {
// The source stream failed, so the upload request must be aborted.
// [e] is the root cause, thus the error of the upload request is swallowed.
+6 -2
View File
@@ -11,6 +11,7 @@ import 'package:localsend_app/rust/frb_generated.dart';
part 'http.freezed.dart';
// These functions are ignored because they are not marked as `pub`: `resolve_file_content`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `from`
RsHttpClient createClient({
@@ -57,7 +58,7 @@ abstract class RsHttpClient implements RustOpaqueInterface {
required RegisterDto payload,
});
Future<void> upload({
Stream<double> upload({
required ProtocolType protocol,
required String ip,
required int port,
@@ -65,7 +66,10 @@ abstract class RsHttpClient implements RustOpaqueInterface {
required String sessionId,
required String fileId,
required String token,
required Dart2RustStreamReceiver binary,
Dart2RustStreamReceiver? binary,
String? path,
int? fileDescriptor,
required BigInt contentLength,
required RsCancellationToken cancelToken,
});
}
+1 -1
View File
@@ -8,7 +8,7 @@ import 'package:localsend_app/rust/frb_generated.dart';
Future<(Dart2RustStreamSink, Dart2RustStreamReceiver)> createStream() => RustLib.instance.api.crateApiStreamCreateStream();
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamReceiver>>
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner< Dart2RustStreamReceiver>>
abstract class Dart2RustStreamReceiver implements RustOpaqueInterface {}
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamSink>>
+249 -50
View File
@@ -145,7 +145,7 @@ abstract class RustLibApi extends BaseApi {
required RegisterDto payload,
});
Future<void> crateApiHttpRsHttpClientUpload({
Stream<double> crateApiHttpRsHttpClientUpload({
required RsHttpClient that,
required ProtocolType protocol,
required String ip,
@@ -154,7 +154,10 @@ abstract class RustLibApi extends BaseApi {
required String sessionId,
required String fileId,
required String token,
required Dart2RustStreamReceiver binary,
Dart2RustStreamReceiver? binary,
String? path,
int? fileDescriptor,
required BigInt contentLength,
required RsCancellationToken cancelToken,
});
@@ -725,7 +728,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
@override
Future<void> crateApiHttpRsHttpClientUpload({
Stream<double> crateApiHttpRsHttpClientUpload({
required RsHttpClient that,
required ProtocolType protocol,
required String ip,
@@ -734,65 +737,81 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required String sessionId,
required String fileId,
required String token,
required Dart2RustStreamReceiver binary,
Dart2RustStreamReceiver? binary,
String? path,
int? fileDescriptor,
required BigInt contentLength,
required RsCancellationToken cancelToken,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(
final sink = RustStreamSink<double>();
unawaited(
handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(
that,
serializer,
);
sse_encode_StreamSink_f_64_Sse(sink, serializer);
sse_encode_protocol_type(protocol, serializer);
sse_encode_String(ip, serializer);
sse_encode_u_16(port, serializer);
sse_encode_opt_String(publicKey, serializer);
sse_encode_String(sessionId, serializer);
sse_encode_String(fileId, serializer);
sse_encode_String(token, serializer);
sse_encode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
binary,
serializer,
);
sse_encode_opt_String(path, serializer);
sse_encode_opt_box_autoadd_i_32(fileDescriptor, serializer);
sse_encode_u_64(contentLength, serializer);
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(
cancelToken,
serializer,
);
pdeCallFfi(
generalizedFrbRustBinding,
serializer,
funcId: 10,
port: port_,
);
},
codec: SseCodec(
decodeSuccessData: sse_decode_unit,
decodeErrorData: sse_decode_rs_http_client_error,
),
constMeta: kCrateApiHttpRsHttpClientUploadConstMeta,
argValues: [
that,
serializer,
);
sse_encode_protocol_type(protocol, serializer);
sse_encode_String(ip, serializer);
sse_encode_u_16(port, serializer);
sse_encode_opt_String(publicKey, serializer);
sse_encode_String(sessionId, serializer);
sse_encode_String(fileId, serializer);
sse_encode_String(token, serializer);
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
sink,
protocol,
ip,
port,
publicKey,
sessionId,
fileId,
token,
binary,
serializer,
);
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(
path,
fileDescriptor,
contentLength,
cancelToken,
serializer,
);
pdeCallFfi(
generalizedFrbRustBinding,
serializer,
funcId: 10,
port: port_,
);
},
codec: SseCodec(
decodeSuccessData: sse_decode_unit,
decodeErrorData: sse_decode_rs_http_client_error,
],
apiImpl: this,
),
constMeta: kCrateApiHttpRsHttpClientUploadConstMeta,
argValues: [
that,
protocol,
ip,
port,
publicKey,
sessionId,
fileId,
token,
binary,
cancelToken,
],
apiImpl: this,
),
);
return sink.stream;
}
TaskConstMeta get kCrateApiHttpRsHttpClientUploadConstMeta => const TaskConstMeta(
debugName: 'RsHttpClient_upload',
argNames: [
'that',
'sink',
'protocol',
'ip',
'port',
@@ -801,6 +820,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
'fileId',
'token',
'binary',
'path',
'fileDescriptor',
'contentLength',
'cancelToken',
],
);
@@ -2001,6 +2023,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
throw UnimplementedError();
}
@protected
RustStreamSink<double> dco_decode_StreamSink_f_64_Sse(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError();
}
@protected
RustStreamSink<Uint8List> dco_decode_StreamSink_list_prim_u_8_strict_Sse(
dynamic raw,
@@ -2049,6 +2077,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as bool;
}
@protected
Dart2RustStreamReceiver dco_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
raw,
);
}
@protected
ClientInfo dco_decode_box_autoadd_client_info(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2081,6 +2119,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_file_metadata(raw);
}
@protected
int dco_decode_box_autoadd_i_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as int;
}
@protected
PinConfig dco_decode_box_autoadd_pin_config(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2183,6 +2227,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
@protected
double dco_decode_f_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as double;
}
@protected
FileDto dco_decode_file_dto(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2287,6 +2337,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_String(raw);
}
@protected
Dart2RustStreamReceiver?
dco_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null
? null
: dco_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
raw,
);
}
@protected
DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2307,6 +2370,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_box_autoadd_file_metadata(raw);
}
@protected
int? dco_decode_opt_box_autoadd_i_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_box_autoadd_i_32(raw);
}
@protected
PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2987,6 +3056,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
throw UnimplementedError('Unreachable ()');
}
@protected
RustStreamSink<double> sse_decode_StreamSink_f_64_Sse(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
throw UnimplementedError('Unreachable ()');
}
@protected
RustStreamSink<Uint8List> sse_decode_StreamSink_list_prim_u_8_strict_Sse(
SseDeserializer deserializer,
@@ -3039,6 +3116,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getUint8() != 0;
}
@protected
Dart2RustStreamReceiver sse_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
deserializer,
));
}
@protected
ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -3075,6 +3162,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_file_metadata(deserializer));
}
@protected
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_i_32(deserializer));
}
@protected
PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -3190,6 +3283,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return ExpectingPublicKey(publicKey: var_publicKey, kind: var_kind);
}
@protected
double sse_decode_f_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getFloat64();
}
@protected
FileDto sse_decode_file_dto(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -3337,6 +3436,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
Dart2RustStreamReceiver?
sse_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
deserializer,
));
} else {
return null;
}
}
@protected
DeviceType? sse_decode_opt_box_autoadd_device_type(
SseDeserializer deserializer,
@@ -3376,6 +3491,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_i_32(deserializer));
} else {
return null;
}
}
@protected
PinConfig? sse_decode_opt_box_autoadd_pin_config(
SseDeserializer deserializer,
@@ -4152,6 +4278,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
@protected
void sse_encode_StreamSink_f_64_Sse(
RustStreamSink<double> self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(
self.setupAndSerialize(
codec: SseCodec(
decodeSuccessData: sse_decode_f_64,
decodeErrorData: sse_decode_AnyhowException,
),
),
serializer,
);
}
@protected
void sse_encode_StreamSink_list_prim_u_8_strict_Sse(
RustStreamSink<Uint8List> self,
@@ -4238,6 +4381,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putUint8(self ? 1 : 0);
}
@protected
void sse_encode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
self,
serializer,
);
}
@protected
void sse_encode_box_autoadd_client_info(
ClientInfo self,
@@ -4283,6 +4438,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_file_metadata(self, serializer);
}
@protected
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self, serializer);
}
@protected
void sse_encode_box_autoadd_pin_config(
PinConfig self,
@@ -4392,6 +4553,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.kind, serializer);
}
@protected
void sse_encode_f_64(double self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putFloat64(self);
}
@protected
void sse_encode_file_dto(FileDto self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4525,6 +4692,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver? self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
self,
serializer,
);
}
}
@protected
void sse_encode_opt_box_autoadd_device_type(
DeviceType? self,
@@ -4564,6 +4747,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_box_autoadd_i_32(self, serializer);
}
}
@protected
void sse_encode_opt_box_autoadd_pin_config(
PinConfig? self,
@@ -5057,7 +5250,7 @@ class RsHttpClientImpl extends RustOpaque implements RsHttpClient {
payload: payload,
);
Future<void> upload({
Stream<double> upload({
required ProtocolType protocol,
required String ip,
required int port,
@@ -5065,7 +5258,10 @@ class RsHttpClientImpl extends RustOpaque implements RsHttpClient {
required String sessionId,
required String fileId,
required String token,
required Dart2RustStreamReceiver binary,
Dart2RustStreamReceiver? binary,
String? path,
int? fileDescriptor,
required BigInt contentLength,
required RsCancellationToken cancelToken,
}) => RustLib.instance.api.crateApiHttpRsHttpClientUpload(
that: this,
@@ -5077,6 +5273,9 @@ class RsHttpClientImpl extends RustOpaque implements RsHttpClient {
fileId: fileId,
token: token,
binary: binary,
path: path,
fileDescriptor: fileDescriptor,
contentLength: contentLength,
cancelToken: cancelToken,
);
}
+75
View File
@@ -213,6 +213,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
dynamic raw,
);
@protected
RustStreamSink<double> dco_decode_StreamSink_f_64_Sse(dynamic raw);
@protected
RustStreamSink<Uint8List> dco_decode_StreamSink_list_prim_u_8_strict_Sse(
dynamic raw,
@@ -240,6 +243,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool dco_decode_bool(dynamic raw);
@protected
Dart2RustStreamReceiver dco_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
);
@protected
ClientInfo dco_decode_box_autoadd_client_info(dynamic raw);
@@ -257,6 +265,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
FileMetadata dco_decode_box_autoadd_file_metadata(dynamic raw);
@protected
int dco_decode_box_autoadd_i_32(dynamic raw);
@protected
PinConfig dco_decode_box_autoadd_pin_config(dynamic raw);
@@ -299,6 +310,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
ExpectingPublicKey dco_decode_expecting_public_key(dynamic raw);
@protected
double dco_decode_f_64(dynamic raw);
@protected
FileDto dco_decode_file_dto(dynamic raw);
@@ -341,6 +355,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
Dart2RustStreamReceiver?
dco_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
);
@protected
DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw);
@@ -352,6 +372,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
FileMetadata? dco_decode_opt_box_autoadd_file_metadata(dynamic raw);
@protected
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
@protected
PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw);
@@ -595,6 +618,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
RustStreamSink<double> sse_decode_StreamSink_f_64_Sse(
SseDeserializer deserializer,
);
@protected
RustStreamSink<Uint8List> sse_decode_StreamSink_list_prim_u_8_strict_Sse(
SseDeserializer deserializer,
@@ -624,6 +652,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
Dart2RustStreamReceiver sse_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
);
@protected
ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer);
@@ -645,6 +678,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
@protected
PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer);
@@ -695,6 +731,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
double sse_decode_f_64(SseDeserializer deserializer);
@protected
FileDto sse_decode_file_dto(SseDeserializer deserializer);
@@ -743,6 +782,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
Dart2RustStreamReceiver?
sse_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
);
@protected
DeviceType? sse_decode_opt_box_autoadd_device_type(
SseDeserializer deserializer,
@@ -758,6 +803,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
@protected
PinConfig? sse_decode_opt_box_autoadd_pin_config(
SseDeserializer deserializer,
@@ -1067,6 +1115,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_StreamSink_f_64_Sse(
RustStreamSink<double> self,
SseSerializer serializer,
);
@protected
void sse_encode_StreamSink_list_prim_u_8_strict_Sse(
RustStreamSink<Uint8List> self,
@@ -1100,6 +1154,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_client_info(
ClientInfo self,
@@ -1130,6 +1190,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_pin_config(
PinConfig self,
@@ -1193,6 +1256,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_f_64(double self, SseSerializer serializer);
@protected
void sse_encode_file_dto(FileDto self, SseSerializer serializer);
@@ -1250,6 +1316,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver? self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_box_autoadd_device_type(
DeviceType? self,
@@ -1268,6 +1340,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_pin_config(
PinConfig? self,
+75
View File
@@ -215,6 +215,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
dynamic raw,
);
@protected
RustStreamSink<double> dco_decode_StreamSink_f_64_Sse(dynamic raw);
@protected
RustStreamSink<Uint8List> dco_decode_StreamSink_list_prim_u_8_strict_Sse(
dynamic raw,
@@ -242,6 +245,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool dco_decode_bool(dynamic raw);
@protected
Dart2RustStreamReceiver dco_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
);
@protected
ClientInfo dco_decode_box_autoadd_client_info(dynamic raw);
@@ -259,6 +267,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
FileMetadata dco_decode_box_autoadd_file_metadata(dynamic raw);
@protected
int dco_decode_box_autoadd_i_32(dynamic raw);
@protected
PinConfig dco_decode_box_autoadd_pin_config(dynamic raw);
@@ -301,6 +312,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
ExpectingPublicKey dco_decode_expecting_public_key(dynamic raw);
@protected
double dco_decode_f_64(dynamic raw);
@protected
FileDto dco_decode_file_dto(dynamic raw);
@@ -343,6 +357,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
Dart2RustStreamReceiver?
dco_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
dynamic raw,
);
@protected
DeviceType? dco_decode_opt_box_autoadd_device_type(dynamic raw);
@@ -354,6 +374,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
FileMetadata? dco_decode_opt_box_autoadd_file_metadata(dynamic raw);
@protected
int? dco_decode_opt_box_autoadd_i_32(dynamic raw);
@protected
PinConfig? dco_decode_opt_box_autoadd_pin_config(dynamic raw);
@@ -597,6 +620,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
RustStreamSink<double> sse_decode_StreamSink_f_64_Sse(
SseDeserializer deserializer,
);
@protected
RustStreamSink<Uint8List> sse_decode_StreamSink_list_prim_u_8_strict_Sse(
SseDeserializer deserializer,
@@ -626,6 +654,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
Dart2RustStreamReceiver sse_decode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
);
@protected
ClientInfo sse_decode_box_autoadd_client_info(SseDeserializer deserializer);
@@ -647,6 +680,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
int sse_decode_box_autoadd_i_32(SseDeserializer deserializer);
@protected
PinConfig sse_decode_box_autoadd_pin_config(SseDeserializer deserializer);
@@ -697,6 +733,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
double sse_decode_f_64(SseDeserializer deserializer);
@protected
FileDto sse_decode_file_dto(SseDeserializer deserializer);
@@ -745,6 +784,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
Dart2RustStreamReceiver?
sse_decode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
SseDeserializer deserializer,
);
@protected
DeviceType? sse_decode_opt_box_autoadd_device_type(
SseDeserializer deserializer,
@@ -760,6 +805,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer,
);
@protected
int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer);
@protected
PinConfig? sse_decode_opt_box_autoadd_pin_config(
SseDeserializer deserializer,
@@ -1069,6 +1117,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_StreamSink_f_64_Sse(
RustStreamSink<double> self,
SseSerializer serializer,
);
@protected
void sse_encode_StreamSink_list_prim_u_8_strict_Sse(
RustStreamSink<Uint8List> self,
@@ -1102,6 +1156,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_client_info(
ClientInfo self,
@@ -1132,6 +1192,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_pin_config(
PinConfig self,
@@ -1195,6 +1258,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_f_64(double self, SseSerializer serializer);
@protected
void sse_encode_file_dto(FileDto self, SseSerializer serializer);
@@ -1252,6 +1318,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
Dart2RustStreamReceiver? self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_box_autoadd_device_type(
DeviceType? self,
@@ -1270,6 +1342,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_pin_config(
PinConfig? self,
+47 -2
View File
@@ -1,4 +1,5 @@
use crate::api::stream;
use crate::frb_generated::StreamSink;
use flutter_rust_bridge::frb;
pub use localsend::http::client::{ClientError, LsHttpClientVersion};
pub use localsend::http::dto::{
@@ -86,6 +87,7 @@ impl RsHttpClient {
pub async fn upload(
&self,
sink: StreamSink<f64>,
protocol: ProtocolType,
ip: &str,
port: u16,
@@ -93,9 +95,22 @@ impl RsHttpClient {
session_id: &str,
file_id: &str,
token: &str,
binary: stream::Dart2RustStreamReceiver,
binary: Option<stream::Dart2RustStreamReceiver>,
path: Option<String>,
file_descriptor: Option<i32>,
content_length: u64,
cancel_token: &RsCancellationToken,
) -> Result<(), RsHttpClientError> {
let content = resolve_file_content(binary, path, file_descriptor)?;
let progress = move |sent| {
let progress = if content_length == 0 {
1.0
} else {
(sent as f64 / content_length as f64).min(1.0)
};
let _ = sink.add(progress);
};
self.inner
.upload(
protocol,
@@ -105,7 +120,8 @@ impl RsHttpClient {
session_id,
file_id,
token,
localsend::model::transfer::FileContent::Stream(binary.receiver),
content,
progress,
cancel_token.inner.clone(),
)
.await
@@ -130,6 +146,35 @@ impl RsHttpClient {
}
}
fn resolve_file_content(
binary: Option<stream::Dart2RustStreamReceiver>,
path: Option<String>,
file_descriptor: Option<i32>,
) -> Result<localsend::model::transfer::FileContent, RsHttpClientError> {
match (binary, path, file_descriptor) {
(Some(binary), None, None) => Ok(localsend::model::transfer::FileContent::Stream(
binary.receiver,
)),
(None, Some(path), None) => Ok(localsend::model::transfer::FileContent::Path(path.into())),
(None, None, Some(file_descriptor)) => {
#[cfg(target_os = "android")]
{
Ok(localsend::model::transfer::FileContent::Fd(file_descriptor))
}
#[cfg(not(target_os = "android"))]
{
let _ = file_descriptor;
Err(RsHttpClientError::Other(
"File descriptors are only supported on Android".into(),
))
}
}
_ => Err(RsHttpClientError::Other(
"Exactly one upload content source must be provided".into(),
)),
}
}
pub enum RsHttpClientError {
StatusCode {
status: u16,
+87 -5
View File
@@ -633,6 +633,10 @@ fn wire__crate__api__http__RsHttpClient_upload_impl(
let api_that = <RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsHttpClient>,
>>::sse_decode(&mut deserializer);
let api_sink =
<StreamSink<f64, flutter_rust_bridge::for_generated::SseCodec>>::sse_decode(
&mut deserializer,
);
let api_protocol = <crate::api::model::ProtocolType>::sse_decode(&mut deserializer);
let api_ip = <String>::sse_decode(&mut deserializer);
let api_port = <u16>::sse_decode(&mut deserializer);
@@ -640,7 +644,10 @@ fn wire__crate__api__http__RsHttpClient_upload_impl(
let api_session_id = <String>::sse_decode(&mut deserializer);
let api_file_id = <String>::sse_decode(&mut deserializer);
let api_token = <String>::sse_decode(&mut deserializer);
let api_binary = <Dart2RustStreamReceiver>::sse_decode(&mut deserializer);
let api_binary = <Option<Dart2RustStreamReceiver>>::sse_decode(&mut deserializer);
let api_path = <Option<String>>::sse_decode(&mut deserializer);
let api_file_descriptor = <Option<i32>>::sse_decode(&mut deserializer);
let api_content_length = <u64>::sse_decode(&mut deserializer);
let api_cancel_token = <RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsCancellationToken>,
>>::sse_decode(&mut deserializer);
@@ -680,6 +687,7 @@ fn wire__crate__api__http__RsHttpClient_upload_impl(
let api_cancel_token_guard = api_cancel_token_guard.unwrap();
let output_ok = crate::api::http::RsHttpClient::upload(
&*api_that_guard,
api_sink,
api_protocol,
&api_ip,
api_port,
@@ -688,6 +696,9 @@ fn wire__crate__api__http__RsHttpClient_upload_impl(
&api_file_id,
&api_token,
api_binary,
api_path,
api_file_descriptor,
api_content_length,
&*api_cancel_token_guard,
)
.await?;
@@ -2323,6 +2334,14 @@ impl SseDecode for StreamSink<RTCFileReceiver, flutter_rust_bridge::for_generate
}
}
impl SseDecode for StreamSink<f64, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <String>::sse_decode(deserializer);
return StreamSink::deserialize(inner);
}
}
impl SseDecode for StreamSink<Vec<u8>, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -2452,6 +2471,13 @@ impl SseDecode for crate::api::webrtc::ExpectingPublicKey {
}
}
impl SseDecode for f64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
deserializer.cursor.read_f64::<NativeEndian>().unwrap()
}
}
impl SseDecode for crate::api::model::FileDto {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -2609,6 +2635,17 @@ impl SseDecode for Option<String> {
}
}
impl SseDecode for Option<Dart2RustStreamReceiver> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<Dart2RustStreamReceiver>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for Option<crate::api::model::DeviceType> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -2644,6 +2681,17 @@ impl SseDecode for Option<crate::api::model::FileMetadata> {
}
}
impl SseDecode for Option<i32> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<i32>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for Option<crate::api::webrtc::PinConfig> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -4147,6 +4195,13 @@ impl SseEncode for StreamSink<RTCFileReceiver, flutter_rust_bridge::for_generate
}
}
impl SseEncode for StreamSink<f64, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
unimplemented!("")
}
}
impl SseEncode for StreamSink<Vec<u8>, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -4255,6 +4310,13 @@ impl SseEncode for crate::api::webrtc::ExpectingPublicKey {
}
}
impl SseEncode for f64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
serializer.cursor.write_f64::<NativeEndian>(self).unwrap();
}
}
impl SseEncode for crate::api::model::FileDto {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -4387,6 +4449,16 @@ impl SseEncode for Option<String> {
}
}
impl SseEncode for Option<Dart2RustStreamReceiver> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<Dart2RustStreamReceiver>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<crate::api::model::DeviceType> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -4417,6 +4489,16 @@ impl SseEncode for Option<crate::api::model::FileMetadata> {
}
}
impl SseEncode for Option<i32> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<i32>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<crate::api::webrtc::PinConfig> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -4770,14 +4852,14 @@ mod io {
pub extern "C" fn frbgen_localsend_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamReceiver>>::increment_strong_count(ptr as _);
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner< Dart2RustStreamReceiver>>::increment_strong_count(ptr as _);
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_localsend_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamReceiver>>::decrement_strong_count(ptr as _);
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner< Dart2RustStreamReceiver>>::decrement_strong_count(ptr as _);
}
#[unsafe(no_mangle)]
@@ -4923,14 +5005,14 @@ mod web {
pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamReceiver>>::increment_strong_count(ptr as _);
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner< Dart2RustStreamReceiver>>::increment_strong_count(ptr as _);
}
#[wasm_bindgen]
pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamReceiver(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Dart2RustStreamReceiver>>::decrement_strong_count(ptr as _);
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner< Dart2RustStreamReceiver>>::decrement_strong_count(ptr as _);
}
#[wasm_bindgen]
+1 -1
View File
@@ -6,9 +6,9 @@
import 'dart:async' as _i4;
import 'package:flutter/material.dart' as _i8;
import 'package:localsend_app/gen/strings.g.dart' as _i10;
import 'package:localsend_app/isolate/model/device.dart' as _i12;
import 'package:localsend_app/isolate/model/stored_security_context.dart' as _i2;
import 'package:localsend_app/gen/strings.g.dart' as _i10;
import 'package:localsend_app/model/persistence/color_mode.dart' as _i9;
import 'package:localsend_app/model/persistence/favorite_device.dart' as _i6;
import 'package:localsend_app/model/persistence/receive_history_entry.dart' as _i5;
+5 -2
View File
@@ -114,20 +114,23 @@ impl LsHttpClient {
file_id: &str,
token: &str,
content: model::transfer::FileContent,
progress: impl Fn(u64) + Send + 'static,
cancel: tokio_util::sync::CancellationToken,
) -> Result<(), ClientError> {
match self {
LsHttpClient::V2(client) => {
client
.upload(
protocol, ip, port, public_key, session_id, file_id, token, content, cancel,
protocol, ip, port, public_key, session_id, file_id, token, content,
progress, cancel,
)
.await
}
LsHttpClient::V3(client) => {
client
.upload(
protocol, ip, port, public_key, session_id, file_id, token, content, cancel,
protocol, ip, port, public_key, session_id, file_id, token, content,
progress, cancel,
)
.await
}
+8 -1
View File
@@ -200,6 +200,7 @@ impl LsHttpClientV2 {
/// * `file_id` - File ID to upload
/// * `token` - File-specific token from prepare_upload
/// * `content` - The file content to upload (a chunk stream or a raw file descriptor)
/// * `progress` - Called with the cumulative number of bytes read for the upload
/// * `cancel` - Cancellation token; cancelling it aborts the upload with [`ClientError::Cancelled`]
///
/// # Errors
@@ -217,6 +218,7 @@ impl LsHttpClientV2 {
file_id: &str,
token: &str,
content: model::transfer::FileContent,
progress: impl Fn(u64) + Send + 'static,
cancel: CancellationToken,
) -> Result<(), ClientError> {
let url = TargetUrl {
@@ -233,7 +235,12 @@ impl LsHttpClientV2 {
}
.to_string();
let stream = ReceiverStream::new(content.into_receiver()).map(Ok::<Bytes, anyhow::Error>);
let mut sent = 0_u64;
let stream = ReceiverStream::new(content.into_receiver()).map(move |chunk| {
sent += chunk.len() as u64;
progress(sent);
Ok::<Bytes, anyhow::Error>(chunk)
});
let body = reqwest::Body::wrap_stream(stream);
let res = tokio::select! {
+9 -2
View File
@@ -187,6 +187,8 @@ impl LsHttpClientV3 {
/// Uploads a file to the server.
///
/// `progress` is called with the cumulative number of bytes read for the upload.
///
/// `cancel` is a cancellation token; cancelling it aborts the upload with
/// [`ClientError::Cancelled`].
pub async fn upload(
@@ -199,6 +201,7 @@ impl LsHttpClientV3 {
file_id: &str,
token: &str,
content: model::transfer::FileContent,
progress: impl Fn(u64) + Send + 'static,
cancel: CancellationToken,
) -> Result<(), ClientError> {
let send = self
@@ -219,8 +222,12 @@ impl LsHttpClientV3 {
.to_string(),
)
.body({
let stream =
ReceiverStream::new(content.into_receiver()).map(Ok::<Bytes, anyhow::Error>);
let mut sent = 0_u64;
let stream = ReceiverStream::new(content.into_receiver()).map(move |chunk| {
sent += chunk.len() as u64;
progress(sent);
Ok::<Bytes, anyhow::Error>(chunk)
});
reqwest::Body::wrap_stream(stream)
})
.send();
+14 -3
View File
@@ -11,7 +11,7 @@ use localsend::http::state::ClientInfo;
use localsend::model::transfer::{FileContent, FileDto};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU16, Ordering};
use std::sync::atomic::{AtomicU16, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{mpsc, oneshot, Mutex};
@@ -209,6 +209,7 @@ async fn upload_bytes(
) -> Result<(), ClientError> {
let (tx, rx) = mpsc::channel::<Bytes>(4);
let chunks: Vec<Vec<u8>> = bytes.chunks(1024).map(|chunk| chunk.to_vec()).collect();
let sent = Arc::new(AtomicU64::new(0));
tokio::spawn(async move {
for chunk in chunks {
if tx.send(Bytes::from(chunk)).await.is_err() {
@@ -217,7 +218,8 @@ async fn upload_bytes(
}
});
client
let progress = sent.clone();
let result = client
.upload(
ProtocolType::Http,
"127.0.0.1",
@@ -227,9 +229,18 @@ async fn upload_bytes(
file_id,
token,
FileContent::Stream(rx),
move |bytes_sent| {
progress.store(bytes_sent, Ordering::Relaxed);
},
CancellationToken::new(),
)
.await
.await;
if result.is_ok() {
assert_eq!(sent.load(Ordering::Relaxed), bytes.len() as u64);
}
result
}
fn assert_status(result: Result<impl Sized, ClientError>, expected_status: u16) {