From d831e279cb856ec720ed770ce9d0c39eb347b7e1 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 4 Apr 2021 13:37:31 +0200 Subject: m + add list-birthdays and set-contact-birthday MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- Makefile | 37 ++------ TODO | 5 - common.h | 18 +--- config.mk | 2 +- contacts.c.in | 4 +- get-contact-birthday.c | 21 ++++- list-birthdays.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++ multicall-hardlinks.mk | 12 +++ multicall-symlinks.mk | 5 + set-contact-birthday.c | 131 +++++++++++++++++++++++++ singlecall.mk | 10 ++ 11 files changed, 444 insertions(+), 53 deletions(-) create mode 100644 list-birthdays.c create mode 100644 multicall-hardlinks.mk create mode 100644 multicall-symlinks.mk create mode 100644 set-contact-birthday.c create mode 100644 singlecall.mk diff --git a/Makefile b/Makefile index 5ea2d5b..e4ef9ab 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,12 @@ CONFIGFILE = config.mk include $(CONFIGFILE) +CALLTYPE = multicall-hardlinks +# multicall-hardlinks = multiple hardlinks of the same multicall binary is installed +# multicall-symlinks = multiple links to a multicall binary named $(PREFIX)/bin/contacts are installed +# singlecall = separate binaries are install for each command (greatly wastes space when statically linked) +include $(CALLTYPE).mk + BIN =\ add-contact\ @@ -28,6 +34,7 @@ BIN =\ get-contact-photos\ get-contact-sites\ is-contact-ice\ + list-birthdays\ list-chat-contacts\ list-contact-groups\ list-contact-organisations\ @@ -36,6 +43,7 @@ BIN =\ list-organisation-contacts\ print-contact\ remove-contact\ + set-contact-birthday\ set-contact-chats\ set-contact-emails\ set-contact-gender\ @@ -56,7 +64,6 @@ OBJ = $(BIN:=.o) BOBJ = $(BIN:=.bo) -all: $(BIN) contacts $(OBJ): $(@:.o=.c) $(HDR) $(BOBJ): $(@:.bo=.c) $(HDR) @@ -64,7 +71,7 @@ $(BOBJ): $(@:.bo=.c) $(HDR) $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) .c.bo: - $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -Dmain="$$(printf '%s\n' $*_main | tr - _)" -DMULTICALL_BINARY + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -Dmain="$$(printf '%s\n' main__$* | tr - _)" -DMULTICALL_BINARY contacts: contacts.o $(BOBJ) $(CC) -o $@ $@.o $(BOBJ) $(LDFLAGS) @@ -210,30 +217,6 @@ set-contact-photos: set-contact-photos.o set-contact-sites: set-contact-sites.o $(CC) -o $@ $@.o $(LDFLAGS) -install: $(BIN) - mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" - cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/" - -install-mcb: contacts - mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" - set -- $(BIN) &&\ - cp -- "$$1" "$(DESTDIR)$(PREFIX)/bin/$$1" &&\ - linkto="$$1" &&\ - shift 1 &&\ - cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\ - for f; do\ - ln -- "$$linkto" "$$f" || exit 1;\ - done - -install-mcb-symlinks: contacts - mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" - mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" - cp -- contacts "$(DESTDIR)$(PREFIX)/lib/" - cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\ - for f in $(BIN); do\ - ln -s -- ../lib/contacts "$$f" || exit 1;\ - done - uninstall: -cd -- "$(DESTDIR)$(PREFIX)/bin" && rm -f -- $(BIN) -rm -f -- "$(DESTDIR)$(PREFIX)/lib/contacts" @@ -244,4 +227,4 @@ clean: .SUFFIXES: .SUFFIXES: .c .o .bo -.PHONY: all install install-mcb install-mcb-symlinks uninstall clean +.PHONY: all install uninstall clean diff --git a/TODO b/TODO index f0c6588..fd5b32e 100644 --- a/TODO +++ b/TODO @@ -2,9 +2,6 @@ Add tools for checking context for contact info Add tools for .blocks Add tools for .addresses -list-birthdays -set-contact-birthday - Test find-contact-by-chat Test find-contact-by-email Test find-contact-by-name @@ -13,7 +10,6 @@ Test find-contact-by-organisation Test find-contact-by-pgpkey Test find-contact-by-photo Test find-contact-by-site -Test get-contact-birthday Test get-contact-chats Test get-contact-emails Test get-contact-name @@ -22,7 +18,6 @@ Test get-contact-organisations Test get-contact-pgpkeys Test get-contact-photos Test get-contact-sites -Test list-birthdays Test list-chat-contacts Test list-contact-organisations Test list-organisation-contacts diff --git a/common.h b/common.h index ca6db51..3fcd407 100644 --- a/common.h +++ b/common.h @@ -1,4 +1,8 @@ /* See LICENSE file for copyright and license details. */ +#ifdef MULTICALL_BINARY +# define LIBSIMPLY_CONFIG_MULTICALL_BINARY +#endif + #include #include #include @@ -7,17 +11,3 @@ #ifndef BUFSIZ # define BUFSIZ 4096 #endif - - -#ifdef MULTICALL_BINARY -# undef NUSAGE -# define NUSAGE(STATUS, SYNOPSIS)\ - static _LIBSIMPLE_NORETURN void\ - usage(void)\ - {\ - const char *syn = SYNOPSIS ? SYNOPSIS : "";\ - fprintf(stderr, "usage: %s%s%s\n", argv0, *syn ? " " : "", syn);\ - exit(STATUS);\ - }\ - extern char *argv0 -#endif diff --git a/config.mk b/config.mk index 7145091..2c119c1 100644 --- a/config.mk +++ b/config.mk @@ -5,4 +5,4 @@ CC = cc CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -I../libcontacts CFLAGS = -std=c99 -Wall -O2 -LDFLAGS = -L../libcontacts -lcontacts -lsimple +LDFLAGS = -s -L../libcontacts -lcontacts -lsimple diff --git a/contacts.c.in b/contacts.c.in index 5e470ac..b297951 100644 --- a/contacts.c.in +++ b/contacts.c.in @@ -2,7 +2,7 @@ #include #include -#define X(NAM) int NAM##_main(int, char *[]); +#define X(NAM) int main__##NAM(int, char *[]); LIST_COMMANDS #undef X @@ -21,7 +21,7 @@ main(int argc, char *argv[]) #define X(NAM)\ if (!strcmp(name, #NAM))\ - return NAM##_main(argc, argv); + return main__##NAM(argc, argv); LIST_COMMANDS; #undef X diff --git a/get-contact-birthday.c b/get-contact-birthday.c index 29be768..173d6a2 100644 --- a/get-contact-birthday.c +++ b/get-contact-birthday.c @@ -18,7 +18,8 @@ get_age(struct libcontacts_birthday *bday, const struct tm *now) static void print_birthdate(struct libcontacts_birthday *bday, const struct tm *now) { - int age; + static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int age, days = 0, y, m, d; if (bday->year) printf("%04u-", bday->year); else @@ -39,7 +40,20 @@ print_birthdate(struct libcontacts_birthday *bday, const struct tm *now) } if (bday->year && bday->month && bday->day) { age = get_age(bday, now); - printf(", %i %s old today", age, age == 1 ? "year" : "years"); + y = (int)bday->year - 1900; + m = (int)bday->month - 1; + d = (int)bday->day; + while (m != now->tm_mon || d > now->tm_mday) { + days += days_in_month[m] - (d - 1); + days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0)); + d = 1; + if (++m == 12) { + y += 1; + m = 0; + } + } + days += now->tm_mday - d; + printf(", %i %s and %i %s old", age, age == 1 ? "year" : "years", days, days == 1 ? "day" : "days"); } printf("\n"); } @@ -70,7 +84,7 @@ print_birthday(struct libcontacts_birthday *bday, const struct tm *now) bday->month += 1; } } - printf("(%02u)%.3s-%02u (", (unsigned)bday->month % 12, + printf("(%02u)%.3s-%02u (", (unsigned)bday->month, &"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)], (unsigned)bday->day); when.tm_year = now->tm_year + next_year; when.tm_mon = (bday->month - 1) % 12; @@ -97,7 +111,6 @@ print_birthday(struct libcontacts_birthday *bday, const struct tm *now) days += when.tm_mday - d; printf("in %i %s)\n", days, days == 1 ? "day" : "days"); } - } int diff --git a/list-birthdays.c b/list-birthdays.c new file mode 100644 index 0000000..950df2a --- /dev/null +++ b/list-birthdays.c @@ -0,0 +1,252 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + +USAGE("[-n] (-L | contact-id ...)"); + + +static int +get_age(struct libcontacts_birthday *bday, const struct tm *now) +{ + int age = now->tm_year + 1900 - (int)bday->year; + if (now->tm_mon + 1 < bday->month) + age -= 1; + else if (now->tm_mon + 1 == bday->month) + age -= (now->tm_mday < bday->day); + return age; +} + +static void +print_birthdate(struct libcontacts_birthday *bday, const struct tm *now) +{ + static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int age, days = 0, y, m, d; + if (bday->year) + printf("%04u-", bday->year); + else + printf("??""??""-"); + if (bday->month) { + printf("(%02u)%.3s-", (unsigned)bday->month, + &"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)]); + } else { + printf("(??"")??""?-"); + } + if (bday->day) + printf("%02u", (unsigned)bday->day); + else + printf("??"); + if ((bday->month - 1) % 12 == 1 && bday->day == 29) { + bday->day -= bday->before_on_common; + printf(" (%s on common years)", bday->before_on_common ? "(02)Feb-28" : "(03)Mar-01"); + } + if (bday->year && bday->month && bday->day) { + age = get_age(bday, now); + y = (int)bday->year - 1900; + m = (int)bday->month - 1; + d = (int)bday->day; + while (m != now->tm_mon || d > now->tm_mday) { + days += days_in_month[m] - (d - 1); + days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0)); + d = 1; + if (++m == 12) { + y += 1; + m = 0; + } + } + days += now->tm_mday - d; + printf(", %i %s and %i %s old", age, age == 1 ? "year" : "years", days, days == 1 ? "day" : "days"); + } + printf("\n"); +} + +static void +print_birthday(struct libcontacts_birthday *bday, const struct tm *now) +{ + static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int next_year = 0, leap_year = 0, year, age, days = 0, y, m, d; + struct tm when; + if (now->tm_mon + 1 > bday->month) + next_year = 1; + else if (now->tm_mon + 1 == bday->month) + next_year = (now->tm_mday > bday->day); + if (bday->year) { + printf("%04i-", now->tm_year + next_year + 1900); + } else { + printf("??""??""-"); + } + year = now->tm_year + next_year + 1900; + if (year % 4 == 0 && (year % 100 || year % 400 == 0)) + leap_year = 1; + if (((bday->month - 1) % 12 == 1 && bday->day == 29) && leap_year) { + if (bday->before_on_common) { + bday->day -= 1; + } else { + bday->day = 1; + bday->month += 1; + } + } + printf("(%02u)%.3s-%02u (", (unsigned)bday->month, + &"JanFebMarAprMayJunJulAugSepOctNovDec"[3 * ((bday->month - 1) % 12)], (unsigned)bday->day); + when.tm_year = now->tm_year + next_year; + when.tm_mon = (bday->month - 1) % 12; + when.tm_mday = bday->day; + if (bday->year) { + age = get_age(bday, &when); + printf("%i %s ", age, age == 1 ? "year" : "years"); + } + if (when.tm_mon == now->tm_mon && when.tm_mday == now->tm_mday) { + printf("today)\n"); + } else { + y = now->tm_year; + m = now->tm_mon; + d = now->tm_mday; + while (m != when.tm_mon || d > when.tm_mday) { + days += days_in_month[m] - (d - 1); + days += (m == 1 && y % 4 == 0 && (y % 100 || y % 400 == 0)); + d = 1; + if (++m == 12) { + y += 1; + m = 0; + } + } + days += when.tm_mday - d; + printf("in %i %s)\n", days, days == 1 ? "day" : "days"); + } +} + +static int +compare_by_birthdate(const void *apv, const void *bpv) +{ + struct libcontacts_contact *const *ap = apv, *const *bp = bpv; + const struct libcontacts_contact *a = *ap, *b = *bp; + if (!a->birthday != !b->birthday) + return !a->birthday ? -1 : +1; + if (!a->birthday) + return 0; + if (a->birthday->year != b->birthday->year) + return a->birthday->year < b->birthday->year ? -1 : +1; + if (a->birthday->month != b->birthday->month) + return a->birthday->month < b->birthday->month ? -1 : +1; + if (a->birthday->day != b->birthday->day) + return a->birthday->day < b->birthday->day ? -1 : +1; + if (a->birthday->before_on_common != b->birthday->before_on_common) + return a->birthday->before_on_common ? -1 : +1; + return 0; +} + +static struct tm *now; + +static int +compare_by_birthday(const void *apv, const void *bpv) +{ + struct libcontacts_contact *const *ap = apv, *const *bp = bpv; + const struct libcontacts_contact *a = *ap, *b = *bp; + int ac, bc; + if (!a->birthday != !b->birthday) + return !a->birthday ? -1 : +1; + if (!a->birthday) + return 0; + if (!a->birthday->month != !b->birthday->month) + return !a->birthday->month ? -1 : +1; + if (!a->birthday->month) + return 0; + if (!a->birthday->day != !b->birthday->day) + return !a->birthday->day ? -1 : +1; + if (!a->birthday->day) + return 0; + ac = (12 + (a->birthday->month - 1 - now->tm_mon) % 12) % 12; + bc = (12 + (b->birthday->month - 1 - now->tm_mon) % 12) % 12; + if (ac != bc) + return ac - bc; + ac = (31 + (a->birthday->day - now->tm_mday) % 31) % 31; + bc = (31 + (b->birthday->day - now->tm_mday) % 31) % 31; + if (ac != bc) + return ac - bc; + if (a->birthday->before_on_common != b->birthday->before_on_common) + return a->birthday->before_on_common ? -1 : +1; + return 0; +} + +int +main(int argc, char *argv[]) +{ + int next = 0, list = 0; + struct passwd *user; + struct libcontacts_contact **contacts; + time_t tim; + int ret = 0; + size_t i; + + ARGBEGIN { + case 'n': + next = 1; + break; + case 'L': + list = 1; + break; + default: + usage(); + } ARGEND; + + if (list ? argc : !argc) + usage(); + + for (i = 0; argv[i]; i++) + if (!*argv[i] || strchr(argv[i], '/')) + usage(); + + errno = 0; + user = getpwuid(getuid()); + if (!user) + eprintf("getpwuid: %s\n", errno ? strerror(errno) : "user does not exist"); + + tim = time(NULL); + now = localtime(&tim); + if (!now) + eprintf("localtime:"); + + if (list) { + if (libcontacts_load_contacts(&contacts, user)) + eprintf("libcontacts_load_contacts:"); + for (i = 0; contacts[i]; i++); + } else { + contacts = ecalloc((size_t)argc + 1, sizeof(*contacts)); + for (i = 0; *argv; argv++) { + contacts[i] = emalloc(sizeof(**contacts)); + if (libcontacts_load_contact(*argv, contacts[i], user)) { + weprintf("libcontacts_load_contact %s: %s\n", *argv, + errno ? strerror(errno) : "contact file is malformatted"); + ret = 1; + free(contacts[i]); + } else { + i++; + } + } + contacts[i] = NULL; + } + + if (next) + qsort(contacts, i, sizeof(*contacts), compare_by_birthday); + else + qsort(contacts, i, sizeof(*contacts), compare_by_birthdate); + + for (i = 0; contacts[i]; i++) { + if (contacts[i]->birthday) { + if (next) { + if (argc != 1) + printf("%s: ", contacts[i]->id); + print_birthday(contacts[i]->birthday, now); + } else if (contacts[i]->birthday->month && contacts[i]->birthday->year) { + if (argc != 1) + printf("%s: ", contacts[i]->id); + print_birthdate(contacts[i]->birthday, now); + } + } + libcontacts_contact_destroy(contacts[i]); + free(contacts[i]); + } + + if (fflush(stdout) || ferror(stdout) || fclose(stdout)) + eprintf("printf:"); + free(contacts); + return ret; +} diff --git a/multicall-hardlinks.mk b/multicall-hardlinks.mk new file mode 100644 index 0000000..be6ca66 --- /dev/null +++ b/multicall-hardlinks.mk @@ -0,0 +1,12 @@ +all: contacts + +install: contacts + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + set -- $(BIN) &&\ + cp -- "$$1" "$(DESTDIR)$(PREFIX)/bin/$$1" &&\ + linkto="$$1" &&\ + shift 1 &&\ + cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\ + for f; do\ + ln -- "$$linkto" "$$f" || exit 1;\ + done diff --git a/multicall-symlinks.mk b/multicall-symlinks.mk new file mode 100644 index 0000000..314cecd --- /dev/null +++ b/multicall-symlinks.mk @@ -0,0 +1,5 @@ +all: contacts + +install: $(BIN) + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/" diff --git a/set-contact-birthday.c b/set-contact-birthday.c new file mode 100644 index 0000000..535e635 --- /dev/null +++ b/set-contact-birthday.c @@ -0,0 +1,131 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + +/* Date components are in different options to avoid date format confusion */ +USAGE("([-y year | -Y] [-m month | -M] [-d day | -D] [-b | -B] | -u) contact-id ..."); + + +static int +getintarg(const char *arg) +{ + int ret = 0; + for (; isdigit(*arg); arg++) { + if (ret > (INT_MAX - (*arg & 15)) / 10) + usage(); + ret = ret * 10 + (*arg & 15); + } + if (*arg) + usage(); + return ret; +} + +int +main(int argc, char *argv[]) +{ + static int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int year = 0, month = 0, day = 0, before_on_common = -1, remove = 0; + struct passwd *user; + struct libcontacts_contact contact; + int ret = 0; + size_t i; + + ARGBEGIN { + case 'y': + if (year) + usage(); + year = getintarg(ARG()); + break; + case 'Y': + if (year) + usage(); + year = -1; + break; + case 'm': + if (month) + usage(); + month = getintarg(ARG()); + break; + case 'M': + if (month) + usage(); + month = -1; + break; + case 'd': + if (day) + usage(); + day = getintarg(ARG()); + break; + case 'D': + if (day) + usage(); + day = -1; + break; + case 'b': + if (before_on_common == 0) + usage(); + before_on_common = 1; + case 'B': + if (before_on_common == 1) + usage(); + before_on_common = 0; + case 'u': + remove = 1; + break; + default: + usage(); + } ARGEND; + + if ((year | month | day | (before_on_common + 1)) && remove) + usage(); + if (year % 4 == 0 && (year % 100 || year % 400 == 0)) /* 0 is a leap year */ + days_in_month[1] += 1; + if ((month && month > 12) || day > days_in_month[month - 1]) + usage(); + if (!argc) + usage(); + + for (i = 0; argv[i]; i++) + if (!*argv[i] || strchr(argv[i], '/')) + usage(); + + errno = 0; + user = getpwuid(getuid()); + if (!user) + eprintf("getpwuid: %s\n", errno ? strerror(errno) : "user does not exist"); + + for (; *argv; argv++) { + if (libcontacts_load_contact(*argv, &contact, user)) { + weprintf("libcontacts_load_contact %s: %s\n", *argv, errno ? strerror(errno) : "contact file is malformatted"); + ret = 1; + } else { + if (remove) { + if (!contact.birthday) + goto next; + libcontacts_birthday_destroy(contact.birthday); + free(contact.birthday); + contact.birthday = NULL; + } else { + if (!contact.birthday) + contact.birthday = ecalloc(1, sizeof(*contact.birthday)); + if (year) + contact.birthday->year = year < 0 ? 0 : (unsigned int)year; + if (month) + contact.birthday->month = month < 0 ? 0 : (unsigned char)month; + if (day) + contact.birthday->day = day < 0 ? 0 : (unsigned char)day; + if (before_on_common >= 0) + contact.birthday->before_on_common = before_on_common; + } + if (libcontacts_save_contact(&contact, user)) { + weprintf("libcontacts_save_contact %s:", *argv); + ret = 1; + } + next: + libcontacts_contact_destroy(&contact); + } + } + + if (fflush(stdout) || ferror(stdout) || fclose(stdout)) + eprintf("printf:"); + return ret; +} diff --git a/singlecall.mk b/singlecall.mk new file mode 100644 index 0000000..1e429f4 --- /dev/null +++ b/singlecall.mk @@ -0,0 +1,10 @@ +all: $(BIN) + +install: contacts + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + cp -- contacts "$(DESTDIR)$(PREFIX)/lib/" + cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\ + for f in $(BIN); do\ + ln -s -- ../lib/contacts "$$f" || exit 1;\ + done -- cgit v1.2.3-70-g09d2