WebsocketConnection using libsoup-2.4 is sometimes blocking GTK ui thread and prevents opening the main window - websocket

I have a simple GTK app written in Vala with a websocket connection (using libsoup-2.4).
Problem: my app sometimes freezes at startup (each ~10th startups) and doesn't show the gui window at all because of blocking somewhere in libsoup on socket connection.
Vala 0.40.25 with
gtk+-3.0
glib-2.0
gobject-2.0
libsoup-2.4
Here the code:
using Gtk;
class WebsocketConnection {
private Soup.WebsocketConnection websocket_connection;
public signal void ws_message(int type, string message);
public signal void connection_succeeded();
public signal void connection_established();
public signal void connection_failed();
public signal void connection_disengaged();
private string host;
public WebsocketConnection(string host) {
this.host = host;
}
private static string decode_bytes(Bytes byt, int n) {
return (string)byt.get_data();
}
public void init_connection_for(string host) {
MainLoop loop = new MainLoop();
var socket_client = new Soup.Session();
string url = "ws://%s:8080/".printf(host);
message(#"connect to $url");
var websocket_message = new Soup.Message("GET", url);
socket_client.websocket_connect_async.begin(websocket_message, null, null, null, (obj, res) => {
try {
websocket_connection = socket_client.websocket_connect_async.end(res);
message("Connected!");
connection_succeeded();
if (websocket_connection != null) {
websocket_connection.message.connect((type, m_message) => {
ws_message(type, decode_bytes(m_message, m_message.length));
});
websocket_connection.closed.connect(() => {
message("Connection closed");
connection_disengaged();
});
}
} catch (Error e) {
message("Remote error: " + e.message + " " + e.code.to_string());
connection_failed();
loop.quit();
}
loop.quit();
});
loop.run();
}
}
class Main : Gtk.Application {
public Main() {
Object(
application_id: "com.github.syfds.websocket-libsoup-vala-example",
flags : ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new ApplicationWindow(this);
window.title = "Hello, World!";
window.border_width = 10;
window.window_position = WindowPosition.CENTER;
window.set_default_size(350, 70);
window.destroy.connect(Gtk.main_quit);
var grid = new Grid();
grid.orientation = Orientation.VERTICAL;
grid.column_spacing = 5;
grid.row_spacing = 5;
var main_label = new Label("...");
var websocket_host_input = new Entry();
var send_message_btn = new Button.with_label("Connect");
var websocket_host = "192.168.1.252";
var connection = new WebsocketConnection(websocket_host);
connection.connection_succeeded.connect(() => {
message("Connection succeeded");
main_label.set_text("Connection succeeded");
});
connection.connection_failed.connect(() => {
message("Connection failed");
});
connection.ws_message.connect((type, msg) => {
message("message received " + msg);
});
connection.init_connection_for(websocket_host);
grid.add(main_label);
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var app = new Main();
return app.run(args);
}
}
if I connect to the process over gdb I see following picture:
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7f38e6cbbac0 (LWP 29885) "com.github.syfd" 0x00007f38e53098f6 in __libc_recv (
fd=14, buf=0x55890440bc00, len=1024, flags=0) at ../sysdeps/unix/sysv/linux/recv.c:28
2 Thread 0x7f38d5d1c700 (LWP 29886) "gmain" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903d13b40, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
3 Thread 0x7f38d551b700 (LWP 29887) "gdbus" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903d30760, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
4 Thread 0x7f38cffff700 (LWP 29888) "dconf worker" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903f6ccb0, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
(gdb) bt
#0 0x00007f38e53098f6 in __libc_recv (fd=14, buf=0x55890440bc00, len=1024, flags=0)
at ../sysdeps/unix/sysv/linux/recv.c:28
#1 0x00007f38e5eb54f4 in () at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
#2 0x00007f38e5667558 in () at /usr/lib/x86_64-linux-gnu/libsoup-2.4.so.1
#3 0x00007f38e59173a5 in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#4 0x00007f38e5917770 in () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#5 0x00007f38e59177fc in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#6 0x00007f38e5ed8f3d in g_application_run () at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
#7 0x0000558902d69e8f in main_main (args=0x7ffeb2b775f8, args_length1=1)
at /home/sergej/workspace/websocket-libsoup-vala-example-v2/src/Main.vala:121
#8 0x0000558902d69ed2 in main (argc=1, argv=0x7ffeb2b775f8)
at /home/sergej/workspace/websocket-libsoup-vala-example-v2/src/Main.vala:119
It looks like __libc_recv at ../sysdeps/unix/sysv/linux/recv.c:28 is blocking here but I can't understand why and how to do the connection on non-blocking way.
I appreciate any help/hints how to solve the issue with blocked UI or how to create non-blocking websocket connection with libsoup.

You're using async methods, but it needs a bit of work. Make your init_connection_for method async:
public async void init_connection_for(string host) { ... }
then yield to the call to make the websocket connection:
var websocket_connection = yield socket_client.websocket_connect_async(websocket_message, null, null, null);
Then you call your function to set things up at the beginning before it then yields and gets called back by the asynchronous connection:
connection.init_connection_for.begin(websocket_host);
You then also need to compile with GIO, --pkg gio-2.0, for the async methods.
This will get you moving forwards, but there is a lot more that can be done to simplify and re-architect your code. You don't need the extra MainLoop for example.
This is a diff of the changes to help you progress:
--- original.vala 2021-08-08 15:59:41.757378207 +0100
+++ modifed.vala 2021-08-08 17:18:58.680837130 +0100
## -18,35 +18,33 ##
return (string)byt.get_data();
}
- public void init_connection_for(string host) {
+ public async void init_connection_for(string host) {
MainLoop loop = new MainLoop();
var socket_client = new Soup.Session();
string url = "ws://%s:8080/".printf(host);
message(#"connect to $url");
var websocket_message = new Soup.Message("GET", url);
- socket_client.websocket_connect_async.begin(websocket_message, null, null, null, (obj, res) => {
- try {
- websocket_connection = socket_client.websocket_connect_async.end(res);
- message("Connected!");
-
- connection_succeeded();
- if (websocket_connection != null) {
- websocket_connection.message.connect((type, m_message) => {
- ws_message(type, decode_bytes(m_message, m_message.length));
- });
- websocket_connection.closed.connect(() => {
- message("Connection closed");
- connection_disengaged();
- });
- }
- } catch (Error e) {
- message("Remote error: " + e.message + " " + e.code.to_string());
- connection_failed();
- loop.quit();
+ var websocket_connection = yield socket_client.websocket_connect_async(websocket_message, null, null, null);
+ try {
+ message("Connected!");
+
+ connection_succeeded();
+ if (websocket_connection != null) {
+ websocket_connection.message.connect((type, m_message) => {
+ ws_message(type, decode_bytes(m_message, m_message.length));
+ });
+ websocket_connection.closed.connect(() => {
+ message("Connection closed");
+ connection_disengaged();
+ });
}
+ } catch (Error e) {
+ message("Remote error: " + e.message + " " + e.code.to_string());
+ connection_failed();
loop.quit();
- });
+ }
+ loop.quit();
loop.run();
}
## -94,7 +92,7 ##
message("message received " + msg);
});
- connection.init_connection_for(websocket_host);
+ connection.init_connection_for.begin(websocket_host);
grid.add(main_label);
window.add(grid);
window.show_all();

Related

TOTP Problem - Microsoft Authenticator is not matching the code generated on server

I am getting a not verified using the TOTP method I have found on the following link.
OTP code generation and validation with otp.net
!My! code is below.
The _2FAValue line at the top is embedded into the QR barcode that Microsoft Authenticator attaches too.
The _Check... Function is the server ajax call to the server which implements OTP.Net library exposing TOTP calculation.
MakeTOTPSecret creates an SHA1 version of a Guid which is applied to the User profile and stored in _gTOTPSecret. NB: This IS populated in the places it is used.
I think I must have missed something obvious to get a result, here.
loSetup2FAData._s2FAValue = $#"otpauth://totp/{loUser.UserName}?secret={loUser.MakeTOTPSecret()}&digits=6&issuer={Booking.Library.Classes.Constants._sCompanyName}&period=60&algorithm=SHA1";
[AllowAnonymous]
public JsonResult _CheckTOTPCodeOnServer([FromBody] Booking.Site.Models.Shared.CheckTotpData loCheckTotpData)
{
string lsMessage = "<ul>";
try
{
string lsEmail = this.Request.HttpContext.Session.GetString("Buku_sEmail");
Booking.Data.DB.Extensions.IdentityExtend.User loUser = this._oDbContext.Users.Where(U => U.UserName.ToLower() == lsEmail.ToLower() || U.Email == lsEmail).FirstOrDefault();
if (loUser != null && loUser.Load(this._oDbContext) && loUser._gTOTPSecret != Guid.Empty)
{
OtpNet.Totp loTotp = new Totp(Booking.Library.Classes.Utility.StringToBytes(loUser.MakeTOTPSecret()), 60, OtpHashMode.Sha1, 6);
loTotp.ComputeTotp(DateTime.Now);
long lnTimeStepMatched = 0;
bool lbVerify = loTotp.VerifyTotp(loCheckTotpData._nTotp.ToString("000000"), out lnTimeStepMatched, new VerificationWindow(2, 2));
if (lbVerify)
{
lsMessage += "<li>Successfully validated Totp code</li>";
lsMessage += "<li>Save is now activated</li>";
return this.Json(new { bResult = true, sMessage = lsMessage + "</ul>" });
}
}
}
catch (Exception loException)
{
lsMessage += "<li>" + Booking.Library.Classes.Utility.MakeExceptionMessage(true, loException, "\r\n", "_CheckTOTPCodeOnServer") + "</li>";
}
lsMessage += "<li>Unsuccessfully validated Totp code</li>";
return this.Json(new { bResult = false, sMessage = lsMessage + "</ul>" });
}
public string MakeTOTPSecret()
{
string lsReturn = String.Empty;
try
{
using (SHA1Managed loSha1 = new SHA1Managed())
{
var loHash = loSha1.ComputeHash(Encoding.UTF8.GetBytes(this._gTOTPSecret.ToString()));
var loSb = new StringBuilder(loHash.Length * 2);
foreach (byte b in loHash)
{
loSb.Append(b.ToString("X2"));
}
lsReturn = loSb.ToString();
}
}
catch (Exception loException)
{
Booking.Library.Classes.Utility.MakeExceptionMessage(true, loException, "\r\n", "Identity.MakeSHA1Secret");
}
return lsReturn;
}

I am trying to use flutter Isolate with database data. But it's throwing an error. I don't know why is it happening?

I'm using the hive database. My code is:
HiveStocktaking? stocktaking =
_database.getStocktakingById(_stocktakingId);
StocktakingStats stocktakingStats =
_database.getStocktakingStats(_stocktakingId, true);
List<HiveStocktakingItem> stocktakingItemShortage = _database
.getStocktakingAllItem(_stocktakingId, 'shortage', '',
getAll: withProducts)
.values
.first;
List<HiveStocktakingItem> stocktakingItemSurplus = _database
.getStocktakingAllItem(_stocktakingId, 'surplus', '',
getAll: withProducts)
.values
.first;
int shortageLength = withProducts ? stocktakingItemShortage.length : 0;
int surplusLength = withProducts ? stocktakingItemSurplus.length : 0;
if (type == 'excel') {
createIsolate(
stocktaking,
stocktakingStats,
stocktakingItemShortage,
stocktakingItemSurplus,
shortageLength,
surplusLength,
);
}
Future createIsolate(
HiveStocktaking? stocktaking,
StocktakingStats stocktakingStats,
List<HiveStocktakingItem> stocktakingItemShortage,
List<HiveStocktakingItem> stocktakingItemSurplus,
int shortageLength,
int surplusLength) async {
ReceivePort receivePort = ReceivePort();
Isolate.spawn(isolateFunction, receivePort.sendPort);
SendPort childSendPort = await receivePort.first;
ReceivePort responsePort = ReceivePort();
childSendPort.send([
stocktaking,
stocktakingStats,
stocktakingItemShortage,
stocktakingItemSurplus,
shortageLength,
surplusLength,
responsePort.sendPort
]);
var sum = await responsePort.first;
print('sum: $sum');
}
void isolateFunction(SendPort mainSendPort) async {
ReceivePort childReceivePort = ReceivePort();
mainSendPort.send(childReceivePort.sendPort);
await for (var message in childReceivePort) {
HiveStocktaking? stocktaking = message[0];
StocktakingStats stocktakingStats = message[1];
List<HiveStocktakingItem> stocktakingItemShortage = message[2];
List<HiveStocktakingItem> stocktakingItemSurplus = message[3];
int shortageLength = message[4];
int surplusLength = message[5];
SendPort replyPort = message[6];
//heavy task
sync.Workbook workbook = sync.Workbook();
var sheet = workbook.worksheets[0];
sheet.getRangeByIndex(1, 1)
..setText('Отчет по инвентаризации')
..columnWidth = 40
..cellStyle.bold = true
..rowHeight = 30
..cellStyle.fontSize = 20;
sheet.getRangeByIndex(2, 1).setText('Магазин: ${stocktaking?.shopName}');
sheet
.getRangeByIndex(3, 1)
.setText('Дата начала: ${stocktaking?.createdAt}');
sheet
.getRangeByIndex(4, 1)
.setText('Дата завершения: ${stocktaking?.finishedAt}');
sheet.getRangeByIndex(2, 3)
..setText(
'Отсканировано товаров: ${BaseFunctions.numberRound(stocktakingStats.totalScannedMeasurementValue)}')
..columnWidth = 30;
sheet.getRangeByIndex(3, 3).setText(
'Недостач: ${BaseFunctions.numberRound(stocktakingStats.totalMeasurementValue)}');
sheet.getRangeByIndex(4, 3).setText(
'Излишков: ${BaseFunctions.numberRound(stocktakingStats.surplus)}');
// etc generating codes
List<int> bytes = workbook.saveAsStream();
workbook.dispose();
var uint8list = Uint8List.fromList(bytes);
if (Platform.isMacOS) {
String fileName = 'stocktaking_report_' +
DateFormat('dd-MM-yyyy_HH-mm-ss').format(DateTime.now()) +
'.pdf';
String? path =
await PathProviderPlatform.instance.getApplicationSupportPath();
final File file =
File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName');
await file.writeAsBytes(bytes, flush: true);
await Process.run('open', <String>['$path/$fileName'], runInShell: true);
} else {
await FileSaver.instance.saveFile(
'stocktaking_report_' +
DateFormat('dd-MM-yyyy_HH-mm-ss').format(DateTime.now()),
uint8list,
'xlsx',
mimeType: MimeType.MICROSOFTEXCEL,
);
}
replyPort.send(1);
}
}
But it is throwing this error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:io' Class: _RandomAccessFileOpsImpl#13069316)
if I give the createIsolate fake data it works perfectly.
if (type == 'excel') {
ProjectFiles.createIsolate(
HiveStocktaking(),
StocktakingStats(),
[],
[],
0,
0,
);
}
I found a way to solve this issue. If you want to use the hive database in an isolate function note that you must init your database in the isolate function and close the boxes (which you want to use in the isolate) in the main thread. Register the boxes' adapter in the isolate. After your doing close the boxes which opened in isolate. To initiate the database in isolate, you have to set the database path to Hive.init(path). You can send this path through isolate's port. If you want to use closed boxes in the main thread you have to reopen it. Here some codes for example:
static Future<bool> myFunc() async {
ReceivePort receivePort = ReceivePort();
Isolate.spawn(isolateFunc, receivePort.sendPort);
SendPort childSendPort = await receivePort.first;
await Hive.box<YourBox>('boxName').close();
// database path
Directory appDocumentDir = await getApplicationDocumentsDirectory();
ReceivePort responsePort = ReceivePort();
childSendPort.send([appDocumentDir, responsePort.sendPort]);
var sum = await responsePort.first;
var box = await Hive.openBox<YourBox>('boxName');
HiveDatabase.setInstanceYourBox(box);
return sum == 1;
}
static void isolateDownloadStocktaking(SendPort mainSendPort) async {
ReceivePort childReceivePort = ReceivePort();
mainSendPort.send(childReceivePort.sendPort);
await for (var message in childReceivePort) {
Directory appDocumentDir = message[0];
Hive.init(appDocumentDir.path);
Hive.registerAdapter(YourBoxAdapter());
await HiveDatabase.getInstanceYourBox();
// you can use your box
SendPort replyPort = message[1];
await Hive.box<YourBox>('boxName').close();
replyPort.send(1);
}
}
HiveDatabase class is here:
class HiveDatabase {
static Box<YourBox>? _yourBox;
static HiveDatabase instance = HiveDatabase._();
HiveDatabase._();
static Future<HiveDatabase> getInstanceYourBox() async {
_yourBox ??= await Hive.openBox<YourBox>('boxName');
return instance;
}
static Future<HiveDatabase> setInstanceYourBox(Box<YourBox> box) async {
_yourBox = box;
return instance;
}
List<YourBox> elements() {
return (_yourBox?.values ?? []).toList();
}
Future<void> updateElement(YourBox value) async {
await _yourBox?.put(value.id, value);
}
Future<void> addElement(YourBox value) async {
await _yourBox?.put(value.id, value);
}
Future<void> deleteElement(int index) async {
await _yourBox?.deleteAt(index);
}
Future<void> clearYourBox() async {
await _yourBox?.clear();
}
}

Epson js SDK unable to use multiple printers

Intro
We're developing this javascript based web application that is supposed to print receipts using the epson javascript sdk.
Right now we've got this poc where multiple printers can be added to the app and where receipts can be printed per individual printer.
The problem is that the receipt will ONLY be printer from the last added printer.
Further investigating tells us that the sdk just uses the last added (connected) printer. This can be seen at the following images.
In the first image there are 2 printers setup. Notice the different ip addresses.
In the second image we log what EpsonPrinter instance is being used while printing. Notice the ip address is clearly the first printer.
In the third image we trace the network. Notice the ip address that is actually used (ignore the error).
We created our own EpsonPrinter class that can be found here or here below.
EpsonPrinter
export default class EpsonPrinter {
name = null
ipAddress = null
port = null
deviceId = null
crypto = false
buffer = false
eposdev = null
printer = null
intervalID = null
restry = 0
constructor (props) {
const {
name = 'Epson printer',
ipAddress,
port = 8008,
deviceId = 'local_printer',
crypto = false,
buffer = false
} = props
this.name = name
this.ipAddress = ipAddress
this.port = port
this.deviceId = deviceId
this.crypto = crypto
this.buffer = buffer
this.eposdev = new window.epson.ePOSDevice()
this.eposdev.onreconnecting = this.onReconnecting
this.eposdev.onreconnect = this.onReconnect
this.eposdev.ondisconnect = this.onDisconnect
this.connect()
}
onReconnecting = () => {
this.consoleLog('reconnecting')
}
onReconnect = () => {
this.consoleLog('reconnect')
}
onDisconnect = () => {
this.consoleLog('disconnect')
if (this.intervalID === null ){
this.intervalID = setInterval(() => this.reconnect(), 5000)
}
}
connect = () => {
this.consoleLog('connect')
this.eposdev.ondisconnect = null
this.eposdev.disconnect()
this.eposdev.connect(this.ipAddress, this.port, this.connectCallback)
}
reconnect = () => {
this.consoleLog('(Re)connect')
this.eposdev.connect(this.ipAddress, this.port, this.connectCallback)
}
connectCallback = (data) => {
clearInterval(this.intervalID)
this.intervalID = null
this.eposdev.ondisconnect = this.onDisconnect
if (data === 'OK' || data === 'SSL_CONNECT_OK') {
this.createDevice()
} else {
setTimeout(() => this.reconnect(), 5000)
}
}
createDevice = () => {
console.log('create device, try: ' + this.restry)
const options = {
crypto: this.crypto,
buffer: this.buffer
}
this.eposdev.createDevice(this.deviceId, this.eposdev.DEVICE_TYPE_PRINTER, options, this.createDeviceCallback)
}
createDeviceCallback = (deviceObj, code) => {
this.restry++
if (code === 'OK') {
this.printer = deviceObj
this.printer.onreceive = this.onReceive
} else if (code === 'DEVICE_IN_USE') {
if (this.restry < 5) {
setTimeout(() => this.createDevice(), 3000)
}
}
}
onReceive = (response) => {
this.consoleLog('on receive: ', response)
let message = `Print ${this.name} ${response.success ? 'success' : 'failute'}\n`
message += `Code: ${response.code}\n`
message += `Status: \n`
if (response.status === this.printer.ASB_NO_RESPONSE) { message += ' No printer response\n' }
if (response.status === this.printer.ASB_PRINT_SUCCESS) { message += ' Print complete\n' }
if (response.status === this.printer.ASB_DRAWER_KICK) { message += ' Status of the drawer kick number 3 connector pin = "H"\n' }
if (response.status === this.printer.ASB_OFF_LINE) { message += ' Offline status\n' }
if (response.status === this.printer.ASB_COVER_OPEN) { message += ' Cover is open\n' }
if (response.status === this.printer.ASB_PAPER_FEED) { message += ' Paper feed switch is feeding paper\n' }
if (response.status === this.printer.ASB_WAIT_ON_LINE) { message += ' Waiting for online recovery\n' }
if (response.status === this.printer.ASB_PANEL_SWITCH) { message += ' Panel switch is ON\n' }
if (response.status === this.printer.ASB_MECHANICAL_ERR) { message += ' Mechanical error generated\n' }
if (response.status === this.printer.ASB_AUTOCUTTER_ERR) { message += ' Auto cutter error generated\n' }
if (response.status === this.printer.ASB_UNRECOVER_ERR) { message += ' Unrecoverable error generated\n' }
if (response.status === this.printer.ASB_AUTORECOVER_ERR) { message += ' Auto recovery error generated\n' }
if (response.status === this.printer.ASB_RECEIPT_NEAR_END) { message += ' No paper in the roll paper near end detector\n' }
if (response.status === this.printer.ASB_RECEIPT_END) { message += ' No paper in the roll paper end detector\n' }
if (response.status === this.printer.ASB_SPOOLER_IS_STOPPED) { message += ' Stop the spooler\n' }
if (!response.success) {
alert(message)
// TODO: error message?
} else {
// TODO: success -> remove from queue
}
}
printReceipt = () => {
this.consoleLog(`Print receipt, `, this)
try {
if (!this.printer) {
throw `No printer created for ${this.name}`
}
this.printer.addPulse(this.printer.DRAWER_1, this.printer.PULSE_100)
this.printer.addText(`Printed from: ${this.name}\n`)
this.printer.send()
} catch (err) {
let message = `Print ${this.name} failure\n`
message += `Error: ${err}`
alert(message)
}
}
consoleLog = (...rest) => {
console.log(`${this.name}: `, ...rest)
}
}
Poc
The full working poc can be found here.
Epson javascript sdk
2.9.0
Does anyone have any experience with the epson sdk? It it supposed to be able to support multiple connections on the same time? Please let use know.
For the ones looking for a way to handle multiple printers using this SDK. We came up with the following work around:
We created a separated 'printer app' that is responsible for handling ONE printer connection and hosted it online. We then 'load' this printer app into our app that needs multiple connections using Iframes. Communication between app and printer app is done by means of window.PostMessage API to, for example, initialise the printer with the correct printer connection and providing data that has to be printed.
It takes some effort but was the most stable solution we could come up with handling multiple connections.
If anyone else comes up with a better approach please let me know!
You can checkout our printer app here for inspiration (inspect the app because it doesn't show much visiting it just like that).
For use your class EpsonPrinter, i add also myPrinters class after your class:
class myPrinters {
printers = null;
cantidad = 0;
constructor() {
console.log("Creo la coleccion de printers");
this.printers = [];
}
inicializarConeccionImpresora(idImpresora, ip, puerto, _deviceId) {
let ipAddress = ip;
let port = puerto;
let deviceId = _deviceId;
console.log("Agrego una impresora");
let myPrinter = new EpsonPrinter(ipAddress);
myPrinter.port = port;
myPrinter.deviceId = deviceId;
myPrinter.id = idImpresora;
console.log('Id impresora antes de connect es: ' + idImpresora);
myPrinter.connect();
this.printers[this.cantidad] = myPrinter;
this.cantidad ++;
}
imprimirPruebaJS(idImpresora) {
let printer = null;
let printerTemp = null
for(var i = 0; i < this.printers.length; i++) {
printerTemp = this.printers[i];
if (printerTemp.id == idImpresora) {
printer = printerTemp.printer;
}
}
if (printer == null) {
console.log("La impresora no esta iniciada en clase myPrinters");
return;
}
printer.addText('Hola mundo texto normal\n');
printer.addFeed();
printer.addCut(printer.CUT_FEED);
}
}
call myPrinters class in this way :
myEpsonPrinters = new myPrinters();
myEpsonPrinters.inicializarConeccionImpresora(1, '192.168.0.51', 8008, 'local_printer');
myEpsonPrinters.inicializarConeccionImpresora(2, '192.168.0.52', 8008, 'local_printer');
myEpsonPrinters.imprimirPruebaJS(1)
or
myEpsonPrinters.imprimirPruebaJS(2)
Test it and tell me.
Juan
Just create multiple objects for printing simple as this
this.eposdev = [];
let printersCnt = 3;
let self = this;
for(let i=1 ; i <= printersCnt ; i++){
this.eposdev[i] = new window.epson.ePOSDevice()
this.eposdev[i].onreconnecting = function (){
this.consoleLog('reConnecting')
}
this.eposdev[i].onreconnect = function (){
this.consoleLog('onReconnect')
}
this.eposdev[i].ondisconnect = function (){
this.consoleLog('onDisconnect')
}
}
function connect(printerKey) => {
this.consoleLog('connect')
this.eposdev.ondisconnect = null
this.eposdev.disconnect()
this.eposdev.connect(self.ipAddress[printerKey], self.port[printerKey], function(){
clearInterval(self.intervalID)
self.intervalID = null
self.eposdev[i].ondisconnect = self.ondisconnect
if (data === 'OK' || data === 'SSL_CONNECT_OK') {
console.log('create device, try: ' + self.restry)
const options = {
crypto: self.crypto,
buffer: self.buffer
}
self.eposdev[printerKey].createDevice(self.deviceId, self.eposdev[printerKey].DEVICE_TYPE_PRINTER, options, function(deviceObj, code){
this.restry++
if (code === 'OK') {
self.printer[printerKey] = deviceObj
self.printer.onreceive = function(){
console.log("onreceive");
}
} else if (code === 'DEVICE_IN_USE') {
if (self.restry < 5) {
setTimeout(() => self.createDevice(printerKey), 3000)
}
})
}
} else {
setTimeout(() => self.reconnect(printerKey), 5000)
}
})
}
Epson says that with version 2.12.0 you can add more than one printer.

JS: java.lang.Exception: Failed resolving method read on class android.media.AudioRecord

I'm trying to use android.media.AudioRecord to save audio, initialization is OK, startRecording() is also called without error, but when I start reading audio from the buffer I got error Failed resolving method read on class android.media.AudioRecord.
Here is the code:
const SAMPLE_RATE = 44100;
const RECORD_AUDIO = android.Manifest.permission.RECORD_AUDIO;
const AudioRecord = android.media.AudioRecord;
const AudioFormat = android.media.AudioFormat;
const MediaRecorder = android.media.MediaRecorder;
ngOnInit(): void {
this.bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (this.bufferSize == AudioRecord.ERROR || this.bufferSize == AudioRecord.ERROR_BAD_VALUE) {
this.bufferSize = SAMPLE_RATE * 2;
}
this.bufferSize = this.bufferSize * 10;
this.audioBuffer = new Array(this.bufferSize / 2);
if (!permissions.hasPermission(RECORD_AUDIO)) {
permissions.requestPermission(RECORD_AUDIO).then(() => {
this.createRecorder();
}, (err) => {
console.log('[BrowseComponent] ngOnInit, ', 'permissions error:', err);
});
}
else {
this.createRecorder();
}
}
createRecorder() {
this.record = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(SAMPLE_RATE)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.setBufferSizeInBytes(this.bufferSize)
.build();
this.recordState = this.record && this.record.getState();
if (this.recordState != AudioRecord.STATE_INITIALIZED) {
console.error('[BrowseComponent] createRecorder, ', 'AudioRecord can\'t initialize, state:', this.recordState);
return;
}
console.log('[BrowseComponent] createRecorder, ', 'AudioRecord:', this.record);
}
startRecord() {
this.recording = true;
this.record.startRecording();
this.shortsRead = 0;
while (this.recording) {
const numberOfShort = this.record.read(this.audioBuffer, 0, this.bufferSize);
this.shortsRead += numberOfShort;
// Do something with the audioBuffer
}
}
startRecord() is called from (tap) button handler.
Any ideas what may be wrong?
I guess the problem is that you are passing JavaScript Array instead of the Java Primitive (short) typed Array, so the runtime is unable to identify a method that matches the given parameters.
Use Array.create method to typecast, refer the docs here for more details.

pass string to server side with ajax in tapestry

<t:if test="needsSetName">
<label for="userfunction">${message:setname}</label>
<t:textfield
t:mixins="zoneUpdater"
tabindex="-1"
autofocus="false"
ZoneUpdater.clientEvent="keyup"
ZoneUpdater.event="valueChangedSetName"
ZoneUpdater.zone="transferZone"
ZoneUpdater.timeout="3000"
t:id="setName"
value="setName"/>
</t:if>
#OnEvent(value = "valueChangedSetName")
protected void onValueChangedSetName(#RequestParameter(value = "param", allowBlank = true) String setName)
{
TransferOrder to = baskets.getTransferOrder();
this.setName = setName;
to.setComment("Rename from " + this.setName + " to " + setName);
nextSetName = setName;
zoneHubService.updatePendingTransfer();
zoneHubService.addCallback(new JavaScriptCallback()
{
#Override
public void run(JavaScriptSupport javascriptSupport)
{
javascriptSupport.addScript(
String.format("var thing=jQuery('#%s'); thing.focus();thing[0].setSelectionRange(10000, 10000);",
setNameField.getClientId()));
}
});
}
So my problem is, when i type some text into the textfield, it removes symbols like #,&,.. and everything else that comes after these symbols
When i use +, it turns into space.
The string that i get from the server in my method is already "edited".
I'm new to tapestry and ajax and i don't know how to solve this problem.
What do i have to do, so that i get the string back from the server without the server removing these symbols?
I solved my problem.
In my zone-updater.js I had to use encodeURIComponent on my string.
For anyone who has the same problem here is the link to the zoneUpdater.js code.
http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html
The link I, that I found to solve the bug is down.
define([ "jquery", "t5/core/zone" ], function($, zoneManager) {
return function(elementId, clientEvent, listenerURI, zoneElementId, timeout) {
var $element = $("#" + elementId);
var mytimeout;
if (clientEvent) {
$element.on(clientEvent, updateZone);
}
function updateZone() {
jQuery($element).removeClass('saved');
if (mytimeout != null) {
clearTimeout(mytimeout);
}
mytimeout = setTimeout(function() {
var listenerURIWithValue = listenerURI;
if ($element.val()) {
listenerURIWithValue = appendQueryStringParameter(
listenerURIWithValue, 'param', $element.val());
listenerURIWithValue = appendQueryStringParameter(
listenerURIWithValue, 'element', elementId);
}
zoneManager.deferredZoneUpdate(zoneElementId,
listenerURIWithValue);
}, timeout);
}
}
function appendQueryStringParameter(url, name, value) {
if (url.indexOf('?') < 0) {
url += '?'
} else {
url += '&';
}
value = encodeURIComponent(value);
url += name + '=' + value;
return url;
}
});

Resources