add move other methods to repo

This commit is contained in:
Dmitriy Pleshevskiy 2022-08-16 15:43:44 +03:00
parent 4dbfe41e84
commit 72a37b4a1d
Signed by: pleshevskiy
GPG key ID: 1B59187B161C0215
13 changed files with 313 additions and 222 deletions

View file

@ -12,6 +12,7 @@ pub fn execute(repo: impl Repository, args: Args) {
let res = repo.insert_task(repo::InsertTaskData { let res = repo.insert_task(repo::InsertTaskData {
name: args.name, name: args.name,
link: args.link, link: args.link,
index: None,
}); });
match res { match res {

View file

@ -1,7 +1,4 @@
use std::io::Write;
use crate::repo::{self, Repository}; use crate::repo::{self, Repository};
use crate::CurrentTaskInfo;
#[derive(clap::Args)] #[derive(clap::Args)]
pub struct Args { pub struct Args {
@ -16,23 +13,22 @@ pub struct Args {
idx: usize, idx: usize,
} }
pub struct Request { pub fn execute(repo: impl Repository, args: Args) {
pub args: Args, match repo.get_current_task_opt() {
pub current_task_info: Option<CurrentTaskInfo>, Ok(Some(_)) => {
} return eprintln!("You can edit task only when you don't have an active task, yet")
}
pub fn execute(repo: impl Repository, mut req: Request) { Err(err) => {
if req.current_task_info.is_some() { return eprintln!("Cannot read current task: {}", err);
panic!("You can edit task only when you don't have an active task, yet"); }
_ => {}
} }
let args = req.args;
let res = repo.update_task( let res = repo.update_task(
args.idx, args.idx,
repo::UpdateTaskData { repo::UpdateTaskData {
name: args.name, name: args.name,
link: args.no_link.then(|| None).or(args.link.map(Some)), link: args.no_link.then(|| None).or_else(|| args.link.map(Some)),
}, },
); );
match res { match res {

View file

@ -1,41 +1,17 @@
use crate::{CurrentTaskInfo, Task}; use crate::repo::{self, Repository};
use std::io::Write;
use std::path::PathBuf;
pub struct Request { pub fn execute(repo: impl Repository) {
pub current_task_info: Option<CurrentTaskInfo>, match repo.finish_task() {
pub tasks: Vec<Task>, Err(repo::Error::NotFound) => {
pub finished_tasks_file_path: PathBuf, eprintln!("You can use the finish subcommand only when you have an active task")
pub tasks_file_path: PathBuf, }
pub current_task_info_file_path: PathBuf, Err(err) => eprintln!("Cannot finish the task: {}", err),
} Ok(task) => {
println!("The task was finished successfully");
pub fn execute(mut req: Request) { println!(" {}", task.name);
match req.current_task_info.take() { if let Some(link) = task.link {
None => { println!(" link: {}", link);
panic!("You can use the finish subcommand only when you have an active task")
} }
Some(info) => {
let mut finished_tasks: Vec<Task> = std::fs::File::open(&req.finished_tasks_file_path)
.map(|file| serde_json::from_reader(file).unwrap())
.unwrap_or_default();
let task = req.tasks.remove(info.task_idx - 1);
finished_tasks.push(task);
let mut file = std::fs::File::create(req.tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.tasks).unwrap())
.unwrap();
let mut file = std::fs::File::create(req.finished_tasks_file_path).unwrap();
file.write_all(&serde_json::to_vec(&finished_tasks).unwrap())
.unwrap();
let mut file = std::fs::File::create(req.current_task_info_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap())
.unwrap();
println!("finished");
} }
} }
} }

View file

@ -1,15 +1,23 @@
use crate::{CurrentTaskInfo, Task}; use crate::domain::CurrentTaskInfo;
use crate::repo::Repository;
pub struct Request { pub fn execute(repo: impl Repository) {
pub tasks: Vec<Task>, let tasks = match repo.get_tasks() {
pub current_task_info: Option<CurrentTaskInfo>, Ok(tasks) => tasks,
} Err(err) => return eprintln!("Cannot read tasks: {}", err),
};
pub fn execute(req: Request) { let cur_task = match repo.get_current_task_opt() {
for (i, task) in req.tasks.iter().enumerate() { Ok(cur_task) => cur_task,
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
};
for (i, task) in tasks.iter().enumerate() {
let idx = i + 1; let idx = i + 1;
match req.current_task_info { match cur_task {
Some(CurrentTaskInfo { task_idx, .. }) if task_idx == idx => print!("> "), Some(CurrentTaskInfo { task_idx, .. }) if task_idx == idx => print!("> "),
_ => print!(" "), _ => print!(" "),
} }

View file

@ -1,20 +1,26 @@
use std::io::Write; use crate::repo::{self, Repository};
use std::path::PathBuf;
use crate::CurrentTaskInfo; pub fn execute(repo: impl Repository) {
let task = match repo.stop_task() {
pub struct Request { Err(repo::Error::NotFound) => {
pub current_task_info: Option<CurrentTaskInfo>, return eprintln!("You can use the pause subcommand only when you have an active task")
pub current_task_info_file_path: PathBuf,
}
pub fn execute(mut req: Request) {
if req.current_task_info.take().is_none() {
panic!("You can use the pause subcommand only when you have an active task");
} }
Err(err) => {
return eprintln!("Cannot read current task: {}", err);
}
Ok(task) => task,
};
let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap(); match repo.stop_task() {
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap()) Ok(_) => {
.unwrap(); println!("The task was paused successfully");
println!("paused"); println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot pause the task: {}", err);
}
}
} }

View file

@ -1,8 +1,6 @@
use crate::{CurrentTaskInfo, Task}; use crate::repo::{self, Repository};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::io::Write;
use std::path::PathBuf;
#[derive(clap::Args)] #[derive(clap::Args)]
pub struct Args { pub struct Args {
@ -17,46 +15,61 @@ pub enum Priority {
After { idx: usize }, After { idx: usize },
} }
pub struct Request { pub fn execute(repo: impl Repository, args: Args) {
pub args: Args, match repo.get_current_task_opt() {
pub tasks: Vec<Task>, Ok(Some(_)) => {
pub current_task_info: Option<CurrentTaskInfo>, return eprintln!(
pub tasks_file_path: PathBuf, "You can change priority only when you don't have an active task, yet"
} )
}
pub fn execute(mut req: Request) { Err(err) => {
if req.current_task_info.is_some() { return eprintln!("Cannot read current task: {}", err);
panic!("You can change priority only when you don't have an active task, yet"); }
_ => {}
} }
let target_idx = req.args.idx; let target_idx = args.idx;
if target_idx == 0 || target_idx > req.tasks.len() {
panic!("invalid index"); if let Err(err) = repo.get_task_opt(target_idx) {
return eprintln!("Task not found: {}", err);
} }
let idx = match req.args.priority { let idx = match args.priority {
Priority::Before { idx } | Priority::After { idx } => match target_idx.cmp(&idx) { Priority::Before { idx } | Priority::After { idx } => match target_idx.cmp(&idx) {
Ordering::Equal => return, Ordering::Equal => return,
Ordering::Less => idx - 1, Ordering::Less => idx - 1,
Ordering::Greater => idx, Ordering::Greater => idx,
}, },
}; };
if idx == 0 || idx > req.tasks.len() { if let Err(err) = repo.get_task_opt(idx) {
panic!("invalid index"); return eprintln!("Task not found: {}", err);
} }
let target = req.tasks.remove(target_idx - 1); let target = match repo.remove_task(target_idx) {
Ok(removed) => removed,
Err(err) => return eprintln!("Cannot remove the task: {}", err),
};
match req.args.priority { let new_idx = match args.priority {
Priority::Before { .. } => { Priority::Before { .. } => idx - 1,
req.tasks.insert(idx - 1, target); Priority::After { .. } => idx,
} };
Priority::After { .. } => {
req.tasks.insert(idx, target);
}
}
let mut file = std::fs::File::create(&req.tasks_file_path).unwrap(); let res = repo.insert_task(repo::InsertTaskData {
file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) index: Some(new_idx),
.unwrap(); name: target.name,
link: target.link,
});
match res {
Ok(task) => {
println!("The task was reordered successfully");
println!(" {}", task.name);
if let Some(link) = task.link {
println!(" link: {}", link);
}
}
Err(err) => {
eprintln!("Cannot reorder the task: {}", err);
}
}
} }

View file

@ -1,31 +1,28 @@
use crate::repo::{self, Repository};
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
use std::path::PathBuf;
use crate::{CurrentTaskInfo, Task};
#[derive(clap::Args)] #[derive(clap::Args)]
pub struct Args { pub struct Args {
idx: usize, idx: usize,
} }
pub struct Request { pub fn execute(repo: impl Repository, args: Args) {
pub args: Args, match repo.get_current_task_opt() {
pub current_task_info: Option<CurrentTaskInfo>, Ok(Some(_)) => {
pub tasks: Vec<Task>, return eprintln!("You can remove task only when you don't have an active task, yet");
pub tasks_file_path: PathBuf, }
} Err(err) => {
return eprintln!("Cannot read current task: {}", err);
pub fn execute(mut req: Request) { }
if req.current_task_info.is_some() { _ => {}
panic!("You can remove task only when you don't have an active task, yet");
} }
let idx = req.args.idx; let task = match repo.get_task_opt(args.idx) {
if idx == 0 || idx > req.tasks.len() { Ok(Some(task)) => task,
panic!("invalid index"); Ok(None) | Err(repo::Error::NotFound) => return eprintln!("Task not found"),
} Err(err) => return eprintln!("Cannot get task: {}", err),
};
let task = &req.tasks[idx - 1];
println!("You are deleting task:"); println!("You are deleting task:");
println!(" {}", task.name); println!(" {}", task.name);
if let Some(ref link) = task.link { if let Some(ref link) = task.link {
@ -46,11 +43,16 @@ pub fn execute(mut req: Request) {
} }
} }
req.tasks.remove(idx - 1); match repo.remove_task(args.idx) {
Ok(_) => {
let mut file = std::fs::File::create(&req.tasks_file_path).unwrap(); println!("The task was removed successfully");
file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) println!(" {}", task.name);
.unwrap(); if let Some(link) = task.link {
println!(" link: {}", link);
println!("removed"); }
}
Err(err) => {
eprintln!("Cannot remove the task: {}", err);
}
}
} }

View file

@ -1,5 +1,4 @@
use crate::{CurrentTaskInfo, Task}; use crate::repo::{self, Repository};
use std::{io::Write, path::PathBuf};
#[derive(clap::Args)] #[derive(clap::Args)]
pub struct Args { pub struct Args {
@ -9,31 +8,20 @@ pub struct Args {
idx: Option<usize>, idx: Option<usize>,
} }
pub struct Request { pub fn execute(repo: impl Repository, args: Args) {
pub args: Args, let task = match repo.start_task(args.idx.unwrap_or(1)) {
pub tasks: Vec<Task>, Ok(task) => task,
pub current_task_info: Option<CurrentTaskInfo>, Err(repo::Error::NotFound) => return eprintln!("Task not found"),
pub current_task_info_file_path: PathBuf, Err(err) => return eprintln!("Cannot start task: {}", err),
} };
pub fn execute(mut req: Request) { println!("The task was started successfully");
let idx = req.args.idx.unwrap_or(1); println!(" {}", task.name);
if idx == 0 || idx > req.tasks.len() { if let Some(link) = task.link.as_ref() {
panic!("invalid index"); println!(" link: {}", link);
} }
let task = &req.tasks[idx - 1];
req.current_task_info.replace(CurrentTaskInfo { if let (Some(link), true) = (task.link.as_ref(), args.open) {
task_idx: idx,
task: task.clone(),
});
let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap();
file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap())
.unwrap();
println!("started");
if let (Some(link), true) = (task.link.as_ref(), req.args.open) {
log::debug!("opening link..."); log::debug!("opening link...");
std::process::Command::new("xdg-open") std::process::Command::new("xdg-open")
.arg(link) .arg(link)

View file

@ -1,16 +1,19 @@
use crate::CurrentTaskInfo; use crate::repo::Repository;
pub fn execute(current_task_info: Option<CurrentTaskInfo>) { pub fn execute(repo: impl Repository) {
match current_task_info { match repo.get_current_task_opt() {
None => { Ok(None) => {
eprintln!("You don't have an active task."); eprintln!("You don't have an active task.");
} }
Some(info) => { Ok(Some(info)) => {
println!("Information about your current task:"); println!("Information about your current task:");
println!(" {}", info.task.name); println!(" {}", info.task.name);
if let Some(link) = info.task.link { if let Some(link) = info.task.link {
println!(" link: {}", link); println!(" link: {}", link);
} }
} }
Err(err) => {
eprintln!("Cannot read current task: {}", err);
}
} }
} }

View file

@ -5,3 +5,9 @@ pub struct Task {
pub link: Option<String>, pub link: Option<String>,
// created_at // created_at
} }
pub struct CurrentTaskInfo {
pub task_idx: usize,
pub task: Task,
// started_at
}

View file

@ -28,7 +28,6 @@
use clap::Parser; use clap::Parser;
use repo::fs::FsRepo; use repo::fs::FsRepo;
use serde::{Deserialize, Serialize};
use xdg::BaseDirectories; use xdg::BaseDirectories;
mod cli; mod cli;
@ -39,84 +38,35 @@ fn main() {
let args = cli::Args::parse(); let args = cli::Args::parse();
let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap(); let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap();
let repo = FsRepo::new(xdg_dirs); let repo = FsRepo::new(xdg_dirs);
let finished_tasks_file_path = xdg_dirs.place_data_file("finished_data.json").unwrap();
let current_task_info_file_path = xdg_dirs.place_data_file("current.json").unwrap();
let current_task_info: Option<CurrentTaskInfo> =
std::fs::File::open(&current_task_info_file_path)
.map(|file| serde_json::from_reader(file).unwrap())
.unwrap_or_default();
match args.command { match args.command {
cli::SubCommand::Add(args) => { cli::SubCommand::Add(args) => {
cli::add::execute(repo, args); cli::add::execute(repo, args);
} }
cli::SubCommand::Edit(args) => { cli::SubCommand::Edit(args) => {
cli::edit::execute( cli::edit::execute(repo, args);
repo,
cli::edit::Request {
args,
current_task_info,
},
);
} }
cli::SubCommand::Remove(args) => { cli::SubCommand::Remove(args) => {
cli::remove::execute(cli::remove::Request { cli::remove::execute(repo, args);
args,
current_task_info,
tasks,
tasks_file_path,
});
} }
cli::SubCommand::List => { cli::SubCommand::List => {
cli::list::execute(cli::list::Request { cli::list::execute(repo);
tasks,
current_task_info,
});
} }
cli::SubCommand::Priority(args) => { cli::SubCommand::Priority(args) => {
cli::priority::execute(cli::priority::Request { cli::priority::execute(repo, args);
args,
tasks,
tasks_file_path,
current_task_info,
});
} }
cli::SubCommand::Start(args) => { cli::SubCommand::Start(args) => {
cli::start::execute(cli::start::Request { cli::start::execute(repo, args);
args,
tasks,
current_task_info,
current_task_info_file_path,
});
} }
cli::SubCommand::Pause => { cli::SubCommand::Pause => {
cli::pause::execute(cli::pause::Request { cli::pause::execute(repo);
current_task_info,
current_task_info_file_path,
});
} }
cli::SubCommand::Finish => { cli::SubCommand::Finish => {
cli::finish::execute(cli::finish::Request { cli::finish::execute(repo);
tasks,
current_task_info,
current_task_info_file_path,
tasks_file_path,
finished_tasks_file_path,
});
} }
cli::SubCommand::Status => { cli::SubCommand::Status => {
cli::status::execute(current_task_info); cli::status::execute(repo);
} }
} }
} }
#[derive(Deserialize, Serialize)]
pub struct CurrentTaskInfo {
task_idx: usize,
task: Task,
// started_at
}

View file

@ -22,18 +22,25 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {} impl std::error::Error for Error {}
pub struct InsertTaskData { pub struct InsertTaskData {
name: String, pub name: String,
link: Option<String>, pub link: Option<String>,
pub index: Option<usize>,
} }
pub struct UpdateTaskData { pub struct UpdateTaskData {
name: Option<String>, pub name: Option<String>,
link: Option<Option<String>>, pub link: Option<Option<String>>,
} }
pub trait Repository { pub trait Repository {
fn get_current_task_opt(&self) -> Result<Option<domain::CurrentTaskInfo>, Error>;
fn get_task_opt(&self, id: domain::TaskId) -> Result<Option<domain::Task>, Error>;
fn get_tasks(&self) -> Result<Vec<domain::Task>, Error>; fn get_tasks(&self) -> Result<Vec<domain::Task>, Error>;
fn remove_task(&self, id: domain::TaskId) -> Result<domain::Task, Error>;
fn update_task( fn update_task(
&self, &self,
id: domain::TaskId, id: domain::TaskId,
@ -41,4 +48,10 @@ pub trait Repository {
) -> Result<domain::Task, Error>; ) -> Result<domain::Task, Error>;
fn insert_task(&self, insert_data: InsertTaskData) -> Result<domain::Task, Error>; fn insert_task(&self, insert_data: InsertTaskData) -> Result<domain::Task, Error>;
fn start_task(&self, id: domain::TaskId) -> Result<domain::Task, Error>;
fn stop_task(&self) -> Result<domain::Task, Error>;
fn finish_task(&self) -> Result<domain::Task, Error>;
} }

View file

@ -23,7 +23,25 @@ impl From<Task> for domain::Task {
} }
} }
#[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(),
}
}
}
const CURRENT_TASK_FILE: &str = "current.json";
const DATA_FILE: &str = "data.json"; const DATA_FILE: &str = "data.json";
const FINISHED_DATA_FILE: &str = "finished_data.json";
pub struct FsRepo { pub struct FsRepo {
xdg_dirs: BaseDirectories, xdg_dirs: BaseDirectories,
@ -36,11 +54,37 @@ impl FsRepo {
} }
impl Repository for FsRepo { 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::TaskId) -> 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) -> Result<Vec<domain::Task>, Error> { fn get_tasks(&self) -> Result<Vec<domain::Task>, Error> {
self.get_tasks_impl() self.get_tasks_impl()
.map(|tasks| tasks.into_iter().map(Task::into).collect()) .map(|tasks| tasks.into_iter().map(Task::into).collect())
} }
fn remove_task(&self, id: domain::TaskId) -> 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( fn update_task(
&self, &self,
id: domain::TaskId, id: domain::TaskId,
@ -74,20 +118,86 @@ impl Repository for FsRepo {
}; };
let mut tasks = self.get_tasks_impl()?; let mut tasks = self.get_tasks_impl()?;
tasks.push(new_task.clone());
match insert_data.index {
None => tasks.push(new_task.clone()),
Some(idx) => tasks.insert(idx, new_task.clone()),
}
self.save_tasks_impl(tasks)?; self.save_tasks_impl(tasks)?;
Ok(new_task.into()) Ok(new_task.into())
} }
fn start_task(&self, id: domain::TaskId) -> 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 { impl FsRepo {
fn get_current_task_impl(&self) -> Result<Option<CurrentTaskInfo>, Error> {
let file_path = self.xdg_dirs.get_data_file(CURRENT_TASK_FILE);
File::open(&file_path)
.ok()
.map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData))
.transpose()
}
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> { fn get_tasks_impl(&self) -> Result<Vec<Task>, Error> {
let file_path = self.xdg_dirs.get_data_file(DATA_FILE); let file_path = self.xdg_dirs.get_data_file(DATA_FILE);
File::open(&file_path) File::open(&file_path)
.map_err(|_| Error::NotFound) .ok()
.and_then(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) .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> { fn save_tasks_impl(&self, tasks: Vec<Task>) -> Result<(), Error> {
@ -99,4 +209,23 @@ impl FsRepo {
let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?; let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?;
file.write_all(&new_data).map_err(|_| Error::InsertData) 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)
}
} }