diff --git a/server/service/src/main.rs b/server/service/src/main.rs index 1e36640..39ea3e5 100644 --- a/server/service/src/main.rs +++ b/server/service/src/main.rs @@ -15,15 +15,55 @@ use actix_files::NamedFile; use actix_web::{middleware, web, App, HttpResponse, HttpServer}; use failure::Error; use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::params; +use rusqlite::{params, named_params, NO_PARAMS}; use std::env; use std::ops::Deref; use std::cmp; use std::io; +use std::fmt; use serde_json::Value; const DEFAULT_SIZE: usize = 25; +enum SortDir { + Asc, + Desc, +} + +enum Sort { + Key(String), + Dir(SortDir), +} + + + +#[derive(Copy, Clone, Debug, Deserialize)] +enum SortKey { + Name, + Size, + Seeds, + Leeches, + Scraped, +} + +impl fmt::Display for SortKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Copy, Clone, Debug, Deserialize)] +enum SortDir { + Asc, + Desc, +} + +impl fmt::Display for SortDir { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + #[actix_rt::main] async fn main() -> io::Result<()> { println!("Access me at {}", endpoint()); @@ -69,6 +109,8 @@ struct SearchQuery { q: String, page: Option, size: Option, + sort_key: Option, + sort_dir: Option, type_: Option, } @@ -94,6 +136,8 @@ async fn search( struct NewQuery { page: Option, size: Option, + sort_key: Option, + sort_dir: Option, type_: Option, } @@ -126,20 +170,22 @@ fn search_query( } let page = query.page.unwrap_or(1); + let sort_key = query.sort_key.unwrap_or(SortKey::Name); + let sort_dir = query.sort_dir.unwrap_or(SortDir::Desc); let size = cmp::min(100, query.size.unwrap_or(DEFAULT_SIZE)); let type_ = query.type_.as_ref().map_or("torrent", String::deref); let offset = size * (page - 1); - println!( - "query = {}, type = {}, page = {}, size = {}", - q, type_, page, size + dbg!( + "query = {}, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}", + q, type_, page, size, sort_key, sort_dir ); let res = if type_ == "file" { let results = torrent_file_search(conn, q, size, offset)?; serde_json::to_value(&results).unwrap() } else { - let results = torrent_search(conn, q, size, offset)?; + let results = torrent_search(conn, q, sort_key, sort_dir, size, offset)?; serde_json::to_value(&results).unwrap() }; @@ -153,12 +199,14 @@ fn new_query( let page = query.page.unwrap_or(1); let size = cmp::min(100, query.size.unwrap_or(DEFAULT_SIZE)); + let sort_key = query.sort_key.as_ref().unwrap_or(&SortKey::Name); + let sort_dir = query.sort_dir.as_ref().unwrap_or(&SortDir::Desc); let type_ = query.type_.as_ref().map_or("torrent", String::deref); let offset = size * (page - 1); - println!( - "new, type = {}, page = {}, size = {}", - type_, page, size + dbg!( + "new, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}", + type_, page, size, sort_key, sort_dir ); let res = if type_ == "file" { @@ -187,18 +235,23 @@ struct Torrent { fn torrent_search( conn: r2d2::PooledConnection, query: &str, + sort_key: SortKey, + sort_dir: SortDir, size: usize, offset: usize, ) -> Result, Error> { - let stmt_str = "select * from torrents where name like '%' || ?1 || '%' limit ?2, ?3"; + let stmt_str = format!("select * from torrents where name like '%' || :query || '%' order by :sort_key {} limit :offset, :size", sort_dir.to_string().to_lowercase()); let mut stmt = conn.prepare(&stmt_str)?; - let torrent_iter = stmt.query_map( - params![ - query.replace(" ", "%"), - offset.to_string(), - size.to_string(), - ], + let torrent_iter = stmt.query_map_named( + named_params!{ + ":query": query.replace(" ", "%"), + ":sort_key": sort_key.to_string().to_lowercase(), + ":offset": offset.to_string(), + ":size": size.to_string(), + }, |row| { + let size: isize = row.get(2)?; + println!("got size {:?}", size); Ok(Torrent { infohash: row.get(0)?, name: row.get(1)?, @@ -346,7 +399,7 @@ mod tests { let manager = SqliteConnectionManager::file(super::torrents_db_file()); let pool = r2d2::Pool::builder().max_size(15).build(manager).unwrap(); let conn = pool.get().unwrap(); - let results = super::torrent_search(conn, "sherlock", 10, 0); + let results = super::torrent_search(conn, "sherlock", 10, 0, SortKey::Name, SortDir::Desc); assert!(results.unwrap().len() > 2); // println!("Query took {:?} seconds.", end - start); } diff --git a/server/ui/src/components/navbar.tsx b/server/ui/src/components/navbar.tsx index 3ee9d03..8355cbe 100644 --- a/server/ui/src/components/navbar.tsx +++ b/server/ui/src/components/navbar.tsx @@ -13,6 +13,8 @@ export class Navbar extends Component { searchParams: { page: 1, q: "", + sort_key: 'Name', + sort_dir: 'Desc', type_: "torrent" } } @@ -70,13 +72,15 @@ export class Navbar extends Component { search(i: Navbar, event) { event.preventDefault(); - i.context.router.history.push(`/search/${i.state.searchParams.type_}/${i.state.searchParams.q}/${i.state.searchParams.page}`); + i.context.router.history.push(`/search/${i.state.searchParams.type_}/${i.state.searchParams.q}/${i.state.searchParams.page}/${i.state.searchParams.sort_key}/${i.state.searchParams.sort_dir}`); } searchChange(i: Navbar, event) { let searchParams: SearchParams = { q: event.target.value, page: 1, + sort_key: 'Name', + sort_dir: 'Desc', type_: i.state.searchParams.type_ } i.setState({ searchParams: searchParams }); @@ -86,6 +90,8 @@ export class Navbar extends Component { let searchParams: SearchParams = { q: i.state.searchParams.q, page: 1, + sort_key: 'Name', + sort_dir: 'Desc', type_: event.target.value } i.setState({ searchParams: searchParams }); @@ -96,6 +102,8 @@ export class Navbar extends Component { this.state.searchParams = { page: Number(splitPath[4]), q: splitPath[3], + sort_key: splitPath[5], + sort_dir: splitPath[6], type_: splitPath[2] }; } diff --git a/server/ui/src/components/search.tsx b/server/ui/src/components/search.tsx index 821a859..a7406b9 100644 --- a/server/ui/src/components/search.tsx +++ b/server/ui/src/components/search.tsx @@ -11,6 +11,25 @@ interface State { searching: Boolean; } + +function buildSearchURL(sort_key, {state: { searchParams }) { + console.log("got search URL sort_key", sort_key, "searchParams", searchParams, "thing", searchParams); + + //let searchParams = thing.state.searchParams; + + + if (sort_key === searchParams.sort_key) { + const sort_dir = searchParams.sort_dir === "Asc" ? "Desc" : "Asc"; + searchParams.sort_dir = sort_dir; + console.log("no change in sort key from", sort_key, "so switch direction instead.") + } else { + console.log("change sort key from", searchParams.sort_key, "to", sort_key); + searchParams.sort_key = sort_key; + } + const url = `/#/search/${searchParams.type_}/${searchParams.q}/${searchParams.page}/${searchParams.sort_key}/${searchParams.sort_dir}` + return url; +} + export class Search extends Component { state: State = { @@ -20,6 +39,8 @@ export class Search extends Component { searchParams: { q: "", page: 1, + sort_key: 'Name', + sort_dir: 'Desc', type_: 'torrent' }, searching: false @@ -30,9 +51,12 @@ export class Search extends Component { } componentDidMount() { + console.log("got props", this.props); this.state.searchParams = { page: Number(this.props.match.params.page), q: this.props.match.params.q, + sort_key: this.props.match.params.sort_key, + sort_dir: this.props.match.params.sort_dir, type_: this.props.match.params.type_ } this.search(); @@ -44,6 +68,8 @@ export class Search extends Component { this.state.searchParams = { page: Number(this.props.match.params.page), q: this.props.match.params.q, + sort_key: this.props.match.params.sort_key, + sort_dir: this.props.match.params.sort_dir, type_: this.props.match.params.type_ } this.search(); @@ -52,6 +78,7 @@ export class Search extends Component { } search() { + console.log("in search, state", this.state); if (!!this.state.searchParams.q) { this.setState({ searching: true, results: { torrents: [] } }); this.fetchData(this.state.searchParams) @@ -73,7 +100,7 @@ export class Search extends Component { fetchData(searchParams: SearchParams): Promise> { let q = encodeURI(searchParams.q); - return fetch(`${endpoint}/service/search?q=${q}&page=${searchParams.page}&type_=${searchParams.type_}`) + return fetch(`${endpoint}/service/search?q=${q}&page=${searchParams.page}&sort_key=${searchParams.sort_key}&sort_dir=${searchParams.sort_dir}&type_=${searchParams.type_}`) .then(data => data.json()); } @@ -112,11 +139,27 @@ export class Search extends Component { - - - - - + + + + + @@ -207,7 +250,7 @@ export class Search extends Component { switchPage(a: { i: Search, nextPage: boolean }) { let newSearch = a.i.state.searchParams; newSearch.page += (a.nextPage) ? 1 : -1; - a.i.props.history.push(`/search/${newSearch.type_}/${newSearch.q}/${newSearch.page}`); + a.i.props.history.push(`/search/${newSearch.type_}/${newSearch.q}/${newSearch.page}/${newSearch.sort_key}/${newSearch.sort_dir}`); } copyLink(evt) { diff --git a/server/ui/src/index.tsx b/server/ui/src/index.tsx index b64dd81..dbdb31b 100644 --- a/server/ui/src/index.tsx +++ b/server/ui/src/index.tsx @@ -17,7 +17,7 @@ class Index extends Component {
- + {this.symbols()}
diff --git a/server/ui/src/interfaces.ts b/server/ui/src/interfaces.ts index 2fbf4e2..51b5332 100644 --- a/server/ui/src/interfaces.ts +++ b/server/ui/src/interfaces.ts @@ -3,6 +3,8 @@ export interface SearchParams { page: number; size?: number; type_: string; + sort_key: string; + sort_dir: string; } export interface Results {
NameSizeSeedsLeechesScraped + + Name + + + + Size + + + Seeds + + Leeches + + Scraped +