2
#include <controller.h>
3
#include <configparser.h>
4
#include <configcontainer.h>
5
#include <exceptions.h>
6
#include <downloadthread.h>
7
#include <colormanager.h>
22
#include <sys/types.h>
27
using namespace newsbeuter;
29
static std::string lock_file = "lock.pid";
31
void ctrl_c_action(int sig) {
32
GetLogger().log(LOG_DEBUG,"caugh signal %d",sig);
34
::unlink(lock_file.c_str());
36
fprintf(stderr,"%s\n", _("Segmentation fault."));
41
controller::controller() : v(0), rsscache(0), url_file("urls"), cache_file("cache.db"), config_file("config"), queue_file("queue"), refresh_on_start(false), cfg(0) {
42
std::ostringstream cfgfile;
45
if (!(cfgdir = ::getenv("HOME"))) {
46
struct passwd * spw = ::getpwuid(::getuid());
50
std::cout << _("Fatal error: couldn't determine home directory!") << std::endl;
52
snprintf(buf, sizeof(buf), _("Please set the HOME environment variable or add a valid user for UID %u!"), ::getuid());
53
std::cout << buf << std::endl;
60
config_dir.append(NEWSBEUTER_PATH_SEP);
61
config_dir.append(NEWSBEUTER_CONFIG_SUBDIR);
62
mkdir(config_dir.c_str(),0700); // create configuration directory if it doesn't exist
64
url_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + url_file;
65
cache_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + cache_file;
66
config_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + config_file;
67
lock_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + lock_file;
68
queue_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + queue_file;
69
reload_mutex = new mutex();
72
controller::~controller() {
78
void controller::set_view(view * vv) {
82
void controller::run(int argc, char * argv[]) {
86
::signal(SIGINT, ctrl_c_action);
88
::signal(SIGSEGV, ctrl_c_action);
91
bool do_import = false, do_export = false;
92
std::string importfile;
95
if((c = ::getopt(argc,argv,"i:erhu:c:C:d:l:"))<0)
98
case ':': /* fall-through */
99
case '?': /* missing option */
109
refresh_on_start = true;
126
config_file = optarg;
128
case 'd': // this is an undocumented debug commandline option!
129
GetLogger().set_logfile(optarg);
131
case 'l': // this is an undocumented debug commandline option!
133
loglevel level = static_cast<loglevel>(atoi(optarg));
134
if (level > LOG_NONE && level <= LOG_DEBUG)
135
GetLogger().set_loglevel(level);
139
snprintf(msgbuf, sizeof(msgbuf), _("%s: unknown option - %c"), argv[0], static_cast<char>(c));
140
std::cout << msgbuf << std::endl;
146
urlcfg.load_config(url_file);
149
GetLogger().log(LOG_INFO,"Importing OPML file from %s",importfile.c_str());
150
import_opml(importfile.c_str());
154
if (urlcfg.get_urls().size() == 0) {
155
GetLogger().log(LOG_ERROR,"no URLs configured.");
156
snprintf(msgbuf, sizeof(msgbuf), _("Error: no URLs configured. Please fill the file %s with RSS feed URLs or import an OPML file."), url_file.c_str());
157
std::cout << msgbuf << std::endl << std::endl;
162
snprintf(msgbuf, sizeof(msgbuf), _("Starting %s %s..."), PROGRAM_NAME, PROGRAM_VERSION);
163
std::cout << msgbuf << std::endl;
166
if (!utils::try_fs_lock(lock_file, pid)) {
167
GetLogger().log(LOG_ERROR,"an instance is alredy running: pid = %u",pid);
168
snprintf(msgbuf, sizeof(msgbuf), _("Error: an instance of %s is already running (PID: %u)"), PROGRAM_NAME, pid);
169
std::cout << msgbuf << std::endl;
175
std::cout << _("Loading configuration...");
178
configparser cfgparser(config_file.c_str());
179
cfg = new configcontainer();
180
cfg->register_commands(cfgparser);
181
colormanager * colorman = new colormanager();
182
colorman->register_commands(cfgparser);
185
cfgparser.register_handler("bind-key",&keys);
186
cfgparser.register_handler("unbind-key",&keys);
190
} catch (const configexception& ex) {
191
GetLogger().log(LOG_ERROR,"an exception occured while parsing the configuration file: %s",ex.what());
192
std::cout << ex.what() << std::endl;
193
utils::remove_fs_lock(lock_file);
197
if (colorman->colors_loaded())
198
colorman->set_colors(v);
202
std::cout << _("done.") << std::endl;
205
std::cout << _("Loading articles from cache...");
208
rsscache = new cache(cache_file,cfg);
210
for (std::vector<std::string>::const_iterator it=urlcfg.get_urls().begin(); it != urlcfg.get_urls().end(); ++it) {
211
rss_feed feed(rsscache);
212
feed.set_rssurl(*it);
213
feed.set_tags(urlcfg.get_tags(*it));
214
rsscache->internalize_rssfeed(feed);
215
feeds.push_back(feed);
218
std::vector<std::string> tags = urlcfg.get_alltags();
221
std::cout << _("done.") << std::endl;
225
utils::remove_fs_lock(lock_file);
229
v->set_config_container(cfg);
230
v->set_keymap(&keys);
231
v->set_feedlist(feeds);
232
v->run_feedlist(tags);
234
std::cout << _("Cleaning up cache...");
236
rsscache->cleanup_cache(feeds);
238
for (std::vector<rss_feed>::iterator it=feeds.begin(); it != feeds.end(); ++it) {
239
// rsscache->externalize_rssfeed(*it);
242
std::cout << _("done.") << std::endl;
244
utils::remove_fs_lock(lock_file);
247
void controller::update_feedlist() {
248
v->set_feedlist(feeds);
251
bool controller::open_item(rss_feed& feed, std::string guid) {
252
bool show_next_unread = v->run_itemview(feed, guid);
253
feed.get_item_by_guid(guid).set_unread(false);
254
return show_next_unread;
257
void controller::catchup_all() {
258
rsscache->catchup_all();
259
for (std::vector<rss_feed>::iterator it=feeds.begin();it!=feeds.end();++it) {
260
if (it->items().size() > 0) {
261
for (std::vector<rss_item>::iterator jt=it->items().begin();jt!=it->items().end();++jt) {
262
jt->set_unread_nowrite(false);
268
void controller::mark_all_read(unsigned int pos) {
269
if (pos < feeds.size()) {
270
rss_feed& feed = feeds[pos];
271
rsscache->catchup_all(feed.rssurl());
272
if (feed.items().size() > 0) {
273
for (std::vector<rss_item>::iterator it=feed.items().begin();it!=feed.items().end();++it) {
274
it->set_unread_nowrite(false);
280
bool controller::open_feed(unsigned int pos, bool auto_open) {
282
if (pos < feeds.size()) {
284
v->set_status(_("Opening feed..."));
286
rss_feed& feed = feeds[pos];
291
if (feed.items().size() == 0) {
292
v->show_error(_("Error: feed contains no items!"));
294
retval = v->run_itemlist(pos, auto_open);
295
v->set_feedlist(feeds);
298
v->show_error(_("Error: invalid feed!"));
303
void controller::reload(unsigned int pos, unsigned int max) {
305
if (pos < feeds.size()) {
306
rss_feed feed = feeds[pos];
310
std::ostringstream posstr;
312
msg.append(posstr.str());
314
std::ostringstream maxstr;
316
msg.append(maxstr.str());
319
snprintf(msgbuf, sizeof(msgbuf), _("%sLoading %s..."), msg.c_str(), feed.rssurl().c_str());
320
v->set_status(msgbuf);
322
rss_parser parser(feed.rssurl().c_str(), rsscache, cfg);
324
feed = parser.parse();
326
rsscache->externalize_rssfeed(feed);
328
rsscache->internalize_rssfeed(feed);
329
feed.set_tags(urlcfg.get_tags(feed.rssurl()));
332
for (std::vector<rss_item>::iterator it=feed.items().begin();it!=feed.items().end();++it) {
333
if (cfg->get_configvalue_as_bool("podcast-auto-enqueue") && !it->enqueued() && it->enclosure_url().length() > 0) {
334
GetLogger().log(LOG_DEBUG, "controller::reload: enclosure_url = `%s' enclosure_type = `%s'", it->enclosure_url().c_str(), it->enclosure_type().c_str());
335
if (is_valid_podcast_type(it->enclosure_type())) {
336
GetLogger().log(LOG_INFO, "controller::reload: enqueuing `%s'", it->enclosure_url().c_str());
337
enqueue_url(it->enclosure_url());
338
it->set_enqueued(true);
339
rsscache->update_rssitem_unread_and_enqueued(*it, feed.rssurl());
345
v->set_feedlist(feeds);
347
} catch (const std::string& errmsg) {
349
snprintf(buf, sizeof(buf), _("Error while retrieving %s: %s"), feed.rssurl().c_str(), errmsg.c_str());
353
v->show_error(_("Error: invalid feed!"));
357
rss_feed& controller::get_feed(unsigned int pos) {
358
if (pos >= feeds.size()) {
359
throw std::out_of_range(_("invalid feed index (bug)"));
364
void controller::reload_all() {
365
for (unsigned int i=0;i<feeds.size();++i) {
366
this->reload(i,feeds.size());
370
void controller::start_reload_all_thread() {
371
if (reload_mutex->trylock()) {
372
GetLogger().log(LOG_INFO,"starting reload all thread");
373
thread * dlt = new downloadthread(this);
376
GetLogger().log(LOG_INFO,"reload mutex is currently locked");
380
void controller::usage(char * argv0) {
382
snprintf(buf, sizeof(buf),
383
_("%s %s\nusage: %s [-i <file>|-e] [-u <urlfile>] [-c <cachefile>] [-h]\n"
384
"-e export OPML feed to stdout\n"
385
"-r refresh feeds on start\n"
386
"-i <file> import OPML file\n"
387
"-u <urlfile> read RSS feed URLs from <urlfile>\n"
388
"-c <cachefile> use <cachefile> as cache file\n"
389
"-C <configfile> read configuration from <configfile>\n"
390
"-h this help\n"), PROGRAM_NAME, PROGRAM_VERSION, argv0);
392
::exit(EXIT_FAILURE);
395
void controller::import_opml(const char * filename) {
397
nxml_data_t * root, * body;
400
ret = nxml_new (&data);
401
if (ret != NXML_OK) {
402
puts (nxml_strerror (ret));
406
ret = nxml_parse_file (data, const_cast<char *>(filename));
407
if (ret != NXML_OK) {
408
puts (nxml_strerror (ret));
412
nxml_root_element (data, &root);
415
body = nxmle_find_element(data, root, "body", NULL);
417
rec_find_rss_outlines(body, "");
418
urlcfg.write_config();
424
snprintf(buf, sizeof(buf), _("Import of %s finished."), filename);
425
std::cout << buf << std::endl;
428
void controller::export_opml() {
429
std::cout << "<?xml version=\"1.0\"?>" << std::endl;
430
std::cout << "<opml version=\"1.0\">" << std::endl;
431
std::cout << "\t<head>" << std::endl << "\t\t<title>" PROGRAM_NAME " - Exported Feeds</title>" << std::endl << "\t</head>" << std::endl;
432
std::cout << "\t<body>" << std::endl;
433
for (std::vector<rss_feed>::iterator it=feeds.begin(); it != feeds.end(); ++it) {
434
std::cout << "\t\t<outline type=\"rss\" xmlUrl=\"" << it->rssurl() << "\" title=\"" << it->title() << "\" />" << std::endl;
436
std::cout << "\t</body>" << std::endl;
437
std::cout << "</opml>" << std::endl;
440
void controller::rec_find_rss_outlines(nxml_data_t * node, std::string tag) {
442
char * url = nxmle_find_attribute(node, "xmlUrl", NULL);
443
char * type = nxmle_find_attribute(node, "type", NULL);
444
std::string newtag = tag;
447
url = nxmle_find_attribute(node, "url", NULL);
450
if (node->type == NXML_TYPE_ELEMENT && strcmp(node->value,"outline")==0) {
451
if (type && (strcmp(type,"rss")==0 || strcmp(type,"link")==0)) {
454
GetLogger().log(LOG_DEBUG,"OPML import: found RSS outline with url = %s",url);
458
for (std::vector<std::string>::iterator it = urlcfg.get_urls().begin(); it != urlcfg.get_urls().end(); ++it) {
465
GetLogger().log(LOG_DEBUG,"OPML import: added url = %s",url);
466
urlcfg.get_urls().push_back(std::string(url));
467
if (tag.length() > 0) {
468
GetLogger().log(LOG_DEBUG, "OPML import: appending tag %s to url %s", tag.c_str(), url);
469
urlcfg.get_tags(url).push_back(tag);
472
GetLogger().log(LOG_DEBUG,"OPML import: url = %s is already in list",url);
476
char * text = nxmle_find_attribute(node, "text", NULL);
478
if (newtag.length() > 0) {
485
rec_find_rss_outlines(node->children, newtag);
493
std::vector<rss_item> controller::search_for_items(const std::string& query, const std::string& feedurl) {
494
return rsscache->search_for_items(query, feedurl);
497
rss_feed controller::get_feed_by_url(const std::string& feedurl) {
498
return rsscache->get_feed_by_url(feedurl);
501
bool controller::is_valid_podcast_type(const std::string& mimetype) {
502
return mimetype == "audio/mpeg" || mimetype == "video/x-m4v" || mimetype == "audio/x-mpeg";
505
void controller::enqueue_url(const std::string& url) {
506
bool url_found = false;
508
f.open(queue_file.c_str(), std::fstream::in);
513
if (!f.eof() && line.length() > 0) {
523
f.open(queue_file.c_str(), std::fstream::app | std::fstream::out);
524
f << url << std::endl;