Skip to content

Commit

Permalink
add search (#1)
Browse files Browse the repository at this point in the history
* add search

* Update index/details date format
  • Loading branch information
jiacai2050 committed Jan 29, 2022
1 parent 4a5bcc0 commit 2a0e484
Show file tree
Hide file tree
Showing 15 changed files with 331 additions and 169 deletions.
File renamed without changes.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "onehistory"
version = "0.1.3"
version = "0.2.0"
edition = "2021"
authors = ["Jiacai Liu <jiacai2050+1history@gmail.com>"]
description = "All your history in one place"
Expand All @@ -13,7 +13,7 @@ license = "GPL-3.0"
[dependencies]
rusqlite = { version = "0.26.3", features = ["bundled"] }
anyhow = { version = "1.0", features = ["backtrace"] }
clap = { version = "3.0.9", features = ["derive", "env"] }
clap = { version = "3.0.9", features = ["derive", "env", "cargo"] }
home = "0.5.3"
lazy_static = "1.4.0"
env_logger = "0.9.0"
Expand All @@ -24,7 +24,7 @@ warp = "0.3"
mime_guess = "2.0.3"
serde_derive = "1.0"
serde = "1.0"
minijinja = "0.12.0"
minijinja = { version = "0.12.0", features = ["builtins", "urlencode"]}
glob = "0.3.0"
chrono = "0.4"
regex = "1"
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
release-arm64:
./release-arm64.sh

serve:
cargo run -- serve
5 changes: 2 additions & 3 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,14 @@ After backup browser history into 1History, the next step is to visualize those
#+begin_src bash
brew install 1History/onehistory/onehistory
#+end_src
** Binaries
** Binary
The [[https://github.com/1History/1History/releases][release page]] includes precompiled binaries for Linux, macOS and Windows.
** Cargo
#+begin_src bash
cargo install onehistory
#+end_src

* Roadmap
- [ ] Search by title or url

* FAQ
- =Error code 5: The database file is locked= :: This error happens if your browser is opened during backup, as SQLite allow only one open connection.

Expand Down
1 change: 1 addition & 0 deletions release-arm64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -ex

v=$(grep version Cargo.toml | head -1 | awk '{print $3}' | tr -d '"')

rm -rf 1History*zip 1History*zip.sha256sum
cargo build --release
cp -f target/release/onehistory .
zip 1History_v"${v}"_aarch64-apple-darwin.zip onehistory README.org
Expand Down
115 changes: 78 additions & 37 deletions src/database.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
types::{DailyCount, VisitDetail},
util::domain_from,
types::VisitDetail,
util::{domain_from, ymd_midnight},
};
use anyhow::{Context, Result};
use log::debug;
Expand Down Expand Up @@ -203,8 +203,24 @@ ON CONFLICT (data_path)
ts * 1_000
}

pub fn select_visits(&self, start: i64, end: i64) -> Result<Vec<VisitDetail>> {
let sql = r#"
fn keyword_to_like(kw: Option<String>) -> String {
kw.map_or_else(
|| "1".to_string(),
|v| {
let v = v.replace("'", "");
format!("(url like '%{v}%' or title like '%{v}%')")
},
)
}

pub fn select_visits(
&self,
start: i64,
end: i64,
keyword: Option<String>,
) -> Result<Vec<VisitDetail>> {
let sql = format!(
r#"
SELECT
url,
title,
Expand All @@ -214,17 +230,21 @@ FROM
onehistory_urls u,
onehistory_visits v ON u.id = v.item_id
WHERE
visit_time BETWEEN :start AND :end
visit_time BETWEEN :start AND :end and {}
ORDER BY
visit_time
"#;
"#,
Self::keyword_to_like(keyword)
);

let conn = self.conn.lock().unwrap();
let mut stat = conn.prepare(sql)?;
let mut stat = conn.prepare(&sql)?;

let rows = stat.query_map(
named_params! {
":start": Self::unixepoch_to_prtime(start),
":end": Self::unixepoch_to_prtime(end),

},
|row| {
let detail = VisitDetail {
Expand All @@ -245,49 +265,61 @@ ORDER BY
Ok(res)
}

pub fn select_daily_count(&self, start: i64, end: i64) -> Result<Vec<DailyCount>> {
let sql = r#"
pub fn select_daily_count(
&self,
start: i64,
end: i64,
keyword: Option<String>,
) -> Result<Vec<(i64, i64)>> {
let sql = format!(
r#"
SELECT
visit_day,
count(1)
FROM (
SELECT
cast(round(visit_time - (visit_time % (86400 * 1000000))) / 1000 AS integer) AS visit_day
strftime ('%Y-%m-%d', visit_time / 1000000, 'unixepoch', 'localtime') AS visit_day
FROM
onehistory_visits
onehistory_visits v,
onehistory_urls u ON v.item_id = u.id
WHERE
visit_time BETWEEN :start AND :end)
GROUP BY
visit_day
ORDER BY
visit_day;
"#;
visit_time BETWEEN :start AND :end
AND {})
GROUP BY
visit_day
ORDER BY
visit_day;
"#,
Self::keyword_to_like(keyword)
);
let conn = self.conn.lock().unwrap();
let mut stat = conn.prepare(sql)?;
let mut stat = conn.prepare(&sql)?;

let rows = stat.query_map(
named_params! {
":start": Self::unixepoch_to_prtime(start),
":end": Self::unixepoch_to_prtime(end),
},
|row| {
Ok(DailyCount {
day: row.get(0)?,
count: row.get(1)?,
})
},
|row| Ok((row.get(0)?, row.get(1)?)),
)?;

let mut res: Vec<DailyCount> = Vec::new();
let mut res = Vec::new();
for r in rows {
res.push(r?);
let (ymd, cnt): (String, i64) = r?;
res.push((ymd_midnight(&ymd)?, cnt));
}

Ok(res)
}

pub fn select_domain_top100(&self, start: i64, end: i64) -> Result<Vec<(String, i64)>> {
let sql = r#"
pub fn select_domain_top100(
&self,
start: i64,
end: i64,
keyword: Option<String>,
) -> Result<Vec<(String, i64)>> {
let sql = format!(
r#"
SELECT
url,
count(1) AS cnt
Expand All @@ -299,29 +331,36 @@ FROM (
onehistory_urls u ON v.item_id = u.id
WHERE
visit_time BETWEEN :start AND :end
AND title != '')
AND title != '' AND {})
GROUP BY
url
ORDER BY
cnt DESC
"#;
let url_top100 = self.select_top100(sql, start, end)?;
"#,
Self::keyword_to_like(keyword)
);
let url_top100 = self.select_top100(&sql, start, end)?;

let mut domain_top = HashMap::new();
for (url, cnt) in url_top100 {
let domain = domain_from(url);
let total = domain_top.entry(domain).or_insert(cnt);
*total += cnt;
// {:domain (or (second (re-find #"://(.+?)/" url))
}
let mut top_arr = domain_top.into_iter().collect::<Vec<(String, i64)>>();
top_arr.sort_by(|a, b| b.1.cmp(&a.1));

Ok(top_arr.into_iter().take(100).collect::<Vec<_>>())
}

pub fn select_title_top100(&self, start: i64, end: i64) -> Result<Vec<(String, i64)>> {
let sql = r#"
pub fn select_title_top100(
&self,
start: i64,
end: i64,
keyword: Option<String>,
) -> Result<Vec<(String, i64)>> {
let sql = format!(
r#"
SELECT
title,
count(1) AS cnt
Expand All @@ -333,14 +372,16 @@ FROM (
onehistory_urls u ON v.item_id = u.id
WHERE
visit_time BETWEEN :start AND :end
AND title != '')
AND title != '' AND {})
GROUP BY
title
ORDER BY
cnt DESC
LIMIT 100;
"#;
self.select_top100(sql, start, end)
"#,
Self::keyword_to_like(keyword)
);
self.select_top100(&sql, start, end)
}

fn select_top100(&self, sql: &str, start: i64, end: i64) -> Result<Vec<(String, i64)>> {
Expand Down
2 changes: 1 addition & 1 deletion src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn export_csv(csv_file: String, db_file: String) -> Result<()> {
let mut buf_writer = BufWriter::new(f);

buf_writer.write_all(b"time,title,url,visit_type\n")?;
let visits = db.select_visits(start, end)?;
let visits = db.select_visits(start, end, None)?;
let len = visits.len();
for visit in visits {
buf_writer.write_all(
Expand Down
12 changes: 9 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod web;
use crate::database::Database;
use crate::source::Source;
use crate::util::{full_timerange, DEFAULT_CSV_FILE, DEFAULT_DB_FILE};
use anyhow::{Context, Result};
use anyhow::{Context, Error, Result};
use clap::{Parser, Subcommand};
use export::export_csv;
use log::{debug, error, info, LevelFilter};
Expand Down Expand Up @@ -95,8 +95,8 @@ fn backup(history_files: Vec<String>, db_file: String, dry_run: bool) -> Result<
let mut found = 0;
let mut total_affected = 0;
let mut total_duplicated = 0;
for his_file in history_files {
let s = Source::open(his_file.to_string()).context("open")?;
let mut persist = |history_file: String| {
let s = Source::open(history_file).context("open")?;
let rows = s.select(start, end).context("select")?.collect::<Vec<_>>();
debug!("{:?} select {} histories", s.name(), rows.len());
found += rows.len();
Expand All @@ -111,6 +111,12 @@ fn backup(history_files: Vec<String>, db_file: String, dry_run: bool) -> Result<
);
total_affected += affected;
total_duplicated += duplicated;
};
Ok::<_, Error>(())
};
for his_file in history_files {
if let Err(e) = persist(his_file.clone()) {
error!("{} persist failed, err: {:?}", his_file, e);
}
}

Expand Down
28 changes: 20 additions & 8 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@ pub struct VisitDetail {
pub visit_type: i64,
}

#[derive(Debug)]
pub struct DailyCount {
// unix_epoch_ms
pub day: i64,
pub count: i64,
#[derive(Debug, Deserialize)]
pub struct DetailsQueryParams {
pub keyword: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct TimeRange {
pub start: Option<i64>,
pub end: Option<i64>,
pub struct IndexQueryParams {
pub start: Option<String>, // Y-m-d
pub end: Option<String>, // Y-m-d
pub keyword: Option<String>,
}

#[derive(Debug)]
Expand All @@ -44,6 +43,19 @@ impl From<Error> for ServerError {

impl Reject for ServerError {}

#[derive(Debug)]
pub struct ClientError {
pub e: String,
}

impl From<Error> for ClientError {
fn from(err: Error) -> Self {
Self { e: err.to_string() }
}
}

impl Reject for ClientError {}

#[derive(Serialize)]
pub struct ErrorMessage {
pub code: u16,
Expand Down

0 comments on commit 2a0e484

Please sign in to comment.