Compare commits

...

5 Commits

Author SHA1 Message Date
Augusto Gunsch c2cb1f861d
Fix URL 2022-01-09 15:53:19 -03:00
Augusto Gunsch 8925fb87a9
Add installed versions tracking 2022-01-09 14:39:06 -03:00
Augusto Gunsch 47f65f8a96
Improve regex 2022-01-09 11:00:59 -03:00
Augusto Gunsch 191146da4b
Make fancy permission denied error 2022-01-09 08:43:18 -03:00
Augusto Gunsch ce485b733e
Conform to Unix's standards 2022-01-02 21:50:48 -03:00
5 changed files with 112 additions and 29 deletions

View File

@ -1,7 +1,10 @@
use std::fs; use std::fs;
use std::io::ErrorKind;
use std::process::exit;
use reqwest; use reqwest;
use rusqlite::{Connection, Transaction}; use rusqlite::{Connection, Transaction, ErrorCode};
use rusqlite::Error::SqliteFailure;
use rusqlite::params; use rusqlite::params;
use serde_json::Value; use serde_json::Value;
use serde_json::json; use serde_json::json;
@ -10,27 +13,56 @@ use serde_json;
use crate::language::Language; use crate::language::Language;
use crate::entry::{WiktionaryEntries, WiktionaryEntry}; use crate::entry::{WiktionaryEntries, WiktionaryEntry};
use crate::entry::Form; use crate::entry::Form;
use crate::{MAJOR, MINOR, PATCH};
const DB_DIR: &str = "/usr/share/inflectived/";
const CACHE_DIR: &str = "/var/cache/";
/// A database of Wiktionary entries /// A database of Wiktionary entries
pub struct WordDb { pub struct WordDb {
connection: String db_path: String
} }
impl WordDb { impl WordDb {
pub fn new(db_path: &str) -> Self { pub fn new(db_name: &str) -> Self {
Self { let mut db_path = String::from(DB_DIR);
connection: String::from(db_path) db_path.push_str(db_name);
}
Self { db_path }
} }
pub fn connect(&self) -> Connection { pub fn connect(&self) -> Connection {
Connection::open(&self.connection).unwrap() Connection::open(&self.db_path).unwrap()
} }
pub fn clean_tables(&mut self, lang: &Language) { pub fn clean_tables(&mut self, lang: &Language) {
let mut conn = self.connect(); let mut conn = self.connect();
let transaction = conn.transaction().unwrap(); let transaction = conn.transaction().unwrap();
if let Err(e) = transaction.execute("
CREATE TABLE IF NOT EXISTS langs (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
code TINYTEXT UNIQUE NOT NULL,
name TINYTEXT NOT NULL,
major INTEGER NOT NULL,
minor INTEGER NOT NULL,
patch INTEGER NOT NULL
)", []) {
match e {
SqliteFailure(f, _) => match f.code {
ErrorCode::ReadOnly => {
eprintln!("Could not write to database: Permission denied");
eprintln!("Please run as root");
exit(1);
},
_ => panic!("{}", e)
},
_ => panic!("{}", e)
}
}
transaction.execute("DELETE FROM langs WHERE code = ?", [&lang.code]).unwrap();
transaction.execute(&format!("DROP TABLE IF EXISTS {0}_words", &lang.code), []).unwrap(); transaction.execute(&format!("DROP TABLE IF EXISTS {0}_words", &lang.code), []).unwrap();
transaction.execute(&format!("DROP TABLE IF EXISTS {0}_types", &lang.code), []).unwrap(); transaction.execute(&format!("DROP TABLE IF EXISTS {0}_types", &lang.code), []).unwrap();
@ -63,6 +95,11 @@ impl WordDb {
ON {0}_words (word) ON {0}_words (word)
", &lang.code), []).unwrap(); ", &lang.code), []).unwrap();
transaction.execute("
INSERT INTO langs (code, name, major, minor, patch)
VALUES (?, ?, ?, ?, ?)
", params![&lang.code, &lang.name, MAJOR, MINOR, PATCH]).unwrap();
transaction.commit().unwrap(); transaction.commit().unwrap();
} }
@ -169,9 +206,24 @@ impl WordDb {
transaction.commit().unwrap(); transaction.commit().unwrap();
} }
fn try_create_dir(&self, dir: &str) {
match fs::create_dir(dir) {
Err(e) => match e.kind() {
ErrorKind::AlreadyExists => {},
_ => panic!("{}", e)
},
_ => {}
}
}
pub async fn upgrade_lang(&mut self, lang: &Language) { pub async fn upgrade_lang(&mut self, lang: &Language) {
self.try_create_dir(DB_DIR);
println!("Trying to read cached data..."); println!("Trying to read cached data...");
let cached_data = fs::read_to_string("Polish.json"); let mut cache_file = String::from(CACHE_DIR);
cache_file.push_str("Polish.json");
let cached_data = fs::read_to_string(&cache_file);
let mut request = None; let mut request = None;
if let Err(_) = cached_data { if let Err(_) = cached_data {
@ -186,8 +238,11 @@ impl WordDb {
// Actually, the request was sent before // Actually, the request was sent before
println!("Requesting data..."); println!("Requesting data...");
data = request.await.unwrap().text().await.unwrap(); data = request.await.unwrap().text().await.unwrap();
if cfg!(unix) {
println!("Caching data..."); println!("Caching data...");
fs::write("Polish.json", &data).unwrap(); self.try_create_dir(CACHE_DIR);
fs::write(&cache_file, &data).unwrap();
}
} }
else { else {
data = cached_data.unwrap(); data = cached_data.unwrap();

View File

@ -1,13 +1,15 @@
#[derive(Debug)] #[derive(Debug)]
pub struct Language { pub struct Language {
pub code: String, pub code: String,
pub name: String,
pub types: Vec<String> pub types: Vec<String>
} }
impl Language { impl Language {
pub fn new(code: &str, types: Vec<String>) -> Self { pub fn new(code: &str, name: &str, types: Vec<String>) -> Self {
Self { Self {
code: String::from(code), code: String::from(code),
name: String::from(name),
types types
} }
} }

View File

@ -8,11 +8,16 @@ use clap::{App, AppSettings, Arg, SubCommand};
mod database; mod database;
mod language; mod language;
mod entry; mod entry;
mod routes; mod views;
use database::WordDb; use database::WordDb;
use language::Language; use language::Language;
const MAJOR: i32 = 0;
const MINOR: i32 = 1;
const PATCH: i32 = 0;
#[rocket::main] #[rocket::main]
async fn main() { async fn main() {
let matches = App::new("inflectived") let matches = App::new("inflectived")
@ -49,9 +54,10 @@ async fn main() {
.setting(AppSettings::SubcommandRequiredElseHelp) .setting(AppSettings::SubcommandRequiredElseHelp)
.get_matches(); .get_matches();
let mut db = WordDb::new("test.db"); let mut db = WordDb::new("inflectived.db");
let lang = Language::new("polish", let lang = Language::new("pl",
"Polish",
vec![String::from("adj"), vec![String::from("adj"),
String::from("noun"), String::from("noun"),
String::from("verb"), String::from("verb"),
@ -84,9 +90,10 @@ async fn main() {
rocket::custom(figment) rocket::custom(figment)
.manage(db) .manage(db)
.mount("/static", FileServer::from("static/")) .mount("/static", FileServer::from("static/"))
.mount("/", routes![routes::get_entries, .mount("/", routes![views::get_entries,
routes::get_entries_like, views::get_entries_like,
routes::frontend]) views::get_langs,
views::frontend])
.launch() .launch()
.await.unwrap(); .await.unwrap();
}, },

View File

@ -9,7 +9,7 @@ use rusqlite::params;
use crate::database::WordDb; use crate::database::WordDb;
#[get("/frontend")] #[get("/")]
pub fn frontend() -> Option<content::Html<String>> { pub fn frontend() -> Option<content::Html<String>> {
match fs::read_to_string("static/index.html") { match fs::read_to_string("static/index.html") {
Ok(file) => Some(content::Html(file)), Ok(file) => Some(content::Html(file)),
@ -70,3 +70,22 @@ pub fn get_entries_like(db: &State<WordDb>, lang: &str, like: &str, limit: usize
Json(words) Json(words)
} }
#[get("/langs?<installed>")]
pub fn get_langs(db: &State<WordDb>, installed: bool) -> Json<Vec<String>> {
let conn = db.connect();
let mut langs: Vec<String> = Vec::new();
if installed {
let mut statement = conn.prepare("SELECT name FROM langs").unwrap();
let mut rows = statement.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
langs.push(row.get(0).unwrap());
}
}
Json(langs)
}

View File

@ -21,7 +21,7 @@ $(document).ready(() => {
appendTo: '#search-form', appendTo: '#search-form',
source: (request, response) => { source: (request, response) => {
$.ajax({ $.ajax({
url: '/langs/polish/words?like=' + request.term + '&limit=20&offset=0', url: '/langs/pl/words?like=' + request.term + '&limit=20&offset=0',
success: data => response(data) success: data => response(data)
}) })
}, },
@ -44,7 +44,7 @@ $(document).ready(() => {
let word = window.location.hash.replace('#', ''); let word = window.location.hash.replace('#', '');
$.ajax({ $.ajax({
url: '/langs/polish/words/' + word, url: '/langs/pl/words/' + word,
success: (data) => { success: (data) => {
$('#ajax-content').html(generateHtml(word, data)) $('#ajax-content').html(generateHtml(word, data))
@ -163,7 +163,7 @@ $(document).ready(() => {
if('form_of' in sense) { if('form_of' in sense) {
let word = sense.form_of[0].word; let word = sense.form_of[0].word;
html += sense.glosses[0].replace(new RegExp(`of ${word}$`), ''); html += sense.glosses[0].replace(new RegExp(`of ${word}.?$`), '');
html += ` of <a href="#${word}" class="link-primary">${word}</a>`; html += ` of <a href="#${word}" class="link-primary">${word}</a>`;
} else { } else {
let link = ' of <a href="#$1" class="link-primary">$1</a>'; let link = ' of <a href="#$1" class="link-primary">$1</a>';