2 * Bring external RSS feeds into rooms.
4 * Copyright (c) 2007-2012 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 #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__)
81 #define EVRSSCM_syslog(LEVEL, FORMAT) \
82 DBGLOG(LEVEL) syslog(LEVEL, \
83 "IO[%ld]CC[%d][%ld]RSS" FORMAT, \
86 #define EVRSSQ_syslog(LEVEL, FORMAT, ...) \
87 DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT, \
89 #define EVRSSQM_syslog(LEVEL, FORMAT) \
90 DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT)
92 #define EVRSSCSM_syslog(LEVEL, FORMAT) \
93 DBGLOG(LEVEL) syslog(LEVEL, "IO[%ld][%ld]RSS" FORMAT, \
96 typedef enum _RSSState {
103 ConstStr RSSStates[] = {
104 {HKEY("Aggregator created")},
105 {HKEY("Fetching content")},
107 {HKEY("parsing content")},
108 {HKEY("checking usetable")}
111 static void SetRSSState(AsyncIO *IO, RSSState State)
113 CitContext* CCC = IO->CitContext;
115 memcpy(CCC->cs_clientname, RSSStates[State].Key, RSSStates[State].len + 1);
118 void DeleteRoomReference(long QRnumber)
124 rss_room_counter *pRoomC;
126 At = GetNewHashPos(RSSQueueRooms, 0);
128 if (GetHashPosFromKey(RSSQueueRooms, LKEY(QRnumber), At))
130 GetHashPos(RSSQueueRooms, At, &HKLen, &HK, &vData);
133 pRoomC = (rss_room_counter *) vData;
135 if (pRoomC->count == 0)
136 DeleteEntryFromHash(RSSQueueRooms, At);
142 void UnlinkRooms(rss_aggregator *RSSAggr)
144 DeleteRoomReference(RSSAggr->Cfg.QRnumber);
145 if (RSSAggr->OtherQRnumbers != NULL)
152 At = GetNewHashPos(RSSAggr->OtherQRnumbers, 0);
153 while (! server_shutting_down &&
154 GetNextHashPos(RSSAggr->OtherQRnumbers,
160 pRSSConfig *Data = (pRSSConfig*) vData;
161 DeleteRoomReference(Data->QRnumber);
168 void UnlinkRSSAggregator(rss_aggregator *RSSAggr)
172 pthread_mutex_lock(&RSSQueueMutex);
173 UnlinkRooms(RSSAggr);
175 At = GetNewHashPos(RSSFetchUrls, 0);
176 if (GetHashPosFromKey(RSSFetchUrls, SKEY(RSSAggr->Url), At))
178 DeleteEntryFromHash(RSSFetchUrls, At);
181 last_run = time(NULL);
182 pthread_mutex_unlock(&RSSQueueMutex);
185 void DeleteRssCfg(void *vptr)
187 rss_aggregator *RSSAggr = (rss_aggregator *)vptr;
188 AsyncIO *IO = &RSSAggr->IO;
190 if (IO->CitContext != NULL)
191 EVRSSCM_syslog(LOG_DEBUG, "RSS: destroying\n");
193 FreeStrBuf(&RSSAggr->Url);
194 FreeStrBuf(&RSSAggr->rooms);
195 FreeStrBuf(&RSSAggr->CData);
196 FreeStrBuf(&RSSAggr->Key);
197 DeleteHash(&RSSAggr->OtherQRnumbers);
199 DeleteHashPos (&RSSAggr->Pos);
200 DeleteHash (&RSSAggr->Messages);
201 if (RSSAggr->recp.recp_room != NULL)
202 free(RSSAggr->recp.recp_room);
205 if (RSSAggr->Item != NULL)
207 flush_rss_item(RSSAggr->Item);
212 FreeAsyncIOContents(&RSSAggr->IO);
213 memset(RSSAggr, 0, sizeof(rss_aggregator));
217 eNextState RSSAggregator_Terminate(AsyncIO *IO)
219 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
221 EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n");
223 StopCurlWatchers(IO);
224 UnlinkRSSAggregator(RSSAggr);
228 eNextState RSSAggregator_TerminateDB(AsyncIO *IO)
230 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
232 EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n");
235 StopDBWatchers(&RSSAggr->IO);
236 UnlinkRSSAggregator(RSSAggr);
240 eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO)
243 rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data;
245 pUrl = IO->ConnectMe->PlainUrl;
249 EVRSSC_syslog(LOG_DEBUG, "RSS: Aborting by shutdown: %s.\n", pUrl);
251 StopCurlWatchers(IO);
252 UnlinkRSSAggregator(RSSAggr);
256 void AppendLink(StrBuf *Message,
261 if (StrLength(link) > 0)
263 StrBufAppendBufPlain(Message, HKEY("<a href=\""), 0);
264 StrBufAppendBuf(Message, link, 0);
265 StrBufAppendBufPlain(Message, HKEY("\">"), 0);
266 if (StrLength(LinkTitle) > 0)
267 StrBufAppendBuf(Message, LinkTitle, 0);
268 else if ((Title != NULL) && !IsEmptyStr(Title))
269 StrBufAppendBufPlain(Message, Title, -1, 0);
271 StrBufAppendBuf(Message, link, 0);
272 StrBufAppendBufPlain(Message, HKEY("</a><br>\n"), 0);
277 int rss_format_item(AsyncIO *IO, networker_save_message *SaveMsg)
282 if (StrLength(SaveMsg->description) +
283 StrLength(SaveMsg->link) +
284 StrLength(SaveMsg->linkTitle) +
285 StrLength(SaveMsg->reLink) +
286 StrLength(SaveMsg->reLinkTitle) +
287 StrLength(SaveMsg->title) == 0)
289 EVRSSCM_syslog(LOG_INFO, "Refusing to save empty message.");
293 if (SaveMsg->author_or_creator != NULL) {
296 StrBuf *Encoded = NULL;
299 From = html_to_ascii(ChrPtr(SaveMsg->author_or_creator),
300 StrLength(SaveMsg->author_or_creator),
302 StrBufPlain(SaveMsg->author_or_creator, From, -1);
303 StrBufTrim(SaveMsg->author_or_creator);
306 FromAt = strchr(ChrPtr(SaveMsg->author_or_creator), '@') != NULL;
307 if (!FromAt && StrLength (SaveMsg->author_email) > 0)
309 StrBufRFC2047encode(&Encoded, SaveMsg->author_or_creator);
310 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded);
311 CM_SetAsFieldSB(&SaveMsg->Msg, eMessagePath, &SaveMsg->author_email);
317 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &SaveMsg->author_or_creator);
318 CM_CopyField(&SaveMsg->Msg, eMessagePath, eAuthor);
322 StrBufRFC2047encode(&Encoded,
323 SaveMsg->author_or_creator);
324 CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded);
325 CM_SetField(&SaveMsg->Msg, eMessagePath, HKEY("rss@localhost"));
331 CM_SetField(&SaveMsg->Msg, eAuthor, HKEY("rss"));
334 CM_SetField(&SaveMsg->Msg, eNodeName, NODENAME, strlen(NODENAME));
335 if (SaveMsg->title != NULL) {
338 StrBuf *Encoded, *QPEncoded;
341 StrBufSpaceToBlank(SaveMsg->title);
342 len = StrLength(SaveMsg->title);
343 Sbj = html_to_ascii(ChrPtr(SaveMsg->title), len, 512, 0);
345 if ((len > 0) && (Sbj[len - 1] == '\n'))
350 Encoded = NewStrBufPlain(Sbj, len);
354 StrBufRFC2047encode(&QPEncoded, Encoded);
356 CM_SetAsFieldSB(&SaveMsg->Msg, eMsgSubject, &QPEncoded);
357 FreeStrBuf(&Encoded);
359 if (SaveMsg->link == NULL)
360 SaveMsg->link = NewStrBufPlain(HKEY(""));
362 #if 0 /* temporarily disable shorter urls. */
363 SaveMsg->Msg.cm_fields[TMP_SHORTER_URLS] =
364 GetShorterUrls(SaveMsg->description);
367 msglen += 1024 + StrLength(SaveMsg->link) + StrLength(SaveMsg->description) ;
369 Message = NewStrBufPlain(NULL, msglen);
371 StrBufPlain(Message, HKEY(
372 "Content-type: text/html; charset=\"UTF-8\"\r\n\r\n"
374 #if 0 /* disable shorter url for now. */
375 SaveMsg->Msg.cm_fields[TMP_SHORTER_URL_OFFSET] = StrLength(Message);
377 StrBufAppendBuf(Message, SaveMsg->description, 0);
378 StrBufAppendBufPlain(Message, HKEY("<br><br>\n"), 0);
380 AppendLink(Message, SaveMsg->link, SaveMsg->linkTitle, NULL);
381 AppendLink(Message, SaveMsg->reLink, SaveMsg->reLinkTitle, "Reply to this");
382 StrBufAppendBufPlain(Message, HKEY("</body></html>\n"), 0);
384 SaveMsg->Message = Message;
388 eNextState RSSSaveMessage(AsyncIO *IO)
392 rss_aggregator *RSSAggr = (rss_aggregator *) IO->Data;
394 if (rss_format_item(IO, RSSAggr->ThisMsg))
396 CM_SetAsFieldSB(&RSSAggr->ThisMsg->Msg, eMesageText,
397 &RSSAggr->ThisMsg->Message);
399 CtdlSubmitMsg(&RSSAggr->ThisMsg->Msg, &RSSAggr->recp, NULL, 0);
401 /* write the uidl to the use table so we don't store this item again */
403 CheckIfAlreadySeen("RSS Item Insert", RSSAggr->ThisMsg->MsgGUID, IO->Now, 0, eWrite, CCID, IO->ID);
406 if (GetNextHashPos(RSSAggr->Messages,
409 (void**) &RSSAggr->ThisMsg))
410 return NextDBOperation(IO, RSS_FetchNetworkUsetableEntry);
415 eNextState RSS_FetchNetworkUsetableEntry(AsyncIO *IO)
420 rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
422 /* Find out if we've already seen this item */
424 SetRSSState(IO, eRSSUT);
425 if (CheckIfAlreadySeen("RSS Item Seen",
426 Ctx->ThisMsg->MsgGUID,
428 IO->Now - USETABLE_ANTIEXPIRE_HIRES,
433 /* Item has already been seen */
434 EVRSSC_syslog(LOG_DEBUG,
435 "%s has already been seen\n",
436 ChrPtr(Ctx->ThisMsg->MsgGUID));
437 SetRSSState(IO, eRSSParsing);
439 if (GetNextHashPos(Ctx->Messages,
442 (void**) &Ctx->ThisMsg))
443 return NextDBOperation(
445 RSS_FetchNetworkUsetableEntry);
452 SetRSSState(IO, eRSSParsing);
454 NextDBOperation(IO, RSSSaveMessage);
460 void UpdateLastKnownGood(pRSSConfig *pCfg, time_t now)
462 OneRoomNetCfg* pRNCfg;
463 begin_critical_section(S_NETCONFIGS);
464 pRNCfg = CtdlGetNetCfgForRoom (pCfg->QRnumber);
467 RSSCfgLine *RSSCfg = (RSSCfgLine *)pRNCfg->NetConfigs[rssclient];
469 while (RSSCfg != NULL)
471 if (RSSCfg == pCfg->pCfg)
474 RSSCfg = RSSCfg->next;
479 RSSCfg->last_known_good = now;
483 end_critical_section(S_NETCONFIGS);
486 eNextState RSSAggregator_AnalyseReply(AsyncIO *IO)
492 u_char rawdigest[MD5_DIGEST_LEN];
493 struct MD5Context md5context;
495 rss_aggregator *Ctx = (rss_aggregator *) IO->Data;
497 if (IO->HttpReq.httpcode != 200)
503 SetRSSState(IO, eRSSFailure);
504 ErrMsg = NewStrBuf();
505 EVRSSC_syslog(LOG_ALERT, "need a 200, got a %ld !\n",
506 IO->HttpReq.httpcode);
508 strs[0] = ChrPtr(Ctx->Url);
509 lens[0] = StrLength(Ctx->Url);
511 strs[1] = ChrPtr(Ctx->rooms);
512 lens[1] = StrLength(Ctx->rooms);
514 if (IO->HttpReq.CurlError == NULL)
515 IO->HttpReq.CurlError = "";
518 "Error while RSS-Aggregation Run of %s\n"
519 " need a 200, got a %ld !\n"
520 " Curl Error message: \n%s / %s\n"
521 " Response text was: \n"
524 IO->HttpReq.httpcode,
526 IO->HttpReq.CurlError,
527 ChrPtr(IO->HttpReq.ReplyData)
532 "RSS Aggregation run failure",
533 2, strs, (long*) &lens,
538 EVRSSC_syslog(LOG_DEBUG,
539 "RSS feed returned an invalid http status code. <%s><HTTP %ld>\n",
541 IO->HttpReq.httpcode);
549 UpdateLastKnownGood (pCfg, IO->Now);
550 if ((Ctx->roomlist_parts > 1) &&
553 it = GetNewHashPos(RSSFetchUrls, 0);
558 if (GetNextHashPos(Ctx->OtherQRnumbers, it, &len, &Key, &vptr))
568 SetRSSState(IO, eRSSUT);
570 MD5Init(&md5context);
572 MD5Update(&md5context,
573 (const unsigned char*)SKEY(IO->HttpReq.ReplyData));
575 MD5Update(&md5context,
576 (const unsigned char*)SKEY(Ctx->Url));
578 MD5Final(rawdigest, &md5context);
579 guid = NewStrBufPlain(NULL,
580 MD5_DIGEST_LEN * 2 + 12 /* _rss2ctdl*/);
581 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
582 StrBufAppendBufPlain(guid, HKEY("_rssFM"), 0);
583 if (StrLength(guid) > 40)
584 StrBufCutAt(guid, 40, NULL);
585 /* Find out if we've already seen this item */
589 if (CheckIfAlreadySeen("RSS Whole",
592 IO->Now - USETABLE_ANTIEXPIRE,
599 EVRSSC_syslog(LOG_DEBUG, "RSS feed already seen. <%s>\n", ChrPtr(Ctx->Url));
604 SetRSSState(IO, eRSSParsing);
605 return RSSAggregator_ParseReply(IO);
608 eNextState RSSAggregator_FinishHttp(AsyncIO *IO)
610 return CurlQueueDBOperation(IO, RSSAggregator_AnalyseReply);
616 int rss_do_fetching(rss_aggregator *RSSAggr)
618 AsyncIO *IO = &RSSAggr->IO;
624 if ((RSSAggr->next_poll != 0) && (now < RSSAggr->next_poll))
627 ri = (rss_item*) malloc(sizeof(rss_item));
628 memset(ri, 0, sizeof(rss_item));
631 if (! InitcURLIOStruct(&RSSAggr->IO,
633 "Citadel RSS Client",
634 RSSAggregator_FinishHttp,
635 RSSAggregator_Terminate,
636 RSSAggregator_TerminateDB,
637 RSSAggregator_ShutdownAbort))
639 EVRSSCM_syslog(LOG_ALERT, "Unable to initialize libcurl.\n");
642 SetRSSState(IO, eRSSCreated);
644 safestrncpy(((CitContext*)RSSAggr->IO.CitContext)->cs_host,
645 ChrPtr(RSSAggr->Url),
646 sizeof(((CitContext*)RSSAggr->IO.CitContext)->cs_host));
648 EVRSSC_syslog(LOG_DEBUG, "Fetching RSS feed <%s>\n", ChrPtr(RSSAggr->Url));
649 ParseURL(&RSSAggr->IO.ConnectMe, RSSAggr->Url, 80);
650 CurlPrepareURL(RSSAggr->IO.ConnectMe);
652 SetRSSState(IO, eRSSFetching);
653 QueueCurlContext(&RSSAggr->IO);
658 * Scan a room's netconfig to determine whether it is requesting any RSS feeds
660 void rssclient_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
662 const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient];
663 rss_aggregator *RSSAggr = NULL;
664 rss_aggregator *use_this_RSSAggr = NULL;
667 pthread_mutex_lock(&RSSQueueMutex);
668 if (GetHash(RSSQueueRooms, LKEY(qrbuf->QRnumber), &vptr))
670 EVRSSQ_syslog(LOG_DEBUG,
671 "rssclient: [%ld] %s already in progress.\n",
674 pthread_mutex_unlock(&RSSQueueMutex);
677 pthread_mutex_unlock(&RSSQueueMutex);
679 if (server_shutting_down) return;
681 while (RSSCfg != NULL)
683 pthread_mutex_lock(&RSSQueueMutex);
684 GetHash(RSSFetchUrls,
688 use_this_RSSAggr = (rss_aggregator *)vptr;
689 if (use_this_RSSAggr != NULL)
693 StrBufAppendBufPlain(
694 use_this_RSSAggr->rooms,
697 if (use_this_RSSAggr->roomlist_parts==1)
699 use_this_RSSAggr->OtherQRnumbers
700 = NewHash(1, lFlathash);
703 pRSSCfg = (pRSSConfig *) malloc(sizeof(pRSSConfig));
705 pRSSCfg->QRnumber = qrbuf->QRnumber;
706 pRSSCfg->pCfg = RSSCfg;
708 Put(use_this_RSSAggr->OtherQRnumbers,
709 LKEY(qrbuf->QRnumber),
712 use_this_RSSAggr->roomlist_parts++;
714 pthread_mutex_unlock(&RSSQueueMutex);
716 RSSCfg = RSSCfg->next;
719 pthread_mutex_unlock(&RSSQueueMutex);
721 RSSAggr = (rss_aggregator *) malloc(
722 sizeof(rss_aggregator));
724 memset (RSSAggr, 0, sizeof(rss_aggregator));
725 RSSAggr->Cfg.QRnumber = qrbuf->QRnumber;
726 RSSAggr->Cfg.pCfg = RSSCfg;
727 RSSAggr->roomlist_parts = 1;
728 RSSAggr->Url = NewStrBufDup(RSSCfg->Url);
730 RSSAggr->ItemType = RSS_UNSET;
732 RSSAggr->rooms = NewStrBufPlain(
735 pthread_mutex_lock(&RSSQueueMutex);
742 pthread_mutex_unlock(&RSSQueueMutex);
743 RSSCfg = RSSCfg->next;
748 * Scan for rooms that have RSS client requests configured
750 void rssclient_scan(void) {
751 int RSSRoomCount, RSSCount;
752 rss_aggregator *rptr = NULL;
757 time_t now = time(NULL);
759 /* Run no more than once every 15 minutes. */
760 if ((now - last_run) < 900) {
761 EVRSSQ_syslog(LOG_DEBUG,
762 "Client: polling interval not yet reached; last run was %ldm%lds ago",
763 ((now - last_run) / 60),
764 ((now - last_run) % 60)
770 * This is a simple concurrency check to make sure only one rssclient
771 * run is done at a time.
773 pthread_mutex_lock(&RSSQueueMutex);
774 RSSCount = GetCount(RSSFetchUrls);
775 RSSRoomCount = GetCount(RSSQueueRooms);
776 pthread_mutex_unlock(&RSSQueueMutex);
778 if ((RSSRoomCount > 0) || (RSSCount > 0)) {
779 EVRSSQ_syslog(LOG_DEBUG,
780 "rssclient: concurrency check failed; %d rooms and %d url's are queued",
781 RSSRoomCount, RSSCount
786 become_session(&rss_CC);
787 EVRSSQM_syslog(LOG_DEBUG, "rssclient started");
788 CtdlForEachNetCfgRoom(rssclient_scan_room, NULL, rssclient);
790 if (GetCount(RSSFetchUrls) > 0)
792 pthread_mutex_lock(&RSSQueueMutex);
793 EVRSSQ_syslog(LOG_DEBUG,
794 "rssclient starting %d Clients",
795 GetCount(RSSFetchUrls));
797 it = GetNewHashPos(RSSFetchUrls, 0);
798 while (!server_shutting_down &&
799 GetNextHashPos(RSSFetchUrls, it, &len, &Key, &vrptr) &&
801 rptr = (rss_aggregator *)vrptr;
802 if (!rss_do_fetching(rptr))
803 UnlinkRSSAggregator(rptr);
806 pthread_mutex_unlock(&RSSQueueMutex);
809 EVRSSQM_syslog(LOG_DEBUG, "Nothing to do.");
811 EVRSSQM_syslog(LOG_DEBUG, "rssclient ended\n");
815 void rss_cleanup(void)
817 /* citthread_mutex_destroy(&RSSQueueMutex); TODO */
818 DeleteHash(&RSSFetchUrls);
819 DeleteHash(&RSSQueueRooms);
822 void LogDebugEnableRSSClient(const int n)
824 RSSClientDebugEnabled = n;
828 typedef struct __RSSVetoInfo {
834 void rssclient_veto_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
836 RSSVetoInfo *Info = (RSSVetoInfo *) data;
837 const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient];
839 while (RSSCfg != NULL)
841 if ((RSSCfg->last_known_good != 0) &&
842 (RSSCfg->last_known_good + USETABLE_ANTIEXPIRE < Info->Now))
844 StrBufAppendPrintf(Info->ErrMsg,
845 "RSS feed not seen for a %d days:: <",
846 (Info->Now - RSSCfg->last_known_good) / (24 * 60 * 60));
848 StrBufAppendBuf(Info->ErrMsg, RSSCfg->Url, 0);
849 StrBufAppendBufPlain(Info->ErrMsg, HKEY(">\n"), 0);
851 RSSCfg = RSSCfg->next;
855 int RSSCheckUsetableVeto(StrBuf *ErrMsg)
859 Info.ErrMsg = ErrMsg;
860 Info.Now = time (NULL);
863 CtdlForEachNetCfgRoom(rssclient_veto_scan_room, &Info, rssclient);
871 void ParseRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
875 RSSCfg = (RSSCfgLine *) malloc (sizeof(RSSCfgLine));
876 RSSCfg->Url = NewStrBufPlain (NULL, StrLength (Line));
879 StrBufExtract_NextToken(RSSCfg->Url, Line, &LinePos, '|');
880 RSSCfg->last_known_good = StrBufExtractNext_long(Line, &LinePos, '|');
883 RSSCfg->next = (RSSCfgLine *)OneRNCFG->NetConfigs[ThisOne->C];
884 OneRNCFG->NetConfigs[ThisOne->C] = (RoomNetCfgLine*) RSSCfg;
887 void SerializeRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *RNCfg, RoomNetCfgLine *data)
889 RSSCfgLine *RSSCfg = (RSSCfgLine*) data;
891 StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0);
892 StrBufAppendBufPlain(OutputBuffer, HKEY("|"), 0);
893 StrBufAppendBufPlain(OutputBuffer, SKEY(RSSCfg->Url), 0);
894 StrBufAppendPrintf(OutputBuffer, "|%ld\n", RSSCfg->last_known_good);
897 void DeleteRSSClientCfgLine(const CfgLineType *ThisOne, RoomNetCfgLine **data)
899 RSSCfgLine *RSSCfg = (RSSCfgLine*) *data;
901 FreeStrBuf(&RSSCfg->Url);
907 CTDL_MODULE_INIT(rssclient)
911 CtdlRegisterTDAPVetoHook (RSSCheckUsetableVeto, CDB_USETABLE, 0);
913 CtdlREGISTERRoomCfgType(rssclient, ParseRSSClientCfgLine, 0, 1, SerializeRSSClientCfgLine, DeleteRSSClientCfgLine);
914 pthread_mutex_init(&RSSQueueMutex, NULL);
915 RSSQueueRooms = NewHash(1, lFlathash);
916 RSSFetchUrls = NewHash(1, NULL);
917 syslog(LOG_INFO, "%s\n", curl_version());
918 CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
919 CtdlRegisterEVCleanupHook(rss_cleanup);
920 CtdlRegisterDebugFlagHook(HKEY("rssclient"), LogDebugEnableRSSClient, &RSSClientDebugEnabled);
924 CtdlFillSystemContext(&rss_CC, "rssclient");