Index: src/backoffice.c
==================================================================
--- src/backoffice.c
+++ src/backoffice.c
@@ -262,11 +262,11 @@
getpid());
}
backoffice_work();
break;
}
- if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",1) ){
+ if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",0) ){
/* If the no-delay flag is set, exit immediately rather than queuing
** up. Assume that some future request will come along and handle any
** necessary backoffice work. */
db_end_transaction(0);
break;
Index: src/cgi.c
==================================================================
--- src/cgi.c
+++ src/cgi.c
@@ -1929,16 +1929,12 @@
fd = dup(connection);
if( fd!=0 ) nErr++;
close(1);
fd = dup(connection);
if( fd!=1 ) nErr++;
- if( 0 && !g.fAnyTrace ){
- close(2);
- fd = dup(connection);
- if( fd!=2 ) nErr++;
- }
close(connection);
+ for(fd=3; close(fd)==0; fd++){}
g.nPendingRequest = nchildren+1;
g.nRequest = nRequest+1;
return nErr;
}
}
Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -3027,11 +3027,11 @@
** If autosync is enabled setting this to a value greater
** than zero will cause autosync to try no more than this
** number of attempts if there is a sync failure.
*/
/*
-** SETTING: backoffice-nodelay boolean default=on
+** SETTING: backoffice-nodelay boolean default=off
** If backoffice-nodelay is true, then the backoffice processing
** will never invoke sleep(). If it has nothing useful to do,
** it simply exits.
*/
/*
Index: src/email.c
==================================================================
--- src/email.c
+++ src/email.c
@@ -77,12 +77,13 @@
@ -- Remaining characters determine the specific event. For example,
@ -- 'c4413' means check-in with rid=4413.
@ --
@ CREATE TABLE repository.pending_alert(
@ eventid TEXT PRIMARY KEY, -- Object that changed
-@ sentSep BOOLEAN DEFAULT false, -- individual emails sent
-@ sentDigest BOOLEAN DEFAULT false -- digest emails sent
+@ sentSep BOOLEAN DEFAULT false, -- individual alert sent
+@ sentDigest BOOLEAN DEFAULT false, -- digest alert sent
+@ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent
@ ) WITHOUT ROWID;
@
@ DROP TABLE IF EXISTS repository.email_bounce;
@ -- Record bounced emails. If too many bounces are received within
@ -- some defined time range, then cancel the subscription. Older
@@ -115,10 +116,15 @@
){
return; /* Don't create table for disabled email */
}
db_multi_exec(zEmailInit/*works-like:""*/);
email_triggers_enable();
+ }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
+ db_multi_exec(
+ "ALTER TABLE repository.pending_alert"
+ " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
+ );
}
}
/*
** Enable triggers that automatically populate the pending_alert
@@ -294,18 +300,10 @@
@
This is the email for the human administrator for the system.
@ Abuse and trouble reports are send here.
@ (Property: "email-admin")
Inbound emails can be stored in a directory for analysis as
- @ a debugging aid. Put the name of that directory in this entry box.
- @ Disable saving of inbound email by making this an empty string.
- @ Abuse and trouble reports are send here.
- @ (Property: "email-receive-dir")
- @
@
@
db_end_transaction(0);
style_footer();
}
@@ -757,24 +755,10 @@
fossil_print("%s", blob_str(&all));
}
blob_reset(&all);
}
-/*
-** Analyze and act on a received email.
-**
-** This routine takes ownership of the Blob parameter and is responsible
-** for freeing that blob when it is done with it.
-**
-** This routine acts on all email messages received from the
-** "fossil email inbound" command.
-*/
-void email_receive(Blob *pMsg){
- /* To Do: Look for bounce messages and possibly disable subscriptions */
- blob_reset(pMsg);
-}
-
/*
** SETTING: email-send-method width=5 default=off
** Determine the method used to send email. Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
** means no email is ever sent. The "relay" value means emails are sent
@@ -806,16 +790,10 @@
/*
** SETTING: email-self width=40
** This is the email address for the repository. Outbound emails add
** this email address as the "From:" field.
*/
-/*
-** SETTING: email-receive-dir width=40
-** Inbound email messages are saved as separate files in this directory,
-** for debugging analysis. Disable saving of inbound emails omitting
-** this setting, or making it an empty string.
-*/
/*
** SETTING: email-send-relayhost width=40
** This is the hostname and TCP port to which output email messages
** are sent when email-send-method is "relay". There should be an
** SMTP server configured as a Mail Submission Agent listening on the
@@ -822,80 +800,72 @@
** designated host and port and all times.
*/
/*
-** COMMAND: email
+** COMMAND: alerts
**
-** Usage: %fossil email SUBCOMMAND ARGS...
+** Usage: %fossil alerts SUBCOMMAND ARGS...
**
** Subcommands:
**
-** exec Compose and send pending email alerts.
+** pending Show all pending alerts. Useful for debugging.
+**
+** reset Hard reset of all email notification tables
+** in the repository. This erases all subscription
+** information. ** Use with extreme care **
+**
+** send Compose and send pending email alerts.
** Some installations may want to do this via
** a cron-job to make sure alerts are sent
** in a timely manner.
** Options:
**
** --digest Send digests
-** --test Resets to standard output
-**
-** inbound [FILE] Receive an inbound email message. This message
-** is analyzed to see if it is a bounce, and if
-** necessary, subscribers may be disabled.
-**
-** reset Hard reset of all email notification tables
-** in the repository. This erases all subscription
-** information. Use with extreme care.
-**
-** send TO [OPTIONS] Send a single email message using whatever
+** --test Write to standard output
+**
+** settings [NAME VALUE] With no arguments, list all email settings.
+** Or change the value of a single email setting.
+**
+** status Report on the status of the email alert
+** subsystem
+**
+** subscribers [PATTERN] List all subscribers matching PATTERN.
+**
+** test-message TO [OPTS] Send a single email message using whatever
** email sending mechanism is currently configured.
-** Use this for testing the email configuration.
-** Options:
+** Use this for testing the email notification
+** configuration. Options:
**
** --body FILENAME
** --smtp-trace
** --stdout
** --subject|-S SUBJECT
**
-** settings [NAME VALUE] With no arguments, list all email settings.
-** Or change the value of a single email setting.
-**
-** subscribers [PATTERN] List all subscribers matching PATTERN.
-**
** unsubscribe EMAIL Remove a single subscriber with the given EMAIL.
*/
void email_cmd(void){
const char *zCmd;
int nCmd;
db_find_and_open_repository(0, 0);
email_schema(0);
zCmd = g.argc>=3 ? g.argv[2] : "x";
nCmd = (int)strlen(zCmd);
- if( strncmp(zCmd, "exec", nCmd)==0 ){
- u32 eFlags = 0;
- if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST;
- if( find_option("test",0,0)!=0 ){
- eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT;
- }
- verify_all_options();
- email_send_alerts(eFlags);
- }else
- if( strncmp(zCmd, "inbound", nCmd)==0 ){
- Blob email;
- const char *zInboundDir = db_get("email-receive-dir","");
- verify_all_options();
- if( g.argc!=3 && g.argc!=4 ){
- usage("inbound [FILE]");
- }
- blob_read_from_file(&email, g.argc==3 ? "-" : g.argv[3], ExtFILE);
- if( zInboundDir[0] ){
- char *zFN = file_time_tempname(zInboundDir,".email");
- blob_write_to_file(&email, zFN);
- fossil_free(zFN);
- }
- email_receive(&email);
+ if( strncmp(zCmd, "pending", nCmd)==0 ){
+ Stmt q;
+ verify_all_options();
+ if( g.argc!=3 ) usage("pending");
+ db_prepare(&q,"SELECT eventid, sentSep, sentDigest, sentMod"
+ " FROM pending_alert");
+ while( db_step(&q)==SQLITE_ROW ){
+ fossil_print("%10s %7s %10s %7s\n",
+ db_column_text(&q,0),
+ db_column_int(&q,1) ? "sentSep" : "",
+ db_column_int(&q,2) ? "sentDigest" : "",
+ db_column_int(&q,3) ? "sentMod" : "");
+ }
+ db_finalize(&q);
}else
if( strncmp(zCmd, "reset", nCmd)==0 ){
int c;
int bForce = find_option("force","f",0)!=0;
verify_all_options();
@@ -923,10 +893,85 @@
);
email_schema(0);
}
}else
if( strncmp(zCmd, "send", nCmd)==0 ){
+ u32 eFlags = 0;
+ if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST;
+ if( find_option("test",0,0)!=0 ){
+ eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT;
+ }
+ verify_all_options();
+ email_send_alerts(eFlags);
+ }else
+ if( strncmp(zCmd, "settings", nCmd)==0 ){
+ int isGlobal = find_option("global",0,0)!=0;
+ int nSetting;
+ const Setting *pSetting = setting_info(&nSetting);
+ db_open_config(1, 0);
+ verify_all_options();
+ if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]");
+ if( g.argc==5 ){
+ const char *zLabel = g.argv[3];
+ if( strncmp(zLabel, "email-", 6)!=0
+ || (pSetting = db_find_setting(zLabel, 1))==0 ){
+ fossil_fatal("not a valid email setting: \"%s\"", zLabel);
+ }
+ db_set(pSetting->name, g.argv[4], isGlobal);
+ g.argc = 3;
+ }
+ pSetting = setting_info(&nSetting);
+ for(; nSetting>0; nSetting--, pSetting++ ){
+ if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
+ print_setting(pSetting);
+ }
+ }else
+ if( strncmp(zCmd, "status", nCmd)==0 ){
+ int nSetting, n;
+ static const char *zFmt = "%-29s %d\n";
+ const Setting *pSetting = setting_info(&nSetting);
+ db_open_config(1, 0);
+ verify_all_options();
+ if( g.argc!=3 ) usage("status");
+ pSetting = setting_info(&nSetting);
+ for(; nSetting>0; nSetting--, pSetting++ ){
+ if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
+ print_setting(pSetting);
+ }
+ n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep");
+ fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n);
+ n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest");
+ fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n);
+ n = db_int(0,"SELECT count(*) FROM subscriber");
+ fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n);
+ n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified"
+ " AND NOT sdonotcall AND length(ssub)>1");
+ fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n);
+ }else
+ if( strncmp(zCmd, "subscribers", nCmd)==0 ){
+ Stmt q;
+ verify_all_options();
+ if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]");
+ if( g.argc==4 ){
+ char *zPattern = g.argv[3];
+ db_prepare(&q,
+ "SELECT semail FROM subscriber"
+ " WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'"
+ " OR semail GLOB '*%q*' or suname GLOB '*%q*'"
+ " ORDER BY semail",
+ zPattern, zPattern, zPattern, zPattern);
+ }else{
+ db_prepare(&q,
+ "SELECT semail FROM subscriber"
+ " ORDER BY semail");
+ }
+ while( db_step(&q)==SQLITE_ROW ){
+ fossil_print("%s\n", db_column_text(&q, 0));
+ }
+ db_finalize(&q);
+ }else
+ if( strncmp(zCmd, "test-message", nCmd)==0 ){
Blob prompt, body, hdr;
const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
int i;
u32 mFlags = EMAIL_IMMEDIATE_FAIL;
const char *zSubject = find_option("subject", "S", 1);
@@ -957,62 +1002,19 @@
email_sender_free(pSender);
blob_reset(&hdr);
blob_reset(&body);
blob_reset(&prompt);
}else
- if( strncmp(zCmd, "settings", nCmd)==0 ){
- int isGlobal = find_option("global",0,0)!=0;
- int nSetting;
- const Setting *pSetting = setting_info(&nSetting);
- db_open_config(1, 0);
- verify_all_options();
- if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]");
- if( g.argc==5 ){
- const char *zLabel = g.argv[3];
- if( strncmp(zLabel, "email-", 6)!=0
- || (pSetting = db_find_setting(zLabel, 1))==0 ){
- fossil_fatal("not a valid email setting: \"%s\"", zLabel);
- }
- db_set(pSetting->name, g.argv[4], isGlobal);
- g.argc = 3;
- }
- pSetting = setting_info(&nSetting);
- for(; nSetting>0; nSetting--, pSetting++ ){
- if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
- print_setting(pSetting);
- }
- }else
- if( strncmp(zCmd, "subscribers", nCmd)==0 ){
- Stmt q;
- verify_all_options();
- if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]");
- if( g.argc==4 ){
- char *zPattern = g.argv[3];
- db_prepare(&q,
- "SELECT semail FROM subscriber"
- " WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'"
- " OR semail GLOB '*%q*' or suname GLOB '*%q*'"
- " ORDER BY semail",
- zPattern, zPattern, zPattern, zPattern);
- }else{
- db_prepare(&q,
- "SELECT semail FROM subscriber"
- " ORDER BY semail");
- }
- while( db_step(&q)==SQLITE_ROW ){
- fossil_print("%s\n", db_column_text(&q, 0));
- }
- db_finalize(&q);
- }else
if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){
verify_all_options();
if( g.argc!=4 ) usage("unsubscribe EMAIL");
db_multi_exec(
"DELETE FROM subscriber WHERE semail=%Q", g.argv[3]);
}else
{
- usage("exec|inbound|reset|send|setting|subscribers|unsubscribe");
+ usage("pending|reset|send|setting|status|"
+ "subscribers|test-message|unsubscribe");
}
}
/*
** Do error checking on a submitted subscription form. Return TRUE
@@ -1789,11 +1791,12 @@
/*
** A single event that might appear in an alert is recorded as an
** instance of the following object.
*/
struct EmailEvent {
- int type; /* 'c', 't', 'w', 'f' */
+ int type; /* 'c', 'f', 'm', 't', 'w' */
+ int needMod; /* Pending moderator approval */
Blob txt; /* Text description to appear in an alert */
EmailEvent *pNext; /* Next in chronological order */
};
#endif
@@ -1812,33 +1815,34 @@
/*
** Compute and return a linked list of EmailEvent objects
** corresponding to the current content of the temp.wantalert
** table which should be defined as follows:
**
-** CREATE TEMP TABLE wantalert(eventId TEXT);
+** CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN);
*/
-EmailEvent *email_compute_event_text(int *pnEvent){
+EmailEvent *email_compute_event_text(int *pnEvent, int doDigest){
Stmt q;
EmailEvent *p;
EmailEvent anchor;
EmailEvent *pLast;
const char *zUrl = db_get("email-url","http://localhost:8080");
db_prepare(&q,
"SELECT"
- " blob.uuid," /* 0 */
- " datetime(event.mtime)," /* 1 */
+ " blob.uuid," /* 0 */
+ " datetime(event.mtime)," /* 1 */
" coalesce(ecomment,comment)"
" || ' (user: ' || coalesce(euser,user,'?')"
" || (SELECT case when length(x)>0 then ' tags: ' || x else '' end"
" FROM (SELECT group_concat(substr(tagname,5), ', ') AS x"
" FROM tag, tagxref"
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
" AND tagxref.rid=blob.rid AND tagxref.tagtype>0))"
- " || ')' as comment," /* 2 */
+ " || ')' as comment," /* 2 */
" tagxref.value AS branch," /* 3 */
- " wantalert.eventId" /* 4 */
+ " wantalert.eventId," /* 4 */
+ " wantalert.needMod" /* 5 */
" FROM temp.wantalert JOIN tag CROSS JOIN event CROSS JOIN blob"
" LEFT JOIN tagxref ON tagxref.tagid=tag.tagid"
" AND tagxref.tagtype>0"
" AND tagxref.rid=blob.rid"
" WHERE blob.rid=event.objid"
@@ -1853,13 +1857,15 @@
const char *zType = "";
p = fossil_malloc( sizeof(EmailEvent) );
pLast->pNext = p;
pLast = p;
p->type = db_column_text(&q, 4)[0];
+ p->needMod = db_column_int(&q, 5);
p->pNext = 0;
switch( p->type ){
case 'c': zType = "Check-In"; break;
+ case 'f': zType = "Forum post"; break;
case 't': zType = "Wiki Edit"; break;
case 'w': zType = "Ticket Change"; break;
}
blob_init(&p->txt, 0, 0);
blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
@@ -1867,10 +1873,16 @@
zType,
db_column_text(&q,2),
zUrl,
db_column_text(&q,0)
);
+ if( p->needMod ){
+ blob_appendf(&p->txt,
+ "** Pending moderator approval (%s/modreq) **\n",
+ zUrl
+ );
+ }
(*pnEvent)++;
}
db_finalize(&q);
return anchor.pNext;
}
@@ -1905,32 +1917,44 @@
** command line, generate text for all events named in the
** pending_alert table.
**
** This command is intended for testing and debugging the logic
** that generates email alert text.
+**
+** Options:
+**
+** --digest Generate digest alert text
+** --needmod Assume all events are pending moderator approval
*/
void test_alert_cmd(void){
Blob out;
int nEvent;
+ int needMod;
+ int doDigest;
EmailEvent *pEvent, *p;
+ doDigest = find_option("digest",0,0)!=0;
+ needMod = find_option("needmod",0,0)!=0;
db_find_and_open_repository(0, 0);
verify_all_options();
db_begin_transaction();
email_schema(0);
- db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT)");
+ db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT, needMod BOOLEAN)");
if( g.argc==2 ){
- db_multi_exec("INSERT INTO wantalert SELECT eventid FROM pending_alert");
+ db_multi_exec(
+ "INSERT INTO wantalert(eventId,needMod)"
+ " SELECT eventid, %d FROM pending_alert", needMod);
}else{
int i;
for(i=2; ipNext){
blob_append(&out, "\n", 1);
blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt));
}
email_free_eventlist(pEvent);
@@ -1947,12 +1971,12 @@
**
** Add one or more events to the pending_alert queue. Use this
** command during testing to force email notifications for specific
** events.
**
-** EVENTIDs are text. The first character is 'c', 'w', or 't'
-** for check-in, wiki, or ticket. The remaining text is a
+** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w'
+** for check-in, forum, ticket, or wiki. The remaining text is a
** integer that references the EVENT.OBJID value for the event.
** Run /timeline?showid to see these OBJID values.
**
** If the --backoffice option is included, then email_backoffice() is run
** after all alerts have been added. This will cause the alerts to
@@ -1984,11 +2008,35 @@
#define SENDALERT_TRACE 0x0008 /* Trace operation for debugging */
#endif /* INTERFACE */
/*
-** Send alert emails to all subscribers.
+** Send alert emails to subscribers.
+**
+** This procedure is run by either the backoffice, or in response to the
+** "fossil alerts send" command. Details of operation are controlled by
+** the flags parameter.
+**
+** Here is a summary of what happens:
+**
+** (1) Create a TEMP table wantalert(eventId,needMod) and fill it with
+** all the events that we want to send alerts about. The needMod
+** flags is set if and only if the event is still awaiting
+** moderator approval. Events with the needMod flag are only
+** shown to users that have moderator privileges.
+**
+** (2) Call email_compute_event_text() to compute a list of EmailEvent
+** objects that describe all events about which we want to send
+** alerts.
+**
+** (3) Loop over all subscribers. Compose and send one or more email
+** messages to each subscriber that describe the events for
+** which the subscriber has expressed interest and has
+** appropriate privileges.
+**
+** (4) Update the pending_alerts table to indicate that alerts have been
+** sent.
*/
void email_send_alerts(u32 flags){
EmailEvent *pEvents, *p;
int nEvent = 0;
Stmt q;
@@ -2001,10 +2049,11 @@
EmailSender *pSender = 0;
u32 senderFlags = 0;
if( g.fSqlTrace ) fossil_trace("-- BEGIN email_send_alerts(%u)\n", flags);
db_begin_transaction();
+ email_schema(0);
if( !email_enabled() ) goto send_alerts_done;
zUrl = db_get("email-url",0);
if( zUrl==0 ) goto send_alerts_done;
zRepoName = db_get("email-subname",0);
if( zRepoName==0 ) goto send_alerts_done;
@@ -2014,25 +2063,36 @@
senderFlags |= EMAIL_TRACE;
}
pSender = email_sender_new(zDest, senderFlags);
db_multi_exec(
"DROP TABLE IF EXISTS temp.wantalert;"
- "CREATE TEMP TABLE wantalert(eventId TEXT);"
+ "CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN, sentMod);"
);
if( flags & SENDALERT_DIGEST ){
+ /* Unmoderated changes are never sent as part of a digest */
db_multi_exec(
- "INSERT INTO wantalert SELECT eventid FROM pending_alert"
+ "INSERT INTO wantalert(eventId,needMod)"
+ " SELECT eventid, 0"
+ " FROM pending_alert"
" WHERE sentDigest IS FALSE"
+ " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2));"
);
zDigest = "true";
}else{
+ /* Immediate alerts might include events that are subject to
+ ** moderator approval */
db_multi_exec(
- "INSERT INTO wantalert SELECT eventid FROM pending_alert"
- " WHERE sentSep IS FALSE"
+ "INSERT INTO wantalert(eventId,needMod,sentMod)"
+ " SELECT eventid,"
+ " EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2)),"
+ " sentMod"
+ " FROM pending_alert"
+ " WHERE sentSep IS FALSE;"
+ "DELETE FROM wantalert WHERE needMod AND sentMod;"
);
}
- pEvents = email_compute_event_text(&nEvent);
+ pEvents = email_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0);
if( nEvent==0 ) goto send_alerts_done;
blob_init(&hdr, 0, 0);
blob_init(&body, 0, 0);
db_prepare(&q,
"SELECT"
@@ -2051,13 +2111,26 @@
const char *zEmail = db_column_text(&q, 1);
const char *zCap = db_column_text(&q, 3);
int nHit = 0;
for(p=pEvents; p; p=p->pNext){
if( strchr(zSub,p->type)==0 ) continue;
- if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
- /* Setup and admin users can get any notification */
+ if( p->needMod ){
+ /* For events that require moderator approval, only send an alert
+ ** if the recipient is a moderator for that type of event */
+ char xType = '*';
+ switch( p->type ){
+ case 'f': xType = '5'; break;
+ case 't': xType = 'q'; break;
+ case 'w': xType = 'l'; break;
+ }
+ if( strchr(zCap,xType)==0 ) continue;
+ }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
+ /* Setup and admin users can get any notification that does not
+ ** require moderation */
}else{
+ /* Other users only see the alert if they have sufficient
+ ** privilege to view the event itself */
char xType = '*';
switch( p->type ){
case 'c': xType = 'o'; break;
case 'f': xType = '2'; break;
case 't': xType = 'r'; break;
@@ -2089,15 +2162,24 @@
blob_reset(&body);
db_finalize(&q);
email_free_eventlist(pEvents);
if( (flags & SENDALERT_PRESERVE)==0 ){
if( flags & SENDALERT_DIGEST ){
- db_multi_exec("UPDATE pending_alert SET sentDigest=true");
+ db_multi_exec(
+ "UPDATE pending_alert SET sentDigest=true"
+ " WHERE eventid IN (SELECT eventid FROM wantalert);"
+ "DELETE FROM pending_alert WHERE sentDigest AND sentSep;"
+ );
}else{
- db_multi_exec("UPDATE pending_alert SET sentSep=true");
+ db_multi_exec(
+ "UPDATE pending_alert SET sentSep=true"
+ " WHERE eventid IN (SELECT eventid FROM wantalert WHERE NOT needMod);"
+ "UPDATE pending_alert SET sentMod=true"
+ " WHERE eventid IN (SELECT eventid FROM wantalert WHERE needMod);"
+ "DELETE FROM pending_alert WHERE sentDigest AND sentSep;"
+ );
}
- db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep");
}
send_alerts_done:
email_sender_free(pSender);
if( g.fSqlTrace ) fossil_trace("-- END email_send_alerts(%u)\n", flags);
db_end_transaction(0);
@@ -2131,11 +2213,11 @@
** A web-form to send an email message to the repository administrator,
** or (with appropriate permissions) to anybody.
*/
void contact_admin_page(void){
const char *zAdminEmail = db_get("email-admin",0);
- unsigned int uSeed;
+ unsigned int uSeed = 0;
const char *zDecoded;
char *zCaptcha = 0;
login_check_credentials();
if( zAdminEmail==0 || zAdminEmail[0]==0 ){
Index: src/json_timeline.c
==================================================================
--- src/json_timeline.c
+++ src/json_timeline.c
@@ -35,10 +35,11 @@
that we end up with HTTP clients using 3 different names
for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},
+{"event", json_timeline_event, 0},
{"ticket", json_timeline_ticket, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
@@ -519,10 +520,66 @@
payV = NULL;
ok:
db_finalize(&q);
return payV;
}
+
+/*
+** Implementation of /json/timeline/event.
+**
+*/
+cson_value * json_timeline_event(){
+ /* This code is 95% the same as json_timeline_ci(), by the way. */
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_array * list = NULL;
+ int check = 0;
+ Stmt q = empty_Stmt;
+ Blob sql = empty_blob;
+ if( !g.perm.RdWiki ){
+ json_set_err( FSL_JSON_E_DENIED, "Event timeline requires 'j' access.");
+ return NULL;
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ check = json_timeline_setup_sql( "e", &sql, pay );
+ if(check){
+ json_set_err(check, "Query initialization failed.");
+ goto error;
+ }
+
+#if 0
+ /* only for testing! */
+ cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))));
+#endif
+ db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/);
+ blob_reset(&sql);
+ db_prepare(&q, "SELECT"
+ /* For events, the name is generally more useful than
+ the uuid, but the uuid is unambiguous and can be used
+ with commands like 'artifact'. */
+ " substr((SELECT tagname FROM tag AS tn WHERE tn.tagid=json_timeline.tagId AND tagname LIKE 'event-%%'),7) AS name,"
+ " uuid as uuid,"
+ " mtime AS timestamp,"
+ " comment AS comment, "
+ " user AS user,"
+ " eventType AS eventType"
+ " FROM json_timeline"
+ " ORDER BY rowid");
+ list = cson_new_array();
+ json_stmt_to_array_of_obj(&q, list);
+ cson_object_set(pay, "timeline", cson_array_value(list));
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ db_finalize(&q);
+ blob_reset(&sql);
+ return payV;
+}
/*
** Implementation of /json/timeline/wiki.
**
*/
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -1838,41 +1838,43 @@
if( g.fCgiTrace ){
fossil_trace("######## Calling %s #########\n", pCmd->zName);
cgi_print_all(1, 1);
}
#ifdef FOSSIL_ENABLE_TH1_HOOKS
- /*
- ** The TH1 return codes from the hook will be handled as follows:
- **
- ** TH_OK: The xFunc() and the TH1 notification will both be executed.
- **
- ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
- ** skipped. If the xFunc() is being hooked, the error message
- ** will be emitted.
- **
- ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
- **
- ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
- ** skipped.
- **
- ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
- ** executed.
- */
- int rc;
- if( !g.fNoThHook ){
- rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
- }else{
- rc = TH_OK;
- }
- if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
- if( rc==TH_OK || rc==TH_RETURN ){
+ {
+ /*
+ ** The TH1 return codes from the hook will be handled as follows:
+ **
+ ** TH_OK: The xFunc() and the TH1 notification will both be executed.
+ **
+ ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
+ ** skipped. If the xFunc() is being hooked, the error message
+ ** will be emitted.
+ **
+ ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
+ **
+ ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
+ ** skipped.
+ **
+ ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
+ ** executed.
+ */
+ int rc;
+ if( !g.fNoThHook ){
+ rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
+ }else{
+ rc = TH_OK;
+ }
+ if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
+ if( rc==TH_OK || rc==TH_RETURN ){
#endif
- pCmd->xFunc();
+ pCmd->xFunc();
#ifdef FOSSIL_ENABLE_TH1_HOOKS
- }
- if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
- Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
+ }
+ if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
+ Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
+ }
}
}
#endif
}
@@ -2019,10 +2021,15 @@
}
g.httpOut = stdout;
g.httpIn = stdin;
fossil_binary_mode(g.httpOut);
fossil_binary_mode(g.httpIn);
+#if !defined(_WIN32)
+ /* Work around a bug in older versions of althttpd by making sure no
+ ** file descriptors other than 0, 1, and 2 are open. */
+ { int i; for(i=3; close(i)==0 || i<6; i++){} }
+#endif
g.cgiOutput = 1;
blob_read_from_file(&config, zFile, ExtFILE);
while( blob_line(&config, &line) ){
if( !blob_token(&line, &key) ) continue;
if( blob_buffer(&key)[0]=='#' ) continue;
Index: src/makemake.tcl
==================================================================
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -1806,10 +1806,11 @@
foreach s [lsort $extra_files] {
if {$i > 0} {
writeln " \\"
writeln -nonewline " "
}
+ set s [file nativename $s]
writeln -nonewline "\$(SRCDIR)\\${s}"; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation]
writeln -nonewline "OBJ = "
Index: src/translate.c
==================================================================
--- src/translate.c
+++ src/translate.c
@@ -203,37 +203,44 @@
fprintf(out,"\n%*s\"%s%s\"",indent+5, "", zOut, zNewline);
}
}
}
}
+
+static void print_source_ref(const char *zSrcFile, FILE *out){
+/* Set source line reference to the original source file.
+ * This makes compiler show the original file name in the compile error
+ * messages, instead of referring to the translated file.
+ * NOTE: This somewhat complicates stepping in debugger, as the resuling
+ * code would not match the referenced sources.
+ */
+#ifndef FOSSIL_DEBUG
+ const char *arg;
+ if( !*zSrcFile ){
+ return;
+ }
+ fprintf(out,"#line 1 \"");
+ for(arg=zSrcFile; *arg; arg++){
+ if( *arg!='\\' ){
+ fprintf(out,"%c", *arg);
+ }else{
+ fprintf(out,"\\\\");
+ }
+ }
+ fprintf(out,"\"\n");
+#endif
+}
int main(int argc, char **argv){
if( argc==2 ){
- char *arg;
FILE *in = fopen(argv[1], "r");
if( in==0 ){
fprintf(stderr,"can not open %s\n", argv[1]);
exit(1);
}
zInFile = argv[1];
-#ifndef FOSSIL_DEBUG
- /* Set source line reference to the original source file.
- * This makes compiler show the original file name in the compile error
- * messages, instead of referring to the translated file.
- * NOTE: This somewhat complicates stepping in debugger, as the resuling
- * code would not match the referenced sources.
- */
- printf("#line 1 \"");
- for(arg=argv[1]; *arg; arg++){
- if( *arg!='\\' ){
- printf("%c", *arg);
- }else{
- printf("\\\\");
- }
- }
- printf("\"\n");
-#endif
+ print_source_ref(zInFile, stdout);
trans(in, stdout);
fclose(in);
}else{
trans(stdin, stdout);
}
Index: src/webmail.c
==================================================================
--- src/webmail.c
+++ src/webmail.c
@@ -708,11 +708,11 @@
@