-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding other chat backends? #47
Comments
If you want your backend to work with tiny's current backend (e.g. in a single I was hoping to do something like this myself so maybe we can work it out. To summarize current design:
This happens in It seems to me like we need to refactor the code a little bit to support Current Another struct XMPPConn {}
enum XMPPConnEv {}
pub fn handle_xmpp_conn_ev(conn: &mut XMPPConn, ev: XMPPConnEv, tui: &mut TUI); We could of course make Out of curiosity, what backend are you planning to implement? In the meantime I think you can implement your client by using tiny's TUI as a |
I'm interested in bringing all the various messaging apps I use into one terminal interface, where I can seamlessly switch between them. I currently use Discord, Slack, Facebook Messenger, SMS via Pushbullet, and IRC throughout the day and I think it would be cool to have them all inside Tiny. (obviously there will be some loss of functionality) Following your suggestion I've been implementing a Slack backend. I think I got I think we should have a |
I'll write more later but just wanted to point out
We already have this, see |
Oh duh. I think I was just confused because it doesn't look like that method processes the events. |
This is great! I was thinking of doing the same for slack (which we use at OK so I have this idea: Instead of connections returning (or filling in) a vector of events, for TUI Connections have three update functions:
These handle TUI updates themselves. For updating tiny's state (rather than a connection's state) we implement Then for passing input from TUI to connections, we implement In summary it'd look like this: use logger::Logger;
use tui::TUI;
use tui::tabbed::MsgSource;
/// Every `Conn` needs to be `Evented` to be useful
trait Conn {
/// Called when `Conn`s socket is ready for reading.
///
/// * `tui`: TUI handle that can be used to update tabs associated to this connection.
/// * `evs`: Events for tiny to handle.
/// * `logger`: Logger handle for this connection.
///
fn read_ready(&mut self, tui: &mut TUIHandle, evs: &mut Vec<TinyEv>, logger: &mut Logger);
/// Called when `Conn`s socket is ready for writing and the socket is registered for write
/// events.
///
/// Arguments have same meaning with `read_ready`'s arguments.
///
fn write_ready(&mut self, tui: &mut TUIHandle, evs: &mut Vec<TinyEv>, logger: &mut Logger);
/// Called every second.
fn tick(&mut self, evs: &mut Vec<TinyEv>, logger: &mut Logger);
/// Called when a command was sent to this connection.
fn handle_cmd(&mut self, cmd: String, args: Vec<String>, evs: &mut Vec<TinyEv>, logger: &mut Logger);
/// Called when an input was received in a tab that's associated with this connection.
/// Implementations can return events to tiny using `evs` parameter.
fn handle_tui_event(&mut self, ev: TUIEv, evs: &mut Vec<TinyEv>, logger: &mut Logger);
}
pub enum TinyEv {
/// Drop this connection
ConnectionClosed,
// what else?
}
pub enum TUIEv {
Input {
msg: Vec<char>,
from: MsgSource,
}
}
/// A handle that allows updating tabs for a `Conn`.
pub struct TUIHandle {} We then implement Does that make sense? How about this plan: I create create a new branch and |
I like that you've shrunk the requirements for a connection (the existing Conn has a lot of public methods), but I don't understand the role of each of these methods. Here's how I see them:
The plan sounds good to me. I've been working on a Slack backend, and so far it looks not all that hard (I've written Slack stuff before). I think I've almost got something together for PushBullet, but their docs seem very incomplete so that could get interesting. I can already tell you that the batched setup that you have for dealing with IRC connections does not play very well with the websocket interface used by nearly every other chat service. Event loops and handlers are the easiest thing to do, so right now I have a handler that just sits in its own thread and pushes into an |
Feedback on the proposed Some commands may produce an error. EDIT: Just noticed some commands want to know which channel we're in. More reason to have a handle to the TUI I think. |
As far as I understand you're thinking a futures/threads based API whereas I In mio-style code you can't tell if a socket your backend is using have data to Instead if we design threads-based API then every backend can take care of If we design a futures-based API ... well first of all futures API is terrible
This is correct. tiny thinks there are messages to be read from you when
You get your inputs from TUI in So
Hmm so they use threads and/or futures. OK.
Exactly.
This is where you get inputs from TUI. So suppose someone entered
Right.. So maybe designing an API around a As mentioned above, nice thing about about the current design is you don't have The options I see:
Ah, this makes sense. In fact current |
Btw is your code open source yet? I'd love to take a peek to get a better understanding at what we need from the backend API. |
I'm totally down for doing this with Code is here. I butchered my |
mio just decides whether the socket your connection is using can be read of This is how event loops work, under the hood it uses When you want to send data to the remote side of your connection, you tell the Let's see how tiny does this. Suppose we want to send a pub fn privmsg(&mut self, target: &str, msg: &str) {
self.status.get_stream_mut().map(|stream| {
wire::privmsg(stream, target, msg).unwrap();
});
} Which uses impl<'poll> Write for Stream<'poll> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
Stream::Tcp(ref mut s) =>
s.write(buf),
Stream::Tls(ref mut s) =>
s.write(buf),
}
}
} Note that Now a stream can be either impl<'poll> Write for TcpStream<'poll> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// TODO inefficient when the socket is already ready for writing
self.out_buf.extend(buf);
reregister_for_rw(&self.poll, self.inner.as_raw_fd());
Ok(buf.len())
}
} See how Then when the socket is ready for sending, the event loop calls impl<'poll> TcpStream<'poll> {
pub fn write_ready(&mut self) -> io::Result<()> {
let to_send = self.out_buf.len();
match self.inner.write(&self.out_buf) {
Ok(bytes_sent) => {
self.out_buf.drain(0..bytes_sent);
let register = if bytes_sent == to_send {
reregister_for_r
} else {
reregister_for_rw
};
register(&self.poll, self.inner.as_raw_fd());
Ok(())
}
Err(err) => {
reregister_for_rw(&self.poll, self.inner.as_raw_fd());
Err(err)
}
}
}
} This is where we do the actual sending and update our outgoing message buffer. Hopefully this clarifies I'm not saying this good or convenient or anything like that. I'm just |
Hmm I think this is not going to work -- for this API to work you'd need I think we should go with threads-based API and let the backends choose whatever method they like. Just fork a thread for each backend, pass a Only problem is we need to figure how to poll |
I now own a chromebook so I really want these other backends in place. I'm tempted to say that we should figure out an interface between connections and the rest of tiny that's decently clean and just run with it for now. If we have threads all over, I'm sure they can be replaced with the upcoming async features (we're on nightly already). The biggest hurdle for me on getting something up and running is that all of tiny's internals are tightly locked to the way a It just occurred to me that I might be able to hack the new trait-based interface into tiny by adding a new member to the |
I've been working on this and I started to think that we should implement a futures-based API and implement backends on top of that. I think for most backends we need one of these three things:
So it seems to me that if we use futures we don't have to spawn threads per backend. In this design we'd pass a All backend methods would also get a I'll try to design the API in more details and try to port current IRC backend but there's one problem that I don't know how to solve; I don't know how to read Once I figure that out I should be able to try this design.
Yes, and I can also provide guidance ... However I'm a bit busy these days so I can't give any ETAs.. |
So I've been studying futures lately and I think I'm getting back to the thread-based API idea. futures ecosystem (tokio and co) are such a mess and it's impossible to do even the simplest things like reading stdin as So maybe something like this would work:
In addition, I want to keep current backend working without spawning any threads so another interface should be supported.. More on this later. |
I've been fiddling with futures and they look like a hot mess to me too. My compile errors see unintelligible and I can't find anyone on IRC or discord that can help. Fortunately, this now exists: https://crates.io/crates/may |
I was excited about MAY too, but unfortunately it seems like it's deeply broken: Xudong-Huang/may#6 the problem is not specific to MAY, we just can't implement work-stealing threads in Rust because of issues with TLS. |
If I wanted to make a fork of tiny that can speak to other IRC-like chat services where would I start? I've poked around in the code and can't seem to find the part that reads from a connection and converts to an internal data structure.
The text was updated successfully, but these errors were encountered: