insert_event (#1)

Option to insert events seems to work.

Reviewed-on: #1
Co-authored-by: bjoernf <bjoern.foersterling@web.de>
Co-committed-by: bjoernf <bjoern.foersterling@web.de>
This commit is contained in:
bjoernf 2023-09-09 08:26:50 +02:00 committed by bjoernf
parent c713522736
commit 061ed30333
17 changed files with 452 additions and 188 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
a.out
ics_analyzer
.gitconfig
*.o

View File

@ -21,14 +21,17 @@ sudo make install
the default path is for evolution ics files at `~/.local/share/evolution/calendar/system/calendar.ics`
```
ics_analyzer
icscli
```
for a custom path
```
ics_analyzer -f path/to/ics/file.ics
icscli -f path/to/ics/file.ics
```
```
icscli -h
```
#### uninstall
@ -39,9 +42,6 @@ sudo make uninstall
#### TODO
- option to add events (evolution does not care about duplicate uuids)
(but use libuuid to create uuids)
- add option to print upcoming events only until a certain date
- improve makefile
- rename executable to ics_cli
- show end date for events that span over multiple days
- tests

33
docs/ics_format.txt Normal file
View File

@ -0,0 +1,33 @@
ICS files from Microsoft Outlook differ from those that were created by Gnome Evolution.
(semicolons are used instead of colons at some position)
Evolution
---------
all line endings are \r\n
all day event:
DTSTART;VALUE=DATE:20230908
DTEND;VALUE=DATE:20230909
an all day event can span over multiple days and look like this:
DTSTART;VALUE=DATE:20230911^M$
DTEND;VALUE=DATE:20230916^M$
appointment:
DTSTART;TZID=/freeassociation.sourceforge.net/Continent/City:
20230909T090000
DTEND;TZID=/freeassociation.sourceforge.net/Continent/City:
20230909T093000
appointments can also span over multiple days:
DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230913T230000
DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin:
20230914T040000
SEQUENCE is the number of times the event was modified.
Evolution sets this to 2 after it has been generated.
Appointments include the continent and city of the used time zone.
"DESCRIPTION:" is an optional field.

View File

@ -1,32 +0,0 @@
all:
gcc -Wall ./src/*.c
.PHONY: debug
debug:
gcc -Wall -g ./src/*.c
gdb a.out
.PHONY: run
run:
gcc -Wall ./src/*.c
./a.out
.PHONY: install
install: a.out
cp a.out /usr/local/bin/ics_analyzer
.PHONY: clean
clean:
rm a.out
.PHONY: uninstall
uninstall:
rm /usr/local/bin/ics_analyzer
.PHONY: test
test:
./a.out
@echo
./a.out -h
@echo
./a.out -f tests/calendar.ics

View File

@ -1,33 +1,42 @@
.PHONY:all
all:
gcc -Wall *.c
CC = gcc
CFLAGS = -Wall
LDFLAGS = -luuid
.PHONY:debug
debug:
gcc -Wall -g *.c
gdb a.out
# List of all source files (assuming they're all in the same directory)
SRC_FILES = $(wildcard *.c)
.PHONY:run
run:
gcc -Wall *.c
./a.out
# Generate a list of object files by replacing the .c extension with .o
OBJ_FILES = $(SRC_FILES:.c=.o)
EXECUTABLE = "icscli"
# linking
$(EXECUTABLE): $(OBJ_FILES)
gcc -Wall $(OBJ_FILES) -o $(EXECUTABLE) $(LDFLAGS)
main.o: main.c
$(CC) $(CFLAGS) -c $<
# use implicit rule to compile C source files to object files
%.o: %.c %.h
$(CC) $(CFLAGS) -c $<
.PHONY:install
install: a.out
cp a.out /usr/local/bin/ics_analyzer
install: $(EXECUTABLE)
cp $(EXECUTABLE) /usr/local/bin/$(EXECUTABLE)
.PHONY:clean
clean:
-rm a.out
-rm $(EXECUTABLE) *.o
.PHONY:uninstall
uninstall:
-rm /usr/local/bin/ics_analyzer
-rm /usr/local/bin/$(EXECUTABLE)
.PHONY:test
test:
./a.out
./$(EXECUTABLE)
@echo
./a.out -h
./$(EXECUTABLE) -h
@echo
./a.out -f ../tests/calendar.ics
./$(EXECUTABLE) -f ../tests/calendar.ics

View File

@ -1,3 +1,4 @@
#include "insert_event.h"
#include "cli_arg_parsing.h"
#include <stdio.h>
#include <stdlib.h>
@ -5,37 +6,38 @@
#include <string.h>
void usage() {
printf ("-h\tprint this help\n");
printf ("-f\tspecify ics file path\n");
exit(0);
printf ("-f [FILE PATH]\t\tspecify ics file path\n");
printf ("-h\t\t\tprint this help\n");
printf ("-i\t\t\tinsert an event\n");
exit(0);
}
void get_cli_args(int argc, char **argv, char **file_name) {
int opt = 0;
int opt = 0;
memset(file_name, '\0', strlen(*file_name));
memset(file_name, '\0', strlen(*file_name));
if (argc < 2) {
char *home = getenv("HOME");
*file_name = home;
char *home = getenv("HOME");
*file_name = home;
if (home != NULL) {
strcat(*file_name, "/.local/share/evolution/calendar/system/calendar.ics");
} else {
printf ("Environment variable HOME is not set.\n");
exit(1);
}
return;
}
if (home != NULL) {
strcat(*file_name, "/.local/share/evolution/calendar/system/calendar.ics");
} else {
printf ("Environment variable HOME is not set.\n");
exit(1);
}
while ((opt = getopt(argc, argv, "f:h")) != -1) {
switch(opt) {
case 'f':
*file_name = optarg;
break;
case 'h':
usage();
}
}
while ((opt = getopt(argc, argv, "f:hi")) != -1) {
switch(opt) {
case 'f':
*file_name = optarg;
break;
case 'h':
usage();
break;
case 'i':
insert_event(*file_name);
break;
}
}
}

View File

@ -1,41 +0,0 @@
// cut a string into two parts by the first occurence of delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
// cut a string into two parts by delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
#include <string.h>
void cut_string(char my_string[], char delimiter, int side) {
char part1[256] = "";
char part2[256] = "";
int split = 0;
int j = 0;
for (int i = 0; i < strlen(my_string); i++) {
if (my_string[i] == delimiter) {
if (split == 0) {
split = 1;
continue;
}
}
if (split == 0) {
part1[i] = my_string[i];
} else {
part2[j] = my_string[i];
j++;
}
}
memset(my_string, '\0', strlen(my_string));
if (side == 0) {
strcpy(my_string, part1);
} else {
strcpy(my_string, part2);
}
}

View File

@ -1,5 +0,0 @@
#pragma once
// cut string into two parts and choose one part
// side is 0 or 1
void cut_string(char my_string[], char delimiter, int side);

View File

@ -1,15 +1,23 @@
#include "date_time_handling.h"
#include "string_handling.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// buffer needs to contain a string with a strlen of 15 (format: "xxxxxxxxTxxxxxx")
// or a strlen of 16 (format: "YYYYmmddTHHMMSSZ")
void get_date(char buffer[]) {
// add 1 because strlen does not include the null character
size_t buffer_size = strlen(buffer) + 1;
time_t my_unix_ts = time(NULL);
struct tm* my_tm_local = localtime(&my_unix_ts);
strftime(buffer, buffer_size, "%Y%m%dT%H%M%S", my_tm_local);
if (strlen(buffer) == 15) {
strftime(buffer, buffer_size, "%Y%m%dT%H%M%S", my_tm_local);
} else if (strlen(buffer) == 16) {
strftime(buffer, buffer_size, "%Y%m%dT%H%M%SZ", my_tm_local);
}
}
// 20230823T194138 -> 2023-08-23 19:41:38
@ -31,6 +39,26 @@ void pretty_print_date_time(char date_time[]) {
}
}
void marshall_date_time(char date_time[]) {
char transformed_string[strlen(date_time)];
int j = 0;
remove_nl_and_cr(date_time);
for (int i = 0; i<=strlen(date_time); i++) {
if (date_time[i] != ':' && date_time[i] != '-') {
if (date_time[i] == ' ') {
transformed_string[j] = 'T';
} else {
transformed_string[j] = date_time[i];
}
j++;
}
}
memset(date_time, '\0', strlen(date_time));
strcpy(date_time, transformed_string);
}
void print_end_date(char end_date[]) {
// end_date is all day event
if (strlen(end_date) == 8)
@ -43,4 +71,18 @@ void print_end_date(char end_date[]) {
printf ("%c%c:", time[2], time[3]);
printf ("%c%c", time[4], time[5]);
}
char *get_tz() {
char *timezone_path = malloc(256);
ssize_t bytes_read = readlink("/etc/localtime", timezone_path, 255);
if (bytes_read != -1) {
// Null-terminate the string
timezone_path[bytes_read] = '\0';
} else {
perror("readlink");
exit(1);
}
return timezone_path;
}

View File

@ -2,5 +2,7 @@
#include <string.h>
void get_date(char buffer[]);
char *get_tz();
void pretty_print_date_time(char date_time[]);
void marshall_date_time(char date_time[]);
void print_end_date(char end_date[]);

187
src/insert_event.c Normal file
View File

@ -0,0 +1,187 @@
#include "insert_event.h"
#include "string_handling.h"
#include "date_time_handling.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <uuid/uuid.h>
void insert_event(char *file_name) {
int myfd = open(file_name, O_RDWR);
int all_day_event = 0;
char summary_buf[256] = "SUMMARY:";
char *input_buffer = &summary_buf[8];
uuid_t uuid;
char uuid_str[37] = "";
char dtstamp[] = "YYYYmmddTHHMMSSZ";
char *time_zone = get_tz();
char *dtstart_buffer = malloc(128);
char *dtend_buffer = malloc(128);
remove_until_delim(time_zone, '/', 4);
printf("Insert a new event\n");
printf("Is this an all day event? [y/n] ");
all_day_event = binary_user_choice();
printf("SUMMARY: ");
fgets (input_buffer, (sizeof(summary_buf)-strlen(summary_buf)), stdin);
if (strchr(input_buffer, '\n') == NULL)
printf ("The input has been truncated to:\n%s\n", input_buffer);
// modify the string to have \r\n line ending
summary_buf[strlen(summary_buf)+1] = '\0';
summary_buf[strlen(summary_buf)-1] = '\r';
summary_buf[strlen(summary_buf)] = '\n';
uuid_generate(uuid);
// parse uuid to a string
uuid_unparse(uuid, uuid_str);
get_date(dtstamp);
get_dtstart_dtend(dtstart_buffer, all_day_event, "start");
marshall_date_time(dtstart_buffer);
form_dtstart_string(dtstart_buffer, time_zone);
get_dtstart_dtend(dtend_buffer, all_day_event, "end");
marshall_date_time(dtend_buffer);
form_dtend_string(dtend_buffer, time_zone);
seek_cal_end(myfd);
write(myfd, "BEGIN:VEVENT\r\n", strlen("BEGIN:VEVENT\r\n"));
write(myfd, "UID:", strlen("UID:"));
write(myfd, uuid_str, strlen(uuid_str));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "DTSTAMP:", strlen("DTSTAMP:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, dtstart_buffer, strlen(dtstart_buffer));
write(myfd, dtend_buffer, strlen(dtend_buffer));
write(myfd, "SEQUENCE:2\r\n", strlen("SEQUENCE:2\r\n"));
write(myfd, summary_buf, strlen(summary_buf));
write(myfd, "TRANSP:OPAQUE\r\n", strlen("TRANSP:OPAQUE\r\n"));
write(myfd, "CLASS:PUBLIC\r\n", strlen("CLASS:PUBLIC\r\n"));
write(myfd, "CREATED:", strlen("CREATED:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "LAST-MODIFIED:", strlen("LAST-MODIFIED:"));
write(myfd, dtstamp, strlen(dtstamp));
write(myfd, "\r\n", strlen("\r\n"));
write(myfd, "END:VEVENT\r\n", strlen("END:VEVENT\r\n"));
write(myfd, "END:VCALENDAR\r\n", strlen("END:VCALENDAR\r\n"));
close(myfd);
free(time_zone);
free(dtstart_buffer);
free(dtend_buffer);
exit(0);
}
void seek_cal_end(int fd) {
char search_string[] = "END:VCALENDAR";
int j = 0;
char char_reader = '\0';
lseek(fd, -1, SEEK_END);
while(read(fd, &char_reader, 1)) {
// no need to compare to the null terminator of the search_string
if (char_reader == search_string[strlen(search_string)-j-1]) {
j++;
} else {
j = 0;
}
if (j == (strlen(search_string))) {
lseek(fd, -1, SEEK_CUR);
break;
}
lseek(fd, -2, SEEK_CUR);
}
}
int binary_user_choice() {
char input_buffer[64] = "";
if (fgets (input_buffer, sizeof(input_buffer), stdin) == NULL) {
printf ("an fgets error occured\n");
}
if (! strchr(input_buffer, '\n')) {
printf ("Input buffer overflow!\n");
exit(1);
}
if (input_buffer[0] == 'n' || input_buffer[0] == 'N') {
return 0;
} else if (input_buffer[0] == 'y' || input_buffer[0] == 'Y') {
return 1;
} else {
printf ("Please enter a N/n or Y/y character!\n");
exit(1);
}
}
// char *start_or_end should contain "start" or "end"
void get_dtstart_dtend(char input_buffer[], int all_day_event, char *start_or_end) {
if (all_day_event) {
printf("Enter the %s date in YYYY-mm-dd format!\n", start_or_end);
if (fgets(input_buffer, 128, stdin) == NULL) {
perror ("fgets");
exit(1);
}
if (strlen(input_buffer) != 11) {
printf ("Wrong format!\n");
exit(1);
}
} else {
printf("Enter the %s date in YYYY-mm-dd HH:MM:SS format!\n", start_or_end);
if (fgets(input_buffer, 128, stdin) == NULL) {
perror ("fgets");
exit(1);
}
if (strlen(input_buffer) != 20) {
printf ("Wrong format!\n");
exit(1);
}
}
}
void form_dtstart_string(char dtstart_buffer[], char time_zone[]) {
char dtstart_copy[strlen(dtstart_buffer)];
strcpy(dtstart_copy, dtstart_buffer);
// not all day event
if (strlen(dtstart_buffer) == 15) {
strcpy(dtstart_buffer, "DTSTART;TZID=/freeassociation.sourceforge.net");
strcat(dtstart_buffer, time_zone);
strcat(dtstart_buffer, ":\r\n ");
strcat(dtstart_buffer, dtstart_copy);
} else {
// all day event
strcpy(dtstart_buffer, "DTSTART;VALUE=DATE:");
strcat(dtstart_buffer, dtstart_copy);
}
strcat(dtstart_buffer, "\r\n");
}
void form_dtend_string(char dtend_buffer[], char time_zone[]) {
char dtend_copy[strlen(dtend_buffer)];
strcpy(dtend_copy, dtend_buffer);
// not all day event
if (strlen(dtend_buffer) == 15) {
strcpy(dtend_buffer, "DTEND;TZID=/freeassociation.sourceforge.net");
strcat(dtend_buffer, time_zone);
strcat(dtend_buffer, ":\r\n ");
strcat(dtend_buffer, dtend_copy);
} else {
// all day event
strcpy(dtend_buffer, "DTEND;VALUE=DATE:");
strcat(dtend_buffer, dtend_copy);
}
strcat(dtend_buffer, "\r\n");
}

8
src/insert_event.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
void insert_event(char *file_name);
void seek_cal_end(int fd);
int binary_user_choice();
void get_dtstart_dtend(char input_buffer[], int all_day_event, char *start_or_end);
void form_dtstart_string(char dtstart_buffer[], char time_zone[]);
void form_dtend_string(char dtend_buffer[], char time_zone[]);

View File

@ -1,12 +1,11 @@
#include "cli_arg_parsing.h"
#include "date_time_handling.h"
#include "list_handling.h"
#include "cut_string.h"
#include "string_handling.h"
#include "move_lines.h"
#include "read_until_nl.h"
#include "read_until_string.h"
#include "seek_string_a.h"
#include "remove_whitespace.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
@ -14,8 +13,8 @@
#include <limits.h>
int main(int argc, char **argv) {
char *ics_path = "";
get_cli_args(argc, argv, &ics_path);
char *ics_path = "";
get_cli_args(argc, argv, &ics_path);
char my_line[4096] = "";

View File

@ -1,45 +0,0 @@
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
#include <string.h>
void remove_nl_and_cr(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
// remove new lines, carriage returns and spaces
void remove_whitespace(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r' \
|| raw_string[i] == ' ') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}

View File

@ -1,8 +0,0 @@
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
#pragma once
void remove_nl_and_cr(char raw_string[]);
void remove_whitespace(char raw_string[]);

103
src/string_handling.c Normal file
View File

@ -0,0 +1,103 @@
// cut a string into two parts by the first occurence of delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
// cut a string into two parts by delimiter
// and choose the first part (side 0) or the second part (side 1)
// the chosen part will overwrite the original string
#include <string.h>
#include <stdlib.h>
void cut_string(char my_string[], char delimiter, int side) {
char part1[256] = "";
char part2[256] = "";
int split = 0;
int j = 0;
for (int i = 0; i < strlen(my_string); i++) {
if (my_string[i] == delimiter) {
if (split == 0) {
split = 1;
continue;
}
}
if (split == 0) {
part1[i] = my_string[i];
} else {
part2[j] = my_string[i];
j++;
}
}
memset(my_string, '\0', strlen(my_string));
if (side == 0) {
strcpy(my_string, part1);
} else {
strcpy(my_string, part2);
}
}
// this function removes all new lines and carriage returns from a string
// you might want to write a new function that replaces '\r' and '\n'
// with a delimiter of user's choice
void remove_nl_and_cr(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
// remove new lines, carriage returns and spaces
void remove_whitespace(char raw_string[]) {
char processed_string[strlen(raw_string)];
// counter for num of elements of processed_string
int j = 0;
for (int i = 0; i<strlen(raw_string); i++) {
if (raw_string[i] == '\n' || raw_string[i] == '\r' \
|| raw_string[i] == ' ') {
continue;
} else {
processed_string[j] = raw_string[i];
j++;
}
}
processed_string[j] = '\0';
memset(raw_string, '\0', strlen(raw_string));
strcpy(raw_string, processed_string);
}
void remove_until_delim(char raw_string[], char delimiter, int occurence) {
int chunk = 0;
int j = 0;
char *tmp_string = malloc(strlen(raw_string));
memset(tmp_string, '\0', strlen(raw_string));
for (int i = 0; i < strlen(raw_string); i++) {
if (raw_string[i] == delimiter) {
chunk++;
}
if (chunk >= occurence) {
tmp_string[j] = raw_string[i];
j++;
}
}
strcpy(raw_string, tmp_string);
free(tmp_string);
}

8
src/string_handling.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
// cut string into two parts and choose one part
// side is 0 or 1
void cut_string(char my_string[], char delimiter, int side);
void remove_nl_and_cr(char raw_string[]);
void remove_whitespace(char raw_string[]);
void remove_until_delim(char raw_string[], char delimiter, int occurence);