RSS: some more debug logging.
[citadel.git] / citadel / modules / rssclient / serv_rssclient.c
1 /*
2  * Bring external RSS feeds into rooms.
3  *
4  * Copyright (c) 2007-2012 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12  * GNU General Public License for more details.
13  */
14
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <stdio.h>
18
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
21 # include <time.h>
22 #else
23 # if HAVE_SYS_TIME_H
24 #include <sys/time.h>
25 # else
26 #include <time.h>
27 # endif
28 #endif
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <expat.h>
36 #include <curl/curl.h>
37 #include <libcitadel.h>
38 #include "citadel.h"
39 #include "server.h"
40 #include "citserver.h"
41 #include "support.h"
42 #include "config.h"
43 #include "threads.h"
44 #include "ctdl_module.h"
45 #include "msgbase.h"
46 #include "parsedate.h"
47 #include "database.h"
48 #include "citadel_dirs.h"
49 #include "md5.h"
50 #include "context.h"
51 #include "event_client.h"
52 #include "rss_atom_parser.h"
53
54
55 #define TMP_MSGDATA 0xFF
56 #define TMP_SHORTER_URL_OFFSET 0xFE
57 #define TMP_SHORTER_URLS 0xFD
58
59 time_t last_run = 0L;
60
61 pthread_mutex_t RSSQueueMutex; /* locks the access to the following vars: */
62 HashList *RSSQueueRooms = NULL; /* rss_room_counter */
63 HashList *RSSFetchUrls = NULL; /*->rss_aggregator;->RefCount access locked*/
64
65 eNextState RSSAggregator_Terminate(AsyncIO *IO);
66 eNextState RSSAggregator_TerminateDB(AsyncIO *IO);
67 eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO);
68 struct CitContext rss_CC;
69
70 struct rssnetcfg *rnclist = NULL;
71 int RSSClientDebugEnabled = 0;
72 #define N ((rss_aggregator*)IO->Data)->QRnumber
73
74 #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (RSSClientDebugEnabled != 0))
75
76 #define EVRSSC_syslog(LEVEL, FORMAT, ...)                               \
77         DBGLOG(LEVEL) syslog(LEVEL,                                     \
78                              "IO[%ld]CC[%d][%ld]RSS" FORMAT,            \
79                              IO->ID, CCID, N, __VA_ARGS__)
80
81 #define EVRSSCM_syslog(LEVEL, FORMAT)                                   \
82         DBGLOG(LEVEL) syslog(LEVEL,                                     \
83                              "IO[%ld]CC[%d][%ld]RSS" FORMAT,            \
84                              IO->ID, CCID, N)
85
86 #define EVRSSQ_syslog(LEVEL, FORMAT, ...)                               \
87         DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT,                       \
88                              __VA_ARGS__)
89 #define EVRSSQM_syslog(LEVEL, FORMAT)                   \
90         DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT)
91
92 #define EVRSSCSM_syslog(LEVEL, FORMAT)                                  \
93         DBGLOG(LEVEL) syslog(LEVEL, "IO[%ld][%ld]RSS" FORMAT,           \
94                              IO->ID, N)
95
96 void DeleteRoomReference(long QRnumber)
97 {
98         HashPos *At;
99         long HKLen;
100         const char *HK;
101         void *vData = NULL;
102         rss_room_counter *pRoomC;
103
104         At = GetNewHashPos(RSSQueueRooms, 0);
105
106         if (GetHashPosFromKey(RSSQueueRooms, LKEY(QRnumber), At))
107         {
108                 GetHashPos(RSSQueueRooms, At, &HKLen, &HK, &vData);
109                 if (vData != NULL)
110                 {
111                         pRoomC = (rss_room_counter *) vData;
112                         pRoomC->count --;
113                         if (pRoomC->count == 0)
114                                 DeleteEntryFromHash(RSSQueueRooms, At);
115                 }
116         }
117         DeleteHashPos(&At);
118 }
119
120 void UnlinkRooms(rss_aggregator *RSSAggr)
121 {
122         DeleteRoomReference(RSSAggr->QRnumber);
123         if (RSSAggr->OtherQRnumbers != NULL)
124         {
125                 long HKLen;
126                 const char *HK;
127                 HashPos *At;
128                 void *vData;
129
130                 At = GetNewHashPos(RSSAggr->OtherQRnumbers, 0);
131                 while (! server_shutting_down &&
132                        GetNextHashPos(RSSAggr->OtherQRnumbers,
133                                       At,
134                                       &HKLen, &HK,
135                                       &vData) &&
136                        (vData != NULL))
137                 {
138                         long *lData = (long*) vData;
139                         DeleteRoomReference(*lData);
140                 }
141
142                 DeleteHashPos(&At);
143         }
144 }
145
146 void UnlinkRSSAggregator(rss_aggregator *RSSAggr)
147 {
148         HashPos *At;
149
150         pthread_mutex_lock(&RSSQueueMutex);
151         UnlinkRooms(RSSAggr);
152
153         At = GetNewHashPos(RSSFetchUrls, 0);
154         if (GetHashPosFromKey(RSSFetchUrls, SKEY(RSSAggr->Url), At))
155         {
156                 DeleteEntryFromHash(RSSFetchUrls, At);
157         }
158         DeleteHashPos(&At);
159         last_run = time(NULL);
160         pthread_mutex_unlock(&RSSQueueMutex);
161 }
162
163 void DeleteRssCfg(void *vptr)
164 {
165         rss_aggregator *RSSAggr = (rss_aggregator *)vptr;
166         AsyncIO *IO = &RSSAggr->IO;
167
168         if (IO->CitContext != NULL)
169                 EVRSSCM_syslog(LOG_DEBUG, "RSS: destroying\n");
170
171         FreeStrBuf(&RSSAggr->Url);
172         FreeStrBuf(&RSSAggr->rooms);
173         FreeStrBuf(&RSSAggr->CData);
174         FreeStrBuf(&RSSAggr->Key);
175         DeleteHash(&RSSAggr->OtherQRnumbers);
176
177         DeleteHashPos (&RSSAggr->Pos);
178         DeleteHash (&RSSAggr->Messages);
179         if (RSSAggr->recp.recp_room != NULL)
180                 free(RSSAggr->recp.recp_room);
181
182
183         if (RSSAggr->Item != NULL)
184         {
185                 flush_rss_item(RSSAggr->Item);
186
187                 free(RSSAggr->Item);
188         }
189
190         FreeAsyncIOContents(&RSSAggr->IO);
191         memset(RSSAggr, 0, sizeof(rss_aggregator));
192         free(RSSAggr);
193 }
194
195 eNextState RSSAggregator_Terminate(AsyncIO *IO)
196 {
197         rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
198
199         EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n");
200
201         StopCurlWatchers(IO);
202         UnlinkRSSAggregator(RSSAggr);
203         return eAbort;
204 }
205
206 eNextState RSSAggregator_TerminateDB(AsyncIO *IO)
207 {
208         rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
209
210         EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n");
211
212
213         StopDBWatchers(&RSSAggr->IO);
214         UnlinkRSSAggregator(RSSAggr);
215         return eAbort;
216 }
217
218 eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO)
219 {
220         const char *pUrl;
221         rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
222
223         pUrl = IO->ConnectMe->PlainUrl;
224         if (pUrl == NULL)
225                 pUrl = "";
226
227         EVRSSC_syslog(LOG_DEBUG, "RSS: Aborting by shutdown: %s.\n", pUrl);
228
229         StopCurlWatchers(IO);
230         UnlinkRSSAggregator(RSSAggr);
231         return eAbort;
232 }
233
234 void AppendLink(StrBuf *Message,
235                 StrBuf *link,
236                 StrBuf *LinkTitle,
237                 const char *Title)
238 {
239         if (StrLength(link) > 0)
240         {
241                 StrBufAppendBufPlain(Message, HKEY("<a href=\""), 0);
242                 StrBufAppendBuf(Message, link, 0);
243                 StrBufAppendBufPlain(Message, HKEY("\">"), 0);
244                 if (StrLength(LinkTitle) > 0)
245                         StrBufAppendBuf(Message, LinkTitle, 0);
246                 else if ((Title != NULL) && !IsEmptyStr(Title))
247                         StrBufAppendBufPlain(Message, Title, -1, 0);
248                 else
249                         StrBufAppendBuf(Message, link, 0);
250                 StrBufAppendBufPlain(Message, HKEY("</a><br>\n"), 0);
251         }
252 }
253
254
255 void rss_format_item(networker_save_message *SaveMsg)
256 {
257         StrBuf *Message;
258         int msglen = 0;
259
260         if (SaveMsg->author_or_creator != NULL) {
261
262                 char *From;
263                 StrBuf *Encoded = NULL;
264                 int FromAt;
265
266                 From = html_to_ascii(ChrPtr(SaveMsg->author_or_creator),
267                                      StrLength(SaveMsg->author_or_creator),
268                                      512, 0);
269                 StrBufPlain(SaveMsg->author_or_creator, From, -1);
270                 StrBufTrim(SaveMsg->author_or_creator);
271                 free(From);
272
273                 FromAt = strchr(ChrPtr(SaveMsg->author_or_creator), '@') != NULL;
274                 if (!FromAt && StrLength (SaveMsg->author_email) > 0)
275                 {
276                         StrBufRFC2047encode(&Encoded, SaveMsg->author_or_creator);
277                         SaveMsg->Msg.cm_fields['A'] = SmashStrBuf(&Encoded);
278                         SaveMsg->Msg.cm_fields['P'] =
279                                 SmashStrBuf(&SaveMsg->author_email);
280                 }
281                 else
282                 {
283                         if (FromAt)
284                         {
285                                 SaveMsg->Msg.cm_fields['A'] =
286                                         SmashStrBuf(&SaveMsg->author_or_creator);
287                                 SaveMsg->Msg.cm_fields['P'] =
288                                         strdup(SaveMsg->Msg.cm_fields['A']);
289                         }
290                         else
291                         {
292                                 StrBufRFC2047encode(&Encoded,
293                                                     SaveMsg->author_or_creator);
294                                 SaveMsg->Msg.cm_fields['A'] =
295                                         SmashStrBuf(&Encoded);
296                                 SaveMsg->Msg.cm_fields['P'] =
297                                         strdup("rss@localhost");
298
299                         }
300                 }
301         }
302         else {
303                 SaveMsg->Msg.cm_fields['A'] = strdup("rss");
304         }
305
306         SaveMsg->Msg.cm_fields['N'] = strdup(NODENAME);
307         if (SaveMsg->title != NULL) {
308                 long len;
309                 char *Sbj;
310                 StrBuf *Encoded, *QPEncoded;
311
312                 QPEncoded = NULL;
313                 StrBufSpaceToBlank(SaveMsg->title);
314                 len = StrLength(SaveMsg->title);
315                 Sbj = html_to_ascii(ChrPtr(SaveMsg->title), len, 512, 0);
316                 len = strlen(Sbj);
317                 if ((len > 0) && (Sbj[len - 1] == '\n'))
318                 {
319                         len --;
320                         Sbj[len] = '\0';
321                 }
322                 Encoded = NewStrBufPlain(Sbj, len);
323                 free(Sbj);
324
325                 StrBufTrim(Encoded);
326                 StrBufRFC2047encode(&QPEncoded, Encoded);
327
328                 SaveMsg->Msg.cm_fields['U'] = SmashStrBuf(&QPEncoded);
329                 FreeStrBuf(&Encoded);
330         }
331         if (SaveMsg->link == NULL)
332                 SaveMsg->link = NewStrBufPlain(HKEY(""));
333
334 #if 0 /* temporarily disable shorter urls. */
335         SaveMsg->Msg.cm_fields[TMP_SHORTER_URLS] =
336                 GetShorterUrls(SaveMsg->description);
337 #endif
338
339         msglen += 1024 + StrLength(SaveMsg->link) + StrLength(SaveMsg->description) ;
340
341         Message = NewStrBufPlain(NULL, msglen);
342
343         StrBufPlain(Message, HKEY(
344                             "Content-type: text/html; charset=\"UTF-8\"\r\n\r\n"
345                             "<html><body>\n"));
346 #if 0 /* disable shorter url for now. */
347         SaveMsg->Msg.cm_fields[TMP_SHORTER_URL_OFFSET] = StrLength(Message);
348 #endif
349         StrBufAppendBuf(Message, SaveMsg->description, 0);
350         StrBufAppendBufPlain(Message, HKEY("<br><br>\n"), 0);
351
352         AppendLink(Message, SaveMsg->link, SaveMsg->linkTitle, NULL);
353         AppendLink(Message, SaveMsg->reLink, SaveMsg->reLinkTitle, "Reply to this");
354         StrBufAppendBufPlain(Message, HKEY("</body></html>\n"), 0);
355
356
357         SaveMsg->Message = Message;
358 }
359
360 eNextState RSSSaveMessage(AsyncIO *IO)
361 {
362         long len;
363         const char *Key;
364         rss_aggregator *RSSAggr = (rss_aggregator *) IO->Data;
365
366         rss_format_item(RSSAggr->ThisMsg);
367
368         RSSAggr->ThisMsg->Msg.cm_fields['M'] =
369                 SmashStrBuf(&RSSAggr->ThisMsg->Message);
370
371         CtdlSubmitMsg(&RSSAggr->ThisMsg->Msg, &RSSAggr->recp, NULL, 0);
372
373         /* write the uidl to the use table so we don't store this item again */
374
375         CheckIfAlreadySeen("RSS Item Insert", RSSAggr->ThisMsg->MsgGUID, IO->Now, 0, eWrite, IO->ID, CCID);
376
377         if (GetNextHashPos(RSSAggr->Messages,
378                            RSSAggr->Pos,
379                            &len, &Key,
380                            (void**) &RSSAggr->ThisMsg))
381                 return NextDBOperation(IO, RSS_FetchNetworkUsetableEntry);
382         else
383                 return eAbort;
384 }
385
386 eNextState RSS_FetchNetworkUsetableEntry(AsyncIO *IO)
387 {
388         const char *Key;
389         long len;
390         rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
391
392         /* Find out if we've already seen this item */
393 // todo: expiry?
394 #ifndef DEBUG_RSS
395         if (CheckIfAlreadySeen("RSS Item Seen",
396                                Ctx->ThisMsg->MsgGUID,
397                                IO->Now,
398                                IO->Now - USETABLE_ANTIEXPIRE,
399                                eCheckUpdate,
400                                IO->ID, CCID)
401             != 0)
402         {
403                 /* Item has already been seen */
404                 EVRSSC_syslog(LOG_DEBUG,
405                           "%s has already been seen\n",
406                           ChrPtr(Ctx->ThisMsg->MsgGUID));
407
408                 if (GetNextHashPos(Ctx->Messages,
409                                    Ctx->Pos,
410                                    &len, &Key,
411                                    (void**) &Ctx->ThisMsg))
412                         return NextDBOperation(
413                                 IO,
414                                 RSS_FetchNetworkUsetableEntry);
415                 else
416                         return eAbort;
417         }
418         else
419 #endif
420         {
421                 NextDBOperation(IO, RSSSaveMessage);
422                 return eSendMore;
423         }
424         return eSendMore;
425 }
426
427 eNextState RSSAggregator_AnalyseReply(AsyncIO *IO)
428 {
429         u_char rawdigest[MD5_DIGEST_LEN];
430         struct MD5Context md5context;
431         StrBuf *guid;
432         rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
433
434         if (IO->HttpReq.httpcode != 200)
435         {
436                 StrBuf *ErrMsg;
437                 long lens[2];
438                 const char *strs[2];
439
440                 ErrMsg = NewStrBuf();
441                 EVRSSC_syslog(LOG_ALERT, "need a 200, got a %ld !\n",
442                               IO->HttpReq.httpcode);
443                 
444                 strs[0] = ChrPtr(Ctx->Url);
445                 lens[0] = StrLength(Ctx->Url);
446
447                 strs[1] = ChrPtr(Ctx->rooms);
448                 lens[1] = StrLength(Ctx->rooms);
449                 StrBufPrintf(ErrMsg,
450                              "Error while RSS-Aggregation Run of %s\n"
451                              " need a 200, got a %ld !\n"
452                              " Response text was: \n"
453                              " \n %s\n",
454                              ChrPtr(Ctx->Url),
455                              IO->HttpReq.httpcode,
456                              ChrPtr(IO->HttpReq.ReplyData));
457                 CtdlAideFPMessage(
458                         ChrPtr(ErrMsg),
459                         "RSS Aggregation run failure",
460                         2, strs, (long*) &lens,
461                         IO->Now,
462                         IO->ID, CCID);
463                 
464                 FreeStrBuf(&ErrMsg);
465                 EVRSSC_syslog(LOG_DEBUG,
466                               "RSS feed returned an invalid http status code. <%s><HTTP %ld>\n",
467                               ChrPtr(Ctx->Url),
468                               IO->HttpReq.httpcode);
469                 return eAbort;
470         }
471
472         MD5Init(&md5context);
473
474         MD5Update(&md5context,
475                   (const unsigned char*)SKEY(IO->HttpReq.ReplyData));
476
477         MD5Update(&md5context,
478                   (const unsigned char*)SKEY(Ctx->Url));
479
480         MD5Final(rawdigest, &md5context);
481         guid = NewStrBufPlain(NULL,
482                               MD5_DIGEST_LEN * 2 + 12 /* _rss2ctdl*/);
483         StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
484         StrBufAppendBufPlain(guid, HKEY("_rssFM"), 0);
485         if (StrLength(guid) > 40)
486                 StrBufCutAt(guid, 40, NULL);
487         /* Find out if we've already seen this item */
488
489 #ifndef DEBUG_RSS
490
491         if (CheckIfAlreadySeen("RSS Whole",
492                                guid,
493                                IO->Now,
494                                IO->Now - USETABLE_ANTIEXPIRE,
495                                eCheckUpdate,
496                                IO->ID, CCID)
497             != 0)
498         {
499                 FreeStrBuf(&guid);
500
501                 EVRSSC_syslog(LOG_DEBUG, "RSS feed already seen. <%s>\n", ChrPtr(Ctx->Url));
502                 return eAbort;
503         }
504         FreeStrBuf(&guid);
505 #endif
506         return RSSAggregator_ParseReply(IO);
507 }
508
509 eNextState RSSAggregator_FinishHttp(AsyncIO *IO)
510 {
511         return QueueDBOperation(IO, RSSAggregator_AnalyseReply);
512 }
513
514 /*
515  * Begin a feed parse
516  */
517 int rss_do_fetching(rss_aggregator *RSSAggr)
518 {
519         AsyncIO         *IO = &RSSAggr->IO;
520         rss_item *ri;
521         time_t now;
522
523         now = time(NULL);
524
525         if ((RSSAggr->next_poll != 0) && (now < RSSAggr->next_poll))
526                 return 0;
527
528         ri = (rss_item*) malloc(sizeof(rss_item));
529         memset(ri, 0, sizeof(rss_item));
530         RSSAggr->Item = ri;
531
532         if (! InitcURLIOStruct(&RSSAggr->IO,
533                                RSSAggr,
534                                "Citadel RSS Client",
535                                RSSAggregator_FinishHttp,
536                                RSSAggregator_Terminate,
537                                RSSAggregator_TerminateDB,
538                                RSSAggregator_ShutdownAbort))
539         {
540                 EVRSSCM_syslog(LOG_ALERT, "Unable to initialize libcurl.\n");
541                 return 0;
542         }
543
544         safestrncpy(((CitContext*)RSSAggr->IO.CitContext)->cs_host,
545                     ChrPtr(RSSAggr->Url),
546                     sizeof(((CitContext*)RSSAggr->IO.CitContext)->cs_host));
547
548         EVRSSC_syslog(LOG_DEBUG, "Fetching RSS feed <%s>\n", ChrPtr(RSSAggr->Url));
549         ParseURL(&RSSAggr->IO.ConnectMe, RSSAggr->Url, 80);
550         CurlPrepareURL(RSSAggr->IO.ConnectMe);
551
552         QueueCurlContext(&RSSAggr->IO);
553         return 1;
554 }
555
556 /*
557  * Scan a room's netconfig to determine whether it is requesting any RSS feeds
558  */
559 void rssclient_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
560 {
561         const RoomNetCfgLine *pLine;
562         rss_aggregator *RSSAggr = NULL;
563         rss_aggregator *use_this_RSSAggr = NULL;
564         void *vptr;
565
566         pthread_mutex_lock(&RSSQueueMutex);
567         if (GetHash(RSSQueueRooms, LKEY(qrbuf->QRnumber), &vptr))
568         {
569                 EVRSSQ_syslog(LOG_DEBUG,
570                               "rssclient: [%ld] %s already in progress.\n",
571                               qrbuf->QRnumber,
572                               qrbuf->QRname);
573                 pthread_mutex_unlock(&RSSQueueMutex);
574                 return;
575         }
576         pthread_mutex_unlock(&RSSQueueMutex);
577
578         if (server_shutting_down) return;
579
580         pLine = OneRNCFG->NetConfigs[rssclient];
581
582         while (pLine != NULL)
583         {
584                 const char *lPtr = NULL;
585
586                 RSSAggr = (rss_aggregator *) malloc(
587                         sizeof(rss_aggregator));
588
589                 memset (RSSAggr, 0, sizeof(rss_aggregator));
590                 RSSAggr->QRnumber = qrbuf->QRnumber;
591                 RSSAggr->roomlist_parts = 1;
592                 RSSAggr->Url = NewStrBufPlain(NULL, StrLength(pLine->Value[0]));
593                 StrBufExtract_NextToken(RSSAggr->Url,
594                                         pLine->Value[0],
595                                         &lPtr,
596                                         '|');
597
598                 pthread_mutex_lock(&RSSQueueMutex);
599                 GetHash(RSSFetchUrls,
600                         SKEY(RSSAggr->Url),
601                         &vptr);
602
603                 use_this_RSSAggr = (rss_aggregator *)vptr;
604                 if (use_this_RSSAggr != NULL)
605                 {
606                         long *QRnumber;
607                         StrBufAppendBufPlain(
608                                 use_this_RSSAggr->rooms,
609                                 qrbuf->QRname,
610                                 -1, 0);
611                         if (use_this_RSSAggr->roomlist_parts==1)
612                         {
613                                 use_this_RSSAggr->OtherQRnumbers
614                                         = NewHash(1, lFlathash);
615                         }
616                         QRnumber = (long*)malloc(sizeof(long));
617                         *QRnumber = qrbuf->QRnumber;
618                         Put(use_this_RSSAggr->OtherQRnumbers,
619                             LKEY(qrbuf->QRnumber),
620                             QRnumber,
621                             NULL);
622                         use_this_RSSAggr->roomlist_parts++;
623
624                         pthread_mutex_unlock(&RSSQueueMutex);
625
626                         FreeStrBuf(&RSSAggr->Url);
627                         free(RSSAggr);
628                         RSSAggr = NULL;
629                         pLine = pLine->next;
630                         continue;
631                 }
632                 pthread_mutex_unlock(&RSSQueueMutex);
633
634                 RSSAggr->ItemType = RSS_UNSET;
635
636                 RSSAggr->rooms = NewStrBufPlain(
637                         qrbuf->QRname, -1);
638
639                 pthread_mutex_lock(&RSSQueueMutex);
640
641                 Put(RSSFetchUrls,
642                     SKEY(RSSAggr->Url),
643                     RSSAggr,
644                     DeleteRssCfg);
645
646                 pthread_mutex_unlock(&RSSQueueMutex);
647                 pLine = pLine->next;
648         }
649 }
650
651 /*
652  * Scan for rooms that have RSS client requests configured
653  */
654 void rssclient_scan(void) {
655         int RSSRoomCount, RSSCount;
656         rss_aggregator *rptr = NULL;
657         void *vrptr = NULL;
658         HashPos *it;
659         long len;
660         const char *Key;
661         time_t now = time(NULL);
662
663         /* Run no more than once every 15 minutes. */
664         if ((now - last_run) < 900) {
665                 EVRSSQ_syslog(LOG_DEBUG,
666                               "Client: polling interval not yet reached; last run was %ldm%lds ago",
667                               ((now - last_run) / 60),
668                               ((now - last_run) % 60)
669                 );
670                 return;
671         }
672
673         /*
674          * This is a simple concurrency check to make sure only one rssclient
675          * run is done at a time.
676          */
677         pthread_mutex_lock(&RSSQueueMutex);
678         RSSCount = GetCount(RSSFetchUrls);
679         RSSRoomCount = GetCount(RSSQueueRooms);
680         pthread_mutex_unlock(&RSSQueueMutex);
681
682         if ((RSSRoomCount > 0) || (RSSCount > 0)) {
683                 EVRSSQ_syslog(LOG_DEBUG,
684                               "rssclient: concurrency check failed; %d rooms and %d url's are queued",
685                               RSSRoomCount, RSSCount
686                         );
687                 return;
688         }
689
690         become_session(&rss_CC);
691         EVRSSQM_syslog(LOG_DEBUG, "rssclient started");
692         CtdlForEachNetCfgRoom(rssclient_scan_room, NULL, rssclient);
693
694         if (GetCount(RSSFetchUrls) > 0)
695         {
696                 pthread_mutex_lock(&RSSQueueMutex);
697                 EVRSSQ_syslog(LOG_DEBUG,
698                                "rssclient starting %d Clients",
699                                GetCount(RSSFetchUrls));
700                 
701                 it = GetNewHashPos(RSSFetchUrls, 0);
702                 while (!server_shutting_down &&
703                        GetNextHashPos(RSSFetchUrls, it, &len, &Key, &vrptr) &&
704                        (vrptr != NULL)) {
705                         rptr = (rss_aggregator *)vrptr;
706                         if (!rss_do_fetching(rptr))
707                                 UnlinkRSSAggregator(rptr);
708                 }
709                 DeleteHashPos(&it);
710                 pthread_mutex_unlock(&RSSQueueMutex);
711         }
712         else
713                 EVRSSQM_syslog(LOG_DEBUG, "Nothing to do.");
714
715         EVRSSQM_syslog(LOG_DEBUG, "rssclient ended\n");
716         return;
717 }
718
719 void rss_cleanup(void)
720 {
721         /* citthread_mutex_destroy(&RSSQueueMutex); TODO */
722         DeleteHash(&RSSFetchUrls);
723         DeleteHash(&RSSQueueRooms);
724 }
725
726 void LogDebugEnableRSSClient(const int n)
727 {
728         RSSClientDebugEnabled = n;
729 }
730
731 CTDL_MODULE_INIT(rssclient)
732 {
733         if (!threading)
734         {
735                 CtdlREGISTERRoomCfgType(rssclient, ParseGeneric, 0, 1, SerializeGeneric, DeleteGenericCfgLine); /// todo: implement rss specific parser
736                 pthread_mutex_init(&RSSQueueMutex, NULL);
737                 RSSQueueRooms = NewHash(1, lFlathash);
738                 RSSFetchUrls = NewHash(1, NULL);
739                 syslog(LOG_INFO, "%s\n", curl_version());
740                 CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
741                 CtdlRegisterEVCleanupHook(rss_cleanup);
742                 CtdlRegisterDebugFlagHook(HKEY("rssclient"), LogDebugEnableRSSClient, &RSSClientDebugEnabled);
743         }
744         else
745         {
746                 CtdlFillSystemContext(&rss_CC, "rssclient");
747         }
748         return "rssclient";
749 }