#[macro_use]
extern crate serde_derive;
extern crate bson;
extern crate mongodb;
extern crate serde;
extern crate serde_json;
use bson::ordered::OrderedDocument;
use mongodb::{Client, ClientInner, ThreadedClient};
use mongodb::db::ThreadedDatabase;
use std::env::current_dir;
use std::fs;
use std::fs::{File};
use std::path::PathBuf;
use std::process;
use std::sync::Arc;
pub mod util;
const ASSEMBLY: &'static str = "assembly";
const DAIL: &'static str = "dail";
const WESTMINSTER: &'static str = "westminster";
const CONSTITUENCIES: &'static str = "constituencies";
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(default)]
struct Area {
area_type: String,
candidates: Vec<Candidate>,
counts_held: Option<i8>,
description: String,
election_type: String,
electorate: Option<i32>,
name: String,
quota: Option<i32>,
spoilt: Option<i16>,
turnout: Option<i32>,
valid: Option<i32>,
year: i16,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(default)]
struct Candidate {
counts: Vec<i32>,
elected: bool,
first_pref_pc: Option<f32>,
full_name: String,
party: String,
transfers: Option<i32>,
transfers_pc: Option<f32>,
}
pub enum ElectionDatabase {
Assembly(Arc<ClientInner>, String),
Dail(Arc<ClientInner>, String),
Westminster(Arc<ClientInner>, String),
}
impl ElectionDatabase {
fn from_str(input: &str, db_name: Option<&str>) -> Option<Self> {
let mut db = String::new();
match db_name {
Some(s) => db.clone_from(&s.to_string()),
None => {
let unique = util::mini_hash();
db.clone_from(&format!("{}_{}", input, unique));
}
};
let client = Client::with_uri("mongodb://127.0.0.1:27017")
.expect("Failed to initialize standalone client.");
match input.as_ref() {
ASSEMBLY => Some(ElectionDatabase::Assembly(client, db)),
DAIL => Some(ElectionDatabase::Dail(client, db)),
WESTMINSTER => Some(ElectionDatabase::Westminster(client, db)),
_ => None,
}
}
fn get_database(&self) -> String {
match *self {
ElectionDatabase::Assembly(_, ref database) => database.to_string(),
ElectionDatabase::Dail(_, ref database) => database.to_string(),
ElectionDatabase::Westminster(_, ref database) => database.to_string(),
}
}
}
trait CreateDatabase {
fn create_constituencies(&self);
fn walk_constituency_subdirs(&self, dir: &PathBuf) -> Option<Vec<Area>>;
fn walk_json_files(&self, subdir: &PathBuf) -> Option<Vec<Area>>;
fn load_json(&self, jsonfile: &PathBuf) -> Option<Area>;
fn create_documents<'a,T>(&self, areas: &'a Vec<T>) where T: serde::Serialize;
}
impl CreateDatabase for ElectionDatabase {
fn create_constituencies(&self) {
let constituencies_dir = current_dir().unwrap().join(CONSTITUENCIES);
match constituencies_dir.exists() {
true => match self.walk_constituency_subdirs(&constituencies_dir) {
Some(ref a) => self.create_documents(a),
None => {
eprintln!("{}","[mongoloid] No constituencies to create.");
process::exit(1);
}
},
false => {
eprintln!("[mongoloid] Current working directory is missing '{}' sub-directory. Exiting.", CONSTITUENCIES);
process::exit(1);
},
}
}
fn walk_constituency_subdirs(&self, constituencies_dir: &PathBuf) -> Option<Vec<Area>> {
let dir = fs::read_dir(constituencies_dir).unwrap();
let areas: Vec<Area> = dir.filter_map(|entry| {
let entry = entry.unwrap();
let subdir: PathBuf = entry.path();
match subdir.is_dir() {
true => self.walk_json_files(&subdir),
false => None
}
}).collect::<Vec<Vec<Area>>>()
.iter()
.flat_map(|a| a.iter().map(Clone::clone))
.collect();
match areas.is_empty() {
true => None,
false => Some(areas)
}
}
fn walk_json_files(&self, subdir: &PathBuf) -> Option<Vec<Area>> {
let mut areas: Vec<Area> = Vec::new();
match fs::read_dir(subdir) {
Err(why) => panic!("[mongoloid] Couldn't read directory. {}", why),
Ok(dir) => {
let a = dir.filter_map(|entry| {
let entry = entry.unwrap();
let jsonfile: PathBuf = entry.path();
match jsonfile.is_file() {
true => self.load_json(&jsonfile),
false => None
}
}).collect::<Vec<Area>>();
if !a.is_empty() {
areas.extend(a);
}
},
};
match areas.is_empty() {
true => None,
false => Some(areas)
}
}
fn load_json(&self, jsonfile: &PathBuf) -> Option<Area>{
println!("[mongoloid] Processing {:?}", jsonfile);
match File::open(&jsonfile) {
Err(why) => panic!("Couldn't open file: {}", why),
Ok(file) => {
let area: Option<Area> = match serde_json::from_reader(file) {
Ok(a) => Some(a),
Err(why) => panic!("[mongoloid] Couldn't deserialise: {}", why),
};
return area;
},
}
}
fn create_documents<'a,T>(&self, areas: &'a Vec<T>) where T: serde::Serialize {
println!("[mongoloid] Creating database \"{}\"...", self.get_database());
let docs: Vec<OrderedDocument> = areas.iter().filter_map(|a| {
match bson::to_bson(a) {
Err(why) => {
eprintln!("[mongoloid] Couldn't convert row to BSON: {}", why);
None
},
Ok(d) => {
if let bson::Bson::Document(bson_doc) = d {
return Some(bson_doc);
}
None
},
}
}).collect();
if let ElectionDatabase::Dail(ref client, ref db) = *self {
let coll = client.db(db).collection("area");
coll.insert_many(docs, None)
.ok().expect("Failed to insert document.");
}
}
}
pub fn create_database(db_name: Option<&str>) -> Result<(),String> {
match ElectionDatabase::from_str(&util::get_cwd_name(), db_name){
Some(et) => {
println!("[mongoloid] {}", "Creating database");
et.create_constituencies();
println!("[mongoloid] Created database \"{}\"", et.get_database());
},
None => {
eprintln!("[mongoloid] Current working directory {:?} is not an expected election type. Exiting.", current_dir().unwrap());
process::exit(1);
},
};
Ok(())
}
#[cfg(test)]
mod tests {
extern crate bson;
extern crate mongodb;
use bson::{Bson, Document};
use mongodb::{Client, ThreadedClient};
use mongodb::db::ThreadedDatabase;
use std::env::set_current_dir;
#[test]
#[allow(unused_must_use)]
fn database_builds() {
let test_dir = super::current_dir().unwrap().join("test");
let dail_dir = test_dir.join("dail");
let client = Client::with_uri("mongodb://127.0.0.1:27017")
.expect("Failed to initialize standalone client.");
let coll = client.db("test").collection("area");
set_current_dir(&dail_dir);
coll.delete_many(Document::new(), None);
super::create_database(Some("test"));
let result = coll.find_one(None, None).expect(
"Failed to execute find command.",
);
match result.unwrap().get("valid") {
Some(&Bson::I32(valid)) => assert_eq!(44034 as i32, valid),
_ => panic!("Expected Bson::I32!"),
};
coll.delete_many(Document::new(), None);
}
}