1.1.0 released, new user UI and modified system architecture to improve performance

This commit is contained in:
BuildTools 2024-09-21 16:44:15 +08:00 committed by DaLaw2
parent 23138f0fc9
commit b746345b85
159 changed files with 4543 additions and 4698 deletions

View File

@ -1,10 +1,10 @@
[package]
name = "Agent"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
AgentLibrary = { path = "../AgentLibrary" }
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
tokio = { version = "1.41.0", features = ["full", "tracing"] }

View File

@ -1,19 +1,19 @@
[package]
name = "AgentLibrary"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
toml = "0.8.19"
image = "0.25.2"
image = "0.25.4"
chrono = "0.4.38"
sysinfo = "0.31.4"
async-ctrlc = "1.2.0"
lazy_static = "1.4.0"
serde_json = "1.0.128"
lazy_static = "1.5.0"
serde_json = "1.0.132"
Macro = { path = "../Macro" }
Common = { path = "../Common" }
uuid = { version = "1.10.0", features = ["v4"] }
tokio = { version = "1.40.0", features = ["full"] }
serde = { version = "1.0.210", features = ["derive"] }
uuid = { version = "1.11.0", features = ["v4"] }
tokio = { version = "1.41.0", features = ["full"] }
serde = { version = "1.0.213", features = ["derive"] }

View File

@ -1,10 +1,10 @@
use tokio::select;
use tokio::sync::oneshot;
use crate::utils::logging::*;
use crate::connection::channel::control_channel_receiver::ReceiverTX;
use crate::connection::packet::Packet;
use crate::connection::packet::PacketType;
use crate::connection::socket::socket_stream::ReadHalf;
use crate::connection::channel::control_channel_receiver::ReceiverTX;
use crate::utils::logging::*;
use tokio::select;
use tokio::sync::oneshot;
pub struct ReceiveThread {
socket_rx: ReadHalf,
@ -29,21 +29,21 @@ impl ReceiveThread {
if let Ok(packet) = packet {
let packet_type = PacketType::parse_packet_type(&packet.clone_id_byte());
let result = match packet_type {
PacketType::AgentInformationAcknowledgePacket => self.receiver_tx.agent_information_acknowledge_packet.send(packet),
PacketType::AgentInfoAckPacket => self.receiver_tx.agent_info_ack_packet.send(packet),
PacketType::ControlPacket => self.receiver_tx.control_packet.send(packet),
PacketType::DataChannelPortPacket => self.receiver_tx.data_channel_port_packet.send(packet),
PacketType::PerformanceAcknowledgePacket => self.receiver_tx.performance_acknowledge_packet.send(packet),
PacketType::PerformanceAckPacket => self.receiver_tx.performance_ack_packet.send(packet),
_ => {
logging_warning!("Receive Thread", "Receive unexpected packet");
logging_warning!(NetworkEntry::UnexpectedPacket);
Ok(())
},
};
if result.is_err() {
logging_notice!("Receive Thread", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
break;
}
} else {
logging_notice!("Receive Thread", "Management side disconnected");
logging_information!(NetworkEntry::ManagementDisconnect);
break;
}
},

View File

@ -1,37 +1,35 @@
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use crate::utils::logging::*;
use crate::connection::channel::control_channel_receive_thread::ReceiveThread;
use crate::connection::packet::base_packet::BasePacket;
use crate::connection::socket::socket_stream::ReadHalf;
use crate::connection::channel::control_channel_receive_thread::ReceiveThread;
use crate::utils::create_unbounded_channels;
use crate::utils::logging::*;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
pub struct ReceiverTX {
pub agent_information_acknowledge_packet: mpsc::UnboundedSender<BasePacket>,
pub agent_info_ack_packet: mpsc::UnboundedSender<BasePacket>,
pub control_packet: mpsc::UnboundedSender<BasePacket>,
pub data_channel_port_packet: mpsc::UnboundedSender<BasePacket>,
pub performance_acknowledge_packet: mpsc::UnboundedSender<BasePacket>,
pub performance_ack_packet: mpsc::UnboundedSender<BasePacket>,
}
pub struct ControlChannelReceiver {
stop_signal_tx: Option<oneshot::Sender<()>>,
pub agent_information_acknowledge_packet: mpsc::UnboundedReceiver<BasePacket>,
pub agent_info_ack_packet: mpsc::UnboundedReceiver<BasePacket>,
pub control_packet: mpsc::UnboundedReceiver<BasePacket>,
pub data_channel_port_packet: mpsc::UnboundedReceiver<BasePacket>,
pub performance_acknowledge_packet: mpsc::UnboundedReceiver<BasePacket>,
pub performance_ack_packet: mpsc::UnboundedReceiver<BasePacket>,
}
impl ControlChannelReceiver {
pub fn new(socket_rx: ReadHalf) -> Self {
create_unbounded_channels!(4);
let (stop_signal_tx, stop_signal_rx) = oneshot::channel();
let (agent_information_acknowledge_packet_tx, agent_information_acknowledge_packet_rx) = mpsc::unbounded_channel();
let (control_packet_tx, control_packet_rx) = mpsc::unbounded_channel();
let (data_channel_port_packet_tx, data_channel_port_packet_rx) = mpsc::unbounded_channel();
let (performance_acknowledge_packet_tx, performance_acknowledge_packet_rx) = mpsc::unbounded_channel();
let receiver_tx = ReceiverTX {
agent_information_acknowledge_packet: agent_information_acknowledge_packet_tx,
control_packet: control_packet_tx,
data_channel_port_packet: data_channel_port_packet_tx,
performance_acknowledge_packet: performance_acknowledge_packet_tx,
agent_info_ack_packet: channel_0_tx,
control_packet: channel_1_tx,
data_channel_port_packet: channel_2_tx,
performance_ack_packet: channel_3_tx,
};
let mut receive_thread = ReceiveThread::new(socket_rx, receiver_tx, stop_signal_rx);
tokio::spawn(async move {
@ -39,27 +37,25 @@ impl ControlChannelReceiver {
});
Self {
stop_signal_tx: Some(stop_signal_tx),
agent_information_acknowledge_packet: agent_information_acknowledge_packet_rx,
control_packet: control_packet_rx,
data_channel_port_packet: data_channel_port_packet_rx,
performance_acknowledge_packet: performance_acknowledge_packet_rx,
agent_info_ack_packet: channel_0_rx,
control_packet: channel_1_rx,
data_channel_port_packet: channel_2_rx,
performance_ack_packet: channel_3_rx,
}
}
pub async fn disconnect(&mut self) {
self.agent_information_acknowledge_packet.close();
self.agent_info_ack_packet.close();
self.control_packet.close();
self.data_channel_port_packet.close();
self.performance_acknowledge_packet.close();
self.performance_ack_packet.close();
match self.stop_signal_tx.take() {
Some(stop_signal) => {
if stop_signal.send(()).is_ok() {
logging_information!("Control Channel", "Successfully destroyed Receiver");
} else {
logging_error!("Control Channel", "Failed to destroy Receiver");
if stop_signal.send(()).is_err() {
logging_error!(NetworkEntry::DestroyInstanceError);
}
},
None => logging_error!("Control Channel", "Failed to destroy Receiver"),
}
None => logging_error!(NetworkEntry::DestroyInstanceError),
}
}
}

View File

@ -1,11 +1,11 @@
use tokio::sync::{mpsc, oneshot};
use crate::utils::logging::*;
use crate::connection::channel::send_thread::SendThread;
use crate::connection::packet::Packet;
use crate::connection::socket::socket_stream::WriteHalf;
use crate::connection::channel::send_thread::SendThread;
use crate::utils::logging::*;
use tokio::sync::{mpsc, oneshot};
pub type SenderTX = mpsc::UnboundedSender<Box<dyn Packet+Send>>;
pub type SenderRX = mpsc::UnboundedReceiver<Box<dyn Packet+Send>>;
pub type SenderTX = mpsc::UnboundedSender<Box<dyn Packet + Send>>;
pub type SenderRX = mpsc::UnboundedReceiver<Box<dyn Packet + Send>>;
pub struct ControlChannelSender {
sender_tx: SenderTX,
@ -29,20 +29,18 @@ impl ControlChannelSender {
pub async fn disconnect(&mut self) {
match self.stop_signal_tx.take() {
Some(stop_signal) => {
if stop_signal.send(()).is_ok() {
logging_information!("Control Channel", "Successfully destroyed Sender");
} else {
logging_error!("Control Channel", "Failed to destroy Sender");
if stop_signal.send(()).is_err() {
logging_error!(NetworkEntry::DestroyInstanceError);
}
},
None => logging_error!("Control Channel", "Failed to destroy Sender"),
}
None => logging_error!(NetworkEntry::DestroyInstanceError),
}
}
pub async fn send<T: Packet + Send + 'static>(&mut self, packet: T) {
let packet: Box<dyn Packet + Send + 'static> = Box::new(packet);
if self.sender_tx.send(packet).is_err() {
logging_notice!("Control Channel", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
}
}
}

View File

@ -1,10 +1,10 @@
use tokio::select;
use tokio::sync::oneshot;
use crate::utils::logging::*;
use crate::connection::channel::data_channel_receiver::ReceiverTX;
use crate::connection::packet::Packet;
use crate::connection::packet::PacketType;
use crate::connection::socket::socket_stream::ReadHalf;
use crate::connection::channel::data_channel_receiver::ReceiverTX;
use crate::utils::logging::*;
use tokio::select;
use tokio::sync::oneshot;
pub struct ReceiveThread {
socket_rx: ReadHalf,
@ -31,22 +31,24 @@ impl ReceiveThread {
let result = match packet_type {
PacketType::AlivePacket => self.receiver_tx.alive_packet.send(packet),
PacketType::FileBodyPacket => self.receiver_tx.file_body_packet.send(packet),
PacketType::FileHeaderAckPacket => self.receiver_tx.file_header_ack_packet.send(packet),
PacketType::FileHeaderPacket => self.receiver_tx.file_header_packet.send(packet),
PacketType::FileTransferEndPacket => self.receiver_tx.file_transfer_end_packet.send(packet),
PacketType::ResultAcknowledgePacket => self.receiver_tx.result_acknowledge_packet.send(packet),
PacketType::FileTransferResultPacket => self.receiver_tx.file_transfer_result_packet.send(packet),
PacketType::StillProcessPacket => self.receiver_tx.still_process_packet.send(packet),
PacketType::TaskInfoPacket => self.receiver_tx.task_info_packet.send(packet),
PacketType::TaskResultAckPacket => self.receiver_tx.task_result_ack_packet.send(packet),
_ => {
logging_warning!("Receive Thread", "Receive unexpected packet");
logging_warning!(NetworkEntry::UnexpectedPacket);
Ok(())
},
};
if result.is_err() {
logging_notice!("Receive Thread", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
break;
}
} else {
logging_notice!("Receive Thread", "Management side disconnected");
logging_information!(NetworkEntry::ManagementDisconnect);
break;
}
},

View File

@ -1,49 +1,50 @@
use tokio::sync::oneshot;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use crate::utils::logging::*;
use crate::connection::channel::data_channel_receive_thread::ReceiveThread;
use crate::connection::packet::base_packet::BasePacket;
use crate::connection::socket::socket_stream::ReadHalf;
use crate::connection::channel::data_channel_receive_thread::ReceiveThread;
use crate::utils::create_unbounded_channels;
use crate::utils::logging::*;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
pub struct ReceiverTX {
pub alive_packet: UnboundedSender<BasePacket>,
pub file_body_packet: UnboundedSender<BasePacket>,
pub file_header_ack_packet: UnboundedSender<BasePacket>,
pub file_header_packet: UnboundedSender<BasePacket>,
pub file_transfer_end_packet: UnboundedSender<BasePacket>,
pub result_acknowledge_packet: UnboundedSender<BasePacket>,
pub file_transfer_result_packet: UnboundedSender<BasePacket>,
pub still_process_packet: UnboundedSender<BasePacket>,
pub task_info_packet: UnboundedSender<BasePacket>,
pub task_result_ack_packet: UnboundedSender<BasePacket>,
}
pub struct DataChannelReceiver {
stop_signal_tx: Option<oneshot::Sender<()>>,
pub alive_packet: UnboundedReceiver<BasePacket>,
pub file_body_packet: UnboundedReceiver<BasePacket>,
pub file_header_ack_packet: UnboundedReceiver<BasePacket>,
pub file_header_packet: UnboundedReceiver<BasePacket>,
pub file_transfer_end_packet: UnboundedReceiver<BasePacket>,
pub result_acknowledge_packet: UnboundedReceiver<BasePacket>,
pub file_transfer_result_packet: UnboundedReceiver<BasePacket>,
pub still_process_packet: UnboundedReceiver<BasePacket>,
pub task_info_packet: UnboundedReceiver<BasePacket>,
pub task_result_ack_packet: UnboundedReceiver<BasePacket>,
}
impl DataChannelReceiver {
pub fn new(socket_rx: ReadHalf) -> Self {
create_unbounded_channels!(9);
let (stop_signal_tx, stop_signal_rx) = oneshot::channel();
let (alive_packet_tx, alive_packet_rx) = mpsc::unbounded_channel();
let (file_body_packet_tx, file_body_packet_rx) = mpsc::unbounded_channel();
let (file_header_packet_tx, file_header_packet_rx) = mpsc::unbounded_channel();
let (file_transfer_end_packet_tx, file_transfer_end_packet_rx) = mpsc::unbounded_channel();
let (result_acknowledge_packet_tx, result_acknowledge_packet_rx) = mpsc::unbounded_channel();
let (still_process_packet_tx, still_process_packet_rx) = mpsc::unbounded_channel();
let (task_info_packet_tx, task_info_packet_rx) = mpsc::unbounded_channel();
let receiver_tx = ReceiverTX {
alive_packet: alive_packet_tx,
file_body_packet: file_body_packet_tx,
file_header_packet: file_header_packet_tx,
file_transfer_end_packet: file_transfer_end_packet_tx,
result_acknowledge_packet: result_acknowledge_packet_tx,
still_process_packet: still_process_packet_tx,
task_info_packet: task_info_packet_tx,
alive_packet: channel_0_tx,
file_body_packet: channel_1_tx,
file_header_ack_packet: channel_2_tx,
file_header_packet: channel_3_tx,
file_transfer_end_packet: channel_4_tx,
file_transfer_result_packet: channel_5_tx,
still_process_packet: channel_6_tx,
task_info_packet: channel_7_tx,
task_result_ack_packet: channel_8_tx,
};
let mut receive_thread = ReceiveThread::new(socket_rx, receiver_tx, stop_signal_rx);
tokio::spawn(async move {
@ -51,32 +52,35 @@ impl DataChannelReceiver {
});
Self {
stop_signal_tx: Some(stop_signal_tx),
alive_packet: alive_packet_rx,
file_body_packet: file_body_packet_rx,
file_header_packet: file_header_packet_rx,
file_transfer_end_packet: file_transfer_end_packet_rx,
result_acknowledge_packet: result_acknowledge_packet_rx,
still_process_packet: still_process_packet_rx,
task_info_packet: task_info_packet_rx,
alive_packet: channel_0_rx,
file_body_packet: channel_1_rx,
file_header_ack_packet: channel_2_rx,
file_header_packet: channel_3_rx,
file_transfer_end_packet: channel_4_rx,
file_transfer_result_packet: channel_5_rx,
still_process_packet: channel_6_rx,
task_info_packet: channel_7_rx,
task_result_ack_packet: channel_8_rx,
}
}
pub async fn disconnect(&mut self) {
self.alive_packet.close();
self.file_body_packet.close();
self.file_header_ack_packet.close();
self.file_header_packet.close();
self.file_transfer_end_packet.close();
self.file_transfer_result_packet.close();
self.still_process_packet.close();
self.task_info_packet.close();
self.task_result_ack_packet.close();
match self.stop_signal_tx.take() {
Some(stop_signal) => {
if stop_signal.send(()).is_ok() {
logging_information!("Data Channel", "Successfully destroyed the Receiver");
} else {
logging_error!("Data Channel", "Failed to destroy Receiver");
if stop_signal.send(()).is_err() {
logging_error!(NetworkEntry::DestroyInstanceError);
}
},
None => logging_error!("Data Channel", "Failed to destroy Receiver"),
}
None => logging_error!(NetworkEntry::DestroyInstanceError),
}
}
}

View File

@ -1,11 +1,11 @@
use tokio::sync::{mpsc, oneshot};
use crate::utils::logging::*;
use crate::connection::channel::send_thread::SendThread;
use crate::connection::packet::Packet;
use crate::connection::socket::socket_stream::WriteHalf;
use crate::connection::channel::send_thread::SendThread;
use crate::utils::logging::*;
use tokio::sync::{mpsc, oneshot};
pub type SenderTX = mpsc::UnboundedSender<Box<dyn Packet+Send>>;
pub type SenderRX = mpsc::UnboundedReceiver<Box<dyn Packet+Send>>;
pub type SenderTX = mpsc::UnboundedSender<Box<dyn Packet + Send>>;
pub type SenderRX = mpsc::UnboundedReceiver<Box<dyn Packet + Send>>;
pub struct DataChannelSender {
sender_tx: SenderTX,
@ -29,20 +29,18 @@ impl DataChannelSender {
pub async fn disconnect(&mut self) {
match self.stop_signal_tx.take() {
Some(stop_signal) => {
if stop_signal.send(()).is_ok() {
logging_information!("Data Channel", "Successfully destroyed the Sender")
} else {
logging_error!("Data Channel", "Failed to destroy Sender")
if stop_signal.send(()).is_err() {
logging_error!(NetworkEntry::DestroyInstanceError);
}
},
None => logging_error!("Data Channel", "Failed to destroy Sender"),
}
None => logging_error!(NetworkEntry::DestroyInstanceError),
}
}
pub async fn send<T: Packet + Send + 'static>(&mut self, packet: T) {
let packet: Box<dyn Packet + Send + 'static> = Box::new(packet);
if self.sender_tx.send(packet).is_err() {
logging_notice!("Data Channel", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
}
}
}

View File

@ -6,11 +6,11 @@ pub mod data_channel_receiver;
pub mod data_channel_sender;
pub mod send_thread;
use crate::connection::socket::socket_stream::SocketStream;
use crate::connection::channel::data_channel_sender::DataChannelSender;
use crate::connection::channel::data_channel_receiver::DataChannelReceiver;
use crate::connection::channel::control_channel_sender::ControlChannelSender;
use crate::connection::channel::control_channel_receiver::ControlChannelReceiver;
use crate::connection::channel::control_channel_sender::ControlChannelSender;
use crate::connection::channel::data_channel_receiver::DataChannelReceiver;
use crate::connection::channel::data_channel_sender::DataChannelSender;
use crate::connection::socket::socket_stream::SocketStream;
pub struct ControlChannel;

View File

@ -1,9 +1,9 @@
use crate::connection::packet::Packet;
use crate::connection::socket::socket_stream::WriteHalf;
use crate::utils::logging::*;
use tokio::select;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use crate::utils::logging::*;
use crate::connection::packet::Packet;
use crate::connection::socket::socket_stream::WriteHalf;
type SenderRX = mpsc::UnboundedReceiver<Box<dyn Packet + Send>>;
@ -30,12 +30,12 @@ impl SendThread {
match packet {
Some(packet) => {
if self.socket_tx.send_packet(packet).await.is_err() {
logging_notice!("Send Thread", "Management side disconnected");
logging_information!(NetworkEntry::ManagementDisconnect);
break;
}
},
None => {
logging_notice!("Send Thread", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
break;
},
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct AgentInfoPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct AgentInformationPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl AgentInformationPacket {
pub fn new(data: Vec<u8>) -> Self {
Self {
length: length_to_byte(16 + data.len()),
id: PacketType::AgentInformationPacket.as_byte(),
data,
packet_type: PacketType::AgentInformationPacket
}
}
}
impl Packet for AgentInformationPacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct AliveAckPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct AliveAcknowledgePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl AliveAcknowledgePacket {
pub fn new() -> Self {
Self {
length: length_to_byte(16),
id: PacketType::AliveAcknowledgePacket.as_byte(),
data: Vec::new(),
packet_type: PacketType::AliveAcknowledgePacket
}
}
}
impl Packet for AliveAcknowledgePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct ControlAckPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct ControlAcknowledgePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl ControlAcknowledgePacket {
pub fn new() -> Self {
Self {
length: length_to_byte(16),
id: PacketType::ControlAcknowledgePacket.as_byte(),
data: Vec::new(),
packet_type: PacketType::ControlAcknowledgePacket
}
}
}
impl Packet for ControlAcknowledgePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct FileHeaderAcknowledgePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl FileHeaderAcknowledgePacket {
pub fn new() -> Self {
Self {
length: length_to_byte(16),
id: PacketType::FileHeaderAcknowledgePacket.as_byte(),
data: Vec::new(),
packet_type: PacketType::FileHeaderAcknowledgePacket
}
}
}
impl Packet for FileHeaderAcknowledgePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct FileTransferResultPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl FileTransferResultPacket {
pub fn new(data: Vec<u8>) -> Self {
Self {
length: length_to_byte(16 + data.len()),
id: PacketType::FileTransferResultPacket.as_byte(),
data,
packet_type: PacketType::FileTransferResultPacket
}
}
}
impl Packet for FileTransferResultPacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -1,11 +1,9 @@
pub mod agent_information_packet;
pub mod alive_acknowledge_packet;
pub mod control_acknowledge_packet;
pub mod file_header_acknowledge_packet;
pub mod file_transfer_result_packet;
pub mod agent_info_packet;
pub mod alive_ack_packet;
pub mod control_ack_packet;
pub mod performance_packet;
pub mod result_packet;
pub mod still_process_acknowledge_packet;
pub mod task_info_acknowledge_packet;
pub mod still_process_ack_packet;
pub mod task_info_ack_packet;
pub mod task_result_packet;
pub use Common::connection::packet::*;

View File

@ -1,57 +1,10 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct PerformancePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl PerformancePacket {
pub fn new(data: Vec<u8>) -> Self {
Self {
length: length_to_byte(16 + data.len()),
id: PacketType::PerformancePacket.as_byte(),
data,
packet_type: PacketType::PerformancePacket
}
}
}
impl Packet for PerformancePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct ResultPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl ResultPacket {
pub fn new(data: Vec<u8>) -> Self {
Self {
length: length_to_byte(16 + data.len()),
id: PacketType::ResultPacket.as_byte(),
data,
packet_type: PacketType::ResultPacket
}
}
}
impl Packet for ResultPacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct StillProcessAckPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct StillProcessAcknowledgePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl StillProcessAcknowledgePacket {
pub fn new() -> Self {
Self {
length: length_to_byte(16),
id: PacketType::StillProcessAcknowledgePacket.as_byte(),
data: Vec::new(),
packet_type: PacketType::StillProcessAcknowledgePacket
}
}
}
impl Packet for StillProcessAcknowledgePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct TaskInfoAckPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,57 +0,0 @@
use crate::connection::packet::{Packet, PacketType, length_to_byte};
pub struct TaskInfoAcknowledgePacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}
impl TaskInfoAcknowledgePacket {
pub fn new() -> Self {
Self {
length: length_to_byte(16),
id: PacketType::TaskInfoAcknowledgePacket.as_byte(),
data: Vec::new(),
packet_type: PacketType::TaskInfoAcknowledgePacket
}
}
}
impl Packet for TaskInfoAcknowledgePacket {
fn as_length_byte(&self) -> &[u8] {
&self.length
}
fn as_id_byte(&self) -> &[u8] {
&self.id
}
fn as_data_byte(&self) -> &[u8] {
&self.data
}
fn clone_length_byte(&self) -> Vec<u8> {
self.length.clone()
}
fn clone_id_byte(&self) -> Vec<u8> {
self.id.clone()
}
fn clone_data_byte(&self) -> Vec<u8> {
self.data.clone()
}
fn data_to_string(&self) -> String {
String::from_utf8_lossy(&*self.data.clone()).to_string()
}
fn packet_type(&self) -> PacketType {
self.packet_type
}
fn equal(&self, packet_type: PacketType) -> bool {
self.packet_type.eq(&packet_type)
}
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct TaskResultPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,7 +1,7 @@
use crate::connection::socket::socket_stream::SocketStream;
use crate::utils::config::Config;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use crate::utils::config::Config;
use crate::connection::socket::socket_stream::SocketStream;
pub struct ManagementSocket;

View File

@ -1,42 +1,46 @@
use std::mem;
use uuid::Uuid;
use tokio::select;
use std::sync::Arc;
use tokio::fs::File;
use tokio::sync::RwLock;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::io::AsyncWriteExt;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tokio::time::{Instant, sleep};
use crate::utils::logging::*;
use crate::utils::config::Config;
use crate::connection::channel::control_channel_receiver::ControlChannelReceiver;
use crate::connection::channel::control_channel_sender::ControlChannelSender;
use crate::connection::channel::data_channel_receiver::DataChannelReceiver;
use crate::connection::channel::data_channel_sender::DataChannelSender;
use crate::connection::channel::{ControlChannel, DataChannel};
use crate::connection::packet::agent_info_packet::AgentInfoPacket;
use crate::connection::packet::alive_ack_packet::AliveAckPacket;
use crate::connection::packet::control_ack_packet::ControlAckPacket;
use crate::connection::packet::file_body_packet::FileBodyPacket;
use crate::connection::packet::file_header_ack_packet::FileHeaderAckPacket;
use crate::connection::packet::file_header_packet::FileHeaderPacket;
use crate::connection::packet::file_transfer_end_packet::FileTransferEndPacket;
use crate::connection::packet::file_transfer_result_packet::FileTransferResultPacket;
use crate::connection::packet::performance_packet::PerformancePacket;
use crate::connection::packet::task_result_packet::TaskResultPacket;
use crate::connection::packet::task_info_ack_packet::TaskInfoAckPacket;
use crate::connection::packet::Packet;
use crate::connection::socket::socket_stream::SocketStream;
use crate::management::inference_manager::InferenceManager;
use crate::management::monitor::Monitor;
use crate::utils::clear_unbounded_channel;
use crate::management::utils::task_info::TaskInfo;
use crate::management::utils::model_type::ModelType;
use crate::management::utils::agent_state::AgentState;
use crate::management::utils::file_header::FileHeader;
use crate::management::utils::task_result::TaskResult;
use crate::management::utils::bounding_box::BoundingBox;
use crate::connection::packet::result_packet::ResultPacket;
use crate::management::calculate_manager::CalculateManager;
use crate::connection::socket::socket_stream::SocketStream;
use crate::connection::channel::{ControlChannel, DataChannel};
use crate::connection::packet::performance_packet::PerformancePacket;
use crate::management::utils::file_transfer_result::FileTransferResult;
use crate::connection::channel::data_channel_sender::DataChannelSender;
use crate::connection::channel::data_channel_receiver::DataChannelReceiver;
use crate::connection::channel::control_channel_sender::ControlChannelSender;
use crate::connection::packet::agent_information_packet::AgentInformationPacket;
use crate::connection::packet::alive_acknowledge_packet::AliveAcknowledgePacket;
use crate::connection::channel::control_channel_receiver::ControlChannelReceiver;
use crate::connection::packet::control_acknowledge_packet::ControlAcknowledgePacket;
use crate::connection::packet::file_transfer_result_packet::FileTransferResultPacket;
use crate::connection::packet::task_info_acknowledge_packet::TaskInfoAcknowledgePacket;
use crate::connection::packet::file_header_acknowledge_packet::FileHeaderAcknowledgePacket;
use crate::management::utils::inference_argument::ModelType;
use crate::management::utils::task_result::TaskResult;
use crate::management::utils::task_info::TaskInfo;
use crate::utils::clear_unbounded_channel;
use crate::utils::config::Config;
use crate::utils::logging::*;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::mem;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::select;
use tokio::sync::RwLock;
use tokio::time::{sleep, Instant};
use uuid::Uuid;
use crate::connection::packet::still_process_ack_packet::StillProcessAckPacket;
pub struct Agent {
pub state: AgentState,
@ -50,10 +54,10 @@ pub struct Agent {
impl Agent {
pub async fn new(socket_stream: SocketStream) -> Result<Self, LogEntry> {
let config = Config::now().await;
let (mut control_channel_sender, mut control_channel_receiver) = ControlChannel::new(socket_stream);
let mut information_confirm = false;
let information = serde_json::to_vec(&Monitor::get_system_info().await)
.map_err(|err| error_entry!("Agent", "Unable to serialize data", format!("Err: {err}")))?;
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
let (mut control_channel_sender, mut control_channel_receiver) = ControlChannel::new(socket_stream);
let timer = Instant::now();
let mut polling_times = 0_u32;
let polling_interval = Duration::from_millis(config.polling_interval);
@ -61,27 +65,25 @@ impl Agent {
while timer.elapsed() <= timeout_duration {
if timer.elapsed() > polling_times * polling_interval {
if !information_confirm {
control_channel_sender.send(AgentInformationPacket::new(information.clone())).await;
control_channel_sender.send(AgentInfoPacket::new(information.clone())).await;
} else {
let performance = Monitor::get_performance().await;
let performance_data = serde_json::to_vec(&performance)
.map_err(|err| error_entry!("Agent", "Unable to serialize data", format!("Err: {err}")))?;
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
control_channel_sender.send(PerformancePacket::new(performance_data)).await;
}
polling_times += 1;
}
select! {
biased;
packet = control_channel_receiver.agent_information_acknowledge_packet.recv() => {
let _ = packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
packet = control_channel_receiver.agent_info_ack_packet.recv() => {
let _ = packet.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
information_confirm = true;
},
packet = control_channel_receiver.performance_acknowledge_packet.recv() => {
let _ = packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
}
packet = control_channel_receiver.performance_ack_packet.recv() => {
let _ = packet.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
if !information_confirm {
Err(error_entry!("Agent", "Wrong packet delivery order"))?;
Err(error_entry!(MiscEntry::WrongDeliverOrder))?;
}
let agent = Self {
state: AgentState::None,
@ -92,11 +94,11 @@ impl Agent {
data_channel_receiver: None,
};
return Ok(agent);
},
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
}
Err(notice_entry!("Agent", "Control Channel timeout"))
Err(information_entry!(NetworkEntry::ControlChannelTimeout))
}
pub async fn run(agent: Arc<RwLock<Agent>>) {
@ -110,6 +112,20 @@ impl Agent {
});
}
pub async fn terminate(agent: &Arc<RwLock<Agent>>) {
logging_information!(SystemEntry::Terminating);
let mut agent = agent.write().await;
agent.control_channel_sender.disconnect().await;
agent.control_channel_receiver.disconnect().await;
if let Some(data_channel_sender) = &mut agent.data_channel_sender {
data_channel_sender.disconnect().await;
}
if let Some(data_channel_receiver) = &mut agent.data_channel_receiver {
data_channel_receiver.disconnect().await;
}
logging_information!(SystemEntry::TerminateComplete);
}
async fn performance(agent: Arc<RwLock<Agent>>) {
let config = Config::now().await;
let mut polling_times = 0_u32;
@ -122,31 +138,32 @@ impl Agent {
return;
}
if timeout_timer.elapsed() > timeout_duration {
logging_notice!("Agent", "Control Channel timeout");
logging_information!(NetworkEntry::ControlChannelTimeout);
break;
}
if polling_timer.elapsed() > polling_times * polling_interval {
let performance = Monitor::get_performance().await;
match serde_json::to_vec(&performance) {
Ok(performance_data) => {
agent.write().await.control_channel_sender.send(PerformancePacket::new(performance_data)).await;
},
Err(err) => logging_error!("Agent", "Unable to serialize data", format!("Err: {err}")),
agent.write().await.control_channel_sender
.send(PerformancePacket::new(performance_data)).await;
}
Err(err) => logging_error!(IOEntry::SerdeSerializeError(err))
}
polling_times += 1;
}
let mut agent = agent.write().await;
select! {
biased;
reply = agent.control_channel_receiver.performance_acknowledge_packet.recv() => {
reply = agent.control_channel_receiver.performance_ack_packet.recv() => {
if reply.is_some() {
clear_unbounded_channel(&mut agent.control_channel_receiver.performance_acknowledge_packet).await;
clear_unbounded_channel(&mut agent.control_channel_receiver.performance_ack_packet).await;
timeout_timer = Instant::now();
} else {
logging_notice!("Agent", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
break;
}
},
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
}
@ -155,22 +172,29 @@ impl Agent {
async fn management(agent: Arc<RwLock<Agent>>) {
loop {
Self::refresh_state(agent.clone()).await;
Self::refresh_state(&agent).await;
let state = agent.read().await.state;
match state {
AgentState::ProcessTask => Self::process_task(agent.clone()).await,
AgentState::Idle(idle_time) => Self::idle(agent.clone(), Duration::from_secs(idle_time)).await,
AgentState::CreateDataChannel => Self::create_data_channel(agent.clone()).await,
let result = match state {
AgentState::ProcessTask => Self::process_task(&agent).await,
AgentState::Idle(idle_time) => Self::idle(&agent, Duration::from_secs(idle_time)).await,
AgentState::CreateDataChannel => Self::create_data_channel(&agent).await,
AgentState::Terminate => {
Self::terminate(agent.clone()).await;
Self::terminate(&agent).await;
return;
},
_ => {},
}
_ => Ok(())
};
if let Err(entry) = result {
logging_entry!(entry);
}
}
}
async fn refresh_state(agent: Arc<RwLock<Agent>>) {
async fn refresh_state(agent: &Arc<RwLock<Agent>>) {
{
let mut agent = agent.write().await;
clear_unbounded_channel(&mut agent.control_channel_receiver.control_packet).await;
}
let config = Config::now().await;
let timer = Instant::now();
let timeout_duration = Duration::from_secs(config.control_channel_timeout);
@ -184,51 +208,40 @@ impl Agent {
packet = agent.control_channel_receiver.control_packet.recv() => {
match packet {
Some(packet) => {
clear_unbounded_channel(&mut agent.control_channel_receiver.control_packet).await;
match serde_json::from_slice::<AgentState>(packet.as_data_byte()) {
Ok(state) => agent.state = state,
Err(err) => {
logging_error!("Agent", "Unable to parse packet data", format!("Err: {err}"));
logging_error!(IOEntry::SerdeDeserializeError(err));
continue;
},
}
}
},
}
None => {
logging_notice!("Agent", "Channel has been closed");
logging_information!(NetworkEntry::ChannelClosed);
agent.state = AgentState::Terminate;
},
return;
}
}
},
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
agent.control_channel_sender.send(ControlAcknowledgePacket::new()).await;
agent.control_channel_sender.send(ControlAckPacket::new()).await;
return;
}
}
async fn process_task(agent: Arc<RwLock<Agent>>) {
let result = match Self::receive_task(agent.clone()).await {
Ok(task_info) => {
match Self::inference_task(task_info).await {
Ok(bounding_box) => Ok(bounding_box),
Err(entry) => {
logging_entry!(entry.clone());
Err(entry.message)
},
}
},
Err(entry) => {
logging_entry!(entry.clone());
Err(entry.message)
},
};
if let Err(entry) = Self::send_result(agent.clone(), result).await {
logging_entry!(entry);
}
async fn process_task(agent: &Arc<RwLock<Agent>>) -> Result<(), LogEntry> {
let task_info = Self::receive_task(agent).await?;
let result = Self::waiting_inference(agent, &task_info).await
.map_err(|err| err.message);
let task_result = TaskResult::new(result);
Self::notice_complete(agent, &task_result).await?;
Self::transfer_result(agent, &task_info).await?;
Ok(())
}
async fn receive_task(agent: Arc<RwLock<Agent>>) -> Result<TaskInfo, LogEntry> {
let task_info = Self::receive_task_info(agent.clone()).await?;
async fn receive_task(agent: &Arc<RwLock<Agent>>) -> Result<TaskInfo, LogEntry> {
let task_info = Self::receive_task_info(agent).await?;
let previous_task_uuid = agent.read().await.previous_task_uuid;
let need_receive_model = if let Some(previous_task_uuid) = previous_task_uuid {
previous_task_uuid != task_info.uuid
@ -236,94 +249,98 @@ impl Agent {
true
};
if need_receive_model {
let model_folder = Path::new(".").join("SavedModel");
Self::receive_file(agent.clone(), &model_folder).await?;
let model_folder = PathBuf::from("./SavedModel");
Self::receive_file(agent, &model_folder).await?;
agent.write().await.previous_task_uuid = Some(task_info.uuid);
}
let image_folder = Path::new(".").join("SavedFile");
Self::receive_file(agent.clone(), &image_folder).await?;
let media_folder = PathBuf::from("./SavedFile");
Self::receive_file(agent, &media_folder).await?;
Ok(task_info)
}
async fn receive_task_info(agent: Arc<RwLock<Agent>>) -> Result<TaskInfo, LogEntry> {
async fn receive_task_info(agent: &Arc<RwLock<Agent>>) -> Result<TaskInfo, LogEntry> {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
clear_unbounded_channel(&mut data_channel_receiver.task_info_packet).await;
}
let config = Config::now().await;
let timer = Instant::now();
let timeout_duration = Duration::from_secs(config.data_channel_timeout);
let task_info = loop {
if agent.read().await.state == AgentState::Terminate {
Err(notice_entry!("Agent", "Terminate. Interrupt current operation"))?;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > timeout_duration {
Err(notice_entry!("Agent", "Data Channel timeout"))?;
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if let Some(data_channel_receiver) = &mut agent.write().await.data_channel_receiver {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
select! {
packet = data_channel_receiver.task_info_packet.recv() => {
let packet = packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
clear_unbounded_channel(&mut data_channel_receiver.task_info_packet).await;
.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
break serde_json::from_slice::<TaskInfo>(packet.as_data_byte())
.map_err(|err| error_entry!("Agent", "Unable to parse packet data", format!("Err: {err}")))?;
.map_err(|err| error_entry!(IOEntry::SerdeDeserializeError(err)))?;
},
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?
}
};
if let Some(data_channel_sender) = &mut agent.write().await.data_channel_sender {
data_channel_sender.send(TaskInfoAcknowledgePacket::new()).await;
if let Some(data_channel_sender) = agent.write().await.data_channel_sender.as_mut() {
data_channel_sender.send(TaskInfoAckPacket::new()).await;
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
return Ok(task_info);
Ok(task_info)
}
async fn receive_file(agent: Arc<RwLock<Agent>>, save_folder: &PathBuf) -> Result<(), LogEntry> {
let file_header = Self::receive_file_header(agent.clone()).await?;
let file_body = Self::receive_file_body(agent.clone(), &file_header).await?;
async fn receive_file(agent: &Arc<RwLock<Agent>>, save_folder: &PathBuf) -> Result<(), LogEntry> {
let file_header = Self::receive_file_header(agent).await?;
let file_body = Self::receive_file_body(agent, &file_header).await?;
Self::create_file(file_header, file_body, save_folder).await?;
Ok(())
}
async fn receive_file_header(agent: Arc<RwLock<Agent>>) -> Result<FileHeader, LogEntry> {
async fn receive_file_header(agent: &Arc<RwLock<Agent>>) -> Result<FileHeader, LogEntry> {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
clear_unbounded_channel(&mut data_channel_receiver.file_header_packet).await;
}
let config = Config::now().await;
let timer = Instant::now();
let timeout_duration = Duration::from_secs(config.data_channel_timeout);
if let Some(data_channel_receiver) = &mut agent.write().await.data_channel_receiver {
clear_unbounded_channel(&mut data_channel_receiver.file_header_packet).await;
}
let file_header = loop {
if agent.read().await.state == AgentState::Terminate {
Err(notice_entry!("Agent", "Terminate. Interrupt current operation"))?;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > timeout_duration {
Err(notice_entry!("Agent", "Data Channel timeout"))?;
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if let Some(data_channel_receiver) = &mut agent.write().await.data_channel_receiver {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
select! {
packet = data_channel_receiver.file_header_packet.recv() => {
let packet = packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
clear_unbounded_channel(&mut data_channel_receiver.file_header_packet).await;
.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
break serde_json::from_slice::<FileHeader>(packet.as_data_byte())
.map_err(|err| error_entry!("Agent", "Unable to parse packet data", format!("Err: {err}")))?;
},
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
.map_err(|err| error_entry!(IOEntry::SerdeDeserializeError(err)))?;
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
};
if let Some(data_channel_sender) = &mut agent.write().await.data_channel_sender {
data_channel_sender.send(FileHeaderAcknowledgePacket::new()).await;
data_channel_sender.send(FileHeaderAckPacket::new()).await;
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
return Ok(file_header);
Ok(file_header)
}
async fn receive_file_body(agent: Arc<RwLock<Agent>>, file_header: &FileHeader) -> Result<Vec<Vec<u8>>, LogEntry> {
async fn receive_file_body(agent: &Arc<RwLock<Agent>>, file_header: &FileHeader) -> Result<Vec<Vec<u8>>, LogEntry> {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
clear_unbounded_channel(&mut data_channel_receiver.file_body_packet).await;
}
let config = Config::now().await;
let mut file_block: HashMap<usize, Vec<u8>> = HashMap::new();
let mut missing_blocks = Vec::new();
@ -331,178 +348,347 @@ impl Agent {
let timeout_duration = Duration::from_secs(config.data_channel_timeout);
loop {
if agent.read().await.state == AgentState::Terminate {
Err(notice_entry!("Agent", "Terminate. Interrupt current operation"))?;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > timeout_duration {
Err(notice_entry!("Agent", "Data Channel timeout"))?;
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
select! {
biased;
packet = data_channel_receiver.file_body_packet.recv() => {
let packet = &packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
clear_unbounded_channel(&mut data_channel_receiver.file_body_packet).await;
timer = Instant::now();
let (sequence_bytes, file_body) = packet.data.split_at(mem::size_of::<usize>());
let packet = packet
.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
let (sequence_bytes, file_body) = packet.data.split_at(size_of::<usize>());
let sequence_bytes = sequence_bytes.try_into()
.map_err(|err| error_entry!("Agent", "Unable to parse packet data", format!("Err: {err}")))?;
.map_err(|_| error_entry!(MiscEntry::InvalidPacket))?;
let sequence_number = usize::from_be_bytes(sequence_bytes);
file_block.insert(sequence_number, Vec::from(file_body));
continue;
},
packet = data_channel_receiver.file_transfer_end_packet.recv() => {
let _ = &packet
.ok_or(notice_entry!("Agent", "Channel has been closed"))?;
timer = Instant::now();
continue;
}
packet = data_channel_receiver.file_transfer_end_packet.recv() => {
clear_unbounded_channel(&mut data_channel_receiver.file_transfer_end_packet).await;
let _ = packet
.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
for sequence_number in 0..file_header.packet_count {
if !file_block.contains_key(&sequence_number) {
missing_blocks.push(sequence_number);
}
}
},
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
timer = Instant::now();
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
if let Some(data_channel_sender) = agent.write().await.data_channel_sender.as_mut() {
if missing_blocks.len() != 0_usize {
let missing_blocks = mem::take(&mut missing_blocks);
let result = FileTransferResult::new(Some(missing_blocks));
let result_data = serde_json::to_vec(&result)
.map_err(|err| error_entry!("Agent", "Unable to serialize data", format!("Err: {err}")))?;
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
data_channel_sender.send(FileTransferResultPacket::new(result_data)).await;
} else {
let result = FileTransferResult::new(None);
let result_data = serde_json::to_vec(&result)
.map_err(|err| error_entry!("Agent", "Unable to serialize data", format!("Err: {err}")))?;
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
data_channel_sender.send(FileTransferResultPacket::new(result_data)).await;
let mut sorted_blocks: Vec<Vec<u8>> = Vec::with_capacity(file_header.packet_count);
for index in 0..file_header.packet_count {
if let Some(block) = file_block.remove(&index) {
sorted_blocks.push(block);
} else {
Err(error_entry!("Agent", "Missing file block"))?
Err(error_entry!(MiscEntry::MissingFileBlockError))?
}
}
return Ok(sorted_blocks);
};
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
}
}
async fn create_file(file_header: FileHeader, file_body: Vec<Vec<u8>>, saved_folder: &PathBuf) -> Result<(), LogEntry> {
let saved_path = saved_folder.join(file_header.filename);
let saved_path = saved_folder.join(file_header.file_name);
let mut file = File::create(&saved_path).await
.map_err(|err| error_entry!("Agent", "Unable create file", format!("File: {}, Err: {}", saved_path.display(), err)))?;
.map_err(|err| error_entry!(IOEntry::CreateFileError(saved_path.display(), err)))?;
for chunk in file_body {
file.write_all(&chunk).await
.map_err(|err| error_entry!("Agent", "Unable to write to file", format!("File: {}, Err: {}", saved_path.display(), err)))?;
.map_err(|err| error_entry!(IOEntry::WriteFileError(saved_path.display(), err)))?;
}
Ok(())
}
async fn inference_task(task_info: TaskInfo) -> Result<Vec<BoundingBox>, LogEntry> {
let model_path = Path::new(".").join("SavedModel").join(task_info.model_filename);
let image_path = Path::new(".").join("SavedFile").join(task_info.image_filename);
return match task_info.model_type {
ModelType::Ultralytics => CalculateManager::ultralytics_inference(model_path, image_path).await,
ModelType::YOLOv4 => CalculateManager::yolov4_inference(model_path, image_path).await,
ModelType::YOLOv7 => CalculateManager::yolov7_inference(model_path, image_path).await,
};
}
async fn send_result(agent: Arc<RwLock<Agent>>, result: Result<Vec<BoundingBox>, String>) -> Result<(), LogEntry> {
async fn waiting_inference(agent: &Arc<RwLock<Agent>>, task_info: &TaskInfo) -> Result<(), LogEntry> {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
clear_unbounded_channel(&mut data_channel_receiver.still_process_packet).await;
}
let config = Config::now().await;
let result = TaskResult::new(result);
let result_data = serde_json::to_vec(&result)
.map_err(|err| error_entry!("Agent", "Unable to serialize data", format!("Err: {err}")))?;
let timer = Instant::now();
let mut polling_times = 0_u32;
let polling_interval = Duration::from_millis(config.polling_interval);
let task_info = task_info.clone();
let mut timer = Instant::now();
let timeout_duration = Duration::from_secs(config.control_channel_timeout);
let join_handle = tokio::spawn(Self::inference(task_info));
loop {
if agent.read().await.state == AgentState::Terminate {
Err(notice_entry!("Agent", "Terminate. Interrupt current operation"))?;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > timeout_duration {
Err(notice_entry!("Agent", "Data Channel timeout"))?;
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if timer.elapsed() > polling_times * polling_interval {
if let Some(data_channel_sender) = agent.write().await.data_channel_sender.as_mut() {
data_channel_sender.send(ResultPacket::new(result_data.clone())).await;
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
}
polling_times += 1;
if join_handle.is_finished() {
break join_handle.await
.map_err(|err| error_entry!(SystemEntry::TaskPanickedError(err)))?;
}
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
let mut agent = agent.write().await;
if let Some(data_channel_receiver) = agent.data_channel_receiver.as_mut() {
select! {
packet = data_channel_receiver.result_acknowledge_packet.recv() => {
if let Some(_) = packet {
clear_unbounded_channel(&mut data_channel_receiver.result_acknowledge_packet).await;
return Ok(());
} else {
Err(notice_entry!("Agent", "Channel has been closed"))?;
}
},
packet = data_channel_receiver.still_process_packet.recv() => {
let _ = packet.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
clear_unbounded_channel(&mut data_channel_receiver.still_process_packet).await;
timer = Instant::now();
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
} else {
Err(warning_entry!("Agent", "Data Channel is not ready"))?;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
if let Some(data_channel_sender) = agent.data_channel_sender.as_mut() {
data_channel_sender.send(StillProcessAckPacket::new()).await;
} else {
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
}
}
async fn idle(agent: Arc<RwLock<Agent>>, idle_duration: Duration) {
async fn inference(task_info: TaskInfo) -> Result<(), LogEntry> {
let inference_argument = task_info.inference_argument;
let model_path = PathBuf::from(format!("./SavedModel/{}", task_info.model_file_name));
let media_path = PathBuf::from(format!("./SavedFile/{}", task_info.media_file_name));
match (inference_argument.model_type, media_path.extension().and_then(OsStr::to_str)) {
(ModelType::Ultralytics, Some("png") | Some("jpg") | Some("jpeg")) =>
InferenceManager::ultralytics_inference_image(inference_argument, model_path, media_path).await,
(ModelType::Ultralytics, Some("mp4")) =>
InferenceManager::ultralytics_inference_video(inference_argument, model_path, media_path).await,
(ModelType::YOLOv4, Some("png") | Some("jpg") | Some("jpeg")) =>
InferenceManager::yolov4_inference_picture(inference_argument, model_path, media_path).await,
(ModelType::YOLOv4, Some("mp4")) =>
InferenceManager::yolov4_inference_video(inference_argument, model_path, media_path).await,
(ModelType::YOLOv7, Some("png") | Some("jpg") | Some("jpeg")) =>
InferenceManager::yolov7_inference_picture(inference_argument, model_path, media_path).await,
(ModelType::YOLOv7, Some("mp4")) =>
InferenceManager::yolov7_inference_video(inference_argument, model_path, media_path).await,
_ => Err(error_entry!(TaskEntry::UnSupportFileType(task_info.uuid))),
}
}
async fn notice_complete(agent: &Arc<RwLock<Agent>>, task_result: &TaskResult) -> Result<(), LogEntry> {
let config = Config::now().await;
let task_result_data = serde_json::to_vec(task_result)
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
let mut polling_times = 0_u32;
let timer = Instant::now();
let polling_interval = Duration::from_millis(config.polling_interval);
let timeout_duration = Duration::from_secs(config.control_channel_timeout);
loop {
if agent.read().await.state == AgentState::Terminate {
logging_notice!("Agent", "Terminate. Interrupt current operation");
return;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > idle_duration {
return;
if timer.elapsed() > timeout_duration {
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if timer.elapsed() > polling_times * polling_interval {
let mut agent = agent.write().await;
let data_channel_sender = agent.data_channel_sender.as_mut()
.ok_or(warning_entry!(NetworkEntry::DataChannelNotReady))?;
data_channel_sender.send(TaskResultPacket::new(task_result_data.clone())).await;
polling_times += 1;
}
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
select! {
packet = data_channel_receiver.task_result_ack_packet.recv() => {
clear_unbounded_channel(&mut data_channel_receiver.task_result_ack_packet).await;
if packet.is_some() {
return Ok(())
} else {
Err(information_entry!(NetworkEntry::ChannelClosed))?;
}
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
} else {
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?
}
}
}
async fn transfer_result(agent: &Arc<RwLock<Agent>>, task_info: &TaskInfo) -> Result<(), LogEntry> {
let file_name = task_info.media_file_name.clone();
let file_path = PathBuf::from(format!("./Result/{}", file_name));
let file_body = Self::read_file(agent, &file_path).await?;
Self::transfer_file_header(agent, &file_name, file_body.len()).await?;
Self::transfer_file_body(agent, file_body).await?;
Ok(())
}
async fn read_file(agent: &Arc<RwLock<Agent>>, file_path: &PathBuf) -> Result<Vec<Vec<u8>>, LogEntry> {
let mut sequence_number = 0_usize;
let mut buffer = vec![0; 1_048_576];
let mut packets = Vec::new();
let mut file = File::open(file_path.clone()).await
.map_err(|err| error_entry!(IOEntry::ReadFileError(file_path.display(), err)))?;
while agent.read().await.state != AgentState::Terminate {
let bytes_read = file.read(&mut buffer).await
.map_err(|err| error_entry!(IOEntry::ReadFileError(file_path.display(), err)))?;
if bytes_read == 0 {
return Ok(packets);
}
let mut data = sequence_number.to_be_bytes().to_vec();
data.extend_from_slice(&buffer[..bytes_read]);
packets.push(data);
sequence_number += 1;
}
Err(information_entry!(SystemEntry::Cancel))?
}
async fn transfer_file_header(agent: &Arc<RwLock<Agent>>, file_name: &String, packet_count: usize) -> Result<(), LogEntry> {
let config = Config::now().await;
let file_header = FileHeader::new(file_name.clone(), packet_count);
let file_header_data = serde_json::to_vec(&file_header)
.map_err(|err| error_entry!(IOEntry::SerdeSerializeError(err)))?;
let timer = Instant::now();
let mut polling_times = 0_u32;
let polling_interval = Duration::from_millis(config.polling_interval);
let timeout_duration = Duration::from_secs(config.control_channel_timeout);
while agent.read().await.state != AgentState::Terminate {
if timer.elapsed() > timeout_duration {
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
if timer.elapsed() > polling_times * polling_interval {
let mut agent = agent.write().await;
let data_channel_sender = agent.data_channel_sender.as_mut()
.ok_or(warning_entry!(NetworkEntry::DataChannelNotReady))?;
data_channel_sender.send(FileHeaderPacket::new(file_header_data.clone())).await;
polling_times += 1;
}
let mut agent = agent.write().await;
if let Some(data_channel_receiver) = agent.data_channel_receiver.as_mut() {
select! {
packet = data_channel_receiver.file_header_ack_packet.recv() => {
clear_unbounded_channel(&mut data_channel_receiver.file_header_ack_packet).await;
if packet.is_some() {
return Ok(())
} else {
Err(information_entry!(NetworkEntry::ChannelClosed))?;
}
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
} else {
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?
}
}
Err(information_entry!(SystemEntry::Cancel))
}
async fn transfer_file_body(agent: &Arc<RwLock<Agent>>, file_body: Vec<Vec<u8>>) -> Result<(), LogEntry> {
let config = Config::now().await;
let mut timer = Instant::now();
let mut require_send: Vec<usize> = (0..file_body.len()).collect();
let timeout_duration = Duration::from_secs(config.file_transfer_timeout);
while agent.read().await.state != AgentState::Terminate {
if timer.elapsed() > timeout_duration {
agent.write().await.state = AgentState::CreateDataChannel;
Err(information_entry!(NetworkEntry::DataChannelTimeout))?;
}
for chunk in &require_send {
let data = file_body.get(*chunk)
.ok_or(error_entry!(MiscEntry::MissingFileBlockError))?;
let mut agent = agent.write().await;
let data_channel_sender = agent.data_channel_sender.as_mut()
.ok_or(warning_entry!(NetworkEntry::DataChannelNotReady))?;
data_channel_sender.send(FileBodyPacket::new(data.clone())).await;
}
if !require_send.is_empty() {
let mut agent = agent.write().await;
let data_channel_sender = agent.data_channel_sender.as_mut()
.ok_or(warning_entry!(NetworkEntry::DataChannelNotReady))?;
data_channel_sender.send(FileTransferEndPacket::new()).await;
require_send = Vec::new();
timer = Instant::now();
}
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
select! {
biased;
packet = data_channel_receiver.file_transfer_result_packet.recv() => {
clear_unbounded_channel(&mut data_channel_receiver.file_transfer_result_packet).await;
let packet = packet.ok_or(information_entry!(NetworkEntry::ChannelClosed))?;
let file_transfer_result = serde_json::from_slice::<FileTransferResult>(packet.as_data_byte())
.map_err(|err| error_entry!(IOEntry::SerdeDeserializeError(err)))?;
timer = Instant::now();
match file_transfer_result.into() {
Some(missing_chunks) => require_send = missing_chunks,
None => return Ok(()),
}
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue
}
} else {
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?
}
}
Err(information_entry!(SystemEntry::Cancel))?
}
async fn idle(agent: &Arc<RwLock<Agent>>, idle_duration: Duration) -> Result<(), LogEntry> {
if let Some(data_channel_receiver) = agent.write().await.data_channel_receiver.as_mut() {
clear_unbounded_channel(&mut data_channel_receiver.alive_packet).await;
}
let config = Config::now().await;
let timer = Instant::now();
while timer.elapsed() <= idle_duration {
if agent.read().await.state == AgentState::Terminate {
Err(information_entry!(SystemEntry::Cancel))?;
}
let mut agent = agent.write().await;
if let Some(data_channel_receiver) = agent.data_channel_receiver.as_mut() {
select! {
biased;
_ = data_channel_receiver.alive_packet.recv() => clear_unbounded_channel(&mut data_channel_receiver.alive_packet).await,
_ = data_channel_receiver.alive_packet.recv() =>
clear_unbounded_channel(&mut data_channel_receiver.alive_packet).await,
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
} else {
logging_warning!("Agent", "Data Channel is not available.");
return;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
if let Some(data_channel_sender) = agent.data_channel_sender.as_mut() {
data_channel_sender.send(AliveAcknowledgePacket::new()).await;
data_channel_sender.send(AliveAckPacket::new()).await;
} else {
logging_warning!("Agent", "Data Channel is not available.");
return;
Err(warning_entry!(NetworkEntry::DataChannelNotReady))?;
}
}
Ok(())
}
#[allow(unused_assignments)]
async fn create_data_channel(agent: Arc<RwLock<Agent>>) {
async fn create_data_channel(agent: &Arc<RwLock<Agent>>) -> Result<(), LogEntry> {
{
let mut agent = agent.write().await;
clear_unbounded_channel(&mut agent.control_channel_receiver.data_channel_port_packet).await;
}
let config = Config::now().await;
let mut port: Option<u16> = None;
let timer = Instant::now();
let timeout_duration = Duration::from_secs(config.control_channel_timeout);
loop {
if agent.read().await.state == AgentState::Terminate {
logging_notice!("Agent", "Terminate. Interrupt current operation");
return;
Err(information_entry!(SystemEntry::Cancel))?;
}
if timer.elapsed() > timeout_duration {
agent.write().await.state = AgentState::Terminate;
logging_notice!("Agent", "Control Channel timout.");
return;
Err(information_entry!(NetworkEntry::ControlChannelTimeout))?;
}
{
let mut agent = agent.write().await;
@ -515,53 +701,30 @@ impl Agent {
if bytes.len() == 2 {
port = Some(u16::from_be_bytes([bytes[0], bytes[1]]))
} else {
logging_error!("Agent", "Unable to parse packet data");
logging_error!(MiscEntry::InvalidPacket);
continue;
}
} else {
agent.state = AgentState::Terminate;
logging_notice!("Agent", "Channel has been closed");
return;
Err(information_entry!(NetworkEntry::ChannelClosed))?;
}
},
}
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
}
if let Some(port) = port {
let address = format!("{}:{}", config.management_address, port);
match TcpStream::connect(&address).await {
Ok(tcp_stream) => {
let socket_stream = SocketStream::new(tcp_stream);
let (data_channel_sender, data_channel_receiver) = DataChannel::new(socket_stream);
let mut agent = agent.write().await;
agent.data_channel_sender = Some(data_channel_sender);
agent.data_channel_receiver = Some(data_channel_receiver);
logging_information!("Agent", "Data Channel created successfully");
return;
},
Err(err) => {
logging_error!("Agent", "Unable to establish connection", format!("Err: {err}"));
return;
},
}
} else {
logging_error!("Agent", "Unknown error");
return;
let tcp_stream = TcpStream::connect(&address).await
.map_err(|err| error_entry!(NetworkEntry::EstablishConnectionError(err)))?;
let socket_stream = SocketStream::new(tcp_stream);
let (data_channel_sender, data_channel_receiver) = DataChannel::new(socket_stream);
let mut agent = agent.write().await;
agent.data_channel_sender = Some(data_channel_sender);
agent.data_channel_receiver = Some(data_channel_receiver);
break;
}
}
}
pub async fn terminate(agent: Arc<RwLock<Agent>>) {
logging_information!("Agent", "Termination in progress");
let mut agent = agent.write().await;
agent.control_channel_sender.disconnect().await;
agent.control_channel_receiver.disconnect().await;
if let Some(data_channel_sender) = &mut agent.data_channel_sender {
data_channel_sender.disconnect().await;
}
if let Some(data_channel_receiver) = &mut agent.data_channel_receiver {
data_channel_receiver.disconnect().await;
}
logging_information!("Agent", "Termination complete");
logging_information!(NetworkEntry::CreateDataChannelSuccess);
Ok(())
}
}

View File

@ -1,43 +0,0 @@
use std::path::PathBuf;
use std::process::Stdio;
use tokio::process::Command as AsyncCommand;
use crate::utils::logging::*;
use crate::management::utils::bounding_box::BoundingBox;
pub struct CalculateManager;
impl CalculateManager {
pub async fn ultralytics_inference(model_path: PathBuf, image_path: PathBuf) -> Result<Vec<BoundingBox>, LogEntry> {
#[cfg(target_os = "windows")]
let python = "python";
#[cfg(target_os = "linux")]
let python = "python3";
let process = AsyncCommand::new(python)
.arg("Script/ultralytics/inference.py")
.arg(model_path)
.arg(image_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| error_entry!("Calculate Manager", "Unable to create process", format!("Err: {err}")))?;
let output = process.wait_with_output().await
.map_err(|err| error_entry!("Calculate Manager", "An error occurred during process execution", format!("Err: {err}")))?;
if output.status.success() {
let serialized_data = String::from_utf8_lossy(&output.stdout);
let bounding_boxes: Vec<BoundingBox> = serde_json::from_str(&serialized_data)
.map_err(|err| error_entry!("Calculate Manager", "Unable to deserialize data", format!("Err: {err}")))?;
Ok(bounding_boxes)
} else {
let err = String::from_utf8_lossy(&output.stderr);
Err(error_entry!("Calculate Manager", "An error occurred during process execution", format!("Err: {err}")))?
}
}
pub async fn yolov4_inference(_model_path: PathBuf, _image_path: PathBuf) -> Result<Vec<BoundingBox>, LogEntry> {
Ok(Vec::new())
}
pub async fn yolov7_inference(_model_path: PathBuf, _image_path: PathBuf) -> Result<Vec<BoundingBox>, LogEntry> {
Ok(Vec::new())
}
}

View File

@ -1,23 +1,23 @@
use tokio::fs;
use tokio::fs::File;
use crate::utils::logging::*;
use crate::utils::logging::{LogLevel, Logger};
use crate::utils::static_files::StaticFiles;
use std::path::PathBuf;
use std::process::Stdio;
use tokio::fs;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::process::Command as AsyncCommand;
use crate::utils::logging::*;
use crate::utils::static_files::StaticFiles;
use crate::utils::logging::{Logger, LogLevel};
pub struct FileManager;
impl FileManager {
pub async fn initialize() {
logging_information!("File Manager", "Initializing");
let folders = ["SavedModel", "SavedFile", "Script", "Script/ultralytics"];
logging_information!(SystemEntry::Initializing);
let folders = ["SavedModel", "SavedFile", "Result", "Script", "Script/ultralytics"];
for &folder_name in &folders {
match fs::create_dir(folder_name).await {
Ok(_) => logging_information!("File Manager", format!("Create {} folder successfully", folder_name)),
Err(err) => logging_error!("File Manager", format!("Cannot create {} folder", folder_name), format!("Err: {err}")),
let path = PathBuf::from(folder_name);
if let Err(err) = fs::create_dir(folder_name).await {
logging_error!(IOEntry::CreateDirectoryError(path.display(), err));
}
}
if let Err(entry) = Self::clone_repository().await {
@ -26,19 +26,19 @@ impl FileManager {
if let Err(entry) = Self::extract_embed_folders().await {
logging_entry!(entry);
}
logging_information!("File Manager", "Initialization completed");
logging_information!(SystemEntry::InitializeComplete);
}
pub async fn cleanup() {
logging_information!("File Manager", "Cleaning up");
let folders = ["SavedModel", "SavedFile", "Script"];
logging_information!(SystemEntry::Cleaning);
let folders = ["SavedModel", "SavedFile", "Result", "Script"];
for &folder_name in &folders {
match fs::remove_dir_all(folder_name).await {
Ok(_) => logging_information!("File Manager", format!("Delete {folder_name} folder successfully")),
Err(err) => logging_error!("File Manager", format!("Failed to delete {folder_name} folder"), format!("Err: {err}")),
let path = PathBuf::from(folder_name);
if let Err(err) = fs::remove_dir_all(folder_name).await {
logging_error!(IOEntry::DeleteDirectoryError(path.display(), err));
}
};
logging_information!("File Manager", "Cleanup completed");
logging_information!(SystemEntry::CleanComplete);
}
pub async fn extract_embed_folders() -> Result<(), LogEntry> {
@ -47,14 +47,14 @@ impl FileManager {
if let Some(first_part) = file_path.iter().next().and_then(|s| s.to_str()) {
if first_part.eq("script") {
let relative_path = file_path.strip_prefix(first_part).unwrap_or(&file_path);
let full_path = PathBuf::from("Script").join(relative_path);
let full_path = PathBuf::from(format!("Script/{}", relative_path.display()));
let file_data = &StaticFiles::get(file.as_ref())
.ok_or(error_entry!("File Manager", "Unable to read file", format!("File: {}", full_path.display())))?
.ok_or(error_entry!("Unable to read file", format!("File: {}", full_path.display())))?
.data;
let mut file = File::create(&full_path).await
.map_err(|err| error_entry!("File Manager", "Unable to create file", format!("File: {}, Err: {}", full_path.display(), err)))?;
.map_err(|err| error_entry!("Unable to create file", format!("File: {}, Err: {}", full_path.display(), err)))?;
file.write_all(file_data).await
.map_err(|err| error_entry!("File Manager", "Unable to write file", format!("File: {}, Err: {}", full_path.display(), err)))?;
.map_err(|err| error_entry!("Unable to write file", format!("File: {}, Err: {}", full_path.display(), err)))?;
}
}
}
@ -68,16 +68,18 @@ impl FileManager {
let mut cmd = AsyncCommand::new("cmd");
#[cfg(target_os = "linux")]
let mut cmd = AsyncCommand::new("sh");
let status = cmd
let mut process = cmd
.arg(if cfg!(target_os = "windows") { "/C" } else { "-c" })
.arg(format!("cd Script/ && git clone {} --depth 1 && git clone {} --depth 1", yolov4_repository, yolov7_repository))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.status()
.await
.map_err(|err| critical_entry!("File Manager", "Unable to create process", format!("Err: {err}")))?;
.spawn()
.map_err(|err| critical_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
let status = process.wait().await
.map_err(|err| error_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
if !status.success() {
Err(critical_entry!("File Manager", "An error occurred during process execution"))?
let err = format!("Process exit with code: {}", status.code().unwrap_or(-1));
Err(error_entry!(SystemEntry::ChildProcessError(err)))?
}
Ok(())
}

View File

@ -0,0 +1,98 @@
use crate::management::utils::inference_argument::InferenceArgument;
use crate::utils::logging::*;
use std::path::PathBuf;
use std::process::Stdio;
use tokio::process::Command as AsyncCommand;
pub struct InferenceManager;
impl InferenceManager {
pub async fn ultralytics_inference_image(inference_argument: InferenceArgument,
model_path: PathBuf, image_path: PathBuf) -> Result<(), LogEntry>
{
#[cfg(target_os = "windows")]
let python = "python";
#[cfg(target_os = "linux")]
let python = "python3";
let save_folder = PathBuf::from("./Result");
let mut process = AsyncCommand::new(python)
.arg("Script/ultralytics/picture_inference.py")
.arg(inference_argument.detect_mode.to_string())
.arg(model_path)
.arg(image_path)
.arg(save_folder)
.arg(inference_argument.imgsz.to_string())
.arg(inference_argument.conf.to_string())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| error_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
let status = process.wait().await
.map_err(|err| error_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
if !status.success() {
let err = format!("Process exit with code: {}", status.code().unwrap_or(-1));
Err(error_entry!(SystemEntry::ChildProcessError(err)))?
}
Ok(())
}
pub async fn ultralytics_inference_video(inference_argument: InferenceArgument,
model_path: PathBuf, video_path: PathBuf) -> Result<(), LogEntry>
{
#[cfg(target_os = "windows")]
let python = "python";
#[cfg(target_os = "linux")]
let python = "python3";
let save_folder = PathBuf::from("./Result");
let mut process = AsyncCommand::new(python)
.arg("Script/ultralytics/video_inference.py")
.arg(inference_argument.detect_mode.to_string())
.arg(model_path)
.arg(video_path)
.arg(save_folder)
.arg(inference_argument.imgsz.to_string())
.arg(inference_argument.conf.to_string())
.arg(inference_argument.batch.to_string())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| error_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
let status = process.wait().await
.map_err(|err| error_entry!(SystemEntry::ChildProcessError(err.to_string())))?;
if !status.success() {
let err = format!("Process exit with code: {}", status.code().unwrap_or(-1));
Err(error_entry!(SystemEntry::ChildProcessError(err)))?
}
Ok(())
}
#[allow(unused_variables)]
pub async fn yolov4_inference_picture(inference_argument: InferenceArgument,
model_path: PathBuf, video_path: PathBuf) -> Result<(), LogEntry>
{
Ok(())
}
#[allow(unused_variables)]
pub async fn yolov4_inference_video(inference_argument: InferenceArgument,
model_path: PathBuf, video_path: PathBuf) -> Result<(), LogEntry>
{
Ok(())
}
#[allow(unused_variables)]
pub async fn yolov7_inference_picture(inference_argument: InferenceArgument,
model_path: PathBuf, video_path: PathBuf) -> Result<(), LogEntry>
{
Ok(())
}
#[allow(unused_variables)]
pub async fn yolov7_inference_video(inference_argument: InferenceArgument,
model_path: PathBuf, video_path: PathBuf) -> Result<(), LogEntry>
{
Ok(())
}
}

View File

@ -1,17 +1,18 @@
use tokio::select;
use std::sync::Arc;
use tokio::time::sleep;
use async_ctrlc::CtrlC;
use std::time::Duration;
use lazy_static::lazy_static;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::utils::logging::*;
use crate::utils::config::Config;
use crate::management::agent::Agent;
use crate::management::monitor::Monitor;
use crate::management::file_manager::FileManager;
use crate::management::utils::agent_state::AgentState;
use crate::connection::socket::management_socket::ManagementSocket;
use crate::management::agent::Agent;
use crate::management::file_manager::FileManager;
use crate::management::monitor::Monitor;
use crate::management::utils::agent_state::AgentState;
use crate::utils::config::Config;
use crate::utils::logging::*;
use async_ctrlc::CtrlC;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::time::Duration;
use tokio::select;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tokio::time::sleep;
use Common::utils::log_entry::system::SystemEntry;
lazy_static! {
static ref MANAGEMENT: RwLock<Management> = RwLock::new(Management::new());
@ -44,19 +45,19 @@ impl Management {
tokio::spawn(async move {
Self::hot_reload().await;
});
logging_information!("Management", "Online now");
logging_information!(SystemEntry::Online);
match CtrlC::new() {
Ok(ctrlc) => ctrlc.await,
Err(err) => logging_emergency!("Management", "Unable to create instance", format!("Err: {err}")),
Err(err) => logging_emergency!("Unable to create instance", format!("Err: {err}")),
}
}
pub async fn terminate() {
logging_information!("Management", "Termination in process");
logging_information!(SystemEntry::Terminating);
Self::instance_mut().await.terminate = true;
Monitor::terminate().await;
FileManager::cleanup().await;
logging_information!("Management", "Termination complete");
logging_information!(SystemEntry::TerminateComplete);
}
pub async fn hot_reload() {
@ -64,9 +65,8 @@ impl Management {
while !Self::instance().await.terminate {
let mut management = Self::instance_mut().await;
if let Some(agent) = management.agent.clone() {
match agent.read().await.state {
AgentState::Terminate => management.agent = None,
_ => sleep(Duration::from_millis(config.internal_timestamp)).await,
if agent.read().await.state == AgentState::Terminate {
management.agent = None;
}
} else {
select! {
@ -76,12 +76,12 @@ impl Management {
let agent = Arc::new(RwLock::new(agent));
Agent::run(agent.clone()).await;
management.agent = Some(agent);
logging_information!("Management", format!("Management {management_ip} is connected"));
},
Err(entry) => logging_entry!(entry),
logging_information!(SystemEntry::ManagementConnect(management_ip));
}
Err(entry) => logging_entry!(entry)
}
},
_ = sleep(Duration::from_millis(config.internal_timestamp)) => continue,
}
_ = sleep(Duration::from_secs(config.refresh_interval)) => continue
}
}
}

View File

@ -1,7 +1,7 @@
pub mod utils;
pub mod agent;
pub mod calculate_manager;
pub mod file_manager;
pub mod inference_manager;
pub mod management;
pub use Common::management::*;

View File

@ -1,8 +1,8 @@
use std::fs;
use tokio::sync::RwLock;
use std::net::ToSocketAddrs;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::fs;
use std::net::ToSocketAddrs;
use tokio::sync::RwLock;
lazy_static! {
static ref CONFIG: RwLock<Config> = RwLock::new(Config::new());
@ -19,11 +19,11 @@ pub struct Config {
pub internal_timestamp: u64,
pub management_address: String,
pub management_port: u16,
pub refresh_interval: u64,
pub polling_interval: u64,
pub control_channel_timeout: u64,
pub data_channel_timeout: u64,
pub file_transfer_timeout: u64,
pub confidence_threshold: f64,
}
impl Config {
@ -50,9 +50,10 @@ impl Config {
Config::validate_mini_second(config.internal_timestamp)
&& Config::validate_full_address(&config.management_address, config.management_port)
&& Config::validate_second(config.control_channel_timeout)
&& Config::validate_mini_second(config.refresh_interval)
&& Config::validate_second(config.polling_interval)
&& Config::validate_second(config.data_channel_timeout)
&& Config::validate_second(config.file_transfer_timeout)
&& Config::validate_confidence(config.confidence_threshold)
}
fn validate_mini_second(second: u64) -> bool {
@ -66,8 +67,4 @@ impl Config {
fn validate_full_address(address: &str, port: u16) -> bool {
format!("{}:{}", address, port).to_socket_addrs().is_ok()
}
fn validate_confidence(confidence_threshold: f64) -> bool {
!confidence_threshold.is_nan() && confidence_threshold >= 0.0 && confidence_threshold <= 1.0
}
}

View File

@ -1,9 +1,13 @@
pub use crate::{logging_alert, logging_critical, logging_debug, logging_emergency, logging_entry, logging_error, logging_information, logging_warning};
pub use Common::utils::log_entry::io::IOEntry;
pub use Common::utils::log_entry::misc::MiscEntry;
pub use Common::utils::log_entry::network::NetworkEntry;
pub use Common::utils::log_entry::system::SystemEntry;
pub use Common::utils::log_entry::task::TaskEntry;
pub use Common::utils::logging::*;
pub use Common::{debug_entry, information_entry, notice_entry, warning_entry, error_entry, critical_entry, alert_entry, emergency_entry};
pub use crate::{logging_debug, logging_information, logging_notice, logging_warning, logging_error, logging_critical, logging_alert, logging_emergency, logging_entry};
pub use Common::{alert_entry, critical_entry, debug_entry, emergency_entry, error_entry, information_entry, warning_entry};
use lazy_static::lazy_static;
use std::collections::VecDeque;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
lazy_static! {
@ -11,14 +15,14 @@ lazy_static! {
}
pub struct Logger {
system_log: VecDeque<LogEntry>,
system_log: Vec<LogEntry>,
}
impl Logger {
fn new() -> Self {
let mut system_log = VecDeque::new();
let mut system_log = Vec::new();
let log_entry = information_entry!("Logger", "Online now");
system_log.push_back(log_entry);
system_log.push(log_entry);
Self {
system_log,
}
@ -36,101 +40,91 @@ impl Logger {
let log_entry = LogEntry::new(level, position, message, debug_info);
Self::logging_console(log_entry.clone());
let mut logger = Self::instance_mut().await;
logger.system_log.push_back(log_entry);
logger.system_log.push(log_entry);
}
pub async fn add_system_log_entry(log_entry: LogEntry) {
Self::logging_console(log_entry.clone());
let mut logger = Self::instance_mut().await;
logger.system_log.push_back(log_entry);
logger.system_log.push(log_entry);
}
pub fn logging_console(log_entry: LogEntry) {
println!("{}", log_entry.to_colored_string());
}
pub async fn get_system_logs() -> VecDeque<LogEntry> {
pub async fn get_system_logs() -> Vec<LogEntry> {
Self::instance().await.system_log.clone()
}
}
#[macro_export]
macro_rules! logging_debug {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Debug, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Debug, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Debug, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Debug, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_information {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Information, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Information, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Information, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
};
}
#[macro_export]
macro_rules! logging_notice {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Notice, $position, $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Notice, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Information, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_warning {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Warning, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Warning, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Warning, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Warning, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_error {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Error, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Error, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Error, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Error, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_critical {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Critical, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Critical, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Critical, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Critical, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_alert {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Alert, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Alert, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Alert, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Alert, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}
#[macro_export]
macro_rules! logging_emergency {
($position:expr, $message:expr) => {
Logger::add_system_log(LogLevel::Emergency, $position, $message, "").await
($message:expr) => {
Logger::add_system_log(LogLevel::Emergency, format!("{}:{}", file!(), line!()), $message, "").await
};
($position:expr, $message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Emergency, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info)).await
($message:expr, $debug_info:expr) => {
Logger::add_system_log(LogLevel::Emergency, format!("{}:{}", file!(), line!()), $message, $debug_info).await
};
}

View File

@ -1,4 +1,5 @@
pub mod config;
pub mod logging;
pub use Macro::*;
pub use Common::utils::*;

1
Build/requirements.txt Normal file
View File

@ -0,0 +1 @@
ffmpeg-python==0.2.0

1054
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[workspace]
members = ["Management", "ManagementLibrary", "Agent", "AgentLibrary", "Common"]
members = ["Management", "ManagementLibrary", "Agent", "AgentLibrary", "Common", "Macro"]
resolver = "2"
[profile.release]

View File

@ -1,16 +1,22 @@
[package]
name = "Common"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
toml = "0.8.19"
glib = "0.20.4"
chrono = "0.4.38"
colored = "2.1.0"
sysinfo = "0.31.4"
sysinfo = "0.32.0"
gstreamer = "0.23.2"
thiserror = "1.0.65"
rust-embed = "8.5.0"
lazy_static = "1.5.0"
tokio = { version = "1.40.0", features = ["full"] }
serde = { version = "1.0.210", features = ["derive"] }
uuid = { version = "1.10.0", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] }
serde_json = "1.0.132"
Macro = { path = "../Macro" }
serde = { version = "1.0.213", features = ["derive"] }
tokio = { version = "1.41.0", features = ["full", "tracing"] }
uuid = { version = "1.11.0", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] }

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct FileBodyPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct FileHeaderAckPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct FileHeaderPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithoutData;
#[derive(DefinePacketWithoutData)]
pub struct FileTransferEndPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -0,0 +1,10 @@
use crate::connection::packet::{length_to_byte, Packet, PacketType};
use crate::utils::DefinePacketWithData;
#[derive(DefinePacketWithData)]
pub struct FileTransferResultPacket {
length: Vec<u8>,
id: Vec<u8>,
data: Vec<u8>,
packet_type: PacketType,
}

View File

@ -1,4 +1,9 @@
pub mod base_packet;
pub mod file_body_packet;
pub mod file_header_ack_packet;
pub mod file_header_packet;
pub mod file_transfer_end_packet;
pub mod file_transfer_result_packet;
pub trait Packet: Send {
fn as_length_byte(&self) -> &[u8];
@ -15,26 +20,26 @@ pub trait Packet: Send {
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum PacketType {
BasePacket,
AgentInformationPacket,
AgentInformationAcknowledgePacket,
AgentInfoPacket,
AgentInfoAckPacket,
AlivePacket,
AliveAcknowledgePacket,
AliveAckPacket,
ControlPacket,
ControlAcknowledgePacket,
ControlAckPacket,
DataChannelPortPacket,
FileBodyPacket,
FileHeaderPacket,
FileHeaderAcknowledgePacket,
FileHeaderAckPacket,
FileTransferResultPacket,
FileTransferEndPacket,
PerformancePacket,
PerformanceAcknowledgePacket,
ResultPacket,
ResultAcknowledgePacket,
PerformanceAckPacket,
TaskResultPacket,
TaskResultAckPacket,
StillProcessPacket,
StillProcessAcknowledgePacket,
StillProcessAckPacket,
TaskInfoPacket,
TaskInfoAcknowledgePacket,
TaskInfoAckPacket,
}
impl PacketType {
@ -48,26 +53,26 @@ impl PacketType {
byte_array.copy_from_slice(&byte);
let id = usize::from_be_bytes(byte_array);
match id {
1 => PacketType::AgentInformationPacket,
2 => PacketType::AgentInformationAcknowledgePacket,
1 => PacketType::AgentInfoPacket,
2 => PacketType::AgentInfoAckPacket,
3 => PacketType::AlivePacket,
4 => PacketType::AliveAcknowledgePacket,
4 => PacketType::AliveAckPacket,
5 => PacketType::ControlPacket,
6 => PacketType::ControlAcknowledgePacket,
6 => PacketType::ControlAckPacket,
7 => PacketType::DataChannelPortPacket,
8 => PacketType::FileBodyPacket,
9 => PacketType::FileHeaderPacket,
10 => PacketType::FileHeaderAcknowledgePacket,
10 => PacketType::FileHeaderAckPacket,
11 => PacketType::FileTransferResultPacket,
12 => PacketType::FileTransferEndPacket,
13 => PacketType::PerformancePacket,
14 => PacketType::PerformanceAcknowledgePacket,
15 => PacketType::ResultPacket,
16 => PacketType::ResultAcknowledgePacket,
14 => PacketType::PerformanceAckPacket,
15 => PacketType::TaskResultPacket,
16 => PacketType::TaskResultAckPacket,
17 => PacketType::StillProcessPacket,
18 => PacketType::StillProcessAcknowledgePacket,
18 => PacketType::StillProcessAckPacket,
19 => PacketType::TaskInfoPacket,
20 => PacketType::TaskInfoAcknowledgePacket,
20 => PacketType::TaskInfoAckPacket,
_ => PacketType::BasePacket,
}
}

View File

@ -1,9 +1,9 @@
use crate::connection::packet::base_packet::BasePacket;
use crate::connection::packet::Packet;
use std::io;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use crate::connection::packet::Packet;
use crate::connection::packet::base_packet::BasePacket;
use tokio::net::TcpStream;
pub struct SocketStream {
read_half: ReadHalf,

View File

@ -1,12 +1,13 @@
use sysinfo::System;
use tokio::time::sleep;
use std::process::Command;
use crate::management::utils::agent_information::AgentInformation;
use crate::management::utils::performance::Performance;
use crate::utils::log_entry::system::SystemEntry;
use crate::utils::logging::*;
use lazy_static::lazy_static;
use std::process::Command;
use sysinfo::System;
use tokio::process::Command as AsyncCommand;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::utils::logging::*;
use crate::management::utils::performance::Performance;
use crate::management::utils::agent_information::AgentInformation;
use tokio::time::sleep;
lazy_static! {
static ref MONITOR: RwLock<Monitor> = RwLock::new(Monitor::new());
@ -39,7 +40,7 @@ impl Monitor {
tokio::spawn(async {
Self::update_performance().await;
});
logging_console!(information_entry!("Monitor", "Online now"));
logging_console!(information_entry!(SystemEntry::Online));
}
pub async fn terminate() {
@ -48,17 +49,31 @@ impl Monitor {
fn system_info() -> AgentInformation {
let sys = System::new_all();
let host_name = System::host_name().expect("Fail to get system information.");
let os_name = if cfg!(target_os = "windows") {
let long_os_version = System::long_os_version().expect("Fail to get system information.");
let kernel_version = System::kernel_version().expect("Fail to get system information.");
format!("{} build {}", long_os_version, kernel_version)
} else if cfg!(target_os = "linux") {
let long_os_version = System::long_os_version().expect("Fail to get system information.");
let kernel_version = System::kernel_version().expect("Fail to get system information.");
format!("{} {}", long_os_version, kernel_version)
} else {
System::long_os_version().expect("Fail to get system information.")
};
let cpu = sys.cpus().get(0).map(|cpu| cpu.brand())
.expect("Monitor: Fail to get system information.")
.expect("Fail to get system information.")
.to_string();
let gpu = Self::get_gpu_name().expect("Monitor: Fail to get system information.");
let vram = Self::get_vram_total().expect("Monitor: Fail to get system information.") as f64;
let cores = sys.physical_core_count().expect("Fail to get system information.");
let ram = sys.total_memory() as f64;
let gpu = Self::get_gpu_name().expect("Fail to get system information.");
let vram = Self::get_vram_total().expect("Fail to get system information.") as f64;
AgentInformation {
host_name: System::host_name().expect("Monitor: Fail to get system information."),
system_name: System::name().expect("Monitor: Fail to get system information."),
host_name,
os_name,
cpu,
cores: sys.physical_core_count().expect("Monitor: Fail to get system information."),
ram: sys.total_memory() as f64,
cores,
ram,
gpu,
vram,
}
@ -69,7 +84,7 @@ impl Monitor {
.arg("--query-gpu=name")
.arg("--format=csv,noheader")
.output()
.map_err(|_| "Monitor: Fail to get gpu information.".to_string())?;
.map_err(|_| "Fail to get gpu information.".to_string())?;
let gpu_name = String::from_utf8_lossy(&gpu_name.stdout).trim().to_string();
Ok(gpu_name)
}
@ -79,10 +94,10 @@ impl Monitor {
.arg("--query-gpu=memory.total")
.arg("--format=csv,noheader,nounits")
.output()
.map_err(|_| "Monitor: Fail to get gpu information.".to_string())?;
.map_err(|_| "Fail to get gpu information.".to_string())?;
let vram_total = String::from_utf8_lossy(&vram_total.stdout).trim().to_string()
.parse::<u64>()
.map_err(|_| "Monitor: Fail to parse gpu information.".to_string())?;
.map_err(|_| "Fail to parse gpu information.".to_string())?;
Ok(vram_total * 1_048_576_u64)
}
@ -92,10 +107,10 @@ impl Monitor {
.arg("--format=csv,noheader,nounits")
.output()
.await
.map_err(|_| "Monitor: Fail to get gpu information.".to_string())?;
.map_err(|_| "Fail to get gpu information.".to_string())?;
let gpu_usage = String::from_utf8_lossy(&gpu_usage.stdout).trim().to_string()
.parse::<u64>()
.map_err(|_| "Monitor: Fail to parse gpu information.".to_string())?;
.map_err(|_| "Fail to parse gpu information.".to_string())?;
Ok(gpu_usage)
}
@ -105,10 +120,10 @@ impl Monitor {
.arg("--format=csv,noheader,nounits")
.output()
.await
.map_err(|_| "Monitor: Fail to get gpu information.".to_string())?;
.map_err(|_| "Fail to get gpu information.".to_string())?;
let vram_used = String::from_utf8_lossy(&vram_used.stdout).trim().to_string()
.parse::<u64>()
.map_err(|_| "Monitor: Fail to parse gpu information.".to_string())?;
.map_err(|_| "Fail to parse gpu information.".to_string())?;
Ok(vram_used * 1_048_576_u64)
}

View File

@ -1,11 +1,11 @@
use std::fmt::Display;
use serde::{Serialize, Deserialize};
use crate::management::utils::format::format_bytes;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Serialize, Deserialize, Clone)]
pub struct AgentInformation {
pub host_name: String,
pub system_name: String,
pub os_name: String,
pub cpu: String,
pub cores: usize,
pub ram: f64,
@ -16,7 +16,7 @@ pub struct AgentInformation {
impl Display for AgentInformation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = format!("Host Name: {}, System Name: {}, CPU Model: {}, Cores: {}, RAM Total: {}, GPU Model: {}, VRAM Total: {}",
self.host_name, self.system_name, self.cpu, self.cores, format_bytes(self.ram), self.gpu, format_bytes(self.vram)
self.host_name, self.os_name, self.cpu, self.cores, format_bytes(self.ram), self.gpu, format_bytes(self.vram)
);
write!(f, "{}", str)
}

View File

@ -1,5 +1,5 @@
use std::cmp::Ordering;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
pub enum AgentState {

View File

@ -1,11 +0,0 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BoundingBox {
pub xmin: u32,
pub xmax: u32,
pub ymin: u32,
pub ymax: u32,
pub name: String,
pub confidence: f64,
}

View File

@ -2,14 +2,14 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct FileHeader {
pub filename: String,
pub file_name: String,
pub packet_count: usize,
}
impl FileHeader {
pub fn new(filename: String, packet_count: usize) -> Self {
pub fn new(file_name: String, packet_count: usize) -> Self {
Self {
filename,
file_name,
packet_count,
}
}

View File

@ -0,0 +1,35 @@
use std::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InferenceArgument {
pub model_type: ModelType,
pub detect_mode: DetectMode,
#[serde(default, skip_deserializing)]
pub cache: bool,
pub imgsz: usize,
pub batch: usize,
pub conf: f32,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub enum ModelType {
Ultralytics,
YOLOv4,
YOLOv7,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub enum DetectMode {
Predict,
Track,
}
impl Display for DetectMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DetectMode::Predict => write!(f, "predict"),
DetectMode::Track => write!(f, "track"),
}
}
}

View File

@ -1,11 +1,9 @@
pub mod agent_information;
pub mod bounding_box;
pub mod agent_state;
pub mod file_header;
pub mod file_transfer_result;
pub mod format;
pub mod model_type;
pub mod inference_argument;
pub mod performance;
pub mod prevent_reenter;
pub mod task_info;
pub mod task_result;

View File

@ -1,33 +0,0 @@
use std::str::FromStr;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub enum ModelType {
Ultralytics,
YOLOv4,
YOLOv7,
}
impl FromStr for ModelType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Ultralytics" => Ok(ModelType::Ultralytics),
"YOLOv4" => Ok(ModelType::YOLOv4),
"YOLOv7" => Ok(ModelType::YOLOv7),
_ => Err(()),
}
}
}
impl Display for ModelType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
ModelType::Ultralytics => "Ultralytics",
ModelType::YOLOv4 => "YOLOv4",
ModelType::YOLOv7 => "YOLOv7",
})
}
}

View File

@ -1,7 +1,7 @@
use std::fmt::Display;
use serde::{Serialize, Deserialize};
use crate::management::utils::format::format_bytes;
use crate::management::utils::agent_information::AgentInformation;
use crate::management::utils::format::format_bytes;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct Performance {

View File

@ -1,23 +0,0 @@
use std::sync::atomic::{AtomicBool, Ordering};
pub struct PreventReenter {
flag: &'static AtomicBool,
}
impl PreventReenter {
pub fn new(flag: &'static AtomicBool) -> Option<Self> {
if flag.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() {
Some(PreventReenter {
flag
})
} else {
None
}
}
}
impl Drop for PreventReenter {
fn drop(&mut self) {
self.flag.store(false, Ordering::Release);
}
}

View File

@ -1,22 +1,22 @@
use uuid::Uuid;
use crate::management::utils::inference_argument::InferenceArgument;
use serde::{Deserialize, Serialize};
use crate::management::utils::model_type::ModelType;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone)]
pub struct TaskInfo {
pub uuid: Uuid,
pub model_filename: String,
pub image_filename: String,
pub model_type: ModelType,
pub model_file_name: String,
pub media_file_name: String,
pub inference_argument: InferenceArgument,
}
impl TaskInfo {
pub fn new(uuid: Uuid, model_filename: String, image_filename: String, model_type: ModelType) -> Self {
pub fn new(uuid: Uuid, model_file_name: String, image_file_name: String, inference_argument: InferenceArgument) -> Self {
Self {
uuid,
model_filename,
image_filename,
model_type,
model_file_name,
media_file_name: image_file_name,
inference_argument,
}
}
}

View File

@ -1,19 +1,18 @@
use serde::{Serialize, Deserialize};
use crate::management::utils::bounding_box::BoundingBox;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct TaskResult {
pub result: Result<Vec<BoundingBox>, String>,
pub status: Result<(), String>,
}
impl TaskResult {
pub fn new(result: Result<Vec<BoundingBox>, String>) -> Self {
pub fn new(result: Result<(), String>) -> Self {
Self {
result,
status: result,
}
}
pub fn into(self) -> Result<Vec<BoundingBox>, String> {
self.result
pub fn into(self) -> Result<(), String> {
self.status
}
}

View File

@ -0,0 +1,24 @@
use glib::error::Error as GError;
use gstreamer::StateChangeError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GStreamerEntry {
#[error("GStreamer initialization failed: {0}")]
InitializeError(GError),
#[error("Failed to create GStreamer pipeline: {0}")]
CreatePipelineError(GError),
#[error("Failed to get GStreamer bus")]
GetBusError,
#[error("Failed to set pipeline status: {0}")]
PipelineSetStateError(StateChangeError),
#[error("GStreamer internal error: {0}")]
InternalError(GError),
}
impl From<GStreamerEntry> for String {
#[inline(always)]
fn from(value: GStreamerEntry) -> Self {
value.to_string()
}
}

View File

@ -0,0 +1,44 @@
use serde_json::error::Error as SerdeJsonError;
use std::io::Error as IoError;
use std::path::Display;
use thiserror::Error;
use toml::ser::Error as TomlError;
#[derive(Error, Debug)]
pub enum IOEntry<'a> {
#[error("Failed to create directory {0}: {1}")]
CreateDirectoryError(Display<'a>, IoError),
#[error("Failed to create file {0}: {1}")]
CreateFileError(Display<'a>, IoError),
#[error("Failed to delete directory {0}: {1}")]
DeleteDirectoryError(Display<'a>, IoError),
#[error("Failed to delete file {0}: {1}")]
DeleteFileError(Display<'a>, IoError),
#[error("Failed to move directory {0} to {1}: {2}")]
MoveDirectoryError(Display<'a>, Display<'a>, IoError),
#[error("Failed to move file {0} to {1}: {2}")]
MoveFileError(Display<'a>, Display<'a>, IoError),
#[error("Failed to read directory {0}: {1}")]
ReadDirectoryError(Display<'a>, IoError),
#[error("Failed to read file {0}: {1}")]
ReadFileError(Display<'a>, IoError),
#[error("Failed to write directory {0}: {1}")]
WriteDirectoryError(Display<'a>, IoError),
#[error("Failed to write file {0}: {1}")]
WriteFileError(Display<'a>, IoError),
#[error("Failed to get absolute path of file {0}: {1}")]
GetAbsolutePathError(Display<'a>, IoError),
#[error("Failed to serialize to TOML: {0}")]
TomlSerializeError(TomlError),
#[error("Failed to serialize data: {0}")]
SerdeSerializeError(SerdeJsonError),
#[error("Failed to deserialize data: {0}")]
SerdeDeserializeError(SerdeJsonError),
}
impl From<IOEntry<'_>> for String {
#[inline(always)]
fn from(value: IOEntry) -> Self {
value.to_string()
}
}

View File

@ -0,0 +1,20 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MiscEntry {
#[error("Invalid file name")]
InvalidFileNameError,
#[error("Missing file block")]
MissingFileBlockError,
#[error("Received invalid packet")]
InvalidPacket,
#[error("Wrong packet deliver order")]
WrongDeliverOrder,
}
impl From<MiscEntry> for String {
#[inline(always)]
fn from(value: MiscEntry) -> Self {
value.to_string()
}
}

View File

@ -0,0 +1,6 @@
pub mod gstreamer;
pub mod io;
pub mod misc;
pub mod network;
pub mod system;
pub mod task;

View File

@ -0,0 +1,37 @@
use std::io::Error as IOError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NetworkEntry {
#[error("Channel has been closed")]
ChannelClosed,
#[error("Control channel timeout")]
ControlChannelTimeout,
#[error("Create data channel success")]
CreateDataChannelSuccess,
#[error("Create data channel timeout")]
CreateDataChannelTimout,
#[error("Data channel timeout")]
DataChannelTimeout,
#[error("Data channel not ready")]
DataChannelNotReady,
#[error("Failed to bind port: {0}")]
BindPortError(IOError),
#[error("Failed to establish connection: {0}")]
EstablishConnectionError(IOError),
#[error("Receive unexpected packet")]
UnexpectedPacket,
#[error("Agent side disconnect")]
AgentDisconnect,
#[error("Management side disconnect")]
ManagementDisconnect,
#[error("Failed to destroy instance")]
DestroyInstanceError,
}
impl From<NetworkEntry> for String {
#[inline(always)]
fn from(value: NetworkEntry) -> Self {
value.to_string()
}
}

View File

@ -0,0 +1,53 @@
use std::io::Error as IoError;
use std::net::SocketAddr;
use thiserror::Error;
use tokio::task::JoinError;
#[derive(Error, Debug)]
pub enum SystemEntry {
#[error("Online now")]
Online,
#[error("Initializing")]
Initializing,
#[error("Initialization completed")]
InitializeComplete,
#[error("Termination in process")]
Terminating,
#[error("Termination completed")]
TerminateComplete,
#[error("Cleaning up")]
Cleaning,
#[error("Cleanup completed")]
CleanComplete,
#[error("Operation cancel")]
Cancel,
#[error("Invalid configuration")]
InvalidConfig,
#[error("Configuration not found")]
ConfigNotFound,
#[error("Web service ready")]
WebReady,
#[error("Web service panic: {0}")]
WebPanic(IoError),
#[error("Management {0} is connected")]
ManagementConnect(SocketAddr),
#[error("Agent {0} is connected")]
AgentConnect(SocketAddr),
#[error("Agent instance already exists")]
AgentExistError,
#[error("Agent instance does not exist")]
AgentDoesNotExistError,
#[error("Port pool has no available port")]
NoAvailablePort,
#[error("Child process execution error: {0}")]
ChildProcessError(String),
#[error("Task panic while execution: {0}")]
TaskPanickedError(JoinError),
}
impl From<SystemEntry> for String {
#[inline(always)]
fn from(value: SystemEntry) -> Self {
value.to_string()
}
}

View File

@ -0,0 +1,21 @@
use thiserror::Error;
use uuid::Uuid;
#[derive(Error, Debug)]
pub enum TaskEntry {
#[error("Task {0}, unsupported file type")]
UnSupportFileType(Uuid),
#[error("Task {0} cannot be assigned to any agent")]
TaskAssignError(Uuid),
#[error("Task {0} does not exist")]
TaskDoesNotExist(Uuid),
#[error("Error occur while agent processing task: {0}")]
AgentProcessingError(String),
}
impl From<TaskEntry> for String {
#[inline(always)]
fn from(value: TaskEntry) -> Self {
value.to_string()
}
}

View File

@ -1,14 +1,13 @@
use chrono::{DateTime, Local};
use colored::*;
use std::fmt::Display;
use chrono::{DateTime, Local};
pub use crate::{debug_entry, information_entry, notice_entry, warning_entry, error_entry, critical_entry, alert_entry, emergency_entry, logging_console};
pub use crate::{alert_entry, critical_entry, debug_entry, emergency_entry, error_entry, information_entry, logging_console, warning_entry};
#[derive(Copy, Clone)]
pub enum LogLevel {
Debug,
Information,
Notice,
Warning,
Error,
Critical,
@ -17,29 +16,27 @@ pub enum LogLevel {
}
impl LogLevel {
pub fn to_plain_string(&self) -> String {
pub fn to_plain_string(self) -> String {
match self {
LogLevel::Debug => "Debug ".to_string(),
LogLevel::Information => "Information".to_string(),
LogLevel::Notice => "Notice ".to_string(),
LogLevel::Warning => "Warning ".to_string(),
LogLevel::Error => "Error ".to_string(),
LogLevel::Critical => "Critical ".to_string(),
LogLevel::Alert => "Alert ".to_string(),
LogLevel::Emergency => "Emergency ".to_string(),
LogLevel::Debug => "[Debug] ".to_string(),
LogLevel::Information => "[Information]".to_string(),
LogLevel::Warning => "[Warning] ".to_string(),
LogLevel::Error => "[Error] ".to_string(),
LogLevel::Critical => "[Critical] ".to_string(),
LogLevel::Alert => "[Alert] ".to_string(),
LogLevel::Emergency => "[Emergency] ".to_string(),
}
}
pub fn to_colored_string(&self) -> ColoredString {
pub fn to_colored_string(self) -> ColoredString {
match self {
LogLevel::Debug => "Debug ".to_string().bright_black(),
LogLevel::Information => "Information".to_string().bright_blue(),
LogLevel::Notice => "Notice ".to_string().bright_green(),
LogLevel::Warning => "Warning ".to_string().yellow(),
LogLevel::Error => "Error ".to_string().bright_red(),
LogLevel::Critical => "Critical ".to_string().bright_yellow(),
LogLevel::Alert => "Alert ".to_string().red(),
LogLevel::Emergency => "Emergency ".to_string().magenta(),
LogLevel::Debug => "[Debug] ".to_string().bright_black(),
LogLevel::Information => "[Information]".to_string().bright_blue(),
LogLevel::Warning => "[Warning] ".to_string().yellow(),
LogLevel::Error => "[Error] ".to_string().bright_red(),
LogLevel::Critical => "[Critical] ".to_string().bright_yellow(),
LogLevel::Alert => "[Alert] ".to_string().red(),
LogLevel::Emergency => "[Emergency] ".to_string().magenta(),
}
}
}
@ -73,123 +70,106 @@ impl LogEntry {
}
impl LogEntry {
pub fn to_plain_string(&self) -> String {
pub fn to_plain_string(self) -> String {
let level = self.level.to_plain_string();
let timestramp = self.timestamp.format("%Y/%m/%d %H:%M:%S").to_string();
let timestamp = self.timestamp.format("%Y/%m/%d %H:%M:%S").to_string();
let position = self.position.clone();
let message = self.message.clone();
let str = if self.debug_info.is_empty() {
format!("[{}] {} {}: {}", level, timestramp, position, message)
format!("{} {} {}: {}", level, timestamp, position, message)
} else {
let debug_info = self.debug_info.bright_black();
format!("[{}] {} {}: {}\n{}", level, timestramp, position, message, debug_info)
format!("{} {} {}: {}\n{}", level, timestamp, position, message, debug_info)
};
str
}
pub fn to_colored_string(&self) -> String {
pub fn to_colored_string(self) -> String {
let level = self.level.to_colored_string();
let timestramp = self.timestamp.format("%Y/%m/%d %H:%M:%S").to_string();
let timestamp = self.timestamp.format("%Y/%m/%d %H:%M:%S").to_string();
let position = self.position.cyan();
let message = self.message.white();
let message = self.message;
let str = if self.debug_info.is_empty() {
format!("[{}] {} {}: {}", level, timestramp, position, message)
format!("{} {} {}: {}", level, timestamp, position, message)
} else {
let debug_info = self.debug_info.bright_black();
format!("[{}] {} {}: {}\n{}", level, timestramp, position, message, debug_info)
format!("{} {} {}: {}\n{}", level, timestamp, position, message, debug_info)
};
str
}
}
impl Display for LogEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = self.to_plain_string();
write!(f, "{}", str)
}
}
pub fn logging_console(log_entry: LogEntry) {
println!("{}", log_entry.to_colored_string());
}
#[macro_export]
macro_rules! debug_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Debug, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Debug, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Debug, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Debug, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! information_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Information, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Information, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Information, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
};
}
#[macro_export]
macro_rules! notice_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Notice, $position, $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Notice, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Information, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! warning_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Warning, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Warning, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Warning, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Warning, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! error_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Error, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Error, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Error, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Error, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! critical_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Critical, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Critical, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Critical, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Critical, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! alert_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Alert, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Alert, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Alert, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Alert, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}
#[macro_export]
macro_rules! emergency_entry {
($position:expr, $message:expr) => {
LogEntry::new(LogLevel::Emergency, $position, $message, "")
($message:expr) => {
LogEntry::new(LogLevel::Emergency, format!("{}:{}", file!(), line!()), $message, "")
};
($position:expr, $message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Emergency, $position, $message, format!("{}:{} {}", file!(), line!(), $debug_info))
($message:expr, $debug_info:expr) => {
LogEntry::new(LogLevel::Emergency, format!("{}:{}", file!(), line!()), $message, $debug_info)
};
}

View File

@ -1,8 +1,10 @@
pub mod log_entry;
pub mod logging;
pub mod static_files;
use tokio::sync::mpsc;
use crate::connection::packet::base_packet::BasePacket;
use tokio::sync::mpsc;
pub use Macro::*;
#[inline(always)]
pub async fn clear_unbounded_channel(rx: &mut mpsc::UnboundedReceiver<BasePacket>) {

View File

@ -1,108 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Config</title>
<link rel="icon" href="/misc/config.ico" type="image/x-icon"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="/javascript/config.js"></script>
<script type="text/javascript">window.onload = loadCurrentConfig;</script>
</head>
<body>
<div class="container mt-5">
<h2>Config</h2>
<form id="config-form" class="mt-4">
<div class="form-group">
<label for="internal_timestamp">Internal Timestamp (ms):</label>
<input type="number" class="form-control" id="internal_timestamp" name="internal_timestamp" min="1" max="60000" required>
</div>
<div class="form-group">
<label for="agent_listen_port">Agent Listen Port :</label>
<input type="number" class="form-control" id="agent_listen_port" name="agent_listen_port" min="1" max="65535" required>
</div>
<div class="form-group">
<label for="http_server_bind_port">HTTP Server Bind Port :</label>
<input type="number" class="form-control" id="http_server_bind_port" name="http_server_bind_port" min="1" max="65535" required>
</div>
<div class="form-group">
<label for="dedicated_port_range_start">Dedicated Port Range :</label>
<div class="d-flex align-items-center">
<input type="number" class="form-control" id="dedicated_port_range_start" name="dedicated_port_range_start" min="1" max="65535" required>
<span class="mx-2">~</span>
<label for="dedicated_port_range_end"></label>
<input type="number" class="form-control" id="dedicated_port_range_end" name="dedicated_port_range_end" min="1" max="65535" required>
</div>
</div>
<div class="form-group">
<label for="refresh_interval">Refresh Interval (s):</label>
<input type="number" class="form-control" id="refresh_interval" name="refresh_interval" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="polling_interval">Polling Interval (ms):</label>
<input type="number" class="form-control" id="polling_interval" name="polling_interval" min="1" max="60000" required>
</div>
<div class="form-group">
<label for="bind_retry_duration">Bind Retry Duration (s):</label>
<input type="number" class="form-control" id="bind_retry_duration" name="bind_retry_duration" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="agent_idle_duration">Agent Idle Duration (s):</label>
<input type="number" class="form-control" id="agent_idle_duration" name="agent_idle_duration" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="control_channel_timeout">Control Channel timeout (s):</label>
<input type="number" class="form-control" id="control_channel_timeout" name="control_channel_timeout" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="data_channel_timeout">Data Channel timeout (s):</label>
<input type="number" class="form-control" id="data_channel_timeout" name="data_channel_timeout" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="file_transfer_timeout">File Transfer timeout (s):</label>
<input type="number" class="form-control" id="file_transfer_timeout" name="file_transfer_timeout" min="1" max="3600" required>
</div>
<div class="form-group">
<label for="font_path">Font Path :</label>
<input type="text" class="form-control" id="font_path" name="font_path" required>
</div>
<div class="form-group">
<label for="font_size">Font Size (pt):</label>
<input type="number" class="form-control" id="font_size" name="font_size" min="1" step="0.1" required>
</div>
<div class="form-group">
<label for="border_width">Border Width (px):</label>
<input type="number" class="form-control" id="border_width" name="border_width" min="1" required>
</div>
<div class="form-group">
<label>Border Color:</label>
<div class="d-flex align-items-center">
<label class="mx-2" for="border_color_r">R</label>
<input type="number" class="form-control mx-1" id="border_color_r" min="0" max="255" required>
<label class="mx-2" for="border_color_g">G</label>
<input type="number" class="form-control mx-1" id="border_color_g" min="0" max="255" required>
<label class="mx-2" for="border_color_b">B</label>
<input type="number" class="form-control mx-1" id="border_color_b" min="0" max="255" required>
</div>
</div>
<div class="form-group">
<label>Text Color:</label>
<div class="d-flex align-items-center">
<label class="mx-2" for="text_color_r">R</label>
<input type="number" class="form-control mx-1" id="text_color_r" min="0" max="255" required>
<label class="mx-2" for="text_color_g">G</label>
<input type="number" class="form-control mx-1" id="text_color_g" min="0" max="255" required>
<label class="mx-2" for="text_color_b">B</label>
<input type="number" class="form-control mx-1" id="text_color_b" min="0" max="255" required>
</div>
</div>
<button type="button" class="btn btn-primary" onclick="submitForm()">Submit</button>
</form>
<p class="mt-4 text-muted">Some settings will take effect after a restart system.</p>
</div>
</body>
</html>

View File

@ -1,119 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Inference</title>
<link href="/misc/inference.ico" rel="icon" type="image/x-icon"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="/javascript/inference.js"></script>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<form id="uploadForm">
<h1>Inference</h1>
<p>Select the format of the model you want to upload:</p>
<div class="form-group">
<label>Automatic identification</label>
<div class="col-2 form-check">
<div class="row">
<input class="form-check-input" id="defaultRadio" name="modelType"
type="radio" value="Ultralytics" checked>
<label class="form-check-label" for="defaultRadio">Default</label>
</div>
</div>
</div>
<div class="form-group">
<label>YOLO Model</label>
<div class="row">
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV3Radio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloV3Radio">YOLOv3</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV4Radio" name="modelType"
type="radio" value="YOLOv4">
<label class="form-check-label" for="yoloV4Radio">YOLOv4</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV5Radio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloV5Radio">YOLOv5</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV6Radio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloV6Radio">YOLOv6</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV7Radio" name="modelType"
type="radio" value="YOLOv7">
<label class="form-check-label" for="yoloV7Radio">YOLOv7</label>
</div>
</div>
<div class="row">
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV8Radio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloV8Radio">YOLOv8</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloV9Radio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloV9Radio">YOLOv9</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloNASRadio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloNASRadio">YOLO NAS</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="yoloWorldRadio" name="modelType"
type="radio" value="Ultralytics">
<label class="form-check-label" for="yoloWorldRadio">YOLO World</label>
</div>
</div>
</div>
<div class="form-group">
<label>SAM Model</label>
<div class="row">
<div class="col-2 form-check">
<input class="form-check-input" id="samRadio" name="modelType"
type="radio" value="SAM">
<label class="form-check-label" for="samRadio">SAM</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="mobileSAMRadio" name="modelType"
type="radio" value="Mobile SAM">
<label class="form-check-label" for="mobileSAMRadio">Mobile SAM</label>
</div>
<div class="col-2 form-check">
<input class="form-check-input" id="fastSAMRadio" name="modelType"
type="radio" value="Fast SAM">
<label class="form-check-label" for="fastSAMRadio">Fast SAM</label>
</div>
</div>
</div>
<div class="form-group">
<div class="form-group">
<label for="modelFile">Model File</label>
<input class="form-control-file" id="modelFile" name="modelFile" required type="file">
</div>
</div>
<div class="form-group">
<label for="inferenceFile">Inference file</label>
<input class="form-control-file" id="inferenceFile" name="inferenceFile" required type="file">
</div>
<button class="btn btn-primary" onclick="submitForm()" type="button">Upload</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Log Viewer</title>
<link rel="icon" href="/misc/log.ico" type="image/x-icon"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="/javascript/log.js"></script>
<script type="text/javascript">window.onload = loadSystemLog;</script>
</head>
<body>
<div class="container mt-4">
<h1 class="text-center">Log Viewer</h1>
<div class="row mb-3">
<div class="col-auto">
<button class="btn btn-primary" onclick="loadSystemLog()">Load System Log</button>
</div>
<div class="col">
<div class="input-group">
<input type="text" id="agent-id-input" class="form-control" placeholder="Agent ID">
<div class="input-group-append">
<button class="btn btn-secondary" onclick="loadAgentLog()">Load Agent Log</button>
</div>
</div>
</div>
</div>
<div id="log-container" class="border rounded p-3" style="height: 725px; overflow-y: auto;"></div>
</div>
</body>
</html>

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Test</title>
<script>
document.addEventListener("DOMContentLoaded", function() {
const ws = new WebSocket('ws://127.0.0.1:8080/monitor/websocket/performance/system');
ws.onopen = function() {
console.log('WebSocket Connection Opened');
};
ws.onmessage = function(event) {
console.log('Message from server ', event.data);
const messages = document.getElementById('messages');
const message = document.createElement('li');
message.textContent = event.data;
messages.appendChild(message);
};
ws.onerror = function(error) {
console.log('WebSocket Error: ' + error);
};
ws.onclose = function(event) {
console.log('WebSocket Connection Closed', event);
};
});
</script>
</head>
<body>
<h1>WebSocket Client Test</h1>
<ul id="messages"></ul>
</body>
</html>

View File

@ -1,90 +0,0 @@
async function loadCurrentConfig() {
try {
let response = await fetch('/config/get');
if(response.ok) {
let config = await response.json();
document.getElementById('internal_timestamp').value = config.internal_timestamp;
document.getElementById('agent_listen_port').value = config.agent_listen_port;
document.getElementById('http_server_bind_port').value = config.http_server_bind_port;
document.getElementById('dedicated_port_range_start').value = config.dedicated_port_range[0];
document.getElementById('dedicated_port_range_end').value = config.dedicated_port_range[1];
document.getElementById('refresh_interval').value = config.refresh_interval;
document.getElementById('polling_interval').value = config.polling_interval;
document.getElementById('bind_retry_duration').value = config.bind_retry_duration;
document.getElementById('agent_idle_duration').value = config.agent_idle_duration;
document.getElementById('control_channel_timeout').value = config.control_channel_timeout;
document.getElementById('data_channel_timeout').value = config.data_channel_timeout;
document.getElementById('file_transfer_timeout').value = config.file_transfer_timeout;
document.getElementById('font_path').value = config.font_path;
document.getElementById('font_size').value = config.font_size;
document.getElementById('border_width').value = config.border_width;
document.getElementById('border_color_r').value = config.border_color[0];
document.getElementById('border_color_g').value = config.border_color[1];
document.getElementById('border_color_b').value = config.border_color[2];
document.getElementById('text_color_r').value = config.text_color[0];
document.getElementById('text_color_g').value = config.text_color[1];
document.getElementById('text_color_b').value = config.text_color[2];
} else {
console.error('Failed to fetch current config:', response.statusText);
}
} catch (error) {
console.error('Error:', error);
}
}
async function submitForm() {
const getRange = (idPrefix) => {
return [
parseInt(document.getElementById(idPrefix + '_start').value),
parseInt(document.getElementById(idPrefix + '_end').value)
];
}
const getRGB = (idPrefix) => {
return [
parseInt(document.getElementById(idPrefix + '_r').value),
parseInt(document.getElementById(idPrefix + '_g').value),
parseInt(document.getElementById(idPrefix + '_b').value),
];
};
let config = {
internal_timestamp: parseInt(document.getElementById('internal_timestamp').value),
agent_listen_port: parseInt(document.getElementById('agent_listen_port').value),
http_server_bind_port: parseInt(document.getElementById('http_server_bind_port').value),
dedicated_port_range: getRange('dedicated_port_range'),
refresh_interval: parseInt(document.getElementById('refresh_interval').value),
polling_interval: parseInt(document.getElementById('polling_interval').value),
bind_retry_duration: parseInt(document.getElementById('bind_retry_duration').value),
agent_idle_duration: parseInt(document.getElementById('agent_idle_duration').value),
control_channel_timeout: parseInt(document.getElementById('control_channel_timeout').value),
data_channel_timeout: parseInt(document.getElementById('data_channel_timeout').value),
file_transfer_timeout: parseInt(document.getElementById('file_transfer_timeout').value),
font_path: document.getElementById('font_path').value,
font_size: parseFloat(document.getElementById('font_size').value),
border_width: parseInt(document.getElementById('border_width').value),
border_color: getRGB('border_color'),
text_color: getRGB('text_color')
};
try {
let response = await fetch('/config/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
});
let data = await response.text();
if (response.ok) {
alert('Config updated successfully.')
await loadCurrentConfig();
} else {
alert('Failed to update config: ' + data);
}
} catch (error) {
console.error('Error submitting form:', error);
alert('Error: ' + error);
}
}

View File

@ -1,27 +0,0 @@
function submitForm() {
const originalFormData = new FormData(document.getElementById('uploadForm'));
const filteredFormData = new FormData();
const modelTypeValue = document.querySelector('input[name="modelType"]:checked').value;
filteredFormData.append('modelType', modelTypeValue);
for (let [key, value] of originalFormData.entries()) {
if (value && value.name) {
filteredFormData.append(key, value, value.name);
}
}
fetch('/inference/save_file', {
method: 'POST',
body: filteredFormData
})
.then(response => {
if (response.ok) {
alert('File uploaded successfully!');
} else {
response.text().then(errorMessage => {
alert('Upload failed: ' + errorMessage);
});
}
})
.catch(error => {
alert('An error occurred: ' + error.message);
});
}

View File

@ -1,75 +0,0 @@
let lastLogType = 'system';
let lastUpdate = new Date();
function formatDate(date) {
function pad(number) {
if (number < 10)
return '0' + number;
return number;
}
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + '-' + pad(date.getHours()) + '-' + pad(date.getMinutes()) + '-' + pad(date.getSeconds());
}
function loadSystemLog() {
lastLogType = 'system';
lastUpdate = new Date();
fetch('/log/system_log')
.then(response => {
if (!response.ok)
throw new Error('Network response was not ok.');
return response.text();
})
.then(data => {
document.getElementById('log-container').innerHTML = data;
})
.catch(_ => {
document.getElementById('log-container').textContent = 'Error loading system log.';
});
}
function loadAgentLog() {
const agentId = document.getElementById('agent-id-input').value;
if (!agentId) {
alert("Please enter a Agent ID.");
return;
}
lastLogType = agentId;
lastUpdate = new Date();
fetch(`/log/${agentId}`)
.then(response => {
if (!response.ok)
throw new Error('Network response was not ok.');
return response.text();
})
.then(data => {
document.getElementById('log-container').innerHTML = data;
})
.catch(error => {
console.error('Fetch error:', error);
document.getElementById('log-container').textContent = `Error loading agent ${agentId} log.`;
});
}
function updateLog() {
const since = formatDate(lastUpdate);
let updatePath;
if (lastLogType === 'system')
updatePath = `/log/system_log/since/${since}`;
else
updatePath = `/log/${lastLogType}/since/${since}`;
fetch(updatePath)
.then(response => {
if (!response.ok)
throw new Error('Network response was not ok.');
return response.text();
})
.then(data => {
if (data) {
document.getElementById('log-container').innerHTML += data;
lastUpdate = new Date();
}
})
.catch(error => console.error('Fetch error:', error));
}
setInterval(updateLog, 10000);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,57 +0,0 @@
import sys
import json
from ultralytics import YOLO
class BoundingBox:
def __init__(self, box: list, name: str, confidence: float):
self.xmin, self.ymin, self.xmax, self.ymax = box
self.xmin = int(self.xmin)
self.xmax = int(self.xmax)
self.ymin = int(self.ymin)
self.ymax = int(self.ymax)
self.name = name
self.confidence = confidence
def to_dict(self):
return {
"xmin": self.xmin,
"xmax": self.xmax,
"ymin": self.ymin,
"ymax": self.ymax,
"name": self.name,
"confidence": self.confidence
}
def panic(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 3:
panic("Unexpected argument.")
model_path: str = sys.argv[1]
media_path: str = sys.argv[2]
try:
model: YOLO = YOLO(model_path)
try:
result: list = model.predict(media_path, verbose=False)
bounding_boxs: list = []
boxes: list = result[0].boxes.xyxy.tolist()
names: list = result[0].names
classes: list = result[0].boxes.cls.tolist()
confidences: list = result[0].boxes.conf.tolist()
for box, cls, conf in zip(boxes, classes, confidences):
confidence: float = conf
name: str = names[int(cls)]
bounding_boxs.append(BoundingBox(box, name, confidence).to_dict())
print(json.dumps(bounding_boxs))
except AttributeError as err:
panic("Unable to inference media.")
except IndexError as err:
panic("Unknown error.")
except FileNotFoundError as err:
panic("Model does not exist.")
except ValueError as err:
panic("Unable to parse model file.")
except ImportError as err:
panic("Model file is not support.")

View File

@ -0,0 +1,91 @@
import argparse
import logging
import sys
from pathlib import Path
from ultralytics.models.yolo.detect import DetectionPredictor
from ultralytics.utils import LOGGER
from ultralytics.utils import callbacks
class PictureInference:
def __init__(self, model_path: Path, picture_path: Path, save_path: Path, **kwargs):
self.model_path = model_path
self.picture_path = picture_path
self.save_path = save_path
self.args = kwargs
if not kwargs.get("verbose", False):
LOGGER.setLevel(logging.NOTSET)
def yolo_predict(self):
"""
Perform YOLO prediction on the input picture and save the results.
"""
try:
default_args = {
'conf': 0.25,
'imgsz': 640,
'mode': 'predict',
'model': self.model_path,
'save': True,
'task': 'detect',
}
args = {**default_args, **self.args}
callback = callbacks.get_default_callbacks()
predictor = DetectionPredictor(overrides=args, _callbacks=callback)
predictor.setup_model(model=self.model_path)
predictor.save_dir = self.save_path
predictor.predict_cli(source=self.picture_path)
except Exception as e:
self.panic(f"Error during YOLO prediction: {e}")
@staticmethod
def panic(*args, **kwargs):
"""
Print an error message to stderr and exit the program.
:param args: Arguments to print.
:param kwargs: Keyword arguments to print.
"""
print(*args, file=sys.stderr, **kwargs)
sys.exit(1)
def inference(self):
"""
Inference the picture by performing YOLO tracking/prediction and handling codec transformations.
"""
try:
picture_filename = self.picture_path.stem
picture_suffix = self.picture_path.suffix.lower().replace(".", "")
self.yolo_predict()
predicted_picture = self.save_path / f"{picture_filename}.{picture_suffix}"
if not predicted_picture.exists():
self.panic("Unexpected error: Predicted picture does not exist.")
except Exception as e:
self.panic(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description='picture Inference Script',
usage='%(prog)s mode model picture save imgsz conf verbose',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('model_path', type=Path, help='Path to the model')
parser.add_argument('picture_path', type=Path, help='Path to the input picture')
parser.add_argument('save_path', type=Path, help='Path to save the output')
parser.add_argument('imgsz', type=int, help='Image size for processing')
parser.add_argument('conf', type=float, help='Confidence threshold')
parser.add_argument('--verbose', action='store_true', help='Enable verbose logging')
args = parser.parse_args()
model_path = args.model_path
picture_path = args.picture_path
save_path = args.save_path
other_args = {'imgsz': args.imgsz, 'conf': args.conf, 'verbose': args.verbose}
picture_inference = PictureInference(model_path, picture_path, save_path, **other_args)
picture_inference.inference()
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)

View File

@ -0,0 +1,189 @@
import argparse
import logging
import sys
from pathlib import Path
import ffmpeg
from ultralytics.models.yolo.detect import DetectionPredictor
from ultralytics.trackers import register_tracker
from ultralytics.utils import LOGGER
from ultralytics.utils import callbacks, MACOS, WINDOWS
class VideoInference:
def __init__(self, mode: str, model_path: Path, video_path: Path, save_path: Path, **kwargs):
self.mode = mode
self.model_path = model_path
self.video_path = video_path
self.save_path = save_path
self.args = kwargs
if not kwargs.get("verbose", False):
LOGGER.setLevel(logging.NOTSET)
def get_codec_name(self, video_path: Path) -> str:
"""
Retrieve the codec name of a video using ffmpeg.
:param video_path: Path to the video file.
:return: Codec name as a string.
"""
try:
probe = ffmpeg.probe(str(video_path))
streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'video']
if not streams:
self.panic(f"No video streams found in {video_path}")
codec_name = streams[0]['codec_name']
return codec_name
except Exception as e:
self.panic(f"Error getting codec name for {video_path}: {e}")
def yolo_predict(self):
"""
Perform YOLO prediction on the input video and save the results.
"""
try:
default_args = {
'batch': 16,
'conf': 0.25,
'imgsz': 640,
'mode': 'predict',
'model': self.model_path,
'save': True,
'task': 'detect',
}
args = {**default_args, **self.args}
callback = callbacks.get_default_callbacks()
predictor = DetectionPredictor(overrides=args, _callbacks=callback)
predictor.setup_model(model=self.model_path)
predictor.save_dir = self.save_path
predictor.predict_cli(source=self.video_path)
except Exception as e:
self.panic(f"Error during YOLO prediction: {e}")
def yolo_track(self):
"""
Perform YOLO tracking on the input video and save the results.
"""
try:
default_args = {
'batch': 1,
'conf': 0.1,
'imgsz': 640,
'mode': 'track',
'model': self.model_path,
'save': True,
'task': 'detect',
}
args = {**default_args, **self.args}
callback = callbacks.get_default_callbacks()
predictor = DetectionPredictor(overrides=args, _callbacks=callback)
register_tracker(predictor, False)
predictor.setup_model(model=self.model_path)
predictor.save_dir = self.save_path
predictor.predict_cli(source=self.video_path)
except Exception as e:
self.panic(f"Error during YOLO tracking: {e}")
@staticmethod
def platform_specific_format() -> str:
"""
Determine the platform-specific video format.
:return: File extension as a string.
"""
return "mp4" if MACOS else "avi" if WINDOWS else "avi"
def transform(self, input_video: Path, temp_video: Path, video_codec: str):
"""
Convert a video to the target format using ffmpeg-python.
:param input_video: Path to the input video file to be converted.
:param temp_video: Path to save the temporary converted video file.
:param video_codec: Target video codec name.
"""
try:
verbose: bool = not self.args.get("verbose", False)
(
ffmpeg
.input(str(input_video))
.output(str(temp_video), vcodec=video_codec, **{'strict': '-2'})
.overwrite_output()
.run(quiet=verbose)
)
except ffmpeg.Error as e:
self.panic(f"Error transforming video {input_video} to {temp_video}: {e}")
@staticmethod
def panic(*args, **kwargs):
"""
Print an error message to stderr and exit the program.
:param args: Arguments to print.
:param kwargs: Keyword arguments to print.
"""
print(*args, file=sys.stderr, **kwargs)
sys.exit(1)
def inference(self):
"""
Inference the video by performing YOLO tracking/prediction and handling codec transformations.
"""
try:
video_filename = self.video_path.stem
video_suffix = self.video_path.suffix.lower().replace(".", "")
video_codec = self.get_codec_name(self.video_path)
if self.mode == "predict":
self.yolo_predict()
elif mode == "track":
self.yolo_track()
else:
self.panic("Unexpected error: Invalid detect mode.")
platform_extension = self.platform_specific_format()
predicted_video = self.save_path / f"{video_filename}.{platform_extension}"
if not predicted_video.exists():
self.panic("Unexpected error: Predicted video does not exist.")
if not MACOS:
temp_output_video = self.save_path / f"{video_filename}_temp.{video_suffix}"
self.transform(predicted_video, temp_output_video, video_codec)
predicted_video.unlink()
final_output_video = self.save_path / f"{video_filename}.{video_suffix}"
temp_output_video.rename(final_output_video)
else:
final_output_video = self.save_path / f"{video_filename}.{video_suffix}"
predicted_video.rename(final_output_video)
except Exception as e:
self.panic(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description='Video Inference Script',
usage='%(prog)s mode model video save imgsz conf batch verbose',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('mode', type=str, choices=['predict', 'track'],
help="Mode of operation: 'predict' or 'track'")
parser.add_argument('model_path', type=Path, help='Path to the model')
parser.add_argument('video_path', type=Path, help='Path to the input video')
parser.add_argument('save_path', type=Path, help='Path to save the output')
parser.add_argument('imgsz', type=int, help='Image size for processing')
parser.add_argument('conf', type=float, help='Confidence threshold')
parser.add_argument('batch', type=int, help='Batch size')
parser.add_argument('--verbose', action='store_true', help='Enable verbose logging')
args = parser.parse_args()
mode = args.mode
model_path = args.model_path
video_path = args.video_path
save_path = args.save_path
other_args = {'imgsz': args.imgsz, 'conf': args.conf, "batch": args.batch, 'verbose': args.verbose}
video_inference = VideoInference(mode, model_path, video_path, save_path, **other_args)
video_inference.inference()
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)

View File

@ -0,0 +1,13 @@
{
"files": {
"main.css": "/static/css/main.ed8a56b7.css",
"main.js": "/static/js/main.01479aeb.js",
"index.html": "/index.html",
"main.ed8a56b7.css.map": "/static/css/main.ed8a56b7.css.map",
"main.01479aeb.js.map": "/static/js/main.01479aeb.js.map"
},
"entrypoints": [
"static/css/main.ed8a56b7.css",
"static/js/main.01479aeb.js"
]
}

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="manifest" href="/manifest.json"/><title>VisioGrid</title><script defer="defer" src="/static/js/main.01479aeb.js"></script><link href="/static/css/main.ed8a56b7.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View File

@ -0,0 +1,15 @@
{
"short_name": "VisioGrid",
"name": "VisioGrid",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,2 @@
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.app{display:flex}.main-content{padding:20px;transition:margin-left .3s ease;width:100%}.main-content.sidebar-open{margin-left:200px}.main-content.sidebar-closed{margin-left:60px}@media (max-width:768px){.main-content.sidebar-closed,.main-content.sidebar-open{margin-left:60px}}.agents-list{padding:20px}.agent-item{background-color:#f9f9f9;border:1px solid #ccc;border-radius:8px;margin-bottom:15px;padding:15px;transition:background-color .3s ease}.agent-item:hover{background-color:#e6e6e6}.agent-link{color:#333;text-decoration:none}.agent-link h2{font-size:20px;margin:0}.agent-link p{margin:5px 0}.dashboard{background-color:#f5f5f5;min-height:80vh;padding:20px}.system-info{background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;margin-bottom:20px;padding:20px}.system-info ul{list-style:none;padding:0}.system-info li{font-size:18px;margin-bottom:10px}.system-info li strong{color:#34495e}.system-info-h1{margin-top:0}.chart-container{display:flex;flex-wrap:wrap;gap:40px;justify-content:center}.chart{background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:20px;width:310px}.chart h2{color:#2c3e50;margin-bottom:10px;text-align:center}@media (max-width:768px){.chart-container{align-items:center;flex-direction:column}.chart{width:90%}}.log-container{background-color:#fff;border-top:none;box-sizing:border-box;display:flex;flex-direction:column;margin-top:0;padding:20px;width:100%}.log-search-input{border:1px solid #ccc;border-radius:5px;box-sizing:border-box;font-size:16px;margin-bottom:20px;max-width:300px;padding:10px 15px;width:50%}.log-scroll{background-color:#f9f9f9;border:1px solid #ddd;border-radius:5px;box-sizing:border-box;flex:1 1;overflow:hidden;width:100%}.log-item{align-items:center;border-bottom:1px solid #eee;display:flex;font-family:monospace;min-width:0;overflow:hidden;padding:5px 10px;width:98.5%}.log-content{flex:1 1;min-width:0;overflow-wrap:break-word;white-space:pre-wrap;word-break:break-word}.latest-log{background-color:#e6f7ff}.log-loader{color:#666;padding:20px;text-align:center}.no-logs{color:#555;font-size:16px;text-align:center}.log-item:hover{background-color:#f1f1f1}@media (max-width:600px){.log-search-input{max-width:none;width:100%}}.config-page{background-color:#f9f9f9;min-height:100vh;padding-top:30px}.config-page .container{max-width:800px}.config-page h2{color:#2c3e50;margin-bottom:40px;text-align:center}.config-page .form-group label{color:#34495e;font-weight:700}.config-page .form-control{border-radius:5px}.config-page .btn-primary{background-color:#3498db;border-color:#3498db;border-radius:5px;font-size:18px;padding:10px;width:100%}.config-page .btn-primary:hover{background-color:#2980b9;border-color:#2980b9}.loader-container{align-items:center;display:flex;height:90vh;justify-content:center}.loader{animation:spin 2s linear infinite;border:16px solid #f3f3f3;border-radius:50%;border-top-color:#3498db;height:120px;width:120px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.sidebar{background-color:#2c3e50;bottom:0;color:#ecf0f1;left:0;overflow:hidden;position:fixed;top:0;transition:width .3s ease;width:180px}.sidebar.closed{background-color:#2c3e50;width:60px}.sidebar-header{align-items:center;background-color:#1a252f;display:flex;height:60px;justify-content:center;padding:0 15px}.sidebar.open .sidebar-header{justify-content:space-between}.sidebar-header h2{font-size:1.5rem;margin:0;padding:0}.toggle-btn{align-items:center;background:#0000;border:none;color:#ecf0f1;cursor:pointer;display:flex;font-size:1.5rem;justify-content:center}.sidebar.closed .toggle-btn{left:50%;margin-left:0;margin-right:0;position:relative;transform:translateX(-50%)}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu li{padding:15px}.menu-item{align-items:center;color:#ecf0f1;display:flex;text-decoration:none;transition:background .2s}.menu-item:hover{background-color:#34495e}.menu-icon{font-size:1.2rem;margin-right:10px}.sidebar.closed .menu-item{justify-content:center}.sidebar.closed .menu-icon{margin-right:0}.sidebar.closed .menu-item span{display:none}
/*# sourceMappingURL=main.ed8a56b7.css.map*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,113 @@
/*!
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v4.4.5
* https://www.chartjs.org
* (c) 2024 Chart.js Contributors
* Released under the MIT License
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @mui/styled-engine v6.1.4
*
* @license MIT
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @remix-run/router v1.20.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router DOM v6.27.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router v6.27.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

BIN
GitHub/Agents-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
GitHub/Agents-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
GitHub/Config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
GitHub/Home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
GitHub/Inference.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
GitHub/Task-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
GitHub/Task-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

14
Macro/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "Macro"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
quote = "1.0.37"
proc-macro2 = "1.0.89"
syn = { version = "2.0.85", features = ["full"] }

Some files were not shown because too many files have changed in this diff Show More