WIP: improve search #1

Closed
rob wants to merge 26 commits from improve-search into master
3 changed files with 90 additions and 76 deletions
Showing only changes of commit 323ec46982 - Show all commits

View File

@ -16,13 +16,13 @@ use actix_web::{middleware, web, App, HttpResponse, HttpServer};
use failure::Error;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::params;
use std::{env, cmp, io, fmt};
use std::{env, cmp, io};
use std::ops::Deref;
use serde_json::Value;
const DEFAULT_SIZE: usize = 25;
#[derive(Copy, Clone, Deserialize)]
#[derive(Copy, Clone, Debug, Deserialize)]
enum SortKey {
Name,
Size,
@ -37,20 +37,7 @@ impl Default for SortKey {
}
}
impl fmt::Display for SortKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match &self {
Self::Name => "name",
Self::Size => "size_bytes",
Self::Seeders => "seeders",
Self::Leechers => "leechers",
Self::Scraped => "scraped_date",
};
write!(f, "{}", s)
}
}
#[derive(Copy, Clone, Deserialize)]
#[derive(Copy, Clone, Debug, Deserialize)]
enum SortDirection {
Asc,
Desc,
@ -62,16 +49,6 @@ impl Default for SortDirection {
}
}
impl fmt::Display for SortDirection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match &self {
Self::Asc => "asc",
Self::Desc => "desc",
};
write!(f, "{}", s)
}
}
#[actix_rt::main]
async fn main() -> io::Result<()> {
println!("Access me at {}", endpoint());
@ -140,6 +117,8 @@ async fn search(
Ok(res)
}
// TODO what is NewQuery used for?
// it is not a file search, that is done through SearchQuery
#[derive(Deserialize)]
struct NewQuery {
page: Option<usize>,
@ -167,6 +146,29 @@ async fn new_torrents(
Ok(res)
}
fn build_order_clause(type_: &str, sort_key: SortKey, sort_dir: SortDirection) -> String {
let dir = match sort_dir {
SortDirection::Asc => "asc",
SortDirection::Desc => "desc",
};
let column = match sort_key {
SortKey::Name => {
if type_ == "file" {
"path"
} else {
"name"
}
},
SortKey::Size => "size_bytes",
SortKey::Seeders => "seeders",
SortKey::Leechers => "leechers",
SortKey::Scraped => "scraped_date",
};
format!("{} {}", column, dir)
}
fn search_query(
query: web::Query<SearchQuery>,
conn: r2d2::PooledConnection<SqliteConnectionManager>,
@ -185,7 +187,7 @@ fn search_query(
let offset = size * (page - 1);
println!(
"query = {}, type = {}, page = {}, size = {}, sort_key = {}, sort_dir = {}",
"query = {}, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}",
q, type_, page, size, sort_key, sort_dir
);
@ -213,7 +215,7 @@ fn new_query(
let offset = size * (page - 1);
println!(
"new, type = {}, page = {}, size = {}, sort_key = {}, sort_dir = {}",
"new, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}",
type_, page, size, sort_key, sort_dir
);
@ -248,8 +250,9 @@ fn torrent_search(
size: usize,
offset: usize,
) -> Result<Vec<Torrent>, Error> {
// `sort_key` and `sort_dir` are already sanitized and should not be escaped:
let stmt_str = format!("select * from torrents where name like '%' || ?1 || '%' order by {} {} limit ?2, ?3", sort_key, sort_dir);
let order_clause = build_order_clause("torrent", sort_key, sort_dir);
// `order_clause` is already sanitized and should not be escaped:
let stmt_str = format!("select * from torrents where name like '%' || ?1 || '%' order by {} limit ?2, ?3", order_clause);
let mut stmt = conn.prepare(&stmt_str)?;
let torrent_iter = stmt.query_map(
params![
@ -334,8 +337,9 @@ fn torrent_file_search(
size: usize,
offset: usize,
) -> Result<Vec<File>, Error> {
// `sort_key` and `sort_dir` are already sanitized and should not be escaped:
let stmt_str = format!("select * from files where path like '%' || ?1 || '%' order by {} {} limit ?2, ?3", sort_key, sort_dir);
let order_clause = build_order_clause("file", sort_key, sort_dir);
// `order_clause` is already sanitized and should not be escaped:
let stmt_str = format!("select * from files where path like '%' || ?1 || '%' order by {} limit ?2, ?3", order_clause);
let mut stmt = conn.prepare(&stmt_str).unwrap();
let file_iter = stmt.query_map(
params![

View File

@ -8,7 +8,6 @@ interface State {
}
export class Navbar extends Component<any, State> {
state: State = {
searchParams: {
page: 1,
@ -80,20 +79,20 @@ export class Navbar extends Component<any, State> {
let searchParams: SearchParams = {
q: event.target.value,
page: 1,
sort_key: 'Name',
sort_dir: 'Desc',
sort_key: i.state.searchParams.sort_key,
sort_dir: i.state.searchParams.sort_dir,
type_: i.state.searchParams.type_
}
i.setState({ searchParams: searchParams });
}
// TODO why does switching from torrent to file search clear the sort_key?
searchTypeChange(i: Navbar, event) {
console.log("in searchTypeChange");
let searchParams: SearchParams = {
q: i.state.searchParams.q,
page: 1,
sort_key: 'Name',
sort_dir: 'Desc',
sort_key: i.state.searchParams.sort_key,
sort_dir: i.state.searchParams.sort_dir,
type_: event.target.value
}
i.setState({ searchParams: searchParams });

View File

@ -11,39 +11,6 @@ interface State {
searching: Boolean;
}
class SortableLink extends Component {
render({ sortKey, state: { searchParams } }) {
return <span>
<a href={this.buildSearchURL(sortKey, searchParams)}>
{sortKey}
{this.renderIcon(sortKey, searchParams)}
</a>
</span>;
}
buildSearchURL(key, searchParams) {
let page, direction;
if (key === searchParams.sort_key) {
page = searchParams.page;
direction = searchParams.sort_dir === "Asc" ? "Desc" : "Asc";
} else {
page = 1;
direction = "Desc";
}
return `/#/search/${searchParams.type_}/${searchParams.q}/${page}/${key}/${direction}`;
}
renderIcon(sortKey, searchParams) {
if (searchParams.sort_key !== sortKey) {
return "";
}
if (searchParams.sort_dir === "Asc") {
return <svg class="icon icon-arrow-up d-none d-sm-inline mr-1"><use xlinkHref="#icon-arrow-up"></use></svg>;
}
return <svg class="icon icon-arrow-down mr-1"><use xlinkHref="#icon-arrow-down"></use></svg>;
}
)
export class Search extends Component<any, State> {
state: State = {
results: {
@ -64,6 +31,7 @@ export class Search extends Component<any, State> {
}
componentDidMount() {
console.log("comp did mount");
this.state.searchParams = {
page: Number(this.props.match.params.page),
q: this.props.match.params.q,
@ -76,6 +44,7 @@ export class Search extends Component<any, State> {
// Re-do search if the props have changed
componentDidUpdate(lastProps: any) {
console.log("comp did update");
if (lastProps.match && lastProps.match.params !== this.props.match.params) {
this.state.searchParams = {
page: Number(this.props.match.params.page),
@ -144,6 +113,48 @@ export class Search extends Component<any, State> {
)
}
sortLink(sortKey) {
return (
<span>
<a href={this.buildSortURL(sortKey)}>
{sortKey}
{this.sortLinkIcon(sortKey)}
</a>
</span>
)
}
buildSortURL(sortKey) {
const searchParams = this.state.searchParams;
let page, sortDir;
if (sortKey === searchParams.sort_key) {
page = searchParams.page;
sortDir = searchParams.sort_dir === "Asc" ? "Desc" : "Asc";
} else {
page = 1;
sortDir = "Desc";
}
return `/#/search/${searchParams.type_}/${searchParams.q}/${page}/${sortKey}/${sortDir}`;
}
sortLinkIcon(sortKey) {
const searchParams = this.state.searchParams;
if (searchParams.sort_key !== sortKey) {
return "";
}
if (searchParams.sort_dir === "Asc") {
return (
<svg class="icon icon-arrow-up d-none d-sm-inline mr-1"><use xlinkHref="#icon-arrow-up"></use></svg>
)
} else {
return (
<svg class="icon icon-arrow-down mr-1"><use xlinkHref="#icon-arrow-down"></use></svg>
)
}
}
torrentsTable() {
return (
<div>
@ -151,19 +162,19 @@ export class Search extends Component<any, State> {
<thead>
<tr>
<th class="search-name-col">
<SortableLink sortKey="Name" state={this.state}/>
{this.sortLink("Name")}
</th>
<th class="text-right">
<SortableLink sortKey="Size" state={this.state}/>
{this.sortLink("Size")}
</th>
<th class="text-right">
<SortableLink sortKey="Seeders" state={this.state}/>
{this.sortLink("Seeders")}
</th>
<th class="text-right d-none d-md-table-cell">
<SortableLink sortKey="Leechers" state={this.state}/>
{this.sortLink("Leechers")}
</th>
<th class="text-right d-none d-md-table-cell">
<SortableLink sortKey="Scraped" state={this.state}/>
{this.sortLink("Scraped")}
</th>
<th></th>
</tr>