2 * Bring external RSS feeds into rooms.
4 * Copyright (c) 2007-2016 by the citadel.org team
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.
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.
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
33 #include <sys/types.h>
36 #include <curl/curl.h>
37 #include <libcitadel.h>
40 #include "citserver.h"
44 #include "ctdl_module.h"
46 #include "parsedate.h"
48 #include "citadel_dirs.h"
51 #include "event_client.h"
52 #include "rss_atom_parser.h"
55 #define TMP_MSGDATA 0xFF
56 #define TMP_SHORTER_URL_OFFSET 0xFE
57 #define TMP_SHORTER_URLS 0xFD
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*/
65 eNextState RSSAggregator_Terminate(AsyncIO *IO);
66 eNextState RSSAggregator_TerminateDB(AsyncIO *IO);
67 eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO);
68 struct CitContext rss_CC;
70 struct rssnetcfg *rnclist = NULL;
71 int RSSClientDebugEnabled = 0;
72 #define N ((rss_aggregator*)IO->Data)->Cfg.QRnumber
74 #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (RSSClientDebugEnabled != 0))
76 typedef enum _RSSState {
83 ConstStr RSSStates[] = {
84 {HKEY("Aggregator created")},
85 {HKEY("Fetching content")},
87 {HKEY("parsing content")},
88 {HKEY("checking usetable")}
92 static size_t GetLocationString( void *ptr, size_t size, size_t nmemb, void *userdata)
94 #define LOCATION "location"
95 if (strncasecmp((char*)ptr, LOCATION, sizeof(LOCATION) - 1) == 0)
97 AsyncIO *IO = (AsyncIO *) userdata;
98 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
100 char *pch = (char*) ptr;
103 pche = pch + (size * nmemb);
104 pch += sizeof(LOCATION);
106 while (isspace(*pch) || (*pch == ':'))
109 while (isspace(*pche) || (*pche == '\0'))
111 if (RSSAggr->RedirectUrl == NULL) {
112 RSSAggr->RedirectUrl = NewStrBufPlain(pch, pche - pch + 1);
115 FlushStrBuf(RSSAggr->RedirectUrl);
116 StrBufPlain(RSSAggr->RedirectUrl, pch, pche - pch + 1);
123 static void SetRSSState(AsyncIO *IO, RSSState State)
125 CitContext* CCC = IO->CitContext;
127 memcpy(CCC->cs_clientname, RSSStates[State].Key, RSSStates[State].len + 1);
132 void DeleteRoomReference(long QRnumber)
138 rss_room_counter *pRoomC;
140 At = GetNewHashPos(RSSQueueRooms, 0);
142 if (GetHashPosFromKey(RSSQueueRooms, LKEY(QRnumber), At))
144 GetHashPos(RSSQueueRooms, At, &HKLen, &HK, &vData);
147 pRoomC = (rss_room_counter *) vData;
149 if (pRoomC->count == 0)
150 DeleteEntryFromHash(RSSQueueRooms, At);
156 void UnlinkRooms(rss_aggregator *RSSAggr)
158 DeleteRoomReference(RSSAggr->Cfg.QRnumber);
159 if (RSSAggr->OtherQRnumbers != NULL)
166 At = GetNewHashPos(RSSAggr->OtherQRnumbers, 0);
167 while (! server_shutting_down &&
168 GetNextHashPos(RSSAggr->OtherQRnumbers,
174 pRSSConfig *Data = (pRSSConfig*) vData;
175 DeleteRoomReference(Data->QRnumber);
182 void UnlinkRSSAggregator(rss_aggregator *RSSAggr)
186 pthread_mutex_lock(&RSSQueueMutex);
187 UnlinkRooms(RSSAggr);
189 At = GetNewHashPos(RSSFetchUrls, 0);
190 if (GetHashPosFromKey(RSSFetchUrls, SKEY(RSSAggr->Url), At))
192 DeleteEntryFromHash(RSSFetchUrls, At);
195 last_run = time(NULL);
196 pthread_mutex_unlock(&RSSQueueMutex);
199 void DeleteRssCfg(void *vptr)
201 rss_aggregator *RSSAggr = (rss_aggregator *)vptr;
202 AsyncIO *IO = &RSSAggr->IO;
204 if (IO->CitContext != NULL) {
205 syslog(LOG_DEBUG, "RSS: destroying\n");
208 FreeStrBuf(&RSSAggr->Url);
209 FreeStrBuf(&RSSAggr->RedirectUrl);
210 FreeStrBuf(&RSSAggr->rooms);
211 FreeStrBuf(&RSSAggr->CData);
212 FreeStrBuf(&RSSAggr->Key);
213 DeleteHash(&RSSAggr->OtherQRnumbers);
215 DeleteHashPos (&RSSAggr->Pos);
216 DeleteHash (&RSSAggr->Messages);
217 if (RSSAggr->recp.recp_room != NULL)
218 free(RSSAggr->recp.recp_room);
221 if (RSSAggr->Item != NULL)
223 flush_rss_item(RSSAggr->Item);
228 FreeAsyncIOContents(&RSSAggr->IO);
229 memset(RSSAggr, 0, sizeof(rss_aggregator));
233 eNextState RSSAggregator_Terminate(AsyncIO *IO)
235 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
237 syslog(LOG_DEBUG, "RSS: Terminating.");
239 StopCurlWatchers(IO);
240 UnlinkRSSAggregator(RSSAggr);
244 eNextState RSSAggregator_TerminateDB(AsyncIO *IO)
246 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
248 syslog(LOG_DEBUG, "RSS: Terminating.");
251 StopDBWatchers(&RSSAggr->IO);
252 UnlinkRSSAggregator(RSSAggr);
256 eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO)
259 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
261 pUrl = IO->ConnectMe->PlainUrl;
265 syslog(LOG_DEBUG, "RSS: Aborting by shutdown: %s.", pUrl);
267 StopCurlWatchers(IO);
268 UnlinkRSSAggregator(RSSAggr);
272 void AppendLink(StrBuf *Message,
277 if (StrLength(link) > 0)
279 StrBufAppendBufPlain(Message, HKEY("<a href=\""), 0);
280 StrBufAppendBuf(Message, link, 0);
281 StrBufAppendBufPlain(Message, HKEY("\">"), 0);
282 if (StrLength(LinkTitle) > 0)
283 StrBufAppendBuf(Message, LinkTitle, 0);
284 else if ((Title != NULL) && !IsEmptyStr(Title))
285 StrBufAppendBufPlain(Message, Title, -1, 0);
287 StrBufAppendBuf(Message, link, 0);
288 StrBufAppendBufPlain(Message, HKEY("</a><br>\n"), 0);
293 int rss_format_item(AsyncIO *IO, networker_save_message *SaveMsg)
298 if (StrLength(SaveMsg->description) +
299 StrLength(SaveMsg->link) +
300 StrLength(SaveMsg->linkTitle) +
301 StrLength(SaveMsg->reLink) +
302 StrLength(SaveMsg->reLinkTitle) +
303 StrLength(SaveMsg->title) == 0)
305 syslog(LOG_INFO, "Refusing to save empty message.");
309 CM_Flush(&SaveMsg->Msg);
311 if (SaveMsg->author_or_creator != NULL) {
314 StrBuf *Encoded = NULL;
317 From = html_to_ascii(ChrPtr(SaveMsg->author_or_creator),
318 StrLength(SaveMsg->author_or_creator),
320 StrBufPlain(SaveMsg->author_or_creator, From, -1);
321 StrBufTrim(SaveMsg->author_or_creator);
324 FromAt = strchr(ChrPtr(SaveMsg->author_or_creator), '@') != NULL;
325 if (!FromAt && StrLength (SaveMsg->author_email) > 0)
327 StrBufRFC2047encode(&Encoded, SaveMsg->author_or_creator);
328 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded);
329 CM_SetAsFieldSB(&SaveMsg->Msg, eMessagePath, &SaveMsg->author_email);
335 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &SaveMsg->author_or_creator);
336 CM_CopyField(&SaveMsg->Msg, eMessagePath, eAuthor);
340 StrBufRFC2047encode(&Encoded,
341 SaveMsg->author_or_creator);
342 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded);
343 CM_SetField(&SaveMsg->Msg, eMessagePath, HKEY("rss@localhost"));
349 CM_SetField(&SaveMsg->Msg, eAuthor, HKEY("rss"));
352 CM_SetField(&SaveMsg->Msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
353 if (SaveMsg->title != NULL) {
356 StrBuf *Encoded, *QPEncoded;
359 StrBufSpaceToBlank(SaveMsg->title);
360 len = StrLength(SaveMsg->title);
361 Sbj = html_to_ascii(ChrPtr(SaveMsg->title), len, 512, 0);
362 if (!IsEmptyStr(Sbj)) {
364 if ((Sbj[len - 1] == '\n'))
369 Encoded = NewStrBufPlain(Sbj, len);
373 StrBufRFC2047encode(&QPEncoded, Encoded);
375 CM_SetAsFieldSB(&SaveMsg->Msg, eMsgSubject, &QPEncoded);
376 FreeStrBuf(&Encoded);
382 if (SaveMsg->link == NULL)
383 SaveMsg->link = NewStrBufPlain(HKEY(""));
385 #if 0 /* temporarily disable shorter urls. */
386 SaveMsg->Msg.cm_fields[TMP_SHORTER_URLS] =
387 GetShorterUrls(SaveMsg->description);
390 msglen += 1024 + StrLength(SaveMsg->link) + StrLength(SaveMsg->description) ;
392 Message = NewStrBufPlain(NULL, msglen);
394 StrBufPlain(Message, HKEY(
395 "Content-type: text/html; charset=\"UTF-8\"\r\n\r\n"
397 #if 0 /* disable shorter url for now. */
398 SaveMsg->Msg.cm_fields[TMP_SHORTER_URL_OFFSET] = StrLength(Message);
400 StrBufAppendBuf(Message, SaveMsg->description, 0);
401 StrBufAppendBufPlain(Message, HKEY("<br><br>\n"), 0);
403 AppendLink(Message, SaveMsg->link, SaveMsg->linkTitle, NULL);
404 AppendLink(Message, SaveMsg->reLink, SaveMsg->reLinkTitle, "Reply to this");
405 StrBufAppendBufPlain(Message, HKEY("</body></html>\n"), 0);
407 SaveMsg->Message = Message;
411 eNextState RSSSaveMessage(AsyncIO *IO)
415 rss_aggregator *RSSAggr = (rss_aggregator *) IO->Data;
417 if (rss_format_item(IO, RSSAggr->ThisMsg))
419 CM_SetAsFieldSB(&RSSAggr->ThisMsg->Msg, eMesageText,
420 &RSSAggr->ThisMsg->Message);
422 CtdlSubmitMsg(&RSSAggr->ThisMsg->Msg, &RSSAggr->recp, NULL, 0);
424 /* write the uidl to the use table so we don't store this item again */
426 CheckIfAlreadySeen("RSS Item Insert", RSSAggr->ThisMsg->MsgGUID, EvGetNow(IO), 0, eWrite, CCID, IO->ID);
429 if (GetNextHashPos(RSSAggr->Messages,
432 (void**) &RSSAggr->ThisMsg))
433 return NextDBOperation(IO, RSS_FetchNetworkUsetableEntry);
438 eNextState RSS_FetchNetworkUsetableEntry(AsyncIO *IO)
440 static const time_t antiExpire = USETABLE_ANTIEXPIRE_HIRES;
442 time_t seenstamp = 0;
445 rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
447 /* Find out if we've already seen this item */
449 SetRSSState(IO, eRSSUT);
450 seenstamp = CheckIfAlreadySeen("RSS Item Seen",
451 Ctx->ThisMsg->MsgGUID,
458 /* Item has already been seen */
459 syslog(LOG_DEBUG, "%s has already been seen - %ld < %ld", ChrPtr(Ctx->ThisMsg->MsgGUID), seenstamp, antiExpire);
461 SetRSSState(IO, eRSSParsing);
463 if (GetNextHashPos(Ctx->Messages,
466 (void**) &Ctx->ThisMsg))
467 return NextDBOperation(
469 RSS_FetchNetworkUsetableEntry);
476 /* Item has already been seen */
478 "%s Parsing - %ld >= %ld",
479 ChrPtr(Ctx->ThisMsg->MsgGUID),
480 seenstamp, antiExpire);
481 SetRSSState(IO, eRSSParsing);
483 NextDBOperation(IO, RSSSaveMessage);
489 void UpdateLastKnownGood(pRSSConfig *pCfg, time_t now)
491 OneRoomNetCfg *pRNCfg;
492 begin_critical_section(S_NETCONFIGS);
493 pRNCfg = CtdlGetNetCfgForRoom(pCfg->QRnumber);
496 RSSCfgLine *RSSCfg = (RSSCfgLine *)pRNCfg->NetConfigs[rssclient];
498 while (RSSCfg != NULL)
500 if (RSSCfg == pCfg->pCfg)
503 RSSCfg = RSSCfg->next;
507 RSSCfg->last_known_good = now;
510 SaveRoomNetConfigFile(pRNCfg, pCfg->QRnumber);
511 FreeRoomNetworkStruct(&pRNCfg);
512 end_critical_section(S_NETCONFIGS);
515 eNextState RSSAggregator_AnalyseReply(AsyncIO *IO)
521 u_char rawdigest[MD5_DIGEST_LEN];
522 struct MD5Context md5context;
524 rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
527 if ((IO->HttpReq.httpcode >= 300) && (IO->HttpReq.httpcode < 400) && (Ctx->RedirectUrl != NULL))
533 SetRSSState(IO, eRSSFailure);
534 ErrMsg = NewStrBuf();
536 syslog(LOG_INFO, "need a 200, got a %ld !", IO->HttpReq.httpcode);
538 strs[0] = ChrPtr(Ctx->Url);
539 lens[0] = StrLength(Ctx->Url);
541 strs[1] = ChrPtr(Ctx->rooms);
542 lens[1] = StrLength(Ctx->rooms);
544 if (IO->HttpReq.CurlError == NULL)
545 IO->HttpReq.CurlError = "";
548 "Error while RSS-Aggregation Run of %s\n"
549 " need a 200, got a %ld !\n"
550 " Curl Error message: \n%s / %s\n"
551 " Redirect header points to: %s\n"
552 " Response text was: \n"
555 IO->HttpReq.httpcode,
557 IO->HttpReq.CurlError,
558 ChrPtr(Ctx->RedirectUrl),
559 ChrPtr(IO->HttpReq.ReplyData)
564 "RSS Aggregation run failure",
565 2, strs, (long*) &lens,
572 "RSS feed returned an invalid http status code. <%s><HTTP %ld>",
578 else if (IO->HttpReq.httpcode != 200)
584 SetRSSState(IO, eRSSFailure);
585 ErrMsg = NewStrBuf();
587 syslog(LOG_INFO, "need a 200, got a %ld !", IO->HttpReq.httpcode);
589 strs[0] = ChrPtr(Ctx->Url);
590 lens[0] = StrLength(Ctx->Url);
592 strs[1] = ChrPtr(Ctx->rooms);
593 lens[1] = StrLength(Ctx->rooms);
595 if (IO->HttpReq.CurlError == NULL)
596 IO->HttpReq.CurlError = "";
599 "Error while RSS-Aggregation Run of %s\n"
600 " need a 200, got a %ld !\n"
601 " Curl Error message: \n%s / %s\n"
602 " Response text was: \n"
605 IO->HttpReq.httpcode,
607 IO->HttpReq.CurlError,
608 ChrPtr(IO->HttpReq.ReplyData)
613 "RSS Aggregation run failure",
614 2, strs, (long*) &lens,
621 "RSS feed returned an invalid http status code. <%s><HTTP %ld>",
632 UpdateLastKnownGood (pCfg, EvGetNow(IO));
633 if ((Ctx->roomlist_parts > 1) &&
636 it = GetNewHashPos(RSSFetchUrls, 0);
641 if (GetNextHashPos(Ctx->OtherQRnumbers, it, &len, &Key, &vptr))
651 SetRSSState(IO, eRSSUT);
653 MD5Init(&md5context);
655 MD5Update(&md5context,
656 (const unsigned char*)SKEY(IO->HttpReq.ReplyData));
658 MD5Update(&md5context,
659 (const unsigned char*)SKEY(Ctx->Url));
661 MD5Final(rawdigest, &md5context);
662 guid = NewStrBufPlain(NULL,
663 MD5_DIGEST_LEN * 2 + 12 /* _rss2ctdl*/);
664 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
665 StrBufAppendBufPlain(guid, HKEY("_rssFM"), 0);
666 if (StrLength(guid) > 40)
667 StrBufCutAt(guid, 40, NULL);
668 /* Find out if we've already seen this item */
672 if (CheckIfAlreadySeen("RSS Whole",
675 EvGetNow(IO) - USETABLE_ANTIEXPIRE,
682 syslog(LOG_DEBUG, "RSS feed already seen. <%s>", ChrPtr(Ctx->Url));
687 SetRSSState(IO, eRSSParsing);
688 return RSSAggregator_ParseReply(IO);
691 eNextState RSSAggregator_FinishHttp(AsyncIO *IO)
693 return CurlQueueDBOperation(IO, RSSAggregator_AnalyseReply);
699 int rss_do_fetching(rss_aggregator *RSSAggr)
701 AsyncIO *IO = &RSSAggr->IO;
710 if ((RSSAggr->next_poll != 0) && (now < RSSAggr->next_poll))
713 ri = (rss_item*) malloc(sizeof(rss_item));
714 memset(ri, 0, sizeof(rss_item));
717 if (! InitcURLIOStruct(&RSSAggr->IO,
719 "Citadel RSS Client",
720 RSSAggregator_FinishHttp,
721 RSSAggregator_Terminate,
722 RSSAggregator_TerminateDB,
723 RSSAggregator_ShutdownAbort))
725 syslog(LOG_INFO, "Unable to initialize libcurl.");
728 chnd = IO->HttpReq.chnd;
730 OPT(HEADERFUNCTION, GetLocationString);
731 SetRSSState(IO, eRSSCreated);
733 safestrncpy(((CitContext*)RSSAggr->IO.CitContext)->cs_host,
734 ChrPtr(RSSAggr->Url),
735 sizeof(((CitContext*)RSSAggr->IO.CitContext)->cs_host));
737 syslog(LOG_DEBUG, "Fetching RSS feed <%s>", ChrPtr(RSSAggr->Url));
738 ParseURL(&RSSAggr->IO.ConnectMe, RSSAggr->Url, 80);
739 CurlPrepareURL(RSSAggr->IO.ConnectMe);
741 SetRSSState(IO, eRSSFetching);
742 QueueCurlContext(&RSSAggr->IO);
747 * Scan a room's netconfig to determine whether it is requesting any RSS feeds
749 void rssclient_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
751 const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient];
752 rss_aggregator *RSSAggr = NULL;
753 rss_aggregator *use_this_RSSAggr = NULL;
756 syslog(LOG_DEBUG, "rssclient_scan_room(%s)", qrbuf->QRname);
757 pthread_mutex_lock(&RSSQueueMutex);
758 if (GetHash(RSSQueueRooms, LKEY(qrbuf->QRnumber), &vptr))
761 "rssclient: [%ld] %s already in progress.",
764 pthread_mutex_unlock(&RSSQueueMutex);
767 pthread_mutex_unlock(&RSSQueueMutex);
769 if (server_shutting_down) return;
771 while (RSSCfg != NULL)
773 pthread_mutex_lock(&RSSQueueMutex);
774 GetHash(RSSFetchUrls,
778 use_this_RSSAggr = (rss_aggregator *)vptr;
779 if (use_this_RSSAggr != NULL)
783 StrBufAppendBufPlain(
784 use_this_RSSAggr->rooms,
787 if (use_this_RSSAggr->roomlist_parts==1)
789 use_this_RSSAggr->OtherQRnumbers
790 = NewHash(1, lFlathash);
793 pRSSCfg = (pRSSConfig *) malloc(sizeof(pRSSConfig));
795 pRSSCfg->QRnumber = qrbuf->QRnumber;
796 pRSSCfg->pCfg = RSSCfg;
798 Put(use_this_RSSAggr->OtherQRnumbers,
799 LKEY(qrbuf->QRnumber),
802 use_this_RSSAggr->roomlist_parts++;
804 pthread_mutex_unlock(&RSSQueueMutex);
806 RSSCfg = RSSCfg->next;
809 pthread_mutex_unlock(&RSSQueueMutex);
811 RSSAggr = (rss_aggregator *) malloc(
812 sizeof(rss_aggregator));
814 memset (RSSAggr, 0, sizeof(rss_aggregator));
815 RSSAggr->Cfg.QRnumber = qrbuf->QRnumber;
816 RSSAggr->Cfg.pCfg = RSSCfg;
817 RSSAggr->roomlist_parts = 1;
818 RSSAggr->Url = NewStrBufDup(RSSCfg->Url);
820 RSSAggr->ItemType = RSS_UNSET;
822 RSSAggr->rooms = NewStrBufPlain(
825 pthread_mutex_lock(&RSSQueueMutex);
832 pthread_mutex_unlock(&RSSQueueMutex);
833 RSSCfg = RSSCfg->next;
838 * Scan for rooms that have RSS client requests configured
840 void rssclient_scan(void) {
841 int RSSRoomCount, RSSCount;
842 rss_aggregator *rptr = NULL;
847 time_t now = time(NULL);
849 /* Run no more than once every 15 minutes. */
850 if ((now - last_run) < 900) {
852 "Client: polling interval not yet reached; last run was %ldm%lds ago",
853 ((now - last_run) / 60),
854 ((now - last_run) % 60)
860 * This is a simple concurrency check to make sure only one rssclient
861 * run is done at a time.
863 pthread_mutex_lock(&RSSQueueMutex);
864 RSSCount = GetCount(RSSFetchUrls);
865 RSSRoomCount = GetCount(RSSQueueRooms);
866 pthread_mutex_unlock(&RSSQueueMutex);
868 if ((RSSRoomCount > 0) || (RSSCount > 0)) {
870 "rssclient: concurrency check failed; %d rooms and %d url's are queued",
871 RSSRoomCount, RSSCount
876 become_session(&rss_CC);
877 syslog(LOG_DEBUG, "rssclient started");
878 CtdlForEachNetCfgRoom(rssclient_scan_room, NULL, rssclient);
880 if (GetCount(RSSFetchUrls) > 0)
882 pthread_mutex_lock(&RSSQueueMutex);
884 "rssclient starting %d Clients",
885 GetCount(RSSFetchUrls));
887 it = GetNewHashPos(RSSFetchUrls, 0);
888 while (!server_shutting_down &&
889 GetNextHashPos(RSSFetchUrls, it, &len, &Key, &vrptr) &&
891 rptr = (rss_aggregator *)vrptr;
892 if (!rss_do_fetching(rptr))
893 UnlinkRSSAggregator(rptr);
896 pthread_mutex_unlock(&RSSQueueMutex);
899 syslog(LOG_DEBUG, "Nothing to do.");
902 syslog(LOG_DEBUG, "rssclient ended");
906 void rss_cleanup(void)
908 /* citthread_mutex_destroy(&RSSQueueMutex); TODO */
909 DeleteHash(&RSSFetchUrls);
910 DeleteHash(&RSSQueueRooms);
913 void LogDebugEnableRSSClient(const int n)
915 RSSClientDebugEnabled = n;
919 typedef struct __RSSVetoInfo {
925 void rssclient_veto_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
927 RSSVetoInfo *Info = (RSSVetoInfo *) data;
928 const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient];
930 while (RSSCfg != NULL)
932 if ((RSSCfg->last_known_good != 0) &&
933 (RSSCfg->last_known_good + USETABLE_ANTIEXPIRE < Info->Now))
935 StrBufAppendPrintf(Info->ErrMsg,
936 "RSS feed not seen for a %d days:: <",
937 (Info->Now - RSSCfg->last_known_good) / (24 * 60 * 60));
939 StrBufAppendBuf(Info->ErrMsg, RSSCfg->Url, 0);
940 StrBufAppendBufPlain(Info->ErrMsg, HKEY(">\n"), 0);
942 RSSCfg = RSSCfg->next;
946 int RSSCheckUsetableVeto(StrBuf *ErrMsg)
950 Info.ErrMsg = ErrMsg;
951 Info.Now = time (NULL);
954 CtdlForEachNetCfgRoom(rssclient_veto_scan_room, &Info, rssclient);
962 void ParseRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
966 RSSCfg = (RSSCfgLine *) malloc (sizeof(RSSCfgLine));
967 RSSCfg->Url = NewStrBufPlain (NULL, StrLength (Line));
970 StrBufExtract_NextToken(RSSCfg->Url, Line, &LinePos, '|');
971 RSSCfg->last_known_good = StrBufExtractNext_long(Line, &LinePos, '|');
974 RSSCfg->next = (RSSCfgLine *)OneRNCFG->NetConfigs[ThisOne->C];
975 OneRNCFG->NetConfigs[ThisOne->C] = (RoomNetCfgLine*) RSSCfg;
978 void SerializeRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *RNCfg, RoomNetCfgLine *data)
980 RSSCfgLine *RSSCfg = (RSSCfgLine*) data;
982 StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0);
983 StrBufAppendBufPlain(OutputBuffer, HKEY("|"), 0);
984 StrBufAppendBufPlain(OutputBuffer, SKEY(RSSCfg->Url), 0);
985 StrBufAppendPrintf(OutputBuffer, "|%ld\n", RSSCfg->last_known_good);
988 void DeleteRSSClientCfgLine(const CfgLineType *ThisOne, RoomNetCfgLine **data)
990 RSSCfgLine *RSSCfg = (RSSCfgLine*) *data;
992 FreeStrBuf(&RSSCfg->Url);
998 CTDL_MODULE_INIT(rssclient)
1002 CtdlRegisterTDAPVetoHook (RSSCheckUsetableVeto, CDB_USETABLE, 0);
1004 CtdlREGISTERRoomCfgType(rssclient, ParseRSSClientCfgLine, 0, 1, SerializeRSSClientCfgLine, DeleteRSSClientCfgLine);
1005 pthread_mutex_init(&RSSQueueMutex, NULL);
1006 RSSQueueRooms = NewHash(1, lFlathash);
1007 RSSFetchUrls = NewHash(1, NULL);
1008 syslog(LOG_INFO, "%s\n", curl_version());
1009 CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
1010 CtdlRegisterEVCleanupHook(rss_cleanup);
1011 CtdlRegisterDebugFlagHook(HKEY("rssclient"), LogDebugEnableRSSClient, &RSSClientDebugEnabled);
1015 CtdlFillSystemContext(&rss_CC, "rssclient");