mirror of https://github.com/BOINC/boinc.git
715 lines
19 KiB
C++
715 lines
19 KiB
C++
// This file is part of BOINC.
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2009 University of California
|
|
//
|
|
// BOINC is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation,
|
|
// either version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// BOINC is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include <string>
|
|
#include <set>
|
|
|
|
#include "parse.h"
|
|
#include "url.h"
|
|
#include "filesys.h"
|
|
|
|
#include "client_state.h"
|
|
#include "client_msgs.h"
|
|
#include "file_names.h"
|
|
|
|
#include "cs_notice.h"
|
|
|
|
using std::vector;
|
|
using std::string;
|
|
using std::set;
|
|
using std::deque;
|
|
|
|
NOTICES notices;
|
|
RSS_FEEDS rss_feeds;
|
|
RSS_FEED_OP rss_feed_op;
|
|
|
|
////////////// UTILITY FUNCTIONS ///////////////
|
|
|
|
static bool cmp(NOTICE n1, NOTICE n2) {
|
|
if (n1.arrival_time < n2.arrival_time) return true;
|
|
if (n1.arrival_time > n2.arrival_time) return false;
|
|
return (strcmp(n1.guid, n2.guid) > 0);
|
|
}
|
|
|
|
static void project_feed_list_file_name(PROJECT* p, char* buf) {
|
|
char url[256];
|
|
escape_project_url(p->master_url, url);
|
|
sprintf(buf, "notices/feeds_%s.xml", url);
|
|
}
|
|
|
|
// parse feed descs from scheduler reply or feed list file
|
|
//
|
|
int parse_rss_feed_descs(MIOFILE& fin, vector<RSS_FEED>& feeds) {
|
|
char tag[256];
|
|
bool is_tag;
|
|
XML_PARSER xp(&fin);
|
|
int retval;
|
|
|
|
while (!xp.get(tag, sizeof(tag), is_tag)) {
|
|
if (!is_tag) continue;
|
|
if (!strcmp(tag, "/rss_feeds")) return 0;
|
|
if (!strcmp(tag, "rss_feed")) {
|
|
RSS_FEED rf;
|
|
retval = rf.parse_desc(xp);
|
|
if (retval) {
|
|
if (log_flags.sched_op_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[sched_op_debug] error in <rss_feed> element"
|
|
);
|
|
}
|
|
} else {
|
|
feeds.push_back(rf);
|
|
}
|
|
}
|
|
}
|
|
return ERR_XML_PARSE;
|
|
}
|
|
|
|
// write a list of feeds to a file
|
|
//
|
|
static void write_rss_feed_descs(MIOFILE& fout, vector<RSS_FEED>& feeds) {
|
|
if (!feeds.size()) return;
|
|
fout.printf("<rss_feeds>\n");
|
|
for (unsigned int i=0; i<feeds.size(); i++) {
|
|
feeds[i].write(fout);
|
|
}
|
|
fout.printf("</rss_feeds>\n");
|
|
}
|
|
|
|
static void write_project_feed_list(PROJECT* p) {
|
|
char buf[256];
|
|
project_feed_list_file_name(p, buf);
|
|
FILE* f = fopen(buf, "w");
|
|
if (!f) return;
|
|
MIOFILE fout;
|
|
fout.init_file(f);
|
|
write_rss_feed_descs(fout, p->proj_feeds);
|
|
fclose(f);
|
|
}
|
|
|
|
// A scheduler RPC returned a list (possibly empty) of feeds.
|
|
// Add new ones to the project's set,
|
|
// and remove ones from the project's set that aren't in the list.
|
|
//
|
|
void handle_sr_feeds(vector<RSS_FEED>& feeds, PROJECT* p) {
|
|
unsigned int i, j;
|
|
bool feed_set_changed = false;
|
|
|
|
// mark current feeds as not found
|
|
//
|
|
for (i=0; i<p->proj_feeds.size(); i++) {
|
|
p->proj_feeds[i].found = false;
|
|
}
|
|
|
|
for (i=0; i<feeds.size(); i++) {
|
|
RSS_FEED& rf = feeds[i];
|
|
bool present = false;
|
|
for (j=0; j<p->proj_feeds.size(); j++) {
|
|
RSS_FEED& rf2 = p->proj_feeds[j];
|
|
if (!strcmp(rf.url, rf2.url)) {
|
|
rf2 = rf;
|
|
present = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!present) {
|
|
rf.found = true;
|
|
p->proj_feeds.push_back(rf);
|
|
feed_set_changed = true;
|
|
}
|
|
}
|
|
|
|
// remove ones no longer present
|
|
//
|
|
vector<RSS_FEED>::iterator iter = p->proj_feeds.begin();
|
|
while (iter != p->proj_feeds.end()) {
|
|
RSS_FEED& rf = *iter;
|
|
if (rf.found) {
|
|
iter++;
|
|
} else {
|
|
iter = p->proj_feeds.erase(iter);
|
|
feed_set_changed = true;
|
|
}
|
|
}
|
|
|
|
// if anything was added or removed, update master set
|
|
//
|
|
if (feed_set_changed) {
|
|
write_project_feed_list(p);
|
|
rss_feeds.update_feed_list();
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// compensate for lameness
|
|
static int month_index(char* x) {
|
|
if (strstr(x, "Jan")) return 0;
|
|
if (strstr(x, "Feb")) return 1;
|
|
if (strstr(x, "Mar")) return 2;
|
|
if (strstr(x, "Apr")) return 3;
|
|
if (strstr(x, "May")) return 4;
|
|
if (strstr(x, "Jun")) return 5;
|
|
if (strstr(x, "Jul")) return 6;
|
|
if (strstr(x, "Aug")) return 7;
|
|
if (strstr(x, "Sep")) return 8;
|
|
if (strstr(x, "Oct")) return 9;
|
|
if (strstr(x, "Nov")) return 10;
|
|
if (strstr(x, "Dev")) return 11;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_rss_time(char* buf) {
|
|
char day_name[64], month_name[64];
|
|
int day_num, year, h, m, s;
|
|
sscanf(buf, "%s %d %s %d %d:%d:%d",
|
|
day_name, &day_num, month_name, &year, &h, &m, &s
|
|
);
|
|
|
|
struct tm tm;
|
|
tm.tm_sec = s;
|
|
tm.tm_min = m;
|
|
tm.tm_hour = h;
|
|
tm.tm_mday = day_num;
|
|
tm.tm_mon = month_index(month_name);
|
|
tm.tm_year = year-1900;
|
|
tm.tm_wday = 0;
|
|
tm.tm_yday = 0;
|
|
tm.tm_isdst = 0;
|
|
|
|
int t = (int)mktime(&tm);
|
|
t -= gstate.host_info.timezone;
|
|
return t;
|
|
}
|
|
#endif
|
|
|
|
///////////// NOTICE ////////////////
|
|
|
|
int NOTICE::parse_rss(XML_PARSER& xp) {
|
|
char tag[1024], buf[256];
|
|
bool is_tag;
|
|
|
|
clear();
|
|
while (!xp.get(tag, sizeof(tag), is_tag)) {
|
|
if (!is_tag) continue;
|
|
if (!strcmp(tag, "/item")) return 0;
|
|
if (xp.parse_str(tag, "title", title, sizeof(title))) continue;
|
|
if (xp.parse_str(tag, "link", link, sizeof(link))) continue;
|
|
if (xp.parse_str(tag, "guid", guid, sizeof(guid))) continue;
|
|
if (xp.parse_string(tag, "description", description)) continue;
|
|
if (xp.parse_str(tag, "pubDate", buf, sizeof(buf))) {
|
|
#ifdef _WIN32
|
|
create_time = parse_rss_time(buf);
|
|
#else
|
|
struct tm tm;
|
|
strptime(buf, "%a, %d %b %Y %H:%M:%S", &tm);
|
|
create_time = mktime(&tm);
|
|
#endif
|
|
continue;
|
|
}
|
|
}
|
|
return ERR_XML_PARSE;
|
|
}
|
|
|
|
///////////// NOTICES ////////////////
|
|
|
|
void NOTICES::init() {
|
|
read_archive_file(NOTICES_DIR"/archive.xml", NULL);
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO, "read %d BOINC notices", (int)notices.size());
|
|
}
|
|
write_archive(NULL);
|
|
}
|
|
|
|
void NOTICES::init_rss() {
|
|
rss_feeds.init();
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO, "read %d total notices", (int)notices.size());
|
|
}
|
|
|
|
// sort by decreasing arrival time, then assign seqnos
|
|
//
|
|
sort(notices.begin(), notices.end(), cmp);
|
|
unsigned int n = notices.size();
|
|
for (unsigned int i=0; i<n; i++) {
|
|
notices[i].seqno = n - i;
|
|
}
|
|
}
|
|
|
|
static inline bool equivalent(NOTICE& n1, NOTICE& n2) {
|
|
if (strcmp(n1.title, n2.title)) return false;
|
|
if (n1.description != n2.description) return false;
|
|
return true;
|
|
}
|
|
|
|
// we're considering adding a notice n.
|
|
// See if an equivalent notice n2 is already there; if so:
|
|
// keep_old: return false
|
|
// !keep_old: delete n2
|
|
//
|
|
bool NOTICES::remove_dups(NOTICE& n, bool keep_old) {
|
|
deque<NOTICE>::iterator i = notices.begin();
|
|
bool removed_something = false;
|
|
while (i != notices.end()) {
|
|
NOTICE& n2 = *i;
|
|
if (equivalent(n, n2) || (n2.arrival_time < gstate.now - 30*86400)) {
|
|
if (keep_old) {
|
|
return false;
|
|
}
|
|
i = notices.erase(i);
|
|
removed_something = true;
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
if (removed_something) {
|
|
gstate.gui_rpcs.set_notice_refresh();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NOTICES::append(NOTICE& n, bool keep_old, bool archive) {
|
|
if (!remove_dups(n, keep_old)) {
|
|
return false;
|
|
}
|
|
if (notices.empty()) {
|
|
n.seqno = 1;
|
|
} else {
|
|
n.seqno = notices.front().seqno + 1;
|
|
}
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] appending notice: %s",
|
|
n.title
|
|
);
|
|
}
|
|
notices.push_front(n);
|
|
if (archive && !strlen(n.feed_url)) {
|
|
write_archive(NULL);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// read and parse the contents of an archive file.
|
|
// If feed_url is empty it's a system msg, else a feed msg.
|
|
// insert items in NOTICES
|
|
//
|
|
int NOTICES::read_archive_file(char* path, char* feed_url) {
|
|
char tag[256];
|
|
bool is_tag;
|
|
|
|
FILE* f = fopen(path, "r");
|
|
if (!f) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] no archive file %s", path
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
MIOFILE fin;
|
|
fin.init_file(f);
|
|
XML_PARSER xp(&fin);
|
|
while (!xp.get(tag, sizeof(tag), is_tag)) {
|
|
if (!is_tag) continue;
|
|
if (!strcmp(tag, "/notices")) {
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
if (!strcmp(tag, "notice")) {
|
|
NOTICE n;
|
|
int retval = n.parse(xp);
|
|
if (retval) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] archive item parse error: %d", retval
|
|
);
|
|
}
|
|
} else {
|
|
if (feed_url) {
|
|
strcpy(n.feed_url, feed_url);
|
|
}
|
|
append(n, false, false);
|
|
}
|
|
}
|
|
}
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO, "[notice_debug] archive parse error");
|
|
}
|
|
fclose(f);
|
|
return ERR_XML_PARSE;
|
|
}
|
|
|
|
// write archive file for the given RSS feed
|
|
// (or, if NULL, non-RSS notices)
|
|
//
|
|
void NOTICES::write_archive(RSS_FEED* rfp) {
|
|
char path[256];
|
|
|
|
if (rfp) {
|
|
rfp->archive_file_name(path);
|
|
} else {
|
|
strcpy(path, NOTICES_DIR"/archive.xml");
|
|
}
|
|
FILE* f = fopen(path, "w");
|
|
if (!f) return;
|
|
MIOFILE fout;
|
|
fout.init_file(f);
|
|
fout.printf("<notices>\n");
|
|
if (!f) return;
|
|
for (unsigned int i=0; i<notices.size(); i++) {
|
|
NOTICE& n = notices[i];
|
|
if (rfp) {
|
|
if (strcmp(rfp->url, n.feed_url)) continue;
|
|
} else {
|
|
if (strlen(n.feed_url)) continue;
|
|
}
|
|
n.write(fout, false);
|
|
}
|
|
fout.printf("</notices>\n");
|
|
fclose(f);
|
|
}
|
|
|
|
// write notices newer than seqno as XML (for GUI RPC).
|
|
// Write them in order of increasing seqno
|
|
//
|
|
void NOTICES::write(int seqno, MIOFILE& fout, bool public_only, bool notice_refresh) {
|
|
unsigned int i;
|
|
fout.printf("<notices>\n");
|
|
if (notice_refresh) {
|
|
NOTICE n;
|
|
n.seqno = -1;
|
|
seqno = -1;
|
|
i = notices.size();
|
|
} else {
|
|
for (i=0; i<notices.size(); i++) {
|
|
NOTICE& n = notices[i];
|
|
if (n.seqno <= seqno) break;
|
|
}
|
|
}
|
|
for (; i>0; i--) {
|
|
NOTICE& n = notices[i-1];
|
|
if (public_only && n.is_private) continue;
|
|
n.write(fout, true);
|
|
}
|
|
fout.printf("</notices>\n");
|
|
}
|
|
|
|
///////////// RSS_FEED ////////////////
|
|
|
|
void RSS_FEED::feed_file_name(char* path) {
|
|
char buf[256];
|
|
escape_project_url(url_base, buf);
|
|
sprintf(path, NOTICES_DIR"/%s", buf);
|
|
}
|
|
|
|
void RSS_FEED::archive_file_name(char* path) {
|
|
char buf[256];
|
|
escape_project_url(url_base, buf);
|
|
sprintf(path, NOTICES_DIR"/archive_%s", buf);
|
|
}
|
|
|
|
// read and parse the contents of the archive file;
|
|
// insert items in NOTICES
|
|
//
|
|
int RSS_FEED::read_archive_file() {
|
|
char path[256];
|
|
archive_file_name(path);
|
|
return notices.read_archive_file(path, url);
|
|
}
|
|
|
|
// parse a feed descriptor (in scheduler reply or feed list file)
|
|
//
|
|
int RSS_FEED::parse_desc(XML_PARSER& xp) {
|
|
char tag[256];
|
|
bool is_tag;
|
|
strcpy(url, "");
|
|
poll_interval = 0;
|
|
next_poll_time = 0;
|
|
use_seqno = false;
|
|
strcpy(last_seqno, "");
|
|
while (!xp.get(tag, sizeof(tag), is_tag)) {
|
|
if (!is_tag) continue;
|
|
if (!strcmp(tag, "/rss_feed")) {
|
|
if (!poll_interval || !strlen(url)) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] URL or poll interval missing in sched reply feed"
|
|
);
|
|
}
|
|
return ERR_XML_PARSE;
|
|
}
|
|
strcpy(url_base, url);
|
|
char* p = strchr(url_base, '?');
|
|
if (p) *p = 0;
|
|
return 0;
|
|
}
|
|
if (xp.parse_str(tag, "url", url, sizeof(url))) continue;
|
|
if (xp.parse_double(tag, "poll_interval", poll_interval)) continue;
|
|
if (xp.parse_double(tag, "next_poll_time", next_poll_time)) continue;
|
|
if (xp.parse_bool(tag, "use_seqno", use_seqno)) continue;
|
|
if (xp.parse_str(tag, "last_seqno", last_seqno, sizeof(last_seqno))) continue;
|
|
}
|
|
return ERR_XML_PARSE;
|
|
}
|
|
|
|
void RSS_FEED::write(MIOFILE& fout) {
|
|
fout.printf(
|
|
" <rss_feed>\n"
|
|
" <url>%s</url>\n"
|
|
" <poll_interval>%f</poll_interval>\n"
|
|
" <next_poll_time>%f</next_poll_time>\n"
|
|
" <use_seqno>%d</use_seqno>\n"
|
|
" <last_seqno>%s</last_seqno>\n"
|
|
" </rss_feed>\n",
|
|
url,
|
|
poll_interval,
|
|
next_poll_time,
|
|
use_seqno?1:0,
|
|
last_seqno
|
|
);
|
|
}
|
|
|
|
// parse the actual RSS feed.
|
|
//
|
|
int RSS_FEED::parse_items(XML_PARSER& xp, int& nitems) {
|
|
char tag[256];
|
|
bool is_tag;
|
|
nitems = 0;
|
|
int ntotal = 0, nerror = 0;
|
|
while (!xp.get(tag, sizeof(tag), is_tag)) {
|
|
if (!is_tag) continue;
|
|
if (!strcmp(tag, "/rss")) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] parsed RSS feed: total %d error %d added %d",
|
|
ntotal, nerror, nitems
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
if (!strcmp(tag, "item")) {
|
|
NOTICE n;
|
|
ntotal++;
|
|
int retval = n.parse_rss(xp);
|
|
if (retval) {
|
|
nerror++;
|
|
} else {
|
|
n.arrival_time = gstate.now;
|
|
strcpy(n.feed_url, url);
|
|
if (notices.append(n, true, false)) {
|
|
nitems++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (xp.parse_str(tag, "seqno", last_seqno, sizeof(last_seqno))) {
|
|
continue;
|
|
}
|
|
}
|
|
return ERR_XML_PARSE;
|
|
}
|
|
|
|
///////////// RSS_FEED_OP ////////////////
|
|
|
|
RSS_FEED_OP::RSS_FEED_OP() {
|
|
error_num = BOINC_SUCCESS;
|
|
gui_http = &gstate.gui_http;
|
|
}
|
|
|
|
// see if time to start new fetch
|
|
//
|
|
bool RSS_FEED_OP::poll() {
|
|
unsigned int i;
|
|
if (gstate.gui_http.is_busy()) return false;
|
|
for (i=0; i<rss_feeds.feeds.size(); i++) {
|
|
RSS_FEED& rf = rss_feeds.feeds[i];
|
|
if (gstate.now > rf.next_poll_time) {
|
|
rf.next_poll_time = gstate.now + rf.poll_interval;
|
|
char filename[256];
|
|
rf.feed_file_name(filename);
|
|
rfp = &rf;
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] start fetch from %s", rf.url
|
|
);
|
|
}
|
|
char url[256];
|
|
if (rf.use_seqno) {
|
|
sprintf(url, "%s?seqno=%s", rf.url, rf.last_seqno);
|
|
} else {
|
|
strcpy(url, rf.url);
|
|
}
|
|
gstate.gui_http.do_rpc(this, url, filename);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// handle a completed RSS feed fetch
|
|
//
|
|
void RSS_FEED_OP::handle_reply(int http_op_retval) {
|
|
char filename[256];
|
|
int nitems;
|
|
|
|
if (http_op_retval) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] fetch of %s failed: %d", rfp->url, http_op_retval
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] handling reply from %s", rfp->url
|
|
);
|
|
}
|
|
|
|
rfp->feed_file_name(filename);
|
|
FILE* f = fopen(filename, "r");
|
|
MIOFILE fin;
|
|
fin.init_file(f);
|
|
XML_PARSER xp(&fin);
|
|
int retval = rfp->parse_items(xp, nitems);
|
|
if (retval) {
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] RSS parse error: %d", retval
|
|
);
|
|
}
|
|
}
|
|
fclose(f);
|
|
|
|
if (nitems) {
|
|
notices.write_archive(rfp);
|
|
}
|
|
}
|
|
|
|
///////////// RSS_FEEDS ////////////////
|
|
|
|
// called on startup. Get list of feeds. Read archives.
|
|
//
|
|
void RSS_FEEDS::init() {
|
|
unsigned int i;
|
|
MIOFILE fin;
|
|
FILE* f;
|
|
|
|
boinc_mkdir(NOTICES_DIR);
|
|
f = fopen(NOTICES_DIR"/feeds.xml", "r");
|
|
if (f) {
|
|
fin.init_file(f);
|
|
parse_rss_feed_descs(fin, feeds);
|
|
fclose(f);
|
|
}
|
|
|
|
for (i=0; i<gstate.projects.size(); i++) {
|
|
PROJECT* p = gstate.projects[i];
|
|
char path[256];
|
|
project_feed_list_file_name(p, path);
|
|
f = fopen(path, "r");
|
|
if (f) {
|
|
fin.init_file(f);
|
|
parse_rss_feed_descs(fin, p->proj_feeds);
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
// normally the main list is union of the project lists.
|
|
// if it's not, the following will fix
|
|
//
|
|
update_feed_list();
|
|
|
|
for (i=0; i<feeds.size(); i++) {
|
|
RSS_FEED& rf = feeds[i];
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] feed: %s, %.0f sec",
|
|
rf.url, rf.poll_interval
|
|
);
|
|
}
|
|
rf.read_archive_file();
|
|
}
|
|
}
|
|
|
|
RSS_FEED* RSS_FEEDS::lookup_url(char* url) {
|
|
for (unsigned int i=0; i<feeds.size(); i++) {
|
|
RSS_FEED& rf = feeds[i];
|
|
if (!strcmp(rf.url, url)) {
|
|
return &rf;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// the set of project feeds has changed.
|
|
// update the master list.
|
|
//
|
|
void RSS_FEEDS::update_feed_list() {
|
|
unsigned int i, j;
|
|
for (i=0; i<feeds.size(); i++) {
|
|
RSS_FEED& rf = feeds[i];
|
|
rf.found = false;
|
|
}
|
|
for (i=0; i<gstate.projects.size(); i++) {
|
|
PROJECT* p = gstate.projects[i];
|
|
for (j=0; j<p->proj_feeds.size(); j++) {
|
|
RSS_FEED& rf = p->proj_feeds[j];
|
|
RSS_FEED* rfp = lookup_url(rf.url);
|
|
if (rfp) {
|
|
rfp->found = true;
|
|
} else {
|
|
rf.found = true;
|
|
feeds.push_back(rf);
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] adding feed: %s, %.0f sec",
|
|
rf.url, rf.poll_interval
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vector<RSS_FEED>::iterator iter = feeds.begin();
|
|
while (iter != feeds.end()) {
|
|
RSS_FEED& rf = *iter;
|
|
if (rf.found) {
|
|
iter++;
|
|
} else {
|
|
// TODO: check if fetch in progress!
|
|
if (log_flags.notice_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[notice_debug] removing feed: %s",
|
|
rf.url
|
|
);
|
|
}
|
|
iter = feeds.erase(iter);
|
|
}
|
|
}
|
|
write_feed_list();
|
|
}
|
|
|
|
void RSS_FEEDS::write_feed_list() {
|
|
FILE* f = fopen(NOTICES_DIR"/feeds.xml", "w");
|
|
if (!f) return;
|
|
MIOFILE fout;
|
|
fout.init_file(f);
|
|
write_rss_feed_descs(fout, feeds);
|
|
fclose(f);
|
|
}
|