summaryrefslogtreecommitdiffstats
path: root/src/db.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/db.rs')
-rw-r--r--src/db.rs144
1 files changed, 78 insertions, 66 deletions
diff --git a/src/db.rs b/src/db.rs
index c428da8..27737cb 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -39,24 +39,22 @@ pub struct DB {
impl DB {
pub fn new<P: AsRef<std::path::Path>>(path: &P) -> failure::Fallible<DB> {
let conn = if path.as_ref().exists() {
- rusqlite::Connection::open(path)
- .map_err(|e| {
- let msg = format!(
- "couldn't open db at {}",
- path.as_ref().display()
- );
- e.context(msg)
- })?
- }
- else {
+ rusqlite::Connection::open(path).map_err(|e| {
+ let msg = format!(
+ "couldn't open db at {}",
+ path.as_ref().display()
+ );
+ e.context(msg)
+ })?
+ } else {
Self::create(path)?
};
- return Ok(DB { conn })
+ return Ok(DB { conn });
}
fn create<P: AsRef<std::path::Path>>(
- path: &P
+ path: &P,
) -> failure::Fallible<rusqlite::Connection> {
eprintln!(
"Initializing database at {}",
@@ -65,49 +63,50 @@ impl DB {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
- let conn = rusqlite::Connection::open(path)
- .map_err(|e| {
- let msg = format!(
- "couldn't create db at {}",
- path.as_ref().display()
- );
- e.context(msg)
- })?;
+ let conn = rusqlite::Connection::open(path).map_err(|e| {
+ let msg = format!(
+ "couldn't create db at {}",
+ path.as_ref().display()
+ );
+ e.context(msg)
+ })?;
conn.execute(SCHEMA, rusqlite::NO_PARAMS)
.map_err(|e| e.context("failed to execute schema"))?;
Ok(conn)
- }
- else {
+ } else {
unimplemented!();
}
}
pub fn most_recent_timestamp(&self) -> failure::Fallible<Option<i64>> {
- Ok(self.conn.query_row(
- "SELECT timestamp FROM tracks ORDER BY timestamp DESC LIMIT 1",
- rusqlite::NO_PARAMS,
- |row| Some(row.get(0))
- ).or_else(|e| {
- match e {
+ Ok(self
+ .conn
+ .query_row(
+ "SELECT timestamp FROM tracks ORDER BY timestamp DESC LIMIT 1",
+ rusqlite::NO_PARAMS,
+ |row| Some(row.get(0)),
+ )
+ .or_else(|e| match e {
rusqlite::Error::QueryReturnedNoRows => Ok(None),
_ => Err(e),
- }
- })?)
+ })?)
}
pub fn insert_tracks(
&self,
- tracks: impl Iterator<Item=lastfm::Track>,
+ tracks: impl Iterator<Item = lastfm::Track>,
) -> failure::Fallible<()> {
- let mut sth = self.conn.prepare("INSERT INTO tracks VALUES (?, ?, ?, ?)")?;
+ let mut sth = self
+ .conn
+ .prepare("INSERT INTO tracks VALUES (?, ?, ?, ?)")?;
for track in tracks {
- sth.execute(
- &[
+ sth.execute(&[
&track.artist as &rusqlite::types::ToSql,
&track.album,
&track.name,
&track.timestamp,
- ]).map(|_| ())?;
+ ])
+ .map(|_| ())?;
}
Ok(())
}
@@ -115,19 +114,16 @@ impl DB {
pub fn query<F: FnMut(&rusqlite::Row)>(
&self,
query: &str,
- mut f: F
+ mut f: F,
) -> failure::Fallible<Vec<String>> {
let mut sth = self.conn.prepare(query)?;
- let cols = sth.column_names()
- .iter()
- .map(|s| s.to_string())
- .collect();
+ let cols = sth.column_names().iter().map(|s| s.to_string()).collect();
- let rows = sth.query_and_then(
- rusqlite::NO_PARAMS,
- |row| { f(row); Ok(()) },
- )?;
+ let rows = sth.query_and_then(rusqlite::NO_PARAMS, |row| {
+ f(row);
+ Ok(())
+ })?;
// this call to collect() forces it to actually consume the iterator
// (and therefore call the callbacks). what i really want here is for
// there to be a query_for_each or something like that, but the weird
@@ -146,35 +142,44 @@ impl DB {
exclude: TimeWindow,
) -> failure::Fallible<Vec<String>> {
let exclude = if exclude != TimeWindow::None {
- format!("
+ format!(
+ "
WHERE artist NOT IN (
SELECT DISTINCT(artist)
FROM {}
)
- ", timewindow_table(&exclude))
- }
- else {
+ ",
+ timewindow_table(&exclude)
+ )
+ } else {
"".to_string()
};
let order = if random {
"ORDER BY random()"
- }
- else {
+ } else {
"ORDER BY count(artist) * (strftime('%s') - max(timestamp)) DESC"
};
- let sql = format!("
+ let sql = format!(
+ "
SELECT artist
FROM {}
{}
GROUP BY artist
{}
LIMIT {}
- ", timewindow_table(&include), exclude, order, count);
+ ",
+ timewindow_table(&include),
+ exclude,
+ order,
+ count
+ );
let mut sth = self.conn.prepare(&sql)?;
- let artists = sth.query_and_then(rusqlite::NO_PARAMS, |row| {
- Ok(row.get_checked(0)?)
- })?.collect::<failure::Fallible<Vec<String>>>()?;
+ let artists = sth
+ .query_and_then(rusqlite::NO_PARAMS, |row| {
+ Ok(row.get_checked(0)?)
+ })?
+ .collect::<failure::Fallible<Vec<String>>>()?;
Ok(artists)
}
@@ -189,25 +194,27 @@ impl DB {
let mut params = vec![artist];
let exclude = if exclude != TimeWindow::None {
params.push(artist);
- format!("
+ format!(
+ "
AND album NOT IN (
SELECT DISTINCT(album)
FROM {}
WHERE artist = ?
)
- ", timewindow_table(&exclude))
- }
- else {
+ ",
+ timewindow_table(&exclude)
+ )
+ } else {
"".to_string()
};
let order = if random {
"ORDER BY random()"
- }
- else {
+ } else {
"ORDER BY count(album) * (strftime('%s') - max(timestamp)) DESC"
};
- let sql = format!("
+ let sql = format!(
+ "
SELECT album
FROM {}
WHERE artist = ?
@@ -215,11 +222,16 @@ impl DB {
GROUP BY album
{}
LIMIT 1
- ", timewindow_table(&include), exclude, order);
+ ",
+ timewindow_table(&include),
+ exclude,
+ order
+ );
let mut sth = self.conn.prepare(&sql)?;
- let artists = sth.query_row::<failure::Fallible<String>, _, _>(&params, |row| {
- Ok(row.get_checked(0)?)
- })??;
+ let artists = sth.query_row::<failure::Fallible<String>, _, _>(
+ &params,
+ |row| Ok(row.get_checked(0)?),
+ )??;
Ok(artists)
}