mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: add http server isolate
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
export 'package:localsend_app/isolate/src/isolate/child/server_isolate.dart'
|
||||||
|
show
|
||||||
|
HttpServerEvent,
|
||||||
|
HttpServerFileUploadEvent,
|
||||||
|
HttpServerFileUploadResultEvent,
|
||||||
|
HttpServerPrepareUploadEvent,
|
||||||
|
HttpServerRegisterEvent,
|
||||||
|
HttpServerSessionEndEvent;
|
||||||
export 'package:localsend_app/isolate/src/isolate/child/sync_provider.dart';
|
export 'package:localsend_app/isolate/src/isolate/child/sync_provider.dart';
|
||||||
export 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart'
|
export 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart'
|
||||||
show
|
show
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import 'package:localsend_app/isolate/constants.dart';
|
||||||
|
import 'package:localsend_app/isolate/model/dto/multicast_dto.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/isolate/child/main.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/isolate/child/sync_provider.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/task/server/http_server.dart';
|
||||||
|
import 'package:localsend_app/rust/api/model.dart' show FileDto;
|
||||||
|
import 'package:localsend_app/rust/api/server.dart';
|
||||||
|
import 'package:localsend_app/util/rust.dart';
|
||||||
|
import 'package:typed_isolates/typed_isolates.dart';
|
||||||
|
|
||||||
|
sealed class BaseHttpServerTask {}
|
||||||
|
|
||||||
|
/// Starts the HTTP server.
|
||||||
|
/// The device information is derived from the sync state.
|
||||||
|
///
|
||||||
|
/// The server emits [HttpServerEvent]s on the stream of this task
|
||||||
|
/// until the server is stopped via [HttpServerStopTask].
|
||||||
|
class HttpServerStartTask implements BaseHttpServerTask {
|
||||||
|
/// Optional PIN that senders must provide to start an upload session.
|
||||||
|
final String? pin;
|
||||||
|
|
||||||
|
HttpServerStartTask({
|
||||||
|
required this.pin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the HTTP server.
|
||||||
|
class HttpServerStopTask implements BaseHttpServerTask {}
|
||||||
|
|
||||||
|
/// Answers a pending [HttpServerPrepareUploadEvent].
|
||||||
|
class HttpServerPrepareUploadDecisionTask implements BaseHttpServerTask {
|
||||||
|
/// The file IDs to accept (a subset of the offered files).
|
||||||
|
/// `null` declines the request.
|
||||||
|
final List<String>? acceptedFileIds;
|
||||||
|
|
||||||
|
HttpServerPrepareUploadDecisionTask({
|
||||||
|
required this.acceptedFileIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a pending [HttpServerFileUploadEvent] with the target the file
|
||||||
|
/// should be saved to: either a file [path] or a writable [fileDescriptor] (Android).
|
||||||
|
///
|
||||||
|
/// The file is written by the Rust server itself.
|
||||||
|
/// A [HttpServerFileUploadResultEvent] is emitted on the stream of this task
|
||||||
|
/// once the file has been received completely (or failed).
|
||||||
|
class HttpServerFileUploadTargetTask implements BaseHttpServerTask {
|
||||||
|
final String sessionId;
|
||||||
|
final String fileId;
|
||||||
|
final String? path;
|
||||||
|
final int? fileDescriptor;
|
||||||
|
|
||||||
|
HttpServerFileUploadTargetTask({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.fileId,
|
||||||
|
required this.path,
|
||||||
|
required this.fileDescriptor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A message sent from the server isolate to the main isolate.
|
||||||
|
sealed class HttpServerEvent {}
|
||||||
|
|
||||||
|
/// A device registered itself on this server.
|
||||||
|
class HttpServerRegisterEvent extends HttpServerEvent {
|
||||||
|
final String ip;
|
||||||
|
final RegisterDtoV2 info;
|
||||||
|
|
||||||
|
HttpServerRegisterEvent({
|
||||||
|
required this.ip,
|
||||||
|
required this.info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sender requests to upload files.
|
||||||
|
/// Must be answered with a [HttpServerPrepareUploadDecisionTask].
|
||||||
|
class HttpServerPrepareUploadEvent extends HttpServerEvent {
|
||||||
|
final String ip;
|
||||||
|
final RegisterDtoV2 info;
|
||||||
|
final Map<String, FileDto> files;
|
||||||
|
|
||||||
|
HttpServerPrepareUploadEvent({
|
||||||
|
required this.ip,
|
||||||
|
required this.info,
|
||||||
|
required this.files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An accepted file is being uploaded.
|
||||||
|
/// Must be answered with a [HttpServerFileUploadTargetTask].
|
||||||
|
class HttpServerFileUploadEvent extends HttpServerEvent {
|
||||||
|
final String sessionId;
|
||||||
|
final String fileId;
|
||||||
|
final FileDto file;
|
||||||
|
|
||||||
|
HttpServerFileUploadEvent({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.fileId,
|
||||||
|
required this.file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of a [HttpServerFileUploadTargetTask].
|
||||||
|
class HttpServerFileUploadResultEvent extends HttpServerEvent {
|
||||||
|
final String sessionId;
|
||||||
|
final String fileId;
|
||||||
|
|
||||||
|
/// `null` if the file has been saved successfully.
|
||||||
|
final String? error;
|
||||||
|
|
||||||
|
HttpServerFileUploadResultEvent({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.fileId,
|
||||||
|
required this.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An upload session ended.
|
||||||
|
class HttpServerSessionEndEvent extends HttpServerEvent {
|
||||||
|
final String sessionId;
|
||||||
|
final SessionEndReasonV2 reason;
|
||||||
|
|
||||||
|
HttpServerSessionEndEvent({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setupHttpServerIsolate(
|
||||||
|
Stream<SendToIsolateData<IsolateTask<BaseHttpServerTask>>> receiveFromMain,
|
||||||
|
void Function(IsolateTaskStreamResult<HttpServerEvent>) sendToMain,
|
||||||
|
InitialData initialData,
|
||||||
|
) async {
|
||||||
|
await setupChildIsolateHelper(
|
||||||
|
debugLabel: 'HttpServerIsolate',
|
||||||
|
receiveFromMain: receiveFromMain,
|
||||||
|
sendToMain: sendToMain,
|
||||||
|
initialData: initialData,
|
||||||
|
handler: (ref, task) async {
|
||||||
|
switch (task.data) {
|
||||||
|
case HttpServerStartTask startTask:
|
||||||
|
final syncState = ref.read(syncProvider);
|
||||||
|
final events = await ref
|
||||||
|
.read(httpServerProvider)
|
||||||
|
.start(
|
||||||
|
port: syncState.port,
|
||||||
|
tls: syncState.protocol == ProtocolType.https
|
||||||
|
? TlsConfig(
|
||||||
|
cert: syncState.securityContext.certificate,
|
||||||
|
privateKey: syncState.securityContext.privateKey,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
alias: syncState.alias,
|
||||||
|
version: protocolVersion,
|
||||||
|
deviceModel: syncState.deviceInfo.deviceModel,
|
||||||
|
deviceType: syncState.deviceInfo.deviceType.toRust(),
|
||||||
|
fingerprint: syncState.securityContext.certificateHash,
|
||||||
|
pin: startTask.pin,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await for (final event in events) {
|
||||||
|
sendToMain(
|
||||||
|
IsolateTaskStreamResult.event(
|
||||||
|
id: task.id,
|
||||||
|
data: switch (event) {
|
||||||
|
RsServerEvent_Register(:final ip, :final info) => HttpServerRegisterEvent(ip: ip, info: info),
|
||||||
|
RsServerEvent_PrepareUpload(:final ip, :final info, :final files) => HttpServerPrepareUploadEvent(
|
||||||
|
ip: ip,
|
||||||
|
info: info,
|
||||||
|
files: files,
|
||||||
|
),
|
||||||
|
RsServerEvent_FileUpload(:final sessionId, :final fileId, :final file) => HttpServerFileUploadEvent(
|
||||||
|
sessionId: sessionId,
|
||||||
|
fileId: fileId,
|
||||||
|
file: file,
|
||||||
|
),
|
||||||
|
RsServerEvent_SessionEnd(:final sessionId, :final reason) => HttpServerSessionEndEvent(
|
||||||
|
sessionId: sessionId,
|
||||||
|
reason: reason,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sendToMain(
|
||||||
|
IsolateTaskStreamResult.done(
|
||||||
|
id: task.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case HttpServerStopTask _:
|
||||||
|
await ref.read(httpServerProvider).stop();
|
||||||
|
return;
|
||||||
|
case HttpServerPrepareUploadDecisionTask decisionTask:
|
||||||
|
await ref.read(httpServerProvider).respondPrepareUpload(acceptedFileIds: decisionTask.acceptedFileIds);
|
||||||
|
return;
|
||||||
|
case HttpServerFileUploadTargetTask targetTask:
|
||||||
|
String? error;
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(httpServerProvider)
|
||||||
|
.respondFileUpload(
|
||||||
|
sessionId: targetTask.sessionId,
|
||||||
|
fileId: targetTask.fileId,
|
||||||
|
path: targetTask.path,
|
||||||
|
fileDescriptor: targetTask.fileDescriptor,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
error = e.humanErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToMain(
|
||||||
|
IsolateTaskStreamResult.event(
|
||||||
|
id: task.id,
|
||||||
|
data: HttpServerFileUploadResultEvent(
|
||||||
|
sessionId: targetTask.sessionId,
|
||||||
|
fileId: targetTask.fileId,
|
||||||
|
error: error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sendToMain(
|
||||||
|
IsolateTaskStreamResult.done(
|
||||||
|
id: task.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:localsend_app/isolate/model/device.dart';
|
import 'package:localsend_app/isolate/model/device.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/http_scan_discovery_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/http_scan_discovery_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/multicast_discovery_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/multicast_discovery_isolate.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/isolate/child/server_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
|
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/parent/parent_isolate_provider.dart';
|
import 'package:localsend_app/isolate/src/isolate/parent/parent_isolate_provider.dart';
|
||||||
@@ -187,6 +188,146 @@ class IsolateHttpUploadCancelAction extends ReduxAction<IsolateController, Paren
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Starts the HTTP server and returns the stream of server events.
|
||||||
|
/// The stream ends when the server is stopped via [IsolateHttpServerStopAction].
|
||||||
|
class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<HttpServerEvent>> {
|
||||||
|
final String? pin;
|
||||||
|
|
||||||
|
IsolateHttpServerStartAction({
|
||||||
|
required this.pin,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
(ParentIsolateState, Stream<HttpServerEvent>) reduce() {
|
||||||
|
final connection = state.httpServer;
|
||||||
|
if (connection == null) {
|
||||||
|
throw StateError('httpServer is not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
state,
|
||||||
|
connection.sendWrappedTaskAndListenStream(
|
||||||
|
task: HttpServerStartTask(
|
||||||
|
pin: pin,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IsolateHttpServerStopAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||||
|
@override
|
||||||
|
ParentIsolateState reduce() {
|
||||||
|
final connection = state.httpServer;
|
||||||
|
if (connection == null) {
|
||||||
|
throw StateError('httpServer is not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.sendToIsolate(
|
||||||
|
SendToIsolateData(
|
||||||
|
syncState: null,
|
||||||
|
data: IsolateTask(
|
||||||
|
data: HttpServerStopTask(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a pending [HttpServerPrepareUploadEvent].
|
||||||
|
class IsolateHttpServerPrepareUploadDecisionAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||||
|
/// The file IDs to accept (a subset of the offered files).
|
||||||
|
/// `null` declines the request.
|
||||||
|
final List<String>? acceptedFileIds;
|
||||||
|
|
||||||
|
IsolateHttpServerPrepareUploadDecisionAction({
|
||||||
|
required this.acceptedFileIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParentIsolateState reduce() {
|
||||||
|
final connection = state.httpServer;
|
||||||
|
if (connection == null) {
|
||||||
|
throw StateError('httpServer is not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.sendToIsolate(
|
||||||
|
SendToIsolateData(
|
||||||
|
syncState: null,
|
||||||
|
data: IsolateTask(
|
||||||
|
data: HttpServerPrepareUploadDecisionTask(
|
||||||
|
acceptedFileIds: acceptedFileIds,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a pending [HttpServerFileUploadEvent] with the target the file
|
||||||
|
/// should be saved to (either a [path] or a writable [fileDescriptor]).
|
||||||
|
/// The returned future completes when the file has been received completely
|
||||||
|
/// and throws if saving the file failed.
|
||||||
|
class IsolateHttpServerFileUploadTargetAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Future<void>> {
|
||||||
|
final String sessionId;
|
||||||
|
final String fileId;
|
||||||
|
final String? path;
|
||||||
|
final int? fileDescriptor;
|
||||||
|
|
||||||
|
IsolateHttpServerFileUploadTargetAction({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.fileId,
|
||||||
|
required this.path,
|
||||||
|
required this.fileDescriptor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
(ParentIsolateState, Future<void>) reduce() {
|
||||||
|
final connection = state.httpServer;
|
||||||
|
if (connection == null) {
|
||||||
|
throw StateError('httpServer is not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
final events = connection.sendWrappedTaskAndListenStream(
|
||||||
|
task: HttpServerFileUploadTargetTask(
|
||||||
|
sessionId: sessionId,
|
||||||
|
fileId: fileId,
|
||||||
|
path: path,
|
||||||
|
fileDescriptor: fileDescriptor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (state, _awaitResult(events));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _awaitResult(Stream<HttpServerEvent> events) async {
|
||||||
|
await for (final event in events) {
|
||||||
|
if (event case HttpServerFileUploadResultEvent(:final error)) {
|
||||||
|
if (error != null) {
|
||||||
|
throw HttpServerFileUploadException(error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw HttpServerFileUploadException('The server isolate did not report a result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saving a file received by the HTTP server failed.
|
||||||
|
class HttpServerFileUploadException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
HttpServerFileUploadException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds the [SendToIsolateData] envelope on top of the generic
|
/// Adds the [SendToIsolateData] envelope on top of the generic
|
||||||
/// [IsolateTaskConnector.sendTaskAndListenStream] from `typed_isolates`.
|
/// [IsolateTaskConnector.sendTaskAndListenStream] from `typed_isolates`.
|
||||||
extension _WrappedTaskConnector<R, T> on IsolateConnector<IsolateTaskStreamResult<R>, SendToIsolateData<IsolateTask<T>>> {
|
extension _WrappedTaskConnector<R, T> on IsolateConnector<IsolateTaskStreamResult<R>, SendToIsolateData<IsolateTask<T>>> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:localsend_app/isolate/model/device.dart';
|
|||||||
import 'package:localsend_app/isolate/src/isolate/child/http_scan_discovery_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/http_scan_discovery_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/main.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/main.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/multicast_discovery_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/multicast_discovery_isolate.dart';
|
||||||
|
import 'package:localsend_app/isolate/src/isolate/child/server_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/sync_provider.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/sync_provider.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart';
|
import 'package:localsend_app/isolate/src/isolate/child/upload_isolate.dart';
|
||||||
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
|
import 'package:localsend_app/isolate/src/isolate/dto/send_to_isolate_data.dart';
|
||||||
@@ -21,12 +22,14 @@ class ParentIsolateState with ParentIsolateStateMappable {
|
|||||||
final IsolateConnector<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<HttpScanTask>>>? httpScanDiscovery;
|
final IsolateConnector<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<HttpScanTask>>>? httpScanDiscovery;
|
||||||
final IsolateConnector<Device, SendToIsolateData<MulticastTask>>? multicastDiscovery;
|
final IsolateConnector<Device, SendToIsolateData<MulticastTask>>? multicastDiscovery;
|
||||||
final IsolateConnector<IsolateTaskStreamResult<HttpUploadEvent>, SendToIsolateData<IsolateTask<BaseHttpUploadTask>>>? httpUpload;
|
final IsolateConnector<IsolateTaskStreamResult<HttpUploadEvent>, SendToIsolateData<IsolateTask<BaseHttpUploadTask>>>? httpUpload;
|
||||||
|
final IsolateConnector<IsolateTaskStreamResult<HttpServerEvent>, SendToIsolateData<IsolateTask<BaseHttpServerTask>>>? httpServer;
|
||||||
|
|
||||||
ParentIsolateState({
|
ParentIsolateState({
|
||||||
required this.syncState,
|
required this.syncState,
|
||||||
required this.httpScanDiscovery,
|
required this.httpScanDiscovery,
|
||||||
required this.multicastDiscovery,
|
required this.multicastDiscovery,
|
||||||
required this.httpUpload,
|
required this.httpUpload,
|
||||||
|
required this.httpServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
static ParentIsolateState initial(SyncState syncState) => ParentIsolateState(
|
static ParentIsolateState initial(SyncState syncState) => ParentIsolateState(
|
||||||
@@ -34,6 +37,7 @@ class ParentIsolateState with ParentIsolateStateMappable {
|
|||||||
httpScanDiscovery: null,
|
httpScanDiscovery: null,
|
||||||
multicastDiscovery: null,
|
multicastDiscovery: null,
|
||||||
httpUpload: null,
|
httpUpload: null,
|
||||||
|
httpServer: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -95,6 +99,15 @@ class IsolateSetupAction extends AsyncReduxAction<IsolateController, ParentIsola
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final httpServer =
|
||||||
|
await TypedIsolates.startIsolate<IsolateTaskStreamResult<HttpServerEvent>, SendToIsolateData<IsolateTask<BaseHttpServerTask>>, InitialData>(
|
||||||
|
task: setupHttpServerIsolate,
|
||||||
|
param: InitialData(
|
||||||
|
syncState: state.syncState,
|
||||||
|
logLevel: Logger.root.level,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (uriContentStreamResolver != null) {
|
if (uriContentStreamResolver != null) {
|
||||||
httpUpload.sendToIsolate(
|
httpUpload.sendToIsolate(
|
||||||
SendToIsolateData(
|
SendToIsolateData(
|
||||||
@@ -111,6 +124,7 @@ class IsolateSetupAction extends AsyncReduxAction<IsolateController, ParentIsola
|
|||||||
httpScanDiscovery: httpScanDiscovery,
|
httpScanDiscovery: httpScanDiscovery,
|
||||||
multicastDiscovery: multicastDiscovery,
|
multicastDiscovery: multicastDiscovery,
|
||||||
httpUpload: httpUpload,
|
httpUpload: httpUpload,
|
||||||
|
httpServer: httpServer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +135,7 @@ class IsolateDisposeAction extends ReduxAction<IsolateController, ParentIsolateS
|
|||||||
state.httpScanDiscovery?.isolate.kill();
|
state.httpScanDiscovery?.isolate.kill();
|
||||||
state.multicastDiscovery?.isolate.kill();
|
state.multicastDiscovery?.isolate.kill();
|
||||||
state.httpUpload?.isolate.kill();
|
state.httpUpload?.isolate.kill();
|
||||||
|
state.httpServer?.isolate.kill();
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,19 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
|||||||
>
|
>
|
||||||
>
|
>
|
||||||
_f$httpUpload = Field('httpUpload', _$httpUpload);
|
_f$httpUpload = Field('httpUpload', _$httpUpload);
|
||||||
|
static IsolateConnector<
|
||||||
|
IsolateTaskStreamResult<HttpServerEvent>,
|
||||||
|
SendToIsolateData<IsolateTask<BaseHttpServerTask>>
|
||||||
|
>?
|
||||||
|
_$httpServer(ParentIsolateState v) => v.httpServer;
|
||||||
|
static const Field<
|
||||||
|
ParentIsolateState,
|
||||||
|
IsolateConnector<
|
||||||
|
IsolateTaskStreamResult<HttpServerEvent>,
|
||||||
|
SendToIsolateData<IsolateTask<BaseHttpServerTask>>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
_f$httpServer = Field('httpServer', _$httpServer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final MappableFields<ParentIsolateState> fields = const {
|
final MappableFields<ParentIsolateState> fields = const {
|
||||||
@@ -69,6 +82,7 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
|||||||
#httpScanDiscovery: _f$httpScanDiscovery,
|
#httpScanDiscovery: _f$httpScanDiscovery,
|
||||||
#multicastDiscovery: _f$multicastDiscovery,
|
#multicastDiscovery: _f$multicastDiscovery,
|
||||||
#httpUpload: _f$httpUpload,
|
#httpUpload: _f$httpUpload,
|
||||||
|
#httpServer: _f$httpServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
static ParentIsolateState _instantiate(DecodingData data) {
|
static ParentIsolateState _instantiate(DecodingData data) {
|
||||||
@@ -77,6 +91,7 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
|||||||
httpScanDiscovery: data.dec(_f$httpScanDiscovery),
|
httpScanDiscovery: data.dec(_f$httpScanDiscovery),
|
||||||
multicastDiscovery: data.dec(_f$multicastDiscovery),
|
multicastDiscovery: data.dec(_f$multicastDiscovery),
|
||||||
httpUpload: data.dec(_f$httpUpload),
|
httpUpload: data.dec(_f$httpUpload),
|
||||||
|
httpServer: data.dec(_f$httpServer),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +181,11 @@ abstract class ParentIsolateStateCopyWith<
|
|||||||
SendToIsolateData<IsolateTask<BaseHttpUploadTask>>
|
SendToIsolateData<IsolateTask<BaseHttpUploadTask>>
|
||||||
>?
|
>?
|
||||||
httpUpload,
|
httpUpload,
|
||||||
|
IsolateConnector<
|
||||||
|
IsolateTaskStreamResult<HttpServerEvent>,
|
||||||
|
SendToIsolateData<IsolateTask<BaseHttpServerTask>>
|
||||||
|
>?
|
||||||
|
httpServer,
|
||||||
});
|
});
|
||||||
ParentIsolateStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
|
ParentIsolateStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
|
||||||
Then<$Out2, $R2> t,
|
Then<$Out2, $R2> t,
|
||||||
@@ -189,12 +209,14 @@ class _ParentIsolateStateCopyWithImpl<$R, $Out>
|
|||||||
Object? httpScanDiscovery = $none,
|
Object? httpScanDiscovery = $none,
|
||||||
Object? multicastDiscovery = $none,
|
Object? multicastDiscovery = $none,
|
||||||
Object? httpUpload = $none,
|
Object? httpUpload = $none,
|
||||||
|
Object? httpServer = $none,
|
||||||
}) => $apply(
|
}) => $apply(
|
||||||
FieldCopyWithData({
|
FieldCopyWithData({
|
||||||
if (syncState != null) #syncState: syncState,
|
if (syncState != null) #syncState: syncState,
|
||||||
if (httpScanDiscovery != $none) #httpScanDiscovery: httpScanDiscovery,
|
if (httpScanDiscovery != $none) #httpScanDiscovery: httpScanDiscovery,
|
||||||
if (multicastDiscovery != $none) #multicastDiscovery: multicastDiscovery,
|
if (multicastDiscovery != $none) #multicastDiscovery: multicastDiscovery,
|
||||||
if (httpUpload != $none) #httpUpload: httpUpload,
|
if (httpUpload != $none) #httpUpload: httpUpload,
|
||||||
|
if (httpServer != $none) #httpServer: httpServer,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
@@ -209,6 +231,7 @@ class _ParentIsolateStateCopyWithImpl<$R, $Out>
|
|||||||
or: $value.multicastDiscovery,
|
or: $value.multicastDiscovery,
|
||||||
),
|
),
|
||||||
httpUpload: data.get(#httpUpload, or: $value.httpUpload),
|
httpUpload: data.get(#httpUpload, or: $value.httpUpload),
|
||||||
|
httpServer: data.get(#httpServer, or: $value.httpServer),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import 'package:localsend_app/rust/api/model.dart';
|
||||||
|
import 'package:localsend_app/rust/api/server.dart';
|
||||||
|
import 'package:refena_flutter/refena_flutter.dart';
|
||||||
|
|
||||||
|
final httpServerProvider = Provider((ref) => HttpServerService());
|
||||||
|
|
||||||
|
/// Wraps the Rust HTTP server.
|
||||||
|
/// Only one server can run at a time.
|
||||||
|
class HttpServerService {
|
||||||
|
RsHttpServer? _server;
|
||||||
|
|
||||||
|
bool get running => _server != null;
|
||||||
|
|
||||||
|
/// Starts the server and returns the stream of server events.
|
||||||
|
/// The stream ends when the server is stopped.
|
||||||
|
Future<Stream<RsServerEvent>> start({
|
||||||
|
required int port,
|
||||||
|
required TlsConfig? tls,
|
||||||
|
required String alias,
|
||||||
|
required String version,
|
||||||
|
required String? deviceModel,
|
||||||
|
required DeviceType? deviceType,
|
||||||
|
required String fingerprint,
|
||||||
|
required String? pin,
|
||||||
|
}) async {
|
||||||
|
if (_server != null) {
|
||||||
|
throw StateError('Server already running');
|
||||||
|
}
|
||||||
|
|
||||||
|
final server = await startServer(
|
||||||
|
port: port,
|
||||||
|
tls: tls,
|
||||||
|
alias: alias,
|
||||||
|
version: version,
|
||||||
|
deviceModel: deviceModel,
|
||||||
|
deviceType: deviceType,
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
pin: pin,
|
||||||
|
);
|
||||||
|
_server = server;
|
||||||
|
return server.listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a pending prepare-upload request.
|
||||||
|
/// [acceptedFileIds] is the subset of the offered files to accept; `null` declines the request.
|
||||||
|
Future<void> respondPrepareUpload({required List<String>? acceptedFileIds}) async {
|
||||||
|
await _requireServer().respondPrepareUpload(acceptedFileIds: acceptedFileIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers a pending file upload with the target the file should be saved to
|
||||||
|
/// (either a [path] or a [fileDescriptor]) and waits until the file has been received completely.
|
||||||
|
Future<void> respondFileUpload({
|
||||||
|
required String sessionId,
|
||||||
|
required String fileId,
|
||||||
|
required String? path,
|
||||||
|
required int? fileDescriptor,
|
||||||
|
}) async {
|
||||||
|
await _requireServer().respondFileUpload(
|
||||||
|
sessionId: sessionId,
|
||||||
|
fileId: fileId,
|
||||||
|
path: path,
|
||||||
|
fileDescriptor: fileDescriptor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the server. The event stream returned by [start] will end.
|
||||||
|
Future<void> stop() async {
|
||||||
|
final server = _server;
|
||||||
|
_server = null;
|
||||||
|
await server?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
RsHttpServer _requireServer() {
|
||||||
|
final server = _server;
|
||||||
|
if (server == null) {
|
||||||
|
throw StateError('Server is not running');
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,8 @@
|
|||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
import 'package:localsend_app/rust/frb_generated.dart';
|
import 'package:localsend_app/rust/frb_generated.dart';
|
||||||
|
|
||||||
Future<void> verifyCert({required String cert, required String publicKey}) => RustLib.instance.api.crateApiCryptoVerifyCert(
|
Future<void> verifyCert({required String cert, required String publicKey}) =>
|
||||||
cert: cert,
|
RustLib.instance.api.crateApiCryptoVerifyCert(cert: cert, publicKey: publicKey);
|
||||||
publicKey: publicKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<KeyPair> generateKeyPair() => RustLib.instance.api.crateApiCryptoGenerateKeyPair();
|
Future<KeyPair> generateKeyPair() => RustLib.instance.api.crateApiCryptoGenerateKeyPair();
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,8 @@ part 'http.freezed.dart';
|
|||||||
// These functions are ignored because they are not marked as `pub`: `resolve_file_content`
|
// 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`
|
// 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({
|
RsHttpClient createClient({required String privateKey, required String cert, required LsHttpClientVersion version, int? timeoutMs}) =>
|
||||||
required String privateKey,
|
RustLib.instance.api.crateApiHttpCreateClient(privateKey: privateKey, cert: cert, version: version, timeoutMs: timeoutMs);
|
||||||
required String cert,
|
|
||||||
required LsHttpClientVersion version,
|
|
||||||
int? timeoutMs,
|
|
||||||
}) => RustLib.instance.api.crateApiHttpCreateClient(
|
|
||||||
privateKey: privateKey,
|
|
||||||
cert: cert,
|
|
||||||
version: version,
|
|
||||||
timeoutMs: timeoutMs,
|
|
||||||
);
|
|
||||||
|
|
||||||
RsCancellationToken createCancellationToken() => RustLib.instance.api.crateApiHttpCreateCancellationToken();
|
RsCancellationToken createCancellationToken() => RustLib.instance.api.crateApiHttpCreateCancellationToken();
|
||||||
|
|
||||||
@@ -35,12 +26,7 @@ abstract class RsCancellationToken implements RustOpaqueInterface {
|
|||||||
|
|
||||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsHttpClient>>
|
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsHttpClient>>
|
||||||
abstract class RsHttpClient implements RustOpaqueInterface {
|
abstract class RsHttpClient implements RustOpaqueInterface {
|
||||||
Future<void> cancel({
|
Future<void> cancel({required ProtocolType protocol, required String ip, required int port, required String sessionId});
|
||||||
required ProtocolType protocol,
|
|
||||||
required String ip,
|
|
||||||
required int port,
|
|
||||||
required String sessionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<PrepareUploadResult> prepareUpload({
|
Future<PrepareUploadResult> prepareUpload({
|
||||||
required ProtocolType protocol,
|
required ProtocolType protocol,
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// This file is automatically generated, so please do not edit it.
|
||||||
|
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||||
|
|
||||||
|
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||||
|
|
||||||
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
||||||
|
import 'package:localsend_app/rust/api/model.dart';
|
||||||
|
import 'package:localsend_app/rust/frb_generated.dart';
|
||||||
|
|
||||||
|
part 'server.freezed.dart';
|
||||||
|
|
||||||
|
// These functions are ignored because they are not marked as `pub`: `resolve_upload_target`
|
||||||
|
|
||||||
|
/// Starts the HTTP server on the given port (IPv4 and IPv6).
|
||||||
|
/// The server runs until [RsHttpServer::stop] is called.
|
||||||
|
///
|
||||||
|
/// Events are received by listening to [RsHttpServer::listen].
|
||||||
|
Future<RsHttpServer> startServer({
|
||||||
|
required int port,
|
||||||
|
TlsConfig? tls,
|
||||||
|
required String alias,
|
||||||
|
required String version,
|
||||||
|
String? deviceModel,
|
||||||
|
DeviceType? deviceType,
|
||||||
|
required String fingerprint,
|
||||||
|
String? pin,
|
||||||
|
}) => RustLib.instance.api.crateApiServerStartServer(
|
||||||
|
port: port,
|
||||||
|
tls: tls,
|
||||||
|
alias: alias,
|
||||||
|
version: version,
|
||||||
|
deviceModel: deviceModel,
|
||||||
|
deviceType: deviceType,
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
pin: pin,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsHttpServer>>
|
||||||
|
abstract class RsHttpServer implements RustOpaqueInterface {
|
||||||
|
/// Emits server events until the server is stopped.
|
||||||
|
/// Can only be listened to once.
|
||||||
|
Stream<RsServerEvent> listen();
|
||||||
|
|
||||||
|
/// Answers the pending [RsServerEvent::FileUpload] event with the target
|
||||||
|
/// the file should be saved to (either a path or a file descriptor)
|
||||||
|
/// and waits until the file has been received completely.
|
||||||
|
Future<void> respondFileUpload({required String sessionId, required String fileId, String? path, int? fileDescriptor});
|
||||||
|
|
||||||
|
/// Answers the pending [RsServerEvent::PrepareUpload] event.
|
||||||
|
///
|
||||||
|
/// Passing the accepted file IDs (a subset of the offered files) accepts the request.
|
||||||
|
/// Passing `None` declines the request.
|
||||||
|
Future<void> respondPrepareUpload({List<String>? acceptedFileIds});
|
||||||
|
|
||||||
|
/// Stops the server.
|
||||||
|
Future<void> stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProtocolTypeV2 {
|
||||||
|
http,
|
||||||
|
https,
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegisterDtoV2 {
|
||||||
|
final String alias;
|
||||||
|
final String version;
|
||||||
|
final String? deviceModel;
|
||||||
|
final DeviceType? deviceType;
|
||||||
|
final String fingerprint;
|
||||||
|
final int port;
|
||||||
|
final ProtocolTypeV2 protocol;
|
||||||
|
final bool download;
|
||||||
|
|
||||||
|
const RegisterDtoV2({
|
||||||
|
required this.alias,
|
||||||
|
required this.version,
|
||||||
|
this.deviceModel,
|
||||||
|
this.deviceType,
|
||||||
|
required this.fingerprint,
|
||||||
|
required this.port,
|
||||||
|
required this.protocol,
|
||||||
|
required this.download,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
alias.hashCode ^
|
||||||
|
version.hashCode ^
|
||||||
|
deviceModel.hashCode ^
|
||||||
|
deviceType.hashCode ^
|
||||||
|
fingerprint.hashCode ^
|
||||||
|
port.hashCode ^
|
||||||
|
protocol.hashCode ^
|
||||||
|
download.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is RegisterDtoV2 &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
alias == other.alias &&
|
||||||
|
version == other.version &&
|
||||||
|
deviceModel == other.deviceModel &&
|
||||||
|
deviceType == other.deviceType &&
|
||||||
|
fingerprint == other.fingerprint &&
|
||||||
|
port == other.port &&
|
||||||
|
protocol == other.protocol &&
|
||||||
|
download == other.download;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class RsServerEvent with _$RsServerEvent {
|
||||||
|
const RsServerEvent._();
|
||||||
|
|
||||||
|
/// A device registered itself via `POST /api/localsend/v2/register`.
|
||||||
|
const factory RsServerEvent.register({
|
||||||
|
required String ip,
|
||||||
|
required RegisterDtoV2 info,
|
||||||
|
}) = RsServerEvent_Register;
|
||||||
|
|
||||||
|
/// A sender requests to upload files via `POST /api/localsend/v2/prepare-upload`.
|
||||||
|
const factory RsServerEvent.prepareUpload({
|
||||||
|
required String ip,
|
||||||
|
required RegisterDtoV2 info,
|
||||||
|
required Map<String, FileDto> files,
|
||||||
|
}) = RsServerEvent_PrepareUpload;
|
||||||
|
|
||||||
|
/// An accepted file is being uploaded via `POST /api/localsend/v2/upload`.
|
||||||
|
const factory RsServerEvent.fileUpload({
|
||||||
|
required String sessionId,
|
||||||
|
required String fileId,
|
||||||
|
required FileDto file,
|
||||||
|
}) = RsServerEvent_FileUpload;
|
||||||
|
|
||||||
|
/// An upload session ended.
|
||||||
|
const factory RsServerEvent.sessionEnd({
|
||||||
|
required String sessionId,
|
||||||
|
required SessionEndReasonV2 reason,
|
||||||
|
}) = RsServerEvent_SessionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SessionEndReasonV2 {
|
||||||
|
finished,
|
||||||
|
cancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TlsConfig {
|
||||||
|
final String cert;
|
||||||
|
final String privateKey;
|
||||||
|
|
||||||
|
const TlsConfig({
|
||||||
|
required this.cert,
|
||||||
|
required this.privateKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => cert.hashCode ^ privateKey.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) || other is TlsConfig && runtimeType == other.runtimeType && cert == other.cert && privateKey == other.privateKey;
|
||||||
|
}
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'server.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$RsServerEvent {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RsServerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RsServerEvent()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class $RsServerEventCopyWith<$Res> {
|
||||||
|
$RsServerEventCopyWith(RsServerEvent _, $Res Function(RsServerEvent) __);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [RsServerEvent].
|
||||||
|
extension RsServerEventPatterns on RsServerEvent {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( RsServerEvent_Register value)? register,TResult Function( RsServerEvent_PrepareUpload value)? prepareUpload,TResult Function( RsServerEvent_FileUpload value)? fileUpload,TResult Function( RsServerEvent_SessionEnd value)? sessionEnd,required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register() when register != null:
|
||||||
|
return register(_that);case RsServerEvent_PrepareUpload() when prepareUpload != null:
|
||||||
|
return prepareUpload(_that);case RsServerEvent_FileUpload() when fileUpload != null:
|
||||||
|
return fileUpload(_that);case RsServerEvent_SessionEnd() when sessionEnd != null:
|
||||||
|
return sessionEnd(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( RsServerEvent_Register value) register,required TResult Function( RsServerEvent_PrepareUpload value) prepareUpload,required TResult Function( RsServerEvent_FileUpload value) fileUpload,required TResult Function( RsServerEvent_SessionEnd value) sessionEnd,}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register():
|
||||||
|
return register(_that);case RsServerEvent_PrepareUpload():
|
||||||
|
return prepareUpload(_that);case RsServerEvent_FileUpload():
|
||||||
|
return fileUpload(_that);case RsServerEvent_SessionEnd():
|
||||||
|
return sessionEnd(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( RsServerEvent_Register value)? register,TResult? Function( RsServerEvent_PrepareUpload value)? prepareUpload,TResult? Function( RsServerEvent_FileUpload value)? fileUpload,TResult? Function( RsServerEvent_SessionEnd value)? sessionEnd,}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register() when register != null:
|
||||||
|
return register(_that);case RsServerEvent_PrepareUpload() when prepareUpload != null:
|
||||||
|
return prepareUpload(_that);case RsServerEvent_FileUpload() when fileUpload != null:
|
||||||
|
return fileUpload(_that);case RsServerEvent_SessionEnd() when sessionEnd != null:
|
||||||
|
return sessionEnd(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String ip, RegisterDtoV2 info)? register,TResult Function( String ip, RegisterDtoV2 info, Map<String, FileDto> files)? prepareUpload,TResult Function( String sessionId, String fileId, FileDto file)? fileUpload,TResult Function( String sessionId, SessionEndReasonV2 reason)? sessionEnd,required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register() when register != null:
|
||||||
|
return register(_that.ip,_that.info);case RsServerEvent_PrepareUpload() when prepareUpload != null:
|
||||||
|
return prepareUpload(_that.ip,_that.info,_that.files);case RsServerEvent_FileUpload() when fileUpload != null:
|
||||||
|
return fileUpload(_that.sessionId,_that.fileId,_that.file);case RsServerEvent_SessionEnd() when sessionEnd != null:
|
||||||
|
return sessionEnd(_that.sessionId,_that.reason);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String ip, RegisterDtoV2 info) register,required TResult Function( String ip, RegisterDtoV2 info, Map<String, FileDto> files) prepareUpload,required TResult Function( String sessionId, String fileId, FileDto file) fileUpload,required TResult Function( String sessionId, SessionEndReasonV2 reason) sessionEnd,}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register():
|
||||||
|
return register(_that.ip,_that.info);case RsServerEvent_PrepareUpload():
|
||||||
|
return prepareUpload(_that.ip,_that.info,_that.files);case RsServerEvent_FileUpload():
|
||||||
|
return fileUpload(_that.sessionId,_that.fileId,_that.file);case RsServerEvent_SessionEnd():
|
||||||
|
return sessionEnd(_that.sessionId,_that.reason);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String ip, RegisterDtoV2 info)? register,TResult? Function( String ip, RegisterDtoV2 info, Map<String, FileDto> files)? prepareUpload,TResult? Function( String sessionId, String fileId, FileDto file)? fileUpload,TResult? Function( String sessionId, SessionEndReasonV2 reason)? sessionEnd,}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case RsServerEvent_Register() when register != null:
|
||||||
|
return register(_that.ip,_that.info);case RsServerEvent_PrepareUpload() when prepareUpload != null:
|
||||||
|
return prepareUpload(_that.ip,_that.info,_that.files);case RsServerEvent_FileUpload() when fileUpload != null:
|
||||||
|
return fileUpload(_that.sessionId,_that.fileId,_that.file);case RsServerEvent_SessionEnd() when sessionEnd != null:
|
||||||
|
return sessionEnd(_that.sessionId,_that.reason);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class RsServerEvent_Register extends RsServerEvent {
|
||||||
|
const RsServerEvent_Register({required this.ip, required this.info}): super._();
|
||||||
|
|
||||||
|
|
||||||
|
final String ip;
|
||||||
|
final RegisterDtoV2 info;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$RsServerEvent_RegisterCopyWith<RsServerEvent_Register> get copyWith => _$RsServerEvent_RegisterCopyWithImpl<RsServerEvent_Register>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RsServerEvent_Register&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.info, info) || other.info == info));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,ip,info);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RsServerEvent.register(ip: $ip, info: $info)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $RsServerEvent_RegisterCopyWith<$Res> implements $RsServerEventCopyWith<$Res> {
|
||||||
|
factory $RsServerEvent_RegisterCopyWith(RsServerEvent_Register value, $Res Function(RsServerEvent_Register) _then) = _$RsServerEvent_RegisterCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String ip, RegisterDtoV2 info
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$RsServerEvent_RegisterCopyWithImpl<$Res>
|
||||||
|
implements $RsServerEvent_RegisterCopyWith<$Res> {
|
||||||
|
_$RsServerEvent_RegisterCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final RsServerEvent_Register _self;
|
||||||
|
final $Res Function(RsServerEvent_Register) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') $Res call({Object? ip = null,Object? info = null,}) {
|
||||||
|
return _then(RsServerEvent_Register(
|
||||||
|
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,info: null == info ? _self.info : info // ignore: cast_nullable_to_non_nullable
|
||||||
|
as RegisterDtoV2,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class RsServerEvent_PrepareUpload extends RsServerEvent {
|
||||||
|
const RsServerEvent_PrepareUpload({required this.ip, required this.info, required final Map<String, FileDto> files}): _files = files,super._();
|
||||||
|
|
||||||
|
|
||||||
|
final String ip;
|
||||||
|
final RegisterDtoV2 info;
|
||||||
|
final Map<String, FileDto> _files;
|
||||||
|
Map<String, FileDto> get files {
|
||||||
|
if (_files is EqualUnmodifiableMapView) return _files;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$RsServerEvent_PrepareUploadCopyWith<RsServerEvent_PrepareUpload> get copyWith => _$RsServerEvent_PrepareUploadCopyWithImpl<RsServerEvent_PrepareUpload>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RsServerEvent_PrepareUpload&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.info, info) || other.info == info)&&const DeepCollectionEquality().equals(other._files, _files));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,ip,info,const DeepCollectionEquality().hash(_files));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RsServerEvent.prepareUpload(ip: $ip, info: $info, files: $files)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $RsServerEvent_PrepareUploadCopyWith<$Res> implements $RsServerEventCopyWith<$Res> {
|
||||||
|
factory $RsServerEvent_PrepareUploadCopyWith(RsServerEvent_PrepareUpload value, $Res Function(RsServerEvent_PrepareUpload) _then) = _$RsServerEvent_PrepareUploadCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String ip, RegisterDtoV2 info, Map<String, FileDto> files
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$RsServerEvent_PrepareUploadCopyWithImpl<$Res>
|
||||||
|
implements $RsServerEvent_PrepareUploadCopyWith<$Res> {
|
||||||
|
_$RsServerEvent_PrepareUploadCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final RsServerEvent_PrepareUpload _self;
|
||||||
|
final $Res Function(RsServerEvent_PrepareUpload) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') $Res call({Object? ip = null,Object? info = null,Object? files = null,}) {
|
||||||
|
return _then(RsServerEvent_PrepareUpload(
|
||||||
|
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,info: null == info ? _self.info : info // ignore: cast_nullable_to_non_nullable
|
||||||
|
as RegisterDtoV2,files: null == files ? _self._files : files // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, FileDto>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class RsServerEvent_FileUpload extends RsServerEvent {
|
||||||
|
const RsServerEvent_FileUpload({required this.sessionId, required this.fileId, required this.file}): super._();
|
||||||
|
|
||||||
|
|
||||||
|
final String sessionId;
|
||||||
|
final String fileId;
|
||||||
|
final FileDto file;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$RsServerEvent_FileUploadCopyWith<RsServerEvent_FileUpload> get copyWith => _$RsServerEvent_FileUploadCopyWithImpl<RsServerEvent_FileUpload>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RsServerEvent_FileUpload&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.file, file) || other.file == file));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,sessionId,fileId,file);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RsServerEvent.fileUpload(sessionId: $sessionId, fileId: $fileId, file: $file)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $RsServerEvent_FileUploadCopyWith<$Res> implements $RsServerEventCopyWith<$Res> {
|
||||||
|
factory $RsServerEvent_FileUploadCopyWith(RsServerEvent_FileUpload value, $Res Function(RsServerEvent_FileUpload) _then) = _$RsServerEvent_FileUploadCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String sessionId, String fileId, FileDto file
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$RsServerEvent_FileUploadCopyWithImpl<$Res>
|
||||||
|
implements $RsServerEvent_FileUploadCopyWith<$Res> {
|
||||||
|
_$RsServerEvent_FileUploadCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final RsServerEvent_FileUpload _self;
|
||||||
|
final $Res Function(RsServerEvent_FileUpload) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') $Res call({Object? sessionId = null,Object? fileId = null,Object? file = null,}) {
|
||||||
|
return _then(RsServerEvent_FileUpload(
|
||||||
|
sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,file: null == file ? _self.file : file // ignore: cast_nullable_to_non_nullable
|
||||||
|
as FileDto,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class RsServerEvent_SessionEnd extends RsServerEvent {
|
||||||
|
const RsServerEvent_SessionEnd({required this.sessionId, required this.reason}): super._();
|
||||||
|
|
||||||
|
|
||||||
|
final String sessionId;
|
||||||
|
final SessionEndReasonV2 reason;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$RsServerEvent_SessionEndCopyWith<RsServerEvent_SessionEnd> get copyWith => _$RsServerEvent_SessionEndCopyWithImpl<RsServerEvent_SessionEnd>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is RsServerEvent_SessionEnd&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.reason, reason) || other.reason == reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,sessionId,reason);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'RsServerEvent.sessionEnd(sessionId: $sessionId, reason: $reason)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $RsServerEvent_SessionEndCopyWith<$Res> implements $RsServerEventCopyWith<$Res> {
|
||||||
|
factory $RsServerEvent_SessionEndCopyWith(RsServerEvent_SessionEnd value, $Res Function(RsServerEvent_SessionEnd) _then) = _$RsServerEvent_SessionEndCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String sessionId, SessionEndReasonV2 reason
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$RsServerEvent_SessionEndCopyWithImpl<$Res>
|
||||||
|
implements $RsServerEvent_SessionEndCopyWith<$Res> {
|
||||||
|
_$RsServerEvent_SessionEndCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final RsServerEvent_SessionEnd _self;
|
||||||
|
final $Res Function(RsServerEvent_SessionEnd) _then;
|
||||||
|
|
||||||
|
/// Create a copy of RsServerEvent
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') $Res call({Object? sessionId = null,Object? reason = null,}) {
|
||||||
|
return _then(RsServerEvent_SessionEnd(
|
||||||
|
sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SessionEndReasonV2,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -18,12 +18,7 @@ Stream<WsServerMessage> connect({
|
|||||||
required ProposingClientInfo info,
|
required ProposingClientInfo info,
|
||||||
required String privateKey,
|
required String privateKey,
|
||||||
required FutureOr<void> Function(LsSignalingConnection) onConnection,
|
required FutureOr<void> Function(LsSignalingConnection) onConnection,
|
||||||
}) => RustLib.instance.api.crateApiWebrtcConnect(
|
}) => RustLib.instance.api.crateApiWebrtcConnect(uri: uri, info: info, privateKey: privateKey, onConnection: onConnection);
|
||||||
uri: uri,
|
|
||||||
info: info,
|
|
||||||
privateKey: privateKey,
|
|
||||||
onConnection: onConnection,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<LsSignalingConnection>>
|
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<LsSignalingConnection>>
|
||||||
abstract class LsSignalingConnection implements RustOpaqueInterface {
|
abstract class LsSignalingConnection implements RustOpaqueInterface {
|
||||||
|
|||||||
+993
-1406
File diff suppressed because it is too large
Load Diff
+300
-465
File diff suppressed because it is too large
Load Diff
+332
-609
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,10 @@ pub fn generate_key_pair() -> anyhow::Result<KeyPair> {
|
|||||||
let private_key = localsend::crypto::token::export_private_key(&signing_key)?;
|
let private_key = localsend::crypto::token::export_private_key(&signing_key)?;
|
||||||
let public_key = localsend::crypto::token::export_public_key(&signing_key)?;
|
let public_key = localsend::crypto::token::export_public_key(&signing_key)?;
|
||||||
|
|
||||||
Ok(
|
Ok(KeyPair {
|
||||||
KeyPair {
|
private_key: private_key.to_string(),
|
||||||
private_key: private_key.to_string(),
|
public_key,
|
||||||
public_key,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyPair {
|
pub struct KeyPair {
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ pub mod crypto;
|
|||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
pub mod server;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
pub mod webrtc;
|
pub mod webrtc;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use flutter_rust_bridge::frb;
|
use flutter_rust_bridge::frb;
|
||||||
pub use localsend::http::dto::{ProtocolType, RegisterDto, RegisterResponseDto, PrepareUploadRequestDto, PrepareUploadResponseDto};
|
pub use localsend::http::dto::{
|
||||||
pub use localsend::model::discovery::{DeviceType};
|
PrepareUploadRequestDto, PrepareUploadResponseDto, ProtocolType, RegisterDto,
|
||||||
pub use localsend::model::transfer::{
|
RegisterResponseDto,
|
||||||
FileDto, FileMetadata
|
|
||||||
};
|
};
|
||||||
|
pub use localsend::model::discovery::DeviceType;
|
||||||
|
pub use localsend::model::transfer::{FileDto, FileMetadata};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[frb(mirror(RegisterDto))]
|
#[frb(mirror(RegisterDto))]
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
use crate::frb_generated::StreamSink;
|
||||||
|
use flutter_rust_bridge::frb;
|
||||||
|
pub use localsend::http::dto_v2::{ProtocolTypeV2, RegisterDtoV2};
|
||||||
|
use localsend::http::server::ServerConfigV2;
|
||||||
|
pub use localsend::http::server::TlsConfig;
|
||||||
|
use localsend::http::server::common::save::FileUploadTarget;
|
||||||
|
pub use localsend::http::server::v2::SessionEndReasonV2;
|
||||||
|
use localsend::http::server::v2::{PrepareUploadDecisionV2, ServerEventV2};
|
||||||
|
use localsend::http::state::ClientInfo;
|
||||||
|
use localsend::model::discovery::DeviceType;
|
||||||
|
use localsend::model::transfer::FileDto;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::{Mutex, mpsc, oneshot};
|
||||||
|
|
||||||
|
/// Events emitted by the HTTP server that must be handled by the application.
|
||||||
|
///
|
||||||
|
/// [RsServerEvent::PrepareUpload] must be answered with [RsHttpServer::respond_prepare_upload]
|
||||||
|
/// and [RsServerEvent::FileUpload] with [RsHttpServer::respond_file_upload].
|
||||||
|
pub enum RsServerEvent {
|
||||||
|
/// A device registered itself via `POST /api/localsend/v2/register`.
|
||||||
|
Register { ip: String, info: RegisterDtoV2 },
|
||||||
|
|
||||||
|
/// A sender requests to upload files via `POST /api/localsend/v2/prepare-upload`.
|
||||||
|
PrepareUpload {
|
||||||
|
ip: String,
|
||||||
|
info: RegisterDtoV2,
|
||||||
|
files: HashMap<String, FileDto>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An accepted file is being uploaded via `POST /api/localsend/v2/upload`.
|
||||||
|
FileUpload {
|
||||||
|
session_id: String,
|
||||||
|
file_id: String,
|
||||||
|
file: FileDto,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An upload session ended.
|
||||||
|
SessionEnd {
|
||||||
|
session_id: String,
|
||||||
|
reason: SessionEndReasonV2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RsHttpServer {
|
||||||
|
event_rx: Mutex<Option<mpsc::Receiver<ServerEventV2>>>,
|
||||||
|
stop_tx: Mutex<Option<oneshot::Sender<()>>>,
|
||||||
|
pending_decision: Mutex<Option<oneshot::Sender<PrepareUploadDecisionV2>>>,
|
||||||
|
pending_uploads: Mutex<HashMap<(String, String), oneshot::Sender<FileUploadTarget>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the HTTP server on the given port (IPv4 and IPv6).
|
||||||
|
/// The server runs until [RsHttpServer::stop] is called.
|
||||||
|
///
|
||||||
|
/// Events are received by listening to [RsHttpServer::listen].
|
||||||
|
pub async fn start_server(
|
||||||
|
port: u16,
|
||||||
|
tls: Option<TlsConfig>,
|
||||||
|
alias: String,
|
||||||
|
version: String,
|
||||||
|
device_model: Option<String>,
|
||||||
|
device_type: Option<DeviceType>,
|
||||||
|
fingerprint: String,
|
||||||
|
pin: Option<String>,
|
||||||
|
) -> anyhow::Result<RsHttpServer> {
|
||||||
|
let (event_tx, event_rx) = mpsc::channel::<ServerEventV2>(16);
|
||||||
|
let (stop_tx, stop_rx) = oneshot::channel::<()>();
|
||||||
|
|
||||||
|
localsend::http::server::start_with_port(
|
||||||
|
port,
|
||||||
|
tls,
|
||||||
|
ClientInfo {
|
||||||
|
alias,
|
||||||
|
version,
|
||||||
|
device_model,
|
||||||
|
device_type,
|
||||||
|
token: fingerprint,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
Some(ServerConfigV2 { pin, event_tx }),
|
||||||
|
None,
|
||||||
|
stop_rx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(RsHttpServer {
|
||||||
|
event_rx: Mutex::new(Some(event_rx)),
|
||||||
|
stop_tx: Mutex::new(Some(stop_tx)),
|
||||||
|
pending_decision: Mutex::new(None),
|
||||||
|
pending_uploads: Mutex::new(HashMap::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RsHttpServer {
|
||||||
|
/// Emits server events until the server is stopped.
|
||||||
|
/// Can only be listened to once.
|
||||||
|
pub async fn listen(&self, sink: StreamSink<RsServerEvent>) {
|
||||||
|
let Some(mut event_rx) = self.event_rx.lock().await.take() else {
|
||||||
|
let _ = sink.add_error(anyhow::anyhow!("Server events already listened to"));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(event) = event_rx.recv().await {
|
||||||
|
match event {
|
||||||
|
ServerEventV2::Register { ip, info } => {
|
||||||
|
let _ = sink.add(RsServerEvent::Register {
|
||||||
|
ip: ip.to_string(),
|
||||||
|
info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ServerEventV2::PrepareUpload {
|
||||||
|
ip,
|
||||||
|
info,
|
||||||
|
files,
|
||||||
|
decision_tx,
|
||||||
|
} => {
|
||||||
|
*self.pending_decision.lock().await = Some(decision_tx);
|
||||||
|
let _ = sink.add(RsServerEvent::PrepareUpload {
|
||||||
|
ip: ip.to_string(),
|
||||||
|
info,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ServerEventV2::FileUpload {
|
||||||
|
session_id,
|
||||||
|
file_id,
|
||||||
|
file,
|
||||||
|
target_tx,
|
||||||
|
} => {
|
||||||
|
self.pending_uploads
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.insert((session_id.clone(), file_id.clone()), target_tx);
|
||||||
|
let _ = sink.add(RsServerEvent::FileUpload {
|
||||||
|
session_id,
|
||||||
|
file_id,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ServerEventV2::SessionEnd { session_id, reason } => {
|
||||||
|
// Drop stale upload responders of this session (their requests already ended).
|
||||||
|
self.pending_uploads
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.retain(|(sid, _), _| sid != &session_id);
|
||||||
|
let _ = sink.add(RsServerEvent::SessionEnd { session_id, reason });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers the pending [RsServerEvent::PrepareUpload] event.
|
||||||
|
///
|
||||||
|
/// Passing the accepted file IDs (a subset of the offered files) accepts the request.
|
||||||
|
/// Passing `None` declines the request.
|
||||||
|
pub async fn respond_prepare_upload(
|
||||||
|
&self,
|
||||||
|
accepted_file_ids: Option<Vec<String>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(decision_tx) = self.pending_decision.lock().await.take() else {
|
||||||
|
return Err(anyhow::anyhow!("No pending prepare-upload request"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let decision = match accepted_file_ids {
|
||||||
|
Some(ids) => PrepareUploadDecisionV2::Accept(ids.into_iter().collect()),
|
||||||
|
None => PrepareUploadDecisionV2::Decline,
|
||||||
|
};
|
||||||
|
|
||||||
|
decision_tx
|
||||||
|
.send(decision)
|
||||||
|
.map_err(|_| anyhow::anyhow!("Prepare-upload request already ended"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answers the pending [RsServerEvent::FileUpload] event with the target
|
||||||
|
/// the file should be saved to (either a path or a file descriptor)
|
||||||
|
/// and waits until the file has been received completely.
|
||||||
|
pub async fn respond_file_upload(
|
||||||
|
&self,
|
||||||
|
session_id: String,
|
||||||
|
file_id: String,
|
||||||
|
path: Option<String>,
|
||||||
|
file_descriptor: Option<i32>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(target_tx) = self
|
||||||
|
.pending_uploads
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.remove(&(session_id, file_id))
|
||||||
|
else {
|
||||||
|
return Err(anyhow::anyhow!("No pending file upload for this file"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (result_tx, result_rx) = oneshot::channel::<Result<(), String>>();
|
||||||
|
let target = resolve_upload_target(path, file_descriptor, result_tx)?;
|
||||||
|
|
||||||
|
target_tx
|
||||||
|
.send(target)
|
||||||
|
.map_err(|_| anyhow::anyhow!("Upload request already ended"))?;
|
||||||
|
|
||||||
|
match result_rx.await {
|
||||||
|
Ok(Ok(())) => Ok(()),
|
||||||
|
Ok(Err(err)) => Err(anyhow::anyhow!(err)),
|
||||||
|
Err(_) => Err(anyhow::anyhow!("Upload request aborted")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the server.
|
||||||
|
pub async fn stop(&self) {
|
||||||
|
if let Some(stop_tx) = self.stop_tx.lock().await.take() {
|
||||||
|
let _ = stop_tx.send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_upload_target(
|
||||||
|
path: Option<String>,
|
||||||
|
file_descriptor: Option<i32>,
|
||||||
|
result_tx: oneshot::Sender<Result<(), String>>,
|
||||||
|
) -> anyhow::Result<FileUploadTarget> {
|
||||||
|
match (path, file_descriptor) {
|
||||||
|
(Some(path), None) => Ok(FileUploadTarget::Path {
|
||||||
|
path: path.into(),
|
||||||
|
result_tx,
|
||||||
|
}),
|
||||||
|
(None, Some(file_descriptor)) => {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
{
|
||||||
|
Ok(FileUploadTarget::Fd {
|
||||||
|
fd: file_descriptor,
|
||||||
|
result_tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
{
|
||||||
|
let _ = (file_descriptor, result_tx);
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"File descriptors are only supported on Android"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(anyhow::anyhow!(
|
||||||
|
"Exactly one upload target must be provided"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[frb(mirror(TlsConfig))]
|
||||||
|
pub struct _TlsConfig {
|
||||||
|
pub cert: String,
|
||||||
|
pub private_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[frb(mirror(ProtocolTypeV2))]
|
||||||
|
pub enum _ProtocolTypeV2 {
|
||||||
|
Http,
|
||||||
|
Https,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[frb(mirror(RegisterDtoV2))]
|
||||||
|
pub struct _RegisterDtoV2 {
|
||||||
|
pub alias: String,
|
||||||
|
pub version: String,
|
||||||
|
pub device_model: Option<String>,
|
||||||
|
pub device_type: Option<DeviceType>,
|
||||||
|
pub fingerprint: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub protocol: ProtocolTypeV2,
|
||||||
|
pub download: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[frb(mirror(SessionEndReasonV2))]
|
||||||
|
pub enum _SessionEndReasonV2 {
|
||||||
|
Finished,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::frb_generated::StreamSink;
|
use crate::frb_generated::StreamSink;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flutter_rust_bridge::{frb, DartFnFuture};
|
use flutter_rust_bridge::{DartFnFuture, frb};
|
||||||
use localsend::crypto::token::SigningTokenKey;
|
use localsend::crypto::token::SigningTokenKey;
|
||||||
use localsend::model::discovery::DeviceType;
|
use localsend::model::discovery::DeviceType;
|
||||||
use localsend::model::transfer::FileDto;
|
use localsend::model::transfer::FileDto;
|
||||||
@@ -14,7 +14,7 @@ pub use localsend::webrtc::webrtc::{
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::{mpsc, oneshot, Mutex};
|
use tokio::sync::{Mutex, mpsc, oneshot};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|||||||
+851
-27
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
use super::{ClientError, ResponseExt, ResultWithPublicKey};
|
use super::{ClientError, ResponseExt, ResultWithPublicKey};
|
||||||
|
use crate::http;
|
||||||
use crate::http::client::url::{ApiVersion, TargetUrl};
|
use crate::http::client::url::{ApiVersion, TargetUrl};
|
||||||
use crate::http::dto::ProtocolType;
|
use crate::http::dto::ProtocolType;
|
||||||
use crate::http;
|
|
||||||
use crate::{crypto, util};
|
use crate::{crypto, util};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use reqwest::{Response, StatusCode};
|
use reqwest::{Response, StatusCode};
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ impl FileContent {
|
|||||||
FileContent::Stream(rx) => {
|
FileContent::Stream(rx) => {
|
||||||
tracing::info!("Reading file content via byte stream from application");
|
tracing::info!("Reading file content via byte stream from application");
|
||||||
rx
|
rx
|
||||||
},
|
}
|
||||||
FileContent::Path(path) => {
|
FileContent::Path(path) => {
|
||||||
tracing::info!("Reading file content from path: {}", path.display());
|
tracing::info!("Reading file content from path: {}", path.display());
|
||||||
let (tx, rx) = mpsc::channel(FILE_CHANNEL_CAPACITY);
|
let (tx, rx) = mpsc::channel(FILE_CHANNEL_CAPACITY);
|
||||||
|
|||||||
Reference in New Issue
Block a user