239 lines
6.9 KiB
Rust
239 lines
6.9 KiB
Rust
#[macro_use]
|
|
extern crate lazy_static;
|
|
|
|
extern crate native_tls;
|
|
extern crate regex;
|
|
|
|
use cursive::align::HAlign;
|
|
use cursive::theme::Effect;
|
|
use cursive::traits::*;
|
|
use cursive::utils::markup::StyledString;
|
|
use cursive::view::Scrollable;
|
|
use cursive::views::{Dialog, EditView, Panel, SelectView};
|
|
use cursive::Cursive;
|
|
|
|
use std::str::FromStr;
|
|
use url::Url;
|
|
|
|
mod status;
|
|
use status::Status;
|
|
|
|
mod link;
|
|
use link::Link;
|
|
|
|
mod content;
|
|
mod history;
|
|
|
|
const HELP: &str = "Welcome to Asuka Gemini browser!
|
|
|
|
Press g to visit an URL
|
|
Press h to show/hide history
|
|
Press q to exit
|
|
";
|
|
|
|
fn main() {
|
|
history::init();
|
|
|
|
let mut siv = Cursive::default();
|
|
|
|
let mut select = SelectView::new();
|
|
|
|
select.add_all_str(HELP.lines());
|
|
select.set_on_submit(|s, link| {
|
|
follow_link(s, link);
|
|
});
|
|
|
|
siv.add_fullscreen_layer(
|
|
Dialog::around(Panel::new(
|
|
select.with_id("main").scrollable().full_screen(),
|
|
))
|
|
.title("Asuka Browser")
|
|
.h_align(HAlign::Center)
|
|
.button("Quit", |s| s.quit())
|
|
.with_id("container"),
|
|
);
|
|
|
|
// We can quit by pressing q
|
|
siv.add_global_callback('q', |s| s.quit());
|
|
// pressing g prompt for an URL
|
|
siv.add_global_callback('g', |s| prompt_for_url(s));
|
|
// pressing h shows/hides history
|
|
siv.add_global_callback('h', |s| show_history(s));
|
|
|
|
siv.run();
|
|
}
|
|
|
|
fn prompt_for_url(s: &mut Cursive) {
|
|
s.add_layer(
|
|
Dialog::new()
|
|
.title("Enter URL")
|
|
// Padding is (left, right, top, bottom)
|
|
.padding((1, 1, 1, 0))
|
|
.content(EditView::new().on_submit(goto_url).fixed_width(20))
|
|
.with_id("url_popup"),
|
|
);
|
|
}
|
|
|
|
fn goto_url(s: &mut Cursive, url: &str) {
|
|
// Prepend gemini scheme if needed
|
|
if url.starts_with("gemini://") {
|
|
visit_url(s, &Url::parse(url).unwrap())
|
|
} else {
|
|
let url = format!("gemini://{}", url);
|
|
visit_url(s, &Url::parse(&url).unwrap())
|
|
};
|
|
}
|
|
|
|
fn show_history(s: &mut Cursive) {
|
|
// Hide popup when pressing h on an opened popup
|
|
if s.find_id::<Dialog>("history_popup").is_some() {
|
|
s.pop_layer();
|
|
return;
|
|
}
|
|
|
|
let mut select = SelectView::new();
|
|
|
|
for url in history::content() {
|
|
let url_s = url.as_str();
|
|
select.add_item_str(url_s);
|
|
}
|
|
|
|
select.set_on_submit(|s, link| {
|
|
s.pop_layer();
|
|
follow_link(s, link);
|
|
});
|
|
|
|
s.add_layer(
|
|
Dialog::around(select.scrollable().fixed_size((50, 10)))
|
|
.title("History")
|
|
.with_id("history_popup"),
|
|
);
|
|
}
|
|
|
|
fn visit_url(s: &mut Cursive, url: &Url) {
|
|
// Close URL popup if any
|
|
if s.find_id::<Dialog>("url_popup").is_some() {
|
|
s.pop_layer();
|
|
}
|
|
|
|
match make_absolute(url.as_str()) {
|
|
Ok(url) => match content::get_data(&url) {
|
|
Ok(new_content) => {
|
|
history::append(url.as_str());
|
|
draw_content(s, url, new_content);
|
|
}
|
|
Err(msg) => {
|
|
s.add_layer(Dialog::info(msg));
|
|
}
|
|
},
|
|
Err(_) => {
|
|
s.add_layer(Dialog::info(format!("Could not parse {}", url.as_str())));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_content(s: &mut Cursive, url: Url, content: String) {
|
|
let mut main_view = s.find_id::<SelectView>("main").unwrap();
|
|
let mut container = s.find_id::<Dialog>("container").unwrap();
|
|
|
|
// handle response status
|
|
if let Some(status_line) = content.lines().next() {
|
|
if let Ok(status) = Status::from_str(status_line) {
|
|
match status {
|
|
Status::Success(_meta) => {}
|
|
Status::Gone(_meta) => {
|
|
s.add_layer(Dialog::info("Sorry page is gone."));
|
|
return;
|
|
}
|
|
Status::RedirectTemporary(new_url) | Status::RedirectPermanent(new_url) => {
|
|
follow_link(s, &new_url)
|
|
}
|
|
Status::TransientCertificateRequired(_meta)
|
|
| Status::AuthorisedCertificatedRequired(_meta) => {
|
|
s.add_layer(Dialog::info(
|
|
"You need a valid certificate to access this page.",
|
|
));
|
|
return;
|
|
}
|
|
other_status => {
|
|
s.add_layer(Dialog::info(format!("ERROR: {:?}", other_status)));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// set title and clear old content
|
|
container.set_title(url.as_str());
|
|
main_view.clear();
|
|
|
|
// draw new content lines
|
|
for line in content.lines().skip(1) {
|
|
match Link::from_str(line) {
|
|
Ok(link) => match link {
|
|
Link::Http(_url, label) => {
|
|
let mut formatted = StyledString::new();
|
|
let www_label = format!("[WWW] {}", label);
|
|
formatted.append(StyledString::styled(www_label, Effect::Italic));
|
|
|
|
main_view.add_item(formatted, String::from("0"))
|
|
}
|
|
Link::Gopher(_url, label) => {
|
|
let mut formatted = StyledString::new();
|
|
let gopher_label = format!("[Gopher] {}", label);
|
|
formatted.append(StyledString::styled(gopher_label, Effect::Italic));
|
|
|
|
main_view.add_item(formatted, String::from("0"))
|
|
}
|
|
Link::Gemini(url, label) => {
|
|
let mut formatted = StyledString::new();
|
|
formatted.append(StyledString::styled(label, Effect::Underline));
|
|
|
|
main_view.add_item(formatted, url.to_string())
|
|
}
|
|
Link::Relative(url, label) => {
|
|
let mut formatted = StyledString::new();
|
|
formatted.append(StyledString::styled(label, Effect::Underline));
|
|
|
|
main_view.add_item(formatted, url.to_string())
|
|
}
|
|
Link::Unknown(_, _) => (),
|
|
},
|
|
Err(_) => main_view.add_item(str::replace(line, "\t", " "), String::from("0")),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_gemini_link(line: &str) -> bool {
|
|
line != "0"
|
|
}
|
|
|
|
fn follow_link(s: &mut Cursive, line: &str) {
|
|
if is_gemini_link(line) {
|
|
let next_url = make_absolute(line).expect("Not an URL");
|
|
visit_url(s, &next_url)
|
|
}
|
|
}
|
|
|
|
fn make_absolute(url: &str) -> Result<url::Url, url::ParseError> {
|
|
// Creates an absolute link if needed
|
|
match history::get_current_host() {
|
|
Some(host) => {
|
|
if url.starts_with("gemini://") {
|
|
Url::parse(url)
|
|
} else if url.starts_with('/') {
|
|
Url::parse(&format!("gemini://{}{}", host, url))
|
|
} else {
|
|
Url::parse(&format!("gemini://{}/{}", host, url))
|
|
}
|
|
}
|
|
None => {
|
|
if url.starts_with("gemini://") {
|
|
Url::parse(url)
|
|
} else {
|
|
Url::parse(&format!("gemini://{}", url))
|
|
}
|
|
}
|
|
}
|
|
}
|