My first program in Rust and an update on my RSS reading habits
I’ve finally found the time to make my first program in Rust and I definitely enjoyed it!
Rust
If you don’t know about the Rust language started by a Mozilla employed developer (and now developed with help from Samsung and the community), have a look at those links:
- The Rust language: memory, ownership and lifetimes - Nicholas Matsakis (linux.conf.au 2014): This describes some interesting features of Rust;
- Rust for Rubyists;
- Main Rust language website;
- A 30-minute Introduction to Rust;
- The Rust Language Tutorial.
So here is my first Rust program. It’s a simple daemon waiting for input on localhost:7777
(Unix socket support was not completed when I started programming in Rust). If it receives an URL, it opens it in a new tab in Firefox.
Update: This code is the first version I made for Rust 0.9 so it is completely outdated. I’ve removed the Gist and created a proper repository with the updated code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
extern mod extra;
use std::from_str::from_str;
use std::io::net::ip::SocketAddr;
use std::io::net::tcp::{TcpListener,TcpStream};
use std::io::{Acceptor,Listener};
use std::run::{Process,ProcessOptions};
use extra::url::Url;
// Note: Error handling should be improved
fn main() {
let browserCommand = "firefox";
let address = "127.0.0.1:7777";
// Prepare a socket listening on localhost:7777
let addr: SocketAddr = from_str(address).expect(format!("Invalid address: {}", address));
let listener = TcpListener::bind(addr).expect(format!("Failed to bind to: {}", address));
let mut acceptor = listener.listen().expect("Could not listen");
// Infinite loop to keep handling new connections.
loop {
let tcpStream = acceptor.accept().expect("Could not accept connection");
debug!("Accepted new connection");
do spawn {
handleClient(tcpStream, browserCommand)
}
}
}
// Accept a new connection and listen for URLs
fn handleClient(tcpStream: TcpStream, browserCommand: &'static str) {
debug!("Spawned new task");
let mut tcpStream = tcpStream;
// Note that as soon as read_to_str() returns, everything sent
// to the socket after this point will be discarded as once
// we're done working with the content we've just read, the
// tcpStream will be freed.
let message = tcpStream.read_to_str();
// Iterate over the lines in the received message
for line in message.lines() {
debug!("Current line is: {}", line);
// This tries to convert the line to a Url struct
let url: Option<Url> = from_str(line);
// If this fails, it returns None, else we got a valid
// URL
match url {
None => { info!("No Url found") }
Some(u) => {
debug!("Found Url in: {}", line);
if checkUrl(&u) {
spawnProcess(&u, browserCommand)
}
}
}
}
}
// Check that the URL is actually usable
fn checkUrl(u: &Url) -> bool {
(u.scheme == ~"http" || u.scheme == ~"https" )
&& u.host != ~""
}
// Spawn a browser to access the URL
fn spawnProcess(u: &Url, command: &'static str) {
debug!("Spawning process {} {}", command, u.to_str());
let pOptions = ProcessOptions::new();
let mut child = Process::new(command, [u.to_str()], pOptions).expect("Could not fork process");
child.finish();
}
Integration with Newsbeuter
I’ve stopped using the Leselys feed reader because it looks like it isn’t being developed anymore, is using mongodb which isn’t really lightweight and I encountered some annoying bugs (which I have not reported yet because I cannot find a reliable way to reproduce them).
Thus I was looking for an alternative and I remembered that I had stumbled upon a nice ncurses RSS aggregator during my previous searches for the Holy Grail of RSS Reader.
In order to get fresh news all the time and to not loose any if I were to not look at them during long periods of time (from week ends to weeks for example), I have to run this in a tmux on my dedicated server. I can easily access it remotely but it makes a simple task not obvious anymore: opening articles in the browser on the client side.
First I tried tunneling ssh connections through the initial connection, but I had to get Firefox to launch and open a tab with X environment variables hard coded:
1
2
#!/usr/bin/env bash
ssh -p 2222 localhost "DISPLAY=:0 XAUTHORITY=/tmp/kde-tim/xauth-1000-_0 firefox ${1}" &> /dev/null &
So this became tedious and I didn’t really like having the remote server allowed direct access to my computers.
So I made this simple Rust program to run on the client side with a simple ssh port forwarding to allow the RSS aggregator on the server to request a tab to be opened on the client:
This is my .confg/newsbeuter/config
config with custom key bindings close to vim ones:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# Use auto-reload, but do no reload at startup
auto-reload yes
suppress-first-reload yes
refresh-on-startup no
# Keep articles for 30 days
keep-articles-days 30
# Reload articles every hour
reload-time 60
# Set custom script as open in browser
browser "/home/tim/remote_firefox.bash %u"
# Unbind most keys to avoid mistakes
unbind-key ENTER
unbind-key r
unbind-key R
unbind-key A
unbind-key C
unbind-key s
unbind-key n
unbind-key p
unbind-key J
unbind-key K
unbind-key ^K
unbind-key o
unbind-key O
unbind-key ^U
unbind-key N
unbind-key l
unbind-key u
unbind-key ^T
unbind-key t
unbind-key e
unbind-key E
unbind-key ^R
unbind-key ^L
unbind-key F
unbind-key f
unbind-key ^F
unbind-key ^B
unbind-key ^E
unbind-key ^N
unbind-key ^P
unbind-key j
unbind-key k
unbind-key D
unbind-key $
unbind-key v
unbind-key ^X
unbind-key ^V
unbind-key ^G
unbind-key |
unbind-key g
unbind-key G
unbind-key UP
unbind-key DOWN
unbind-key PPAGE
unbind-key NPAGE
# Vim-like bindings
bind-key i open
bind-key r reload-all
bind-key a mark-feed-read
bind-key n toogle-article-read
bind-key o open-in-browser-and-mark-read
bind-key k up
bind-key j down
bind-key h next-unread-feed
bind-key l prev-unread-feed
# Set some colors
color listnormal color244 color234
color listfocus color166 color235
color info color136 color235
color background color244 color234
color article color244 color234
And the simple script remote_firefox.bash
used to ask for a new tab:
1
2
#!/usr/bin/env bash
echo -n "${1}" | nc localhost 7777
And finally the full script/shell function I’m using to automate those steps:
1
2
3
4
/home/tim/ffto/ffto &
FFTO_PID=${!}
ssh my.server.com -t -R 7777:localhost:7777 -- tmux attach-session -t newsbeuter
kill -term ${FFTO_PID}
Comments
You can also contact me directly if you have feedback.