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