mirror of
https://github.com/esiur/esiur-dart.git
synced 2025-05-06 04:02:57 +00:00
1.2.4
This commit is contained in:
parent
7eae6b47ce
commit
4c36f591da
@ -1,7 +1,7 @@
|
||||
# Esyur
|
||||
# Esiur
|
||||
Distributed Object Framework
|
||||
|
||||
## Getting Started
|
||||
For help getting started with Esyur, view our
|
||||
[online documentation](http://www.esyur.com), which offers tutorials,
|
||||
For help getting started with Esiur, view our
|
||||
[online documentation](http://www.esiur.com), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
@ -62,7 +62,6 @@ export 'src/Net/IIP/DistributedResourceQueueItemType.dart';
|
||||
export 'src/Net/Packets/IIPAuthPacket.dart';
|
||||
export 'src/Net/Packets/IIPAuthPacketAction.dart';
|
||||
export 'src/Net/Packets/IIPAuthPacketCommand.dart';
|
||||
export 'src/Net/Packets/IIPAuthPacketMethod.dart';
|
||||
export 'src/Net/Packets/IIPPacket.dart';
|
||||
export 'src/Net/Packets/IIPPacketAction.dart';
|
||||
export 'src/Net/Packets/IIPPacketCommand.dart';
|
||||
@ -80,6 +79,7 @@ export 'src/Net/Sockets/TCPSocket.dart';
|
||||
export 'src/Security/Authority/Authentication.dart';
|
||||
export 'src/Security/Authority/AuthenticationState.dart';
|
||||
export 'src/Security/Authority/AuthenticationType.dart';
|
||||
export 'src/Security/Authority/AuthenticationMethod.dart';
|
||||
export 'src/Security/Authority/ClientAuthentication.dart';
|
||||
export 'src/Security/Authority/CoHostAuthentication.dart';
|
||||
export 'src/Security/Authority/HostAuthentication.dart';
|
@ -1,52 +1,42 @@
|
||||
library esyur;
|
||||
library esiur;
|
||||
|
||||
import 'AsyncReply.dart';
|
||||
|
||||
class AsyncQueue<T> extends AsyncReply<T>
|
||||
{
|
||||
List<AsyncReply<T>> _list = new List<AsyncReply<T>>();
|
||||
class AsyncQueue<T> extends AsyncReply<T> {
|
||||
List<AsyncReply<T>> _list = new List<AsyncReply<T>>();
|
||||
|
||||
// object queueLock = new object();
|
||||
|
||||
add(AsyncReply<T> reply)
|
||||
{
|
||||
//lock (queueLock)
|
||||
_list.add(reply);
|
||||
|
||||
//super._resultReady = false;
|
||||
super.setResultReady(false);
|
||||
add(AsyncReply<T> reply) {
|
||||
//lock (queueLock)
|
||||
_list.add(reply);
|
||||
|
||||
reply.then(processQueue);
|
||||
}
|
||||
//super._resultReady = false;
|
||||
super.setResultReady(false);
|
||||
|
||||
remove(AsyncReply<T> reply)
|
||||
{
|
||||
//lock (queueLock)
|
||||
_list.remove(reply);
|
||||
processQueue(null);
|
||||
}
|
||||
|
||||
void processQueue(T o)
|
||||
{
|
||||
//lock (queueLock)
|
||||
for (var i = 0; i < _list.length; i++)
|
||||
if (_list[i].ready)
|
||||
{
|
||||
super.trigger(_list[i].result);
|
||||
super.ready = false;
|
||||
_list.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
|
||||
//super._resultReady = (_list.length == 0);
|
||||
super.setResultReady(_list.length == 0);
|
||||
}
|
||||
|
||||
AsyncQueue()
|
||||
{
|
||||
|
||||
}
|
||||
reply.then(processQueue);
|
||||
}
|
||||
|
||||
remove(AsyncReply<T> reply) {
|
||||
//lock (queueLock)
|
||||
_list.remove(reply);
|
||||
processQueue(null);
|
||||
}
|
||||
|
||||
void processQueue(T o) {
|
||||
//lock (queueLock)
|
||||
for (var i = 0; i < _list.length; i++)
|
||||
if (_list[i].ready) {
|
||||
super.trigger(_list[i].result);
|
||||
super.ready = false;
|
||||
_list.removeAt(i);
|
||||
i--;
|
||||
} else
|
||||
break;
|
||||
|
||||
//super._resultReady = (_list.length == 0);
|
||||
super.setResultReady(_list.length == 0);
|
||||
}
|
||||
|
||||
AsyncQueue() {}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ enum ExceptionCode
|
||||
{
|
||||
HostNotReachable,
|
||||
AccessDenied,
|
||||
UserNotFound,
|
||||
UserOrTokenNotFound,
|
||||
ChallengeFailed,
|
||||
ResourceNotFound,
|
||||
AttachDenied,
|
||||
@ -29,5 +29,6 @@ enum ExceptionCode
|
||||
MethodNotFound,
|
||||
PropertyNotFound,
|
||||
SetPropertyDenied,
|
||||
ReadOnlyProperty
|
||||
ReadOnlyProperty,
|
||||
GeneralFailure
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// library esyur;
|
||||
// library esiur;
|
||||
|
||||
import 'IEventHandler.dart';
|
||||
|
||||
|
@ -22,6 +22,10 @@ SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:esiur/src/Security/Authority/AuthenticationMethod.dart';
|
||||
|
||||
import '../../Core/AsyncBag.dart';
|
||||
|
||||
import '../Sockets/TCPSocket.dart';
|
||||
@ -61,7 +65,6 @@ import '../Packets/IIPPacketAction.dart';
|
||||
import '../Packets/IIPPacketCommand.dart';
|
||||
import '../Packets/IIPPacketEvent.dart';
|
||||
import '../Packets/IIPPacketReport.dart';
|
||||
import '../Packets/IIPAuthPacketMethod.dart';
|
||||
import '../../Data/BinaryList.dart';
|
||||
import '../NetworkConnection.dart';
|
||||
import '../../Data/Guid.dart';
|
||||
@ -97,7 +100,7 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
|
||||
Session _session;
|
||||
|
||||
DC _localPassword;
|
||||
DC _localPasswordOrToken;
|
||||
DC _localNonce, _remoteNonce;
|
||||
|
||||
String _hostname;
|
||||
@ -181,7 +184,6 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
&& instance.attributes.containsKey("password"))
|
||||
{
|
||||
|
||||
|
||||
var host = instance.name.split(":");
|
||||
// assign domain from hostname if not provided
|
||||
|
||||
@ -193,17 +195,29 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
|
||||
var password = DC.stringToBytes(instance.attributes["password"].toString());
|
||||
|
||||
return connect(method: AuthenticationMethod.Credentials, domain: domain, hostname: address, port: port, passwordOrToken: password, username: username);
|
||||
|
||||
}
|
||||
else if (instance.attributes.containsKey("token"))
|
||||
{
|
||||
var host = instance.name.split(":");
|
||||
// assign domain from hostname if not provided
|
||||
|
||||
return connect(domain: domain, hostname: address, port: port, password: password, username: username);
|
||||
var address = host[0];
|
||||
var port = int.parse(host[1]);
|
||||
|
||||
var domain = instance.attributes.containsKey("domain") ? instance.attributes["domain"] : address;
|
||||
|
||||
var token = DC.stringToBytes(instance.attributes["token"].toString());
|
||||
var tokenIndex = instance.attributes["tokenIndex"] ?? 0;
|
||||
return connect(method: AuthenticationMethod.Credentials, domain: domain, hostname: address, port: port, passwordOrToken: token, tokenIndex: tokenIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>.ready(true);
|
||||
}
|
||||
|
||||
AsyncReply<bool> connect({ISocket socket, String hostname, int port, String username, DC password, String domain})
|
||||
AsyncReply<bool> connect({AuthenticationMethod method, ISocket socket, String hostname, int port, String username, int tokenIndex, DC passwordOrToken, String domain})
|
||||
{
|
||||
if (_openReply != null)
|
||||
throw AsyncException(ErrorType.Exception, 0, "Connection in progress");
|
||||
@ -215,9 +229,11 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
_session = new Session(new ClientAuthentication()
|
||||
, new HostAuthentication());
|
||||
|
||||
_session.localAuthentication.method = method;
|
||||
_session.localAuthentication.tokenIndex = tokenIndex;
|
||||
_session.localAuthentication.domain = domain;
|
||||
_session.localAuthentication.username = username;
|
||||
_localPassword = password;
|
||||
_localPasswordOrToken = passwordOrToken;
|
||||
}
|
||||
|
||||
if (_session == null)
|
||||
@ -350,16 +366,35 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
{
|
||||
_session = new Session(new ClientAuthentication()
|
||||
, new HostAuthentication());
|
||||
|
||||
|
||||
_session.localAuthentication.method = AuthenticationMethod.Credentials;
|
||||
_session.localAuthentication.domain = domain;
|
||||
_session.localAuthentication.username = username;
|
||||
_localPassword = DC.stringToBytes(password);
|
||||
|
||||
_localPasswordOrToken = DC.stringToBytes(password);
|
||||
|
||||
init();
|
||||
|
||||
assign(socket);
|
||||
}
|
||||
|
||||
DistributedConnection.connectWithToken(ISocket socket, String domain, int tokenIndex, String token)
|
||||
{
|
||||
_session = new Session(new ClientAuthentication()
|
||||
, new HostAuthentication());
|
||||
|
||||
_session.localAuthentication.method = AuthenticationMethod.Token;
|
||||
_session.localAuthentication.domain = domain;
|
||||
_session.localAuthentication.tokenIndex = tokenIndex;
|
||||
|
||||
_localPasswordOrToken = DC.stringToBytes(token);
|
||||
|
||||
init();
|
||||
|
||||
assign(socket);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of a distributed connection
|
||||
/// </summary>
|
||||
@ -670,7 +705,7 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
{
|
||||
if (_authPacket.command == IIPAuthPacketCommand.Declare)
|
||||
{
|
||||
if (_authPacket.remoteMethod == IIPAuthPacketMethod.Credentials && _authPacket.localMethod == IIPAuthPacketMethod.None)
|
||||
if (_authPacket.remoteMethod == AuthenticationMethod.Credentials && _authPacket.localMethod == AuthenticationMethod.None)
|
||||
{
|
||||
|
||||
/*
|
||||
@ -768,7 +803,7 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
|
||||
// send our hash
|
||||
var localHash = SHA256.compute(new BinaryList()
|
||||
.addDC(_localPassword)
|
||||
.addDC(_localPasswordOrToken)
|
||||
.addDC(_localNonce)
|
||||
.addDC(_remoteNonce)
|
||||
.toDC());
|
||||
@ -788,7 +823,7 @@ class DistributedConnection extends NetworkConnection with IStore
|
||||
var remoteHash = SHA256.compute(new BinaryList()
|
||||
.addDC(_remoteNonce)
|
||||
.addDC(_localNonce)
|
||||
.addDC(_localPassword)
|
||||
.addDC(_localPasswordOrToken)
|
||||
.toDC());
|
||||
|
||||
|
||||
|
@ -22,8 +22,8 @@ SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
import 'package:esyur/esyur.dart';
|
||||
import 'package:esyur/src/Data/KeyValuePair.dart';
|
||||
import 'package:esiur/esiur.dart';
|
||||
import 'package:esiur/src/Data/KeyValuePair.dart';
|
||||
|
||||
import '../../Resource/IResource.dart';
|
||||
import '../../Core/AsyncReply.dart';
|
||||
|
@ -24,7 +24,7 @@ SOFTWARE.
|
||||
import '../../Data/DC.dart';
|
||||
import 'IIPAuthPacketAction.dart';
|
||||
import 'IIPAuthPacketCommand.dart';
|
||||
import 'IIPAuthPacketMethod.dart';
|
||||
import '../../Security/Authority/AuthenticationMethod.dart';
|
||||
|
||||
class IIPAuthPacket
|
||||
{
|
||||
@ -35,7 +35,7 @@ class IIPAuthPacket
|
||||
int errorCode;
|
||||
String errorMessage;
|
||||
|
||||
int localMethod;
|
||||
AuthenticationMethod localMethod;
|
||||
|
||||
DC sourceInfo;
|
||||
|
||||
@ -43,7 +43,7 @@ class IIPAuthPacket
|
||||
|
||||
DC sessionId;
|
||||
|
||||
int remoteMethod;
|
||||
AuthenticationMethod remoteMethod;
|
||||
|
||||
String domain;
|
||||
|
||||
@ -67,6 +67,8 @@ class IIPAuthPacket
|
||||
|
||||
DC remoteNonce;
|
||||
|
||||
int remoteTokenIndex;
|
||||
|
||||
int _dataLengthNeeded;
|
||||
|
||||
bool _notEnough(int offset, int ends, int needed)
|
||||
@ -149,8 +151,8 @@ class IIPAuthPacket
|
||||
}
|
||||
else if (command == IIPAuthPacketCommand.Declare)
|
||||
{
|
||||
remoteMethod = ((data[offset] >> 4) & 0x3);
|
||||
localMethod = ((data[offset] >> 2) & 0x3);
|
||||
remoteMethod = AuthenticationMethod.values[((data[offset] >> 4) & 0x3)];
|
||||
localMethod = AuthenticationMethod.values[((data[offset] >> 2) & 0x3)];
|
||||
var encrypt = ((data[offset++] & 0x2) == 0x2);
|
||||
|
||||
|
||||
@ -168,9 +170,9 @@ class IIPAuthPacket
|
||||
offset += domainLength;
|
||||
|
||||
|
||||
if (remoteMethod == IIPAuthPacketMethod.Credentials)
|
||||
if (remoteMethod == AuthenticationMethod.Credentials)
|
||||
{
|
||||
if (localMethod == IIPAuthPacketMethod.None)
|
||||
if (localMethod == AuthenticationMethod.None)
|
||||
{
|
||||
if (_notEnough(offset, ends, 33))
|
||||
return -_dataLengthNeeded;
|
||||
@ -191,7 +193,24 @@ class IIPAuthPacket
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
else if (remoteMethod == AuthenticationMethod.Token)
|
||||
{
|
||||
if (localMethod == AuthenticationMethod.None)
|
||||
{
|
||||
if (_notEnough(offset, ends, 40))
|
||||
return -_dataLengthNeeded;
|
||||
|
||||
remoteNonce = data.clip(offset, 32);
|
||||
|
||||
offset += 32;
|
||||
|
||||
remoteTokenIndex = data.getUint64(offset);
|
||||
offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (encrypt)
|
||||
{
|
||||
if (_notEnough(offset, ends, 2))
|
||||
@ -220,9 +239,10 @@ class IIPAuthPacket
|
||||
return -_dataLengthNeeded;
|
||||
|
||||
|
||||
if (remoteMethod == IIPAuthPacketMethod.Credentials)
|
||||
if (remoteMethod == AuthenticationMethod.Credentials
|
||||
|| remoteMethod == AuthenticationMethod.Token)
|
||||
{
|
||||
if (localMethod == IIPAuthPacketMethod.None)
|
||||
if (localMethod == AuthenticationMethod.None)
|
||||
{
|
||||
if (_notEnough(offset, ends, 32))
|
||||
return -_dataLengthNeeded;
|
||||
|
@ -1,8 +0,0 @@
|
||||
|
||||
class IIPAuthPacketMethod
|
||||
{
|
||||
static const int None = 0;
|
||||
static const int Certificate = 1;
|
||||
static const int Credentials = 2;
|
||||
static const int Token = 3;
|
||||
}
|
@ -23,7 +23,7 @@ SOFTWARE.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:esyur/esyur.dart';
|
||||
import 'package:esiur/esiur.dart';
|
||||
|
||||
import 'ISocket.dart';
|
||||
import '../../Data/DC.dart';
|
||||
|
@ -22,12 +22,17 @@ SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
import 'AuthenticationMethod.dart';
|
||||
|
||||
import 'AuthenticationType.dart';
|
||||
import 'Source.dart';
|
||||
|
||||
class Authentication
|
||||
{
|
||||
|
||||
int tokenIndex;
|
||||
AuthenticationMethod method;
|
||||
|
||||
String username;
|
||||
//Certificate certificate;
|
||||
String domain;
|
||||
|
16
lib/src/Security/Authority/AuthenticationMethod.dart
Normal file
16
lib/src/Security/Authority/AuthenticationMethod.dart
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
// class AuthenticationMethod
|
||||
// {
|
||||
// static const int None = 0;
|
||||
// static const int Certificate = 1;
|
||||
// static const int Credentials = 2;
|
||||
// static const int Token = 3;
|
||||
// }
|
||||
|
||||
enum AuthenticationMethod
|
||||
{
|
||||
None,
|
||||
Certificate,
|
||||
Credentials,
|
||||
Token,
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
name: esyur
|
||||
name: esiur
|
||||
description: Distributed Object Framework.
|
||||
version: 1.2.3
|
||||
version: 1.2.4
|
||||
# author: Ahmed Zamil <ahmed@dijlh.com>
|
||||
homepage: https://github.com/esyur/esyur-dart
|
||||
homepage: https://github.com/esiur/esiur-dart
|
||||
|
||||
environment:
|
||||
sdk: ">=2.1.0 <3.0.0"
|
||||
|
154
test/main.dart
154
test/main.dart
@ -1,128 +1,42 @@
|
||||
import "package:test/test.dart";
|
||||
import 'package:esyur/esyur.dart';
|
||||
import 'package:esiur/esiur.dart';
|
||||
import 'dart:io';
|
||||
|
||||
main() async
|
||||
{
|
||||
//test("Connect to server", () async {
|
||||
var now = DateTime.now();
|
||||
main() async {
|
||||
test("Connect to server", () async {
|
||||
// // // connect to the server
|
||||
var x = await Warehouse.get("iip://localhost:5000/sys/su",
|
||||
{"username": "admin", "password": "1234", "domain": "example.com"});
|
||||
|
||||
// // // connect to the server
|
||||
// var x = await Warehouse.get("iip://localhost:5000/sys/su", {"username": "admin", "password": "1234"
|
||||
// , "domain": "example.com"});
|
||||
|
||||
|
||||
var x = await Warehouse.get("iip://gps.dijlh.com:2628/app", {"username": "delta", "password": "interactivereflection2020"
|
||||
, "domain": "gps.dijlh.com"});
|
||||
|
||||
|
||||
// desc(x);
|
||||
var date = DateTime.now();
|
||||
|
||||
var from =DateTime(date.year, date.month, date.day);
|
||||
var to =DateTime(date.year, date.month, date.day + 1);
|
||||
|
||||
List<dynamic> trackers = await x.getMyTrackers();
|
||||
|
||||
|
||||
|
||||
var rt = await x.getObjectTracks(trackers[0], from, to, 0, 0, 0);
|
||||
|
||||
print("Time ${DateTime.now().difference(now).inSeconds}");
|
||||
|
||||
print(x.suspended);
|
||||
|
||||
DistributedConnection con = x.connection;
|
||||
//con.close();
|
||||
print(x.suspended);
|
||||
|
||||
now = DateTime.now();
|
||||
|
||||
//await con.reconnect();
|
||||
|
||||
print("Time ${DateTime.now().difference(now).inSeconds}");
|
||||
print(x.suspended);
|
||||
var u = await x.getMyTrackers();
|
||||
print(trackers[0].suspended);
|
||||
|
||||
u[0].on("moved", (x){
|
||||
print("Movvvvvvvvvvvvvvvvved");
|
||||
});
|
||||
|
||||
Future.delayed(Duration(seconds: 100));
|
||||
// for(var i = 0; i < trackers.length; i++)
|
||||
// print(trackers[i].name);
|
||||
|
||||
// var arc = await x.getObjectTracks(trackers[1], DateTime.now().subtract(Duration(days: 6)), DateTime.now());
|
||||
|
||||
// x.instance.store.on("close", (x){
|
||||
// print("Closed");
|
||||
// });
|
||||
|
||||
// x.on("modified", (peoperty, value){
|
||||
|
||||
// });
|
||||
|
||||
// var users = await x.Users.Slice(0, 10);
|
||||
|
||||
// print(users);
|
||||
// await sleep(Duration(seconds: 10));
|
||||
|
||||
// get property
|
||||
//print(x.Level);
|
||||
// listen to event
|
||||
//x.on("LevelUp", (v,y,z)=>print("Level up ${v} ${y}${z}"));
|
||||
// use await
|
||||
//print("Added successfully ${await x.Add(40)}");
|
||||
// use named arguments
|
||||
//print(await x.Add(value: 20));
|
||||
// test chunks
|
||||
//x.Stream(10).chunk((c)=>print(c));
|
||||
// property setter
|
||||
//x.Level += 900;
|
||||
|
||||
|
||||
//var msg = await stdin.readLineSync();
|
||||
|
||||
//print("Done");
|
||||
|
||||
//});
|
||||
|
||||
|
||||
print(x);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// describe object
|
||||
desc(dynamic x) {
|
||||
if (x is List)
|
||||
{
|
||||
for(var i = 0; i < x.length; i++)
|
||||
desc(x[i]);
|
||||
}
|
||||
else if (x is DistributedResource)
|
||||
{
|
||||
var y = x.instance.template;
|
||||
print("Fucntions = ${y.functions.length}\n");
|
||||
for (var i = 0; i < y.functions.length; i++) {
|
||||
print("Function ${y.functions[i].name} ${y.functions[i].expansion}");
|
||||
}
|
||||
print("------------------------------\n");
|
||||
print("Events = ${y.events.length}\n");
|
||||
for (var i = 0; i < y.events.length; i++) {
|
||||
print("Events ${y.events[i].name} ${y.events[i].expansion}");
|
||||
}
|
||||
desc(dynamic x) {
|
||||
if (x is List) {
|
||||
for (var i = 0; i < x.length; i++) desc(x[i]);
|
||||
} else if (x is DistributedResource) {
|
||||
var y = x.instance.template;
|
||||
print("Fucntions = ${y.functions.length}\n");
|
||||
for (var i = 0; i < y.functions.length; i++) {
|
||||
print("Function ${y.functions[i].name} ${y.functions[i].expansion}");
|
||||
}
|
||||
print("------------------------------\n");
|
||||
print("Events = ${y.events.length}\n");
|
||||
for (var i = 0; i < y.events.length; i++) {
|
||||
print("Events ${y.events[i].name} ${y.events[i].expansion}");
|
||||
}
|
||||
|
||||
print("------------------------------\n");
|
||||
print("Properties = ${y.properties.length}\n");
|
||||
for (var i = 0; i < y.properties.length; i++) {
|
||||
print("Property ${y.properties[i].name} ${y.properties[i].readExpansion}");
|
||||
// recursion
|
||||
//print("value = ${desc(x.get(y.properties[i].index))}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
print(x.toString());
|
||||
}
|
||||
}
|
||||
print("------------------------------\n");
|
||||
print("Properties = ${y.properties.length}\n");
|
||||
for (var i = 0; i < y.properties.length; i++) {
|
||||
print(
|
||||
"Property ${y.properties[i].name} ${y.properties[i].readExpansion}");
|
||||
// recursion
|
||||
//print("value = ${desc(x.get(y.properties[i].index))}");
|
||||
}
|
||||
} else {
|
||||
print(x.toString());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user