Compare commits

..

No commits in common. "main" and "1.0.1" have entirely different histories.
main ... 1.0.1

20 changed files with 118 additions and 817 deletions

2
.gitignore vendored
View File

@ -1,7 +1,5 @@
takl.sqlite3 takl.sqlite3
takl takl
takl-daemon
takl.sock
out out
.vscode .vscode

View File

@ -1,47 +1,24 @@
FLAGS = -g -Wall -Wextra -DLOG_USE_COLOR FLAGS = -g -Wall -Wextra -lsqlite3
LD_FLAGS = -lsqlite3 -lpthread LD_FLAGS =
NOTIF_FLAGS = `pkg-config --cflags --libs libnotify`
$(shell mkdir -p out) $(shell mkdir -p out)
all: takl takl-daemon
out/%.o: src/%.c out/%.o: src/%.c
$(CC) -c $(FLAGS) $^ -o $@ $(CC) -c $(FLAGS) $^ -o $@
out/notification.o: src/main/notification.c takl: out/main.o out/db.o out/tasks.o
$(CC) -c $(FLAGS) $(NOTIF_FLAGS) $^ -o $@
out/%.o: src/main/%.c
$(CC) -c $(FLAGS) $^ -o $@
out/%.o: src/log/%.c
$(CC) -c $(FLAGS) $^ -o $@
takl: out/main.o out/db.o out/tasks.o out/utils.o
$(CC) $(FLAGS) $(LD_FLAGS) $^ -o $@ $(CC) $(FLAGS) $(LD_FLAGS) $^ -o $@
takl-daemon: out/daemon.o out/db.o out/tasks.o out/utils.o out/log.o out/notification.o
$(CC) $(FLAGS) $(LD_FLAGS) $(NOTIF_FLAGS) $^ -o $@
install: takl
sudo cp takl /usr/bin/takl
install: takl takl-daemon uninstall: takl
sudo cp takl takl-daemon /usr/bin/ sudo rm /usr/bin/takl
uninstall:
-sudo rm /usr/bin/takl
-sudo rm /usr/bin/takl-daemon
-sudo rm ~/.config/autostart/takl-daemon.desktop
gnome-install: install
mkdir -p ~/.config/autostart
cp takl-daemon.desktop ~/.config/autostart/
clean: clean:
-rm out -r rm out -r
-rm ./takl rm ./takl
-rm ./takl-daemon

View File

@ -5,7 +5,7 @@ facile d'utilisation et donnant rapidement les choses demandées
### Installation ### Installation
Les seules dépendances nécessaires sont `gcc` et `libnotify`. Les seules dépendances nécessaires sont `gcc` et `g++`.
(Peut-être que des bibliothèques comme sqlite3 sont à installer sur certaines distributions/ OS) (Peut-être que des bibliothèques comme sqlite3 sont à installer sur certaines distributions/ OS)
```bash ```bash
@ -15,26 +15,18 @@ make install # installation dans /usr/bin/takl
Vous pouvez ensuite utiliser la commande `takl` pour ajouter/ modifier des items à la todo list. Vous pouvez ensuite utiliser la commande `takl` pour ajouter/ modifier des items à la todo list.
Pour recevoir des notifications lorsqu'une tâche arrive à échéance, il faut lancer `takl-daemon` en arrière-plan
dans un environnement similaire à l'utilisateur (Un service systemd ou une tâche cron auront besoin de plus
d'informations pour pouvoir envoyer des notifications).
Pour gnome, `make gnome-install` lancera le programme à tous les prochains redémarrages.
### Utilisation ### Utilisation
``` ```
Utilisation: takl ( list | add | reschedule | info | rm | done ) Utilisation: takl ( list | add | info | rm | done )
list [category] [-a:voir les tâches complétées] list [-a:voir les tâches complétées]
add [category:]<task> [date] add <task> [date]
reschedule <id> [date] Format de la date:
Relatif: min+%d, h+%d, j+%d
Absolu: dd/mm (pas de changement d'année pour l'instant)
info <id1> <id2> ... info <id1> <id2> ...
done <id1> <id2> ... done <id1> <id2> ...
rm <id1> <id2> ... rm <id1> <id2> ...
Format des dates:
Relatif: min+%d, h+%d, j+%d
Absolu: dd/mm (pas de changement d'année pour l'instant)
``` ```
Appeler l'exécutable sans arguments vous renverra cette petite liste de commandes que vous pouvez utiliser. Appeler l'exécutable sans arguments vous renverra cette petite liste de commandes que vous pouvez utiliser.

View File

@ -1,172 +0,0 @@
/*
* Petit daemon qui va envoyer des notifications aux horaires des tâches
* il vérifie si la base de données change par l'intermédiaire d'un socket,
* (implémenté par un fichier stocké dans /tmp/takl.$USER), qui contient le pid
* de ce daemon afin de pouvoir déterminer si un autre daemon tourne.
* (Le cas deux daemons sont lancés en même temps n'est pas traité.)
*/
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include "log/log.h"
#include "main/include/db.h"
#include "main/include/tasks.h"
#include "main/include/utils.h"
#include "main/include/colors.h"
#include "main/include/config.h"
#include "main/include/notification.h"
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
bool has_changed = false;
task_list_t* tasks;
pthread_mutex_t tasks_lock = PTHREAD_MUTEX_INITIALIZER; // locks tasks and has_changed at the same time
void* inotify_check_changes(void* sk_path) {
char* socket_path = (char*)sk_path;
char buffer[EVENT_BUF_LEN];
while (1) { // Regarde si /tmp/takl.$USER a changé (donc si la BDD a changé et doit être rechargée)
int fd = inotify_init();
if (fd < 0) {
log_fatal("Impossible d'initialiser inotify");
free(socket_path);
exit(1);
}
int wd = inotify_add_watch(fd, socket_path, IN_ACCESS);
int length = read(fd, buffer, EVENT_BUF_LEN);
if (length < 0) {
log_fatal("inotify: lecture impossible");
free(socket_path);
exit(1);
}
log_debug("Base de données changée");
pthread_mutex_lock(&tasks_lock);
free_task_list(tasks);
tasks = get_task_list(false, false);
has_changed = true;
pthread_mutex_unlock(&tasks_lock);
inotify_rm_watch(fd, wd);
close(fd);
}
return NULL;
}
int existing_takl_daemon(char* socket_path) {
if (!access(socket_path, F_OK)) { // tmp file exists
FILE* fp = fopen(socket_path, "r+");
int daemon_pid;
fscanf(fp, "%d", &daemon_pid);
fclose(fp);
if(!kill(daemon_pid, 0)) {
free(socket_path);
return true; // The process is running
}
}
FILE* fp = fopen(socket_path, "w");
fprintf(fp, "%d", getpid());
fclose(fp);
return false;
}
int main() {
bool notified_no_change = false;
char* socket_path = get_socket_path();
log_debug("Socket path:%s", socket_path);
if (!existing_takl_daemon(socket_path)) {
log_info("TaKl " VERSION " -- Daemon started");
} else {
log_info("TaKl Daemon déjà en cours d'exécution. Arrêt");
return 1;
}
tasks = get_task_list(false, false);
pthread_t inotify_id = 0; // Lancement d'inotify dans un autre fil
pthread_create(&inotify_id, NULL, inotify_check_changes, (void*)socket_path);
while (1) {
pthread_mutex_lock(&tasks_lock);
task_t next = next_task(tasks);
pthread_mutex_unlock(&tasks_lock);
if (next.due_to == 0) {
if (!notified_no_change)
log_debug("Plus de tâches avec échéance dans la liste");
notified_no_change = true;
while (1) { // sleep until change
sleep(5);
pthread_mutex_lock(&tasks_lock);
if (has_changed) {
log_debug("Changement détecté");
has_changed = false;
pthread_mutex_unlock(&tasks_lock);
break;
}
pthread_mutex_unlock(&tasks_lock);
}
} else {
time_t now = time(0);
double wait = difftime(next.due_to, now) + 5;
log_debug("Prochaine tâche dans %0.1lfs", wait);
notified_no_change = false;
while (difftime(next.due_to, now) >= 5) {
sleep(5);
pthread_mutex_lock(&tasks_lock);
if (has_changed) {
log_debug("Changement détecté");
has_changed = false;
pthread_mutex_unlock(&tasks_lock);
break;
}
pthread_mutex_unlock(&tasks_lock);
now = time(0);
}
if (difftime(next.due_to, now) < 5) { // Exit not du e to the break statement
log_debug("Envoi de la notification tâche [%d]", next.id);
desktop_notification(next);
}
//! si deux tâches sont à la même date à moins de 0.1s près,
//! l'une des deux seulement sera affichée
sleep(3); // Attendre un peu pour ne pas reconsidérer la même tâche 1063 fois.
}
}
pthread_join(inotify_id, NULL);
free_task_list(tasks);
free(socket_path);
return 0;
}

View File

@ -11,31 +11,11 @@ Fonctions d'interaction avec la base de données
#include "include/colors.h" #include "include/colors.h"
#include "include/config.h" #include "include/config.h"
#include "include/struct.h" #include "include/struct.h"
#include "include/utils.h"
#include "include/db.h"
#define sqlCheck(ret) { sqlAssert(ret, db, __FILE__, __LINE__); }
void sqlAssert(int ret, sqlite3* db, char* file, int line) {
if (ret != SQLITE_OK) {
printf( "%s:%d, error %d: %s\n", file, line, ret, sqlite3_errmsg(db));
exit(1);
}
}
char* get_db_path() { char* get_db_path() {
/* #ifndef DB_FILE
Checks if $TAKL_DB env variable is set //! We should check that $HOME/.config already exists even if it should be the case
else, the DB is located at $HOME/.config/takl.sqlite3
*/
char* env_db_path = getenv("TAKL_DB");
if (env_db_path) {
char* db_path = malloc(sizeof(char)*(strlen(env_db_path)+1));
strcpy(db_path, env_db_path);
return db_path;
} else {
char* base_path = ".config/takl.sqlite3"; char* base_path = ".config/takl.sqlite3";
char* home_dir = getenv("HOME"); char* home_dir = getenv("HOME");
@ -45,7 +25,12 @@ char* get_db_path() {
sprintf(db_path, "%s/%s", home_dir, base_path); sprintf(db_path, "%s/%s", home_dir, base_path);
return db_path; return db_path;
} #else
char* db_path = malloc(sizeof(char)*(strlen(DB_FILE)+1));
memcpy(db_path, DB_FILE, sizeof(char)*(strlen(DB_FILE)+1));
return db_path;
#endif
} }
@ -63,7 +48,6 @@ sqlite3* get_db() {
"CREATE TABLE tasks ( \ "CREATE TABLE tasks ( \
id INTEGER PRIMARY KEY,\ id INTEGER PRIMARY KEY,\
text TEXT NOT NULL,\ text TEXT NOT NULL,\
category TEXT DEFAULT NULL, \
done INTEGER, \ done INTEGER, \
due_to INTEGER \ due_to INTEGER \
);", );",
@ -91,49 +75,46 @@ sqlite3* get_db() {
return db; return db;
} }
void notify_change() {
char* socket_path = get_socket_path();
if (access(socket_path, F_OK) == 0) {
FILE* fp = fopen(socket_path, "r");
int pid;
fscanf(fp, "%d", &pid); // Trigger IN_ACCESS
(void)pid;
fclose(fp);
}
free(socket_path);
}
int add_task(task_t t) { int add_task(task_t t) {
sqlite3* db = get_db(); sqlite3* db = get_db();
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlCheck( sqlite3_prepare_v2(db, "INSERT INTO tasks (id, text, category, done, due_to) VALUES (?1, ?2, ?3, ?4, ?5);", -1, &stmt, 0) ); int ret = sqlite3_prepare_v2(db, "INSERT INTO tasks (id, text, done, due_to) VALUES (?1, ?2, ?3, ?4);", -1, &stmt, 0);
if (ret != SQLITE_OK) {
printf("(get_task) failure fetching data\n");
return 1;
}
char str_id[10]; char str_id[10];
sprintf(str_id, "%d", (int)t.id); sprintf(str_id, "%d", (int)t.id);
sqlCheck( sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC);
sqlCheck( sqlite3_bind_text(stmt, 2, t.text, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 2, t.text, -1, SQLITE_STATIC);
sqlCheck( sqlite3_bind_text(stmt, 3, t.category, -1, SQLITE_STATIC) );
char str_done[2]; // Just a boolean char str_done[2]; // Just a boolean
sprintf(str_done, "%d", t.done); sprintf(str_done, "%d", t.done);
sqlCheck( sqlite3_bind_text(stmt, 4, str_done, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 3, str_done, -1, SQLITE_STATIC);
char str_due_to[15]; char str_due_to[15];
sprintf(str_due_to, "%ld", t.due_to); sprintf(str_due_to, "%ld", t.due_to);
sqlCheck( sqlite3_bind_text(stmt, 5, str_due_to, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 4, str_due_to, -1, SQLITE_STATIC);
sqlite3_step(stmt); ret = sqlite3_step(stmt);
sqlCheck( sqlite3_finalize(stmt) ); sqlite3_finalize(stmt);
sqlCheck( sqlite3_close(db) ); sqlite3_close(db);
notify_change();
if (ret != SQLITE_DONE) {
if (ret == 19) {
printf("Identifiant déjà utilisé\n");
} else {
printf("Return value: %d\n", ret);
}
return 1;
}
return 0; return 0;
} }
@ -141,28 +122,36 @@ int update_task(task_t t) {
sqlite3* db = get_db(); sqlite3* db = get_db();
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlCheck( sqlite3_prepare_v2(db, "UPDATE tasks SET text=?2, done=?3, due_to=?4 WHERE id=?1;", -1, &stmt, 0) ); int ret = sqlite3_prepare_v2(db, "UPDATE tasks SET text=?2, done=?3, due_to=?4 WHERE id=?1;", -1, &stmt, 0);
if (ret != SQLITE_OK) {
printf("(get_task) failure fetching data\n");
return 1;
}
char str_id[10]; char str_id[10];
sprintf(str_id, "%d", (int)t.id); sprintf(str_id, "%d", (int)t.id);
sqlCheck( sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC);
sqlCheck( sqlite3_bind_text(stmt, 2, t.text, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 2, t.text, -1, SQLITE_STATIC);
char str_done[2]; // Just a boolean char str_done[2]; // Just a boolean
sprintf(str_done, "%d", t.done); sprintf(str_done, "%d", t.done);
sqlCheck( sqlite3_bind_text(stmt, 3, str_done, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 3, str_done, -1, SQLITE_STATIC);
char str_due_to[15]; char str_due_to[15];
sprintf(str_due_to, "%ld", t.due_to); sprintf(str_due_to, "%ld", t.due_to);
sqlCheck( sqlite3_bind_text(stmt, 4, str_due_to, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 4, str_due_to, -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlCheck( sqlite3_finalize(stmt) ); ret = sqlite3_step(stmt);
sqlCheck( sqlite3_close(db) );
notify_change();
sqlite3_finalize(stmt);
sqlite3_close(db);
if (ret != SQLITE_DONE) {
printf("Return value: %d\n", ret);
return 1;
}
return 0; return 0;
} }
@ -171,17 +160,21 @@ void delete_task(int id) {
sqlite3* db = get_db(); sqlite3* db = get_db();
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlCheck( sqlite3_prepare_v2(db, "DELETE FROM tasks WHERE id=?1;", -1, &stmt, 0) ); int ret = sqlite3_prepare_v2(db, "DELETE FROM tasks WHERE id=?1;", -1, &stmt, 0);
if (ret != SQLITE_OK) {
printf("(get_task) failure fetching data\n");
exit(1);
}
char str_id[10]; char str_id[10];
sprintf(str_id, "%d", id); sprintf(str_id, "%d", id);
sqlCheck( sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC);
sqlite3_step(stmt); sqlite3_step(stmt);
sqlCheck( sqlite3_finalize(stmt) ); sqlite3_finalize(stmt);
sqlCheck( sqlite3_close(db) ); sqlite3_close(db);
notify_change();
} }
@ -191,13 +184,19 @@ void get_task(int id, task_t* t) {
sqlite3* db = get_db(); sqlite3* db = get_db();
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlCheck( sqlite3_prepare_v2(db, "SELECT id, text, done, due_to, category FROM tasks WHERE id=?1;", -1, &stmt, NULL) ); int ret = sqlite3_prepare_v2(db, "SELECT id, text, done, due_to FROM tasks WHERE id=?1;", -1, &stmt, 0);
if (ret != SQLITE_OK) {
printf("(get_task) failure fetching data\n");
exit(1);
}
char str_id[10]; char str_id[10];
sprintf(str_id, "%d", id); sprintf(str_id, "%d", id);
sqlCheck( sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC) ); sqlite3_bind_text(stmt, 1, str_id, -1, SQLITE_STATIC);
int ret = sqlite3_step(stmt); ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW) { if (ret == SQLITE_ROW) {
t->id = strtol((char*)sqlite3_column_text(stmt, 0), NULL, 10); t->id = strtol((char*)sqlite3_column_text(stmt, 0), NULL, 10);
t->done = strtol((char*)sqlite3_column_text(stmt, 2), NULL, 10); t->done = strtol((char*)sqlite3_column_text(stmt, 2), NULL, 10);
@ -206,33 +205,24 @@ void get_task(int id, task_t* t) {
char* text = (char*)sqlite3_column_text(stmt, 1); char* text = (char*)sqlite3_column_text(stmt, 1);
t->text = malloc(sizeof(char)*(strlen(text)+1)); t->text = malloc(sizeof(char)*(strlen(text)+1));
strcpy(t->text, text); strcpy(t->text, text);
char* category = (char*)sqlite3_column_text(stmt, 4);
if (category) {
t->category = malloc(sizeof(char)*(strlen(category)+1));
strcpy(t->category, category);
} else {
t->category = NULL;
}
} }
sqlCheck( sqlite3_finalize(stmt) ); sqlite3_finalize(stmt);
sqlCheck( sqlite3_close(db) ); sqlite3_close(db);
// t->id will be (-1) if not found // t->id will be (-1) if not found
} }
task_list_t* get_task_list(char* input_category, bool include_completed) { task_list_t* get_task_list(bool include_completed) {
sqlite3* db = get_db(); sqlite3* db = get_db();
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
if (!input_category) { int ret = sqlite3_prepare_v2(db, "SELECT id, text, done, due_to FROM tasks;", -1, &stmt, NULL);
sqlCheck( sqlite3_prepare_v2(db, "SELECT id, text, done, due_to, category FROM tasks;", -1, &stmt, NULL) );
} else { if (ret != SQLITE_OK) {
sqlCheck( sqlite3_prepare_v2(db, "SELECT id, text, done, due_to, category FROM tasks WHERE category=?1;", -1, &stmt, NULL) ); printf("(get_task) failure fetching data\n");
sqlCheck( sqlite3_bind_text(stmt, 1, input_category, -1, SQLITE_STATIC) ); return NULL;
} }
task_list_t* list = NULL; task_list_t* list = NULL;
while (sqlite3_step(stmt) == SQLITE_ROW) { while (sqlite3_step(stmt) == SQLITE_ROW) {
@ -247,15 +237,6 @@ task_list_t* get_task_list(char* input_category, bool include_completed) {
t.text = malloc(sizeof(char)*(strlen(text)+1)); t.text = malloc(sizeof(char)*(strlen(text)+1));
strcpy(t.text, text); strcpy(t.text, text);
char* category = (char*)sqlite3_column_text(stmt, 4);
if (category) {
t.category = malloc(sizeof(char)*(strlen(category)+1));
strcpy(t.category, category);
} else {
t.category = NULL;
}
task_list_t* cur = malloc(sizeof(task_list_t)); // Add the parsed task to the beginning of the list task_list_t* cur = malloc(sizeof(task_list_t)); // Add the parsed task to the beginning of the list
cur->task = t; cur->task = t;
cur->next = list; cur->next = list;
@ -263,8 +244,8 @@ task_list_t* get_task_list(char* input_category, bool include_completed) {
} }
} }
sqlCheck( sqlite3_finalize(stmt) ); sqlite3_finalize(stmt);
sqlCheck( sqlite3_close(db) ); sqlite3_close(db);
return list; return list;
} }

View File

@ -1,11 +1,12 @@
#ifndef DEF_CONFIG_H #ifndef DEF_CONFIG_H
#define DEF_CONFIG_H #define DEF_CONFIG_H
#define VERSION "1.3.5" #define VERSION "1.0.1"
// By default, $HOME/.config/takl.sqlite3 is used. You can change this behaviour here
//#define DB_FILE "takl.sqlite3"
#define MAX_TASK_ID 10000 // max is set to MAX_TASK_ID-1 #define MAX_TASK_ID 10000 // max is set to MAX_TASK_ID-1
#define NEW_TASK_ID_MAX_RETRIES 10000 // number of retries before giving up #define NEW_TASK_ID_MAX_RETRIES 10000 // number of retries before giving up
#define TMPDIR "/tmp" // Some Unix systems don't use /tmp as tmp dir (eg Android)
#endif #endif

View File

@ -9,11 +9,6 @@ Arrête le programme en cas d'échec
*/ */
void create_db(); void create_db();
/*
Open/closes the socket file to notify of db changes
*/
void notify_change();
/* /*
Ajouter une tâche à la base de données. Ajouter une tâche à la base de données.
Le numéro de tâche ne doit pas déjà être utilisé Le numéro de tâche ne doit pas déjà être utilisé
@ -40,7 +35,7 @@ void get_task(int id, task_t* t);
/* /*
Renvoie la liste des tâches de la base de données Renvoie la liste des tâches de la base de données
*/ */
task_list_t* get_task_list(char* input_category, bool include_completed); task_list_t* get_task_list(bool include_completed);
/* /*
Renvoie un identifiant de tâche encore non utilisé Renvoie un identifiant de tâche encore non utilisé

View File

@ -7,7 +7,6 @@
struct task { struct task {
int id; int id;
char* text; char* text;
char* category;
bool done; bool done;
time_t due_to; time_t due_to;
}; };

View File

@ -3,12 +3,6 @@
#ifndef DEF_TASKS_H #ifndef DEF_TASKS_H
#define DEF_TASKS_H #define DEF_TASKS_H
/*
Sépare la chaîne d'entrée en deux chaînes, l'une avant, l'autre après le premier ':'.
Si il n'y a pas de ':', out_category est NULL
*/
void parse_input(char* in_text, char** out_text, char** out_category);
/* /*
Renvoie une tâche avec un identifiant aléatoire valide Renvoie une tâche avec un identifiant aléatoire valide
*/ */
@ -22,7 +16,7 @@ void print_task(task_t task);
/* /*
Affiche une liste de tâches de manière compacte (et par catégorie: urgent, à faire, [complétée]) Affiche une liste de tâches de manière compacte (et par catégorie: urgent, à faire, [complétée])
*/ */
void print_task_list(task_list_t* list, bool show_category, bool show_completed); void print_task_list(task_list_t* list, bool show_completed);
/* /*
Libère la mémoire allouée à une liste de tâches Libère la mémoire allouée à une liste de tâches

View File

@ -1,168 +0,0 @@
/*
* Copyright (c) 2020 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "log.h"
#define MAX_CALLBACKS 32
typedef struct {
log_LogFn fn;
void *udata;
int level;
} Callback;
static struct {
void *udata;
log_LockFn lock;
int level;
bool quiet;
Callback callbacks[MAX_CALLBACKS];
} L;
static const char *level_strings[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};
#ifdef LOG_USE_COLOR
static const char *level_colors[] = {
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
};
#endif
static void stdout_callback(log_Event *ev) {
char buf[16];
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
#ifdef LOG_USE_COLOR
fprintf(
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
buf, level_colors[ev->level], level_strings[ev->level],
ev->file, ev->line);
#else
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
#endif
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void file_callback(log_Event *ev) {
char buf[64];
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void lock(void) {
if (L.lock) { L.lock(true, L.udata); }
}
static void unlock(void) {
if (L.lock) { L.lock(false, L.udata); }
}
const char* log_level_string(int level) {
return level_strings[level];
}
void log_set_lock(log_LockFn fn, void *udata) {
L.lock = fn;
L.udata = udata;
}
void log_set_level(int level) {
L.level = level;
}
void log_set_quiet(bool enable) {
L.quiet = enable;
}
int log_add_callback(log_LogFn fn, void *udata, int level) {
for (int i = 0; i < MAX_CALLBACKS; i++) {
if (!L.callbacks[i].fn) {
L.callbacks[i] = (Callback) { fn, udata, level };
return 0;
}
}
return -1;
}
int log_add_fp(FILE *fp, int level) {
return log_add_callback(file_callback, fp, level);
}
static void init_event(log_Event *ev, void *udata) {
if (!ev->time) {
time_t t = time(NULL);
ev->time = localtime(&t);
}
ev->udata = udata;
}
void log_log(int level, const char *file, int line, const char *fmt, ...) {
log_Event ev = {
.fmt = fmt,
.file = file,
.line = line,
.level = level,
};
lock();
if (!L.quiet && level >= L.level) {
init_event(&ev, stderr);
va_start(ev.ap, fmt);
stdout_callback(&ev);
va_end(ev.ap);
}
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) {
Callback *cb = &L.callbacks[i];
if (level >= cb->level) {
init_event(&ev, cb->udata);
va_start(ev.ap, fmt);
cb->fn(&ev);
va_end(ev.ap);
}
}
unlock();
}

View File

@ -1,49 +0,0 @@
/**
* Copyright (c) 2020 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>
#define LOG_VERSION "0.1.0"
typedef struct {
va_list ap;
const char *fmt;
const char *file;
struct tm *time;
void *udata;
int line;
int level;
} log_Event;
typedef void (*log_LogFn)(log_Event *ev);
typedef void (*log_LockFn)(bool lock, void *udata);
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
const char* log_level_string(int level);
void log_set_lock(log_LockFn fn, void *udata);
void log_set_level(int level);
void log_set_quiet(bool enable);
int log_add_callback(log_LogFn fn, void *udata, int level);
int log_add_fp(FILE *fp, int level);
void log_log(int level, const char *file, int line, const char *fmt, ...);
#endif

View File

@ -3,24 +3,22 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "main/include/colors.h" #include "include/colors.h"
#include "main/include/config.h" #include "include/config.h"
#include "main/include/tasks.h" #include "include/tasks.h"
#include "main/include/db.h" #include "include/db.h"
void help(char* name) { void help(char* name) {
printf(BLUE "\t-- TaKl " VERSION " --\n" RESET); printf(BLUE "\t-- TaKl " VERSION " --\n" RESET);
printf("Utilisation: %s ( list | add | reschedule | info | rm | done )\n", name); printf("Utilisation: %s ( list | add | info | rm | done )\n", name);
printf("\tlist [category] [-a:voir les tâches complétées]\n"); printf("\tlist [-a:voir les tâches complétées]\n");
printf("\tadd [category:]<task> [date]\n"); printf("\tadd <task> [date]\n");
printf("\treschedule <id> [date]\n"); printf("\tFormat de la date:\n");
printf("\t\tRelatif: min+%%d, h+%%d, j+%%d\n");
printf("\t\tAbsolu: dd/mm (pas de changement d'année pour l'instant)\n");
printf("\tinfo <id1> <id2> ...\n"); printf("\tinfo <id1> <id2> ...\n");
printf("\tdone <id1> <id2> ...\n"); printf("\tdone <id1> <id2> ...\n");
printf("\trm <id1> <id2> ...\n"); printf("\trm <id1> <id2> ...\n");
printf("\nFormat des dates:\n");
printf("\tRelatif: min+%%d, h+%%d, j+%%d\n");
printf("\tAbsolu: dd/mm (pas de changement d'année pour l'instant)\n");
} }
@ -35,7 +33,7 @@ time_t parseDateTime(const char* input) {
date.tm_year = current_time->tm_year; date.tm_year = current_time->tm_year;
date.tm_mon = current_time->tm_mon; date.tm_mon = current_time->tm_mon;
date.tm_mday = current_time->tm_mday; date.tm_mday = current_time->tm_mday;
date.tm_hour = current_time->tm_hour-1; //TODO: buggy in non-UTC date.tm_hour = current_time->tm_hour;
date.tm_min = current_time->tm_min; date.tm_min = current_time->tm_min;
date.tm_sec = current_time->tm_sec; date.tm_sec = current_time->tm_sec;
@ -88,80 +86,21 @@ int add(int argc, char* argv[]) {
task_t t = create_task(argv[2], due_to); task_t t = create_task(argv[2], due_to);
if (t.id == -1) { if (t.id == -1) {
if (t.category) {
free(t.category);
free(t.text);
}
return 1; return 1;
} }
if (add_task(t) != 0) { if (add_task(t) != 0) {
printf("Erreur lors de l'ajout à la base de données\n"); printf("Erreur lors de l'ajout à la base de données\n");
if (t.category) {
free(t.category);
free(t.text);
}
return 1; return 1;
} }
if (t.category) {
free(t.category);
free(t.text);
}
printf(BOLD YELLOW "[%d]" RESET " ajoutée\n", t.id); printf(BOLD YELLOW "[%d]" RESET " ajoutée\n", t.id);
return 0; return 0;
} }
int reschedule(int argc, char* argv[]) { int list_tasks(bool show_completed) {
if (argc < 3) { task_list_t* tasks = get_task_list(show_completed);
printf(BOLD YELLOW "Argument manquant\n" RESET); print_task_list(tasks, show_completed); // show_completed: true would work as all the tasks are not loaded if false
help(argv[0]);
return 1;
}
int id = strtol(argv[2], NULL, 10);
task_t t;
t.id = -1;
get_task(id, &t);
if (t.id == -1) {
printf(YELLOW "Tâche " BOLD "[%d]" RESET YELLOW " inexistante. Vérifiez que l'identifiant est bien correct\n" RESET, id);
return 1;
}
time_t due_to = parseDateTime(argv[3]);
if (due_to == -1) {
printf(RED "Échéance invalide, impossible de reprogrammer la tâche.\n" RESET);
return 1;
}
t.due_to = due_to;
if (update_task(t) != 0) {
printf("Erreur lors de la modification de la tâche\n");
return 1;
}
return 0;
}
int list_tasks(int argc, char* argv[]) {
bool show_completed = false;
char* category = NULL;
for (int i=2; i < argc; i++) {
if (!strcmp(argv[i], "-a")) {
show_completed = true;
} else {
category = argv[i];
}
}
task_list_t* tasks = get_task_list(category, show_completed);
print_task_list(tasks, !category, show_completed); // show_completed: true would work as all the tasks are not loaded if false
free_task_list(tasks); free_task_list(tasks);
return 0; return 0;
@ -189,9 +128,6 @@ int info(int argc, char* argv[]) {
print_task(t); print_task(t);
free(t.text); free(t.text);
if (t.category) {
free(t.category);
}
} }
return 0; return 0;
@ -259,10 +195,8 @@ int main(int argc, char* argv[]) {
if (!strcmp(argv[1], "add")) { if (!strcmp(argv[1], "add")) {
return add(argc, argv); return add(argc, argv);
} else if (!strcmp(argv[1], "reschedule")) {
return reschedule(argc, argv);
} else if (!strcmp(argv[1], "list")) { } else if (!strcmp(argv[1], "list")) {
return list_tasks(argc, argv); return list_tasks(argc > 2 && !strcmp(argv[2], "-a"));
} else if (!strcmp(argv[1], "info")) { } else if (!strcmp(argv[1], "info")) {
return info(argc, argv); return info(argc, argv);
} else if (!strcmp(argv[1], "done")) { } else if (!strcmp(argv[1], "done")) {

View File

@ -1,10 +0,0 @@
#ifndef DEF_NOTIFICATION_H
#define DEF_NOTIFICATION_H
#include "tasks.h"
/*
Envoyer une notification avec t
*/
void desktop_notification(task_t t);
#endif

View File

@ -1,14 +0,0 @@
#include <pthread.h>
#include "struct.h"
/*
Renvoie le chemin d'accès au socket (/tmp/takl.$USER sauf si défini différemment dans la config)
*/
char* get_socket_path();
/*
Renvoie l'élément arrivant dans le moins de temps d'une liste de tâches.
*/
task_t next_task(task_list_t* tasks);

View File

@ -1,11 +0,0 @@
#include <libnotify/notify.h>
#include "include/notification.h"
void desktop_notification(task_t t) {
notify_init ("TaKl");
NotifyNotification * Notif = notify_notification_new ("TaKl Daemon", t.text, "dialog-information");
notify_notification_show (Notif, NULL);
g_object_unref(G_OBJECT(Notif));
notify_uninit();
}

View File

@ -1,59 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <pthread.h>
#include "include/config.h"
#include "include/struct.h"
char* get_socket_path() {
/*
Checks if $TAKL_SOCKET env variable is set
else, the socket is located at /tmp/takl
*/
char* env_socket_path = getenv("TAKL_SOCKET");
if (env_socket_path) {
char* socket_path = malloc(sizeof(char)*(strlen(env_socket_path)+1));
strcpy(socket_path, env_socket_path);
return socket_path;
} else {
char* base_path = TMPDIR "/takl";
char* username = getenv("USER");
if (!username)
username = getenv("USERNAME");
assert(username != NULL);
char* socket_path = malloc(sizeof(char)*(strlen(base_path)+strlen(username)+1));
sprintf(socket_path, "%s.%s", base_path, username);
return socket_path;
}
}
task_t next_task(task_list_t* tasks) {
time_t now = time(0);
task_t next;
next.due_to = 0;
task_list_t* cur = tasks;
while (cur) {
task_t t = cur->task;
if (!t.done && (difftime(now, t.due_to) <= 0 || t.due_to == 0)) { // La tâche a une échéance, qui n'est pas passée
if (next.due_to == 0 || difftime(next.due_to, t.due_to) >= 0) {
next = t;
}
}
cur = cur->next;
}
return next;
}

View File

@ -4,46 +4,19 @@ Fonctions utilitaires concernant les tâches
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <string.h>
#include "include/db.h" #include "include/db.h"
#include "include/struct.h" #include "include/struct.h"
#include "include/colors.h" #include "include/colors.h"
void parse_input(char* in_text, char** out_text, char** out_category) {
int n = strlen(in_text);
int i=0;
for (;i < n; i++) {
if (in_text[i] == ':') {
break;
}
}
if (i == n) {
*out_text = in_text;
*out_category = NULL;
} else {
*out_text = malloc(sizeof(char)*(n-i));
*out_category = malloc(sizeof(char)*i);
memcpy(*out_text, (void*)in_text+((i+1)*sizeof(char)), (size_t)((n-i)*sizeof(char)));
memcpy(*out_category, in_text, (size_t)(i*sizeof(char)));
(*out_category)[i] = '\0';
}
}
task_t create_task(char* text, time_t due_to) { task_t create_task(char* text, time_t due_to) {
task_t task; task_t task;
task.id = get_new_task_id(); task.id = get_new_task_id();
task.text = text;
task.done = false; task.done = false;
task.due_to = due_to; task.due_to = due_to;
task.text = NULL;
task.category = NULL;
parse_input(text, &(task.text), &(task.category));
return task; return task;
} }
@ -51,9 +24,6 @@ task_t create_task(char* text, time_t due_to) {
void print_task(task_t task) { void print_task(task_t task) {
printf(YELLOW "=== Tâche " BOLD "[%d]" RESET YELLOW " ===\n" RESET, task.id); printf(YELLOW "=== Tâche " BOLD "[%d]" RESET YELLOW " ===\n" RESET, task.id);
if (task.category) {
printf(BLUE "%s:" RESET, task.category);
}
printf(BOLD "%s\n" RESET, task.text); printf(BOLD "%s\n" RESET, task.text);
printf("Statut: "); printf("Statut: ");
@ -82,40 +52,32 @@ void print_task(task_t task) {
} }
void print_single_task(task_t t, bool show_category, char* color) { void print_task_list(task_list_t* list, bool show_completed) {
if (t.category && show_category) {
printf(BOLD "%s[%d]" RESET " " BLUE "%s:" RESET "%s\n", color, t.id, t.category, t.text);
} else {
printf(BOLD "%s[%d]" RESET " %s\n", color, t.id, t.text);
}
}
void print_task_list(task_list_t* list, bool show_category, bool show_completed) {
task_list_t* cur = list; task_list_t* cur = list;
time_t now = time(0); time_t now = time(0);
while (cur) { // Show not completed red tasks first while (cur) { // Show not completed red tasks first
task_t t = cur->task; task_t t = cur->task;
if (!t.done && difftime(now, t.due_to) >= 0 && t.due_to != 0) { if (!t.done && difftime(now, t.due_to) >= 0 && t.due_to != 0) {
print_single_task(t, show_category, RED); // Task not completed but should be ! printf(BOLD RED "[%d]" RESET " %s\n", t.id, t.text); // Task not completed but should be !
} }
cur = cur->next; cur = cur->next;
} }
cur = list; cur = list;
while (cur) { // Show not completed but not due_to now then while (cur) { // Show not completed but not due_to then
task_t t = cur->task; task_t t = cur->task;
if (!t.done && (difftime(now, t.due_to) <= 0 || t.due_to == 0)) { if (!t.done && (difftime(now, t.due_to) <= 0 || t.due_to == 0)) {
print_single_task(t, show_category, YELLOW); // Task not completed but fine printf(BOLD YELLOW "[%d]" RESET " %s\n", t.id, t.text); // Task not completed but should be !
} }
cur = cur->next; cur = cur->next;
} }
if (show_completed) { if (show_completed) {
cur = list; cur = list;
while (cur) { // Show completed while (cur) { // Show not completed but not due_to then
task_t t = cur->task; task_t t = cur->task;
if (t.done) { if (t.done) {
print_single_task(t, show_category, GREEN); // Task already done printf(BOLD GREEN "[%d]" RESET " %s\n", t.id, t.text); // Task not completed but should be !
} }
cur = cur->next; cur = cur->next;
} }

View File

@ -1,7 +0,0 @@
[Desktop Entry]
Type=Application
Exec=/usr/bin/takl-daemon
Hidden=false
Terminal=false
X-GNOME-Autostart-enabled=true
Name=TaKl Notifier Daemon

View File

@ -1,42 +0,0 @@
#!/bin/bash
TMPDIR=$(mktemp -d)
#! On ne teste pas que l'on peut bien récupérer ces chemins automatiquement,
#! ce qui peut-être une source d'erreurs
export TAKL_DB="$TMPDIR/takl.sqlite3"
export TAKL_SOCKET="$TMPDIR/takl.socket"
echo "Using $TMPDIR"
make -j
# Catégories
./takl add "nocategory"
./takl add "category1:test1"
# Dates
./takl add "date:avec date" min+5
./takl add "date:avec date2" h+5
./takl add "date:avec date3" j+5
./takl add "date:avec date4" "31/12"
TASK_ID=$(./takl add "done soon" | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' | awk '-F[' '{ print $2 }' | awk '-F]' '{ print $1 }')
# List
./takl list
./takl list date
# Reschedule
./takl reschedule $TASK_ID "22/11"
# Get info
./takl info $TASK_ID
# Mark as done
./takl done $TASK_ID
./takl list -a
./takl rm $TASK_ID
#* À rajouter: tests sur le fait que les changements ont bien eu lieu
rm $TMPDIR -r