2025-05-01 13:40:23 +03:00
|
|
|
use std::{io, path, process::exit};
|
|
|
|
|
|
|
|
use io::Write;
|
|
|
|
|
|
|
|
use clap::Parser;
|
|
|
|
use cli::CliArgs;
|
|
|
|
use rusqlite::{Connection, params_from_iter};
|
|
|
|
|
|
|
|
const DB_PATH: &str = ".tags";
|
|
|
|
|
|
|
|
mod cli;
|
|
|
|
|
2025-05-01 00:18:55 +03:00
|
|
|
fn main() {
|
2025-05-01 13:40:23 +03:00
|
|
|
let args = CliArgs::parse();
|
|
|
|
|
|
|
|
match args.commands {
|
|
|
|
cli::Commands::Init => {
|
|
|
|
if !has_database() {
|
|
|
|
let conn = Connection::open(DB_PATH).unwrap();
|
|
|
|
init_db(&conn);
|
|
|
|
} else {
|
|
|
|
panic!("Database is already initialized in this folder");
|
|
|
|
}
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
cli::Commands::Tags(args) => {
|
|
|
|
let conn = Connection::open(DB_PATH).unwrap();
|
|
|
|
|
|
|
|
if args.list {
|
|
|
|
let mut w = io::stdout();
|
|
|
|
let tags = list_tags(&conn);
|
|
|
|
for tag in tags {
|
|
|
|
writeln!(&mut w, "{}", tag).unwrap();
|
|
|
|
}
|
|
|
|
w.flush().unwrap();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match args.commands {
|
|
|
|
Some(cli::TagsCommands::Add { add }) => add_tags(&conn, add),
|
|
|
|
Some(cli::TagsCommands::Remove { remove }) => remove_tags(&conn, remove),
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn list_tags(conn: &Connection) -> Vec<String> {
|
|
|
|
let mut stmt = conn.prepare("SELECT name FROM tag").unwrap();
|
|
|
|
let result = stmt.query_map([], |row| row.get(0)).unwrap();
|
|
|
|
|
|
|
|
let mut tags = Vec::new();
|
|
|
|
for name in result {
|
|
|
|
tags.push(name.unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
tags
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_tags(conn: &Connection, tags: Vec<String>) {
|
|
|
|
let mut query = r#"DELETE FROM tag WHERE name IN ("#.to_string();
|
|
|
|
|
|
|
|
for (i, _tag) in tags.iter().enumerate() {
|
|
|
|
query.push('?');
|
|
|
|
if i < tags.len() - 1 {
|
|
|
|
query.push(',');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query.push(')');
|
|
|
|
|
|
|
|
conn.execute(&query, params_from_iter(tags)).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_tags(conn: &Connection, tags: Vec<String>) {
|
|
|
|
let mut query = r#"INSERT INTO tag(name) VALUES"#.to_string();
|
|
|
|
|
|
|
|
for (i, _tag) in tags.iter().enumerate() {
|
|
|
|
query.push_str("(?)");
|
|
|
|
if i < tags.len() - 1 {
|
|
|
|
query.push(',');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.execute(&query, params_from_iter(tags)).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init_db(conn: &Connection) {
|
|
|
|
conn.execute(
|
|
|
|
r#"CREATE TABLE IF NOT EXISTS tag(
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
name VARCHAR(255) NOT NULL UNIQUE
|
|
|
|
);"#,
|
|
|
|
(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
r#"CREATE TABLE IF NOT EXISTS file(
|
|
|
|
id INT NOT NULL PRIMARY KEY,
|
|
|
|
path VARCHAR(255) NOT NULL UNIQUE
|
|
|
|
);"#,
|
|
|
|
(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
r#"CREATE TABLE IF NOT EXISTS file_tag(
|
|
|
|
tag_id INT REFERENCES tag(id),
|
|
|
|
file_id INT REFERENCES file(id)
|
|
|
|
);"#,
|
|
|
|
(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_database() -> bool {
|
|
|
|
path::Path::new(DB_PATH).exists()
|
2025-05-01 00:18:55 +03:00
|
|
|
}
|