WIP: semi-working, pre-refactor
This commit is contained in:
parent
5f4d6b4493
commit
d08921573f
|
@ -15,15 +15,55 @@ use actix_files::NamedFile;
|
||||||
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
|
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
use rusqlite::params;
|
use rusqlite::{params, named_params, NO_PARAMS};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::fmt;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
const DEFAULT_SIZE: usize = 25;
|
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]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
println!("Access me at {}", endpoint());
|
println!("Access me at {}", endpoint());
|
||||||
|
@ -69,6 +109,8 @@ struct SearchQuery {
|
||||||
q: String,
|
q: String,
|
||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
size: Option<usize>,
|
size: Option<usize>,
|
||||||
|
sort_key: Option<SortKey>,
|
||||||
|
sort_dir: Option<SortDir>,
|
||||||
type_: Option<String>,
|
type_: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +136,8 @@ async fn search(
|
||||||
struct NewQuery {
|
struct NewQuery {
|
||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
size: Option<usize>,
|
size: Option<usize>,
|
||||||
|
sort_key: Option<SortKey>,
|
||||||
|
sort_dir: Option<SortDir>,
|
||||||
type_: Option<String>,
|
type_: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,20 +170,22 @@ fn search_query(
|
||||||
}
|
}
|
||||||
|
|
||||||
let page = query.page.unwrap_or(1);
|
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 size = cmp::min(100, query.size.unwrap_or(DEFAULT_SIZE));
|
||||||
let type_ = query.type_.as_ref().map_or("torrent", String::deref);
|
let type_ = query.type_.as_ref().map_or("torrent", String::deref);
|
||||||
let offset = size * (page - 1);
|
let offset = size * (page - 1);
|
||||||
|
|
||||||
println!(
|
dbg!(
|
||||||
"query = {}, type = {}, page = {}, size = {}",
|
"query = {}, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}",
|
||||||
q, type_, page, size
|
q, type_, page, size, sort_key, sort_dir
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = if type_ == "file" {
|
let res = if type_ == "file" {
|
||||||
let results = torrent_file_search(conn, q, size, offset)?;
|
let results = torrent_file_search(conn, q, size, offset)?;
|
||||||
serde_json::to_value(&results).unwrap()
|
serde_json::to_value(&results).unwrap()
|
||||||
} else {
|
} 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()
|
serde_json::to_value(&results).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -153,12 +199,14 @@ fn new_query(
|
||||||
|
|
||||||
let page = query.page.unwrap_or(1);
|
let page = query.page.unwrap_or(1);
|
||||||
let size = cmp::min(100, query.size.unwrap_or(DEFAULT_SIZE));
|
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 type_ = query.type_.as_ref().map_or("torrent", String::deref);
|
||||||
let offset = size * (page - 1);
|
let offset = size * (page - 1);
|
||||||
|
|
||||||
println!(
|
dbg!(
|
||||||
"new, type = {}, page = {}, size = {}",
|
"new, type = {}, page = {}, size = {}, sort_key = {:?}, sort_dir = {:?}",
|
||||||
type_, page, size
|
type_, page, size, sort_key, sort_dir
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = if type_ == "file" {
|
let res = if type_ == "file" {
|
||||||
|
@ -187,18 +235,23 @@ struct Torrent {
|
||||||
fn torrent_search(
|
fn torrent_search(
|
||||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
sort_key: SortKey,
|
||||||
|
sort_dir: SortDir,
|
||||||
size: usize,
|
size: usize,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
) -> Result<Vec<Torrent>, Error> {
|
) -> Result<Vec<Torrent>, 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 mut stmt = conn.prepare(&stmt_str)?;
|
||||||
let torrent_iter = stmt.query_map(
|
let torrent_iter = stmt.query_map_named(
|
||||||
params![
|
named_params!{
|
||||||
query.replace(" ", "%"),
|
":query": query.replace(" ", "%"),
|
||||||
offset.to_string(),
|
":sort_key": sort_key.to_string().to_lowercase(),
|
||||||
size.to_string(),
|
":offset": offset.to_string(),
|
||||||
],
|
":size": size.to_string(),
|
||||||
|
},
|
||||||
|row| {
|
|row| {
|
||||||
|
let size: isize = row.get(2)?;
|
||||||
|
println!("got size {:?}", size);
|
||||||
Ok(Torrent {
|
Ok(Torrent {
|
||||||
infohash: row.get(0)?,
|
infohash: row.get(0)?,
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
|
@ -346,7 +399,7 @@ mod tests {
|
||||||
let manager = SqliteConnectionManager::file(super::torrents_db_file());
|
let manager = SqliteConnectionManager::file(super::torrents_db_file());
|
||||||
let pool = r2d2::Pool::builder().max_size(15).build(manager).unwrap();
|
let pool = r2d2::Pool::builder().max_size(15).build(manager).unwrap();
|
||||||
let conn = pool.get().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);
|
assert!(results.unwrap().len() > 2);
|
||||||
// println!("Query took {:?} seconds.", end - start);
|
// println!("Query took {:?} seconds.", end - start);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ export class Navbar extends Component<any, State> {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
page: 1,
|
page: 1,
|
||||||
q: "",
|
q: "",
|
||||||
|
sort_key: 'Name',
|
||||||
|
sort_dir: 'Desc',
|
||||||
type_: "torrent"
|
type_: "torrent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,13 +72,15 @@ export class Navbar extends Component<any, State> {
|
||||||
|
|
||||||
search(i: Navbar, event) {
|
search(i: Navbar, event) {
|
||||||
event.preventDefault();
|
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) {
|
searchChange(i: Navbar, event) {
|
||||||
let searchParams: SearchParams = {
|
let searchParams: SearchParams = {
|
||||||
q: event.target.value,
|
q: event.target.value,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
sort_key: 'Name',
|
||||||
|
sort_dir: 'Desc',
|
||||||
type_: i.state.searchParams.type_
|
type_: i.state.searchParams.type_
|
||||||
}
|
}
|
||||||
i.setState({ searchParams: searchParams });
|
i.setState({ searchParams: searchParams });
|
||||||
|
@ -86,6 +90,8 @@ export class Navbar extends Component<any, State> {
|
||||||
let searchParams: SearchParams = {
|
let searchParams: SearchParams = {
|
||||||
q: i.state.searchParams.q,
|
q: i.state.searchParams.q,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
sort_key: 'Name',
|
||||||
|
sort_dir: 'Desc',
|
||||||
type_: event.target.value
|
type_: event.target.value
|
||||||
}
|
}
|
||||||
i.setState({ searchParams: searchParams });
|
i.setState({ searchParams: searchParams });
|
||||||
|
@ -96,6 +102,8 @@ export class Navbar extends Component<any, State> {
|
||||||
this.state.searchParams = {
|
this.state.searchParams = {
|
||||||
page: Number(splitPath[4]),
|
page: Number(splitPath[4]),
|
||||||
q: splitPath[3],
|
q: splitPath[3],
|
||||||
|
sort_key: splitPath[5],
|
||||||
|
sort_dir: splitPath[6],
|
||||||
type_: splitPath[2]
|
type_: splitPath[2]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,25 @@ interface State {
|
||||||
searching: Boolean;
|
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<any, State> {
|
export class Search extends Component<any, State> {
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
|
@ -20,6 +39,8 @@ export class Search extends Component<any, State> {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
q: "",
|
q: "",
|
||||||
page: 1,
|
page: 1,
|
||||||
|
sort_key: 'Name',
|
||||||
|
sort_dir: 'Desc',
|
||||||
type_: 'torrent'
|
type_: 'torrent'
|
||||||
},
|
},
|
||||||
searching: false
|
searching: false
|
||||||
|
@ -30,9 +51,12 @@ export class Search extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
console.log("got props", this.props);
|
||||||
this.state.searchParams = {
|
this.state.searchParams = {
|
||||||
page: Number(this.props.match.params.page),
|
page: Number(this.props.match.params.page),
|
||||||
q: this.props.match.params.q,
|
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_
|
type_: this.props.match.params.type_
|
||||||
}
|
}
|
||||||
this.search();
|
this.search();
|
||||||
|
@ -44,6 +68,8 @@ export class Search extends Component<any, State> {
|
||||||
this.state.searchParams = {
|
this.state.searchParams = {
|
||||||
page: Number(this.props.match.params.page),
|
page: Number(this.props.match.params.page),
|
||||||
q: this.props.match.params.q,
|
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_
|
type_: this.props.match.params.type_
|
||||||
}
|
}
|
||||||
this.search();
|
this.search();
|
||||||
|
@ -52,6 +78,7 @@ export class Search extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
search() {
|
search() {
|
||||||
|
console.log("in search, state", this.state);
|
||||||
if (!!this.state.searchParams.q) {
|
if (!!this.state.searchParams.q) {
|
||||||
this.setState({ searching: true, results: { torrents: [] } });
|
this.setState({ searching: true, results: { torrents: [] } });
|
||||||
this.fetchData(this.state.searchParams)
|
this.fetchData(this.state.searchParams)
|
||||||
|
@ -73,7 +100,7 @@ export class Search extends Component<any, State> {
|
||||||
|
|
||||||
fetchData(searchParams: SearchParams): Promise<Array<Torrent>> {
|
fetchData(searchParams: SearchParams): Promise<Array<Torrent>> {
|
||||||
let q = encodeURI(searchParams.q);
|
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());
|
.then(data => data.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,11 +139,27 @@ export class Search extends Component<any, State> {
|
||||||
<table class="table table-fixed table-hover table-sm table-striped table-hover-purple table-padding">
|
<table class="table table-fixed table-hover table-sm table-striped table-hover-purple table-padding">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="search-name-col">Name</th>
|
<th class="search-name-col">
|
||||||
<th class="text-right">Size</th>
|
<a
|
||||||
<th class="text-right">Seeds</th>
|
href={buildSearchURL('Name', this)}>
|
||||||
<th class="text-right d-none d-md-table-cell">Leeches</th>
|
Name
|
||||||
<th class="text-right d-none d-md-table-cell">Scraped</th>
|
</a>
|
||||||
|
</th>
|
||||||
|
<th class="text-right">
|
||||||
|
<a
|
||||||
|
href={buildSearchURL('Size', this)}>
|
||||||
|
Size
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th class="text-right">
|
||||||
|
<a href="#">Seeds</a>
|
||||||
|
</th>
|
||||||
|
<th class="text-right d-none d-md-table-cell">
|
||||||
|
<a href="#">Leeches</a>
|
||||||
|
</th>
|
||||||
|
<th class="text-right d-none d-md-table-cell">
|
||||||
|
<a href="#">Scraped</a>
|
||||||
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -207,7 +250,7 @@ export class Search extends Component<any, State> {
|
||||||
switchPage(a: { i: Search, nextPage: boolean }) {
|
switchPage(a: { i: Search, nextPage: boolean }) {
|
||||||
let newSearch = a.i.state.searchParams;
|
let newSearch = a.i.state.searchParams;
|
||||||
newSearch.page += (a.nextPage) ? 1 : -1;
|
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) {
|
copyLink(evt) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Index extends Component<any, any> {
|
||||||
<div class="mt-3 p-0">
|
<div class="mt-3 p-0">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path={`/search/:type_/:q/:page`} component={Search} />
|
<Route path={`/search/:type_/:q/:page/:sort_key/:sort_dir`} component={Search} />
|
||||||
</Switch>
|
</Switch>
|
||||||
{this.symbols()}
|
{this.symbols()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,8 @@ export interface SearchParams {
|
||||||
page: number;
|
page: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
type_: string;
|
type_: string;
|
||||||
|
sort_key: string;
|
||||||
|
sort_dir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Results {
|
export interface Results {
|
||||||
|
|
Loading…
Reference in New Issue