feat(android): file sending improving
This commit is contained in:
+44
-30
@@ -193,6 +193,7 @@ class AppService extends ChangeNotifier {
|
|||||||
List<NearbyDevice>? peers;
|
List<NearbyDevice>? peers;
|
||||||
NearbyDevice? connectedDevice;
|
NearbyDevice? connectedDevice;
|
||||||
NearbyDeviceInfo? currentDeviceInfo;
|
NearbyDeviceInfo? currentDeviceInfo;
|
||||||
|
NearbyConnectionAndroidInfo? connectionAndroidInfo;
|
||||||
|
|
||||||
String platformVersion = 'Unknown';
|
String platformVersion = 'Unknown';
|
||||||
String platformModel = 'Unknown';
|
String platformModel = 'Unknown';
|
||||||
@@ -217,8 +218,7 @@ class AppService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get isAndroidGroupOwner {
|
bool get isAndroidGroupOwner {
|
||||||
return Platform.isAndroid &&
|
return Platform.isAndroid && (connectionAndroidInfo?.isGroupOwner ?? false);
|
||||||
(_nearbyService.android?.connectionInfo?.isGroupOwner ?? false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getPlatformInfo() async {
|
Future<void> getPlatformInfo() async {
|
||||||
@@ -332,6 +332,7 @@ class AppService extends ChangeNotifier {
|
|||||||
Future<void> connect(NearbyDevice device) async {
|
Future<void> connect(NearbyDevice device) async {
|
||||||
try {
|
try {
|
||||||
await _nearbyService.connect(device);
|
await _nearbyService.connect(device);
|
||||||
|
connectionAndroidInfo = await _nearbyService.android?.getConnectionInfo();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -380,34 +381,33 @@ class AppService extends ChangeNotifier {
|
|||||||
ValueChanged<ReceivedNearbyMessage>? listener,
|
ValueChanged<ReceivedNearbyMessage>? listener,
|
||||||
ValueChanged<File>? onFileSaved,
|
ValueChanged<File>? onFileSaved,
|
||||||
}) async {
|
}) async {
|
||||||
final eventListener = NearbyServiceStreamListener(
|
final messagesListener = NearbyServiceMessagesListener(
|
||||||
onCreated: () {
|
onCreated: () {
|
||||||
updateState(AppState.communicationChannelCreated);
|
updateState(AppState.communicationChannelCreated);
|
||||||
},
|
},
|
||||||
onMessage: (event) {
|
onData: (event) {
|
||||||
listener?.call(event);
|
listener?.call(event);
|
||||||
},
|
},
|
||||||
onFile: (event) async {
|
|
||||||
final content = event.content;
|
|
||||||
final fileAcceptFuture = _filesAccepts[content.id];
|
|
||||||
|
|
||||||
if (fileAcceptFuture != null && (await fileAcceptFuture == true)) {
|
|
||||||
final downloadsDir = Directory('storage/emulated/0/Download');
|
|
||||||
final newFile = await event.file.copy(
|
|
||||||
'${downloadsDir.path}/${content.id}_${content.fileName}',
|
|
||||||
);
|
|
||||||
onFileSaved?.call(newFile);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (e, [StackTrace? s]) {
|
onError: (e, [StackTrace? s]) {
|
||||||
stopListeningAll();
|
stopListeningAll();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
final filesListener = NearbyServiceFilesListener(
|
||||||
|
onData: (event) async {
|
||||||
|
final content = event.content;
|
||||||
|
final downloadsDir = Directory('storage/emulated/0/Download');
|
||||||
|
final newFile = await event.file.copy(
|
||||||
|
'${downloadsDir.path}/${content.id}_${content.fileName}',
|
||||||
|
);
|
||||||
|
onFileSaved?.call(newFile);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await _nearbyService.startCommunicationChannel(
|
await _nearbyService.startCommunicationChannel(
|
||||||
NearbyCommunicationChannelData(
|
NearbyCommunicationChannelData(
|
||||||
connectedDevice!.info.id,
|
connectedDevice!.info.id,
|
||||||
eventListener: eventListener,
|
messagesListener: messagesListener,
|
||||||
|
filesListener: filesListener,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -422,16 +422,29 @@ class AppService extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendFile(String filePath) {
|
void sendFileRequest(String filePath) {
|
||||||
if (connectedDevice == null) return;
|
if (connectedDevice == null) return;
|
||||||
_nearbyService.send(
|
_nearbyService.send(
|
||||||
OutgoingNearbyMessage(
|
OutgoingNearbyMessage(
|
||||||
content: NearbyMessageFileContent(filePath: filePath),
|
content: NearbyMessageFileRequest(filePath: filePath),
|
||||||
receiver: connectedDevice!.info,
|
receiver: connectedDevice!.info,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendFileAccept(NearbyMessageFileRequest request) {
|
||||||
|
if (connectedDevice == null) return;
|
||||||
|
_nearbyService.send(
|
||||||
|
OutgoingNearbyMessage(
|
||||||
|
receiver: connectedDevice!.info,
|
||||||
|
content: NearbyMessageFileResponse.fromRequest(
|
||||||
|
request,
|
||||||
|
response: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void setFileAcceptFuture(String id, Future<bool?> future) {
|
void setFileAcceptFuture(String id, Future<bool?> future) {
|
||||||
_filesAccepts[id] = future;
|
_filesAccepts[id] = future;
|
||||||
}
|
}
|
||||||
@@ -794,7 +807,7 @@ class _ConnectedBody extends StatelessWidget {
|
|||||||
void _listener(BuildContext context, ReceivedNearbyMessage message) {
|
void _listener(BuildContext context, ReceivedNearbyMessage message) {
|
||||||
final senderSubtitle = 'From ${message.sender.displayName} '
|
final senderSubtitle = 'From ${message.sender.displayName} '
|
||||||
'(ID: ${message.sender.id})';
|
'(ID: ${message.sender.id})';
|
||||||
message.content.get(
|
message.content.byType(
|
||||||
onText: (content) {
|
onText: (content) {
|
||||||
AppShackBar.show(
|
AppShackBar.show(
|
||||||
Scaffold.of(context).context,
|
Scaffold.of(context).context,
|
||||||
@@ -802,15 +815,16 @@ class _ConnectedBody extends StatelessWidget {
|
|||||||
subtitle: senderSubtitle,
|
subtitle: senderSubtitle,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onFile: (content) {
|
onFileRequest: (content) {
|
||||||
context.read<AppService>().setFileAcceptFuture(
|
ActionDialog.show(
|
||||||
content.id,
|
context,
|
||||||
ActionDialog.show(
|
title: 'File request ${content.fileName}',
|
||||||
context,
|
subtitle: senderSubtitle,
|
||||||
title: 'File request ${content.fileName}',
|
).then((value) {
|
||||||
subtitle: senderSubtitle,
|
if (value == true) {
|
||||||
),
|
context.read<AppService>().sendFileAccept(content);
|
||||||
);
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -907,7 +921,7 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
|
|||||||
child: _ActionButton(
|
child: _ActionButton(
|
||||||
title: 'Send',
|
title: 'Send',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
service.sendFile(filePath);
|
service.sendFileRequest(filePath);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import 'package:nearby_service/nearby_service.dart';
|
|||||||
/// A representation of a file that can be got from the Nearby Service's
|
/// A representation of a file that can be got from the Nearby Service's
|
||||||
/// communication channel.
|
/// communication channel.
|
||||||
///
|
///
|
||||||
/// You can use [id] to compare it to the id from [NearbyMessageFileContent.id].
|
/// You can use [id] to compare it to the id from [NearbyMessageFileRequest.id].
|
||||||
///
|
///
|
||||||
/// From the communication channel, you usually get
|
/// From the communication channel, you usually get
|
||||||
/// the [NearbyMessageFileContent] request first. After that, you get [NearbyFile].
|
/// the [NearbyMessageFileRequest] request first. After that, you get [NearbyFile].
|
||||||
///
|
///
|
||||||
class NearbyFile {
|
class NearbyFile {
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import 'package:nearby_service/src/utils/random.dart';
|
|||||||
/// Type of the message.
|
/// Type of the message.
|
||||||
///
|
///
|
||||||
/// If [text], it will be a text message.
|
/// If [text], it will be a text message.
|
||||||
/// If [file], it will be a file request. After accepting request,
|
/// If [fileRequest], it will be a file request. After accepting request,
|
||||||
/// user can get file stream from connected device.
|
/// user can get file stream from connected device.
|
||||||
///
|
///
|
||||||
enum NearbyMessageContentType {
|
enum NearbyMessageContentType {
|
||||||
text,
|
text,
|
||||||
file;
|
fileRequest,
|
||||||
|
fileResponse;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Checks if this is [NearbyMessageContentType.text]
|
/// Checks if this is [NearbyMessageContentType.text]
|
||||||
@@ -20,10 +21,17 @@ enum NearbyMessageContentType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Checks if this is [NearbyMessageContentType.file]
|
/// Checks if this is [NearbyMessageContentType.fileRequest]
|
||||||
///
|
///
|
||||||
bool get isFile {
|
bool get isFileRequest {
|
||||||
return this == NearbyMessageContentType.file;
|
return this == NearbyMessageContentType.fileRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Checks if this is [NearbyMessageContentType.fileResponse]
|
||||||
|
///
|
||||||
|
bool get isFileResponse {
|
||||||
|
return this == NearbyMessageContentType.fileResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +43,7 @@ abstract class NearbyMessageContent {
|
|||||||
const NearbyMessageContent(this._type);
|
const NearbyMessageContent(this._type);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Contains the conditional logic of creating [NearbyMessageFileContent]
|
/// Contains the conditional logic of creating [NearbyMessageFileRequest]
|
||||||
/// or [NearbyMessageTextContent] by `type` field of [json].
|
/// or [NearbyMessageTextContent] by `type` field of [json].
|
||||||
///
|
///
|
||||||
factory NearbyMessageContent.fromJson(Map<String, dynamic>? json) {
|
factory NearbyMessageContent.fromJson(Map<String, dynamic>? json) {
|
||||||
@@ -43,10 +51,12 @@ abstract class NearbyMessageContent {
|
|||||||
final type = NearbyMessageContentType.values.firstWhere(
|
final type = NearbyMessageContentType.values.firstWhere(
|
||||||
(e) => e.name == json?['type'],
|
(e) => e.name == json?['type'],
|
||||||
);
|
);
|
||||||
if (type.isFile) {
|
if (type.isFileRequest) {
|
||||||
return NearbyMessageFileContent.fromJson(json);
|
return NearbyMessageFileRequest.fromJson(json);
|
||||||
} else if (type.isText) {
|
} else if (type.isText) {
|
||||||
return NearbyMessageTextContent.fromJson(json);
|
return NearbyMessageTextContent.fromJson(json);
|
||||||
|
} else if (type.isFileResponse) {
|
||||||
|
return NearbyMessageFileResponse.fromJson(json);
|
||||||
} else {
|
} else {
|
||||||
throw NearbyServiceException.unsupportedDecoding(json);
|
throw NearbyServiceException.unsupportedDecoding(json);
|
||||||
}
|
}
|
||||||
@@ -66,17 +76,23 @@ abstract class NearbyMessageContent {
|
|||||||
/// * The [onText] callback returns this instance of [NearbyMessageContent],
|
/// * The [onText] callback returns this instance of [NearbyMessageContent],
|
||||||
/// cast as [NearbyMessageTextContent] if is a text.
|
/// cast as [NearbyMessageTextContent] if is a text.
|
||||||
///
|
///
|
||||||
/// * The [onFile] callback returns this instance of [NearbyMessageContent],
|
/// * The [onFileRequest] callback returns this instance of [NearbyMessageContent],
|
||||||
/// cast as [NearbyMessageFileContent] if is a file.
|
/// cast as [NearbyMessageFileRequest] if is a file request.
|
||||||
///
|
///
|
||||||
T? get<T>({
|
/// * The [onFileResponse] callback returns this instance of [NearbyMessageContent],
|
||||||
|
/// cast as [NearbyMessageFileResponse] if is a file response.
|
||||||
|
///
|
||||||
|
T? byType<T>({
|
||||||
T Function(NearbyMessageTextContent)? onText,
|
T Function(NearbyMessageTextContent)? onText,
|
||||||
T Function(NearbyMessageFileContent)? onFile,
|
T Function(NearbyMessageFileRequest)? onFileRequest,
|
||||||
|
T Function(NearbyMessageFileResponse)? onFileResponse,
|
||||||
}) {
|
}) {
|
||||||
if (this is NearbyMessageTextContent && onText != null) {
|
if (this is NearbyMessageTextContent && onText != null) {
|
||||||
return onText(this as NearbyMessageTextContent);
|
return onText(this as NearbyMessageTextContent);
|
||||||
} else if (this is NearbyMessageFileContent && onFile != null) {
|
} else if (this is NearbyMessageFileRequest && onFileRequest != null) {
|
||||||
return onFile(this as NearbyMessageFileContent);
|
return onFileRequest(this as NearbyMessageFileRequest);
|
||||||
|
} else if (this is NearbyMessageFileResponse && onFileResponse != null) {
|
||||||
|
return onFileResponse(this as NearbyMessageFileResponse);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -141,38 +157,12 @@ class NearbyMessageTextContent extends NearbyMessageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
abstract class NearbyMessageFileContent extends NearbyMessageContent {
|
||||||
/// Nearby message File content. Used for file sending requests.
|
const NearbyMessageFileContent(
|
||||||
/// Does not contain file bytes!
|
super.type, {
|
||||||
///
|
|
||||||
/// Contains [filePath] - the name of file to be sent or received.
|
|
||||||
///
|
|
||||||
class NearbyMessageFileContent extends NearbyMessageContent {
|
|
||||||
const NearbyMessageFileContent._({
|
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.filePath,
|
required this.filePath,
|
||||||
}) : super(
|
});
|
||||||
NearbyMessageContentType.file,
|
|
||||||
);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Basic constructor with [filePath] to be sent or received.
|
|
||||||
///
|
|
||||||
NearbyMessageFileContent({required this.filePath})
|
|
||||||
: id = RandomUtils.instance.nextInt(1000000, 9999999).toString(),
|
|
||||||
super(
|
|
||||||
NearbyMessageContentType.file,
|
|
||||||
);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Gets [NearbyMessageFileContent] from [json].
|
|
||||||
///
|
|
||||||
factory NearbyMessageFileContent.fromJson(Map<String, dynamic>? json) {
|
|
||||||
return NearbyMessageFileContent._(
|
|
||||||
id: json?['id'] ?? '',
|
|
||||||
filePath: json?['filePath'] ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// ID for comparing file requests.
|
/// ID for comparing file requests.
|
||||||
@@ -192,23 +182,16 @@ class NearbyMessageFileContent extends NearbyMessageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isValid => filePath.isNotEmpty;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is NearbyMessageFileContent &&
|
other is NearbyMessageFileContent &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
|
id == other.id &&
|
||||||
filePath == other.filePath;
|
filePath == other.filePath;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => filePath.hashCode;
|
int get hashCode => id.hashCode ^ filePath.hashCode;
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'NearbyMessageFileContent{filePath: $filePath, id: $id}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
@@ -219,3 +202,92 @@ class NearbyMessageFileContent extends NearbyMessageContent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Nearby message File content. Used for file sending requests.
|
||||||
|
/// Does not contain file bytes!
|
||||||
|
///
|
||||||
|
/// Contains [filePath] - the name of file to be sent or received.
|
||||||
|
///
|
||||||
|
class NearbyMessageFileRequest extends NearbyMessageFileContent {
|
||||||
|
const NearbyMessageFileRequest._({
|
||||||
|
required super.id,
|
||||||
|
required super.filePath,
|
||||||
|
}) : super(
|
||||||
|
NearbyMessageContentType.fileRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Basic constructor with [filePath] to be sent or received.
|
||||||
|
///
|
||||||
|
NearbyMessageFileRequest({required super.filePath})
|
||||||
|
: super(
|
||||||
|
NearbyMessageContentType.fileRequest,
|
||||||
|
id: RandomUtils.instance.nextInt(1000000, 9999999).toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Gets [NearbyMessageFileRequest] from [json].
|
||||||
|
///
|
||||||
|
factory NearbyMessageFileRequest.fromJson(Map<String, dynamic>? json) {
|
||||||
|
return NearbyMessageFileRequest._(
|
||||||
|
id: json?['id'] ?? '',
|
||||||
|
filePath: json?['filePath'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isValid => filePath.isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NearbyMessageFileRequest{id: $id, filePath: $filePath}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NearbyMessageFileResponse extends NearbyMessageFileContent {
|
||||||
|
NearbyMessageFileResponse({
|
||||||
|
required super.id,
|
||||||
|
required super.filePath,
|
||||||
|
required this.response,
|
||||||
|
}) : super(
|
||||||
|
NearbyMessageContentType.fileResponse,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory NearbyMessageFileResponse.fromRequest(
|
||||||
|
NearbyMessageFileRequest request, {
|
||||||
|
required bool response,
|
||||||
|
}) {
|
||||||
|
return NearbyMessageFileResponse(
|
||||||
|
id: request.id,
|
||||||
|
filePath: request.filePath,
|
||||||
|
response: response,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory NearbyMessageFileResponse.fromJson(Map<String, dynamic>? json) {
|
||||||
|
return NearbyMessageFileResponse(
|
||||||
|
id: json?['id'] ?? '',
|
||||||
|
filePath: json?['filePath'] ?? '',
|
||||||
|
response: json?['response'] ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool response;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isValid => filePath.isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'response': response,
|
||||||
|
...super.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NearbyMessageFileResponse{response: $response, id: $id, filePath: $filePath}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,14 +19,6 @@ class NearbyAndroidService extends NearbyService {
|
|||||||
return _socketService.state;
|
return _socketService.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// The connection information of a Wi-Fi p2p group connection
|
|
||||||
/// from the socket service.
|
|
||||||
///
|
|
||||||
NearbyConnectionAndroidInfo? get connectionInfo {
|
|
||||||
return _socketService.connectionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Initializes Android [WifiP2PManager](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager)
|
/// Initializes Android [WifiP2PManager](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager)
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:nearby_service/nearby_service.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
class FileCreator {
|
|
||||||
FileCreator({required this.content});
|
|
||||||
|
|
||||||
static const _finishCommand = '@@FINISH_SENDING_FILE_';
|
|
||||||
|
|
||||||
static String generateFinishCommand(String id) {
|
|
||||||
return '$_finishCommand$id';
|
|
||||||
}
|
|
||||||
|
|
||||||
final NearbyMessageFileContent content;
|
|
||||||
final _bytes = <int>[];
|
|
||||||
|
|
||||||
String get finishCommand => '$_finishCommand${content.id}';
|
|
||||||
|
|
||||||
void add(List<int> value) {
|
|
||||||
_bytes.addAll(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<NearbyFile> getFile() async {
|
|
||||||
final directory = await getTemporaryDirectory();
|
|
||||||
final file = File('${directory.path}/${content.fileName}');
|
|
||||||
|
|
||||||
await file.writeAsBytes(_bytes);
|
|
||||||
return NearbyFile(file: file, content: content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
part of 'nearby_socket_service.dart';
|
||||||
|
|
||||||
|
class FileSocketsManager {
|
||||||
|
FileSocketsManager(this._network, this._service);
|
||||||
|
|
||||||
|
final NearbyServiceNetwork _network;
|
||||||
|
final NearbyAndroidService _service;
|
||||||
|
final _filesSockets = <FilesSocket>[];
|
||||||
|
final _serverWaitingContents = <String, HttpRequest>{};
|
||||||
|
|
||||||
|
NearbyServiceFilesListener? _filesListener;
|
||||||
|
|
||||||
|
void setListener(NearbyServiceFilesListener? listener) {
|
||||||
|
_filesListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onWsRequest(HttpRequest request) {
|
||||||
|
final type = NearbySocketType.fromRequest(request);
|
||||||
|
final fileId = NearbyFileId.fromRequest(request);
|
||||||
|
if (type == NearbySocketType.file && fileId != null) {
|
||||||
|
_serverWaitingContents[fileId] = request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleFileMessageContent(
|
||||||
|
NearbyMessageFileContent content, {
|
||||||
|
required NearbyAndroidCommunicationChannelData androidData,
|
||||||
|
required bool isReceived,
|
||||||
|
}) async {
|
||||||
|
final info = await _service.getConnectionInfo();
|
||||||
|
if (info != null && info.groupFormed) {
|
||||||
|
if (info.isGroupOwner) {
|
||||||
|
await _handleServerFileContent(content);
|
||||||
|
if (content is NearbyMessageFileResponse && isReceived) {
|
||||||
|
await _tryTransferData(content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await _handleClientFileContent(
|
||||||
|
content,
|
||||||
|
connectionData: androidData,
|
||||||
|
ownerIpAddress: info.ownerIpAddress,
|
||||||
|
);
|
||||||
|
if (content is NearbyMessageFileRequest && !isReceived) {
|
||||||
|
await _tryTransferData(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> closeAll() async {
|
||||||
|
for (final fileSocket in _filesSockets) {
|
||||||
|
await fileSocket.close();
|
||||||
|
}
|
||||||
|
_filesSockets.clear();
|
||||||
|
_filesListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleClientFileContent(
|
||||||
|
NearbyMessageFileContent content, {
|
||||||
|
required NearbyAndroidCommunicationChannelData connectionData,
|
||||||
|
required String ownerIpAddress,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final shouldStartFileSocket = content.byType(
|
||||||
|
onFileResponse: (response) => response.response,
|
||||||
|
onFileRequest: (request) => true,
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
if (shouldStartFileSocket) {
|
||||||
|
final socket = await _network.connectToSocket(
|
||||||
|
ownerIpAddress: ownerIpAddress,
|
||||||
|
port: connectionData.port,
|
||||||
|
socketType: NearbySocketType.file,
|
||||||
|
headers: {
|
||||||
|
NearbyFileId.key: content.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (socket != null) {
|
||||||
|
_filesSockets.add(
|
||||||
|
FilesSocket.startListening(
|
||||||
|
content: content,
|
||||||
|
socket: socket,
|
||||||
|
listener: _filesListener,
|
||||||
|
onDestroy: _filesSockets.remove,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Logger.info(
|
||||||
|
'The file socket was created for the file ${content.fileName}',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await Future.delayed(
|
||||||
|
connectionData.clientReconnectInterval,
|
||||||
|
() => _handleClientFileContent(
|
||||||
|
content,
|
||||||
|
connectionData: connectionData,
|
||||||
|
ownerIpAddress: ownerIpAddress,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleServerFileContent(
|
||||||
|
NearbyMessageFileContent content,
|
||||||
|
) async {
|
||||||
|
MapEntry<String, HttpRequest>? request;
|
||||||
|
try {
|
||||||
|
request = _serverWaitingContents.entries.firstWhere(
|
||||||
|
(element) => element.key == content.id,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
request = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
Logger.debug('Found cached server file request ${request.key}');
|
||||||
|
_filesSockets.add(
|
||||||
|
FilesSocket.startListening(
|
||||||
|
content: content,
|
||||||
|
socket: await WebSocketTransformer.upgrade(request.value),
|
||||||
|
listener: _filesListener,
|
||||||
|
onDestroy: _filesSockets.remove,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_serverWaitingContents.remove(content.id);
|
||||||
|
Logger.info('Created file socket for ${content.fileName}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _tryTransferData(NearbyMessageFileContent content) async {
|
||||||
|
final fileSocket = _find(content.id);
|
||||||
|
if (fileSocket != null) {
|
||||||
|
Logger.info('Start transferring a file ${content.fileName}');
|
||||||
|
final file = File(content.filePath);
|
||||||
|
|
||||||
|
file.openRead().listen(
|
||||||
|
(data) {
|
||||||
|
fileSocket.sendData(data);
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
Logger.debug('Sent finished command for ${content.fileName}');
|
||||||
|
fileSocket.sendData(
|
||||||
|
FilesSocket.generateFinishCommand(fileSocket.content.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilesSocket? _find(String id) {
|
||||||
|
FilesSocket? fileSocket;
|
||||||
|
try {
|
||||||
|
fileSocket = _filesSockets.firstWhere(
|
||||||
|
(element) => element.content.id == id,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
fileSocket = null;
|
||||||
|
}
|
||||||
|
return fileSocket;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:nearby_service/nearby_service.dart';
|
import 'package:nearby_service/nearby_service.dart';
|
||||||
import 'package:nearby_service/src/platforms/android/socket_service/file_creator.dart';
|
import 'package:nearby_service/src/utils/file_socket.dart';
|
||||||
import 'package:nearby_service/src/utils/logger.dart';
|
import 'package:nearby_service/src/utils/logger.dart';
|
||||||
import 'package:nearby_service/src/utils/random.dart';
|
import 'package:nearby_service/src/utils/random.dart';
|
||||||
import 'package:nearby_service/src/utils/stream_mapper.dart';
|
import 'package:nearby_service/src/utils/stream_mapper.dart';
|
||||||
@@ -13,24 +13,28 @@ part 'ping_manager.dart';
|
|||||||
|
|
||||||
part 'network.dart';
|
part 'network.dart';
|
||||||
|
|
||||||
|
part 'file_sockets_manager.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
/// A service for creating a communication channel on the Android platform.
|
/// A service for creating a communication channel on the Android platform.
|
||||||
///
|
///
|
||||||
class NearbySocketService {
|
class NearbySocketService {
|
||||||
NearbySocketService(this._manager);
|
NearbySocketService(this._service);
|
||||||
|
|
||||||
final NearbyAndroidService _manager;
|
final NearbyAndroidService _service;
|
||||||
final _pingManager = NearbySocketPingManager();
|
final _pingManager = NearbySocketPingManager();
|
||||||
final _network = NearbyServiceNetwork();
|
final _network = NearbyServiceNetwork();
|
||||||
|
late final _fileSocketsManager = FileSocketsManager(_network, _service);
|
||||||
|
|
||||||
final state = ValueNotifier(CommunicationChannelState.notConnected);
|
final state = ValueNotifier(CommunicationChannelState.notConnected);
|
||||||
NearbyConnectionAndroidInfo? connectionInfo;
|
|
||||||
|
|
||||||
FileCreator? fileCreator;
|
NearbyAndroidCommunicationChannelData _androidData =
|
||||||
|
const NearbyAndroidCommunicationChannelData();
|
||||||
|
|
||||||
String? _connectedDeviceId;
|
String? _connectedDeviceId;
|
||||||
WebSocket? _socket;
|
WebSocket? _socket;
|
||||||
HttpServer? _server;
|
HttpServer? _server;
|
||||||
StreamSubscription? _streamSubscription;
|
StreamSubscription? _messagesSubscription;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Start a socket with the user's role defined.
|
/// Start a socket with the user's role defined.
|
||||||
@@ -46,24 +50,25 @@ class NearbySocketService {
|
|||||||
required NearbyCommunicationChannelData data,
|
required NearbyCommunicationChannelData data,
|
||||||
}) async {
|
}) async {
|
||||||
state.value = CommunicationChannelState.loading;
|
state.value = CommunicationChannelState.loading;
|
||||||
|
|
||||||
|
_androidData = data.androidData;
|
||||||
_connectedDeviceId = data.connectedDeviceId;
|
_connectedDeviceId = data.connectedDeviceId;
|
||||||
connectionInfo = await _manager.getConnectionInfo();
|
|
||||||
if (connectionInfo != null && connectionInfo!.groupFormed) {
|
_fileSocketsManager.setListener(data.filesListener);
|
||||||
final androidData = data.androidData;
|
|
||||||
if (connectionInfo!.isGroupOwner) {
|
final info = await _service.getConnectionInfo();
|
||||||
|
|
||||||
|
if (info != null && info.groupFormed) {
|
||||||
|
if (info.isGroupOwner) {
|
||||||
await _startServerSubscription(
|
await _startServerSubscription(
|
||||||
serverListener: androidData.serverListener,
|
socketListener: data.messagesListener,
|
||||||
socketListener: data.eventListener,
|
info: info,
|
||||||
info: connectionInfo!,
|
|
||||||
port: androidData.port,
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
await _tryConnectClient(
|
await _tryConnectClient(
|
||||||
socketListener: data.eventListener,
|
socketListener: data.messagesListener,
|
||||||
reconnectInterval: androidData.clientReconnectInterval,
|
info: info,
|
||||||
info: connectionInfo!,
|
|
||||||
port: androidData.port,
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -77,7 +82,7 @@ class NearbySocketService {
|
|||||||
Future<bool> send(OutgoingNearbyMessage message) async {
|
Future<bool> send(OutgoingNearbyMessage message) async {
|
||||||
if (message.isValid) {
|
if (message.isValid) {
|
||||||
if (_socket != null && message.receiver.id == _connectedDeviceId) {
|
if (_socket != null && message.receiver.id == _connectedDeviceId) {
|
||||||
final sender = await _manager.getCurrentDeviceInfo();
|
final sender = await _service.getCurrentDeviceInfo();
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
_socket!.add(
|
_socket!.add(
|
||||||
jsonEncode(
|
jsonEncode(
|
||||||
@@ -87,20 +92,13 @@ class NearbySocketService {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
message.content.get(
|
if (message.content is NearbyMessageFileContent) {
|
||||||
onFile: (fileContent) {
|
_fileSocketsManager.handleFileMessageContent(
|
||||||
final file = File(fileContent.filePath);
|
message.content as NearbyMessageFileContent,
|
||||||
|
androidData: _androidData,
|
||||||
file.openRead().listen(
|
isReceived: false,
|
||||||
(data) => _socket?.add(data),
|
);
|
||||||
onDone: () {
|
}
|
||||||
_socket?.add(
|
|
||||||
FileCreator.generateFinishCommand(fileContent.id),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -111,17 +109,20 @@ class NearbySocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Turns off [_streamSubscription] and [_socket].
|
/// Turns off [_messagesSubscription] and [_socket].
|
||||||
///
|
///
|
||||||
Future<bool> cancel() async {
|
Future<bool> cancel() async {
|
||||||
try {
|
try {
|
||||||
await _streamSubscription?.cancel();
|
await _messagesSubscription?.cancel();
|
||||||
_streamSubscription = null;
|
await _fileSocketsManager.closeAll();
|
||||||
|
|
||||||
|
_messagesSubscription = null;
|
||||||
_socket?.close();
|
_socket?.close();
|
||||||
_socket = null;
|
_socket = null;
|
||||||
_server?.close(force: true);
|
_server?.close(force: true);
|
||||||
_server = null;
|
_server = null;
|
||||||
_connectedDeviceId = null;
|
_connectedDeviceId = null;
|
||||||
|
|
||||||
state.value = CommunicationChannelState.notConnected;
|
state.value = CommunicationChannelState.notConnected;
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -130,50 +131,45 @@ class NearbySocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _tryConnectClient({
|
Future<void> _tryConnectClient({
|
||||||
required NearbyServiceStreamListener socketListener,
|
required NearbyServiceMessagesListener socketListener,
|
||||||
required NearbyConnectionAndroidInfo info,
|
required NearbyConnectionAndroidInfo info,
|
||||||
required int port,
|
|
||||||
required Duration reconnectInterval,
|
|
||||||
}) async {
|
}) async {
|
||||||
final response = await _network.pingServer(
|
final response = await _network.pingServer(
|
||||||
address: info.ownerIpAddress,
|
address: info.ownerIpAddress,
|
||||||
port: port,
|
port: _androidData.port,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (await _pingManager.checkPong(response)) {
|
if (await _pingManager.checkPong(response)) {
|
||||||
_socket = await _network.connectToSocket(
|
_socket = await _network.connectToSocket(
|
||||||
ownerIpAddress: info.ownerIpAddress,
|
ownerIpAddress: info.ownerIpAddress,
|
||||||
port: port,
|
port: _androidData.port,
|
||||||
|
socketType: NearbySocketType.message,
|
||||||
);
|
);
|
||||||
_createSocketSubscription(socketListener);
|
_createSocketSubscription(socketListener);
|
||||||
} else {
|
} else {
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
'Retry to connect to the server in ${reconnectInterval.inSeconds}s',
|
'Retry to connect to the server in ${_androidData.clientReconnectInterval.inSeconds}s',
|
||||||
);
|
);
|
||||||
Future.delayed(reconnectInterval, () {
|
Future.delayed(_androidData.clientReconnectInterval, () {
|
||||||
_tryConnectClient(
|
_tryConnectClient(
|
||||||
socketListener: socketListener,
|
socketListener: socketListener,
|
||||||
reconnectInterval: reconnectInterval,
|
|
||||||
info: info,
|
info: info,
|
||||||
port: port,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _startServerSubscription({
|
Future<void> _startServerSubscription({
|
||||||
required NearbyServiceStreamListener socketListener,
|
required NearbyServiceMessagesListener socketListener,
|
||||||
required NearbyConnectionAndroidInfo info,
|
required NearbyConnectionAndroidInfo info,
|
||||||
required int port,
|
|
||||||
ValueChanged<HttpRequest>? serverListener,
|
|
||||||
}) async {
|
}) async {
|
||||||
_server = await _network.startServer(
|
_server = await _network.startServer(
|
||||||
ownerIpAddress: info.ownerIpAddress,
|
ownerIpAddress: info.ownerIpAddress,
|
||||||
port: port,
|
port: _androidData.port,
|
||||||
);
|
);
|
||||||
_server?.listen(
|
_server?.listen(
|
||||||
(request) async {
|
(request) async {
|
||||||
serverListener?.call(request);
|
_androidData.serverListener?.call(request);
|
||||||
final isPing = await _pingManager.checkPing(request);
|
final isPing = await _pingManager.checkPing(request);
|
||||||
if (isPing) {
|
if (isPing) {
|
||||||
Logger.debug('Server got ping request');
|
Logger.debug('Server got ping request');
|
||||||
@@ -182,8 +178,13 @@ class NearbySocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.uri.path == _Urls.ws) {
|
if (request.uri.path == _Urls.ws) {
|
||||||
_socket = await WebSocketTransformer.upgrade(request);
|
final type = NearbySocketType.fromRequest(request);
|
||||||
_createSocketSubscription(socketListener);
|
if (type == NearbySocketType.message) {
|
||||||
|
_socket = await WebSocketTransformer.upgrade(request);
|
||||||
|
_createSocketSubscription(socketListener);
|
||||||
|
} else {
|
||||||
|
_fileSocketsManager.onWsRequest(request);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
request.response
|
request.response
|
||||||
..statusCode = HttpStatus.notFound
|
..statusCode = HttpStatus.notFound
|
||||||
@@ -194,43 +195,44 @@ class NearbySocketService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createSocketSubscription(NearbyServiceStreamListener socketListener) {
|
void _createSocketSubscription(NearbyServiceMessagesListener socketListener) {
|
||||||
Logger.debug('Starting socket subscription');
|
Logger.debug('Starting socket subscription');
|
||||||
|
|
||||||
if (_connectedDeviceId != null) {
|
if (_connectedDeviceId != null) {
|
||||||
_streamSubscription = _socket?.listen(
|
_messagesSubscription = _socket?.listen(
|
||||||
(event) async {
|
(event) async {
|
||||||
if (fileCreator != null) {
|
// if (fileLoaders != null) {
|
||||||
if (event is List<int>) {
|
// if (event is List<int>) {
|
||||||
fileCreator!.add(event);
|
// fileLoaders!.add(event);
|
||||||
} else if (event == fileCreator?.finishCommand) {
|
// } else if (event == fileLoaders?.finishCommand) {
|
||||||
final file = await fileCreator!.getFile().whenComplete(
|
// final file = await fileLoaders!.getFile().whenComplete(
|
||||||
() {
|
// () {
|
||||||
fileCreator = null;
|
// fileLoaders = null;
|
||||||
},
|
// },
|
||||||
|
// );
|
||||||
|
// socketListener.onFile?.call(file);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
try {
|
||||||
|
final message = MessagesStreamMapper.toMessage(event);
|
||||||
|
if (message != null) {
|
||||||
|
final newMessage = MessagesStreamMapper.replaceId(
|
||||||
|
message,
|
||||||
|
_connectedDeviceId!,
|
||||||
);
|
);
|
||||||
socketListener.onFile?.call(file);
|
if (newMessage.content is NearbyMessageFileContent) {
|
||||||
}
|
_fileSocketsManager.handleFileMessageContent(
|
||||||
} else {
|
newMessage.content as NearbyMessageFileContent,
|
||||||
try {
|
androidData: _androidData,
|
||||||
final message = MessagesStreamMapper.toMessage(event);
|
isReceived: true,
|
||||||
if (message != null) {
|
|
||||||
final newMessage = MessagesStreamMapper.replaceId(
|
|
||||||
message,
|
|
||||||
_connectedDeviceId!,
|
|
||||||
);
|
);
|
||||||
newMessage.content.get(
|
|
||||||
onFile: (fileContent) {
|
|
||||||
fileCreator = FileCreator(content: fileContent);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
socketListener.onMessage(newMessage);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
socketListener.onData(newMessage);
|
||||||
Logger.error(e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(e);
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
state.value = CommunicationChannelState.notConnected;
|
state.value = CommunicationChannelState.notConnected;
|
||||||
@@ -244,7 +246,7 @@ class NearbySocketService {
|
|||||||
cancelOnError: socketListener.cancelOnError,
|
cancelOnError: socketListener.cancelOnError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_streamSubscription != null) {
|
if (_messagesSubscription != null) {
|
||||||
state.value = CommunicationChannelState.connected;
|
state.value = CommunicationChannelState.connected;
|
||||||
Logger.info('Socket subscription was created successfully');
|
Logger.info('Socket subscription was created successfully');
|
||||||
socketListener.onCreated?.call();
|
socketListener.onCreated?.call();
|
||||||
|
|||||||
@@ -57,13 +57,21 @@ class NearbyServiceNetwork {
|
|||||||
Future<WebSocket?> connectToSocket({
|
Future<WebSocket?> connectToSocket({
|
||||||
required String ownerIpAddress,
|
required String ownerIpAddress,
|
||||||
required int port,
|
required int port,
|
||||||
|
required NearbySocketType socketType,
|
||||||
|
Map<String, String>? headers,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final connectionId = RandomUtils.instance.nextInt(100, 999);
|
final connectionId = RandomUtils.instance.nextInt(100, 999);
|
||||||
final url =
|
final url =
|
||||||
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
|
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
|
||||||
Logger.debug('Connecting to $url');
|
Logger.debug('Connecting to $url');
|
||||||
final socket = await WebSocket.connect(url);
|
final socket = await WebSocket.connect(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
NearbySocketType.key: socketType.name,
|
||||||
|
...?headers,
|
||||||
|
},
|
||||||
|
);
|
||||||
Logger.info('Connected to $url');
|
Logger.info('Connected to $url');
|
||||||
return socket;
|
return socket;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -71,3 +79,32 @@ class NearbyServiceNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NearbySocketType {
|
||||||
|
message,
|
||||||
|
file;
|
||||||
|
|
||||||
|
static const key = 'SocketType';
|
||||||
|
|
||||||
|
static NearbySocketType? fromRequest(HttpRequest request) {
|
||||||
|
return NearbySocketType.fromString(
|
||||||
|
request.headers.value(NearbySocketType.key),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NearbySocketType? fromString(String? value) {
|
||||||
|
try {
|
||||||
|
return values.firstWhere((element) => element.name == value);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NearbyFileId {
|
||||||
|
static const key = 'FileID';
|
||||||
|
|
||||||
|
static String? fromRequest(HttpRequest request) {
|
||||||
|
return request.headers.value(NearbyFileId.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -163,7 +163,8 @@ class NearbyIOSService extends NearbyService {
|
|||||||
Logger.debug('Creating messages subscription');
|
Logger.debug('Creating messages subscription');
|
||||||
_state.value = CommunicationChannelState.loading;
|
_state.value = CommunicationChannelState.loading;
|
||||||
await endCommunicationChannel();
|
await endCommunicationChannel();
|
||||||
final eventListener = data.eventListener;
|
final eventListener = data.messagesListener;
|
||||||
|
final filesListener = data.filesListener;
|
||||||
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
|
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
|
||||||
// .map(MessagesStreamMapper.toMessage)
|
// .map(MessagesStreamMapper.toMessage)
|
||||||
// .where((event) => event?.sender.id == data.connectedDeviceId)
|
// .where((event) => event?.sender.id == data.connectedDeviceId)
|
||||||
@@ -174,11 +175,11 @@ class NearbyIOSService extends NearbyService {
|
|||||||
try {
|
try {
|
||||||
final message = MessagesStreamMapper.toMessage(event);
|
final message = MessagesStreamMapper.toMessage(event);
|
||||||
if (message != null && message.sender.id == data.connectedDeviceId) {
|
if (message != null && message.sender.id == data.connectedDeviceId) {
|
||||||
eventListener.onMessage(message);
|
eventListener.onData(message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
eventListener.onFile?.call(event);
|
filesListener?.onData(event);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error(e);
|
Logger.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class NearbyCommunicationChannelData {
|
|||||||
///
|
///
|
||||||
const NearbyCommunicationChannelData(
|
const NearbyCommunicationChannelData(
|
||||||
this.connectedDeviceId, {
|
this.connectedDeviceId, {
|
||||||
required this.eventListener,
|
required this.messagesListener,
|
||||||
|
this.filesListener,
|
||||||
this.androidData = const NearbyAndroidCommunicationChannelData(),
|
this.androidData = const NearbyAndroidCommunicationChannelData(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,7 +29,12 @@ class NearbyCommunicationChannelData {
|
|||||||
///
|
///
|
||||||
/// Listener for message stream changes.
|
/// Listener for message stream changes.
|
||||||
///
|
///
|
||||||
final NearbyServiceStreamListener eventListener;
|
final NearbyServiceMessagesListener messagesListener;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Listener for message stream changes.
|
||||||
|
///
|
||||||
|
final NearbyServiceFilesListener? filesListener;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Android-specific connection data.
|
/// Android-specific connection data.
|
||||||
@@ -41,18 +47,18 @@ class NearbyCommunicationChannelData {
|
|||||||
other is NearbyCommunicationChannelData &&
|
other is NearbyCommunicationChannelData &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
connectedDeviceId == other.connectedDeviceId &&
|
connectedDeviceId == other.connectedDeviceId &&
|
||||||
eventListener == other.eventListener &&
|
messagesListener == other.messagesListener &&
|
||||||
androidData == other.androidData;
|
androidData == other.androidData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
connectedDeviceId.hashCode ^
|
connectedDeviceId.hashCode ^
|
||||||
eventListener.hashCode ^
|
messagesListener.hashCode ^
|
||||||
androidData.hashCode;
|
androidData.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'NearbyCommunicationChannelData{connectedDeviceId: $connectedDeviceId, eventListener: $eventListener, androidData: $androidData}';
|
return 'NearbyCommunicationChannelData{connectedDeviceId: $connectedDeviceId, eventListener: $messagesListener, androidData: $androidData}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,58 @@ import 'package:nearby_service/nearby_service.dart';
|
|||||||
///
|
///
|
||||||
/// Stream Subscription Listener.
|
/// Stream Subscription Listener.
|
||||||
///
|
///
|
||||||
class NearbyServiceStreamListener {
|
class NearbyServiceSocketListener<T> {
|
||||||
///
|
///
|
||||||
/// It is required to pass the [onMessage] parameter to process the
|
/// It is required to pass the [onData] parameter to process the
|
||||||
/// data that came through the stream.
|
/// data that came through the stream.
|
||||||
///
|
///
|
||||||
const NearbyServiceStreamListener({
|
const NearbyServiceSocketListener({
|
||||||
required this.onMessage,
|
required this.onData,
|
||||||
this.onFile,
|
|
||||||
this.onCreated,
|
this.onCreated,
|
||||||
this.onDone,
|
this.onDone,
|
||||||
this.onError,
|
this.onError,
|
||||||
this.cancelOnError,
|
this.cancelOnError,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ValueChanged<ReceivedNearbyMessage> onMessage;
|
final ValueChanged<T> onData;
|
||||||
final ValueChanged<NearbyFile>? onFile;
|
|
||||||
final VoidCallback? onCreated;
|
final VoidCallback? onCreated;
|
||||||
final VoidCallback? onDone;
|
final VoidCallback? onDone;
|
||||||
final void Function(Object, [StackTrace])? onError;
|
final void Function(Object, [StackTrace])? onError;
|
||||||
final bool? cancelOnError;
|
final bool? cancelOnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Stream Subscription Listener.
|
||||||
|
///
|
||||||
|
class NearbyServiceMessagesListener
|
||||||
|
extends NearbyServiceSocketListener<ReceivedNearbyMessage> {
|
||||||
|
///
|
||||||
|
/// It is required to pass the [onData] parameter to process the
|
||||||
|
/// data that came through the stream.
|
||||||
|
///
|
||||||
|
const NearbyServiceMessagesListener({
|
||||||
|
required super.onData,
|
||||||
|
super.onCreated,
|
||||||
|
super.onDone,
|
||||||
|
super.onError,
|
||||||
|
super.cancelOnError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Stream Subscription Listener.
|
||||||
|
///
|
||||||
|
class NearbyServiceFilesListener
|
||||||
|
extends NearbyServiceSocketListener<NearbyFile> {
|
||||||
|
///
|
||||||
|
/// It is required to pass the [onData] parameter to process the
|
||||||
|
/// data that came through the stream.
|
||||||
|
///
|
||||||
|
const NearbyServiceFilesListener({
|
||||||
|
required super.onData,
|
||||||
|
super.onCreated,
|
||||||
|
super.onDone,
|
||||||
|
super.onError,
|
||||||
|
super.cancelOnError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:nearby_service/nearby_service.dart';
|
||||||
|
import 'package:nearby_service/src/utils/logger.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
class FilesSocket {
|
||||||
|
FilesSocket.startListening({
|
||||||
|
required this.content,
|
||||||
|
required WebSocket socket,
|
||||||
|
required NearbyServiceFilesListener? listener,
|
||||||
|
required void Function(FilesSocket) onDestroy,
|
||||||
|
}) : _socket = socket {
|
||||||
|
_socket.listen(
|
||||||
|
(event) async {
|
||||||
|
if (event is List<int>) {
|
||||||
|
saveChunk(event);
|
||||||
|
} else if (event == finishCommand) {
|
||||||
|
final file = await getFile().whenComplete(
|
||||||
|
() {
|
||||||
|
onDestroy(this);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Logger.info('File ${file.content.fileName} was created');
|
||||||
|
listener?.onData.call(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: listener?.onError,
|
||||||
|
cancelOnError: listener?.cancelOnError,
|
||||||
|
onDone: listener?.onDone,
|
||||||
|
);
|
||||||
|
listener?.onCreated?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _finishCommand = '@@FINISH_SENDING_FILE_';
|
||||||
|
|
||||||
|
static String generateFinishCommand(String id) {
|
||||||
|
return '$_finishCommand$id';
|
||||||
|
}
|
||||||
|
|
||||||
|
final WebSocket _socket;
|
||||||
|
final NearbyMessageFileContent content;
|
||||||
|
final _bytes = <int>[];
|
||||||
|
|
||||||
|
int chunksCount = 0;
|
||||||
|
|
||||||
|
String get finishCommand => '$_finishCommand${content.id}';
|
||||||
|
|
||||||
|
void sendData(dynamic event) {
|
||||||
|
_socket.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveChunk(List<int> value) {
|
||||||
|
_bytes.addAll(value);
|
||||||
|
chunksCount = chunksCount + 1;
|
||||||
|
final logStep = min(pow(10, chunksCount.toString().length - 1), 100);
|
||||||
|
if (chunksCount % logStep == 0) {
|
||||||
|
Logger.debug('Got $chunksCount chunks for the file ${content.fileName}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<NearbyFile> getFile() async {
|
||||||
|
try {
|
||||||
|
final directory = await getTemporaryDirectory();
|
||||||
|
final file = File('${directory.path}/${content.fileName}');
|
||||||
|
|
||||||
|
await file.writeAsBytes(_bytes);
|
||||||
|
return NearbyFile(file: file, content: content);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> close() => _socket.close();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user