mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
refactor: reuse core discovery
This commit is contained in:
@@ -216,9 +216,9 @@ Future<void> postInit(BuildContext context, Ref ref, bool appStart) async {
|
||||
}
|
||||
|
||||
try {
|
||||
ref.redux(nearbyDevicesProvider).dispatchAsync(StartMulticastListener()); // ignore: unawaited_futures
|
||||
ref.redux(nearbyDevicesProvider).dispatchAsync(StartDiscoveryListener()); // ignore: unawaited_futures
|
||||
} catch (e) {
|
||||
_logger.warning('Starting multicast listener failed', e);
|
||||
_logger.warning('Starting discovery listener failed', e);
|
||||
}
|
||||
|
||||
// ignore: dead_code
|
||||
|
||||
@@ -135,7 +135,7 @@ class SettingsTabController extends ReduxNotifier<SettingsTabVm> {
|
||||
state.portController.text = newServerState.port.toString();
|
||||
await _settingsService.setAlias(newServerState.alias);
|
||||
await _settingsService.setPort(newServerState.port);
|
||||
external(_isolateController).dispatch(IsolateSendMulticastRestartListenerAction());
|
||||
external(_isolateController).dispatch(IsolateDiscoveryRestartAction());
|
||||
external(_localIpService).dispatchAsync(FetchLocalIpAction()); // ignore: unawaited_futures
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -44,14 +44,17 @@ class NearbyDevicesService extends ReduxNotifier<NearbyDevicesState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Binds the UDP port and listens for incoming announcements.
|
||||
/// Starts the discovery (which binds the UDP port) and registers every
|
||||
/// confirmed device: answered announcements, scan results and devices fed in
|
||||
/// via [IsolateDiscoveryAddDeviceAction] all arrive on this one stream.
|
||||
/// This should run forever as long as the app is running.
|
||||
class StartMulticastListener extends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {
|
||||
class StartDiscoveryListener extends AsyncReduxAction<NearbyDevicesService, NearbyDevicesState> {
|
||||
@override
|
||||
Future<NearbyDevicesState> reduce() async {
|
||||
await for (final device in notifier._isolateController.state.multicastDiscovery!.receiveFromIsolate) {
|
||||
final stream = external(notifier._isolateController).dispatchTakeResult(IsolateDiscoveryListenAction());
|
||||
await for (final device in stream) {
|
||||
await dispatchAsync(RegisterDeviceAction(device));
|
||||
notifier._discoveryLogger.addLog('[DISCOVER/UDP] ${device.alias} (${device.ip}, model: ${device.deviceModel})');
|
||||
notifier._discoveryLogger.addLog('[DISCOVER] ${device.alias} (${device.ip}, model: ${device.deviceModel})');
|
||||
}
|
||||
return state;
|
||||
}
|
||||
@@ -138,7 +141,7 @@ class UnregisterSignalingDeviceAction extends ReduxAction<NearbyDevicesService,
|
||||
class StartMulticastScan extends ReduxAction<NearbyDevicesService, NearbyDevicesState> {
|
||||
@override
|
||||
NearbyDevicesState reduce() {
|
||||
external(notifier._isolateController).dispatch(IsolateSendMulticastAnnouncementAction());
|
||||
external(notifier._isolateController).dispatch(IsolateDiscoveryAnnouncementAction());
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -166,18 +169,17 @@ class StartLegacyScan extends AsyncReduxAction<NearbyDevicesService, NearbyDevic
|
||||
|
||||
dispatch(_SetRunningIpsAction({...state.runningIps, localIp}));
|
||||
|
||||
final stream = external(notifier._isolateController).dispatchTakeResult(
|
||||
IsolateInterfaceHttpDiscoveryAction(
|
||||
networkInterface: localIp,
|
||||
port: port,
|
||||
https: https,
|
||||
),
|
||||
);
|
||||
|
||||
await for (final device in stream) {
|
||||
notifier._discoveryLogger.addLog('[DISCOVER/TCP] ${device.alias} (${device.ip}, model: ${device.deviceModel})');
|
||||
await dispatchAsync(RegisterDeviceAction(device));
|
||||
}
|
||||
// The found devices arrive on the [StartDiscoveryListener] stream;
|
||||
// this stream only signals when the scan is finished.
|
||||
await external(notifier._isolateController)
|
||||
.dispatchTakeResult(
|
||||
IsolateDiscoverySubnetScanAction(
|
||||
networkInterface: localIp,
|
||||
port: port,
|
||||
https: https,
|
||||
),
|
||||
)
|
||||
.drain<void>();
|
||||
|
||||
return state.copyWith(
|
||||
runningIps: state.runningIps.where((ip) => ip != localIp).toSet(),
|
||||
@@ -201,17 +203,16 @@ class StartFavoriteScan extends AsyncReduxAction<NearbyDevicesService, NearbyDev
|
||||
}
|
||||
dispatch(_SetRunningFavoriteScanAction(true));
|
||||
|
||||
final stream = external(notifier._isolateController).dispatchTakeResult(
|
||||
IsolateFavoriteHttpDiscoveryAction(
|
||||
favorites: devices.map((e) => (e.ip, e.port)).toList(),
|
||||
https: https,
|
||||
),
|
||||
);
|
||||
|
||||
await for (final device in stream) {
|
||||
notifier._discoveryLogger.addLog('[DISCOVER/TCP] ${device.alias} (${device.ip}, model: ${device.deviceModel})');
|
||||
await dispatchAsync(RegisterDeviceAction(device));
|
||||
}
|
||||
// The found devices arrive on the [StartDiscoveryListener] stream;
|
||||
// this stream only signals when every favorite has been probed.
|
||||
await external(notifier._isolateController)
|
||||
.dispatchTakeResult(
|
||||
IsolateDiscoveryFavoriteScanAction(
|
||||
favorites: devices.map((e) => (e.ip, e.port)).toList(),
|
||||
https: https,
|
||||
),
|
||||
)
|
||||
.drain<void>();
|
||||
|
||||
return state.copyWith(
|
||||
runningFavoriteScan: false,
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/provider/favorites_provider.dart';
|
||||
import 'package:localsend_app/provider/http_provider.dart';
|
||||
import 'package:localsend_app/provider/logging/discovery_logs_provider.dart';
|
||||
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
|
||||
import 'package:localsend_app/provider/network/send_provider.dart';
|
||||
import 'package:localsend_app/provider/network/server/server_provider.dart';
|
||||
import 'package:localsend_app/provider/network/server/server_utils.dart';
|
||||
@@ -59,7 +58,11 @@ class ReceiveController {
|
||||
return;
|
||||
}
|
||||
|
||||
await server.ref.redux(nearbyDevicesProvider).dispatchAsync(RegisterDeviceAction(event.info.toDevice(event.ip, HttpDiscovery(ip: event.ip))));
|
||||
// Feed the device into the discovery store; it comes back (and is
|
||||
// registered) via the [StartDiscoveryListener] stream.
|
||||
server.ref
|
||||
.redux(parentIsolateProvider)
|
||||
.dispatch(IsolateDiscoveryAddDeviceAction(device: event.info.toDevice(event.ip, HttpDiscovery(ip: event.ip))));
|
||||
server.ref.notifier(discoveryLoggerProvider).addLog('[DISCOVER/TCP] Received "/register" HTTP request: ${event.info.alias} (${event.ip})');
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use futures_util::StreamExt;
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use store::DeviceStore;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
@@ -105,6 +106,10 @@ struct DiscoveryState {
|
||||
store: DeviceStore,
|
||||
event_tx: Option<mpsc::Sender<DiscoveryEvent>>,
|
||||
|
||||
/// Whether announcements of other devices are answered, see
|
||||
/// [`DiscoveryHandle::set_answer_announcements`].
|
||||
answering: AtomicBool,
|
||||
|
||||
/// The interface addresses a subnet scan is currently running for.
|
||||
scanning: std::sync::Mutex<HashSet<Ipv4Addr>>,
|
||||
}
|
||||
@@ -279,6 +284,16 @@ impl DiscoveryHandle {
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
/// Sets whether announcements of other devices are answered with a
|
||||
/// register request (the answer is what makes the announcing device
|
||||
/// enter the store). On by default.
|
||||
///
|
||||
/// An application whose server is not running turns this off: the answer
|
||||
/// would advertise an HTTP port that nobody listens on.
|
||||
pub fn set_answer_announcements(&self, answer: bool) {
|
||||
self.state.answering.store(answer, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Puts a device confirmed outside of discovery into the store, e.g. one
|
||||
/// that answered an announcement by registering with this device's HTTP
|
||||
/// server. The confirmation is emitted as `Discovered` or `Updated`;
|
||||
@@ -353,6 +368,7 @@ pub async fn start(config: DiscoveryConfig, stop_rx: oneshot::Receiver<()>) -> D
|
||||
timeout: config.timeout,
|
||||
store: DeviceStore::new(),
|
||||
event_tx: config.event_tx,
|
||||
answering: AtomicBool::new(true),
|
||||
scanning: std::sync::Mutex::new(HashSet::new()),
|
||||
});
|
||||
|
||||
@@ -362,6 +378,9 @@ pub async fn start(config: DiscoveryConfig, stop_rx: oneshot::Receiver<()>) -> D
|
||||
let state = state.clone();
|
||||
async move {
|
||||
while let Some(event) = multicast_rx.recv().await {
|
||||
if !state.answering.load(Ordering::Relaxed) {
|
||||
continue;
|
||||
}
|
||||
let MulticastEvent::Discovered {
|
||||
ip,
|
||||
scope_id,
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'package:localsend_isolates/rust/frb_generated.dart';
|
||||
|
||||
part 'crypto.freezed.dart';
|
||||
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`
|
||||
|
||||
Future<void> verifyCert({required String cert, required String publicKey}) =>
|
||||
RustLib.instance.api.crateApiCryptoVerifyCert(cert: cert, publicKey: publicKey);
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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:localsend_isolates/rust/api/model.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart';
|
||||
import 'package:localsend_isolates/rust/frb_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `rs_device`
|
||||
|
||||
/// Starts the discovery: binds the UDP multicast sockets on all usable
|
||||
/// network interfaces, answers announcements of other devices with an HTTP
|
||||
/// register request, and keeps the store of confirmed devices.
|
||||
///
|
||||
/// Announcements are received from the IPv4 [group] and, as a LocalSend
|
||||
/// extension, from the (currently hardcoded) IPv6 group `ff12::fd3a:e420`.
|
||||
///
|
||||
/// [port] is used both to bind the multicast sockets and as the HTTP server
|
||||
/// port announced to other devices. [cert_pem] and [private_key_pem] are this
|
||||
/// device's TLS identity, sent as client certificate with every register
|
||||
/// request; [fingerprint] must be the certificate's SHA-256 fingerprint.
|
||||
///
|
||||
/// Nothing is announced until [RsDiscovery::announce] is called.
|
||||
///
|
||||
/// Cannot fail besides an invalid [group]: when no multicast socket could be
|
||||
/// bound (e.g. the port is taken by another process), discovery still runs
|
||||
/// without multicast — see [RsDiscovery::multicast_error] — and still learns
|
||||
/// about devices through [RsDiscovery::discover], [RsDiscovery::scan_subnet]
|
||||
/// and [RsDiscovery::add_device].
|
||||
Future<RsDiscovery> startDiscovery({
|
||||
required String group,
|
||||
required int port,
|
||||
List<String>? networkWhitelist,
|
||||
List<String>? networkBlacklist,
|
||||
required String alias,
|
||||
required String version,
|
||||
String? deviceModel,
|
||||
DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
required ProtocolTypeV2 protocol,
|
||||
required bool download,
|
||||
required String certPem,
|
||||
required String privateKeyPem,
|
||||
required BigInt timeoutMs,
|
||||
}) => RustLib.instance.api.crateApiDiscoveryStartDiscovery(
|
||||
group: group,
|
||||
port: port,
|
||||
networkWhitelist: networkWhitelist,
|
||||
networkBlacklist: networkBlacklist,
|
||||
alias: alias,
|
||||
version: version,
|
||||
deviceModel: deviceModel,
|
||||
deviceType: deviceType,
|
||||
fingerprint: fingerprint,
|
||||
protocol: protocol,
|
||||
download: download,
|
||||
certPem: certPem,
|
||||
privateKeyPem: privateKeyPem,
|
||||
timeoutMs: timeoutMs,
|
||||
);
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsDiscovery>>
|
||||
abstract class RsDiscovery implements RustOpaqueInterface {
|
||||
/// Puts a device confirmed outside of the discovery into the store, e.g.
|
||||
/// one that answered an announcement by registering with this device's
|
||||
/// HTTP server. The device is emitted on [RsDiscovery::listen].
|
||||
Future<void> addDevice({required RsDiscoveredDevice device});
|
||||
|
||||
/// Announces this device to the network, which makes every other LocalSend
|
||||
/// device on it register with this device over HTTP.
|
||||
///
|
||||
/// Devices registering in response arrive at the application as server
|
||||
/// events, not here: feed them back via [RsDiscovery::add_device].
|
||||
///
|
||||
/// Returns once the whole announcement burst has been sent, which takes a
|
||||
/// few seconds, or immediately once the discovery has been stopped or
|
||||
/// multicast is unavailable.
|
||||
Future<void> announce();
|
||||
|
||||
/// Discovers a device at a known address, e.g. a favorite or a peer that
|
||||
/// multicast does not reach, by sending it a register request.
|
||||
///
|
||||
/// The confirmed device is also emitted on [RsDiscovery::listen].
|
||||
/// Returns `None` when the device did not answer or answered with this
|
||||
/// device's own fingerprint (i.e. the device discovered itself).
|
||||
Future<RsDiscoveredDevice?> discover({required String host, required int port, required ProtocolTypeV2 protocol});
|
||||
|
||||
/// Emits a [RsDiscoveredDevice] for every device confirmation until the
|
||||
/// discovery is stopped. Can only be listened to once.
|
||||
Stream<RsDiscoveredDevice> listen();
|
||||
|
||||
/// The reason the multicast sockets could not be bound, when they could
|
||||
/// not. Discovery then neither hears nor sends announcements.
|
||||
Future<String?> multicastError();
|
||||
|
||||
/// Scans the `/24` subnet of the local interface address [interface_ip]
|
||||
/// by sending every other host a register request, for networks that do
|
||||
/// not carry multicast.
|
||||
///
|
||||
/// The found devices are emitted on [RsDiscovery::listen] as they answer;
|
||||
/// this method returns once the whole scan has finished. At most one scan
|
||||
/// runs per interface: a call for an address that is still being scanned
|
||||
/// returns immediately.
|
||||
Future<void> scanSubnet({required String interfaceIp, required int port, required ProtocolTypeV2 protocol});
|
||||
|
||||
/// Sets whether announcements of other devices are answered with a
|
||||
/// register request (the answer is what makes the announcing device enter
|
||||
/// the store). On by default.
|
||||
///
|
||||
/// Turned off while the HTTP server is not running: the answer would
|
||||
/// advertise a port that nobody listens on.
|
||||
Future<void> setAnswerAnnouncements({required bool answer});
|
||||
|
||||
/// Stops the discovery, which also ends the [RsDiscovery::listen] stream.
|
||||
/// Returns after all sockets are closed, so the port can be bound again.
|
||||
Future<void> stop();
|
||||
}
|
||||
|
||||
/// A device that was confirmed over HTTP by the discovery: it answered a
|
||||
/// register request, or its register request was accepted by our server and
|
||||
/// was fed back via [RsDiscovery::add_device].
|
||||
///
|
||||
/// Emitted on [RsDiscovery::listen] for every confirmation, so a device
|
||||
/// re-appears whenever it re-announces itself or is re-discovered.
|
||||
class RsDiscoveredDevice {
|
||||
final String alias;
|
||||
|
||||
/// Protocol version (major.minor) implemented by the device.
|
||||
final String version;
|
||||
final String? deviceModel;
|
||||
final DeviceType? deviceType;
|
||||
|
||||
/// Fingerprint identifying the device; devices are deduplicated by it.
|
||||
final String fingerprint;
|
||||
|
||||
/// The host the device was confirmed on: an IP address, or the scoped
|
||||
/// form `fe80::1%3` for link-local IPv6 (the Rust HTTP client accepts
|
||||
/// both back as a host).
|
||||
final String host;
|
||||
|
||||
/// The port of the device's HTTP server.
|
||||
final int port;
|
||||
final ProtocolTypeV2 protocol;
|
||||
|
||||
/// Whether the device's download API is active.
|
||||
final bool download;
|
||||
|
||||
const RsDiscoveredDevice({
|
||||
required this.alias,
|
||||
required this.version,
|
||||
this.deviceModel,
|
||||
this.deviceType,
|
||||
required this.fingerprint,
|
||||
required this.host,
|
||||
required this.port,
|
||||
required this.protocol,
|
||||
required this.download,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
alias.hashCode ^
|
||||
version.hashCode ^
|
||||
deviceModel.hashCode ^
|
||||
deviceType.hashCode ^
|
||||
fingerprint.hashCode ^
|
||||
host.hashCode ^
|
||||
port.hashCode ^
|
||||
protocol.hashCode ^
|
||||
download.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RsDiscoveredDevice &&
|
||||
runtimeType == other.runtimeType &&
|
||||
alias == other.alias &&
|
||||
version == other.version &&
|
||||
deviceModel == other.deviceModel &&
|
||||
deviceType == other.deviceType &&
|
||||
fingerprint == other.fingerprint &&
|
||||
host == other.host &&
|
||||
port == other.port &&
|
||||
protocol == other.protocol &&
|
||||
download == other.download;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// 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:localsend_isolates/rust/api/model.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart';
|
||||
import 'package:localsend_isolates/rust/frb_generated.dart';
|
||||
|
||||
/// Starts UDP multicast discovery: binds the multicast sockets on all usable
|
||||
/// network interfaces and listens for announcements of other devices.
|
||||
///
|
||||
/// Announcements are sent to the IPv4 [group] and, as a LocalSend extension,
|
||||
/// to the (currently hardcoded) IPv6 group `ff12::fd3a:e420`.
|
||||
///
|
||||
/// [port] is used both to bind the multicast sockets and as the HTTP server
|
||||
/// port announced to other devices.
|
||||
///
|
||||
/// Nothing is announced until [RsMulticast::announce] is called.
|
||||
///
|
||||
/// Fails when [group] is not a valid IPv4 address or when no network
|
||||
/// interface could be used at all.
|
||||
Future<RsMulticast> startMulticast({
|
||||
required String group,
|
||||
required int port,
|
||||
List<String>? networkWhitelist,
|
||||
List<String>? networkBlacklist,
|
||||
required String alias,
|
||||
required String version,
|
||||
String? deviceModel,
|
||||
DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
required ProtocolTypeV2 protocol,
|
||||
required bool download,
|
||||
}) => RustLib.instance.api.crateApiMulticastStartMulticast(
|
||||
group: group,
|
||||
port: port,
|
||||
networkWhitelist: networkWhitelist,
|
||||
networkBlacklist: networkBlacklist,
|
||||
alias: alias,
|
||||
version: version,
|
||||
deviceModel: deviceModel,
|
||||
deviceType: deviceType,
|
||||
fingerprint: fingerprint,
|
||||
protocol: protocol,
|
||||
download: download,
|
||||
);
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RsMulticast>>
|
||||
abstract class RsMulticast implements RustOpaqueInterface {
|
||||
/// Announces this device to the network, which makes every other LocalSend
|
||||
/// device on it register with this device over HTTP.
|
||||
///
|
||||
/// Returns once the whole announcement burst has been sent, which takes a
|
||||
/// few seconds, or immediately once discovery has been stopped.
|
||||
Future<void> announce();
|
||||
|
||||
/// Emits a [RsMulticastDiscovered] for every announcement received from
|
||||
/// another device until discovery is stopped.
|
||||
/// Can only be listened to once.
|
||||
Stream<RsMulticastDiscovered> listen();
|
||||
|
||||
/// Stops discovery, which also ends the [RsMulticast::listen] stream.
|
||||
/// Returns after all sockets are closed, so the port can be bound again.
|
||||
Future<void> stop();
|
||||
}
|
||||
|
||||
class MulticastMessageV2 {
|
||||
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 MulticastMessageV2({
|
||||
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 MulticastMessageV2 &&
|
||||
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;
|
||||
}
|
||||
|
||||
/// Another device announced itself via UDP multicast.
|
||||
///
|
||||
/// The peer expects to be answered with an HTTP register request.
|
||||
class RsMulticastDiscovered {
|
||||
/// The address the announcement was sent from. The peer's HTTP server is
|
||||
/// reachable at this address on `message.port`.
|
||||
///
|
||||
/// A link-local IPv6 source carries its interface scope as `fe80::1%3`,
|
||||
/// which the Rust HTTP client accepts back as a host.
|
||||
final String ip;
|
||||
|
||||
/// The announcement as it was received.
|
||||
final MulticastMessageV2 message;
|
||||
|
||||
const RsMulticastDiscovered({
|
||||
required this.ip,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => ip.hashCode ^ message.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) || other is RsMulticastDiscovered && runtimeType == other.runtimeType && ip == other.ip && message == other.message;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,10 +10,10 @@ import 'dart:ffi' as ffi;
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
import 'package:localsend_isolates/rust/api/cancel.dart';
|
||||
import 'package:localsend_isolates/rust/api/crypto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/logging.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart';
|
||||
import 'package:localsend_isolates/rust/api/multicast.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart';
|
||||
import 'package:localsend_isolates/rust/api/stream.dart';
|
||||
import 'package:localsend_isolates/rust/api/webrtc.dart';
|
||||
@@ -52,15 +52,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsCancellationTokenPtr =>
|
||||
wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationTokenPtr;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsDiscoveryPtr =>
|
||||
wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscoveryPtr;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsHttpClientPtr =>
|
||||
wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClientPtr;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsHttpServerPtr =>
|
||||
wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServerPtr;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsMulticastPtr =>
|
||||
wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticastPtr;
|
||||
|
||||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@@ -88,15 +88,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
Dart2RustStreamSink dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(dynamic raw);
|
||||
|
||||
@@ -121,15 +121,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<void> Function(LsSignalingConnection)
|
||||
dco_decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException(
|
||||
@@ -169,15 +169,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
Set<String> dco_decode_Set_String_None(dynamic raw);
|
||||
|
||||
@@ -193,10 +193,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustStreamSink<Uint8List> dco_decode_StreamSink_list_prim_u_8_strict_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsHashFileEvent> dco_decode_StreamSink_rs_hash_file_event_Sse(dynamic raw);
|
||||
RustStreamSink<RsDiscoveredDevice> dco_decode_StreamSink_rs_discovered_device_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsMulticastDiscovered> dco_decode_StreamSink_rs_multicast_discovered_Sse(dynamic raw);
|
||||
RustStreamSink<RsHashFileEvent> dco_decode_StreamSink_rs_hash_file_event_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsServerEvent> dco_decode_StreamSink_rs_server_event_Sse(dynamic raw);
|
||||
@@ -263,6 +263,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RegisterDtoV2 dco_decode_box_autoadd_register_dto_v_2(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice dco_decode_box_autoadd_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
RTCSendFileResponse dco_decode_box_autoadd_rtc_send_file_response(dynamic raw);
|
||||
|
||||
@@ -332,9 +335,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
LsHttpClientVersion dco_decode_ls_http_client_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
MulticastMessageV2 dco_decode_multicast_message_v_2(dynamic raw);
|
||||
|
||||
@protected
|
||||
String? dco_decode_opt_String(dynamic raw);
|
||||
|
||||
@@ -360,6 +360,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PrepareUploadResponseDto? dco_decode_opt_box_autoadd_prepare_upload_response_dto(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice? dco_decode_opt_box_autoadd_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
TlsConfig? dco_decode_opt_box_autoadd_tls_config(dynamic raw);
|
||||
|
||||
@@ -420,15 +423,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ResultWithPublicKeyRegisterResponseDto dco_decode_result_with_public_key_register_response_dto(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice dco_decode_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHashFileEvent dco_decode_rs_hash_file_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClientError dco_decode_rs_http_client_error(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticastDiscovered dco_decode_rs_multicast_discovered(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsServerEvent dco_decode_rs_server_event(dynamic raw);
|
||||
|
||||
@@ -519,15 +522,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Dart2RustStreamSink sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(
|
||||
SseDeserializer deserializer,
|
||||
@@ -562,15 +565,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||
|
||||
@@ -606,15 +609,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Set<String> sse_decode_Set_String_None(SseDeserializer deserializer);
|
||||
|
||||
@@ -630,10 +633,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustStreamSink<Uint8List> sse_decode_StreamSink_list_prim_u_8_strict_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsHashFileEvent> sse_decode_StreamSink_rs_hash_file_event_Sse(SseDeserializer deserializer);
|
||||
RustStreamSink<RsDiscoveredDevice> sse_decode_StreamSink_rs_discovered_device_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsMulticastDiscovered> sse_decode_StreamSink_rs_multicast_discovered_Sse(SseDeserializer deserializer);
|
||||
RustStreamSink<RsHashFileEvent> sse_decode_StreamSink_rs_hash_file_event_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsServerEvent> sse_decode_StreamSink_rs_server_event_Sse(SseDeserializer deserializer);
|
||||
@@ -700,6 +703,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RegisterDtoV2 sse_decode_box_autoadd_register_dto_v_2(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice sse_decode_box_autoadd_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RTCSendFileResponse sse_decode_box_autoadd_rtc_send_file_response(SseDeserializer deserializer);
|
||||
|
||||
@@ -769,9 +775,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
LsHttpClientVersion sse_decode_ls_http_client_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
MulticastMessageV2 sse_decode_multicast_message_v_2(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
||||
|
||||
@@ -799,6 +802,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PrepareUploadResponseDto? sse_decode_opt_box_autoadd_prepare_upload_response_dto(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice? sse_decode_opt_box_autoadd_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TlsConfig? sse_decode_opt_box_autoadd_tls_config(SseDeserializer deserializer);
|
||||
|
||||
@@ -859,15 +865,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ResultWithPublicKeyRegisterResponseDto sse_decode_result_with_public_key_register_response_dto(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice sse_decode_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHashFileEvent sse_decode_rs_hash_file_event(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClientError sse_decode_rs_http_client_error(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticastDiscovered sse_decode_rs_multicast_discovered(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsServerEvent sse_decode_rs_server_event(SseDeserializer deserializer);
|
||||
|
||||
@@ -970,15 +976,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(
|
||||
Dart2RustStreamSink self,
|
||||
@@ -1024,15 +1030,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException(
|
||||
@@ -1088,15 +1094,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Set_String_None(Set<String> self, SseSerializer serializer);
|
||||
|
||||
@@ -1113,10 +1119,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
void sse_encode_StreamSink_list_prim_u_8_strict_Sse(RustStreamSink<Uint8List> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_hash_file_event_Sse(RustStreamSink<RsHashFileEvent> self, SseSerializer serializer);
|
||||
void sse_encode_StreamSink_rs_discovered_device_Sse(RustStreamSink<RsDiscoveredDevice> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_multicast_discovered_Sse(RustStreamSink<RsMulticastDiscovered> self, SseSerializer serializer);
|
||||
void sse_encode_StreamSink_rs_hash_file_event_Sse(RustStreamSink<RsHashFileEvent> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_server_event_Sse(RustStreamSink<RsServerEvent> self, SseSerializer serializer);
|
||||
@@ -1184,6 +1190,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_register_dto_v_2(RegisterDtoV2 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_rs_discovered_device(RsDiscoveredDevice self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_rtc_send_file_response(RTCSendFileResponse self, SseSerializer serializer);
|
||||
|
||||
@@ -1253,9 +1262,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_ls_http_client_version(LsHttpClientVersion self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_multicast_message_v_2(MulticastMessageV2 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
||||
|
||||
@@ -1283,6 +1289,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_prepare_upload_response_dto(PrepareUploadResponseDto? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_rs_discovered_device(RsDiscoveredDevice? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_tls_config(TlsConfig? self, SseSerializer serializer);
|
||||
|
||||
@@ -1344,15 +1353,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_result_with_public_key_register_response_dto(ResultWithPublicKeyRegisterResponseDto self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_discovered_device(RsDiscoveredDevice self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_hash_file_event(RsHashFileEvent self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_http_client_error(RsHttpClientError self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_multicast_discovered(RsMulticastDiscovered self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_server_event(RsServerEvent self, SseSerializer serializer);
|
||||
|
||||
@@ -1672,6 +1681,38 @@ class RustLibWire implements BaseWire {
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationTokenPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscoveryPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_localsend_isolates_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery',
|
||||
);
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscoveryPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscoveryPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_localsend_isolates_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery',
|
||||
);
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscoveryPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
@@ -1735,36 +1776,4 @@ class RustLibWire implements BaseWire {
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServerPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticastPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_localsend_isolates_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast',
|
||||
);
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticastPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticastPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_localsend_isolates_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast',
|
||||
);
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticastPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import 'dart:convert';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||
import 'package:localsend_isolates/rust/api/cancel.dart';
|
||||
import 'package:localsend_isolates/rust/api/crypto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/logging.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart';
|
||||
import 'package:localsend_isolates/rust/api/multicast.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart';
|
||||
import 'package:localsend_isolates/rust/api/stream.dart';
|
||||
import 'package:localsend_isolates/rust/api/webrtc.dart';
|
||||
@@ -54,15 +54,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsCancellationTokenPtr =>
|
||||
wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsDiscoveryPtr =>
|
||||
wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsHttpClientPtr =>
|
||||
wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsHttpServerPtr =>
|
||||
wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer;
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_RsMulticastPtr =>
|
||||
wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast;
|
||||
|
||||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@@ -90,15 +90,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
Dart2RustStreamSink dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(dynamic raw);
|
||||
|
||||
@@ -123,15 +123,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<void> Function(LsSignalingConnection)
|
||||
dco_decode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException(
|
||||
@@ -171,15 +171,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscovery dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClient dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpServer dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticast dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(dynamic raw);
|
||||
|
||||
@protected
|
||||
Set<String> dco_decode_Set_String_None(dynamic raw);
|
||||
|
||||
@@ -195,10 +195,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustStreamSink<Uint8List> dco_decode_StreamSink_list_prim_u_8_strict_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsHashFileEvent> dco_decode_StreamSink_rs_hash_file_event_Sse(dynamic raw);
|
||||
RustStreamSink<RsDiscoveredDevice> dco_decode_StreamSink_rs_discovered_device_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsMulticastDiscovered> dco_decode_StreamSink_rs_multicast_discovered_Sse(dynamic raw);
|
||||
RustStreamSink<RsHashFileEvent> dco_decode_StreamSink_rs_hash_file_event_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsServerEvent> dco_decode_StreamSink_rs_server_event_Sse(dynamic raw);
|
||||
@@ -265,6 +265,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RegisterDtoV2 dco_decode_box_autoadd_register_dto_v_2(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice dco_decode_box_autoadd_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
RTCSendFileResponse dco_decode_box_autoadd_rtc_send_file_response(dynamic raw);
|
||||
|
||||
@@ -334,9 +337,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
LsHttpClientVersion dco_decode_ls_http_client_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
MulticastMessageV2 dco_decode_multicast_message_v_2(dynamic raw);
|
||||
|
||||
@protected
|
||||
String? dco_decode_opt_String(dynamic raw);
|
||||
|
||||
@@ -362,6 +362,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PrepareUploadResponseDto? dco_decode_opt_box_autoadd_prepare_upload_response_dto(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice? dco_decode_opt_box_autoadd_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
TlsConfig? dco_decode_opt_box_autoadd_tls_config(dynamic raw);
|
||||
|
||||
@@ -422,15 +425,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ResultWithPublicKeyRegisterResponseDto dco_decode_result_with_public_key_register_response_dto(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice dco_decode_rs_discovered_device(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHashFileEvent dco_decode_rs_hash_file_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsHttpClientError dco_decode_rs_http_client_error(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsMulticastDiscovered dco_decode_rs_multicast_discovered(dynamic raw);
|
||||
|
||||
@protected
|
||||
RsServerEvent dco_decode_rs_server_event(dynamic raw);
|
||||
|
||||
@@ -521,15 +524,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Dart2RustStreamSink sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(
|
||||
SseDeserializer deserializer,
|
||||
@@ -564,15 +567,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||
|
||||
@@ -608,15 +611,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RsCancellationToken sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscovery sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClient sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpServer sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticast sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Set<String> sse_decode_Set_String_None(SseDeserializer deserializer);
|
||||
|
||||
@@ -632,10 +635,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustStreamSink<Uint8List> sse_decode_StreamSink_list_prim_u_8_strict_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsHashFileEvent> sse_decode_StreamSink_rs_hash_file_event_Sse(SseDeserializer deserializer);
|
||||
RustStreamSink<RsDiscoveredDevice> sse_decode_StreamSink_rs_discovered_device_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsMulticastDiscovered> sse_decode_StreamSink_rs_multicast_discovered_Sse(SseDeserializer deserializer);
|
||||
RustStreamSink<RsHashFileEvent> sse_decode_StreamSink_rs_hash_file_event_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<RsServerEvent> sse_decode_StreamSink_rs_server_event_Sse(SseDeserializer deserializer);
|
||||
@@ -702,6 +705,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RegisterDtoV2 sse_decode_box_autoadd_register_dto_v_2(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice sse_decode_box_autoadd_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RTCSendFileResponse sse_decode_box_autoadd_rtc_send_file_response(SseDeserializer deserializer);
|
||||
|
||||
@@ -771,9 +777,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
LsHttpClientVersion sse_decode_ls_http_client_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
MulticastMessageV2 sse_decode_multicast_message_v_2(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
||||
|
||||
@@ -801,6 +804,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PrepareUploadResponseDto? sse_decode_opt_box_autoadd_prepare_upload_response_dto(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice? sse_decode_opt_box_autoadd_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TlsConfig? sse_decode_opt_box_autoadd_tls_config(SseDeserializer deserializer);
|
||||
|
||||
@@ -861,15 +867,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ResultWithPublicKeyRegisterResponseDto sse_decode_result_with_public_key_register_response_dto(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsDiscoveredDevice sse_decode_rs_discovered_device(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHashFileEvent sse_decode_rs_hash_file_event(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsHttpClientError sse_decode_rs_http_client_error(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsMulticastDiscovered sse_decode_rs_multicast_discovered(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RsServerEvent sse_decode_rs_server_event(SseDeserializer deserializer);
|
||||
|
||||
@@ -972,15 +978,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDart2RustStreamSink(
|
||||
Dart2RustStreamSink self,
|
||||
@@ -1026,15 +1032,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerLsSignalingConnection_Output_unit_AnyhowException(
|
||||
@@ -1090,15 +1096,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(RsDiscovery self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(RsHttpClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(RsHttpServer self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(RsMulticast self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_Set_String_None(Set<String> self, SseSerializer serializer);
|
||||
|
||||
@@ -1115,10 +1121,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
void sse_encode_StreamSink_list_prim_u_8_strict_Sse(RustStreamSink<Uint8List> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_hash_file_event_Sse(RustStreamSink<RsHashFileEvent> self, SseSerializer serializer);
|
||||
void sse_encode_StreamSink_rs_discovered_device_Sse(RustStreamSink<RsDiscoveredDevice> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_multicast_discovered_Sse(RustStreamSink<RsMulticastDiscovered> self, SseSerializer serializer);
|
||||
void sse_encode_StreamSink_rs_hash_file_event_Sse(RustStreamSink<RsHashFileEvent> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_rs_server_event_Sse(RustStreamSink<RsServerEvent> self, SseSerializer serializer);
|
||||
@@ -1186,6 +1192,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_register_dto_v_2(RegisterDtoV2 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_rs_discovered_device(RsDiscoveredDevice self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_rtc_send_file_response(RTCSendFileResponse self, SseSerializer serializer);
|
||||
|
||||
@@ -1255,9 +1264,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_ls_http_client_version(LsHttpClientVersion self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_multicast_message_v_2(MulticastMessageV2 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
||||
|
||||
@@ -1285,6 +1291,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_prepare_upload_response_dto(PrepareUploadResponseDto? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_rs_discovered_device(RsDiscoveredDevice? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_tls_config(TlsConfig? self, SseSerializer serializer);
|
||||
|
||||
@@ -1346,15 +1355,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_result_with_public_key_register_response_dto(ResultWithPublicKeyRegisterResponseDto self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_discovered_device(RsDiscoveredDevice self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_hash_file_event(RsHashFileEvent self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_http_client_error(RsHttpClientError self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_multicast_discovered(RsMulticastDiscovered self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rs_server_event(RsServerEvent self, SseSerializer serializer);
|
||||
|
||||
@@ -1460,6 +1469,12 @@ class RustLibWire implements BaseWire {
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(int ptr) =>
|
||||
wasmModule.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(ptr);
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(int ptr) =>
|
||||
wasmModule.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(ptr);
|
||||
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(int ptr) =>
|
||||
wasmModule.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(ptr);
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(int ptr) =>
|
||||
wasmModule.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(ptr);
|
||||
|
||||
@@ -1471,12 +1486,6 @@ class RustLibWire implements BaseWire {
|
||||
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(int ptr) =>
|
||||
wasmModule.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(ptr);
|
||||
|
||||
void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(int ptr) =>
|
||||
wasmModule.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(ptr);
|
||||
|
||||
void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(int ptr) =>
|
||||
wasmModule.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(ptr);
|
||||
}
|
||||
|
||||
@JS('wasm_bindgen')
|
||||
@@ -1517,6 +1526,10 @@ extension type RustLibWasmModule._(JSObject _) implements JSObject {
|
||||
|
||||
external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsCancellationToken(int ptr);
|
||||
|
||||
external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(int ptr);
|
||||
|
||||
external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery(int ptr);
|
||||
|
||||
external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(int ptr);
|
||||
|
||||
external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpClient(int ptr);
|
||||
@@ -1524,8 +1537,4 @@ extension type RustLibWasmModule._(JSObject _) implements JSObject {
|
||||
external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(int ptr);
|
||||
|
||||
external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer(int ptr);
|
||||
|
||||
external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(int ptr);
|
||||
|
||||
external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsMulticast(int ptr);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/main.dart';
|
||||
import 'package:localsend_isolates/src/isolate/dto/send_to_isolate_data.dart';
|
||||
import 'package:localsend_isolates/src/task/discovery/discovery.dart';
|
||||
import 'package:typed_isolates/typed_isolates.dart';
|
||||
|
||||
sealed class DiscoveryTask {}
|
||||
|
||||
/// Starts the discovery and streams every confirmed device.
|
||||
/// The stream never completes; it survives [DiscoveryRestartTask]s.
|
||||
class DiscoveryListenTask implements DiscoveryTask {}
|
||||
|
||||
/// Sends an announcement to all devices on all network interfaces.
|
||||
/// They will respond by registering with this device's HTTP server.
|
||||
class DiscoveryAnnouncementTask implements DiscoveryTask {}
|
||||
|
||||
/// Restarts the discovery, e.g. after the port or the network settings changed.
|
||||
class DiscoveryRestartTask implements DiscoveryTask {}
|
||||
|
||||
/// Scans the subnet of one network interface over HTTP.
|
||||
/// Completes (without events) when the scan is finished; the found devices
|
||||
/// arrive on the [DiscoveryListenTask] stream.
|
||||
class DiscoverySubnetScanTask implements DiscoveryTask {
|
||||
final String networkInterface;
|
||||
final int port;
|
||||
final bool https;
|
||||
|
||||
DiscoverySubnetScanTask({
|
||||
required this.networkInterface,
|
||||
required this.port,
|
||||
required this.https,
|
||||
});
|
||||
}
|
||||
|
||||
/// Probes the known addresses of the favorites over HTTP.
|
||||
/// Completes (without events) when every favorite has been probed; the found
|
||||
/// devices arrive on the [DiscoveryListenTask] stream.
|
||||
class DiscoveryFavoriteScanTask implements DiscoveryTask {
|
||||
final List<(String, int)> favorites;
|
||||
final bool https;
|
||||
|
||||
DiscoveryFavoriteScanTask({
|
||||
required this.favorites,
|
||||
required this.https,
|
||||
});
|
||||
}
|
||||
|
||||
/// Feeds a device confirmed outside of the discovery into the store, e.g. one
|
||||
/// that registered with this device's HTTP server. The device comes back on
|
||||
/// the [DiscoveryListenTask] stream.
|
||||
class DiscoveryAddDeviceTask implements DiscoveryTask {
|
||||
final Device device;
|
||||
|
||||
DiscoveryAddDeviceTask({
|
||||
required this.device,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setupDiscoveryIsolate(
|
||||
Stream<SendToIsolateData<IsolateTask<DiscoveryTask>>> receiveFromMain,
|
||||
void Function(IsolateTaskStreamResult<Device>) sendToMain,
|
||||
InitialData initialData,
|
||||
) async {
|
||||
await setupChildIsolateHelper(
|
||||
debugLabel: 'DiscoveryIsolate',
|
||||
receiveFromMain: receiveFromMain,
|
||||
sendToMain: sendToMain,
|
||||
initialData: initialData,
|
||||
handler: (ref, task) async {
|
||||
switch (task.data) {
|
||||
case DiscoveryListenTask():
|
||||
await for (final device in ref.read(discoveryProvider).startListener()) {
|
||||
sendToMain(
|
||||
IsolateTaskStreamResult.event(
|
||||
id: task.id,
|
||||
data: device,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
case DiscoveryAnnouncementTask():
|
||||
await ref.read(discoveryProvider).sendAnnouncement();
|
||||
break;
|
||||
case DiscoveryRestartTask():
|
||||
ref.read(discoveryProvider).restartListener();
|
||||
break;
|
||||
case DiscoverySubnetScanTask data:
|
||||
await ref
|
||||
.read(discoveryProvider)
|
||||
.scanSubnet(
|
||||
networkInterface: data.networkInterface,
|
||||
port: data.port,
|
||||
https: data.https,
|
||||
);
|
||||
break;
|
||||
case DiscoveryFavoriteScanTask data:
|
||||
await ref
|
||||
.read(discoveryProvider)
|
||||
.discoverFavorites(
|
||||
devices: data.favorites,
|
||||
https: data.https,
|
||||
);
|
||||
break;
|
||||
case DiscoveryAddDeviceTask data:
|
||||
await ref.read(discoveryProvider).addDevice(data.device);
|
||||
break;
|
||||
}
|
||||
sendToMain(
|
||||
IsolateTaskStreamResult.done(
|
||||
id: task.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -6,23 +6,9 @@ class HttpClientCollection {
|
||||
final String _privateKey;
|
||||
final String _certificate;
|
||||
|
||||
/// A client that accepts any valid peer certificate.
|
||||
///
|
||||
/// Only for discovery, where the certificate of the peer is not known yet
|
||||
/// and is learned from the response. Never use this to transfer files:
|
||||
/// it cannot tell the discovered device apart from anyone else answering
|
||||
/// on that address.
|
||||
final RsHttpClient discovery;
|
||||
|
||||
/// The request timeout of the [discovery] client.
|
||||
/// Also apply it to short-lived pinned requests made during discovery.
|
||||
final int discoveryTimeout;
|
||||
|
||||
HttpClientCollection({
|
||||
required String privateKey,
|
||||
required String certificate,
|
||||
required this.discovery,
|
||||
required this.discoveryTimeout,
|
||||
}) : _privateKey = privateKey,
|
||||
_certificate = certificate;
|
||||
|
||||
@@ -48,19 +34,9 @@ class HttpClientCollection {
|
||||
}
|
||||
|
||||
final httpProvider = ViewProvider((ref) {
|
||||
final (securityContext, discoveryTimeout) = ref.watch(
|
||||
syncProvider.select((state) => (state.securityContext, state.discoveryTimeout)),
|
||||
);
|
||||
final securityContext = ref.watch(syncProvider.select((state) => state.securityContext));
|
||||
return HttpClientCollection(
|
||||
privateKey: securityContext.privateKey,
|
||||
certificate: securityContext.certificate,
|
||||
discovery: createClient(
|
||||
privateKey: securityContext.privateKey,
|
||||
cert: securityContext.certificate,
|
||||
version: LsHttpClientVersion.v2,
|
||||
expectedFingerprint: null,
|
||||
timeoutMs: discoveryTimeout,
|
||||
),
|
||||
discoveryTimeout: discoveryTimeout,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/main.dart';
|
||||
import 'package:localsend_isolates/src/isolate/dto/send_to_isolate_data.dart';
|
||||
import 'package:localsend_isolates/src/task/discovery/http_scan_discovery.dart';
|
||||
import 'package:typed_isolates/typed_isolates.dart';
|
||||
|
||||
sealed class HttpScanTask {}
|
||||
|
||||
class HttpInterfaceScanTask implements HttpScanTask {
|
||||
final String networkInterface;
|
||||
final int port;
|
||||
final bool https;
|
||||
|
||||
HttpInterfaceScanTask({
|
||||
required this.networkInterface,
|
||||
required this.port,
|
||||
required this.https,
|
||||
});
|
||||
}
|
||||
|
||||
class HttpFavoriteScanTask implements HttpScanTask {
|
||||
final List<(String, int)> favorites;
|
||||
final bool https;
|
||||
|
||||
HttpFavoriteScanTask({
|
||||
required this.favorites,
|
||||
required this.https,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setupHttpScanDiscoveryIsolate(
|
||||
Stream<SendToIsolateData<IsolateTask<HttpScanTask>>> receiveFromMain,
|
||||
void Function(IsolateTaskStreamResult<Device>) sendToMain,
|
||||
InitialData initialData,
|
||||
) async {
|
||||
await setupChildIsolateHelper(
|
||||
debugLabel: 'HttpScanDiscoveryIsolate',
|
||||
receiveFromMain: receiveFromMain,
|
||||
sendToMain: sendToMain,
|
||||
initialData: initialData,
|
||||
handler: (ref, task) async {
|
||||
final stream = switch (task.data) {
|
||||
HttpInterfaceScanTask data =>
|
||||
ref
|
||||
.read(httpScanDiscoveryProvider)
|
||||
.getStream(
|
||||
networkInterface: data.networkInterface,
|
||||
port: data.port,
|
||||
https: data.https,
|
||||
),
|
||||
HttpFavoriteScanTask data =>
|
||||
ref
|
||||
.read(httpScanDiscoveryProvider)
|
||||
.getFavoriteStream(
|
||||
devices: data.favorites,
|
||||
https: data.https,
|
||||
),
|
||||
};
|
||||
await for (final device in stream) {
|
||||
sendToMain(
|
||||
IsolateTaskStreamResult.event(
|
||||
id: task.id,
|
||||
data: device,
|
||||
),
|
||||
);
|
||||
}
|
||||
sendToMain(
|
||||
IsolateTaskStreamResult.done(
|
||||
id: task.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/main.dart';
|
||||
import 'package:localsend_isolates/src/isolate/dto/send_to_isolate_data.dart';
|
||||
import 'package:localsend_isolates/src/task/discovery/multicast_discovery.dart';
|
||||
|
||||
sealed class MulticastTask {}
|
||||
|
||||
/// Sends an announcement to all devices to all network interfaces.
|
||||
/// They will respond with their device information.
|
||||
///
|
||||
/// This is not wrapped in an [IsolateTask] because
|
||||
/// - (1) it is not important if the device was found by this specific announcement or another one
|
||||
/// - (2) it is not 100% accurate to know if a device was found by this announcement or another one
|
||||
class MulticastAnnouncementTask implements MulticastTask {
|
||||
static const instance = MulticastAnnouncementTask._();
|
||||
|
||||
const MulticastAnnouncementTask._();
|
||||
}
|
||||
|
||||
/// Restarts the listener.
|
||||
class MulticastRestartListenerTask implements MulticastTask {
|
||||
static const instance = MulticastRestartListenerTask._();
|
||||
|
||||
const MulticastRestartListenerTask._();
|
||||
}
|
||||
|
||||
Future<void> setupMulticastDiscoveryIsolate(
|
||||
Stream<SendToIsolateData<MulticastTask>> receiveFromMain,
|
||||
void Function(Device) sendToMain,
|
||||
InitialData initialData,
|
||||
) async {
|
||||
await setupChildIsolateHelper(
|
||||
debugLabel: 'MulticastDiscoveryIsolate',
|
||||
receiveFromMain: receiveFromMain,
|
||||
sendToMain: sendToMain,
|
||||
initialData: initialData,
|
||||
init: (ref) async {
|
||||
ref.read(multicastDiscoveryProvider).startListener().listen((event) {
|
||||
sendToMain(event);
|
||||
});
|
||||
},
|
||||
handler: (ref, task) async {
|
||||
switch (task) {
|
||||
case MulticastAnnouncementTask():
|
||||
await ref.read(multicastDiscoveryProvider).sendAnnouncement();
|
||||
break;
|
||||
case MulticastRestartListenerTask():
|
||||
ref.read(multicastDiscoveryProvider).restartListener();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart' show WebSendParams;
|
||||
import 'package:localsend_isolates/src/isolate/child/http_scan_discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/multicast_discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/server_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/upload_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/dto/send_to_isolate_data.dart';
|
||||
@@ -12,12 +11,37 @@ import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:typed_isolates/id.dart';
|
||||
import 'package:typed_isolates/typed_isolates.dart';
|
||||
|
||||
class IsolateInterfaceHttpDiscoveryAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<Device>> {
|
||||
/// Starts the discovery and returns the stream of confirmed devices:
|
||||
/// answered announcements, scan results and devices fed in via
|
||||
/// [IsolateDiscoveryAddDeviceAction] all arrive on this one stream.
|
||||
/// The stream never completes; it survives [IsolateDiscoveryRestartAction]s.
|
||||
class IsolateDiscoveryListenAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<Device>> {
|
||||
@override
|
||||
(ParentIsolateState, Stream<Device>) reduce() {
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
return (
|
||||
state,
|
||||
connection.sendWrappedTaskAndListenStream(
|
||||
task: DiscoveryListenTask(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the subnet of one network interface over HTTP,
|
||||
/// for networks that do not carry multicast.
|
||||
/// The returned stream completes (without events) when the scan is finished;
|
||||
/// the found devices arrive on the [IsolateDiscoveryListenAction] stream.
|
||||
class IsolateDiscoverySubnetScanAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<Device>> {
|
||||
final String networkInterface;
|
||||
final int port;
|
||||
final bool https;
|
||||
|
||||
IsolateInterfaceHttpDiscoveryAction({
|
||||
IsolateDiscoverySubnetScanAction({
|
||||
required this.networkInterface,
|
||||
required this.port,
|
||||
required this.https,
|
||||
@@ -25,68 +49,71 @@ class IsolateInterfaceHttpDiscoveryAction extends ReduxActionWithResult<IsolateC
|
||||
|
||||
@override
|
||||
(ParentIsolateState, Stream<Device>) reduce() {
|
||||
final connection = state.httpScanDiscovery;
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('httpScanDiscovery is not initialized');
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
final task = HttpInterfaceScanTask(
|
||||
networkInterface: networkInterface,
|
||||
port: port,
|
||||
https: https,
|
||||
);
|
||||
|
||||
return (
|
||||
state,
|
||||
connection.sendWrappedTaskAndListenStream(
|
||||
task: task,
|
||||
task: DiscoverySubnetScanTask(
|
||||
networkInterface: networkInterface,
|
||||
port: port,
|
||||
https: https,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IsolateFavoriteHttpDiscoveryAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<Device>> {
|
||||
/// Probes the known addresses of the favorites over HTTP.
|
||||
/// The returned stream completes (without events) when every favorite has been
|
||||
/// probed; the found devices arrive on the [IsolateDiscoveryListenAction] stream.
|
||||
class IsolateDiscoveryFavoriteScanAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<Device>> {
|
||||
final List<(String, int)> favorites;
|
||||
final bool https;
|
||||
|
||||
IsolateFavoriteHttpDiscoveryAction({
|
||||
IsolateDiscoveryFavoriteScanAction({
|
||||
required this.favorites,
|
||||
required this.https,
|
||||
});
|
||||
|
||||
@override
|
||||
(ParentIsolateState, Stream<Device>) reduce() {
|
||||
final connection = state.httpScanDiscovery;
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('httpScanDiscovery is not initialized');
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
final task = HttpFavoriteScanTask(
|
||||
favorites: favorites,
|
||||
https: https,
|
||||
);
|
||||
|
||||
return (
|
||||
state,
|
||||
connection.sendWrappedTaskAndListenStream(
|
||||
task: task,
|
||||
task: DiscoveryFavoriteScanTask(
|
||||
favorites: favorites,
|
||||
https: https,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IsolateSendMulticastAnnouncementAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
/// Sends an announcement which makes every other LocalSend device on the
|
||||
/// network register with this device's HTTP server.
|
||||
class IsolateDiscoveryAnnouncementAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
@override
|
||||
ParentIsolateState reduce() {
|
||||
final connection = state.multicastDiscovery;
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('multicastDiscovery is not initialized');
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
connection.sendToIsolate(
|
||||
SendToIsolateData(
|
||||
syncState: null,
|
||||
data: MulticastAnnouncementTask.instance,
|
||||
data: IsolateTask(
|
||||
data: DiscoveryAnnouncementTask(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -94,18 +121,53 @@ class IsolateSendMulticastAnnouncementAction extends ReduxAction<IsolateControll
|
||||
}
|
||||
}
|
||||
|
||||
class IsolateSendMulticastRestartListenerAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
/// Restarts the discovery, e.g. after the port or the network settings changed.
|
||||
class IsolateDiscoveryRestartAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
@override
|
||||
ParentIsolateState reduce() {
|
||||
final connection = state.multicastDiscovery;
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('multicastDiscovery is not initialized');
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
connection.sendToIsolate(
|
||||
SendToIsolateData(
|
||||
syncState: null,
|
||||
data: MulticastRestartListenerTask.instance,
|
||||
data: IsolateTask(
|
||||
data: DiscoveryRestartTask(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/// Feeds a device confirmed outside of the discovery into the discovery store,
|
||||
/// e.g. one that registered with this device's HTTP server. The device comes
|
||||
/// back on the [IsolateDiscoveryListenAction] stream.
|
||||
class IsolateDiscoveryAddDeviceAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
final Device device;
|
||||
|
||||
IsolateDiscoveryAddDeviceAction({
|
||||
required this.device,
|
||||
});
|
||||
|
||||
@override
|
||||
ParentIsolateState reduce() {
|
||||
final connection = state.discovery;
|
||||
if (connection == null) {
|
||||
throw StateError('discovery is not initialized');
|
||||
}
|
||||
|
||||
connection.sendToIsolate(
|
||||
SendToIsolateData(
|
||||
syncState: null,
|
||||
data: IsolateTask(
|
||||
data: DiscoveryAddDeviceTask(
|
||||
device: device,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -119,13 +119,7 @@ class _PublishSyncStateAction extends ReduxAction<IsolateController, ParentIsola
|
||||
|
||||
@override
|
||||
ParentIsolateState reduce() {
|
||||
state.httpScanDiscovery?.sendToIsolate(
|
||||
SendToIsolateData(
|
||||
syncState: syncState,
|
||||
data: null,
|
||||
),
|
||||
);
|
||||
state.multicastDiscovery?.sendToIsolate(
|
||||
state.discovery?.sendToIsolate(
|
||||
SendToIsolateData(
|
||||
syncState: syncState,
|
||||
data: null,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/http_scan_discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/main.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/multicast_discovery_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/server_isolate.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/sync_provider.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/upload_isolate.dart';
|
||||
@@ -19,23 +18,20 @@ part 'parent_isolate_provider.mapper.dart';
|
||||
@MappableClass()
|
||||
class ParentIsolateState with ParentIsolateStateMappable {
|
||||
final SyncState syncState;
|
||||
final IsolateConnector<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<HttpScanTask>>>? httpScanDiscovery;
|
||||
final IsolateConnector<Device, SendToIsolateData<MulticastTask>>? multicastDiscovery;
|
||||
final IsolateConnector<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<DiscoveryTask>>>? discovery;
|
||||
final IsolateConnector<IsolateTaskStreamResult<HttpUploadEvent>, SendToIsolateData<IsolateTask<BaseHttpUploadTask>>>? httpUpload;
|
||||
final IsolateConnector<IsolateTaskStreamResult<HttpServerEvent>, SendToIsolateData<IsolateTask<BaseHttpServerTask>>>? httpServer;
|
||||
|
||||
ParentIsolateState({
|
||||
required this.syncState,
|
||||
required this.httpScanDiscovery,
|
||||
required this.multicastDiscovery,
|
||||
required this.discovery,
|
||||
required this.httpUpload,
|
||||
required this.httpServer,
|
||||
});
|
||||
|
||||
static ParentIsolateState initial(SyncState syncState) => ParentIsolateState(
|
||||
syncState: syncState,
|
||||
httpScanDiscovery: null,
|
||||
multicastDiscovery: null,
|
||||
discovery: null,
|
||||
httpUpload: null,
|
||||
httpServer: null,
|
||||
);
|
||||
@@ -66,17 +62,8 @@ class IsolateController extends ReduxNotifier<ParentIsolateState> {
|
||||
class IsolateSetupAction extends AsyncReduxAction<IsolateController, ParentIsolateState> {
|
||||
@override
|
||||
Future<ParentIsolateState> reduce() async {
|
||||
final httpScanDiscovery =
|
||||
await TypedIsolates.startIsolate<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<HttpScanTask>>, InitialData>(
|
||||
task: setupHttpScanDiscoveryIsolate,
|
||||
param: InitialData(
|
||||
syncState: state.syncState,
|
||||
logLevel: Logger.root.level,
|
||||
),
|
||||
);
|
||||
|
||||
final multicastDiscovery = await TypedIsolates.startIsolate<Device, SendToIsolateData<MulticastTask>, InitialData>(
|
||||
task: setupMulticastDiscoveryIsolate,
|
||||
final discovery = await TypedIsolates.startIsolate<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<DiscoveryTask>>, InitialData>(
|
||||
task: setupDiscoveryIsolate,
|
||||
param: InitialData(
|
||||
syncState: state.syncState,
|
||||
logLevel: Logger.root.level,
|
||||
@@ -102,8 +89,7 @@ class IsolateSetupAction extends AsyncReduxAction<IsolateController, ParentIsola
|
||||
);
|
||||
|
||||
return state.copyWith(
|
||||
httpScanDiscovery: httpScanDiscovery,
|
||||
multicastDiscovery: multicastDiscovery,
|
||||
discovery: discovery,
|
||||
httpUpload: httpUpload,
|
||||
httpServer: httpServer,
|
||||
);
|
||||
@@ -113,8 +99,7 @@ class IsolateSetupAction extends AsyncReduxAction<IsolateController, ParentIsola
|
||||
class IsolateDisposeAction extends ReduxAction<IsolateController, ParentIsolateState> {
|
||||
@override
|
||||
ParentIsolateState reduce() {
|
||||
state.httpScanDiscovery?.isolate.kill();
|
||||
state.multicastDiscovery?.isolate.kill();
|
||||
state.discovery?.isolate.kill();
|
||||
state.httpUpload?.isolate.kill();
|
||||
state.httpServer?.isolate.kill();
|
||||
return state;
|
||||
|
||||
+11
-31
@@ -31,24 +31,17 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
||||
);
|
||||
static IsolateConnector<
|
||||
IsolateTaskStreamResult<Device>,
|
||||
SendToIsolateData<IsolateTask<HttpScanTask>>
|
||||
SendToIsolateData<IsolateTask<DiscoveryTask>>
|
||||
>?
|
||||
_$httpScanDiscovery(ParentIsolateState v) => v.httpScanDiscovery;
|
||||
_$discovery(ParentIsolateState v) => v.discovery;
|
||||
static const Field<
|
||||
ParentIsolateState,
|
||||
IsolateConnector<
|
||||
IsolateTaskStreamResult<Device>,
|
||||
SendToIsolateData<IsolateTask<HttpScanTask>>
|
||||
SendToIsolateData<IsolateTask<DiscoveryTask>>
|
||||
>
|
||||
>
|
||||
_f$httpScanDiscovery = Field('httpScanDiscovery', _$httpScanDiscovery);
|
||||
static IsolateConnector<Device, SendToIsolateData<MulticastTask>>?
|
||||
_$multicastDiscovery(ParentIsolateState v) => v.multicastDiscovery;
|
||||
static const Field<
|
||||
ParentIsolateState,
|
||||
IsolateConnector<Device, SendToIsolateData<MulticastTask>>
|
||||
>
|
||||
_f$multicastDiscovery = Field('multicastDiscovery', _$multicastDiscovery);
|
||||
_f$discovery = Field('discovery', _$discovery);
|
||||
static IsolateConnector<
|
||||
IsolateTaskStreamResult<HttpUploadEvent>,
|
||||
SendToIsolateData<IsolateTask<BaseHttpUploadTask>>
|
||||
@@ -79,8 +72,7 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
||||
@override
|
||||
final MappableFields<ParentIsolateState> fields = const {
|
||||
#syncState: _f$syncState,
|
||||
#httpScanDiscovery: _f$httpScanDiscovery,
|
||||
#multicastDiscovery: _f$multicastDiscovery,
|
||||
#discovery: _f$discovery,
|
||||
#httpUpload: _f$httpUpload,
|
||||
#httpServer: _f$httpServer,
|
||||
};
|
||||
@@ -88,8 +80,7 @@ class ParentIsolateStateMapper extends ClassMapperBase<ParentIsolateState> {
|
||||
static ParentIsolateState _instantiate(DecodingData data) {
|
||||
return ParentIsolateState(
|
||||
syncState: data.dec(_f$syncState),
|
||||
httpScanDiscovery: data.dec(_f$httpScanDiscovery),
|
||||
multicastDiscovery: data.dec(_f$multicastDiscovery),
|
||||
discovery: data.dec(_f$discovery),
|
||||
httpUpload: data.dec(_f$httpUpload),
|
||||
httpServer: data.dec(_f$httpServer),
|
||||
);
|
||||
@@ -171,11 +162,9 @@ abstract class ParentIsolateStateCopyWith<
|
||||
SyncState? syncState,
|
||||
IsolateConnector<
|
||||
IsolateTaskStreamResult<Device>,
|
||||
SendToIsolateData<IsolateTask<HttpScanTask>>
|
||||
SendToIsolateData<IsolateTask<DiscoveryTask>>
|
||||
>?
|
||||
httpScanDiscovery,
|
||||
IsolateConnector<Device, SendToIsolateData<MulticastTask>>?
|
||||
multicastDiscovery,
|
||||
discovery,
|
||||
IsolateConnector<
|
||||
IsolateTaskStreamResult<HttpUploadEvent>,
|
||||
SendToIsolateData<IsolateTask<BaseHttpUploadTask>>
|
||||
@@ -206,15 +195,13 @@ class _ParentIsolateStateCopyWithImpl<$R, $Out>
|
||||
@override
|
||||
$R call({
|
||||
SyncState? syncState,
|
||||
Object? httpScanDiscovery = $none,
|
||||
Object? multicastDiscovery = $none,
|
||||
Object? discovery = $none,
|
||||
Object? httpUpload = $none,
|
||||
Object? httpServer = $none,
|
||||
}) => $apply(
|
||||
FieldCopyWithData({
|
||||
if (syncState != null) #syncState: syncState,
|
||||
if (httpScanDiscovery != $none) #httpScanDiscovery: httpScanDiscovery,
|
||||
if (multicastDiscovery != $none) #multicastDiscovery: multicastDiscovery,
|
||||
if (discovery != $none) #discovery: discovery,
|
||||
if (httpUpload != $none) #httpUpload: httpUpload,
|
||||
if (httpServer != $none) #httpServer: httpServer,
|
||||
}),
|
||||
@@ -222,14 +209,7 @@ class _ParentIsolateStateCopyWithImpl<$R, $Out>
|
||||
@override
|
||||
ParentIsolateState $make(CopyWithData data) => ParentIsolateState(
|
||||
syncState: data.get(#syncState, or: $value.syncState),
|
||||
httpScanDiscovery: data.get(
|
||||
#httpScanDiscovery,
|
||||
or: $value.httpScanDiscovery,
|
||||
),
|
||||
multicastDiscovery: data.get(
|
||||
#multicastDiscovery,
|
||||
or: $value.multicastDiscovery,
|
||||
),
|
||||
discovery: data.get(#discovery, or: $value.discovery),
|
||||
httpUpload: data.get(#httpUpload, or: $value.httpUpload),
|
||||
httpServer: data.get(#httpServer, or: $value.httpServer),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:localsend_isolates/constants.dart';
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/model/dto/multicast_dto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart' show ProtocolTypeV2;
|
||||
import 'package:localsend_isolates/src/isolate/child/sync_provider.dart';
|
||||
import 'package:localsend_isolates/util/rust.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
final _logger = Logger('Discovery');
|
||||
|
||||
final discoveryProvider = Provider((ref) {
|
||||
return DiscoveryService(ref);
|
||||
});
|
||||
|
||||
/// Owns the Rust discovery (`core/discovery`): the multicast sockets, the
|
||||
/// register requests answering announcements, the subnet scans and the store
|
||||
/// of confirmed devices all live on the Rust side. This service configures it
|
||||
/// from the [syncProvider] state and maps every confirmation to a [Device].
|
||||
class DiscoveryService {
|
||||
DiscoveryService(this._ref);
|
||||
|
||||
final Ref _ref;
|
||||
RsDiscovery? _discovery;
|
||||
Completer<void> _retryCompleter = Completer();
|
||||
bool _listening = false;
|
||||
|
||||
/// Starts the discovery and emits every device confirmation:
|
||||
/// answered announcements, scan results and devices fed in
|
||||
/// via [addDevice] all arrive on this one stream.
|
||||
Stream<Device> startListener() {
|
||||
if (_listening) {
|
||||
_logger.info('Already listening to discovery');
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
_listening = true;
|
||||
|
||||
final devices = StreamController<Device>();
|
||||
unawaited(_runListener(devices));
|
||||
return devices.stream;
|
||||
}
|
||||
|
||||
Future<void> _runListener(StreamController<Device> devices) async {
|
||||
// Announcements are only answered while the server runs: the answer would
|
||||
// advertise an HTTP port that nobody listens on otherwise.
|
||||
_ref.stream(syncProvider).listen((event) {
|
||||
if (event.prev.serverRunning != event.next.serverRunning) {
|
||||
unawaited(_discovery?.setAnswerAnnouncements(answer: event.next.serverRunning));
|
||||
}
|
||||
});
|
||||
|
||||
while (true) {
|
||||
final syncState = _ref.read(syncProvider);
|
||||
|
||||
final RsDiscovery discovery;
|
||||
try {
|
||||
discovery = await startDiscovery(
|
||||
group: syncState.multicastGroup,
|
||||
port: syncState.port,
|
||||
networkWhitelist: syncState.networkWhitelist,
|
||||
networkBlacklist: syncState.networkBlacklist,
|
||||
alias: syncState.alias,
|
||||
version: protocolVersion,
|
||||
deviceModel: syncState.deviceInfo.deviceModel,
|
||||
deviceType: syncState.deviceInfo.deviceType.toRust(),
|
||||
fingerprint: syncState.securityContext.certificateHash,
|
||||
protocol: syncState.protocol == ProtocolType.https ? ProtocolTypeV2.https : ProtocolTypeV2.http,
|
||||
download: syncState.download,
|
||||
certPem: syncState.securityContext.certificate,
|
||||
privateKeyPem: syncState.securityContext.privateKey,
|
||||
timeoutMs: BigInt.from(syncState.discoveryTimeout),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.warning('Could not start discovery (group: ${syncState.multicastGroup}, port: ${syncState.port})', e);
|
||||
// Wait for the next restart request instead of hot-looping
|
||||
_retryCompleter = Completer();
|
||||
await _retryCompleter.future;
|
||||
continue;
|
||||
}
|
||||
|
||||
final multicastError = await discovery.multicastError();
|
||||
if (multicastError != null) {
|
||||
_logger.warning('Discovery runs without multicast (group: ${syncState.multicastGroup}, port: ${syncState.port}): $multicastError');
|
||||
}
|
||||
|
||||
if (!_ref.read(syncProvider).serverRunning) {
|
||||
await discovery.setAnswerAnnouncements(answer: false);
|
||||
}
|
||||
|
||||
_discovery = discovery;
|
||||
|
||||
// Tell everyone in the network that I am online.
|
||||
unawaited(discovery.announce());
|
||||
|
||||
await for (final device in discovery.listen()) {
|
||||
if (!devices.isClosed) {
|
||||
devices.add(device.toDevice());
|
||||
}
|
||||
}
|
||||
|
||||
// The stream ended because [restartListener] stopped the discovery.
|
||||
_discovery = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Restarts the discovery, e.g. after the port or the network settings changed.
|
||||
void restartListener() {
|
||||
final discovery = _discovery;
|
||||
if (discovery != null) {
|
||||
// Ends the listen stream, which makes [startListener] rebind.
|
||||
unawaited(discovery.stop());
|
||||
} else if (!_retryCompleter.isCompleted) {
|
||||
// Starting failed previously; let [startListener] try again.
|
||||
_retryCompleter.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an announcement which triggers a response on every LocalSend member of the network.
|
||||
Future<void> sendAnnouncement() async {
|
||||
final discovery = _discovery;
|
||||
if (discovery == null) {
|
||||
_logger.info('Discovery is not running, skipping announcement');
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.info('Announce via UDP');
|
||||
await discovery.announce();
|
||||
}
|
||||
|
||||
/// Scans the subnet of [networkInterface] by probing every host over HTTP,
|
||||
/// for networks that do not carry multicast.
|
||||
/// Found devices arrive on the [startListener] stream; this method returns
|
||||
/// once the whole scan has finished.
|
||||
Future<void> scanSubnet({required String networkInterface, required int port, required bool https}) async {
|
||||
final discovery = _discovery;
|
||||
if (discovery == null) {
|
||||
_logger.info('Discovery is not running, skipping subnet scan');
|
||||
return;
|
||||
}
|
||||
|
||||
await discovery.scanSubnet(
|
||||
interfaceIp: networkInterface,
|
||||
port: port,
|
||||
protocol: https ? ProtocolTypeV2.https : ProtocolTypeV2.http,
|
||||
);
|
||||
}
|
||||
|
||||
/// Probes the known addresses of the favorites.
|
||||
/// Found devices arrive on the [startListener] stream; this method returns
|
||||
/// once every favorite has been probed.
|
||||
Future<void> discoverFavorites({required List<(String, int)> devices, required bool https}) async {
|
||||
final discovery = _discovery;
|
||||
if (discovery == null) {
|
||||
_logger.info('Discovery is not running, skipping favorite scan');
|
||||
return;
|
||||
}
|
||||
|
||||
final protocol = https ? ProtocolTypeV2.https : ProtocolTypeV2.http;
|
||||
await Future.wait([
|
||||
for (final (host, port) in devices) discovery.discover(host: host, port: port, protocol: protocol),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Feeds a device confirmed outside of the discovery into the store, e.g.
|
||||
/// one that registered with this device's HTTP server.
|
||||
/// The device comes back on the [startListener] stream.
|
||||
Future<void> addDevice(Device device) async {
|
||||
final discovery = _discovery;
|
||||
final ip = device.ip;
|
||||
if (discovery == null || ip == null) {
|
||||
// A device lost here re-appears on its next register request, which the
|
||||
// announcement sent when the discovery (re)starts triggers by itself.
|
||||
_logger.info('Discovery is not running, skipping device ${device.alias} ($ip)');
|
||||
return;
|
||||
}
|
||||
|
||||
await discovery.addDevice(device: device.toRsDiscoveredDevice(ip));
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/src/task/discovery/http_target_discovery.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:pool/pool.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
final _logger = Logger('HttpScanDiscovery');
|
||||
|
||||
const _concurrency = 50;
|
||||
|
||||
final httpScanDiscoveryProvider = ViewProvider((ref) {
|
||||
return HttpScanDiscoveryService(
|
||||
targetedDiscoveryService: ref.accessor(httpTargetDiscoveryProvider),
|
||||
);
|
||||
});
|
||||
|
||||
class _CancelToken {
|
||||
bool cancelled = false;
|
||||
}
|
||||
|
||||
/// The token of the currently running scan per network interface.
|
||||
Map<String, _CancelToken> _cancelTokens = {};
|
||||
|
||||
class HttpScanDiscoveryService {
|
||||
final StateAccessor<HttpTargetDiscoveryService> _targetedDiscoveryService;
|
||||
|
||||
HttpScanDiscoveryService({
|
||||
required StateAccessor<HttpTargetDiscoveryService> targetedDiscoveryService,
|
||||
}) : _targetedDiscoveryService = targetedDiscoveryService;
|
||||
|
||||
Stream<Device> getStream({required String networkInterface, required int port, required bool https}) {
|
||||
final ipList = List.generate(256, (i) => '${networkInterface.split('.').take(3).join('.')}.$i').where((ip) => ip != networkInterface).toList();
|
||||
|
||||
// Let the previous scan of this interface skip its remaining requests, so its stream ends.
|
||||
_cancelTokens[networkInterface]?.cancelled = true;
|
||||
final token = _cancelTokens[networkInterface] = _CancelToken();
|
||||
|
||||
final stream = Pool(_concurrency).forEach<String, Device?>(ipList, (ip) async => token.cancelled ? null : _doRequest(ip, port, https));
|
||||
return stream.where((device) => device != null).cast<Device>();
|
||||
}
|
||||
|
||||
Stream<Device> getFavoriteStream({required List<(String, int)> devices, required bool https}) {
|
||||
final stream = Pool(_concurrency).forEach<(String, int), Device?>(devices, (device) => _doRequest(device.$1, device.$2, https));
|
||||
return stream.where((device) => device != null).cast<Device>();
|
||||
}
|
||||
|
||||
Future<Device?> _doRequest(String currentIp, int port, bool https) async {
|
||||
_logger.fine('Requesting $currentIp');
|
||||
final device = await _targetedDiscoveryService.state.discover(
|
||||
ip: currentIp,
|
||||
port: port,
|
||||
https: https,
|
||||
onError: null,
|
||||
);
|
||||
if (device != null) {
|
||||
_logger.info('[DISCOVER/TCP] ${device.alias} (${device.ip}, model: ${device.deviceModel})');
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import 'package:localsend_isolates/api_route_builder.dart';
|
||||
import 'package:localsend_isolates/constants.dart';
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart' as rust_model;
|
||||
import 'package:localsend_isolates/src/isolate/child/http_provider.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/sync_provider.dart';
|
||||
import 'package:localsend_isolates/util/rust.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
final _logger = Logger('TargetedDiscovery');
|
||||
|
||||
final httpTargetDiscoveryProvider = ViewProvider((ref) {
|
||||
final client = ref.watch(httpProvider).discovery;
|
||||
final syncState = ref.watch(syncProvider);
|
||||
return HttpTargetDiscoveryService(
|
||||
client,
|
||||
syncState.toRegisterDto(),
|
||||
syncState.securityContext.certificateHash,
|
||||
);
|
||||
});
|
||||
|
||||
/// Try to discover a single device using the given IP and port.
|
||||
class HttpTargetDiscoveryService {
|
||||
final RsHttpClient _client;
|
||||
final rust_model.RegisterDto _registerDto;
|
||||
final String _fingerprint;
|
||||
|
||||
HttpTargetDiscoveryService(this._client, this._registerDto, this._fingerprint);
|
||||
|
||||
Future<Device?> discover({
|
||||
required String ip,
|
||||
required int port,
|
||||
required bool https,
|
||||
void Function(String url, Object? error)? onError = defaultErrorPrinter,
|
||||
}) async {
|
||||
final url = ApiRoute.register.targetRaw(ip, port, https, protocolVersion);
|
||||
try {
|
||||
final response = await _client.register(
|
||||
protocol: https ? rust_model.ProtocolType.https : rust_model.ProtocolType.http,
|
||||
ip: ip,
|
||||
port: port,
|
||||
payload: _registerDto,
|
||||
);
|
||||
if (response.body.token == _fingerprint) {
|
||||
// discovered itself
|
||||
return null;
|
||||
}
|
||||
return response.body.toDevice(ip, port, https, HttpDiscovery(ip: ip));
|
||||
} catch (e) {
|
||||
onError?.call(url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void defaultErrorPrinter(String url, Object? error) {
|
||||
_logger.warning('$url: $error');
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:localsend_isolates/constants.dart';
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/model/dto/multicast_dto.dart';
|
||||
import 'package:localsend_isolates/rust/api/multicast.dart';
|
||||
import 'package:localsend_isolates/rust/api/server.dart' show ProtocolTypeV2;
|
||||
import 'package:localsend_isolates/src/isolate/child/http_provider.dart';
|
||||
import 'package:localsend_isolates/src/isolate/child/sync_provider.dart';
|
||||
import 'package:localsend_isolates/util/rust.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
|
||||
final _logger = Logger('Multicast');
|
||||
|
||||
final multicastDiscoveryProvider = Provider((ref) {
|
||||
return MulticastService(ref);
|
||||
});
|
||||
|
||||
class MulticastService {
|
||||
MulticastService(this._ref);
|
||||
|
||||
final Ref _ref;
|
||||
RsMulticast? _multicast;
|
||||
Completer<void> _retryCompleter = Completer();
|
||||
bool _listening = false;
|
||||
|
||||
/// Binds the UDP sockets and listens to multicast announcements.
|
||||
Stream<Device> startListener() {
|
||||
if (_listening) {
|
||||
_logger.info('Already listening to multicast');
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
_listening = true;
|
||||
|
||||
// Verified devices are pushed in as [_answerAnnouncement] resolves, which
|
||||
// runs concurrently: one unreachable peer must not hold up the others.
|
||||
final devices = StreamController<Device>();
|
||||
unawaited(_runListener(devices));
|
||||
return devices.stream;
|
||||
}
|
||||
|
||||
Future<void> _runListener(StreamController<Device> devices) async {
|
||||
while (true) {
|
||||
final syncState = _ref.read(syncProvider);
|
||||
|
||||
final RsMulticast multicast;
|
||||
try {
|
||||
multicast = await startMulticast(
|
||||
group: syncState.multicastGroup,
|
||||
port: syncState.port,
|
||||
networkWhitelist: syncState.networkWhitelist,
|
||||
networkBlacklist: syncState.networkBlacklist,
|
||||
alias: syncState.alias,
|
||||
version: protocolVersion,
|
||||
deviceModel: syncState.deviceInfo.deviceModel,
|
||||
deviceType: syncState.deviceInfo.deviceType.toRust(),
|
||||
fingerprint: syncState.securityContext.certificateHash,
|
||||
protocol: syncState.protocol == ProtocolType.https ? ProtocolTypeV2.https : ProtocolTypeV2.http,
|
||||
download: syncState.download,
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.warning('Could not start multicast discovery (group: ${syncState.multicastGroup}, port: ${syncState.port})', e);
|
||||
// Wait for the next restart request instead of hot-looping
|
||||
_retryCompleter = Completer();
|
||||
await _retryCompleter.future;
|
||||
continue;
|
||||
}
|
||||
|
||||
_multicast = multicast;
|
||||
|
||||
// Tell everyone in the network that I am online.
|
||||
unawaited(multicast.announce());
|
||||
|
||||
await for (final event in multicast.listen()) {
|
||||
if (!_ref.read(syncProvider).serverRunning) {
|
||||
// only respond when server is running
|
||||
continue;
|
||||
}
|
||||
|
||||
unawaited(() async {
|
||||
final device = await _answerAnnouncement(event.message.toDevice(event.ip));
|
||||
if (device != null && !devices.isClosed) {
|
||||
devices.add(device);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
// The stream ended because [restartListener] stopped the discovery.
|
||||
_multicast = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Restarts the listener, e.g. after the port or the network settings changed.
|
||||
void restartListener() {
|
||||
final multicast = _multicast;
|
||||
if (multicast != null) {
|
||||
// Ends the listen stream, which makes [startListener] rebind.
|
||||
unawaited(multicast.stop());
|
||||
} else if (!_retryCompleter.isCompleted) {
|
||||
// Starting failed previously; let [startListener] try again.
|
||||
_retryCompleter.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an announcement which triggers a response on every LocalSend member of the network.
|
||||
Future<void> sendAnnouncement() async {
|
||||
final multicast = _multicast;
|
||||
if (multicast == null) {
|
||||
_logger.info('Multicast discovery is not running, skipping announcement');
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.info('Announce via UDP');
|
||||
await multicast.announce();
|
||||
}
|
||||
|
||||
/// Responds to an announcement and returns the peer.
|
||||
/// Data from the multicast announcement is ignored.
|
||||
///
|
||||
/// Returns null if the peer could not be reached or could not be verified.
|
||||
Future<Device?> _answerAnnouncement(Device peer) async {
|
||||
final clients = _ref.read(httpProvider);
|
||||
try {
|
||||
final response = await clients
|
||||
.pinnedTo(peer.fingerprint, timeoutMs: clients.discoveryTimeout)
|
||||
.register(
|
||||
protocol: peer.getProtocolType(),
|
||||
ip: peer.ip!,
|
||||
port: peer.port,
|
||||
payload: _ref.read(syncProvider).toRegisterDto(),
|
||||
);
|
||||
|
||||
_logger.info('Respond to announcement of ${peer.alias} (${peer.ip}, model: ${peer.deviceModel}) via TCP');
|
||||
|
||||
return response.body.toDevice(peer.ip!, peer.port, peer.https, const MulticastDiscovery());
|
||||
} catch (e) {
|
||||
_logger.warning('Could not respond to announcement of ${peer.alias} (${peer.ip})', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import 'package:localsend_isolates/constants.dart';
|
||||
import 'package:localsend_isolates/model/device.dart';
|
||||
import 'package:localsend_isolates/model/dto/file_dto.dart';
|
||||
import 'package:localsend_isolates/model/dto/multicast_dto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart' as rust_discovery;
|
||||
import 'package:localsend_isolates/rust/api/http.dart' as rust_http;
|
||||
import 'package:localsend_isolates/rust/api/model.dart' as rust_model;
|
||||
import 'package:localsend_isolates/rust/api/multicast.dart' as rust_multicast;
|
||||
import 'package:localsend_isolates/rust/api/server.dart' as rust_server;
|
||||
import 'package:localsend_isolates/src/isolate/child/sync_provider.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
@@ -129,11 +129,11 @@ extension RustFileDtoExt on rust_model.FileDto {
|
||||
}
|
||||
}
|
||||
|
||||
extension MulticastMessageV2Ext on rust_multicast.MulticastMessageV2 {
|
||||
Device toDevice(String ip) {
|
||||
extension RsDiscoveredDeviceExt on rust_discovery.RsDiscoveredDevice {
|
||||
Device toDevice() {
|
||||
return Device(
|
||||
signalingId: null,
|
||||
ip: ip,
|
||||
ip: host,
|
||||
version: version,
|
||||
port: port,
|
||||
https: protocol == rust_server.ProtocolTypeV2.https,
|
||||
@@ -142,7 +142,23 @@ extension MulticastMessageV2Ext on rust_multicast.MulticastMessageV2 {
|
||||
deviceModel: deviceModel,
|
||||
deviceType: deviceType?.toDart() ?? DeviceType.desktop,
|
||||
download: download,
|
||||
discoveryMethods: {MulticastDiscovery()},
|
||||
discoveryMethods: {HttpDiscovery(ip: host)},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceToRsDiscoveredDeviceExt on Device {
|
||||
rust_discovery.RsDiscoveredDevice toRsDiscoveredDevice(String ip) {
|
||||
return rust_discovery.RsDiscoveredDevice(
|
||||
alias: alias,
|
||||
version: version,
|
||||
deviceModel: deviceModel,
|
||||
deviceType: deviceType.toRust(),
|
||||
fingerprint: fingerprint,
|
||||
host: ip,
|
||||
port: port,
|
||||
protocol: https ? rust_server.ProtocolTypeV2.https : rust_server.ProtocolTypeV2.http,
|
||||
download: download,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
use crate::frb_generated::StreamSink;
|
||||
use localsend::discovery::{
|
||||
DeviceChannel, DeviceIdentity, DiscoveredDevice, DiscoveryConfig, DiscoveryEvent,
|
||||
DiscoveryHandle, HttpChannel,
|
||||
};
|
||||
use localsend::http::dto_v2::ProtocolTypeV2;
|
||||
use localsend::model::discovery::DeviceType;
|
||||
use localsend::multicast::{DEFAULT_MULTICAST_GROUP_V6, InterfaceFilter, MulticastDevice};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{Mutex, mpsc, oneshot};
|
||||
|
||||
/// A device that was confirmed over HTTP by the discovery: it answered a
|
||||
/// register request, or its register request was accepted by our server and
|
||||
/// was fed back via [RsDiscovery::add_device].
|
||||
///
|
||||
/// Emitted on [RsDiscovery::listen] for every confirmation, so a device
|
||||
/// re-appears whenever it re-announces itself or is re-discovered.
|
||||
pub struct RsDiscoveredDevice {
|
||||
pub alias: String,
|
||||
|
||||
/// Protocol version (major.minor) implemented by the device.
|
||||
pub version: String,
|
||||
|
||||
pub device_model: Option<String>,
|
||||
|
||||
pub device_type: Option<DeviceType>,
|
||||
|
||||
/// Fingerprint identifying the device; devices are deduplicated by it.
|
||||
pub fingerprint: String,
|
||||
|
||||
/// The host the device was confirmed on: an IP address, or the scoped
|
||||
/// form `fe80::1%3` for link-local IPv6 (the Rust HTTP client accepts
|
||||
/// both back as a host).
|
||||
pub host: String,
|
||||
|
||||
/// The port of the device's HTTP server.
|
||||
pub port: u16,
|
||||
|
||||
pub protocol: ProtocolTypeV2,
|
||||
|
||||
/// Whether the device's download API is active.
|
||||
pub download: bool,
|
||||
}
|
||||
|
||||
pub struct RsDiscovery {
|
||||
handle: DiscoveryHandle,
|
||||
event_rx: Mutex<Option<mpsc::Receiver<DiscoveryEvent>>>,
|
||||
stop_tx: Mutex<Option<oneshot::Sender<()>>>,
|
||||
}
|
||||
|
||||
/// Starts the discovery: binds the UDP multicast sockets on all usable
|
||||
/// network interfaces, answers announcements of other devices with an HTTP
|
||||
/// register request, and keeps the store of confirmed devices.
|
||||
///
|
||||
/// Announcements are received from the IPv4 [group] and, as a LocalSend
|
||||
/// extension, from the (currently hardcoded) IPv6 group `ff12::fd3a:e420`.
|
||||
///
|
||||
/// [port] is used both to bind the multicast sockets and as the HTTP server
|
||||
/// port announced to other devices. [cert_pem] and [private_key_pem] are this
|
||||
/// device's TLS identity, sent as client certificate with every register
|
||||
/// request; [fingerprint] must be the certificate's SHA-256 fingerprint.
|
||||
///
|
||||
/// Nothing is announced until [RsDiscovery::announce] is called.
|
||||
///
|
||||
/// Cannot fail besides an invalid [group]: when no multicast socket could be
|
||||
/// bound (e.g. the port is taken by another process), discovery still runs
|
||||
/// without multicast — see [RsDiscovery::multicast_error] — and still learns
|
||||
/// about devices through [RsDiscovery::discover], [RsDiscovery::scan_subnet]
|
||||
/// and [RsDiscovery::add_device].
|
||||
pub async fn start_discovery(
|
||||
group: String,
|
||||
port: u16,
|
||||
network_whitelist: Option<Vec<String>>,
|
||||
network_blacklist: Option<Vec<String>>,
|
||||
alias: String,
|
||||
version: String,
|
||||
device_model: Option<String>,
|
||||
device_type: Option<DeviceType>,
|
||||
fingerprint: String,
|
||||
protocol: ProtocolTypeV2,
|
||||
download: bool,
|
||||
cert_pem: String,
|
||||
private_key_pem: String,
|
||||
timeout_ms: u64,
|
||||
) -> anyhow::Result<RsDiscovery> {
|
||||
let group = group
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("Invalid multicast group: {group}"))?;
|
||||
|
||||
let (event_tx, event_rx) = mpsc::channel::<DiscoveryEvent>(16);
|
||||
let (stop_tx, stop_rx) = oneshot::channel::<()>();
|
||||
|
||||
let handle = localsend::discovery::start(
|
||||
DiscoveryConfig {
|
||||
group,
|
||||
// Hardcoded for now; becomes a parameter once the app exposes it.
|
||||
group_v6: Some(DEFAULT_MULTICAST_GROUP_V6),
|
||||
port,
|
||||
interface_filter: InterfaceFilter {
|
||||
whitelist: network_whitelist,
|
||||
blacklist: network_blacklist,
|
||||
},
|
||||
device: MulticastDevice {
|
||||
alias,
|
||||
version,
|
||||
device_model,
|
||||
device_type,
|
||||
fingerprint,
|
||||
port,
|
||||
protocol,
|
||||
download,
|
||||
},
|
||||
identity: DeviceIdentity {
|
||||
cert_pem,
|
||||
private_key_pem,
|
||||
},
|
||||
timeout: Duration::from_millis(timeout_ms),
|
||||
event_tx: Some(event_tx),
|
||||
},
|
||||
stop_rx,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(RsDiscovery {
|
||||
handle,
|
||||
event_rx: Mutex::new(Some(event_rx)),
|
||||
stop_tx: Mutex::new(Some(stop_tx)),
|
||||
})
|
||||
}
|
||||
|
||||
impl RsDiscovery {
|
||||
/// Emits a [RsDiscoveredDevice] for every device confirmation until the
|
||||
/// discovery is stopped. Can only be listened to once.
|
||||
pub async fn listen(&self, sink: StreamSink<RsDiscoveredDevice>) {
|
||||
let Some(mut event_rx) = self.event_rx.lock().await.take() else {
|
||||
let _ = sink.add_error(anyhow::anyhow!("Discovery events already listened to"));
|
||||
return;
|
||||
};
|
||||
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
let (DiscoveryEvent::Discovered { device } | DiscoveryEvent::Updated { device }) =
|
||||
event;
|
||||
let _ = sink.add(rs_device(device));
|
||||
}
|
||||
}
|
||||
|
||||
/// The reason the multicast sockets could not be bound, when they could
|
||||
/// not. Discovery then neither hears nor sends announcements.
|
||||
pub fn multicast_error(&self) -> Option<String> {
|
||||
self.handle.multicast_error().map(|err| format!("{err:#}"))
|
||||
}
|
||||
|
||||
/// Announces this device to the network, which makes every other LocalSend
|
||||
/// device on it register with this device over HTTP.
|
||||
///
|
||||
/// Devices registering in response arrive at the application as server
|
||||
/// events, not here: feed them back via [RsDiscovery::add_device].
|
||||
///
|
||||
/// Returns once the whole announcement burst has been sent, which takes a
|
||||
/// few seconds, or immediately once the discovery has been stopped or
|
||||
/// multicast is unavailable.
|
||||
pub async fn announce(&self) {
|
||||
self.handle.announce().await;
|
||||
}
|
||||
|
||||
/// Sets whether announcements of other devices are answered with a
|
||||
/// register request (the answer is what makes the announcing device enter
|
||||
/// the store). On by default.
|
||||
///
|
||||
/// Turned off while the HTTP server is not running: the answer would
|
||||
/// advertise a port that nobody listens on.
|
||||
pub fn set_answer_announcements(&self, answer: bool) {
|
||||
self.handle.set_answer_announcements(answer);
|
||||
}
|
||||
|
||||
/// Discovers a device at a known address, e.g. a favorite or a peer that
|
||||
/// multicast does not reach, by sending it a register request.
|
||||
///
|
||||
/// The confirmed device is also emitted on [RsDiscovery::listen].
|
||||
/// Returns `None` when the device did not answer or answered with this
|
||||
/// device's own fingerprint (i.e. the device discovered itself).
|
||||
pub async fn discover(
|
||||
&self,
|
||||
host: String,
|
||||
port: u16,
|
||||
protocol: ProtocolTypeV2,
|
||||
) -> Option<RsDiscoveredDevice> {
|
||||
match self.handle.discover(&host, port, protocol).await {
|
||||
Ok(found) => found.map(|found| rs_device(found.device)),
|
||||
Err(err) => {
|
||||
tracing::debug!("Could not discover {host}:{port}: {err:#}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the `/24` subnet of the local interface address [interface_ip]
|
||||
/// by sending every other host a register request, for networks that do
|
||||
/// not carry multicast.
|
||||
///
|
||||
/// The found devices are emitted on [RsDiscovery::listen] as they answer;
|
||||
/// this method returns once the whole scan has finished. At most one scan
|
||||
/// runs per interface: a call for an address that is still being scanned
|
||||
/// returns immediately.
|
||||
pub async fn scan_subnet(
|
||||
&self,
|
||||
interface_ip: String,
|
||||
port: u16,
|
||||
protocol: ProtocolTypeV2,
|
||||
) -> anyhow::Result<()> {
|
||||
let interface_ip = interface_ip
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("Invalid interface address: {interface_ip}"))?;
|
||||
self.handle.scan_subnet(interface_ip, port, protocol).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Puts a device confirmed outside of the discovery into the store, e.g.
|
||||
/// one that answered an announcement by registering with this device's
|
||||
/// HTTP server. The device is emitted on [RsDiscovery::listen].
|
||||
pub async fn add_device(&self, device: RsDiscoveredDevice) {
|
||||
self.handle
|
||||
.add_device(DiscoveredDevice {
|
||||
alias: device.alias,
|
||||
version: device.version,
|
||||
device_model: device.device_model,
|
||||
device_type: device.device_type,
|
||||
fingerprint: device.fingerprint,
|
||||
channel: DeviceChannel::Http(HttpChannel {
|
||||
host: device.host,
|
||||
port: device.port,
|
||||
protocol: device.protocol,
|
||||
}),
|
||||
download: device.download,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Stops the discovery, which also ends the [RsDiscovery::listen] stream.
|
||||
/// Returns after all sockets are closed, so the port can be bound again.
|
||||
pub async fn stop(&self) {
|
||||
if let Some(stop_tx) = self.stop_tx.lock().await.take() {
|
||||
let _ = stop_tx.send(());
|
||||
self.handle.wait_stopped().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The stored device flattened for the Dart side; only the channel of the
|
||||
/// current confirmation is carried.
|
||||
fn rs_device(device: DiscoveredDevice) -> RsDiscoveredDevice {
|
||||
let DeviceChannel::Http(http) = device.channel;
|
||||
RsDiscoveredDevice {
|
||||
alias: device.alias,
|
||||
version: device.version,
|
||||
device_model: device.device_model,
|
||||
device_type: device.device_type,
|
||||
fingerprint: device.fingerprint,
|
||||
host: http.host,
|
||||
port: http.port,
|
||||
protocol: http.protocol,
|
||||
download: device.download,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
pub mod cancel;
|
||||
pub mod crypto;
|
||||
pub mod discovery;
|
||||
pub mod http;
|
||||
pub mod logging;
|
||||
pub mod model;
|
||||
pub mod multicast;
|
||||
pub mod server;
|
||||
pub mod stream;
|
||||
pub mod webrtc;
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
use crate::frb_generated::StreamSink;
|
||||
use flutter_rust_bridge::frb;
|
||||
use localsend::model::discovery::DeviceType;
|
||||
pub use localsend::model::discovery::{MulticastMessageV2, ProtocolTypeV2};
|
||||
use localsend::multicast::{
|
||||
DEFAULT_MULTICAST_GROUP_V6, InterfaceFilter, MulticastConfig, MulticastDevice, MulticastEvent,
|
||||
MulticastHandle,
|
||||
};
|
||||
use tokio::sync::{Mutex, mpsc, oneshot};
|
||||
|
||||
/// Another device announced itself via UDP multicast.
|
||||
///
|
||||
/// The peer expects to be answered with an HTTP register request.
|
||||
pub struct RsMulticastDiscovered {
|
||||
/// The address the announcement was sent from. The peer's HTTP server is
|
||||
/// reachable at this address on `message.port`.
|
||||
///
|
||||
/// A link-local IPv6 source carries its interface scope as `fe80::1%3`,
|
||||
/// which the Rust HTTP client accepts back as a host.
|
||||
pub ip: String,
|
||||
|
||||
/// The announcement as it was received.
|
||||
pub message: MulticastMessageV2,
|
||||
}
|
||||
|
||||
pub struct RsMulticast {
|
||||
handle: MulticastHandle,
|
||||
event_rx: Mutex<Option<mpsc::Receiver<MulticastEvent>>>,
|
||||
stop_tx: Mutex<Option<oneshot::Sender<()>>>,
|
||||
}
|
||||
|
||||
/// Starts UDP multicast discovery: binds the multicast sockets on all usable
|
||||
/// network interfaces and listens for announcements of other devices.
|
||||
///
|
||||
/// Announcements are sent to the IPv4 [group] and, as a LocalSend extension,
|
||||
/// to the (currently hardcoded) IPv6 group `ff12::fd3a:e420`.
|
||||
///
|
||||
/// [port] is used both to bind the multicast sockets and as the HTTP server
|
||||
/// port announced to other devices.
|
||||
///
|
||||
/// Nothing is announced until [RsMulticast::announce] is called.
|
||||
///
|
||||
/// Fails when [group] is not a valid IPv4 address or when no network
|
||||
/// interface could be used at all.
|
||||
pub async fn start_multicast(
|
||||
group: String,
|
||||
port: u16,
|
||||
network_whitelist: Option<Vec<String>>,
|
||||
network_blacklist: Option<Vec<String>>,
|
||||
alias: String,
|
||||
version: String,
|
||||
device_model: Option<String>,
|
||||
device_type: Option<DeviceType>,
|
||||
fingerprint: String,
|
||||
protocol: ProtocolTypeV2,
|
||||
download: bool,
|
||||
) -> anyhow::Result<RsMulticast> {
|
||||
let group = group
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("Invalid multicast group: {group}"))?;
|
||||
|
||||
let (event_tx, event_rx) = mpsc::channel::<MulticastEvent>(16);
|
||||
let (stop_tx, stop_rx) = oneshot::channel::<()>();
|
||||
|
||||
let handle = localsend::multicast::start(
|
||||
MulticastConfig {
|
||||
group,
|
||||
// Hardcoded for now; becomes a parameter once the app exposes it.
|
||||
group_v6: Some(DEFAULT_MULTICAST_GROUP_V6),
|
||||
port,
|
||||
interface_filter: InterfaceFilter {
|
||||
whitelist: network_whitelist,
|
||||
blacklist: network_blacklist,
|
||||
},
|
||||
device: MulticastDevice {
|
||||
alias,
|
||||
version,
|
||||
device_model,
|
||||
device_type,
|
||||
fingerprint,
|
||||
port,
|
||||
protocol,
|
||||
download,
|
||||
},
|
||||
event_tx,
|
||||
},
|
||||
stop_rx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(RsMulticast {
|
||||
handle,
|
||||
event_rx: Mutex::new(Some(event_rx)),
|
||||
stop_tx: Mutex::new(Some(stop_tx)),
|
||||
})
|
||||
}
|
||||
|
||||
impl RsMulticast {
|
||||
/// Emits a [RsMulticastDiscovered] for every announcement received from
|
||||
/// another device until discovery is stopped.
|
||||
/// Can only be listened to once.
|
||||
pub async fn listen(&self, sink: StreamSink<RsMulticastDiscovered>) {
|
||||
let Some(mut event_rx) = self.event_rx.lock().await.take() else {
|
||||
let _ = sink.add_error(anyhow::anyhow!("Multicast events already listened to"));
|
||||
return;
|
||||
};
|
||||
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
let MulticastEvent::Discovered {
|
||||
ip,
|
||||
scope_id,
|
||||
message,
|
||||
} = event;
|
||||
|
||||
let _ = sink.add(RsMulticastDiscovered {
|
||||
ip: match scope_id {
|
||||
// A link-local source is unreachable without its scope.
|
||||
Some(scope_id) => format!("{ip}%{scope_id}"),
|
||||
None => ip.to_string(),
|
||||
},
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Announces this device to the network, which makes every other LocalSend
|
||||
/// device on it register with this device over HTTP.
|
||||
///
|
||||
/// Returns once the whole announcement burst has been sent, which takes a
|
||||
/// few seconds, or immediately once discovery has been stopped.
|
||||
pub async fn announce(&self) {
|
||||
self.handle.announce().await;
|
||||
}
|
||||
|
||||
/// Stops discovery, which also ends the [RsMulticast::listen] stream.
|
||||
/// Returns after all sockets are closed, so the port can be bound again.
|
||||
pub async fn stop(&self) {
|
||||
if let Some(stop_tx) = self.stop_tx.lock().await.take() {
|
||||
let _ = stop_tx.send(());
|
||||
self.handle.wait_stopped().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[frb(mirror(MulticastMessageV2))]
|
||||
pub struct _MulticastMessageV2 {
|
||||
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,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user