Ran rustfmt

This commit is contained in:
2023-05-11 01:33:14 +02:00
parent 5364da1934
commit 2c94fd7af2
10 changed files with 349 additions and 292 deletions

View File

@@ -1,14 +1,18 @@
use serde::{Deserialize, Serialize}; use indication::{
use indication::{CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Oia, Passthru, Popup, Proxy, RunResult, Screen, ScreenMode, Scroll, Setting, Stats, TerminalName, Thumb, Tls, TlsHello, TraceFile, UiError}; CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Oia, Passthru, Popup,
Proxy, RunResult, Screen, ScreenMode, Scroll, Setting, Stats, TerminalName, Thumb, Tls,
TlsHello, TraceFile, UiError,
};
use operation::{Fail, Register, Run, Succeed}; use operation::{Fail, Register, Run, Succeed};
use serde::{Deserialize, Serialize};
pub mod operation;
pub mod indication; pub mod indication;
pub mod operation;
pub mod types; pub mod types;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum Indication { pub enum Indication {
Bell{}, // TODO: make sure this emits/parses {"bell": {}} Bell {}, // TODO: make sure this emits/parses {"bell": {}}
/// Indicates that the host connection has changed state. /// Indicates that the host connection has changed state.
Connection(Connection), Connection(Connection),
/// A new host connection is being attempted /// A new host connection is being attempted
@@ -30,10 +34,10 @@ pub enum Indication {
state: bool, state: bool,
}, },
/// File transfer state change /// File transfer state change
#[serde(rename="ft")] #[serde(rename = "ft")]
FileTransfer(FileTransfer), FileTransfer(FileTransfer),
/// An XTerm escape sequence requested a new icon name /// An XTerm escape sequence requested a new icon name
Icon{ Icon {
text: String, text: String,
}, },
/// The first message sent /// The first message sent
@@ -68,13 +72,13 @@ pub enum Indication {
/// Error in b3270's input /// Error in b3270's input
UiError(UiError), UiError(UiError),
/// Xterm escape sequence requested a change to the window title /// Xterm escape sequence requested a change to the window title
WindowTitle{ WindowTitle {
text: String, text: String,
} },
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum InitializeIndication { pub enum InitializeIndication {
CodePages(Vec<CodePage>), CodePages(Vec<CodePage>),
/// Indicates that the host connection has changed state. /// Indicates that the host connection has changed state.
@@ -88,7 +92,9 @@ pub enum InitializeIndication {
/// Change in the state of the Operator Information Area /// Change in the state of the Operator Information Area
Oia(Oia), Oia(Oia),
/// Set of supported prefixes /// Set of supported prefixes
Prefixes{value: String}, Prefixes {
value: String,
},
/// List of supported proxies /// List of supported proxies
Proxies(Vec<Proxy>), Proxies(Vec<Proxy>),
/// Screen dimensions/characteristics changed /// Screen dimensions/characteristics changed
@@ -106,7 +112,7 @@ pub enum InitializeIndication {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum Operation { pub enum Operation {
/// Run an action /// Run an action
Run(Run), Run(Run),
@@ -117,4 +123,3 @@ pub enum Operation {
/// Tell b3270 that a passthru action succeeded /// Tell b3270 that a passthru action succeeded
Succeed(Succeed), Succeed(Succeed),
} }

View File

@@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::b3270::types::{Color, GraphicRendition}; use crate::b3270::types::{Color, GraphicRendition};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum ActionCause { pub enum ActionCause {
Command, Command,
Default, Default,
@@ -22,27 +22,27 @@ pub enum ActionCause {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct CodePage{ pub struct CodePage {
/// The canonical name of the code page /// The canonical name of the code page
pub name: String, pub name: String,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<String>, pub aliases: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Connection{ pub struct Connection {
/// New connection state /// New connection state
pub state: ConnectionState, pub state: ConnectionState,
/// Host name, if connected /// Host name, if connected
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub host: Option<String>, pub host: Option<String>,
/// Source of the connection /// Source of the connection
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub cause: Option<ActionCause>, pub cause: Option<ActionCause>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum ComposeType { pub enum ComposeType {
Std, Std,
@@ -50,7 +50,7 @@ pub enum ComposeType {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum ConnectionState { pub enum ConnectionState {
NotConnected, NotConnected,
@@ -65,20 +65,20 @@ pub enum ConnectionState {
ConnectedUnbound, ConnectedUnbound,
ConnectedENvt, ConnectedENvt,
ConnectedESscp, ConnectedESscp,
#[serde(rename="connected-e-tn3270e")] #[serde(rename = "connected-e-tn3270e")]
ConnectedETn3270e, ConnectedETn3270e,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Erase { pub struct Erase {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub logical_rows: Option<u8>, pub logical_rows: Option<u8>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub logical_cols: Option<u8>, pub logical_cols: Option<u8>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub fg: Option<Color>, pub fg: Option<Color>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub bg: Option<Color>, pub bg: Option<Color>,
} }
@@ -101,34 +101,36 @@ pub struct Model {
pub struct Oia { pub struct Oia {
#[serde(flatten)] #[serde(flatten)]
pub field: OiaField, pub field: OiaField,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub lu: Option<String>, pub lu: Option<String>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case", tag="field")] #[serde(rename_all = "kebab-case", tag = "field")]
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum OiaField { pub enum OiaField {
/// Composite character in progress /// Composite character in progress
Compose{ Compose {
value: bool, value: bool,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
char: Option<String>, char: Option<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
type_: Option<ComposeType>, type_: Option<ComposeType>,
}, },
/// Insert mode /// Insert mode
Insert{value: bool}, Insert {
/// Keyboard is locked value: bool,
Lock{
#[serde(default, skip_serializing_if="Option::is_none")]
value: Option<String>
}, },
Lu{ /// Keyboard is locked
Lock {
#[serde(default, skip_serializing_if = "Option::is_none")]
value: Option<String>,
},
Lu {
/// Host session logical unit name /// Host session logical unit name
value: String, value: String,
/// Printer session LU name /// Printer session LU name
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
lu: Option<String>, lu: Option<String>,
}, },
/// Communication pending /// Communication pending
@@ -139,7 +141,7 @@ pub enum OiaField {
value: bool, value: bool,
/// Printer session LU name /// Printer session LU name
// TODO: determine if this is sent with this message or with Lu // TODO: determine if this is sent with this message or with Lu
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
lu: Option<String>, lu: Option<String>,
}, },
/// Reverse input mode /// Reverse input mode
@@ -155,7 +157,7 @@ pub enum OiaField {
value: String, value: String,
}, },
Timing { Timing {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
value: Option<String>, value: Option<String>,
}, },
Typeahead { Typeahead {
@@ -181,17 +183,17 @@ pub enum OiaFieldName {
impl OiaField { impl OiaField {
pub fn field_name(&self) -> OiaFieldName { pub fn field_name(&self) -> OiaFieldName {
match self { match self {
OiaField::Compose {..} => OiaFieldName::Compose, OiaField::Compose { .. } => OiaFieldName::Compose,
OiaField::Insert {..} => OiaFieldName::Insert, OiaField::Insert { .. } => OiaFieldName::Insert,
OiaField::Lock {..} => OiaFieldName::Lock, OiaField::Lock { .. } => OiaFieldName::Lock,
OiaField::Lu {..} => OiaFieldName::Lu, OiaField::Lu { .. } => OiaFieldName::Lu,
OiaField::NotUndera {..} => OiaFieldName::NotUndera, OiaField::NotUndera { .. } => OiaFieldName::NotUndera,
OiaField::PrinterSession {..} => OiaFieldName::PrinterSession, OiaField::PrinterSession { .. } => OiaFieldName::PrinterSession,
OiaField::ReverseInput {..} => OiaFieldName::ReverseInput, OiaField::ReverseInput { .. } => OiaFieldName::ReverseInput,
OiaField::ScreenTrace {..} => OiaFieldName::ScreenTrace, OiaField::ScreenTrace { .. } => OiaFieldName::ScreenTrace,
OiaField::Script {..} => OiaFieldName::Script, OiaField::Script { .. } => OiaFieldName::Script,
OiaField::Timing {..} => OiaFieldName::Timing, OiaField::Timing { .. } => OiaFieldName::Timing,
OiaField::Typeahead {..} => OiaFieldName::Typeahead, OiaField::Typeahead { .. } => OiaFieldName::Typeahead,
} }
} }
} }
@@ -224,24 +226,24 @@ pub struct ScreenMode {
pub struct TlsHello { pub struct TlsHello {
pub supported: bool, pub supported: bool,
pub provider: String, // docs claim this is always set, but I'm not sure. pub provider: String, // docs claim this is always set, but I'm not sure.
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<String>, pub options: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Tls { pub struct Tls {
pub secure: bool, pub secure: bool,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub verified: Option<bool>, pub verified: Option<bool>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub session: Option<String>, pub session: Option<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub host_cert: Option<String>, pub host_cert: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct ConnectAttempt { pub struct ConnectAttempt {
pub host_ip: String, pub host_ip: String,
pub port: String, pub port: String,
@@ -251,9 +253,9 @@ pub struct ConnectAttempt {
// TODO: change this to an enum // TODO: change this to an enum
pub struct Cursor { pub struct Cursor {
pub enabled: bool, pub enabled: bool,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub row: Option<u8>, pub row: Option<u8>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub column: Option<u8>, pub column: Option<u8>,
} }
@@ -265,15 +267,15 @@ pub struct FileTransfer {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all="lowercase", tag="state")] #[serde(rename_all = "lowercase", tag = "state")]
pub enum FileTransferState { pub enum FileTransferState {
Awaiting, Awaiting,
Running{ Running {
/// Number of bytes transferred /// Number of bytes transferred
bytes: usize bytes: usize,
}, },
Aborting, Aborting,
Complete{ Complete {
/// Completion message /// Completion message
text: String, text: String,
/// Transfer succeeded /// Transfer succeeded
@@ -282,27 +284,27 @@ pub enum FileTransferState {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub struct Passthru { pub struct Passthru {
pub p_tag: String, pub p_tag: String,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_r_tag: Option<String>, pub parent_r_tag: Option<String>,
pub action: String, pub action: String,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>, pub args: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Popup { pub struct Popup {
#[serde(rename="type")] #[serde(rename = "type")]
pub type_: PopupType, pub type_: PopupType,
pub text: String, pub text: String,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<bool>, pub error: Option<bool>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub enum PopupType { pub enum PopupType {
/// Error message from a connection attempt /// Error message from a connection attempt
ConnectError, ConnectError,
@@ -319,15 +321,14 @@ pub enum PopupType {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub struct Row { pub struct Row {
pub row: u8, pub row: u8,
pub changes: Vec<Change>, pub changes: Vec<Change>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub enum CountOrText { pub enum CountOrText {
Count(usize), Count(usize),
Text(String), Text(String),
@@ -338,48 +339,48 @@ pub struct Change {
pub column: u8, pub column: u8,
#[serde(flatten)] #[serde(flatten)]
pub change: CountOrText, pub change: CountOrText,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub fg: Option<Color>, pub fg: Option<Color>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub bg: Option<Color>, pub bg: Option<Color>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
/// Graphic rendition /// Graphic rendition
pub gr: Option<GraphicRendition>, pub gr: Option<GraphicRendition>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Screen { pub struct Screen {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<Cursor>, pub cursor: Option<Cursor>,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rows: Vec<Row>, pub rows: Vec<Row>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub struct RunResult { pub struct RunResult {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub r_tag: Option<String>, pub r_tag: Option<String>,
pub success: bool, pub success: bool,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub text: Vec<String>, pub text: Vec<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub abort: Option<bool>, pub abort: Option<bool>,
/// Execution time in seconds /// Execution time in seconds
pub time: f32, pub time: f32,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub struct Scroll { pub struct Scroll {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub fg: Option<Color>, pub fg: Option<Color>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub bg: Option<Color>, pub bg: Option<Color>,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename="kebab-case")] #[serde(rename = "kebab-case")]
pub struct Stats { pub struct Stats {
pub bytes_received: usize, pub bytes_received: usize,
pub bytes_sent: usize, pub bytes_sent: usize,
@@ -390,7 +391,7 @@ pub struct Stats {
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct TerminalName { pub struct TerminalName {
pub text: String, pub text: String,
#[serde(rename="override")] #[serde(rename = "override")]
pub override_: bool, pub override_: bool,
} }
@@ -410,7 +411,7 @@ pub struct Thumb {
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct TraceFile { pub struct TraceFile {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
} }
@@ -418,13 +419,13 @@ pub struct TraceFile {
pub struct UiError { pub struct UiError {
pub fatal: bool, pub fatal: bool,
pub text: String, pub text: String,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub operation: Option<String>, pub operation: Option<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub member: Option<String>, pub member: Option<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub line: Option<usize>, pub line: Option<usize>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub column: Option<usize>, pub column: Option<usize>,
} }
@@ -433,7 +434,13 @@ mod test {
use super::*; use super::*;
#[test] #[test]
pub fn connection_state_serializes_as_expected() { pub fn connection_state_serializes_as_expected() {
assert_eq!(serde_json::to_string(&ConnectionState::ConnectedETn3270e).unwrap(),r#""connected-e-tn3270e""#); assert_eq!(
assert_eq!(serde_json::to_string(&ConnectionState::ConnectedESscp).unwrap(),r#""connected-e-sscp""#); serde_json::to_string(&ConnectionState::ConnectedETn3270e).unwrap(),
r#""connected-e-tn3270e""#
);
assert_eq!(
serde_json::to_string(&ConnectionState::ConnectedESscp).unwrap(),
r#""connected-e-sscp""#
);
} }
} }

View File

@@ -4,45 +4,45 @@ use serde::{Deserialize, Serialize};
// {"run":{"actions":"Key(a)"}} // {"run":{"actions":"Key(a)"}}
// Operations // Operations
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Run { pub struct Run {
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub r_tag: Option<String>, pub r_tag: Option<String>,
#[serde(rename="type", default, skip_serializing_if="Option::is_none")] #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub type_: Option<String>, pub type_: Option<String>,
pub actions: Vec<Action>, pub actions: Vec<Action>,
} }
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Action { pub struct Action {
pub action: String, pub action: String,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>, pub args: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Register { pub struct Register {
pub name: String, pub name: String,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub help_text: Option<String>, pub help_text: Option<String>,
#[serde(default, skip_serializing_if="Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub help_params: Option<String>, pub help_params: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
/// Completes a passthru action unsuccessfully /// Completes a passthru action unsuccessfully
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Fail { pub struct Fail {
pub p_tag: String, pub p_tag: String,
pub text: Vec<String>, pub text: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
#[serde(rename_all="kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Succeed { pub struct Succeed {
pub p_tag: String, pub p_tag: String,
#[serde(default, skip_serializing_if="Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub text: Vec<String>, pub text: Vec<String>,
} }

View File

@@ -1,8 +1,8 @@
use bitflags::bitflags;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter, Write}; use std::fmt::{Display, Formatter, Write};
use std::str::FromStr; use std::str::FromStr;
use bitflags::bitflags;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::{Error, Visitor};
bitflags! { bitflags! {
#[derive(Clone,Copy,Debug,PartialEq, Eq, Hash)] #[derive(Clone,Copy,Debug,PartialEq, Eq, Hash)]
@@ -36,7 +36,8 @@ static FLAG_NAMES: &'static [(GraphicRendition, &'static str)] = &[
impl Display for GraphicRendition { impl Display for GraphicRendition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let flag_names = FLAG_NAMES.iter() let flag_names = FLAG_NAMES
.iter()
.filter_map(|(val, name)| self.contains(*val).then_some(*name)); .filter_map(|(val, name)| self.contains(*val).then_some(*name));
for (n, name) in flag_names.enumerate() { for (n, name) in flag_names.enumerate() {
if n != 0 { if n != 0 {
@@ -49,7 +50,10 @@ impl Display for GraphicRendition {
} }
impl Serialize for GraphicRendition { impl Serialize for GraphicRendition {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() { if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string()) serializer.serialize_str(&self.to_string())
} else { } else {
@@ -60,7 +64,6 @@ impl Serialize for GraphicRendition {
struct GrVisitor; struct GrVisitor;
impl Visitor<'_> for GrVisitor { impl Visitor<'_> for GrVisitor {
type Value = GraphicRendition; type Value = GraphicRendition;
@@ -68,15 +71,24 @@ impl Visitor<'_> for GrVisitor {
write!(formatter, "graphic rendition string or binary value") write!(formatter, "graphic rendition string or binary value")
} }
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: Error { fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_u64((v & 0xFFFF) as u64) self.visit_u64((v & 0xFFFF) as u64)
} }
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: Error { fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(GraphicRendition::from_bits_truncate((v & 0xFFFF) as u16)) Ok(GraphicRendition::from_bits_truncate((v & 0xFFFF) as u16))
} }
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: Error { fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
GraphicRendition::from_str(v).map_err(E::custom) GraphicRendition::from_str(v).map_err(E::custom)
} }
} }
@@ -87,7 +99,8 @@ impl FromStr for GraphicRendition {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(",") s.split(",")
.map(|attr| { .map(|attr| {
FLAG_NAMES.iter() FLAG_NAMES
.iter()
.find(|(_, name)| *name == attr) .find(|(_, name)| *name == attr)
.map(|x| x.0) .map(|x| x.0)
.ok_or_else(|| format!("Invalid attr name {attr}")) .ok_or_else(|| format!("Invalid attr name {attr}"))
@@ -97,7 +110,10 @@ impl FromStr for GraphicRendition {
} }
impl<'de> Deserialize<'de> for GraphicRendition { impl<'de> Deserialize<'de> for GraphicRendition {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() { if deserializer.is_human_readable() {
deserializer.deserialize_str(GrVisitor) deserializer.deserialize_str(GrVisitor)
} else { } else {
@@ -108,16 +124,19 @@ impl<'de> Deserialize<'de> for GraphicRendition {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::str::FromStr;
use super::GraphicRendition; use super::GraphicRendition;
use std::str::FromStr;
#[test] #[test]
fn from_str_1() { fn from_str_1() {
assert_eq!(GraphicRendition::from_str("underline,blink"), Ok(GraphicRendition::BLINK | GraphicRendition::UNDERLINE)) assert_eq!(
GraphicRendition::from_str("underline,blink"),
Ok(GraphicRendition::BLINK | GraphicRendition::UNDERLINE)
)
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
#[serde(rename="camelCase")] #[serde(rename = "camelCase")]
#[repr(u8)] #[repr(u8)]
pub enum Color { pub enum Color {
NeutralBlack, NeutralBlack,
@@ -142,22 +161,22 @@ impl From<Color> for u8 {
fn from(value: Color) -> Self { fn from(value: Color) -> Self {
use Color::*; use Color::*;
match value { match value {
NeutralBlack => 0, NeutralBlack => 0,
Blue => 1, Blue => 1,
Red => 2, Red => 2,
Pink => 3, Pink => 3,
Green => 4, Green => 4,
Turquoise => 5, Turquoise => 5,
Yellow => 6, Yellow => 6,
NeutralWhite => 7, NeutralWhite => 7,
Black => 8, Black => 8,
DeepBlue => 9, DeepBlue => 9,
Orange => 10, Orange => 10,
Purple => 11, Purple => 11,
PaleGreen => 12, PaleGreen => 12,
PaleTurquoise => 13, PaleTurquoise => 13,
Gray => 14, Gray => 14,
White => 15, White => 15,
} }
} }
} }
@@ -166,16 +185,16 @@ impl From<u8> for Color {
fn from(value: u8) -> Self { fn from(value: u8) -> Self {
use Color::*; use Color::*;
match value & 0xF { match value & 0xF {
0 => NeutralBlack, 0 => NeutralBlack,
1 => Blue, 1 => Blue,
2 => Red, 2 => Red,
3 => Pink, 3 => Pink,
4 => Green, 4 => Green,
5 => Turquoise, 5 => Turquoise,
6 => Yellow, 6 => Yellow,
7 => NeutralWhite, 7 => NeutralWhite,
8 => Black, 8 => Black,
9 => DeepBlue, 9 => DeepBlue,
10 => Orange, 10 => Orange,
11 => Purple, 11 => Purple,
12 => PaleGreen, 12 => PaleGreen,

View File

@@ -0,0 +1 @@

View File

@@ -1,4 +1,4 @@
pub mod b3270; pub mod b3270;
pub mod tracker; pub mod tracker;
pub mod executor; pub mod executor;

View File

@@ -1,7 +1,10 @@
use std::collections::HashMap; use crate::b3270::indication::{
use crate::b3270::indication::{Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row, RunResult, Screen, ScreenMode, Scroll, Setting, TerminalName, Thumb, Tls, TraceFile}; Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row,
use crate::b3270::{Indication, InitializeIndication}; RunResult, Screen, ScreenMode, Scroll, Setting, TerminalName, Thumb, Tls, TraceFile,
};
use crate::b3270::types::{Color, GraphicRendition, PackedAttr}; use crate::b3270::types::{Color, GraphicRendition, PackedAttr};
use crate::b3270::{Indication, InitializeIndication};
use std::collections::HashMap;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
struct CharCell { struct CharCell {
@@ -48,9 +51,7 @@ impl Tracker {
| Indication::Icon { .. } | Indication::Icon { .. }
| Indication::Popup(_) | Indication::Popup(_)
| Indication::Stats(_) | Indication::Stats(_)
| Indication::WindowTitle { .. } | Indication::WindowTitle { .. } => (),
=> (),
Indication::Connection(conn) => { Indication::Connection(conn) => {
self.connection = conn.clone(); self.connection = conn.clone();
} }
@@ -64,43 +65,47 @@ impl Tracker {
let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.cols) as usize; let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.cols) as usize;
self.screen = vec![ self.screen = vec![
vec![CharCell{ vec![
attr: u32::c_pack( CharCell {
erase.fg.unwrap_or(Color::NeutralBlack), attr: u32::c_pack(
erase.bg.unwrap_or(Color::Blue), erase.fg.unwrap_or(Color::NeutralBlack),
GraphicRendition::empty(), erase.bg.unwrap_or(Color::Blue),
), GraphicRendition::empty(),
ch: ' ', ),
};cols] ch: ' ',
; rows };
cols
];
rows
] ]
} }
Indication::Formatted { state } => {self.formatted = *state; } Indication::Formatted { state } => {
self.formatted = *state;
}
Indication::Initialize(init) => { Indication::Initialize(init) => {
let mut static_init = Vec::with_capacity(init.len()); let mut static_init = Vec::with_capacity(init.len());
for indicator in init.clone() { for indicator in init.clone() {
match indicator { match indicator {
InitializeIndication::CodePages(_) | InitializeIndication::CodePages(_)
InitializeIndication::Hello(_) | | InitializeIndication::Hello(_)
InitializeIndication::Models(_) | | InitializeIndication::Models(_)
InitializeIndication::Prefixes { .. } | | InitializeIndication::Prefixes { .. }
InitializeIndication::Proxies(_) | | InitializeIndication::Proxies(_)
InitializeIndication::TlsHello(_) | | InitializeIndication::TlsHello(_)
InitializeIndication::Tls(_) | | InitializeIndication::Tls(_)
InitializeIndication::TraceFile(_) => | InitializeIndication::TraceFile(_) => static_init.push(indicator),
static_init.push(indicator),
// The rest are passed through to normal processing. // The rest are passed through to normal processing.
InitializeIndication::Thumb(thumb) => { InitializeIndication::Thumb(thumb) => {
self.handle_indication(&mut Indication::Thumb(thumb)); self.handle_indication(&mut Indication::Thumb(thumb));
}, }
InitializeIndication::Setting(setting) => { InitializeIndication::Setting(setting) => {
self.handle_indication(&mut Indication::Setting(setting)); self.handle_indication(&mut Indication::Setting(setting));
} }
InitializeIndication::ScreenMode(mode) => { InitializeIndication::ScreenMode(mode) => {
self.handle_indication(&mut Indication::ScreenMode(mode)); self.handle_indication(&mut Indication::ScreenMode(mode));
}, }
InitializeIndication::Oia(oia) => { InitializeIndication::Oia(oia) => {
self.handle_indication(&mut Indication::Oia(oia)); self.handle_indication(&mut Indication::Oia(oia));
} }
@@ -127,21 +132,19 @@ impl Tracker {
// update screen contents // update screen contents
let cols = self.screen[row_idx].iter_mut().skip(col_idx); let cols = self.screen[row_idx].iter_mut().skip(col_idx);
match change.change { match change.change {
CountOrText::Count(n) => { CountOrText::Count(n) => cols.take(n).for_each(|cell| {
cols.take(n).for_each(|cell| { let mut attr = cell.attr;
let mut attr = cell.attr; if let Some(fg) = change.fg {
if let Some(fg) = change.fg { attr = attr.c_setfg(fg);
attr = attr.c_setfg(fg); }
} if let Some(bg) = change.bg {
if let Some(bg) = change.bg { attr = attr.c_setbg(bg);
attr = attr.c_setbg(bg); }
} if let Some(gr) = change.gr {
if let Some(gr) = change.gr { attr = attr.c_setgr(gr);
attr = attr.c_setgr(gr); }
} cell.attr = attr;
cell.attr = attr; }),
})
}
CountOrText::Text(ref text) => { CountOrText::Text(ref text) => {
cols.zip(text.chars()).for_each(|(cell, ch)| { cols.zip(text.chars()).for_each(|(cell, ch)| {
let mut attr = cell.attr; let mut attr = cell.attr;
@@ -164,18 +167,18 @@ impl Tracker {
} }
Indication::ScreenMode(mode) => { Indication::ScreenMode(mode) => {
self.screen_mode = *mode; self.screen_mode = *mode;
self.handle_indication(&mut Indication::Erase(Erase{ self.handle_indication(&mut Indication::Erase(Erase {
logical_rows: Some(self.screen_mode.rows), logical_rows: Some(self.screen_mode.rows),
logical_cols: Some(self.screen_mode.cols), logical_cols: Some(self.screen_mode.cols),
fg: None, fg: None,
bg: None, bg: None,
})); }));
} }
Indication::Scroll(Scroll{ fg, bg }) => { Indication::Scroll(Scroll { fg, bg }) => {
let fg = fg.or(self.erase.fg).unwrap_or(Color::Blue); let fg = fg.or(self.erase.fg).unwrap_or(Color::Blue);
let bg = bg.or(self.erase.bg).unwrap_or(Color::NeutralBlack); let bg = bg.or(self.erase.bg).unwrap_or(Color::NeutralBlack);
let mut row = self.screen.remove(0); let mut row = self.screen.remove(0);
row.fill(CharCell{ row.fill(CharCell {
attr: u32::c_pack(fg, bg, GraphicRendition::empty()), attr: u32::c_pack(fg, bg, GraphicRendition::empty()),
ch: ' ', ch: ' ',
}); });
@@ -190,7 +193,7 @@ impl Tracker {
Indication::Thumb(thumb) => { Indication::Thumb(thumb) => {
self.thumb = thumb.clone(); self.thumb = thumb.clone();
} }
Indication::TraceFile(TraceFile{name}) => { Indication::TraceFile(TraceFile { name }) => {
self.trace_file = name.clone(); self.trace_file = name.clone();
} }
Indication::Tls(tls) => { Indication::Tls(tls) => {
@@ -201,16 +204,15 @@ impl Tracker {
Indication::UiError(_) => {} // we can assume that this came from the last sent command Indication::UiError(_) => {} // we can assume that this came from the last sent command
Indication::Passthru(_) => {} // dunno how to handle this one Indication::Passthru(_) => {} // dunno how to handle this one
Indication::FileTransfer(_) => {} Indication::FileTransfer(_) => {}
Indication::RunResult(RunResult{r_tag, ..}) => { Indication::RunResult(RunResult { r_tag, .. }) => {
if let Some(dest) = r_tag { if let Some(dest) = r_tag {
return Disposition::Direct(dest.clone()); return Disposition::Direct(dest.clone());
} else { } else {
return Disposition::Drop; return Disposition::Drop;
} }
} }
} }
return Disposition::Broadcast return Disposition::Broadcast;
} }
pub fn get_init_indication(&self) -> Vec<Indication> { pub fn get_init_indication(&self) -> Vec<Indication> {
@@ -219,12 +221,13 @@ impl Tracker {
contents.push(InitializeIndication::Erase(self.erase)); contents.push(InitializeIndication::Erase(self.erase));
contents.push(InitializeIndication::Thumb(self.thumb)); contents.push(InitializeIndication::Thumb(self.thumb));
contents.extend(self.oia.values() contents.extend(self.oia.values().cloned().map(InitializeIndication::Oia));
.cloned() contents.extend(
.map(InitializeIndication::Oia)); self.settings
contents.extend(self.settings.values() .values()
.cloned() .cloned()
.map(InitializeIndication::Setting)); .map(InitializeIndication::Setting),
);
contents.extend(self.tls.clone().map(InitializeIndication::Tls)); contents.extend(self.tls.clone().map(InitializeIndication::Tls));
// Construct a screen snapshot // Construct a screen snapshot
@@ -232,7 +235,9 @@ impl Tracker {
Indication::Initialize(contents), Indication::Initialize(contents),
Indication::Connection(self.connection.clone()), Indication::Connection(self.connection.clone()),
Indication::Screen(self.screen_snapshot()), Indication::Screen(self.screen_snapshot()),
Indication::Formatted {state: self.formatted}, Indication::Formatted {
state: self.formatted,
},
]; ];
if let Some(terminal_name) = self.terminal_name.clone() { if let Some(terminal_name) = self.terminal_name.clone() {
result.push(Indication::TerminalName(terminal_name)); result.push(Indication::TerminalName(terminal_name));
@@ -255,7 +260,7 @@ impl Tracker {
let (first, rest) = row.split_at(split_pt); let (first, rest) = row.split_at(split_pt);
row = rest; row = rest;
let content = first.iter().map(|cell| cell.ch).collect(); let content = first.iter().map(|cell| cell.ch).collect();
result.push(Change{ result.push(Change {
column, column,
fg: Some(cur_gr.c_fg()), fg: Some(cur_gr.c_fg()),
bg: Some(cur_gr.c_bg()), bg: Some(cur_gr.c_bg()),
@@ -269,16 +274,19 @@ impl Tracker {
} }
fn screen_snapshot(&self) -> Screen { fn screen_snapshot(&self) -> Screen {
Screen{ Screen {
cursor: Some(self.cursor), cursor: Some(self.cursor),
rows: self.screen.iter() rows: self
.map(Vec::as_slice).map(Self::format_row) .screen
.iter()
.map(Vec::as_slice)
.map(Self::format_row)
.enumerate() .enumerate()
.map(|(row_id, changes)| Row{ .map(|(row_id, changes)| Row {
row: row_id as u8 - 1, row: row_id as u8 - 1,
changes, changes,
}) })
.collect() .collect(),
} }
} }
} }
@@ -313,7 +321,7 @@ impl Default for Tracker {
cursor: Cursor { cursor: Cursor {
enabled: false, enabled: false,
row: None, row: None,
column: None column: None,
}, },
connection: Connection { connection: Connection {
state: ConnectionState::NotConnected, state: ConnectionState::NotConnected,
@@ -328,4 +336,4 @@ impl Default for Tracker {
}; };
ret ret
} }
} }

View File

@@ -1,29 +1,29 @@
use anyhow::anyhow;
use base64::engine::general_purpose::STANDARD as B64_STANDARD;
use base64::Engine;
use bytes::Buf;
use d3270_common::b3270::indication::RunResult;
use d3270_common::b3270::operation::Action;
use d3270_common::b3270::{operation, Indication, Operation};
use d3270_common::tracker::{Disposition, Tracker};
use futures::future::BoxFuture;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt};
use rand::RngCore;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use anyhow::anyhow; use tokio::io::{AsyncBufReadExt, AsyncWrite, BufReader, Lines};
use base64::Engine;
use base64::engine::general_purpose::STANDARD as B64_STANDARD;
use bytes::Buf;
use futures::{FutureExt, Stream, StreamExt, TryFutureExt};
use futures::future::BoxFuture;
use rand::RngCore;
use tokio::io::{BufReader, AsyncBufReadExt, Lines, AsyncWrite};
use tokio::process::{Child, ChildStdout}; use tokio::process::{Child, ChildStdout};
use tokio::sync::{mpsc, oneshot, broadcast}; use tokio::sync::{broadcast, mpsc, oneshot};
use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError}; use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use d3270_common::b3270::{Indication, Operation, operation};
use d3270_common::b3270::indication::RunResult;
use d3270_common::b3270::operation::Action;
use d3270_common::tracker::{Disposition, Tracker};
pub struct B3270 { pub struct B3270 {
tracker: Tracker, // tracker: Tracker, //
child: Child, // child: Child, //
comm: mpsc::Receiver<B3270Request>, // comm: mpsc::Receiver<B3270Request>, //
ind_chan: broadcast::Sender<Indication>, // ind_chan: broadcast::Sender<Indication>, //
child_reader: Lines<BufReader<ChildStdout>>, // child_reader: Lines<BufReader<ChildStdout>>, //
write_buf: VecDeque<u8>, write_buf: VecDeque<u8>,
@@ -38,8 +38,14 @@ pub enum B3270Request {
enum HandleReceiveState { enum HandleReceiveState {
Steady(BroadcastStream<Indication>), Steady(BroadcastStream<Indication>),
Wait(oneshot::Receiver<(Vec<Indication>, broadcast::Receiver<Indication>)>), Wait(oneshot::Receiver<(Vec<Indication>, broadcast::Receiver<Indication>)>),
Resume(std::vec::IntoIter<Indication>, broadcast::Receiver<Indication>), Resume(
TryRestart(BoxFuture<'static, Result<(), ()>>, oneshot::Receiver<(Vec<Indication>, broadcast::Receiver<Indication>)>), std::vec::IntoIter<Indication>,
broadcast::Receiver<Indication>,
),
TryRestart(
BoxFuture<'static, Result<(), ()>>,
oneshot::Receiver<(Vec<Indication>, broadcast::Receiver<Indication>)>,
),
} }
pub struct Handle { pub struct Handle {
sender: mpsc::Sender<B3270Request>, sender: mpsc::Sender<B3270Request>,
@@ -66,11 +72,12 @@ impl Stream for Handle {
match rcvr.poll_unpin(cx) { match rcvr.poll_unpin(cx) {
Poll::Ready(Ok((inds, rcvr))) => { Poll::Ready(Ok((inds, rcvr))) => {
// reverse the indicators so that they can be popped. // reverse the indicators so that they can be popped.
self.receiver = Some(HandleReceiveState::Resume(inds.into_iter(), rcvr)); self.receiver =
Some(HandleReceiveState::Resume(inds.into_iter(), rcvr));
} }
Poll::Ready(Err(error)) => { Poll::Ready(Err(error)) => {
warn!(%error, "unable to reconnect to b3270 server"); warn!(%error, "unable to reconnect to b3270 server");
return Poll::Ready(None) return Poll::Ready(None);
} }
Poll::Pending => { Poll::Pending => {
self.receiver = Some(HandleReceiveState::Wait(rcvr)); self.receiver = Some(HandleReceiveState::Wait(rcvr));
@@ -78,41 +85,41 @@ impl Stream for Handle {
} }
} }
} }
Some(HandleReceiveState::Resume(mut inds, rcvr)) => { Some(HandleReceiveState::Resume(mut inds, rcvr)) => match inds.next() {
match inds.next() { Some(next) => {
Some(next) => { self.receiver = Some(HandleReceiveState::Resume(inds, rcvr));
self.receiver = Some(HandleReceiveState::Resume(inds, rcvr)); return Poll::Ready(Some(next));
return Poll::Ready(Some(next));
}
None => {
self.receiver = Some(HandleReceiveState::Steady(BroadcastStream::new(rcvr)));
}
} }
} None => {
Some(HandleReceiveState::Steady(mut rcvr)) => { self.receiver =
match rcvr.poll_next_unpin(cx) { Some(HandleReceiveState::Steady(BroadcastStream::new(rcvr)));
Poll::Ready(Some(Ok(msg))) => {
self.receiver = Some(HandleReceiveState::Steady(rcvr));
return Poll::Ready(Some(msg))
}
Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => {
warn!("Dropped messages from b3270 server; starting resync");
let (os_snd, os_rcv) = oneshot::channel();
let fut = self.sender.clone().reserve_owned()
.map_ok(move |permit| {
permit.send(B3270Request::Resync(os_snd));
})
.map_err(|_| ())
.boxed();
self.receiver = Some(HandleReceiveState::TryRestart(fut, os_rcv));
}
Poll::Ready(None) => {
warn!("Failed to receive from b3270 server");
return Poll::Ready(None)
},
Poll::Pending => return Poll::Pending
} }
} },
Some(HandleReceiveState::Steady(mut rcvr)) => match rcvr.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(msg))) => {
self.receiver = Some(HandleReceiveState::Steady(rcvr));
return Poll::Ready(Some(msg));
}
Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => {
warn!("Dropped messages from b3270 server; starting resync");
let (os_snd, os_rcv) = oneshot::channel();
let fut = self
.sender
.clone()
.reserve_owned()
.map_ok(move |permit| {
permit.send(B3270Request::Resync(os_snd));
})
.map_err(|_| ())
.boxed();
self.receiver = Some(HandleReceiveState::TryRestart(fut, os_rcv));
}
Poll::Ready(None) => {
warn!("Failed to receive from b3270 server");
return Poll::Ready(None);
}
Poll::Pending => return Poll::Pending,
},
None => { None => {
return Poll::Ready(None); return Poll::Ready(None);
@@ -122,11 +129,18 @@ impl Stream for Handle {
} }
} }
impl B3270 { impl B3270 {
pub fn spawn(mut child: Child) -> (tokio::task::JoinHandle<anyhow::Error>, mpsc::Sender<B3270Request>) { pub fn spawn(
mut child: Child,
) -> (
tokio::task::JoinHandle<anyhow::Error>,
mpsc::Sender<B3270Request>,
) {
let (subproc_snd, subproc_rcv) = mpsc::channel(10); let (subproc_snd, subproc_rcv) = mpsc::channel(10);
let child_reader = child.stdout.take().expect("Should always be given a child that has stdout captured"); let child_reader = child
.stdout
.take()
.expect("Should always be given a child that has stdout captured");
let child_reader = BufReader::new(child_reader).lines(); let child_reader = BufReader::new(child_reader).lines();
// A single connect can result in a flurry of messages, so we need a big buffer // A single connect can result in a flurry of messages, so we need a big buffer
let (ind_chan, _) = broadcast::channel(100); let (ind_chan, _) = broadcast::channel(100);
@@ -153,9 +167,7 @@ impl Future for B3270 {
while let Poll::Ready(buf) = Pin::new(&mut self.child_reader).poll_next_line(cx) { while let Poll::Ready(buf) = Pin::new(&mut self.child_reader).poll_next_line(cx) {
match buf { match buf {
Ok(Some(line)) => match serde_json::from_str(&line) { Ok(Some(line)) => match serde_json::from_str(&line) {
Ok(ind) => { Ok(ind) => indications.push(ind),
indications.push(ind)
},
Err(error) => { Err(error) => {
warn!(%error, msg=line, "Failed to parse indication"); warn!(%error, msg=line, "Failed to parse indication");
} }
@@ -204,13 +216,15 @@ impl Future for B3270 {
let mut sync_state = None; let mut sync_state = None;
while let Poll::Ready(cmd) = self.comm.poll_recv(cx) { while let Poll::Ready(cmd) = self.comm.poll_recv(cx) {
match cmd { match cmd {
None => {}, None => {}
Some(B3270Request::Resync(sender)) => { Some(B3270Request::Resync(sender)) => {
if sync_state.is_none() { if sync_state.is_none() {
sync_state = Some(self.tracker.get_init_indication()); sync_state = Some(self.tracker.get_init_indication());
} }
// it's OK for this to fail; we just don't get a new client // it's OK for this to fail; we just don't get a new client
sender.send((sync_state.clone().unwrap(), self.ind_chan.subscribe())).ok(); sender
.send((sync_state.clone().unwrap(), self.ind_chan.subscribe()))
.ok();
} }
Some(B3270Request::Action(actions, response_chan)) => { Some(B3270Request::Action(actions, response_chan)) => {
let tag = 'find_tag: loop { let tag = 'find_tag: loop {
@@ -225,15 +239,12 @@ impl Future for B3270 {
type_: Some("keymap".to_owned()), type_: Some("keymap".to_owned()),
actions, actions,
}); });
let result = serde_json::to_writer( let result = serde_json::to_writer(&mut self.write_buf, &op);
&mut self.write_buf,
&op
);
match result { match result {
Ok(()) => { Ok(()) => {
self.write_buf.push_back(b'\n'); self.write_buf.push_back(b'\n');
self.action_response_map.insert(tag, response_chan); self.action_response_map.insert(tag, response_chan);
}, }
Err(error) => error!(?op, %error, "Failed to serialize op"), Err(error) => error!(?op, %error, "Failed to serialize op"),
} }
} }
@@ -244,7 +255,13 @@ impl Future for B3270 {
'write: while !self.write_buf.is_empty() { 'write: while !self.write_buf.is_empty() {
let myself = &mut *self; let myself = &mut *self;
let chunk = myself.write_buf.chunk(); let chunk = myself.write_buf.chunk();
let stdin = Pin::new(myself.child.stdin.as_mut().expect("Should always have child stdin")); let stdin = Pin::new(
myself
.child
.stdin
.as_mut()
.expect("Should always have child stdin"),
);
match stdin.poll_write(cx, chunk) { match stdin.poll_write(cx, chunk) {
Poll::Pending | Poll::Ready(Ok(0)) => { Poll::Pending | Poll::Ready(Ok(0)) => {
break 'write; break 'write;

View File

@@ -1,28 +1,29 @@
use anyhow::anyhow;
use std::ffi::OsString; use std::ffi::OsString;
use std::process::Stdio; use std::process::Stdio;
use std::str::FromStr; use std::str::FromStr;
use anyhow::anyhow;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let mut subprocess_args = vec![ let mut subprocess_args = vec![OsString::from_str("-json").unwrap()];
OsString::from_str("-json").unwrap(),
];
let mut args_iter = std::env::args_os().peekable(); let mut args_iter = std::env::args_os().peekable();
let mut connect_str = None; let mut connect_str = None;
while let Some(arg) = args_iter.next() { while let Some(arg) = args_iter.next() {
// we default to one of the ignored args // we default to one of the ignored args
match arg.to_str().unwrap_or("-json") { match arg.to_str().unwrap_or("-json") {
"-json" | "-xml" | "-indent" | "--" | "-json" | "-xml" | "-indent" | "--" | "-scriptportonce" | "-nowrapperdoc"
"-scriptportonce" | "-nowrapperdoc" | | "-socket" | "-v" | "--version" => {}
"-socket" | "-v" | "--version" => {}
"-scriptport" | "-httpd" => { "-scriptport" | "-httpd" => {
args_iter.next(); args_iter.next();
} }
"-connect" => { "-connect" => {
connect_str = args_iter.next() connect_str = args_iter
.next()
.ok_or_else(|| anyhow!("Arg required for -connect")) .ok_or_else(|| anyhow!("Arg required for -connect"))
.and_then(|arg| arg.into_string().map_err(|_| anyhow!("Invalid connect string"))) .and_then(|arg| {
arg.into_string()
.map_err(|_| anyhow!("Invalid connect string"))
})
.map(Some)?; .map(Some)?;
} }
"-e" => { "-e" => {
@@ -37,7 +38,7 @@ async fn main() -> anyhow::Result<()> {
} }
} }
let _connect_str = connect_str.ok_or_else(||anyhow!("No connect string given"))?; let _connect_str = connect_str.ok_or_else(|| anyhow!("No connect string given"))?;
let subproc = tokio::process::Command::new("b3270") let subproc = tokio::process::Command::new("b3270")
.args(&subprocess_args) .args(&subprocess_args)
@@ -51,5 +52,4 @@ async fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub mod connection;
pub mod connection;

View File

@@ -1,3 +1,3 @@
fn main() { fn main() {
eprintln!("Hello from d3270c"); eprintln!("Hello from d3270c");
} }