diff --git a/html/semiauto/assets/css/style.css b/html/semiauto/assets/css/style.css
index 933b7d3..4fdee9a 100644
--- a/html/semiauto/assets/css/style.css
+++ b/html/semiauto/assets/css/style.css
@@ -344,6 +344,16 @@ th, td {
color: #fff;
}
+.btn-disable {
+ color: white;
+ background-color: var(--bs-red);
+}
+
+.btn-enable {
+ color: white;
+ background-color: #36d636;
+}
+
.btn-logout {
background-color: #4280ab;
color: #fff;
diff --git a/html/semiauto/assets/js/custom.js b/html/semiauto/assets/js/custom.js
index f24c080..3ae64f0 100644
--- a/html/semiauto/assets/js/custom.js
+++ b/html/semiauto/assets/js/custom.js
@@ -28,8 +28,7 @@ let $input_gatenumber = null;
let $input_flightnumber = null;
let $input_licenseplate = null;
let $input_conveyorbelt = null;
-let $input_hours = null;
-let $input_minutes = null;
+let $input_etad = null;
let $row_airplane = null;
let $row_city = null;
@@ -42,6 +41,11 @@ let $col_conveyorbelt = null;
let $col_procedure = null;
let $col_licenseplate = null;
+/**
+ * @type {message[]}
+ */
+let selected_messages = [];
+
/**
* @typedef {Object} message
* @property {number} id - The ID of the message
@@ -438,8 +442,8 @@ function reload_database() {
});
}
-function empty_preview(){
- $preview_arabic.empty();
+function empty_preview() {
+ $preview_arabic.empty();
$preview_chinese.empty();
$preview_english.empty();
$preview_indonesia.empty();
@@ -455,7 +459,7 @@ function empty_preview(){
}
-function enable_disable_fields(){
+function enable_disable_fields() {
$row_airplane.hide();
$row_city.hide();
$row_gatenumber.hide();
@@ -469,10 +473,10 @@ function enable_disable_fields(){
// show airplane row if English preview contains the placeholder
const text = $preview_english.text() || "";
- if (text.indexOf('[CITY]') !== -1) $row_city.show();
- if (text.indexOf('[AIRPLANE_NAME]') !== -1) $row_airplane.show();
- if (text.indexOf('[FLIGHT_NUMBER]') !== -1) $row_airplane.show();
- if (text.indexOf('[GATENUMBER]') !== -1) $row_gatenumber.show();
+ if (text.indexOf('[CITY]') !== -1) $row_city.show();
+ if (text.indexOf('[AIRPLANE_NAME]') !== -1) $row_airplane.show();
+ if (text.indexOf('[FLIGHT_NUMBER]') !== -1) $row_airplane.show();
+ if (text.indexOf('[GATENUMBER]') !== -1) $row_gatenumber.show();
if (text.indexOf('[ETAD]') !== -1) $row_time.show();
if (text.indexOf('[REASON]') !== -1) $col_reason.show();
if (text.indexOf('[PLACES]') !== -1) $col_places.show();
@@ -482,76 +486,140 @@ function enable_disable_fields(){
if (text.indexOf('[PLATNOMOR]') !== -1) $col_licenseplate.show();
}
-function update_preview(language){
+function ValidString(str) {
+ return (str != null && typeof str === 'string' && str.trim().length > 0);
+}
+
+function update_preview(language) {
let text = "";
let $preview = null;
if (language === "indonesia") {
$preview = $preview_indonesia;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "indonesia")?.message_details || "";
} else if (language === "english") {
$preview = $preview_english;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "english")?.message_details || "";
} else if (language === "chinese") {
$preview = $preview_chinese;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "chinese")?.message_details || "";
} else if (language === "japanese") {
$preview = $preview_japanese;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "japanese")?.message_details || "";
} else if (language === "arabic") {
$preview = $preview_arabic;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "arabic")?.message_details || "";
} else if (language === "local") {
$preview = $preview_local;
+ text = selected_messages.find(m => (m.language || "").toLowerCase() === "local")?.message_details || "";
}
- if ($preview) {
- text = $preview.text();
- }
+
if (text.indexOf('[CITY]') !== -1) {
let cities = $select_city.val();
- if (Array.isArray(cities)) {
- text = text.replace(/\[CITY\]/g, cities.join(", "));
- } else {
- text = text.replace(/\[CITY\]/g, cities || "");
+ if (Array.isArray(cities) && cities.length > 0) {
+ let citiesNames = [];
+ for (let cityTag of cities) {
+ let ct = window.semiautodata.cities.find(c => c.tag === cityTag);
+ if (ct) {
+ citiesNames.push(ct.value);
+ }
+ }
+ if (citiesNames.length > 0) {
+ text = text.replace(/\[CITY\]/g, citiesNames.join(", "));
+ }
}
}
if (text.indexOf('[AIRPLANE_NAME]') !== -1) {
- let airlineTag = $select_airline.val() || "";
- let airlineObj = window.semiautodata.airlines.find(a => a.tag === airlineTag);
- let airlineName = airlineObj ? airlineObj.value : "";
- text = text.replace(/\[AIRPLANE_NAME\]/g, airlineName);
+ let airlineTag = $select_airline.val();
+ if (ValidString(airlineTag)) {
+ let airlineObj = window.semiautodata.airlines.find(a => a.tag === airlineTag);
+ let airlineName = airlineObj ? airlineObj.value : "";
+ if (airlineName.length > 0) {
+ text = text.replace(/\[AIRPLANE_NAME\]/g, airlineName);
+ }
+ }
}
if (text.indexOf('[FLIGHT_NUMBER]') !== -1) {
- let flightNumber = $input_flightnumber.val() || "";
- text = text.replace(/\[FLIGHT_NUMBER\]/g, flightNumber);
+ let airlineTag = $select_airline.val();
+ let flightNumber = $input_flightnumber.val();
+ if (ValidString(airlineTag) && ValidString(flightNumber)) {
+ text = text.replace(/\[FLIGHT_NUMBER\]/g, `${airlineTag} ${flightNumber}`);
+ }
}
if (text.indexOf('[GATENUMBER]') !== -1) {
- let gateNumber = $input_gatenumber.val() || "";
- text = text.replace(/\[GATENUMBER\]/g, gateNumber);
+ let gateNumber = $input_gatenumber.val();
+ if (ValidString(gateNumber)) {
+ text = text.replace(/\[GATENUMBER\]/g, gateNumber);
+ }
}
if (text.indexOf('[ETAD]') !== -1) {
- let etad = $input_etad.val() || "";
- text = text.replace(/\[ETAD\]/g, etad);
+ let etad = $input_etad.val();
+ if (ValidString(etad)) {
+ text = text.replace(/\[ETAD\]/g, etad);
+ }
}
if (text.indexOf('[REASON]') !== -1) {
- let reason = $select_reason.val() || "";
- text = text.replace(/\[REASON\]/g, reason);
+ let reason = $select_reason.val();
+ if (ValidString(reason)) {
+ let reasonobj = window.semiautodata.reasons.find(r => r.tag === reason);
+ let reasontext = reasonobj ? reasonobj.value : "";
+ if (ValidString(reasontext)) {
+ text = text.replace(/\[REASON\]/g, reasontext);
+ }
+ }
}
if (text.indexOf('[PLACES]') !== -1) {
- let placeTag = $select_places.val() || "";
- text = text.replace(/\[PLACES\]/g, placeTag);
+ let placeTag = $select_places.val();
+ if (ValidString(placeTag)) {
+ let placeobj = window.semiautodata.places.find(p => p.tag === placeTag);
+ let placetext = placeobj ? placeobj.value : "";
+ if (ValidString(placetext)) {
+ text = text.replace(/\[PLACES\]/g, placetext);
+ }
+ }
}
if (text.indexOf('[SHALAT]') !== -1) {
- let shalatTag = $select_shalat.val() || "";
- text = text.replace(/\[SHALAT\]/g, shalatTag);
+ let shalatTag = $select_shalat.val();
+ if (ValidString(shalatTag)) {
+ let shalatobj = window.semiautodata.shalat.find(s => s.tag === shalatTag);
+ let shalattext = shalatobj ? shalatobj.value : "";
+ if (ValidString(shalattext)) {
+ text = text.replace(/\[SHALAT\]/g, shalattext);
+ }
+ }
}
if (text.indexOf('[BCB]') !== -1) {
- let bcbTag = $select_bcb.val() || "";
- text = text.replace(/\[BCB\]/g, bcbTag);
+ let bcbTag = $select_bcb.val();
+ if (ValidString(bcbTag)) {
+ text = text.replace(/\[BCB\]/g, bcbTag);
+ }
}
if (text.indexOf('[PROCEDURE]') !== -1) {
- let procedureTag = $select_procedure.val() || "";
- text = text.replace(/\[PROCEDURE\]/g, procedureTag);
+ let procedureTag = $select_procedure.val();
+ if (ValidString(procedureTag)) {
+ let procedureobj = window.semiautodata.procedures.find(p => p.tag === procedureTag);
+ let proceduretext = procedureobj ? procedureobj.value : "";
+ if (ValidString(proceduretext)) {
+ text = text.replace(/\[PROCEDURE\]/g, proceduretext);
+ }
+ }
}
if (text.indexOf('[PLATNOMOR]') !== -1) {
- let platNomorTag = $select_licenseplate.val() || "";
- text = text.replace(/\[PLATNOMOR\]/g, platNomorTag);
+ let platNomorTag = $select_licenseplate.val();
+ if (ValidString(platNomorTag)) {
+ text = text.replace(/\[PLATNOMOR\]/g, platNomorTag);
+ }
+ }
+ if (text.indexOf('[SEQUENCE]') !== -1) {
+ let sequenceTag = $select_sequence.val();
+ if (ValidString(sequenceTag)) {
+ let sequenceobj = window.semiautodata.sequences.find(s => s.tag === sequenceTag);
+ let sequencetext = sequenceobj ? sequenceobj.value : "";
+ if (ValidString(sequencetext)) {
+ text = text.replace(/\[SEQUENCE\]/g, sequencetext);
+ }
+ }
}
if ($preview) {
@@ -559,13 +627,45 @@ function update_preview(language){
}
}
-function update_all_previews(){
+function isComplete(chkbox, preview) {
+ if (chkbox.is(":checked")) {
+ const text = preview.text();
+ if (text.indexOf('[') !== -1) {
+ return false;
+ }
+ if (text.indexOf(']') !== -1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function check_complete_message() {
+ let complete = true;
+ if (!isComplete($enable_indonesia, $preview_indonesia)) complete = false;
+ if (!isComplete($enable_english, $preview_english)) complete = false;
+ if (!isComplete($enable_chinese, $preview_chinese)) complete = false;
+ if (!isComplete($enable_japanese, $preview_japanese)) complete = false;
+ if (!isComplete($enable_arabic, $preview_arabic)) complete = false;
+ if (!isComplete($enable_local, $preview_local)) complete = false;
+
+ if (complete) {
+ $("#send_broadcast").removeClass("btn-disable").addClass("btn-enable");
+ } else {
+ $("#send_broadcast").removeClass("btn-enable").addClass("btn-disable");
+ }
+
+}
+
+function update_all_previews() {
update_preview("indonesia");
update_preview("english");
update_preview("chinese");
update_preview("japanese");
update_preview("arabic");
update_preview("local");
+
+ check_complete_message();
}
function fill_items() {
@@ -584,8 +684,7 @@ function fill_items() {
$input_flightnumber.empty();
$input_licenseplate.empty();
$input_conveyorbelt.empty();
- $input_hours.empty();
- $input_minutes.empty();
+ $input_etad.empty();
empty_preview();
@@ -623,17 +722,17 @@ function fill_items() {
$enable_indonesia.prop("checked", !!checked);
return true;
}
- if (l.startsWith("english") ) {
+ if (l.startsWith("english")) {
if (checked) $preview_english.text(value); else $preview_english.empty();
$enable_english.prop("checked", !!checked);
return true;
}
- if (l.startsWith("chinese") ) {
+ if (l.startsWith("chinese")) {
if (checked) $preview_chinese.text(value); else $preview_chinese.empty();
$enable_chinese.prop("checked", !!checked);
return true;
}
- if (l.startsWith("japanese") ) {
+ if (l.startsWith("japanese")) {
if (checked) $preview_japanese.text(value); else $preview_japanese.empty();
$enable_japanese.prop("checked", !!checked);
return true;
@@ -660,6 +759,7 @@ function fill_items() {
}
}
+ selected_messages = [];
if (willSelect) {
// unselect other selected rows and clear their previews
$tbody_message.find("tr.table-active").each(function () {
@@ -676,8 +776,10 @@ function fill_items() {
const sameMsgs = window.semiautodata.messages.filter(m => Number(m.id) === Number(groupId));
for (let m of sameMsgs) {
applyLang(m.language, m.message_details, true);
+ selected_messages.push(m);
}
enable_disable_fields();
+ update_all_previews();
}
} else {
// deselect this row and clear its previews
@@ -711,7 +813,7 @@ function fill_items() {
multiple: false
});
$select_airline.val(null).trigger("change");
-
+
$select_airline.on('change', function () {
update_all_previews();
});
@@ -742,21 +844,21 @@ function fill_items() {
placeholder: "Select a place",
width: '100%',
multiple: false,
- data: window.semiautodata.places.map(p => ({ id: p.tag, text: p.value }))
+ data: window.semiautodata.places.map(p => ({ id: p.tag, text: p.value + " [" + p.tag + "]" }))
});
-
+
$select_places.val(null).trigger("change");
$select_places.on('change', function () {
update_all_previews();
});
}
if (window.semiautodata.shalat !== null && Array.isArray(window.semiautodata.shalat) && window.semiautodata.shalat.length > 0) {
-
+
$select_shalat.select2({
placeholder: "Select shalat time",
width: '100%',
multiple: false,
- data: window.semiautodata.shalat.map(s => ({ id: s.tag, text: s.value }))
+ data: window.semiautodata.shalat.map(s => ({ id: s.tag, text: s.value + " [" + s.tag + "]" }))
});
$select_shalat.val(null).trigger("change");
}
@@ -765,20 +867,20 @@ function fill_items() {
placeholder: "Select a reason",
width: '100%',
multiple: false,
- data: window.semiautodata.reasons.map(r => ({ id: r.tag, text: r.value }))
+ data: window.semiautodata.reasons.map(r => ({ id: r.tag, text: r.value + " [" + r.tag + "]" }))
});
$select_reason.val(null).trigger("change");
$select_reason.on('change', function () {
update_all_previews();
});
}
-
+
if (window.semiautodata.compensation !== null && Array.isArray(window.semiautodata.compensation) && window.semiautodata.compensation.length > 0) {
$select_compensation.select2({
placeholder: "Select compensation",
width: '100%',
multiple: false,
- data: window.semiautodata.compensation.map(c => ({ id: c.tag, text: c.value }))
+ data: window.semiautodata.compensation.map(c => ({ id: c.tag, text: c.value + " [" + c.tag + "]" }))
});
$select_compensation.val(null).trigger("change");
$select_compensation.on('change', function () {
@@ -790,7 +892,7 @@ function fill_items() {
placeholder: "Select procedure",
width: '100%',
multiple: false,
- data: window.semiautodata.procedures.map(p => ({ id: p.tag, text: p.value }))
+ data: window.semiautodata.procedures.map(p => ({ id: p.tag, text: p.value + " [" + p.tag + "]" }))
});
$select_procedure.val(null).trigger("change");
$select_procedure.on('change', function () {
@@ -813,11 +915,112 @@ function fill_items() {
$input_licenseplate.on('input', function () {
update_all_previews();
});
-
+
+ $input_etad.on('input', function () {
+ update_all_previews();
+ });
+
+
+
}
}
+function send_broadcast_message() {
+ console.log("send_broadcast_message");
+ if ($("#send_broadcast").hasClass("btn-disable")) {
+ alert("Cannot send broadcast message. Please complete all required fields.");
+ return;
+ }
+ // get all checked broadcast zones from tbody_broadcastzones
+ let selected_zones = [];
+ $tbody_broadcastzones.find("input[type='checkbox']").each(function () {
+ const $checkbox = $(this);
+ if ($checkbox.is(":checked")) {
+ selected_zones.push($checkbox.val());
+ }
+ });
+
+ if (selected_zones.length === 0) {
+ alert("Please select at least one broadcast zone.");
+ return;
+ }
+
+ let languages = [];
+ if ($enable_indonesia.is(":checked")) languages.push("INDONESIA");
+ if ($enable_english.is(":checked")) languages.push("ENGLISH");
+ if ($enable_chinese.is(":checked")) languages.push("CHINESE");
+ if ($enable_japanese.is(":checked")) languages.push("JAPANESE");
+ if ($enable_arabic.is(":checked")) languages.push("ARABIC");
+ if ($enable_local.is(":checked")) languages.push("LOCAL");
+
+ if (languages.length === 0) {
+ alert("Please select at least one language to send.");
+ return;
+ }
+
+ let description = selected_messages.length > 0 ? selected_messages[0].description : "";
+ if (!ValidString(description)) {
+ alert("Message not selected");
+ return;
+ }
+
+ let tags = [];
+ let msg = selected_messages[0];
+ tags.push("ANN_ID:"+msg.id);
+ if (msg.message_details.indexOf('[CITY]') !== -1) {
+ let cities = $select_city.val();
+ if (Array.isArray(cities) && cities.length > 0) {
+ tags.push("CITY:" + cities.join(";"));
+ }
+ }
+ if (msg.message_details.indexOf('[AIRPLANE_NAME]') !== -1) {
+ tags.push("AL:" + $select_airline.val());
+ }
+ if (msg.message_details.indexOf('[FLIGHT_NUMBER]') !== -1) {
+ tags.push("FLNUM:" + $input_flightnumber.val());
+ }
+ if (msg.message_details.indexOf('[PLATNOMOR]') !== -1) {
+ tags.push("PLATNOMOR:" + $input_licenseplate.val());
+ }
+ if (msg.message_details.indexOf('[PLACES]') !== -1) {
+ tags.push("PLACES:" + $select_places.val());
+ }
+ if (msg.message_details.indexOf('[ETAD]') !== -1) {
+ tags.push("ETAD:" + $input_etad.val());
+ }
+ if (msg.message_details.indexOf('[SHALAT]') !== -1) {
+ tags.push("SHALAT:" + $select_shalat.val());
+ }
+ if (msg.message_details.indexOf('[BCB]') !== -1) {
+ tags.push("BCB:" + $input_conveyorbelt.val());
+ }
+ if (msg.message_details.indexOf('[GATENUMBER]') !== -1) {
+ tags.push("GATECODE:" + $input_gatenumber.val());
+ }
+ if (msg.message_details.indexOf('[REASON]') !== -1) {
+ tags.push("REASON:" + $select_reason.val());
+ }
+ if (msg.message_details.indexOf('[PROCEDURE]') !== -1) {
+ tags.push("PROCEDURE:" + $select_procedure.val());
+ }
+
+ let payload = {
+ description: description,
+ languages: languages.join(";"),
+ tags: tags.join(" "),
+ broadcastzones: selected_zones.join(";")
+ }
+ console.log("Payload:", payload);
+ fetchAPI("SemiAuto", "POST", {}, payload, (data) => {
+ alert("Broadcast message sent successfully.");
+ }, (error) => {
+ console.error("Error:", error);
+ alert("Error sending broadcast message: " + error);
+ });
+
+}
+
// App start here
$(document).ready(function () {
console.log("javascript loaded");
@@ -846,8 +1049,7 @@ $(document).ready(function () {
$input_flightnumber = $("#input_flightnumber");
$input_licenseplate = $("#input_licenseplate");
$input_conveyorbelt = $("#input_conveyorbelt");
- $input_hours = $("#input_hours");
- $input_minutes = $("#input_minutes");
+ $input_etad = $("#input_etad");
$row_airplane = $("#row_airplane");
$row_city = $("#row_city");
$row_gatenumber = $("#row_gatenumber");
@@ -864,5 +1066,9 @@ $(document).ready(function () {
reload_database();
});
+ $("#send_broadcast").off("click").on("click", function () {
+ send_broadcast_message();
+ });
+
});
\ No newline at end of file
diff --git a/html/semiauto/index.html b/html/semiauto/index.html
index 559d47d..9fc0744 100644
--- a/html/semiauto/index.html
+++ b/html/semiauto/index.html
@@ -110,7 +110,7 @@
@@ -123,18 +123,8 @@
-
-
+
diff --git a/libs/linux-aarch64/libbassenc_mp3.so b/libs/linux-aarch64/libbassenc_mp3.so
new file mode 100644
index 0000000..1611982
Binary files /dev/null and b/libs/linux-aarch64/libbassenc_mp3.so differ
diff --git a/libs/linux-aarch64/libbassenc_ogg.so b/libs/linux-aarch64/libbassenc_ogg.so
new file mode 100644
index 0000000..f358cc7
Binary files /dev/null and b/libs/linux-aarch64/libbassenc_ogg.so differ
diff --git a/libs/linux-aarch64/libbassenc_opus.so b/libs/linux-aarch64/libbassenc_opus.so
new file mode 100644
index 0000000..9802f2e
Binary files /dev/null and b/libs/linux-aarch64/libbassenc_opus.so differ
diff --git a/libs/linux-armhf/libbassenc_mp3.so b/libs/linux-armhf/libbassenc_mp3.so
new file mode 100644
index 0000000..218240d
Binary files /dev/null and b/libs/linux-armhf/libbassenc_mp3.so differ
diff --git a/libs/linux-armhf/libbassenc_ogg.so b/libs/linux-armhf/libbassenc_ogg.so
new file mode 100644
index 0000000..70a908c
Binary files /dev/null and b/libs/linux-armhf/libbassenc_ogg.so differ
diff --git a/libs/linux-armhf/libbassenc_opus.so b/libs/linux-armhf/libbassenc_opus.so
new file mode 100644
index 0000000..ae5dc15
Binary files /dev/null and b/libs/linux-armhf/libbassenc_opus.so differ
diff --git a/libs/linux-x86-64/libbassenc_mp3.so b/libs/linux-x86-64/libbassenc_mp3.so
new file mode 100644
index 0000000..33f1248
Binary files /dev/null and b/libs/linux-x86-64/libbassenc_mp3.so differ
diff --git a/libs/linux-x86-64/libbassenc_ogg.so b/libs/linux-x86-64/libbassenc_ogg.so
new file mode 100644
index 0000000..5a72d40
Binary files /dev/null and b/libs/linux-x86-64/libbassenc_ogg.so differ
diff --git a/libs/linux-x86-64/libbassenc_opus.so b/libs/linux-x86-64/libbassenc_opus.so
new file mode 100644
index 0000000..3a6bdd7
Binary files /dev/null and b/libs/linux-x86-64/libbassenc_opus.so differ
diff --git a/libs/linux-x86/libbassenc_mp3.so b/libs/linux-x86/libbassenc_mp3.so
new file mode 100644
index 0000000..058e6f6
Binary files /dev/null and b/libs/linux-x86/libbassenc_mp3.so differ
diff --git a/libs/linux-x86/libbassenc_ogg.so b/libs/linux-x86/libbassenc_ogg.so
new file mode 100644
index 0000000..fedf6c5
Binary files /dev/null and b/libs/linux-x86/libbassenc_ogg.so differ
diff --git a/libs/linux-x86/libbassenc_opus.so b/libs/linux-x86/libbassenc_opus.so
new file mode 100644
index 0000000..124a1a2
Binary files /dev/null and b/libs/linux-x86/libbassenc_opus.so differ
diff --git a/libs/win32-arm64/bass.dll b/libs/win32-arm64/bass.dll
deleted file mode 100644
index 7df046a..0000000
Binary files a/libs/win32-arm64/bass.dll and /dev/null differ
diff --git a/libs/win32-arm64/bassenc.dll b/libs/win32-arm64/bassenc.dll
deleted file mode 100644
index 74be787..0000000
Binary files a/libs/win32-arm64/bassenc.dll and /dev/null differ
diff --git a/libs/win32-x86-64/bassenc_mp3.dll b/libs/win32-x86-64/bassenc_mp3.dll
new file mode 100644
index 0000000..930d490
Binary files /dev/null and b/libs/win32-x86-64/bassenc_mp3.dll differ
diff --git a/libs/win32-x86-64/bassenc_ogg.dll b/libs/win32-x86-64/bassenc_ogg.dll
new file mode 100644
index 0000000..babee47
Binary files /dev/null and b/libs/win32-x86-64/bassenc_ogg.dll differ
diff --git a/libs/win32-x86-64/bassenc_opus.dll b/libs/win32-x86-64/bassenc_opus.dll
new file mode 100644
index 0000000..8fc0301
Binary files /dev/null and b/libs/win32-x86-64/bassenc_opus.dll differ
diff --git a/libs/win32-x86/bassenc_mp3.dll b/libs/win32-x86/bassenc_mp3.dll
new file mode 100644
index 0000000..1980b7d
Binary files /dev/null and b/libs/win32-x86/bassenc_mp3.dll differ
diff --git a/libs/win32-x86/bassenc_ogg.dll b/libs/win32-x86/bassenc_ogg.dll
new file mode 100644
index 0000000..0628192
Binary files /dev/null and b/libs/win32-x86/bassenc_ogg.dll differ
diff --git a/libs/win32-x86/bassenc_opus.dll b/libs/win32-x86/bassenc_opus.dll
new file mode 100644
index 0000000..327c299
Binary files /dev/null and b/libs/win32-x86/bassenc_opus.dll differ
diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt
index 4f5ebd9..d95bf23 100644
--- a/src/MainExtension01.kt
+++ b/src/MainExtension01.kt
@@ -401,7 +401,7 @@ class MainExtension01 {
"[ETAD]" -> {
val values = variables["ETAD"].orEmpty().split(":").map { it.trim() }.filter { IsNumber(it) }
- println("ETAD values: $values")
+ //println("ETAD values: $values")
if (values.size == 2) {
if (IsNumber(values[0]) && IsNumber(values[1])) {
val _h = values[0].toInt()
diff --git a/src/audio/AudioPlayer.kt b/src/audio/AudioPlayer.kt
index 730cb62..17ee231 100644
--- a/src/audio/AudioPlayer.kt
+++ b/src/audio/AudioPlayer.kt
@@ -16,9 +16,12 @@ import contentCache
import org.tinylog.Logger
@Suppress("unused")
-class AudioPlayer (var samplingrate: Int) {
+class AudioPlayer (var samplingrate: Int = 44100) {
val bass: Bass = Bass.Instance
val bassenc : BassEnc = BassEnc.Instance
+ val bassencmp3: BassEncMP3 = BassEncMP3.Instance
+ val bassencopus: BassEncOpus = BassEncOpus.Instance
+ val bassencogg : BassEncOGG = BassEncOGG.Instance
var initedDevice = -1
@@ -28,6 +31,9 @@ class AudioPlayer (var samplingrate: Int) {
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
+ Logger.info { "BassEncMP3 version ${Integer.toHexString(bassencmp3.BASS_Encode_MP3_GetVersion())}" }
+ Logger.info { "BassEncOpus version ${Integer.toHexString(bassencopus.BASS_Encode_OPUS_GetVersion())}" }
+ Logger.info {" BassEncOGG version ${Integer.toHexString(bassencogg.BASS_Encode_OGG_GetVersion())}"}
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
}
diff --git a/src/audio/BassEnc.java b/src/audio/BassEnc.java
index 63c9038..266c9b4 100644
--- a/src/audio/BassEnc.java
+++ b/src/audio/BassEnc.java
@@ -88,7 +88,7 @@ public interface BassEnc extends Library {
* @param length number of bytes
* @param user the user pointer passed to BASS_Encode_Start
*/
- void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user);
+ void ENCODEPROC(int encoderhandle, int channelhandle, Pointer encodedData, int length, Pointer user);
}
interface ENCODEPROCEX extends Callback {
@@ -101,7 +101,7 @@ public interface BassEnc extends Library {
* @param offset file offset of the data
* @param user the user pointer passed to BASS_Encode_Start
*/
- void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user);
+ void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
}
interface ENCODERPROC extends Callback {
@@ -115,7 +115,7 @@ public interface BassEnc extends Library {
* @param user the user pointer passed to BASS_Encode_Start
* @return the amount of encoded data (-1 = stop)
*/
- int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user);
+ int ENCODERPROC(int encoderHandle, int channelHandle, Pointer encodedData, int length, int maxOut, Pointer user);
}
diff --git a/src/audio/BassEncMP3.java b/src/audio/BassEncMP3.java
new file mode 100644
index 0000000..ab561af
--- /dev/null
+++ b/src/audio/BassEncMP3.java
@@ -0,0 +1,13 @@
+package audio;
+
+import com.sun.jna.Library;
+
+@SuppressWarnings("unused")
+public interface BassEncMP3 extends Library {
+
+ BassEncMP3 Instance = (BassEncMP3) com.sun.jna.Native.load("bassenc_mp3", BassEncMP3.class);
+ int BASS_Encode_MP3_GetVersion();
+ int BASS_Encode_MP3_Start(int handle, String options, int flags, BassEnc.ENCODEPROCEX proc, Object user);
+ int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
+
+}
diff --git a/src/audio/BassEncOGG.java b/src/audio/BassEncOGG.java
new file mode 100644
index 0000000..194ccfc
--- /dev/null
+++ b/src/audio/BassEncOGG.java
@@ -0,0 +1,15 @@
+package audio;
+
+import com.sun.jna.Library;
+
+@SuppressWarnings("unused")
+public interface BassEncOGG extends Library {
+
+ BassEncOGG Instance = (BassEncOGG) com.sun.jna.Native.load("bassenc_ogg", BassEncOGG.class);
+ int BASS_ENCODE_OGG_RESET = 0x1000000;
+ int BASS_Encode_OGG_GetVersion();
+ int BASS_Encode_OGG_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
+ int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
+ boolean BASS_Encode_OGG_NewStream(int handle, String options, int flags);
+
+}
diff --git a/src/audio/BassEncOpus.java b/src/audio/BassEncOpus.java
new file mode 100644
index 0000000..75686a0
--- /dev/null
+++ b/src/audio/BassEncOpus.java
@@ -0,0 +1,18 @@
+package audio;
+
+import com.sun.jna.Library;
+
+@SuppressWarnings("unused")
+public interface BassEncOpus extends Library {
+ BassEncOpus Instance = (BassEncOpus) com.sun.jna.Native.load("bassenc_opus", BassEncOpus.class);
+ int BASS_ENCODE_OPUS_RESET = 0x1000000;
+ int BASS_ENCODE_OPUS_CTLONLY = 0x2000000;
+
+
+ int BASS_Encode_OPUS_GetVersion();
+ int BASS_Encode_OPUS_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
+ int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
+ boolean BASS_Encode_OPUS_NewStream(int handle, String options, int flags);
+
+
+}
diff --git a/src/audio/Mp3Encoder.kt b/src/audio/Mp3Encoder.kt
new file mode 100644
index 0000000..149f9fb
--- /dev/null
+++ b/src/audio/Mp3Encoder.kt
@@ -0,0 +1,126 @@
+package audio
+
+import audioPlayer
+import com.sun.jna.Memory
+import com.sun.jna.Pointer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+import org.tinylog.Logger
+import java.util.function.Consumer
+
+@Suppress("unused")
+class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
+ var push_handle: Int = 0
+ var mp3_handle: Int = 0
+ private val bass = audioPlayer.bass
+ private val bassencmp3 = audioPlayer.bassencmp3
+ var callback : Consumer
? = null
+
+ /**
+ * Check if the encoder is started
+ * @return true if started, false otherwise
+ */
+ fun isStarted() : Boolean {
+ return push_handle!=0 && mp3_handle!=0
+ }
+
+ val proc = BassEnc.ENCODEPROCEX{
+ enchandle, sourcehandle, buffer, length, offset, user ->
+ if (enchandle == mp3_handle) {
+ if (sourcehandle == push_handle) {
+ val data = ByteArray(length)
+ buffer.read(0, data, 0, length)
+ callback?.accept(data)
+ } else Logger.error { "MP3 Encoder callback called with unknown source handle: $sourcehandle" }
+ } else Logger.error { "MP3 Encoder callback called with unknown encoder handle: $enchandle" }
+ }
+
+ /**
+ * Start the MP3 encoder
+ * @param cb Function to receive encoded MP3 data
+ */
+ fun Start(cb: Consumer){
+ callback = null
+ push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
+ if (push_handle!=0){
+ Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
+
+ mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE,proc, null)
+
+ if (mp3_handle!=0){
+ callback = cb
+
+ CoroutineScope(Dispatchers.Default).launch {
+ val readsize = 4*1024 // 4 K buffer
+ Logger.info{"MP3 Encoder started successfully." }
+ while(isActive && push_handle!=0){
+ val p = Memory(readsize.toLong())
+ val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
+
+ if (read==-1){
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to read data from MP3 Encoder stream. BASS error code: $err" }
+ break
+ }
+ delay(2)
+ }
+ Logger.info{"MP3 Encoder finished successfully." }
+ }
+
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to start MP3 Encoder. BASS error code: $err"}
+ }
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to initialize MP3 Encoder. BASS error code: $err" }
+ }
+ }
+
+ /**
+ * Push PCM data to be encoded
+ * @param data PCM data in ByteArray
+ * @return Number of bytes written, or 0 if failed
+ */
+ fun PushData(data: ByteArray): Int {
+ if (push_handle==0){
+ Logger.error{"MP3 Encoder is not started." }
+ return 0
+ }
+ val mem = Memory(data.size.toLong())
+ mem.write(0, data, 0, data.size)
+ val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
+ if (written==-1){
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
+ return 0
+ }
+ //println("MP3 Encoder: Pushed $written bytes of PCM data.")
+ return written
+ }
+
+ /**
+ * Stop the MP3 encoder and free resources
+ */
+ fun Stop(){
+
+ if (push_handle!=0){
+ val res = bass.BASS_StreamFree(push_handle)
+ if (res){
+ Logger.info{"MP3 Encoder stream freed successfully." }
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to free MP3 Encoder stream. BASS error code: $err"}
+ }
+ push_handle = 0
+ }
+ // auto close by BASS_ENCODE_AUTOFREE
+ mp3_handle = 0
+ callback = null
+ println("MP3 Encoder: Stopped.")
+ }
+}
\ No newline at end of file
diff --git a/src/audio/OpusEncoder.kt b/src/audio/OpusEncoder.kt
new file mode 100644
index 0000000..41bb0bd
--- /dev/null
+++ b/src/audio/OpusEncoder.kt
@@ -0,0 +1,127 @@
+package audio
+
+import audioPlayer
+import com.sun.jna.Memory
+import com.sun.jna.Pointer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+import org.tinylog.Logger
+import java.util.function.Consumer
+
+@Suppress("unused")
+class OpusEncoder(val samplingrate: Int=44100, val channels: Int=1) {
+ var push_handle: Int = 0
+ var opus_handle: Int = 0
+ private val bass = audioPlayer.bass
+ private val bassencopus = audioPlayer.bassencopus
+ var callback : Consumer? = null
+
+ /**
+ * Check if the encoder is started
+ * @return true if started, false otherwise
+ */
+ fun isStarted() : Boolean {
+ return push_handle!=0 && opus_handle!=0
+ }
+
+ val proc = BassEnc.ENCODEPROC{
+ enchandle, sourcehandle, buffer, length, user ->
+ if (enchandle == opus_handle) {
+ if (sourcehandle == push_handle) {
+ val data = ByteArray(length)
+ buffer.read(0, data, 0, length)
+ callback?.accept(data)
+ //println("MP3 Encoder callback: Sent $length bytes")
+ } else Logger.error { "Opus Encoder callback called with unknown source handle: $sourcehandle" }
+ } else Logger.error { "Opus Encoder callback called with unknown encoder handle: $enchandle" }
+ }
+
+ /**
+ * Start the Opus encoder
+ * @param cb Function to receive encoded MP3 data
+ */
+ fun Start(cb: Consumer){
+ callback = null
+ push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
+ if (push_handle!=0){
+ Logger.info{"Opus Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
+
+ opus_handle = bassencopus.BASS_Encode_OPUS_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE, proc, null)
+
+ if (opus_handle!=0){
+ callback = cb
+
+ CoroutineScope(Dispatchers.Default).launch {
+ val readsize = 8 * 1024 * 1024 // 8 MB buffer
+ Logger.info{"Opus Encoder started successfully." }
+ while(isActive && push_handle!=0){
+ delay(2)
+ val p = Memory(readsize.toLong())
+ val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
+
+ if (read==-1){
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to read data from Opus Encoder stream. BASS error code: $err" }
+ break
+ }
+ }
+ Logger.info{"Opus Encoder finished successfully." }
+ }
+
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to start Opus Encoder. BASS error code: $err"}
+ }
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to initialize Opus Encoder. BASS error code: $err" }
+ }
+ }
+
+ /**
+ * Push PCM data to be encoded
+ * @param data PCM data in ByteArray
+ * @return Number of bytes written, or 0 if failed
+ */
+ fun PushData(data: ByteArray): Int {
+ if (push_handle==0){
+ Logger.error{"Opus Encoder is not started." }
+ return 0
+ }
+ val mem = Memory(data.size.toLong())
+ mem.write(0, data, 0, data.size)
+ val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
+ if (written==-1){
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to push data to Opus Encoder. BASS error code: $err" }
+ return 0
+ }
+ //println("Opus Encoder: Pushed $written bytes of PCM data.")
+ return written
+ }
+
+ /**
+ * Stop the Opus encoder and free resources
+ */
+ fun Stop(){
+
+ if (push_handle!=0){
+ val res = bass.BASS_StreamFree(push_handle)
+ if (res){
+ Logger.info{"Opus Encoder stream freed successfully." }
+ } else {
+ val err = bass.BASS_ErrorGetCode()
+ Logger.error{"Failed to free Opus Encoder stream. BASS error code: $err"}
+ }
+ push_handle = 0
+ }
+ // auto close by BASS_ENCODE_AUTOFREE
+ opus_handle = 0
+ callback = null
+ println("Opus Encoder: Stopped.")
+ }
+}
\ No newline at end of file
diff --git a/src/barix/BarixConnection.kt b/src/barix/BarixConnection.kt
index f791016..3ddf2c4 100644
--- a/src/barix/BarixConnection.kt
+++ b/src/barix/BarixConnection.kt
@@ -1,5 +1,7 @@
package barix
+import audio.Mp3Encoder
+import audio.OpusEncoder
import codes.Somecodes
import com.fasterxml.jackson.databind.JsonNode
import kotlinx.coroutines.CoroutineScope
@@ -24,37 +26,37 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
private val inet = InetSocketAddress(ipaddress, port)
private val maxUDPsize = 1000
private var _tcp: Socket? = null
- private val PipeOuts = mutableMapOf()
+ private val pipeOuts = mutableMapOf()
+ private val mp3encoder = Mp3Encoder()
fun AddPipeOut(key: String, pipeOut: PipedOutputStream) {
RemovePipeOut(key)
- PipeOuts[key] = pipeOut
+ pipeOuts[key] = pipeOut
println("Added pipeOut $key to BarixConnection $channel ($ipaddress)")
}
fun RemovePipeOut(key: String) {
- println("Removing pipeOut $key")
- if (PipeOuts.contains(key)){
- val pipe = PipeOuts[key]
+ if (pipeOuts.contains(key)){
+ val pipe = pipeOuts[key]
try {
pipe?.close()
} catch (e: Exception) {
// ignore
}
println("Removed pipeOut $key from BarixConnection $channel ($ipaddress)")
- PipeOuts.remove(key)
+ pipeOuts.remove(key)
}
}
fun ClearPipeOuts() {
- PipeOuts.values.forEach { piped ->
+ pipeOuts.values.forEach { piped ->
try {
piped.close()
} catch (e: Exception) {
// ignore
}
}
- PipeOuts.clear()
+ pipeOuts.clear()
}
/**
* Buffer remain in bytes
@@ -137,34 +139,40 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
CoroutineScope(Dispatchers.IO).launch {
DatagramSocket().use{ udp ->
val bb = ByteBuffer.wrap(data)
+ if (!mp3encoder.isStarted()) {
+ mp3encoder.Start { data ->
+ pipeOuts.keys.forEach { kk ->
+ val pp = pipeOuts[kk]
+ try {
+ pp?.write(data)
+ pp?.flush()
+ println("Written ${data.size} bytes to pipeOut $kk")
+ } catch (e: Exception) {
+ Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
+ pp?.close()
+ pipeOuts.remove(kk)
+ }
+ }
+ }
+ }
while(bb.hasRemaining()){
try {
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
bb.get(chunk)
- //println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
while(bufferRemain
- val pp = PipeOuts[kk]
- try {
- pp?.write(chunk)
- pp?.flush()
- println("Written ${chunk.size} bytes to pipeOut $kk")
- } catch (e: Exception) {
- Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
- pp?.close()
- PipeOuts.remove(kk)
- }
- }
+
} catch (e: Exception) {
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
return@launch
}
}
+ mp3encoder.Stop()
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
}
}
diff --git a/src/web/WebApp.kt b/src/web/WebApp.kt
index 163acef..cbf7124 100644
--- a/src/web/WebApp.kt
+++ b/src/web/WebApp.kt
@@ -49,13 +49,14 @@ import java.io.File
import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.nio.file.Path
+import java.util.UUID
@Suppress("unused")
class WebApp(val listenPort: Int, val userlist: List>, val _config: configFile) {
lateinit var app: Javalin
- lateinit var semiauto : Javalin
+ lateinit var semiauto: Javalin
val objectmapper = jacksonObjectMapper()
private fun SendReply(context: WsMessageContext, command: String, value: String) {
@@ -73,7 +74,7 @@ class WebApp(val listenPort: Int, val userlist: List>, val
}
- private fun Start_WebServer(){
+ private fun Start_WebServer() {
Logger.info { "Starting Web Application on port $listenPort" }
app = Javalin.create { config ->
config.useVirtualThreads = true
@@ -253,15 +254,26 @@ class WebApp(val listenPort: Int, val userlist: List>, val
if (param.isNotEmpty()) {
val bc = Get_Barix_Connection_by_ZoneName(param)
if (bc != null) {
- val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
+
+
+ val key = ctx.cookie("client-id") ?: UUID.randomUUID().toString()
+ .also { ctx.cookie("client-id", it) }
val pipeIN = PipedInputStream(8192)
val pipeOUT = PipedOutputStream(pipeIN)
// write WAV Header 44 bytes contains samplingrate 44100 Hz, 16 bit, mono
- pipeOUT.write(Generate_WAV_Header())
+// pipeOUT.write(Generate_WAV_Header())
bc.AddPipeOut(key, pipeOUT)
- ctx.contentType("audio/wav")
- ctx.header("Cache-Control", "no-cache")
+ ctx.contentType("audio/mpeg")
+ ctx.header("Cache-Control", "no-cache, no-store, must-revalidate")
+ ctx.header("Pragma", "no-cache")
+ ctx.header("Expires", "0")
+
+ // 🔥 This one is critical — tells Jetty to send data in HTTP chunks as it becomes available:
+ ctx.header("Transfer-Encoding", "chunked")
+
+ // Keeps the TCP socket open so the browser doesn’t close after initial data
ctx.header("Connection", "keep-alive")
+
ctx.result(pipeIN)
println("LiveAudio Open for zone $param SUCCESS")
} else ctx.status(400)
@@ -273,17 +285,23 @@ class WebApp(val listenPort: Int, val userlist: List>, val
get("Close/{broadcastzone}") { ctx ->
val param = ctx.pathParam("broadcastzone")
println("LiveAudio Close for zone $param")
- if (param.isNotEmpty()) {
- val bc = Get_Barix_Connection_by_ZoneName(param)
- if (bc != null) {
- val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
- bc.RemovePipeOut(key)
- ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
- println("LiveAudio Close for zone $param SUCCESS")
+ val key = ctx.cookie("client-id")
+ if (key != null && key.isNotEmpty()) {
+ if (param.isNotEmpty()) {
+ val bc = Get_Barix_Connection_by_ZoneName(param)
+ if (bc != null) {
+
+ bc.RemovePipeOut(key)
+ ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
+ println("LiveAudio Close for zone $param SUCCESS")
+ } else ctx.status(400)
+ .result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
} else ctx.status(400)
- .result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
- } else ctx.status(400)
- .result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
+ .result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
+ } else {
+ ctx.status(400)
+ .result(objectmapper.writeValueAsString(resultMessage("No client-id cookie found")))
+ }
}
}
path("VoiceType") {
@@ -2043,55 +2061,55 @@ class WebApp(val listenPort: Int, val userlist: List>, val
}.start(listenPort)
}
- private fun Start_SemiAutoServer(){
- Logger.info {"Starting SemiAuto web server at port ${listenPort+1}"}
- semiauto = Javalin.create{ config ->
+ private fun Start_SemiAutoServer() {
+ Logger.info { "Starting SemiAuto web server at port ${listenPort + 1}" }
+ semiauto = Javalin.create { config ->
config.useVirtualThreads = true
- config.staticFiles.add ("/semiauto")
+ config.staticFiles.add("/semiauto")
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
config.router.apiBuilder {
- path("/"){
+ path("/") {
get {
- it.cookie("semiauto-user","")
+ it.cookie("semiauto-user", "")
it.redirect("login.html")
}
}
- path("logout"){
+ path("logout") {
get {
- it.cookie("semiauto-user","")
+ it.cookie("semiauto-user", "")
it.redirect("login.html")
}
}
- path("login.html"){
+ path("login.html") {
post {
val formuser = it.formParam("username") ?: ""
val formpass = it.formParam("password") ?: ""
- if (formuser.isNotEmpty() && formpass.isNotEmpty()){
- val user = db.userDB.List.find { u -> u.username==formuser && u.password==formpass }
- if (user!=null){
- it.cookie("semiauto-user",user.username)
+ if (formuser.isNotEmpty() && formpass.isNotEmpty()) {
+ val user = db.userDB.List.find { u -> u.username == formuser && u.password == formpass }
+ if (user != null) {
+ it.cookie("semiauto-user", user.username)
it.redirect("index.html")
} else ResultMessageString(it, 400, "Invalid username or password")
} else ResultMessageString(it, 400, "Username or password cannot be empty")
}
}
- path("index.html"){
+ path("index.html") {
before { CheckSemiAutoUsers(it) }
}
- path("log.html"){
+ path("log.html") {
before { CheckSemiAutoUsers(it) }
}
- path("api"){
- path("Initialize"){
+ path("api") {
+ path("Initialize") {
get { ctx ->
val username = ctx.cookie("semiauto-user") ?: ""
- if (username.isNotEmpty()){
- val user = db.userDB.List.find { u -> u.username==username }
- if (user!=null){
+ if (username.isNotEmpty()) {
+ val user = db.userDB.List.find { u -> u.username == username }
+ if (user != null) {
val result = SemiAutoInitData(username)
// messages
String_To_List(user.messagebank_ann_id).forEach { msg ->
- db.messageDB.List.filter{it.ANN_ID==msg.toUInt()}.forEach {xx ->
+ db.messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx ->
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
}
}
@@ -2100,47 +2118,55 @@ class WebApp(val listenPort: Int, val userlist: List>, val
// cities
String_To_List(user.city_tags).forEach { ct ->
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
- ?.let{
- result.cities.add("${it.TAG};${it.Description}")
- }
+ ?.let {
+ result.cities.add("${it.TAG};${it.Description}")
+ }
}
// airplane names
String_To_List(user.airline_tags).forEach { at ->
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
- ?.let{
- result.airlines.add("${it.TAG};${it.Description}")
- }
+ ?.let {
+ result.airlines.add("${it.TAG};${it.Description}")
+ }
}
// places
- db.soundDB.List.filter{it.Category==Category.Places.name}.distinctBy { it.TAG }.forEach { xx ->
- result.places.add("${xx.TAG};${xx.Description}")
- }
+ db.soundDB.List.filter { it.Category == Category.Places.name }.distinctBy { it.TAG }
+ .forEach { xx ->
+ result.places.add("${xx.TAG};${xx.Description}")
+ }
// shalat
- db.soundDB.List.filter{it.Category==Category.Shalat.name}.distinctBy { it.TAG }.forEach { xx ->
- result.shalat.add("${xx.TAG};${xx.Description}")
- }
+ db.soundDB.List.filter { it.Category == Category.Shalat.name }.distinctBy { it.TAG }
+ .forEach { xx ->
+ result.shalat.add("${xx.TAG};${xx.Description}")
+ }
// sequences
- db.soundDB.List.filter{it.Category==Category.Sequence.name}.distinctBy { it.TAG }.forEach { xx ->
+ db.soundDB.List.filter { it.Category == Category.Sequence.name }
+ .distinctBy { it.TAG }.forEach { xx ->
result.sequences.add("${xx.TAG};${xx.Description}")
}
// reasons
- db.soundDB.List.filter{it.Category==Category.Reason.name}.distinctBy { it.TAG }.forEach { xx ->
- result.reasons.add("${xx.TAG};${xx.Description}")
- }
+ db.soundDB.List.filter { it.Category == Category.Reason.name }.distinctBy { it.TAG }
+ .forEach { xx ->
+ result.reasons.add("${xx.TAG};${xx.Description}")
+ }
// procedures
- db.soundDB.List.filter{it.Category==Category.Procedure.name}.distinctBy { it.TAG }.forEach { xx ->
+ db.soundDB.List.filter { it.Category == Category.Procedure.name }
+ .distinctBy { it.TAG }.forEach { xx ->
result.procedures.add("${xx.TAG};${xx.Description}")
}
// gates
- db.soundDB.List.filter{it.Category==Category.Gate.name}.distinctBy { it.TAG }.forEach { xx ->
- result.gates.add("${xx.TAG};${xx.Description}")
- }
+ db.soundDB.List.filter { it.Category == Category.Gate.name }.distinctBy { it.TAG }
+ .forEach { xx ->
+ result.gates.add("${xx.TAG};${xx.Description}")
+ }
// compensation
- db.soundDB.List.filter{it.Category==Category.Compensation.name}.distinctBy { it.TAG }.forEach { xx ->
+ db.soundDB.List.filter { it.Category == Category.Compensation.name }
+ .distinctBy { it.TAG }.forEach { xx ->
result.compensation.add("${xx.TAG};${xx.Description}")
}
// greetings
- db.soundDB.List.filter{it.Category==Category.Greeting.name}.distinctBy { it.TAG }.forEach { xx ->
+ db.soundDB.List.filter { it.Category == Category.Greeting.name }
+ .distinctBy { it.TAG }.forEach { xx ->
result.greetings.add("${xx.TAG};${xx.Description}")
}
@@ -2149,18 +2175,18 @@ class WebApp(val listenPort: Int, val userlist: List>, val
} else ResultMessageString(ctx, 400, "Username is empty")
}
}
- path("SemiAuto"){
+ path("SemiAuto") {
post { ctx ->
- val json : JsonNode = objectmapper.readTree(ctx.body())
+ val json: JsonNode = objectmapper.readTree(ctx.body())
// butuh description, languages, tags, dan broadcastzones
val description = json.get("description").asText("")
val languages = json.get("languages").asText("")
val tags = json.get("tags").asText("")
val broadcastzones = json.get("broadcastzones").asText("")
- if (description.isNotEmpty()){
- if (languages.isNotEmpty()){
- if (tags.isNotEmpty()){
- if (broadcastzones.isNotEmpty()){
+ if (description.isNotEmpty()) {
+ if (languages.isNotEmpty()) {
+ if (tags.isNotEmpty()) {
+ if (broadcastzones.isNotEmpty()) {
val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
@@ -2172,10 +2198,10 @@ class WebApp(val listenPort: Int, val userlist: List>, val
1u,
languages
)
- if (db.queuetableDB.Add(qt)){
+ if (db.queuetableDB.Add(qt)) {
db.queuetableDB.Resort()
- Logger.info{"SemiAutoWeb added to queue table: $qt" }
- println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
+ Logger.info { "SemiAutoWeb added to queue table: $qt" }
+ //println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
@@ -2185,13 +2211,12 @@ class WebApp(val listenPort: Int, val userlist: List>, val
}
}
- path("Log"){
- get("/{datelog}"){ ctx ->
+ path("Log") {
+ get("/{datelog}") { ctx ->
val datelog = ctx.pathParam("datelog")
- if (ValidDate(datelog)){
+ if (ValidDate(datelog)) {
println("SemiAuto Get Log for date $datelog")
- db.GetLogForHtml(datelog){
- loghtml ->
+ db.GetLogForHtml(datelog) { loghtml ->
val resultstring = objectmapper.writeValueAsString(loghtml)
println("Log HTML for date $datelog: $resultstring")
ctx.result(resultstring)
@@ -2202,18 +2227,19 @@ class WebApp(val listenPort: Int, val userlist: List>, val
}
}
}
- }.start(listenPort+1)
+ }.start(listenPort + 1)
}
+
fun ResultMessageString(ctx: Context, code: Int, message: String) {
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
}
- fun CheckSemiAutoUsers(ctx: Context){
+ fun CheckSemiAutoUsers(ctx: Context) {
val user = ctx.cookie("semiauto-user")
if (user == null) {
ctx.redirect("login.html")
}
- val foundUser = db.userDB.List.find { u -> u.username==user }
+ val foundUser = db.userDB.List.find { u -> u.username == user }
if (foundUser == null) {
ctx.redirect("login.html")
}