Compare commits
No commits in common. "improve-search" and "master" have entirely different histories.
improve-se
...
master
|
@ -24,33 +24,6 @@ use serde_json::Value;
|
|||
|
||||
const DEFAULT_SIZE: usize = 25;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
enum SortKey {
|
||||
Name,
|
||||
Size,
|
||||
Seeds,
|
||||
Leeches,
|
||||
Scraped,
|
||||
}
|
||||
|
||||
impl Default for SortKey {
|
||||
fn default() -> Self {
|
||||
Self::Scraped
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
enum SortDirection {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
impl Default for SortDirection {
|
||||
fn default() -> Self {
|
||||
Self::Desc
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
println!("Access me at {}", endpoint());
|
||||
|
@ -96,8 +69,6 @@ struct SearchQuery {
|
|||
q: String,
|
||||
page: Option<usize>,
|
||||
size: Option<usize>,
|
||||
sort_key: Option<SortKey>,
|
||||
sort_dir: Option<SortDirection>,
|
||||
type_: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -130,6 +101,7 @@ async fn new_torrents(
|
|||
db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
|
||||
query: web::Query<NewQuery>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
|
||||
let res = web::block(move || {
|
||||
let conn = db.get().unwrap();
|
||||
new_query(query, conn)
|
||||
|
@ -154,22 +126,20 @@ fn search_query(
|
|||
}
|
||||
|
||||
let page = query.page.unwrap_or(1);
|
||||
let sort_key = query.sort_key.unwrap_or_default();
|
||||
let sort_dir = query.sort_dir.unwrap_or_default();
|
||||
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 = {}, sort_key = {:?}, sort_dir = {:?}",
|
||||
q, type_, page, size, sort_key, sort_dir
|
||||
"query = {}, type = {}, page = {}, size = {}",
|
||||
q, type_, page, size
|
||||
);
|
||||
|
||||
let res = if type_ == "file" {
|
||||
let results = torrent_file_search(conn, q, sort_key, sort_dir, size, offset)?;
|
||||
let results = torrent_file_search(conn, q, size, offset)?;
|
||||
serde_json::to_value(&results).unwrap()
|
||||
} else {
|
||||
let results = torrent_search(conn, q, sort_key, sort_dir, size, offset)?;
|
||||
let results = torrent_search(conn, q, size, offset)?;
|
||||
serde_json::to_value(&results).unwrap()
|
||||
};
|
||||
|
||||
|
@ -217,14 +187,10 @@ struct Torrent {
|
|||
fn torrent_search(
|
||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||
query: &str,
|
||||
sort_key: SortKey,
|
||||
sort_dir: SortDirection,
|
||||
size: usize,
|
||||
offset: usize,
|
||||
) -> Result<Vec<Torrent>, Error> {
|
||||
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 stmt_str = "select * from torrents where name like '%' || ?1 || '%' limit ?2, ?3";
|
||||
let mut stmt = conn.prepare(&stmt_str)?;
|
||||
let torrent_iter = stmt.query_map(
|
||||
params![
|
||||
|
@ -304,14 +270,10 @@ struct File {
|
|||
fn torrent_file_search(
|
||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||
query: &str,
|
||||
sort_key: SortKey,
|
||||
sort_dir: SortDirection,
|
||||
size: usize,
|
||||
offset: usize,
|
||||
) -> Result<Vec<File>, Error> {
|
||||
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 stmt_str = "select * from files where path like '%' || ?1 || '%' limit ?2, ?3";
|
||||
let mut stmt = conn.prepare(&stmt_str).unwrap();
|
||||
let file_iter = stmt.query_map(
|
||||
params![
|
||||
|
@ -375,29 +337,6 @@ fn torrent_file_new(
|
|||
Ok(files)
|
||||
}
|
||||
|
||||
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::Seeds => "seeders",
|
||||
SortKey::Leeches => "leechers",
|
||||
SortKey::Scraped => "scraped_date",
|
||||
};
|
||||
|
||||
format!("{} {}", column, dir)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
|
@ -407,7 +346,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", super::SortKey::Name, super::SortDirection::Desc, 10, 0);
|
||||
let results = super::torrent_search(conn, "sherlock", 10, 0);
|
||||
assert!(results.unwrap().len() > 2);
|
||||
// println!("Query took {:?} seconds.", end - start);
|
||||
}
|
||||
|
|
|
@ -79,13 +79,6 @@ a::-moz-focus-inner {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
th .icon {
|
||||
color: #666666;
|
||||
position: relative;
|
||||
top: 0.125em;
|
||||
left: 0.125em;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: spin 2s linear infinite;
|
||||
width: 20vw;
|
||||
|
|
|
@ -8,12 +8,11 @@ interface State {
|
|||
}
|
||||
|
||||
export class Navbar extends Component<any, State> {
|
||||
|
||||
state: State = {
|
||||
searchParams: {
|
||||
page: 1,
|
||||
q: "",
|
||||
sort_key: 'Name',
|
||||
sort_dir: 'Desc',
|
||||
type_: "torrent"
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +52,7 @@ export class Navbar extends Component<any, State> {
|
|||
value={this.state.searchParams.q}
|
||||
onInput={linkEvent(this, this.searchChange)}></input>
|
||||
<div class="input-group-append">
|
||||
<select value={this.state.searchParams.type_}
|
||||
<select value={this.state.searchParams.type_}
|
||||
onInput={linkEvent(this, this.searchTypeChange)}
|
||||
class="custom-select border-top-0 border-bottom-0 rounded-0">
|
||||
<option disabled>Type</option>
|
||||
|
@ -71,15 +70,13 @@ export class Navbar extends Component<any, State> {
|
|||
|
||||
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.state.searchParams.sort_key}/${i.state.searchParams.sort_dir}`);
|
||||
i.context.router.history.push(`/search/${i.state.searchParams.type_}/${i.state.searchParams.q}/${i.state.searchParams.page}`);
|
||||
}
|
||||
|
||||
searchChange(i: Navbar, event) {
|
||||
let searchParams: SearchParams = {
|
||||
q: event.target.value,
|
||||
page: 1,
|
||||
sort_key: i.state.searchParams.sort_key,
|
||||
sort_dir: i.state.searchParams.sort_dir,
|
||||
type_: i.state.searchParams.type_
|
||||
}
|
||||
i.setState({ searchParams: searchParams });
|
||||
|
@ -89,21 +86,16 @@ export class Navbar extends Component<any, State> {
|
|||
let searchParams: SearchParams = {
|
||||
q: i.state.searchParams.q,
|
||||
page: 1,
|
||||
sort_key: i.state.searchParams.sort_key,
|
||||
sort_dir: i.state.searchParams.sort_dir,
|
||||
type_: event.target.value
|
||||
}
|
||||
i.setState({ searchParams: searchParams });
|
||||
}
|
||||
|
||||
fillSearchField() {
|
||||
let splitPath: Array<string> = this.context.router.route.location.pathname.split("/");
|
||||
if (splitPath.length == 7 && splitPath[1] == 'search')
|
||||
if (splitPath.length == 5 && splitPath[1] == 'search')
|
||||
this.state.searchParams = {
|
||||
page: Number(splitPath[4]),
|
||||
q: splitPath[3],
|
||||
sort_key: splitPath[5],
|
||||
sort_dir: splitPath[6],
|
||||
type_: splitPath[2]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ interface State {
|
|||
}
|
||||
|
||||
export class Search extends Component<any, State> {
|
||||
|
||||
state: State = {
|
||||
results: {
|
||||
torrents: []
|
||||
|
@ -19,8 +20,6 @@ export class Search extends Component<any, State> {
|
|||
searchParams: {
|
||||
q: "",
|
||||
page: 1,
|
||||
sort_key: 'Name',
|
||||
sort_dir: 'Desc',
|
||||
type_: 'torrent'
|
||||
},
|
||||
searching: false
|
||||
|
@ -34,8 +33,6 @@ export class Search extends Component<any, State> {
|
|||
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();
|
||||
|
@ -47,8 +44,6 @@ export class Search extends Component<any, State> {
|
|||
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();
|
||||
|
@ -78,7 +73,7 @@ export class Search extends Component<any, State> {
|
|||
|
||||
fetchData(searchParams: SearchParams): Promise<Array<Torrent>> {
|
||||
let q = encodeURI(searchParams.q);
|
||||
return fetch(`${endpoint}/service/search?q=${q}&page=${searchParams.page}&sort_key=${searchParams.sort_key}&sort_dir=${searchParams.sort_dir}&type_=${searchParams.type_}`)
|
||||
return fetch(`${endpoint}/service/search?q=${q}&page=${searchParams.page}&type_=${searchParams.type_}`)
|
||||
.then(data => data.json());
|
||||
}
|
||||
|
||||
|
@ -111,57 +106,17 @@ export class Search extends Component<any, State> {
|
|||
)
|
||||
}
|
||||
|
||||
sortLink(sortKey: string) {
|
||||
return (
|
||||
<span>
|
||||
<a href={this.buildSortURL(sortKey)}>
|
||||
{sortKey}
|
||||
{this.sortLinkIcon(sortKey)}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
buildSortURL(sortKey: string) {
|
||||
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: string) {
|
||||
const searchParams = this.state.searchParams;
|
||||
if (searchParams.sort_key !== sortKey) {
|
||||
return "";
|
||||
}
|
||||
if (searchParams.sort_dir === "Asc") {
|
||||
return (
|
||||
<svg class="icon icon-arrow-up2"><use xlinkHref="#icon-arrow-up2"></use></svg>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<svg class="icon icon-arrow-down2"><use xlinkHref="#icon-arrow-down2"></use></svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
torrentsTable() {
|
||||
return (
|
||||
<div>
|
||||
<table class="table table-fixed table-hover table-sm table-striped table-hover-purple table-padding">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="search-name-col">{this.sortLink("Name")}</th>
|
||||
<th class="text-right">{this.sortLink("Size")}</th>
|
||||
<th class="text-right">{this.sortLink("Seeds")}</th>
|
||||
<th class="text-right d-none d-md-table-cell">{this.sortLink("Leeches")}</th>
|
||||
<th class="text-right d-none d-md-table-cell">{this.sortLink("Scraped")}</th>
|
||||
<th class="search-name-col">Name</th>
|
||||
<th class="text-right">Size</th>
|
||||
<th class="text-right">Seeds</th>
|
||||
<th class="text-right d-none d-md-table-cell">Leeches</th>
|
||||
<th class="text-right d-none d-md-table-cell">Scraped</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -252,7 +207,7 @@ export class Search extends Component<any, State> {
|
|||
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}/${newSearch.sort_key}/${newSearch.sort_dir}`);
|
||||
a.i.props.history.push(`/search/${newSearch.type_}/${newSearch.q}/${newSearch.page}`);
|
||||
}
|
||||
|
||||
copyLink(evt) {
|
||||
|
|
|
@ -17,7 +17,7 @@ class Index extends Component<any, any> {
|
|||
<div class="mt-3 p-0">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path={`/search/:type_/:q/:page/:sort_key/:sort_dir`} component={Search} />
|
||||
<Route path={`/search/:type_/:q/:page`} component={Search} />
|
||||
</Switch>
|
||||
{this.symbols()}
|
||||
</div>
|
||||
|
@ -49,12 +49,6 @@ class Index extends Component<any, any> {
|
|||
<title>arrow-up</title>
|
||||
<path d="M16 1l-15 15h9v16h12v-16h9z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-arrow-up2" viewBox="0 0 32 32">
|
||||
<path d="M27.414 12.586l-10-10c-0.781-0.781-2.047-0.781-2.828 0l-10 10c-0.781 0.781-0.781 2.047 0 2.828s2.047 0.781 2.828 0l6.586-6.586v19.172c0 1.105 0.895 2 2 2s2-0.895 2-2v-19.172l6.586 6.586c0.39 0.39 0.902 0.586 1.414 0.586s1.024-0.195 1.414-0.586c0.781-0.781 0.781-2.047 0-2.828z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-arrow-down2" viewBox="0 0 32 32">
|
||||
<path d="M27.414 19.414l-10 10c-0.781 0.781-2.047 0.781-2.828 0l-10-10c-0.781-0.781-0.781-2.047 0-2.828s2.047-0.781 2.828 0l6.586 6.586v-19.172c0-1.105 0.895-2 2-2s2 0.895 2 2v19.172l6.586-6.586c0.39-0.39 0.902-0.586 1.414-0.586s1.024 0.195 1.414 0.586c0.781 0.781 0.781 2.047 0 2.828z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-search" viewBox="0 0 32 32">
|
||||
<title>search</title>
|
||||
<path d="M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12c2.972 0 5.691-1.081 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z"></path>
|
||||
|
|
|
@ -3,8 +3,6 @@ export interface SearchParams {
|
|||
page: number;
|
||||
size?: number;
|
||||
type_: string;
|
||||
sort_key: string;
|
||||
sort_dir: string;
|
||||
}
|
||||
|
||||
export interface Results {
|
||||
|
|
Loading…
Reference in New Issue