Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
132ad3adf8 | |||
aec7116651 | |||
b690ee4987 | |||
4203b21097 | |||
62ad59ab76 | |||
a6cb3c7804 | |||
78cbc633c4 | |||
85497018c4 | |||
1fd49b8664 |
17 changed files with 354 additions and 174 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1 @@
|
||||||
/target
|
/target
|
||||||
|
|
||||||
.direnv/
|
|
||||||
|
|
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -286,6 +286,43 @@ 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"
|
||||||
|
@ -310,6 +347,8 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"time",
|
"time",
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,6 +14,8 @@ 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,9 +0,0 @@
|
||||||
CREATE VIEW finished_tasks
|
|
||||||
AS
|
|
||||||
SELECT
|
|
||||||
t.*,
|
|
||||||
row_number() OVER (ORDER BY t.finished_at DESC) AS idx
|
|
||||||
FROM tasks AS t
|
|
||||||
WHERE t.finished_at IS NOT NULL
|
|
||||||
ORDER BY t.finished_at DESC
|
|
||||||
;
|
|
|
@ -24,13 +24,3 @@ FROM tasks AS t
|
||||||
WHERE t.finished_at IS NULL
|
WHERE t.finished_at IS NULL
|
||||||
ORDER BY t.created_at
|
ORDER BY t.created_at
|
||||||
;
|
;
|
||||||
|
|
||||||
CREATE VIEW finished_tasks
|
|
||||||
AS
|
|
||||||
SELECT
|
|
||||||
t.*,
|
|
||||||
row_number() OVER (ORDER BY t.finished_at DESC) AS idx
|
|
||||||
FROM tasks AS t
|
|
||||||
WHERE t.finished_at IS NOT NULL
|
|
||||||
ORDER BY t.finished_at DESC
|
|
||||||
;
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
(import (
|
|
||||||
fetchTarball {
|
|
||||||
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
|
||||||
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
|
||||||
) {
|
|
||||||
src = ./.;
|
|
||||||
}).defaultNix
|
|
43
flake.lock
43
flake.lock
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1661008273,
|
|
||||||
"narHash": "sha256-UpDqsGzUswIHG7FwzeIewjWlElF17UVLNbI2pwlbcBY=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "0cc6444e74cd21e8da8d81ef4cd778492e10f843",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"utils": "utils"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1659877975,
|
|
||||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
31
flake.nix
31
flake.nix
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
||||||
utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, utils }:
|
|
||||||
utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
cargoToml = with builtins; (fromTOML (readFile ./Cargo.toml));
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
|
||||||
inherit (cargoToml.package) name version;
|
|
||||||
src = nixpkgs.lib.cleanSource ./.;
|
|
||||||
doCheck = true;
|
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
apps.default = utils.lib.mkApp {
|
|
||||||
inherit (cargoToml.package) name;
|
|
||||||
drv = packages.default;
|
|
||||||
};
|
|
||||||
|
|
||||||
devShell = with pkgs; mkShell {
|
|
||||||
packages = [ cargo rustc rustfmt clippy rust-analyzer ];
|
|
||||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
8
makefile
8
makefile
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
install:
|
|
||||||
cargo install --path .
|
|
||||||
|
|
||||||
new-migration:
|
|
||||||
touch ./database/migrations/$$(date +%Y%m%d%H%M).sql
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
(import (
|
|
||||||
fetchTarball {
|
|
||||||
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
|
||||||
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
|
||||||
) {
|
|
||||||
src = ./.;
|
|
||||||
}).shellNix
|
|
17
shell/zsh
17
shell/zsh
|
@ -3,19 +3,11 @@ function __tas_task_idx() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function __tas_show() {
|
function __tas_show() {
|
||||||
tas show $(__tas_task_idx "$1")
|
tas show $(__tas_task_idx "$@")
|
||||||
}
|
}
|
||||||
|
|
||||||
function __tas_start() {
|
function __tas_start() {
|
||||||
if [[ "$1" != "" ]]; then
|
tas start $(__tas_task_idx "$@")
|
||||||
tas start $(__tas_task_idx "$1")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function __tas_remove() {
|
|
||||||
if [[ "$1" != "" ]]; then
|
|
||||||
tas remove $(__tas_task_idx "$1")
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __tas_list() {
|
function __tas_list() {
|
||||||
|
@ -36,11 +28,6 @@ function taz() {
|
||||||
|
|
||||||
"start" | "st")
|
"start" | "st")
|
||||||
__tas_start "$(__tas_list)"
|
__tas_start "$(__tas_list)"
|
||||||
;;
|
|
||||||
|
|
||||||
"remove" | "rm")
|
|
||||||
__tas_remove "$(__tas_list)"
|
|
||||||
;;
|
|
||||||
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
26
src/bin/fs_to_sqlite.rs
Normal file
26
src/bin/fs_to_sqlite.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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 tasks = fs_repo.get_tasks().unwrap();
|
||||||
|
|
||||||
|
let sqlite_repo = repo::sqlite::SqliteRepo::new(xdg_dirs).unwrap();
|
||||||
|
for task in 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,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
log::info!(" inserted");
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,27 +18,20 @@ use crate::repo::Repository;
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[clap(short, long)]
|
|
||||||
finished: bool,
|
|
||||||
|
|
||||||
projects: Vec<String>,
|
projects: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(repo: impl Repository, args: Args) {
|
pub fn execute(repo: impl Repository, args: Args) {
|
||||||
let tasks = match repo.get_tasks(args.finished) {
|
let tasks = match repo.get_tasks() {
|
||||||
Ok(tasks) => tasks,
|
Ok(tasks) => tasks,
|
||||||
Err(err) => return eprintln!("Cannot read tasks: {}", err),
|
Err(err) => return eprintln!("Cannot read tasks: {}", err),
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur_task = if args.finished {
|
let cur_task = match repo.get_current_task_opt() {
|
||||||
None
|
|
||||||
} else {
|
|
||||||
match repo.get_current_task_opt() {
|
|
||||||
Ok(cur_task) => cur_task,
|
Ok(cur_task) => cur_task,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return eprintln!("Cannot read current task: {}", err);
|
return eprintln!("Cannot read current task: {}", err);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let projects = args
|
let projects = args
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
//! 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;
|
||||||
|
@ -68,7 +69,7 @@ pub trait Repository {
|
||||||
|
|
||||||
fn get_task_opt(&self, id: domain::TaskIdx) -> Result<Option<domain::Task>, Error>;
|
fn get_task_opt(&self, id: domain::TaskIdx) -> Result<Option<domain::Task>, Error>;
|
||||||
|
|
||||||
fn get_tasks(&self, finished: bool) -> Result<Vec<domain::Task>, Error>;
|
fn get_tasks(&self) -> Result<Vec<domain::Task>, Error>;
|
||||||
|
|
||||||
fn remove_task(&self, id: domain::TaskIdx) -> Result<domain::Task, Error>;
|
fn remove_task(&self, id: domain::TaskIdx) -> Result<domain::Task, Error>;
|
||||||
|
|
||||||
|
|
263
src/repo/fs.rs
Normal file
263
src/repo/fs.rs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
//! 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CURRENT_TASK_FILE: &str = "current.json";
|
||||||
|
const DATA_FILE: &str = "data.json";
|
||||||
|
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) -> Result<Vec<domain::Task>, Error> {
|
||||||
|
self.get_tasks_impl()
|
||||||
|
.map(|tasks| 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ impl<'r> TryFrom<&'r rusqlite::Row<'_>> for Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMA_FILE: &str = "tas.db";
|
const SCHEMA_FILE: &str = "schema.sql";
|
||||||
|
|
||||||
pub struct SqliteRepo {
|
pub struct SqliteRepo {
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
|
@ -82,16 +82,11 @@ impl Repository for SqliteRepo {
|
||||||
self.get_task_opt_impl(id).map(|t| t.map(From::from))
|
self.get_task_opt_impl(id).map(|t| t.map(From::from))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tasks(&self, finished: bool) -> Result<Vec<domain::Task>, Error> {
|
fn get_tasks(&self) -> Result<Vec<domain::Task>, Error> {
|
||||||
let mut stmt = if finished {
|
let mut stmt = self
|
||||||
self.conn
|
.conn
|
||||||
.prepare("SELECT * FROM finished_tasks")
|
|
||||||
.map_err(|_| Error::PrepareQuery)?
|
|
||||||
} else {
|
|
||||||
self.conn
|
|
||||||
.prepare("SELECT * FROM active_tasks")
|
.prepare("SELECT * FROM active_tasks")
|
||||||
.map_err(|_| Error::PrepareQuery)?
|
.map_err(|_| Error::PrepareQuery)?;
|
||||||
};
|
|
||||||
|
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map([], |row| Task::try_from(row))
|
.query_map([], |row| Task::try_from(row))
|
||||||
|
@ -256,16 +251,12 @@ impl SqliteRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! run_migration {
|
macro_rules! run_migration {
|
||||||
($this:ident, $ver:ident = $version:expr) => {
|
($this:ident, $ver:ident = $version:literal) => {
|
||||||
run_migration!($this, $ver = $version => concat!("migrations/", $version));
|
|
||||||
};
|
|
||||||
|
|
||||||
($this:ident, $ver:ident = $version:expr => $sql_name:expr) => {
|
|
||||||
$this
|
$this
|
||||||
.conn
|
.conn
|
||||||
.execute_batch(&format!(
|
.execute_batch(&format!(
|
||||||
"BEGIN; {} COMMIT;",
|
"BEGIN; {} COMMIT;",
|
||||||
include_str!(concat!("../../database/", $sql_name, ".sql"))
|
include_str!(concat!("../../database/migrations/", $version, ".sql"))
|
||||||
))
|
))
|
||||||
.map_err(|_| MigrationError::Upgrade)?;
|
.map_err(|_| MigrationError::Upgrade)?;
|
||||||
|
|
||||||
|
@ -297,21 +288,17 @@ impl std::fmt::Display for MigrationError {
|
||||||
|
|
||||||
impl std::error::Error for MigrationError {}
|
impl std::error::Error for MigrationError {}
|
||||||
|
|
||||||
const LATEST_VERSION: i64 = 202208201623;
|
const LATEST_VERSION: i64 = 202208162308;
|
||||||
|
|
||||||
impl SqliteRepo {
|
impl SqliteRepo {
|
||||||
pub fn upgrade(&self) -> Result<(), MigrationError> {
|
pub fn upgrade(&self) -> Result<(), MigrationError> {
|
||||||
let mut version = self.version();
|
let mut version = self.version();
|
||||||
match version {
|
if version == Some(LATEST_VERSION) {
|
||||||
Some(LATEST_VERSION) => return Ok(()),
|
return Ok(());
|
||||||
None => {
|
|
||||||
run_migration!(self, version = LATEST_VERSION => "schema");
|
|
||||||
}
|
|
||||||
Some(v) => {
|
|
||||||
if v == 202208162308 {
|
|
||||||
run_migration!(self, version = 202208201623);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version.is_none() {
|
||||||
|
run_migration!(self, version = 202208162308);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.conn
|
self.conn
|
||||||
|
|
Loading…
Reference in a new issue