mirror of
https://github.com/YGGverse/btracker-gemini.git
synced 2026-03-31 17:15:30 +00:00
implement search feature
This commit is contained in:
parent
fdd6e10e3a
commit
96c3df1b41
3 changed files with 119 additions and 84 deletions
160
src/main.rs
160
src/main.rs
|
|
@ -1,15 +1,13 @@
|
|||
mod config;
|
||||
mod format;
|
||||
mod route;
|
||||
|
||||
use anyhow::Result;
|
||||
use btracker_fs::public::{Order, Public, Sort, Torrent};
|
||||
use chrono::Local;
|
||||
use clap::Parser;
|
||||
use config::Config;
|
||||
use librqbit_core::{
|
||||
Id20,
|
||||
torrent_metainfo::{TorrentMetaV1Owned, torrent_from_bytes},
|
||||
};
|
||||
use librqbit_core::torrent_metainfo::{TorrentMetaV1Owned, torrent_from_bytes};
|
||||
use log::*;
|
||||
use native_tls::{HandshakeError, Identity, TlsAcceptor, TlsStream};
|
||||
use std::{
|
||||
|
|
@ -17,7 +15,6 @@ use std::{
|
|||
io::{Read, Write},
|
||||
net::{SocketAddr, TcpListener, TcpStream},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
|
@ -155,23 +152,16 @@ fn response(
|
|||
peer: &SocketAddr,
|
||||
stream: &mut TlsStream<TcpStream>,
|
||||
) {
|
||||
use route::Route;
|
||||
|
||||
debug!("Incoming request from `{peer}` to `{}`", request.url.path());
|
||||
let path = request.url.path().trim_matches('/');
|
||||
// try index page
|
||||
if path.is_empty() {
|
||||
send(
|
||||
&match index(
|
||||
config,
|
||||
public,
|
||||
request.url.query(),
|
||||
request.url.query_pairs().find_map(|a| {
|
||||
if a.0 == "page" {
|
||||
a.1.parse::<usize>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
) {
|
||||
|
||||
let route = Route::from_url(&request.url);
|
||||
|
||||
// try index page, including optional page value
|
||||
match route {
|
||||
Route::List { page } => send(
|
||||
&match list(config, public, request.url.query(), page) {
|
||||
Ok(data) => response::success::Default {
|
||||
data: data.as_bytes(),
|
||||
meta: response::success::default::Meta {
|
||||
|
|
@ -193,11 +183,8 @@ fn response(
|
|||
error!("Internal server error on handle peer `{peer}` request: `{e}`")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
// try search
|
||||
else if path == "search" {
|
||||
send(
|
||||
),
|
||||
Route::Search => send(
|
||||
&response::Input::Default(response::input::Default {
|
||||
message: Some("Keyword, file, hash...".into()),
|
||||
})
|
||||
|
|
@ -211,58 +198,55 @@ fn response(
|
|||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
// try info page
|
||||
else if let Ok(id) = Id20::from_str(path)
|
||||
&& let Some(torrent) = public.torrent(id)
|
||||
{
|
||||
send(
|
||||
&match info(config, torrent) {
|
||||
Ok(data) => response::success::Default {
|
||||
data: data.as_bytes(),
|
||||
meta: response::success::default::Meta {
|
||||
mime: "text/gemini".to_string(),
|
||||
},
|
||||
}
|
||||
.into_bytes(),
|
||||
Err(e) => {
|
||||
error!("Internal server error on handle peer `{peer}` request: `{e}`");
|
||||
response::failure::temporary::General {
|
||||
message: Some("Internal server error".to_string()),
|
||||
),
|
||||
Route::Info(id) => match public.torrent(id) {
|
||||
Some(torrent) => send(
|
||||
&match info(config, torrent) {
|
||||
Ok(data) => response::success::Default {
|
||||
data: data.as_bytes(),
|
||||
meta: response::success::default::Meta {
|
||||
mime: "text/gemini".to_string(),
|
||||
},
|
||||
}
|
||||
.into_bytes()
|
||||
}
|
||||
},
|
||||
stream,
|
||||
|result| {
|
||||
if let Err(e) = result {
|
||||
error!("Internal server error on handle peer `{peer}` request: `{e}`")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
// not found
|
||||
else {
|
||||
warn!(
|
||||
"Requested resource `{}` not found by peer `{peer}`",
|
||||
request.url.as_str()
|
||||
);
|
||||
send(
|
||||
&response::Failure::Permanent(response::failure::Permanent::NotFound(
|
||||
response::failure::permanent::NotFound { message: None },
|
||||
))
|
||||
.into_bytes(),
|
||||
stream,
|
||||
|result| {
|
||||
if let Err(e) = result {
|
||||
error!(
|
||||
"Internal server error on handle peer `{peer}` request `{}`: `{e}`",
|
||||
request.url.as_str()
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.into_bytes(),
|
||||
Err(e) => {
|
||||
error!("Internal server error on handle peer `{peer}` request: `{e}`");
|
||||
response::failure::temporary::General {
|
||||
message: Some("Internal server error".to_string()),
|
||||
}
|
||||
.into_bytes()
|
||||
}
|
||||
},
|
||||
stream,
|
||||
|result| {
|
||||
if let Err(e) = result {
|
||||
error!("Internal server error on handle peer `{peer}` request: `{e}`")
|
||||
}
|
||||
},
|
||||
),
|
||||
None => todo!(),
|
||||
},
|
||||
Route::NotFound => {
|
||||
warn!(
|
||||
"Requested resource `{}` not found by peer `{peer}`",
|
||||
request.url.as_str()
|
||||
);
|
||||
send(
|
||||
&response::Failure::Permanent(response::failure::Permanent::NotFound(
|
||||
response::failure::permanent::NotFound { message: None },
|
||||
))
|
||||
.into_bytes(),
|
||||
stream,
|
||||
|result| {
|
||||
if let Err(e) = result {
|
||||
error!(
|
||||
"Internal server error on handle peer `{peer}` request `{}`: `{e}`",
|
||||
request.url.as_str()
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +267,7 @@ fn send(data: &[u8], stream: &mut TlsStream<TcpStream>, callback: impl FnOnce(Re
|
|||
|
||||
// rotes
|
||||
|
||||
fn index(
|
||||
fn list(
|
||||
config: &Config,
|
||||
public: &Public,
|
||||
keyword: Option<&str>,
|
||||
|
|
@ -291,6 +275,10 @@ fn index(
|
|||
) -> Result<String> {
|
||||
use plurify::Plurify;
|
||||
|
||||
fn query(keyword: Option<&str>) -> String {
|
||||
keyword.map(|k| format!("?{}", k)).unwrap_or_default()
|
||||
}
|
||||
|
||||
let (total, torrents) = public.torrents(
|
||||
keyword,
|
||||
Some((Sort::Modified, Order::Desc)),
|
||||
|
|
@ -347,18 +335,22 @@ fn index(
|
|||
|
||||
if let Some(p) = page {
|
||||
b.push(format!(
|
||||
"=> /{} Back",
|
||||
"=> {}{} Back",
|
||||
if p > 2 {
|
||||
Some(format!("?page={}", p - 1))
|
||||
format!("/{}", p - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_default()
|
||||
"/".into()
|
||||
},
|
||||
query(keyword)
|
||||
))
|
||||
}
|
||||
|
||||
if page.unwrap_or(1) * public.default_limit < total {
|
||||
b.push(format!("=> /?page={} Next", page.map_or(2, |p| p + 1)))
|
||||
b.push(format!(
|
||||
"=> /{}{} Next",
|
||||
page.map_or(2, |p| p + 1),
|
||||
query(keyword)
|
||||
))
|
||||
}
|
||||
|
||||
b.push("\n=> /search Search".into());
|
||||
|
|
|
|||
42
src/route.rs
Normal file
42
src/route.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use librqbit_core::Id20;
|
||||
use regex::Regex;
|
||||
use url::Url;
|
||||
|
||||
pub enum Route {
|
||||
Info(Id20),
|
||||
List { page: Option<usize> },
|
||||
NotFound,
|
||||
Search,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn from_url(url: &Url) -> Self {
|
||||
let p = url.path().to_lowercase();
|
||||
let q = url.query();
|
||||
|
||||
if p.is_empty() {
|
||||
return Self::List { page: None };
|
||||
}
|
||||
|
||||
if let Ok(id) = Id20::from_str(p.trim_matches('/')) {
|
||||
return Self::Info(id);
|
||||
}
|
||||
|
||||
if p == "/search" && q.is_none() {
|
||||
return Self::Search;
|
||||
}
|
||||
|
||||
if Regex::new(r"^/(|search)").unwrap().is_match(&p) {
|
||||
return Self::List {
|
||||
page: Regex::new(r"/(\d+)$").unwrap().captures(&p).map(|c| {
|
||||
c.get(1)
|
||||
.map_or(1, |p| p.as_str().parse::<usize>().unwrap_or(1))
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
Self::NotFound
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue