From e0fde984100fa128e78fcca019e9b480430e4ddd Mon Sep 17 00:00:00 2001 From: Dianne Skoll Date: Tue, 12 Nov 2024 09:26:31 -0500 Subject: [PATCH] Add $DedupeReminders global variable. --- man/remind.1.in | 20 ++++++ src/Makefile.in | 7 +- src/calendar.c | 13 +++- src/dedupe.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++ src/dorem.c | 8 +++ src/globals.h | 1 + src/init.c | 2 + src/main.c | 1 + src/protos.h | 5 ++ src/var.c | 1 + tests/dedupe.rem | 25 +++++++ tests/test-rem | 4 ++ tests/test.cmp | 90 +++++++++++++++++++++++- 13 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 src/dedupe.c create mode 100644 tests/dedupe.rem diff --git a/man/remind.1.in b/man/remind.1.in index 55848eca..8a2b37a6 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -2520,6 +2520,26 @@ This variable can be set only to "/" or "-". It holds the character used to separate portions of a date when \fBRemind\fR prints a DATE or DATETIME value. .TP +.B $DedupeReminders +If this variable is set to 1, then Remind will suppress duplicate +reminders. A given reminder is considered to be a duplicate of a +previous one if it has the \fIexact\fR same trigger date, trigger +time, and body. By default, this variable is set to 0 and Remind does +not suppress duplicate reminders. +.RS +.PP +As an example, consider the following reminder file: +.nf + SET $DedupeReminders 1 + REM Wednesday MSG Phooey + REM 20 MSG Phooey +.fi +.PP +On Wednesday, 20 November 2024, only \fIone\fR "Phooey" will be issued. +In December of 2024, "Phooey" will be issued every Wednesday as well +as on Friday, 20 December 2024 +.RE +.TP .B $DefaultColor This variable can be set to a string that has the form of three space-separated numbers. Each number must be an integer from 0 to diff --git a/src/Makefile.in b/src/Makefile.in index f007a6c7..5acf2fc4 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -26,9 +26,10 @@ MANS= $(srcdir)/../man/rem2ps.1 $(srcdir)/../man/remind.1 \ .SUFFIXES: .SUFFIXES: .c .o -REMINDSRCS= calendar.c dynbuf.c dorem.c dosubst.c expr.c files.c funcs.c \ - globals.c hbcal.c init.c main.c md5.c moon.c omit.c queue.c \ - sort.c token.c trigger.c userfns.c utils.c var.c +REMINDSRCS= calendar.c dedupe.c dynbuf.c dorem.c dosubst.c expr.c \ + files.c funcs.c globals.c hbcal.c init.c main.c md5.c \ + moon.c omit.c queue.c sort.c token.c trigger.c \ + userfns.c utils.c var.c REMINDHDRS=config.h custom.h dynbuf.h err.h globals.h lang.h \ md5.h protos.h rem2ps.h types.h version.h diff --git a/src/calendar.c b/src/calendar.c index d06d687b..675ce7e0 100644 --- a/src/calendar.c +++ b/src/calendar.c @@ -2054,8 +2054,6 @@ static int DoCalRem(ParsePtr p, int col) if ((dse == DSEToday) || (DoSimpleCalDelta && ShouldTriggerReminder(&trig, &tim, dse, &err))) { - NumTriggered++; - /* The parse_ptr should not be nested, but just in case... */ if (!p->isnested) { if (DBufPuts(&raw_buf, p->pos) != OK) { @@ -2152,9 +2150,20 @@ static int DoCalRem(ParsePtr p, int col) } } s = DBufValue(&obuf); + if (DedupeReminders) { + if (ShouldDedupe(dse, tim.ttime, DBufValue(&obuf))) { + DBufFree(&obuf); + DBufFree(&raw_buf); + DBufFree(&pre_buf); + FreeTrig(&trig); + return OK; + } + } + if (!DoSimpleCalendar) while (isempty(*s)) s++; DBufPuts(&pre_buf, s); s = DBufValue(&pre_buf); + NumTriggered++; e = NEW(CalEntry); if (!e) { DBufFree(&obuf); diff --git a/src/dedupe.c b/src/dedupe.c new file mode 100644 index 00000000..3e145668 --- /dev/null +++ b/src/dedupe.c @@ -0,0 +1,175 @@ +/***************************************************************/ +/* */ +/* DEDUPE.C */ +/* */ +/* Code to suppress duplicate reminders */ +/* */ +/* This file is part of REMIND. */ +/* Copyright (C) 1992-2024 by Dianne Skoll */ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* */ +/***************************************************************/ + +#include "config.h" +#include "types.h" +#include "globals.h" +#include "protos.h" +#include +#include + +#define DEDUPE_HASH_SLOTS 64 +typedef struct dedupe_entry { + struct dedupe_entry *next; + int trigger_date; + int trigger_time; + char const *body; +} DedupeEntry; + +static DedupeEntry *DedupeTable[DEDUPE_HASH_SLOTS]; + +/***************************************************************/ +/* */ +/* FreeDedupeEntry */ +/* */ +/* Free a DedupeEntry object */ +/* */ +/***************************************************************/ +static void +FreeDedupeEntry(DedupeEntry *e) +{ + if (e->body) { + free((char *) e->body); + } + free(e); +} + +/***************************************************************/ +/* */ +/* GetDedupeBucket */ +/* */ +/* Get the bucket for a given date and body */ +/* */ +/***************************************************************/ +static unsigned int +GetDedupeBucket(int trigger_date, int trigger_time, char const *body) +{ + unsigned int bucket = trigger_date; + if (trigger_time != NO_TIME) { + bucket += trigger_time; + } + bucket += HashVal(body); + return bucket % DEDUPE_HASH_SLOTS; +} + +/***************************************************************/ +/* */ +/* FindDedupeEntry */ +/* */ +/* Check if we have a dedupe entry for a given date and body */ +/* */ +/***************************************************************/ +static DedupeEntry * +FindDedupeEntry(int trigger_date, int trigger_time, char const *body) +{ + DedupeEntry *e; + + unsigned int bucket = GetDedupeBucket(trigger_date, trigger_time, body); + + e = DedupeTable[bucket]; + while(e) { + if (e->trigger_date == trigger_date && + e->trigger_time == trigger_time && + !strcmp(body, e->body)) { + return e; + } + e = e->next; + } + return NULL; +} + +/***************************************************************/ +/* */ +/* InsertDedupeEntry */ +/* */ +/* Insert a dedupe entry for given date and body. */ +/* */ +/***************************************************************/ +static void +InsertDedupeEntry(int trigger_date, int trigger_time, char const *body) +{ + DedupeEntry *e; + + unsigned int bucket = GetDedupeBucket(trigger_date, trigger_time, body); + + e = malloc(sizeof(DedupeEntry)); + if (!e) { + return; /* No error checking... what can we do? */ + } + e->trigger_date = trigger_date; + e->trigger_time = trigger_time; + e->body = strdup(body); + if (!e->body) { + free(e); + return; + } + + e->next = DedupeTable[bucket]; + DedupeTable[bucket] = e; +} + +/***************************************************************/ +/* */ +/* ShouldDedupe */ +/* */ +/* Returns 1 if we've already issued this exact reminder; 0 */ +/* otherwise. If it returns 0, remembers that we have seen */ +/* the reminder */ +/* */ +/***************************************************************/ +int +ShouldDedupe(int trigger_date, int trigger_time, char const *body) +{ + DedupeEntry *e = FindDedupeEntry(trigger_date, trigger_time, body); + + if (e) { + return 1; + } + InsertDedupeEntry(trigger_date, trigger_time, body); + return 0; +} + +/***************************************************************/ +/* */ +/* ClearDedupeTable */ +/* */ +/* Free all the storage used by the dedupe table */ +/* */ +/***************************************************************/ +void +ClearDedupeTable(void) +{ + DedupeEntry *e, *next; + for (int i=0; inext; + FreeDedupeEntry(e); + e = next; + } + DedupeTable[i] = NULL; + } +} +/***************************************************************/ +/* */ +/* InitDedupeTable */ +/* */ +/* Initialize the dedupe table at program startup */ +/* */ +/***************************************************************/ +void +InitDedupeTable(void) +{ + for (int i=0; ittime, DBufValue(&buf))) { + DBufFree(&buf); + return OK; + } + } + /* If we are sorting, just queue it up in the sort buffer */ if (SortByDate) { if (InsertIntoSortBuffer(dse, tim->ttime, DBufValue(&buf), diff --git a/src/globals.h b/src/globals.h index f056af73..7cca86c3 100644 --- a/src/globals.h +++ b/src/globals.h @@ -139,6 +139,7 @@ EXTERN INIT( int UseVTColors, 0); EXTERN INIT( int Use256Colors, 0); EXTERN INIT( int UseTrueColors, 0); EXTERN INIT( int TerminalBackground, TERMINAL_BACKGROUND_UNKNOWN); +EXTERN INIT( int DedupeReminders, 0); /* Latitude and longitude */ EXTERN INIT( int LatDeg, 0); diff --git a/src/init.c b/src/init.c index 4c035c56..19be7126 100644 --- a/src/init.c +++ b/src/init.c @@ -192,6 +192,8 @@ void InitRemind(int argc, char const *argv[]) PurgeFP = NULL; + InitDedupeTable(); + /* Make sure remind is not installed set-uid or set-gid */ if (getgid() != getegid() || getuid() != geteuid()) { diff --git a/src/main.c b/src/main.c index d0848a08..302f542d 100644 --- a/src/main.c +++ b/src/main.c @@ -226,6 +226,7 @@ PerIterationInit(void) DefaultColorB = -1; NumTriggered = 0; ClearLastTriggers(); + ClearDedupeTable(); } /***************************************************************/ diff --git a/src/protos.h b/src/protos.h index e1dec50c..4fe57bcd 100644 --- a/src/protos.h +++ b/src/protos.h @@ -249,3 +249,8 @@ void print_builtinfunc_tokens(void); void print_remind_tokens(void); void get_var_hash_stats(int *total, int *maxlen, double *avglen); void get_userfunc_hash_stats(int *total, int *maxlen, double *avglen); + +/* Dedupe code */ +int ShouldDedupe(int trigger_date, int trigger_time, char const *body); +void ClearDedupeTable(void); +void InitDedupeTable(void); diff --git a/src/var.c b/src/var.c index 0390d338..b40e5419 100644 --- a/src/var.c +++ b/src/var.c @@ -864,6 +864,7 @@ static SysVar SysVarArr[] = { {"DateSep", 1, SPECIAL_TYPE, date_sep_func, 0, 0 }, {"DateTimeSep", 1, SPECIAL_TYPE, datetime_sep_func, 0, 0 }, {"December", 1, STR_TYPE, &DynamicMonthName[11],0, 0 }, + {"DedupeReminders",1, INT_TYPE, &DedupeReminders, 0, 1 }, {"DefaultColor", 1, SPECIAL_TYPE, default_color_func, 0, 0 }, {"DefaultPrio", 1, INT_TYPE, &DefaultPrio, 0, 9999 }, {"DefaultTDelta", 1, INT_TYPE, &DefaultTDelta, 0, 1440 }, diff --git a/tests/dedupe.rem b/tests/dedupe.rem new file mode 100644 index 00000000..85d563fb --- /dev/null +++ b/tests/dedupe.rem @@ -0,0 +1,25 @@ +SET $DedupeReminders 1 +SET $SuppressLRM 1 +REM Wednesday MSG foo +FLUSH +REM 8 MSG foo +FLUSH +REM Wednesday MSG Bar +FLUSH +REM Wednesday MSG Bar +FLUSH +REM Wednesday AT 23:59 +1440 MSG with_time +FLUSH +REM AT 23:59 +1440 MSG with_time +FLUSH +REM AT 23:58 +1440 MSG with_time +FLUSH + +REM 8 RUN echo %"foo%" +FLUSH +REM 8 RUN echo %"foo%" +FLUSH +REM Wed RUN echo %"foo%" +FLUSH +REM Wed RUN echo %"bar%" +FLUSH diff --git a/tests/test-rem b/tests/test-rem index 650eb689..20a06539 100644 --- a/tests/test-rem +++ b/tests/test-rem @@ -586,6 +586,10 @@ rm -f ../tests/once.timestamp # Newlines in calendar output (echo 'REM 16 MSG foo%_bar%_baz wookie quux apple %_ %_ %_ blech'; echo "REM 16 MSG ANOTHER") | ../src/remind -c -w80 - 1 sep 1990 >> ../tests/test.out 2>&1 +# Dedupe feature +../src/remind -c ../tests/dedupe.rem 1 November 2023 >> ../tests/test.out 2>&1 +../src/remind -q ../tests/dedupe.rem 8 November 2023 >> ../tests/test.out 2>&1 + # Remove references to SysInclude, which is build-specific grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out diff --git a/tests/test.cmp b/tests/test.cmp index c2358cf9..75697408 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -2729,6 +2729,7 @@ Variable Value $DateSep "-" $DateTimeSep "@" $December "December" + $DedupeReminders 0 [0, 1] $DefaultColor "-1 -1 -1" $DefaultPrio 5000 [0, 9999] $DefaultTDelta 0 [0, 1440] @@ -13377,7 +13378,93 @@ No reminders. | | | | | | | | | | | | | | | | +----------+----------+----------+----------+----------+----------+----------+ - + +----------------------------------------------------------------------------+ +| November 2023 | ++----------+----------+----------+----------+----------+----------+----------+ +| Sunday | Monday | Tuesday |Wednesday | Thursday | Friday | Saturday | ++----------+----------+----------+----------+----------+----------+----------+ +| | | |1 |2 |3 |4 | +| | | | | | | | +| | | |11:58pm |11:58pm |11:58pm |11:58pm | +| | | |with_time |with_time |with_time |with_time | +| | | | | | | | +| | | |11:59pm |11:59pm |11:59pm |11:59pm | +| | | |with_time |with_time |with_time |with_time | +| | | | | | | | +| | | |foo | | | | +| | | | | | | | +| | | |Bar | | | | +| | | | | | | | +| | | |bar | | | | ++----------+----------+----------+----------+----------+----------+----------+ +|5 |6 |7 |8 |9 |10 |11 | +| | | | | | | | +|11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +|11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +| | | |foo | | | | +| | | | | | | | +| | | |Bar | | | | +| | | | | | | | +| | | |bar | | | | ++----------+----------+----------+----------+----------+----------+----------+ +|12 |13 |14 |15 |16 |17 |18 | +| | | | | | | | +|11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +|11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +| | | |foo | | | | +| | | | | | | | +| | | |Bar | | | | +| | | | | | | | +| | | |bar | | | | ++----------+----------+----------+----------+----------+----------+----------+ +|19 |20 |21 |22 |23 |24 |25 | +| | | | | | | | +|11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm |11:58pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +|11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm |11:59pm | +|with_time |with_time |with_time |with_time |with_time |with_time |with_time | +| | | | | | | | +| | | |foo | | | | +| | | | | | | | +| | | |Bar | | | | +| | | | | | | | +| | | |bar | | | | ++----------+----------+----------+----------+----------+----------+----------+ +|26 |27 |28 |29 |30 | | | +| | | | | | | | +|11:58pm |11:58pm |11:58pm |11:58pm |11:58pm | | | +|with_time |with_time |with_time |with_time |with_time | | | +| | | | | | | | +|11:59pm |11:59pm |11:59pm |11:59pm |11:59pm | | | +|with_time |with_time |with_time |with_time |with_time | | | +| | | | | | | | +| | | |foo | | | | +| | | | | | | | +| | | |Bar | | | | +| | | | | | | | +| | | |bar | | | | ++----------+----------+----------+----------+----------+----------+----------+ + Reminders for Wednesday, 8th November, 2023: + +foo + +Bar + +with_time + +with_time + +foo +bar # Remind Tokens addomit @@ -13623,6 +13710,7 @@ $Daemon $DateSep $DateTimeSep $December +$DedupeReminders $DefaultColor $DefaultPrio $DefaultTDelta