parent
45672c2964
commit
9a16557016
8 changed files with 2 additions and 380 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -286,43 +286,6 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.142"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.142"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.83"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -347,8 +310,6 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"time",
|
"time",
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,8 +14,6 @@ maintenance = { status = "experimental" }
|
||||||
clap = { version = "3.2.16", default-features = false, features = ["derive", "std"] }
|
clap = { version = "3.2.16", default-features = false, features = ["derive", "std"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled", "time"] }
|
rusqlite = { version = "0.28.0", features = ["bundled", "time"] }
|
||||||
serde = { version = "1.0.142", features = ["derive"] }
|
|
||||||
serde_json = "1.0.83"
|
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
xdg = "2.4.1"
|
xdg = "2.4.1"
|
||||||
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
use tas::repo::{self, Repository};
|
|
||||||
use xdg::BaseDirectories;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap();
|
|
||||||
let fs_repo = repo::fs::FsRepo::new(xdg_dirs.clone());
|
|
||||||
let sqlite_repo = repo::sqlite::SqliteRepo::new(xdg_dirs.clone()).unwrap();
|
|
||||||
|
|
||||||
log::info!("active tasks");
|
|
||||||
|
|
||||||
let fs_tasks = fs_repo.get_tasks(false).unwrap();
|
|
||||||
if !fs_tasks.is_empty() {
|
|
||||||
for task in fs_tasks {
|
|
||||||
log::info!("task: {}", task.name);
|
|
||||||
log::info!(" inserting...");
|
|
||||||
|
|
||||||
sqlite_repo
|
|
||||||
.insert_task(repo::InsertTaskData {
|
|
||||||
name: task.name,
|
|
||||||
project: task.project,
|
|
||||||
link: task.link,
|
|
||||||
dir_path: task.dir_path,
|
|
||||||
index: None,
|
|
||||||
finished_at: None,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
log::info!(" inserted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("finished tasks");
|
|
||||||
|
|
||||||
let fs_tasks = fs_repo.get_tasks(true).unwrap();
|
|
||||||
if !fs_tasks.is_empty() {
|
|
||||||
let meta = std::fs::metadata(xdg_dirs.get_data_file(repo::fs::FINISHED_DATA_FILE)).unwrap();
|
|
||||||
let finished_at = meta
|
|
||||||
.modified()
|
|
||||||
.map(time::OffsetDateTime::from)
|
|
||||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc());
|
|
||||||
|
|
||||||
for task in fs_tasks {
|
|
||||||
log::info!("task: {}", task.name);
|
|
||||||
log::info!(" inserting...");
|
|
||||||
|
|
||||||
sqlite_repo
|
|
||||||
.insert_task(repo::InsertTaskData {
|
|
||||||
name: task.name,
|
|
||||||
project: task.project,
|
|
||||||
link: task.link,
|
|
||||||
dir_path: task.dir_path,
|
|
||||||
index: None,
|
|
||||||
finished_at: Some(finished_at),
|
|
||||||
})
|
|
||||||
// TODO: think of a better solution than idx
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
log::info!(" inserted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -43,7 +43,6 @@ pub fn execute(repo: impl Repository, args: Args) {
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
index: None,
|
index: None,
|
||||||
finished_at: None,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
|
|
|
@ -77,7 +77,6 @@ pub fn execute(repo: impl Repository, args: Args) {
|
||||||
project: target.project,
|
project: target.project,
|
||||||
link: target.link,
|
link: target.link,
|
||||||
dir_path: target.dir_path,
|
dir_path: target.dir_path,
|
||||||
finished_at: None,
|
|
||||||
});
|
});
|
||||||
match res {
|
match res {
|
||||||
Ok(task) => {
|
Ok(task) => {
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
//! You should have received a copy of the GNU General Public License
|
//! You should have received a copy of the GNU General Public License
|
||||||
//! along with tas. If not, see <https://www.gnu.org/licenses/>.
|
//! along with tas. If not, see <https://www.gnu.org/licenses/>.
|
||||||
//!
|
//!
|
||||||
pub mod fs;
|
|
||||||
pub mod sqlite;
|
pub mod sqlite;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -55,7 +54,6 @@ pub struct InsertTaskData {
|
||||||
pub link: Option<String>,
|
pub link: Option<String>,
|
||||||
pub dir_path: Option<PathBuf>,
|
pub dir_path: Option<PathBuf>,
|
||||||
pub index: Option<usize>,
|
pub index: Option<usize>,
|
||||||
pub finished_at: Option<time::OffsetDateTime>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UpdateTaskData {
|
pub struct UpdateTaskData {
|
||||||
|
|
268
src/repo/fs.rs
268
src/repo/fs.rs
|
@ -1,268 +0,0 @@
|
||||||
//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@ideascup.me>
|
|
||||||
//!
|
|
||||||
//! tas is free software: you can redistribute it and/or modify
|
|
||||||
//! it under the terms of the GNU General Public License as published by
|
|
||||||
//! the Free Software Foundation, either version 3 of the License, or
|
|
||||||
//! (at your option) any later version.
|
|
||||||
//!
|
|
||||||
//! tas is distributed in the hope that it will be useful,
|
|
||||||
//! but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
//! GNU General Public License for more details.
|
|
||||||
//!
|
|
||||||
//! You should have received a copy of the GNU General Public License
|
|
||||||
//! along with tas. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
//!
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::domain;
|
|
||||||
use crate::repo::{Error, InsertTaskData, Repository, UpdateTaskData};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use xdg::BaseDirectories;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub struct Task {
|
|
||||||
name: String,
|
|
||||||
group: Option<String>,
|
|
||||||
link: Option<String>,
|
|
||||||
path: Option<String>,
|
|
||||||
// created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Task> for domain::Task {
|
|
||||||
fn from(repo: Task) -> Self {
|
|
||||||
domain::Task {
|
|
||||||
name: repo.name,
|
|
||||||
project: repo.group,
|
|
||||||
link: repo.link,
|
|
||||||
dir_path: repo.path.map(PathBuf::from),
|
|
||||||
created_at: time::OffsetDateTime::now_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub struct CurrentTaskInfo {
|
|
||||||
task_idx: usize,
|
|
||||||
task: Task,
|
|
||||||
// started_at
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CurrentTaskInfo> for domain::CurrentTaskInfo {
|
|
||||||
fn from(repo: CurrentTaskInfo) -> Self {
|
|
||||||
domain::CurrentTaskInfo {
|
|
||||||
task_idx: repo.task_idx,
|
|
||||||
task: repo.task.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const CURRENT_TASK_FILE: &str = "current.json";
|
|
||||||
pub const DATA_FILE: &str = "data.json";
|
|
||||||
pub const FINISHED_DATA_FILE: &str = "finished_data.json";
|
|
||||||
|
|
||||||
pub struct FsRepo {
|
|
||||||
xdg_dirs: BaseDirectories,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FsRepo {
|
|
||||||
pub fn new(xdg_dirs: BaseDirectories) -> Self {
|
|
||||||
Self { xdg_dirs }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Repository for FsRepo {
|
|
||||||
fn get_current_task_opt(&self) -> Result<Option<domain::CurrentTaskInfo>, Error> {
|
|
||||||
self.get_current_task_impl()
|
|
||||||
.map(|cur_task| cur_task.map(From::from))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_task_opt(&self, id: domain::TaskIdx) -> Result<Option<domain::Task>, Error> {
|
|
||||||
let tasks = self.get_tasks_impl()?;
|
|
||||||
if id == 0 || id > tasks.len() {
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(tasks[id - 1].clone().into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tasks(&self, finished: bool) -> Result<Vec<domain::Task>, Error> {
|
|
||||||
let fs_tasks = if finished {
|
|
||||||
self.get_finished_tasks_impl()?.into_iter().rev().collect()
|
|
||||||
} else {
|
|
||||||
self.get_tasks_impl()?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(fs_tasks.into_iter().map(Task::into).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_task(&self, id: domain::TaskIdx) -> Result<domain::Task, Error> {
|
|
||||||
let mut tasks = self.get_tasks_impl()?;
|
|
||||||
if id == 0 || id > tasks.len() {
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
let removed = tasks.remove(id - 1);
|
|
||||||
self.save_tasks_impl(tasks)?;
|
|
||||||
|
|
||||||
Ok(removed.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_task(
|
|
||||||
&self,
|
|
||||||
id: domain::TaskIdx,
|
|
||||||
update_data: UpdateTaskData,
|
|
||||||
) -> Result<domain::Task, Error> {
|
|
||||||
let mut tasks = self.get_tasks_impl()?;
|
|
||||||
if id == 0 || id > tasks.len() {
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut task = &mut tasks[id - 1];
|
|
||||||
if let Some(name) = update_data.name {
|
|
||||||
task.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(group) = update_data.project {
|
|
||||||
task.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(link) = update_data.link {
|
|
||||||
task.link = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(path) = update_data.dir_path {
|
|
||||||
task.path = path.and_then(|p| p.into_os_string().into_string().ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_task = task.clone();
|
|
||||||
|
|
||||||
self.save_tasks_impl(tasks)?;
|
|
||||||
|
|
||||||
Ok(new_task.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_task(&self, insert_data: InsertTaskData) -> Result<domain::Task, Error> {
|
|
||||||
let new_task = Task {
|
|
||||||
name: insert_data.name,
|
|
||||||
group: insert_data.project,
|
|
||||||
link: insert_data.link,
|
|
||||||
path: insert_data
|
|
||||||
.dir_path
|
|
||||||
.and_then(|p| p.into_os_string().into_string().ok()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tasks = self.get_tasks_impl()?;
|
|
||||||
|
|
||||||
match insert_data.index {
|
|
||||||
None => tasks.push(new_task.clone()),
|
|
||||||
Some(idx) => tasks.insert(idx, new_task.clone()),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.save_tasks_impl(tasks)?;
|
|
||||||
|
|
||||||
Ok(new_task.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_task(&self, id: domain::TaskIdx) -> Result<domain::Task, Error> {
|
|
||||||
let tasks = self.get_tasks_impl()?;
|
|
||||||
if id == 0 || id > tasks.len() {
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
let task = &tasks[id - 1];
|
|
||||||
let mut cur_task = self.get_current_task_impl()?;
|
|
||||||
|
|
||||||
cur_task.replace(CurrentTaskInfo {
|
|
||||||
task_idx: id,
|
|
||||||
task: task.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
self.save_current_task_impl(cur_task)?;
|
|
||||||
Ok(task.clone().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_task(&self) -> Result<domain::Task, Error> {
|
|
||||||
let mut cur_task = self.get_current_task_impl()?;
|
|
||||||
let old = cur_task.take().ok_or(Error::NotFound)?;
|
|
||||||
self.save_current_task_impl(cur_task)?;
|
|
||||||
Ok(old.task.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_task(&self) -> Result<domain::Task, Error> {
|
|
||||||
let mut cur_task = self.get_current_task_impl()?;
|
|
||||||
let old = cur_task.take().ok_or(Error::NotFound)?;
|
|
||||||
|
|
||||||
let mut finished_tasks = self.get_finished_tasks_impl()?;
|
|
||||||
let mut tasks = self.get_tasks_impl()?;
|
|
||||||
|
|
||||||
let task = tasks.remove(old.task_idx - 1);
|
|
||||||
finished_tasks.push(task.clone());
|
|
||||||
|
|
||||||
self.save_current_task_impl(cur_task)?;
|
|
||||||
self.save_tasks_impl(tasks)?;
|
|
||||||
self.save_finished_tasks_impl(finished_tasks)?;
|
|
||||||
|
|
||||||
Ok(task.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FsRepo {
|
|
||||||
fn get_current_task_impl(&self) -> Result<Option<CurrentTaskInfo>, Error> {
|
|
||||||
let file_path = self.xdg_dirs.get_data_file(CURRENT_TASK_FILE);
|
|
||||||
Ok(File::open(&file_path)
|
|
||||||
.ok()
|
|
||||||
.and_then(|file| serde_json::from_reader(file).ok()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_current_task_impl(&self, cur_task: Option<CurrentTaskInfo>) -> Result<(), Error> {
|
|
||||||
let file_path = self
|
|
||||||
.xdg_dirs
|
|
||||||
.place_data_file(CURRENT_TASK_FILE)
|
|
||||||
.map_err(|_| Error::InsertData)?;
|
|
||||||
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
|
|
||||||
let new_data = serde_json::to_vec(&cur_task).map_err(|_| Error::InvalidData)?;
|
|
||||||
file.write_all(&new_data).map_err(|_| Error::InsertData)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tasks_impl(&self) -> Result<Vec<Task>, Error> {
|
|
||||||
let file_path = self.xdg_dirs.get_data_file(DATA_FILE);
|
|
||||||
File::open(&file_path)
|
|
||||||
.ok()
|
|
||||||
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
|
|
||||||
.transpose()
|
|
||||||
.map(|tasks| tasks.unwrap_or_default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_tasks_impl(&self, tasks: Vec<Task>) -> Result<(), Error> {
|
|
||||||
let file_path = self
|
|
||||||
.xdg_dirs
|
|
||||||
.place_data_file(DATA_FILE)
|
|
||||||
.map_err(|_| Error::InsertData)?;
|
|
||||||
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
|
|
||||||
let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?;
|
|
||||||
file.write_all(&new_data).map_err(|_| Error::InsertData)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_finished_tasks_impl(&self) -> Result<Vec<Task>, Error> {
|
|
||||||
let file_path = self.xdg_dirs.get_data_file(FINISHED_DATA_FILE);
|
|
||||||
File::open(&file_path)
|
|
||||||
.ok()
|
|
||||||
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
|
|
||||||
.transpose()
|
|
||||||
.map(|tasks| tasks.unwrap_or_default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_finished_tasks_impl(&self, tasks: Vec<Task>) -> Result<(), Error> {
|
|
||||||
let file_path = self
|
|
||||||
.xdg_dirs
|
|
||||||
.place_data_file(FINISHED_DATA_FILE)
|
|
||||||
.map_err(|_| Error::InsertData)?;
|
|
||||||
let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?;
|
|
||||||
let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?;
|
|
||||||
file.write_all(&new_data).map_err(|_| Error::InsertData)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -121,8 +121,8 @@ impl Repository for SqliteRepo {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"INSERT INTO tasks (name, project, link, dir_path, created_at, finished_at)
|
"INSERT INTO tasks (name, project, link, dir_path)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
)
|
)
|
||||||
.map_err(|_| Error::PrepareQuery)?;
|
.map_err(|_| Error::PrepareQuery)?;
|
||||||
|
|
||||||
|
@ -134,10 +134,6 @@ impl Repository for SqliteRepo {
|
||||||
&insert_data
|
&insert_data
|
||||||
.dir_path
|
.dir_path
|
||||||
.and_then(|p| p.into_os_string().into_string().ok()),
|
.and_then(|p| p.into_os_string().into_string().ok()),
|
||||||
&insert_data
|
|
||||||
.finished_at
|
|
||||||
.unwrap_or_else(|| time::OffsetDateTime::now_utc()),
|
|
||||||
&insert_data.finished_at,
|
|
||||||
))
|
))
|
||||||
.map_err(|_| Error::InsertData)?;
|
.map_err(|_| Error::InsertData)?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue