feat: Add TaskWarrior integration with TaskChampion
- Error handling with TaskError enum and TaskResult<T> alias - Data models: Task, TaskPriority, TaskStatus, TaskAnnotation - Advanced filtering: TaskFilter with hierarchical projects, tags (AND/OR), priority, due dates, and search - TaskService: Full CRUD, tags, projects, annotations, dependencies, sync
This commit is contained in:
@@ -3,6 +3,7 @@ use crate::app::App;
|
|||||||
mod app;
|
mod app;
|
||||||
mod components;
|
mod components;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod task;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub enum TaskError {
|
||||||
|
Storage(String),
|
||||||
|
NotFound(uuid::Uuid),
|
||||||
|
InvalidTag(String),
|
||||||
|
InvalidProject(String),
|
||||||
|
InvalidPriority(String),
|
||||||
|
InvalidDue(String),
|
||||||
|
InvalidWait(String),
|
||||||
|
InvalidAnnotation(String),
|
||||||
|
InvalidDependency(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TaskError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TaskError::Storage(msg) => write!(f, "Storage error: {}", msg),
|
||||||
|
TaskError::NotFound(id) => write!(f, "Task not found: {}", id),
|
||||||
|
TaskError::InvalidTag(tag) => write!(f, "Invalid tag: {}", tag),
|
||||||
|
TaskError::InvalidProject(project) => write!(f, "Invalid project: {}", project),
|
||||||
|
TaskError::InvalidPriority(priority) => write!(f, "Invalid priority: {}", priority),
|
||||||
|
TaskError::InvalidDue(due) => write!(f, "Invalid due date: {}", due),
|
||||||
|
TaskError::InvalidWait(wait) => write!(f, "Invalid wait date: {}", wait),
|
||||||
|
TaskError::InvalidAnnotation(annotation) => {
|
||||||
|
write!(f, "Invalid annotation: {}", annotation)
|
||||||
|
}
|
||||||
|
TaskError::InvalidDependency(dependency) => {
|
||||||
|
write!(f, "Invalid dependency: {}", dependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TaskResult<T> = Result<T, TaskError>;
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
use super::model::{Task, TaskPriority, TaskStatus};
|
||||||
|
use crate::models::{DueFilter, FilterState, PriorityFilter, StatusFilter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum TagsFilterMode {
|
||||||
|
#[default]
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum DueDateFilter {
|
||||||
|
Overdue,
|
||||||
|
Today,
|
||||||
|
ThisWeek,
|
||||||
|
NoDate,
|
||||||
|
Before(DateTime<Utc>),
|
||||||
|
After(DateTime<Utc>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct TaskFilter {
|
||||||
|
pub status: Option<TaskStatus>,
|
||||||
|
pub project: Option<String>,
|
||||||
|
pub project_include_children: bool,
|
||||||
|
pub tags: HashSet<String>,
|
||||||
|
pub tags_mode: TagsFilterMode,
|
||||||
|
pub priority: Option<TaskPriority>,
|
||||||
|
pub due_filter: Option<DueDateFilter>,
|
||||||
|
pub search_text: Option<String>,
|
||||||
|
pub is_active: Option<bool>,
|
||||||
|
pub is_blocked: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskFilter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_status(mut self, status: TaskStatus) -> Self {
|
||||||
|
self.status = Some(status);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_project(mut self, project: String, include_children: bool) -> Self {
|
||||||
|
self.project = Some(project);
|
||||||
|
self.project_include_children = include_children;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_tags(mut self, tags: HashSet<String>, mode: TagsFilterMode) -> Self {
|
||||||
|
self.tags = tags;
|
||||||
|
self.tags_mode = mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_priority(mut self, priority: TaskPriority) -> Self {
|
||||||
|
self.priority = Some(priority);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_due(mut self, filter: DueDateFilter) -> Self {
|
||||||
|
self.due_filter = Some(filter);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_search(mut self, text: String) -> Self {
|
||||||
|
if !text.is_empty() {
|
||||||
|
self.search_text = Some(text.to_lowercase());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&FilterState> for TaskFilter {
|
||||||
|
fn from(state: &FilterState) -> Self {
|
||||||
|
let mut filter = Self::new();
|
||||||
|
|
||||||
|
filter.status = match state.status_filter {
|
||||||
|
StatusFilter::All => None,
|
||||||
|
StatusFilter::Pending => Some(TaskStatus::Pending),
|
||||||
|
StatusFilter::Completed => Some(TaskStatus::Completed),
|
||||||
|
StatusFilter::Waiting => Some(TaskStatus::Pending),
|
||||||
|
StatusFilter::Deleted => Some(TaskStatus::Deleted),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref project) = state.selected_project {
|
||||||
|
filter.project = Some(project.clone());
|
||||||
|
filter.project_include_children = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.active_tags.is_empty() {
|
||||||
|
filter.tags = state.active_tags.clone();
|
||||||
|
filter.tags_mode = TagsFilterMode::And;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.priority = match state.priority_filter {
|
||||||
|
PriorityFilter::All => None,
|
||||||
|
PriorityFilter::High => Some(TaskPriority::High),
|
||||||
|
PriorityFilter::Medium => Some(TaskPriority::Medium),
|
||||||
|
PriorityFilter::Low => Some(TaskPriority::Low),
|
||||||
|
PriorityFilter::None => Some(TaskPriority::None),
|
||||||
|
};
|
||||||
|
|
||||||
|
filter.due_filter = match state.due_filter {
|
||||||
|
DueFilter::All => None,
|
||||||
|
DueFilter::Overdue => Some(DueDateFilter::Overdue),
|
||||||
|
DueFilter::Today => Some(DueDateFilter::Today),
|
||||||
|
DueFilter::ThisWeek => Some(DueDateFilter::ThisWeek),
|
||||||
|
DueFilter::NoDate => Some(DueDateFilter::NoDate),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !state.search_text.is_empty() {
|
||||||
|
filter.search_text = Some(state.search_text.to_lowercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FilterState> for TaskFilter {
|
||||||
|
fn from(state: FilterState) -> Self {
|
||||||
|
Self::from(&state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskFilter {
|
||||||
|
pub fn matches(&self, task: &Task) -> bool {
|
||||||
|
if let Some(status) = &self.status {
|
||||||
|
match status {
|
||||||
|
TaskStatus::Pending => {
|
||||||
|
if !matches!(task.status, TaskStatus::Pending) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if task.wait.map(|w| w > Utc::now()).unwrap_or(false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if &task.status != status {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(project) = &self.project {
|
||||||
|
match &task.project {
|
||||||
|
None => return false,
|
||||||
|
Some(task_project) => {
|
||||||
|
if self.project_include_children {
|
||||||
|
if !task_project.starts_with(project) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if task_project != project {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.tags.is_empty() {
|
||||||
|
match self.tags_mode {
|
||||||
|
TagsFilterMode::And => {
|
||||||
|
for tag in &self.tags {
|
||||||
|
if !task.tags.contains(tag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TagsFilterMode::Or => {
|
||||||
|
let has_any = self.tags.iter().any(|t| task.tags.contains(t));
|
||||||
|
if !has_any {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(priority) = &self.priority {
|
||||||
|
if &task.priority != priority {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(due_filter) = &self.due_filter {
|
||||||
|
match due_filter {
|
||||||
|
DueDateFilter::Overdue => {
|
||||||
|
if !task.is_overdue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DueDateFilter::Today => {
|
||||||
|
if !task.is_due_today() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DueDateFilter::ThisWeek => {
|
||||||
|
let is_due_this_week = task
|
||||||
|
.due
|
||||||
|
.map(|d| {
|
||||||
|
let now = Utc::now();
|
||||||
|
let week_end = now + chrono::Duration::days(7);
|
||||||
|
d >= now && d <= week_end
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !is_due_this_week {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DueDateFilter::NoDate => {
|
||||||
|
if task.due.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DueDateFilter::Before(dt) => {
|
||||||
|
if task.due.map(|d| d >= *dt).unwrap_or(true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DueDateFilter::After(dt) => {
|
||||||
|
if task.due.map(|d| d <= *dt).unwrap_or(true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(search) = &self.search_text {
|
||||||
|
let desc_match = task.description.to_lowercase().contains(search);
|
||||||
|
let proj_match = task
|
||||||
|
.project
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.to_lowercase().contains(search))
|
||||||
|
.unwrap_or(false);
|
||||||
|
let tag_match = task.tags.iter().any(|t| t.to_lowercase().contains(search));
|
||||||
|
|
||||||
|
if !desc_match && !proj_match && !tag_match {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(is_active) = self.is_active {
|
||||||
|
if task.is_active != is_active {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(is_blocked) = self.is_blocked {
|
||||||
|
if task.is_blocked != is_blocked {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&self, tasks: &[Task]) -> Vec<Task> {
|
||||||
|
tasks.iter().filter(|t| self.matches(t)).cloned().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pub mod error;
|
||||||
|
pub mod filter;
|
||||||
|
pub mod model;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
pub use error::{TaskError, TaskResult};
|
||||||
|
pub use filter::{DueDateFilter, TagsFilterMode, TaskFilter};
|
||||||
|
pub use model::{Task, TaskAnnotation, TaskPriority, TaskStatus, TaskUpdate};
|
||||||
|
pub use service::{SyncResult, TaskService};
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TaskPriority {
|
||||||
|
High,
|
||||||
|
Medium,
|
||||||
|
Low,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TaskPriority {
|
||||||
|
fn default() -> Self {
|
||||||
|
TaskPriority::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TaskPriority {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TaskPriority::High => write!(f, "High"),
|
||||||
|
TaskPriority::Medium => write!(f, "Medium"),
|
||||||
|
TaskPriority::Low => write!(f, "Low"),
|
||||||
|
TaskPriority::None => write!(f, "None"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for TaskPriority {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
"high" | "High" | "H" | "h" => TaskPriority::High,
|
||||||
|
"medium" | "Medium" | "M" | "m" => TaskPriority::Medium,
|
||||||
|
"low" | "Low" | "L" | "l" => TaskPriority::Low,
|
||||||
|
_ => TaskPriority::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for TaskPriority {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::from(s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TaskStatus {
|
||||||
|
Pending,
|
||||||
|
Completed,
|
||||||
|
Deleted,
|
||||||
|
Unknown(String),
|
||||||
|
Recurring,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TaskStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
TaskStatus::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for TaskStatus {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
"pending" | "Pending" | "P" | "p" => TaskStatus::Pending,
|
||||||
|
"completed" | "Completed" | "C" | "c" => TaskStatus::Completed,
|
||||||
|
"deleted" | "Deleted" | "D" | "d" => TaskStatus::Deleted,
|
||||||
|
"recurring" | "Recurring" | "R" | "r" => TaskStatus::Recurring,
|
||||||
|
_ => TaskStatus::Unknown(String::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for TaskStatus {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::from(s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<taskchampion::Status> for TaskStatus {
|
||||||
|
fn from(status: taskchampion::Status) -> Self {
|
||||||
|
match status {
|
||||||
|
taskchampion::Status::Pending => TaskStatus::Pending,
|
||||||
|
taskchampion::Status::Completed => TaskStatus::Completed,
|
||||||
|
taskchampion::Status::Deleted => TaskStatus::Deleted,
|
||||||
|
taskchampion::Status::Unknown(reason) => TaskStatus::Unknown(reason),
|
||||||
|
taskchampion::Status::Recurring => TaskStatus::Recurring,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TaskStatus {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TaskStatus::Pending => write!(f, "Pending"),
|
||||||
|
TaskStatus::Completed => write!(f, "Completed"),
|
||||||
|
TaskStatus::Deleted => write!(f, "Deleted"),
|
||||||
|
TaskStatus::Unknown(reason) => write!(f, "Unknown ({})", reason),
|
||||||
|
TaskStatus::Recurring => write!(f, "Recurring"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct TaskAnnotation {
|
||||||
|
pub entry: DateTime<Utc>,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<taskchampion::Annotation> for TaskAnnotation {
|
||||||
|
fn from(annotation: taskchampion::Annotation) -> Self {
|
||||||
|
TaskAnnotation {
|
||||||
|
entry: annotation.entry,
|
||||||
|
content: annotation.description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Task {
|
||||||
|
pub uuid: uuid::Uuid,
|
||||||
|
pub status: TaskStatus,
|
||||||
|
pub description: String,
|
||||||
|
pub project: Option<String>,
|
||||||
|
pub priority: TaskPriority,
|
||||||
|
pub tags: HashSet<String>,
|
||||||
|
pub due: Option<DateTime<Utc>>,
|
||||||
|
pub wait: Option<DateTime<Utc>>,
|
||||||
|
pub entry: Option<DateTime<Utc>>,
|
||||||
|
pub modified: Option<DateTime<Utc>>,
|
||||||
|
pub annotations: Vec<TaskAnnotation>,
|
||||||
|
pub dependencies: HashSet<uuid::Uuid>,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub is_blocked: bool,
|
||||||
|
pub working_id: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
pub fn new(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
status: TaskStatus,
|
||||||
|
description: String,
|
||||||
|
project: Option<String>,
|
||||||
|
priority: TaskPriority,
|
||||||
|
tags: HashSet<String>,
|
||||||
|
due: Option<DateTime<Utc>>,
|
||||||
|
wait: Option<DateTime<Utc>>,
|
||||||
|
entry: Option<DateTime<Utc>>,
|
||||||
|
modified: Option<DateTime<Utc>>,
|
||||||
|
annotations: Vec<TaskAnnotation>,
|
||||||
|
dependencies: HashSet<uuid::Uuid>,
|
||||||
|
is_active: bool,
|
||||||
|
is_blocked: bool,
|
||||||
|
working_id: Option<usize>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid,
|
||||||
|
status,
|
||||||
|
description,
|
||||||
|
project,
|
||||||
|
priority,
|
||||||
|
tags,
|
||||||
|
due,
|
||||||
|
wait,
|
||||||
|
entry,
|
||||||
|
modified,
|
||||||
|
annotations,
|
||||||
|
dependencies,
|
||||||
|
is_active,
|
||||||
|
is_blocked,
|
||||||
|
working_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_overdue(&self) -> bool {
|
||||||
|
self.due.map_or(false, |due| due < Utc::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_due_today(&self) -> bool {
|
||||||
|
self.due
|
||||||
|
.map_or(false, |due| due.date_naive() == Utc::now().date_naive())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<taskchampion::Task> for Task {
|
||||||
|
fn from(task: taskchampion::Task) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: task.get_uuid(),
|
||||||
|
status: task.get_status().into(),
|
||||||
|
description: task.get_description().to_string(),
|
||||||
|
project: task.get_value("project").map(|v| v.to_string()),
|
||||||
|
priority: task.get_priority().into(),
|
||||||
|
tags: task.get_tags().map(|t| t.to_string()).collect(),
|
||||||
|
due: task.get_due().map(Into::into),
|
||||||
|
wait: task.get_wait().map(Into::into),
|
||||||
|
entry: task.get_entry().map(Into::into),
|
||||||
|
modified: task.get_modified().map(Into::into),
|
||||||
|
annotations: task.get_annotations().map(Into::into).collect(),
|
||||||
|
dependencies: task.get_dependencies().map(Into::into).collect(),
|
||||||
|
is_active: task.is_active(),
|
||||||
|
is_blocked: task.is_blocked(),
|
||||||
|
working_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TaskUpdate {
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub project: Option<String>,
|
||||||
|
pub priority: Option<String>,
|
||||||
|
pub tags: Option<HashSet<String>>,
|
||||||
|
pub due: Option<DateTime<Utc>>,
|
||||||
|
pub wait: Option<DateTime<Utc>>,
|
||||||
|
pub annotations: Option<Vec<String>>,
|
||||||
|
pub dependencies: Option<Vec<String>>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,570 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use taskchampion::{
|
||||||
|
Operations, Replica, ServerConfig, Status, StorageConfig, Tag, storage::AccessMode,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::error::{TaskError, TaskResult};
|
||||||
|
use super::filter::TaskFilter;
|
||||||
|
use super::model::{Task, TaskPriority, TaskStatus};
|
||||||
|
|
||||||
|
pub struct TaskService {
|
||||||
|
replica: Replica,
|
||||||
|
taskdb_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskService {
|
||||||
|
pub fn new() -> TaskResult<Self> {
|
||||||
|
let taskdb_dir = dirs::home_dir()
|
||||||
|
.ok_or_else(|| TaskError::Storage("Cannot find home directory".into()))?
|
||||||
|
.join(".task");
|
||||||
|
|
||||||
|
Self::with_path(taskdb_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_path(taskdb_dir: PathBuf) -> TaskResult<Self> {
|
||||||
|
let storage = StorageConfig::OnDisk {
|
||||||
|
taskdb_dir: taskdb_dir.clone(),
|
||||||
|
create_if_missing: true,
|
||||||
|
access_mode: AccessMode::ReadWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
let replica = Replica::new(
|
||||||
|
storage
|
||||||
|
.into_storage()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
replica,
|
||||||
|
taskdb_dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_task(&mut self, description: String) -> TaskResult<Task> {
|
||||||
|
let uuid = Uuid::new_v4();
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.create_task(uuid, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.set_description(description, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
let working_set = self
|
||||||
|
.replica
|
||||||
|
.working_set()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
let mut task: Task = tc_task.into();
|
||||||
|
task.working_id = working_set.by_uuid(uuid);
|
||||||
|
|
||||||
|
Ok(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_task(&mut self, uuid: Uuid) -> TaskResult<Option<Task>> {
|
||||||
|
let tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
match tc_task {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(task) => {
|
||||||
|
let working_set = self
|
||||||
|
.replica
|
||||||
|
.working_set()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
let mut task: Task = task.into();
|
||||||
|
task.working_id = working_set.by_uuid(uuid);
|
||||||
|
Ok(Some(task))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_tasks(&mut self) -> TaskResult<Vec<Task>> {
|
||||||
|
let all = self
|
||||||
|
.replica
|
||||||
|
.all_tasks()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
let working_set = self
|
||||||
|
.replica
|
||||||
|
.working_set()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
let tasks: Vec<Task> = all
|
||||||
|
.iter()
|
||||||
|
.map(|(uuid, tc_task)| {
|
||||||
|
let mut task: Task = tc_task.clone().into();
|
||||||
|
task.working_id = working_set.by_uuid(*uuid);
|
||||||
|
task
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_filtered_tasks(&mut self, filter: &TaskFilter) -> TaskResult<Vec<Task>> {
|
||||||
|
let all = self.get_all_tasks()?;
|
||||||
|
Ok(filter.apply(&all))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_task(
|
||||||
|
&mut self,
|
||||||
|
uuid: Uuid,
|
||||||
|
description: Option<String>,
|
||||||
|
project: Option<Option<String>>,
|
||||||
|
priority: Option<String>,
|
||||||
|
tags: Option<HashSet<String>>,
|
||||||
|
due: Option<Option<DateTime<Utc>>>,
|
||||||
|
wait: Option<Option<DateTime<Utc>>>,
|
||||||
|
) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
if let Some(desc) = description {
|
||||||
|
tc_task
|
||||||
|
.set_description(desc, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(proj) = project {
|
||||||
|
tc_task
|
||||||
|
.set_value("project", proj, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pri) = priority {
|
||||||
|
tc_task
|
||||||
|
.set_priority(pri, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(d) = due {
|
||||||
|
tc_task
|
||||||
|
.set_due(d, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(w) = wait {
|
||||||
|
tc_task
|
||||||
|
.set_wait(w, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_tags) = tags {
|
||||||
|
let current_tags: HashSet<String> = tc_task.get_tags().map(|t| t.to_string()).collect();
|
||||||
|
|
||||||
|
for tag_str in current_tags.difference(&new_tags) {
|
||||||
|
if let Ok(tag) = Tag::try_from(tag_str.as_str()) {
|
||||||
|
tc_task
|
||||||
|
.remove_tag(&tag, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag_str in new_tags.difference(¤t_tags) {
|
||||||
|
if let Ok(tag) = Tag::try_from(tag_str.as_str()) {
|
||||||
|
tc_task
|
||||||
|
.add_tag(&tag, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete_task(&mut self, uuid: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.done(&mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reopen_task(&mut self, uuid: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.set_status(Status::Pending, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_task(&mut self, uuid: Uuid) -> TaskResult<()> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.set_status(Status::Deleted, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_task(&mut self, uuid: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.start(&mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_task(&mut self, uuid: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.stop(&mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tag(&mut self, uuid: Uuid, tag_str: &str) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
let tag = Tag::try_from(tag_str).map_err(|_| TaskError::InvalidTag(tag_str.to_string()))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.add_tag(&tag, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_tag(&mut self, uuid: Uuid, tag_str: &str) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
let tag = Tag::try_from(tag_str).map_err(|_| TaskError::InvalidTag(tag_str.to_string()))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.remove_tag(&tag, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_tags(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
||||||
|
let all_tasks = self.get_all_tasks()?;
|
||||||
|
let mut tag_counts: HashMap<String, usize> = HashMap::new();
|
||||||
|
|
||||||
|
for task in all_tasks {
|
||||||
|
if matches!(task.status, TaskStatus::Pending) {
|
||||||
|
for tag in task.tags {
|
||||||
|
*tag_counts.entry(tag).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stats: Vec<(String, usize)> = tag_counts.into_iter().collect();
|
||||||
|
|
||||||
|
stats.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||||
|
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_projects(&mut self) -> TaskResult<Vec<(String, usize, usize)>> {
|
||||||
|
let all_tasks = self.get_all_tasks()?;
|
||||||
|
let mut project_counts: HashMap<String, (usize, usize)> = HashMap::new(); // (total, pending)
|
||||||
|
|
||||||
|
for task in all_tasks {
|
||||||
|
if let Some(project) = &task.project {
|
||||||
|
let entry = project_counts.entry(project.clone()).or_insert((0, 0));
|
||||||
|
entry.0 += 1;
|
||||||
|
if matches!(task.status, TaskStatus::Pending) {
|
||||||
|
entry.1 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats: Vec<(String, usize, usize)> = project_counts
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, (task_count, pending_count))| (name, task_count, pending_count))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_projects_for_tree(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
||||||
|
let stats = self.list_projects()?;
|
||||||
|
Ok(stats
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, _total, pending)| (name, pending))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_annotation(&mut self, uuid: Uuid, description: String) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
let annotation = taskchampion::Annotation {
|
||||||
|
entry: Utc::now(),
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.add_annotation(annotation, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_annotation(&mut self, uuid: Uuid, entry: DateTime<Utc>) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.remove_annotation(entry, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_dependency(&mut self, uuid: Uuid, depends_on: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.add_dependency(depends_on, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_dependency(&mut self, uuid: Uuid, depends_on: Uuid) -> TaskResult<Task> {
|
||||||
|
let mut ops = Operations::new();
|
||||||
|
|
||||||
|
let mut tc_task = self
|
||||||
|
.replica
|
||||||
|
.get_task(uuid)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||||
|
.ok_or(TaskError::NotFound(uuid))?;
|
||||||
|
|
||||||
|
tc_task
|
||||||
|
.remove_dependency(depends_on, &mut ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.commit_operations(ops)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.get_task(uuid)?.ok_or(TaskError::NotFound(uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync(&mut self) -> TaskResult<SyncResult> {
|
||||||
|
let server_dir = self.taskdb_dir.join("server");
|
||||||
|
|
||||||
|
if !server_dir.exists() {
|
||||||
|
return Ok(SyncResult {
|
||||||
|
success: false,
|
||||||
|
message: "Server not configured".to_string(),
|
||||||
|
local_ops_before: 0,
|
||||||
|
local_ops_after: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_ops_before = self
|
||||||
|
.replica
|
||||||
|
.num_local_operations()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
let server_config = ServerConfig::Local { server_dir };
|
||||||
|
|
||||||
|
let mut server = server_config
|
||||||
|
.into_server()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
self.replica
|
||||||
|
.sync(&mut server, false)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
let local_ops_after = self
|
||||||
|
.replica
|
||||||
|
.num_local_operations()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(SyncResult {
|
||||||
|
success: true,
|
||||||
|
message: "Sync completed".to_string(),
|
||||||
|
local_ops_before,
|
||||||
|
local_ops_after,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending_sync_operations(&mut self) -> TaskResult<usize> {
|
||||||
|
Ok(self
|
||||||
|
.replica
|
||||||
|
.num_local_operations()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rebuild_working_set(&mut self, renumber: bool) -> TaskResult<()> {
|
||||||
|
self.replica
|
||||||
|
.rebuild_working_set(renumber)
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expire_tasks(&mut self) -> TaskResult<()> {
|
||||||
|
self.replica
|
||||||
|
.expire_tasks()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn working_set(&mut self) -> TaskResult<Vec<(usize, Task)>> {
|
||||||
|
let ws = self
|
||||||
|
.replica
|
||||||
|
.working_set()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
for (idx, uuid) in ws.iter() {
|
||||||
|
if let Some(task) = self.get_task(uuid)? {
|
||||||
|
result.push((idx, task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_task_by_working_id(&mut self, id: usize) -> TaskResult<Option<Task>> {
|
||||||
|
let ws = self
|
||||||
|
.replica
|
||||||
|
.working_set()
|
||||||
|
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
||||||
|
|
||||||
|
match ws.by_index(id) {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(uuid) => self.get_task(uuid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SyncResult {
|
||||||
|
pub success: bool,
|
||||||
|
pub message: String,
|
||||||
|
pub local_ops_before: usize,
|
||||||
|
pub local_ops_after: usize,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user