~admicos/moonlander

ref: dde394f1b7b8076ad1de98045f46460e524af11c moonlander/moonlander/src/network/geminix.rs -rw-r--r-- 6.7 KiB
dde394f1Ecmel Berk Canlier gemini: Make clippy happy 2 months ago
                                                                                
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use super::NetworkConnector;
use crate::util::format_error;

use crate::ui::{content::ContentType, tab::Msg as TabMsg};
use anyhow::{anyhow, Result};
use gemini::{Chunk, ChunkResponse, FailPermDetails, FailTempDetails, Header, NeedCertDetails};
use mime::Mime;
use relm::Channel;
use url::Url;

impl NetworkConnector {
    pub(super) fn fetch_gemini(&mut self, address: Url, extra: String) {
        let stream = self.relm.stream().clone();

        let cache = self.cache.clone();
        let addr = address.clone();

        let (response_sender, recv) = std::sync::mpsc::channel::<ChunkResponse>();

        let (_, sender) = Channel::new(move |msg: Result<Chunk>| {
            let responder = Some(response_sender.clone());

            match msg {
                Ok(Chunk::Header(Header::Input { sensitive, prompt })) => {
                    let c = ContentType::GeminiInput {
                        sensitive,
                        prompt: prompt.unwrap_or_default(),
                    };

                    NetworkConnector::create_cache(addr.clone(), c.clone(), &cache);
                    stream.emit(TabMsg::SetContent(responder, c))
                }

                Ok(Chunk::Header(Header::Success { mime })) => {
                    let c = match mime.parse::<Mime>() {
                        Ok(mime) => match extra.as_str() {
                            "view-source" => ContentType::Plaintext {
                                charset: mime
                                    .get_param("charset")
                                    .map(|c| c.to_string().to_lowercase()),
                            },
                            "download" => ContentType::Download {
                                mime: mime.to_string(),
                            },
                            _ => ContentType::from_mime(&mime),
                        },

                        Err(e) => {
                            let e = anyhow!(e).context("Mimetype parsing error");
                            let (title, description) = format_error(&e);

                            ContentType::Error { title, description }
                        }
                    };

                    NetworkConnector::create_cache(addr.clone(), c.clone(), &cache);
                    stream.emit(TabMsg::SetContent(responder, c))
                }

                Ok(Chunk::Header(Header::Redirect { temporary: _, to })) => {
                    stream.emit(TabMsg::Redirect(to));
                }

                Ok(Chunk::Header(Header::FailTemp { details, why })) => {
                    let details = details.map_or("Temporary Error", |d| match d {
                        FailTempDetails::ServerUnavailable => "Server Unavailable",
                        FailTempDetails::CgiError => "CGI Error",
                        FailTempDetails::ProxyError => "Proxy Error",
                        FailTempDetails::SlowDown => "Slow Down",
                    });

                    let c = ContentType::Error {
                        title: details.to_string(),
                        description: why.unwrap_or_default(),
                    };

                    stream.emit(TabMsg::SetContent(responder, c));
                }

                Ok(Chunk::Header(Header::FailPerm { details, why })) => {
                    let details = details.map_or("Permanent Error", |d| match d {
                        FailPermDetails::NotFound => "Not Found",
                        FailPermDetails::Gone => "Gone",
                        FailPermDetails::ProxyRequestRefused => "Proxy Request Refused",
                        FailPermDetails::BadRequest => "Bad Request",
                    });

                    let c = ContentType::Error {
                        title: details.to_string(),
                        description: why.unwrap_or_default(),
                    };

                    stream.emit(TabMsg::SetContent(responder, c));
                }

                Ok(Chunk::Header(Header::NeedCert { details, why })) => {
                    let details = details.map_or("Need Client Certificate", |d| match d {
                        NeedCertDetails::NotAuthorized => "Certificate Not Authorized",
                        NeedCertDetails::NotValid => "Certificate Not Valid",
                    });

                    let c = ContentType::Error {
                        title: details.to_string(),
                        description: why.unwrap_or_default(),
                    };

                    stream.emit(TabMsg::SetContent(responder, c));
                }

                Ok(Chunk::Data(data)) => {
                    NetworkConnector::append_cache(&addr, &mut data.clone(), &cache);
                    stream.emit(TabMsg::AppendContent(data));
                }

                Ok(Chunk::Done) => {
                    NetworkConnector::ready_cache(&addr, &cache);
                    stream.emit(TabMsg::FinishContent);
                }

                Err(e) => {
                    let (title, description) = format_error(&e);
                    stream.emit(TabMsg::SetContent(
                        responder,
                        ContentType::Error { title, description },
                    ));
                    stream.emit(TabMsg::FinishContent);
                }
            };
        });

        std::thread::spawn(move || {
            log::debug!("Spawned new Gemini thread");
            if let Err(e) = gemini::request(&address, |chunk| {
                // no point in messaging UI thread on error because if this fails sending
                // that will probably fail too

                if sender.send(Ok(chunk.clone())).is_ok() {
                    if let Chunk::Header(_) = chunk {
                        if let Ok(r) = recv.recv() {
                            if let ChunkResponse::Abort = r {
                                sender.send(Ok(Chunk::Done)).unwrap();
                            }

                            r
                        } else {
                            log::error!("Couldn't receive UI thread response. Did you close a tab? Aborting!");
                            ChunkResponse::Abort
                        }
                    } else {
                        ChunkResponse::Continue
                    }
                } else {
                    log::error!("Couldn't send error to UI thread. Did you close a tab? Aborting!");
                    ChunkResponse::Abort
                }
            }) {
                if sender
                    .send(Err(anyhow!(e).context("Gemini request failed")))
                    .is_err()
                {
                    log::error!(
                        "Couldn't send gemini request result to UI thread. Did you close a tab?"
                    )
                }
            }
        });
    }
}