fucking works
can transfer files from desktop to mobile and save them there.
This commit is contained in:
parent
0086fb1774
commit
4501e333f0
10 changed files with 290 additions and 126 deletions
|
@ -1,3 +1,38 @@
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use raptorq::{Decoder, Encoder, EncodingPacket, ObjectTransmissionInformation};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
//
|
let rng = &mut rand::thread_rng();
|
||||||
|
let len = 20_000;
|
||||||
|
let mut v = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
for _ in 0..len {
|
||||||
|
v.push(rng.gen::<u8>());
|
||||||
|
}
|
||||||
|
let config = ObjectTransmissionInformation::with_defaults(len as u64, 1200);
|
||||||
|
let encoder = Encoder::new(&v, config);
|
||||||
|
let mut packets = encoder
|
||||||
|
.get_encoded_packets(10)
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.serialize())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
packets.shuffle(rng);
|
||||||
|
|
||||||
|
let mut decoder = Decoder::new(config);
|
||||||
|
|
||||||
|
let mut v2 = None;
|
||||||
|
for (i, p) in packets.iter().enumerate() {
|
||||||
|
v2 = decoder.decode(EncodingPacket::deserialize(p));
|
||||||
|
if v2.is_some() {
|
||||||
|
println!(
|
||||||
|
"recovered after {i} packets received, out of {} total",
|
||||||
|
packets.len()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(v2.is_some());
|
||||||
|
assert_eq!(v2.unwrap(), v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,29 @@ abstract class Native {
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kGetTxConfigConstMeta;
|
FlutterRustBridgeTaskConstMeta get kGetTxConfigConstMeta;
|
||||||
|
|
||||||
Future<bool> checkRaptor({required Uint8List buffer, required TxConfig txconf, dynamic hint});
|
Future<Uint8List?> decodePackets({required List<RaptorPacket> packets, required TxConfig txconf, dynamic hint});
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kCheckRaptorConstMeta;
|
FlutterRustBridgeTaskConstMeta get kDecodePacketsConstMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RaptorPacket {
|
||||||
|
final Uint8List field0;
|
||||||
|
|
||||||
|
const RaptorPacket({
|
||||||
|
required this.field0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class TxConfig {
|
class TxConfig {
|
||||||
final int len;
|
final int len;
|
||||||
final int mtu;
|
final int mtu;
|
||||||
final String description;
|
final String description;
|
||||||
|
final String? filename;
|
||||||
|
|
||||||
const TxConfig({
|
const TxConfig({
|
||||||
required this.len,
|
required this.len,
|
||||||
required this.mtu,
|
required this.mtu,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
this.filename,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,21 +40,21 @@ class NativeImpl implements Native {
|
||||||
argNames: ["bytes"],
|
argNames: ["bytes"],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<bool> checkRaptor({required Uint8List buffer, required TxConfig txconf, dynamic hint}) {
|
Future<Uint8List?> decodePackets({required List<RaptorPacket> packets, required TxConfig txconf, dynamic hint}) {
|
||||||
var arg0 = _platform.api2wire_uint_8_list(buffer);
|
var arg0 = _platform.api2wire_list_raptor_packet(packets);
|
||||||
var arg1 = _platform.api2wire_box_autoadd_tx_config(txconf);
|
var arg1 = _platform.api2wire_box_autoadd_tx_config(txconf);
|
||||||
return _platform.executeNormal(FlutterRustBridgeTask(
|
return _platform.executeNormal(FlutterRustBridgeTask(
|
||||||
callFfi: (port_) => _platform.inner.wire_check_raptor(port_, arg0, arg1),
|
callFfi: (port_) => _platform.inner.wire_decode_packets(port_, arg0, arg1),
|
||||||
parseSuccessData: _wire2api_bool,
|
parseSuccessData: _wire2api_opt_uint_8_list,
|
||||||
constMeta: kCheckRaptorConstMeta,
|
constMeta: kDecodePacketsConstMeta,
|
||||||
argValues: [buffer, txconf],
|
argValues: [packets, txconf],
|
||||||
hint: hint,
|
hint: hint,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FlutterRustBridgeTaskConstMeta get kCheckRaptorConstMeta => const FlutterRustBridgeTaskConstMeta(
|
FlutterRustBridgeTaskConstMeta get kDecodePacketsConstMeta => const FlutterRustBridgeTaskConstMeta(
|
||||||
debugName: "check_raptor",
|
debugName: "decode_packets",
|
||||||
argNames: ["buffer", "txconf"],
|
argNames: ["packets", "txconf"],
|
||||||
);
|
);
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -66,25 +66,30 @@ class NativeImpl implements Native {
|
||||||
return raw as String;
|
return raw as String;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _wire2api_bool(dynamic raw) {
|
|
||||||
return raw as bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
TxConfig _wire2api_box_autoadd_tx_config(dynamic raw) {
|
TxConfig _wire2api_box_autoadd_tx_config(dynamic raw) {
|
||||||
return _wire2api_tx_config(raw);
|
return _wire2api_tx_config(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _wire2api_opt_String(dynamic raw) {
|
||||||
|
return raw == null ? null : _wire2api_String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
TxConfig? _wire2api_opt_box_autoadd_tx_config(dynamic raw) {
|
TxConfig? _wire2api_opt_box_autoadd_tx_config(dynamic raw) {
|
||||||
return raw == null ? null : _wire2api_box_autoadd_tx_config(raw);
|
return raw == null ? null : _wire2api_box_autoadd_tx_config(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint8List? _wire2api_opt_uint_8_list(dynamic raw) {
|
||||||
|
return raw == null ? null : _wire2api_uint_8_list(raw);
|
||||||
|
}
|
||||||
|
|
||||||
TxConfig _wire2api_tx_config(dynamic raw) {
|
TxConfig _wire2api_tx_config(dynamic raw) {
|
||||||
final arr = raw as List<dynamic>;
|
final arr = raw as List<dynamic>;
|
||||||
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
|
if (arr.length != 4) throw Exception('unexpected arr length: expect 4 but see ${arr.length}');
|
||||||
return TxConfig(
|
return TxConfig(
|
||||||
len: _wire2api_u64(arr[0]),
|
len: _wire2api_u64(arr[0]),
|
||||||
mtu: _wire2api_u16(arr[1]),
|
mtu: _wire2api_u16(arr[1]),
|
||||||
description: _wire2api_String(arr[2]),
|
description: _wire2api_String(arr[2]),
|
||||||
|
filename: _wire2api_opt_String(arr[3]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +141,20 @@ class NativePlatform extends FlutterRustBridgeBase<NativeWire> {
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_list_raptor_packet> api2wire_list_raptor_packet(List<RaptorPacket> raw) {
|
||||||
|
final ans = inner.new_list_raptor_packet_0(raw.length);
|
||||||
|
for (var i = 0; i < raw.length; ++i) {
|
||||||
|
_api_fill_to_wire_raptor_packet(raw[i], ans.ref.ptr[i]);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
ffi.Pointer<wire_uint_8_list> api2wire_opt_String(String? raw) {
|
||||||
|
return raw == null ? ffi.nullptr : api2wire_String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int api2wire_u64(int raw) {
|
int api2wire_u64(int raw) {
|
||||||
return raw;
|
return raw;
|
||||||
|
@ -155,10 +174,15 @@ class NativePlatform extends FlutterRustBridgeBase<NativeWire> {
|
||||||
_api_fill_to_wire_tx_config(apiObj, wireObj.ref);
|
_api_fill_to_wire_tx_config(apiObj, wireObj.ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _api_fill_to_wire_raptor_packet(RaptorPacket apiObj, wire_RaptorPacket wireObj) {
|
||||||
|
wireObj.field0 = api2wire_uint_8_list(apiObj.field0);
|
||||||
|
}
|
||||||
|
|
||||||
void _api_fill_to_wire_tx_config(TxConfig apiObj, wire_TxConfig wireObj) {
|
void _api_fill_to_wire_tx_config(TxConfig apiObj, wire_TxConfig wireObj) {
|
||||||
wireObj.len = api2wire_u64(apiObj.len);
|
wireObj.len = api2wire_u64(apiObj.len);
|
||||||
wireObj.mtu = api2wire_u16(apiObj.mtu);
|
wireObj.mtu = api2wire_u16(apiObj.mtu);
|
||||||
wireObj.description = api2wire_String(apiObj.description);
|
wireObj.description = api2wire_String(apiObj.description);
|
||||||
|
wireObj.filename = api2wire_opt_String(apiObj.filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,23 +279,24 @@ class NativeWire implements FlutterRustBridgeWireBase {
|
||||||
late final _wire_get_tx_config =
|
late final _wire_get_tx_config =
|
||||||
_wire_get_tx_configPtr.asFunction<void Function(int, ffi.Pointer<wire_uint_8_list>)>();
|
_wire_get_tx_configPtr.asFunction<void Function(int, ffi.Pointer<wire_uint_8_list>)>();
|
||||||
|
|
||||||
void wire_check_raptor(
|
void wire_decode_packets(
|
||||||
int port_,
|
int port_,
|
||||||
ffi.Pointer<wire_uint_8_list> buffer,
|
ffi.Pointer<wire_list_raptor_packet> packets,
|
||||||
ffi.Pointer<wire_TxConfig> txconf,
|
ffi.Pointer<wire_TxConfig> txconf,
|
||||||
) {
|
) {
|
||||||
return _wire_check_raptor(
|
return _wire_decode_packets(
|
||||||
port_,
|
port_,
|
||||||
buffer,
|
packets,
|
||||||
txconf,
|
txconf,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _wire_check_raptorPtr = _lookup<
|
late final _wire_decode_packetsPtr = _lookup<
|
||||||
ffi.NativeFunction<ffi.Void Function(ffi.Int64, ffi.Pointer<wire_uint_8_list>, ffi.Pointer<wire_TxConfig>)>>(
|
ffi.NativeFunction<
|
||||||
'wire_check_raptor');
|
ffi.Void Function(
|
||||||
late final _wire_check_raptor =
|
ffi.Int64, ffi.Pointer<wire_list_raptor_packet>, ffi.Pointer<wire_TxConfig>)>>('wire_decode_packets');
|
||||||
_wire_check_raptorPtr.asFunction<void Function(int, ffi.Pointer<wire_uint_8_list>, ffi.Pointer<wire_TxConfig>)>();
|
late final _wire_decode_packets = _wire_decode_packetsPtr
|
||||||
|
.asFunction<void Function(int, ffi.Pointer<wire_list_raptor_packet>, ffi.Pointer<wire_TxConfig>)>();
|
||||||
|
|
||||||
ffi.Pointer<wire_TxConfig> new_box_autoadd_tx_config_0() {
|
ffi.Pointer<wire_TxConfig> new_box_autoadd_tx_config_0() {
|
||||||
return _new_box_autoadd_tx_config_0();
|
return _new_box_autoadd_tx_config_0();
|
||||||
|
@ -282,6 +307,19 @@ class NativeWire implements FlutterRustBridgeWireBase {
|
||||||
late final _new_box_autoadd_tx_config_0 =
|
late final _new_box_autoadd_tx_config_0 =
|
||||||
_new_box_autoadd_tx_config_0Ptr.asFunction<ffi.Pointer<wire_TxConfig> Function()>();
|
_new_box_autoadd_tx_config_0Ptr.asFunction<ffi.Pointer<wire_TxConfig> Function()>();
|
||||||
|
|
||||||
|
ffi.Pointer<wire_list_raptor_packet> new_list_raptor_packet_0(
|
||||||
|
int len,
|
||||||
|
) {
|
||||||
|
return _new_list_raptor_packet_0(
|
||||||
|
len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _new_list_raptor_packet_0Ptr =
|
||||||
|
_lookup<ffi.NativeFunction<ffi.Pointer<wire_list_raptor_packet> Function(ffi.Int32)>>('new_list_raptor_packet_0');
|
||||||
|
late final _new_list_raptor_packet_0 =
|
||||||
|
_new_list_raptor_packet_0Ptr.asFunction<ffi.Pointer<wire_list_raptor_packet> Function(int)>();
|
||||||
|
|
||||||
ffi.Pointer<wire_uint_8_list> new_uint_8_list_0(
|
ffi.Pointer<wire_uint_8_list> new_uint_8_list_0(
|
||||||
int len,
|
int len,
|
||||||
) {
|
) {
|
||||||
|
@ -316,6 +354,17 @@ final class wire_uint_8_list extends ffi.Struct {
|
||||||
external int len;
|
external int len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class wire_RaptorPacket extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_uint_8_list> field0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_list_raptor_packet extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_RaptorPacket> ptr;
|
||||||
|
|
||||||
|
@ffi.Int32()
|
||||||
|
external int len;
|
||||||
|
}
|
||||||
|
|
||||||
final class wire_TxConfig extends ffi.Struct {
|
final class wire_TxConfig extends ffi.Struct {
|
||||||
@ffi.Uint64()
|
@ffi.Uint64()
|
||||||
external int len;
|
external int len;
|
||||||
|
@ -324,6 +373,8 @@ final class wire_TxConfig extends ffi.Struct {
|
||||||
external int mtu;
|
external int mtu;
|
||||||
|
|
||||||
external ffi.Pointer<wire_uint_8_list> description;
|
external ffi.Pointer<wire_uint_8_list> description;
|
||||||
|
|
||||||
|
external ffi.Pointer<wire_uint_8_list> filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef DartPostCObjectFnType
|
typedef DartPostCObjectFnType
|
||||||
|
|
|
@ -9,7 +9,6 @@ export 'bridge_definitions.dart';
|
||||||
|
|
||||||
// Re-export the bridge so it is only necessary to import this file.
|
// Re-export the bridge so it is only necessary to import this file.
|
||||||
export 'bridge_generated.dart';
|
export 'bridge_generated.dart';
|
||||||
import 'dart:io' as io;
|
|
||||||
|
|
||||||
const _base = 'native';
|
const _base = 'native';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
@ -25,7 +25,7 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'camerAwesome App',
|
title: 'Cuttle',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
),
|
),
|
||||||
|
@ -43,20 +43,18 @@ class MyHomePage extends StatefulWidget {
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
final _barcodeScanner = BarcodeScanner(formats: [BarcodeFormat.qrCode]);
|
final _barcodeScanner = BarcodeScanner(formats: [BarcodeFormat.qrCode]);
|
||||||
|
final _rxTextController = BehaviorSubject<String>();
|
||||||
final _buffer = <String>[];
|
late final Stream<String> _rxTextStream = _rxTextController.stream;
|
||||||
final _barcodesController = BehaviorSubject<List<String>>();
|
|
||||||
late final Stream<List<String>> _barcodesStream = _barcodesController.stream;
|
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
|
|
||||||
TxConfig? _txConfig;
|
TxConfig? _txConfig;
|
||||||
String? _rxText;
|
|
||||||
var _cuttleState = CuttleState.unitialized;
|
var _cuttleState = CuttleState.unitialized;
|
||||||
final _rxData = <int>[];
|
final List<RaptorPacket> _rxData = [];
|
||||||
|
String _rxText = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_barcodesController.close();
|
_rxTextController.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,12 +67,12 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
androidOptions: const AndroidAnalysisOptions.nv21(
|
androidOptions: const AndroidAnalysisOptions.nv21(
|
||||||
width: 1024,
|
width: 1024,
|
||||||
),
|
),
|
||||||
maxFramesPerSecond: null,
|
maxFramesPerSecond: 30,
|
||||||
autoStart: false,
|
autoStart: false,
|
||||||
),
|
),
|
||||||
builder: (cameraModeState, previewSize, previewRect) {
|
builder: (cameraModeState, previewSize, previewRect) {
|
||||||
return _BarcodeDisplayWidget(
|
return _RxTextDisplayWidget(
|
||||||
barcodesStream: _barcodesStream,
|
rxTextStream: _rxTextStream,
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
analysisController: cameraModeState.analysisController!,
|
analysisController: cameraModeState.analysisController!,
|
||||||
);
|
);
|
||||||
|
@ -93,7 +91,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final dbytes = bytes;
|
final Uint8List dbytes = bytes;
|
||||||
switch (_cuttleState) {
|
switch (_cuttleState) {
|
||||||
case CuttleState.unitialized:
|
case CuttleState.unitialized:
|
||||||
{
|
{
|
||||||
|
@ -101,8 +99,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
if (txconf != null) {
|
if (txconf != null) {
|
||||||
_txConfig = txconf;
|
_txConfig = txconf;
|
||||||
_cuttleState = CuttleState.receiving;
|
_cuttleState = CuttleState.receiving;
|
||||||
debugPrint(
|
final fname =
|
||||||
"txconf: ${txconf.len} bytes, \"${txconf.description}\"");
|
_txConfig!.filename ?? "large text on the command line";
|
||||||
|
|
||||||
|
final desc = _txConfig!.description;
|
||||||
|
final text = 'Receiving $fname, ${_txConfig!.len} bytes: $desc';
|
||||||
|
_rxText = text;
|
||||||
|
_rxTextController.add(text);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// implicit else here; txconf was null
|
// implicit else here; txconf was null
|
||||||
|
@ -110,69 +113,73 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
// it's not a txconfig, and it's not a raptor packet, so it must be a regular qr code
|
// it's not a txconfig, and it's not a raptor packet, so it must be a regular qr code
|
||||||
_rxText = text;
|
_rxText = text;
|
||||||
|
_rxTextController.add(text);
|
||||||
_cuttleState = CuttleState.received;
|
_cuttleState = CuttleState.received;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case CuttleState.receiving:
|
case CuttleState.receiving:
|
||||||
{
|
{
|
||||||
final check = await api.checkRaptor(
|
var txconf = await api.getTxConfig(bytes: dbytes);
|
||||||
buffer: Uint8List.fromList(_rxData), txconf: _txConfig!);
|
if (txconf != null || barcode.rawValue != null) {
|
||||||
if (check) {
|
continue;
|
||||||
_cuttleState = CuttleState.received;
|
|
||||||
}
|
}
|
||||||
|
final packet = RaptorPacket(field0: dbytes);
|
||||||
|
_rxData.add(packet);
|
||||||
|
final content =
|
||||||
|
await api.decodePackets(packets: _rxData, txconf: _txConfig!);
|
||||||
|
if (content != null) {
|
||||||
|
_rxData.clear();
|
||||||
|
_barcodeScanner.close();
|
||||||
|
_cuttleState = CuttleState.received;
|
||||||
|
_rxTextController.add("DONE RECEIVING $_rxText");
|
||||||
|
final f = await _saveReceivedFile(_txConfig!.filename, content);
|
||||||
|
_rxTextController.add("Saved content to $f");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_rxTextController
|
||||||
|
.add("$_rxText -- ${_rxData.length} bytes so far");
|
||||||
}
|
}
|
||||||
|
|
||||||
case CuttleState.received:
|
case CuttleState.received:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_addBarcode("[${barcode.format.name}]: ${barcode.rawValue}");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugPrint("...sending image resulted error $error");
|
debugPrint("sending image resulted error $error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addBarcode(String value) {
|
Future<String> _saveReceivedFile(String? filename, Uint8List bytes) async {
|
||||||
try {
|
final Directory downloadDir = Directory('/storage/emulated/0/Download');
|
||||||
if (_buffer.length > 300) {
|
final String fname =
|
||||||
_buffer.removeRange(_buffer.length - 300, _buffer.length);
|
filename ?? "cuttle_${DateTime.now().millisecondsSinceEpoch}.txt";
|
||||||
}
|
final String path = "${downloadDir.path}/$fname";
|
||||||
if (_buffer.isEmpty || value != _buffer[0]) {
|
final file = await File(path).create();
|
||||||
_buffer.insert(0, value);
|
await file.writeAsBytes(bytes, flush: true);
|
||||||
_barcodesController.add(_buffer);
|
return path;
|
||||||
_scrollController.animateTo(
|
|
||||||
0,
|
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
curve: Curves.fastLinearToSlowEaseIn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debugPrint("...logging error $err");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BarcodeDisplayWidget extends StatefulWidget {
|
class _RxTextDisplayWidget extends StatefulWidget {
|
||||||
final Stream<List<String>> barcodesStream;
|
final Stream<String> rxTextStream;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
final AnalysisController analysisController;
|
final AnalysisController analysisController;
|
||||||
|
|
||||||
const _BarcodeDisplayWidget({
|
const _RxTextDisplayWidget({
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
super.key,
|
super.key,
|
||||||
required this.barcodesStream,
|
required this.rxTextStream,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.analysisController,
|
required this.analysisController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_BarcodeDisplayWidget> createState() => _BarcodeDisplayWidgetState();
|
State<_RxTextDisplayWidget> createState() => _RxTextDisplayWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BarcodeDisplayWidgetState extends State<_BarcodeDisplayWidget> {
|
class _RxTextDisplayWidgetState extends State<_RxTextDisplayWidget> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Align(
|
return Align(
|
||||||
|
@ -203,19 +210,12 @@ class _BarcodeDisplayWidgetState extends State<_BarcodeDisplayWidget> {
|
||||||
Container(
|
Container(
|
||||||
height: 120,
|
height: 120,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: StreamBuilder<List<String>>(
|
child: SelectionArea(
|
||||||
stream: widget.barcodesStream,
|
child: StreamBuilder<String>(
|
||||||
builder: (context, value) => !value.hasData
|
stream: widget.rxTextStream,
|
||||||
? const SizedBox.expand()
|
builder: (context, value) =>
|
||||||
: ListView.separated(
|
!value.hasData ? const SizedBox.expand() : Text(value.data!),
|
||||||
padding: const EdgeInsets.only(top: 8),
|
)),
|
||||||
controller: widget.scrollController,
|
|
||||||
itemCount: value.data!.length,
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
itemBuilder: (context, index) => Text(value.data![index]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,33 +1,46 @@
|
||||||
use rkyv::Deserialize;
|
use raptorq::{Decoder, EncodingPacket, ObjectTransmissionInformation};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TxConfig {
|
pub struct TxConfig {
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
pub mtu: u16,
|
pub mtu: u16,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub filename: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RaptorPacket(pub Vec<u8>);
|
||||||
|
|
||||||
impl From<cuttle::TxConfig> for TxConfig {
|
impl From<cuttle::TxConfig> for TxConfig {
|
||||||
fn from(value: cuttle::TxConfig) -> Self {
|
fn from(value: cuttle::TxConfig) -> Self {
|
||||||
TxConfig {
|
TxConfig {
|
||||||
len: value.len,
|
len: value.len,
|
||||||
mtu: value.mtu,
|
mtu: value.mtu,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
filename: value.filename,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tx_config(bytes: Vec<u8>) -> Option<TxConfig> {
|
pub fn get_tx_config(bytes: Vec<u8>) -> Option<TxConfig> {
|
||||||
if let Ok(archive) = rkyv::check_archived_root::<cuttle::TxConfig>(&bytes) {
|
if let Ok(archive) = rkyv::check_archived_root::<cuttle::TxConfig>(&bytes) {
|
||||||
if let Ok::<cuttle::TxConfig, _>(conf) = archive.deserialize(&mut rkyv::Infallible) {
|
<cuttle::ArchivedTxConfig as rkyv::Deserialize<cuttle::TxConfig, rkyv::Infallible>>::deserialize(archive, &mut rkyv::Infallible)
|
||||||
return Some(conf.into());
|
.ok().map(Into::into)
|
||||||
} else {
|
} else {
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_raptor(buffer: Vec<u8>, txconf: TxConfig) -> bool {
|
pub fn decode_packets(packets: Vec<RaptorPacket>, txconf: TxConfig) -> Option<Vec<u8>> {
|
||||||
false
|
let conf = ObjectTransmissionInformation::with_defaults(txconf.len, txconf.mtu);
|
||||||
|
let mut decoder = Decoder::new(conf);
|
||||||
|
|
||||||
|
let mut res = None;
|
||||||
|
for RaptorPacket(p) in &packets {
|
||||||
|
res = decoder.decode(EncodingPacket::deserialize(p));
|
||||||
|
if res.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,12 @@ pub extern "C" fn wire_get_tx_config(port_: i64, bytes: *mut wire_uint_8_list) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn wire_check_raptor(
|
pub extern "C" fn wire_decode_packets(
|
||||||
port_: i64,
|
port_: i64,
|
||||||
buffer: *mut wire_uint_8_list,
|
packets: *mut wire_list_raptor_packet,
|
||||||
txconf: *mut wire_TxConfig,
|
txconf: *mut wire_TxConfig,
|
||||||
) {
|
) {
|
||||||
wire_check_raptor_impl(port_, buffer, txconf)
|
wire_decode_packets_impl(port_, packets, txconf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: allocate functions
|
// Section: allocate functions
|
||||||
|
@ -22,6 +22,15 @@ pub extern "C" fn new_box_autoadd_tx_config_0() -> *mut wire_TxConfig {
|
||||||
support::new_leak_box_ptr(wire_TxConfig::new_with_null_ptr())
|
support::new_leak_box_ptr(wire_TxConfig::new_with_null_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn new_list_raptor_packet_0(len: i32) -> *mut wire_list_raptor_packet {
|
||||||
|
let wrap = wire_list_raptor_packet {
|
||||||
|
ptr: support::new_leak_vec_ptr(<wire_RaptorPacket>::new_with_null_ptr(), len),
|
||||||
|
len,
|
||||||
|
};
|
||||||
|
support::new_leak_box_ptr(wrap)
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list {
|
pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list {
|
||||||
let ans = wire_uint_8_list {
|
let ans = wire_uint_8_list {
|
||||||
|
@ -47,12 +56,28 @@ impl Wire2Api<TxConfig> for *mut wire_TxConfig {
|
||||||
Wire2Api::<TxConfig>::wire2api(*wrap).into()
|
Wire2Api::<TxConfig>::wire2api(*wrap).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Wire2Api<Vec<RaptorPacket>> for *mut wire_list_raptor_packet {
|
||||||
|
fn wire2api(self) -> Vec<RaptorPacket> {
|
||||||
|
let vec = unsafe {
|
||||||
|
let wrap = support::box_from_leak_ptr(self);
|
||||||
|
support::vec_from_leak_ptr(wrap.ptr, wrap.len)
|
||||||
|
};
|
||||||
|
vec.into_iter().map(Wire2Api::wire2api).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wire2Api<RaptorPacket> for wire_RaptorPacket {
|
||||||
|
fn wire2api(self) -> RaptorPacket {
|
||||||
|
RaptorPacket(self.field0.wire2api())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Wire2Api<TxConfig> for wire_TxConfig {
|
impl Wire2Api<TxConfig> for wire_TxConfig {
|
||||||
fn wire2api(self) -> TxConfig {
|
fn wire2api(self) -> TxConfig {
|
||||||
TxConfig {
|
TxConfig {
|
||||||
len: self.len.wire2api(),
|
len: self.len.wire2api(),
|
||||||
mtu: self.mtu.wire2api(),
|
mtu: self.mtu.wire2api(),
|
||||||
description: self.description.wire2api(),
|
description: self.description.wire2api(),
|
||||||
|
filename: self.filename.wire2api(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,12 +92,26 @@ impl Wire2Api<Vec<u8>> for *mut wire_uint_8_list {
|
||||||
}
|
}
|
||||||
// Section: wire structs
|
// Section: wire structs
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_list_raptor_packet {
|
||||||
|
ptr: *mut wire_RaptorPacket,
|
||||||
|
len: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct wire_RaptorPacket {
|
||||||
|
field0: *mut wire_uint_8_list,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct wire_TxConfig {
|
pub struct wire_TxConfig {
|
||||||
len: u64,
|
len: u64,
|
||||||
mtu: u16,
|
mtu: u16,
|
||||||
description: *mut wire_uint_8_list,
|
description: *mut wire_uint_8_list,
|
||||||
|
filename: *mut wire_uint_8_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -94,12 +133,27 @@ impl<T> NewWithNullPtr for *mut T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NewWithNullPtr for wire_RaptorPacket {
|
||||||
|
fn new_with_null_ptr() -> Self {
|
||||||
|
Self {
|
||||||
|
field0: core::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for wire_RaptorPacket {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_null_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NewWithNullPtr for wire_TxConfig {
|
impl NewWithNullPtr for wire_TxConfig {
|
||||||
fn new_with_null_ptr() -> Self {
|
fn new_with_null_ptr() -> Self {
|
||||||
Self {
|
Self {
|
||||||
len: Default::default(),
|
len: Default::default(),
|
||||||
mtu: Default::default(),
|
mtu: Default::default(),
|
||||||
description: core::ptr::null_mut(),
|
description: core::ptr::null_mut(),
|
||||||
|
filename: core::ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,21 +35,21 @@ fn wire_get_tx_config_impl(port_: MessagePort, bytes: impl Wire2Api<Vec<u8>> + U
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn wire_check_raptor_impl(
|
fn wire_decode_packets_impl(
|
||||||
port_: MessagePort,
|
port_: MessagePort,
|
||||||
buffer: impl Wire2Api<Vec<u8>> + UnwindSafe,
|
packets: impl Wire2Api<Vec<RaptorPacket>> + UnwindSafe,
|
||||||
txconf: impl Wire2Api<TxConfig> + UnwindSafe,
|
txconf: impl Wire2Api<TxConfig> + UnwindSafe,
|
||||||
) {
|
) {
|
||||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool>(
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Option<Vec<u8>>>(
|
||||||
WrapInfo {
|
WrapInfo {
|
||||||
debug_name: "check_raptor",
|
debug_name: "decode_packets",
|
||||||
port: Some(port_),
|
port: Some(port_),
|
||||||
mode: FfiCallMode::Normal,
|
mode: FfiCallMode::Normal,
|
||||||
},
|
},
|
||||||
move || {
|
move || {
|
||||||
let api_buffer = buffer.wire2api();
|
let api_packets = packets.wire2api();
|
||||||
let api_txconf = txconf.wire2api();
|
let api_txconf = txconf.wire2api();
|
||||||
move |task_callback| Ok(check_raptor(api_buffer, api_txconf))
|
move |task_callback| Ok(decode_packets(api_packets, api_txconf))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ impl support::IntoDart for TxConfig {
|
||||||
self.len.into_into_dart().into_dart(),
|
self.len.into_into_dart().into_dart(),
|
||||||
self.mtu.into_into_dart().into_dart(),
|
self.mtu.into_into_dart().into_dart(),
|
||||||
self.description.into_into_dart().into_dart(),
|
self.description.into_into_dart().into_dart(),
|
||||||
|
self.filename.into_dart(),
|
||||||
]
|
]
|
||||||
.into_dart()
|
.into_dart()
|
||||||
}
|
}
|
||||||
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -17,7 +17,7 @@ pub use qr_utils::{get_content, mk_qr_bytes};
|
||||||
|
|
||||||
pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
||||||
pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
|
pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
|
||||||
pub const STREAMING_MTU: u16 = 2326;
|
pub const STREAMING_MTU: u16 = 1200;
|
||||||
|
|
||||||
/// The application state
|
/// The application state
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -88,21 +88,12 @@ pub fn stream_bytes(
|
||||||
filename: Option<&str>,
|
filename: Option<&str>,
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
let len = bytes.len() as u64;
|
let len = bytes.len() as u64;
|
||||||
let txconfig = TxConfig {
|
|
||||||
len,
|
|
||||||
mtu: STREAMING_MTU,
|
|
||||||
description: desc.to_string(),
|
|
||||||
filename: filename.map(|f| f.to_string()),
|
|
||||||
};
|
|
||||||
let txconfig = rkyv::to_bytes::<_, 256>(&txconfig)
|
|
||||||
.expect("tried to serialize the txconfig")
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let rng = &mut rand::thread_rng();
|
let rng = &mut rand::thread_rng();
|
||||||
|
|
||||||
let config = ObjectTransmissionInformation::with_defaults(len, STREAMING_MTU);
|
let config = ObjectTransmissionInformation::with_defaults(len, STREAMING_MTU);
|
||||||
let encoder = Encoder::new(&bytes, config);
|
let encoder = Encoder::new(&bytes, config);
|
||||||
|
|
||||||
let mut packets = encoder
|
let mut packets = encoder
|
||||||
.get_encoded_packets(10)
|
.get_encoded_packets(10)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -111,9 +102,19 @@ pub fn stream_bytes(
|
||||||
|
|
||||||
packets.shuffle(rng);
|
packets.shuffle(rng);
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
for packet in packets.iter().cycle() {
|
for packet in packets.iter().cycle() {
|
||||||
tx.send(packet.clone()).unwrap_or_default();
|
tx.send(packet.clone()).unwrap_or_default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
txconfig
|
|
||||||
|
let txconfig = TxConfig {
|
||||||
|
len,
|
||||||
|
mtu: STREAMING_MTU,
|
||||||
|
description: desc.to_string(),
|
||||||
|
filename: filename.map(|f| f.to_string()),
|
||||||
|
};
|
||||||
|
rkyv::to_bytes::<_, 256>(&txconfig)
|
||||||
|
.expect("tried to serialize the txconfig")
|
||||||
|
.to_vec()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{stream_bytes, Content, StreamedContent};
|
||||||
/// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG.
|
/// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG.
|
||||||
pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
|
pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
|
||||||
let qr = fast_qr::QRBuilder::new(bytes)
|
let qr = fast_qr::QRBuilder::new(bytes)
|
||||||
.ecl(fast_qr::ECL::M)
|
.ecl(fast_qr::ECL::L)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue