34
35
using namespace newsbeuter;
36
static std::string lock_file = "lock.pid";
37
#define LOCK_SUFFIX ".lock"
39
static std::string lock_file;
38
41
void ctrl_c_action(int sig) {
39
42
GetLogger().log(LOG_DEBUG,"caugh signal %d",sig);
55
58
while ((pid = waitpid(-1,&stat,WNOHANG)) > 0) { }
58
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) {
59
std::ostringstream cfgfile;
61
controller::controller() : v(0), urlcfg(0), rsscache(0), url_file("urls"), cache_file("cache.db"), config_file("config"), queue_file("queue"), refresh_on_start(false), cfg(0) {
62
63
if (!(cfgdir = ::getenv("HOME"))) {
63
64
struct passwd * spw = ::getpwuid(::getuid());
65
66
cfgdir = spw->pw_dir;
67
68
std::cout << _("Fatal error: couldn't determine home directory!") << std::endl;
69
snprintf(buf, sizeof(buf), _("Please set the HOME environment variable or add a valid user for UID %u!"), ::getuid());
70
std::cout << buf << std::endl;
69
std::cout << utils::strprintf(_("Please set the HOME environment variable or add a valid user for UID %u!"), ::getuid()) << std::endl;
71
70
::exit(EXIT_FAILURE);
74
73
config_dir = cfgdir;
77
75
config_dir.append(NEWSBEUTER_PATH_SEP);
78
76
config_dir.append(NEWSBEUTER_CONFIG_SUBDIR);
79
77
mkdir(config_dir.c_str(),0700); // create configuration directory if it doesn't exist
81
url_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + url_file;
82
cache_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + cache_file;
83
config_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + config_file;
84
lock_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + lock_file;
85
queue_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + queue_file;
86
79
reload_mutex = new mutex();
99
93
void controller::run(int argc, char * argv[]) {
96
url_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + url_file;
97
cache_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + cache_file;
98
lock_file = cache_file + LOCK_SUFFIX;
99
config_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + config_file;
100
queue_file = config_dir + std::string(NEWSBEUTER_PATH_SEP) + queue_file;
103
102
::signal(SIGINT, ctrl_c_action);
112
111
bool offline_mode = false, real_offline_mode = false;
113
112
std::string importfile;
115
bool execute_cmds = false;
116
if((c = ::getopt(argc,argv,"i:erhu:c:C:d:l:vVo"))<0)
118
if((c = ::getopt(argc,argv,"i:erhu:c:C:d:l:vVox"))<0)
119
121
case ':': /* fall-through */
170
snprintf(msgbuf, sizeof(msgbuf), _("%s: unknown option - %c"), argv[0], static_cast<char>(c));
171
std::cout << msgbuf << std::endl;
179
std::cout << utils::strprintf(_("%s: unknown option - %c"), argv[0], static_cast<char>(c)) << std::endl;
193
GetLogger().log(LOG_INFO, "nl_langinfo(CODESET): %s", nl_langinfo(CODESET));
186
195
if (!do_export) {
187
snprintf(msgbuf, sizeof(msgbuf), _("Starting %s %s..."), PROGRAM_NAME, PROGRAM_VERSION);
188
std::cout << msgbuf << std::endl;
198
std::cout << utils::strprintf(_("Starting %s %s..."), PROGRAM_NAME, PROGRAM_VERSION) << std::endl;
191
201
if (!utils::try_fs_lock(lock_file, pid)) {
195
205
GetLogger().log(LOG_ERROR,"something went wrong with the lock: %s", strerror(errno));
197
snprintf(msgbuf, sizeof(msgbuf), _("Error: an instance of %s is already running (PID: %u)"), PROGRAM_NAME, pid);
198
std::cout << msgbuf << std::endl;
207
std::cout << utils::strprintf(_("Error: an instance of %s is already running (PID: %u)"), PROGRAM_NAME, pid) << std::endl;
204
GetInterpreter()->set_controller(this);
205
GetInterpreter()->set_view(v);
209
213
std::cout << _("Loading configuration...");
210
214
std::cout.flush();
225
229
cfgparser.register_handler("define-filter",&filters);
228
cfgparser.register_handler("load", GetInterpreter());
232
232
cfgparser.parse("/etc/" PROGRAM_NAME "/config");
233
233
cfgparser.parse(config_file);
248
248
GetLogger().set_errorlogfile(cfg->get_configvalue("error-log").c_str());
252
252
std::cout << _("done.") << std::endl;
254
254
// create cache object
255
255
std::string cachefilepath = cfg->get_configvalue("cache-file");
256
256
if (cachefilepath.length() > 0 && !cachefile_given_on_cmdline) {
257
257
cache_file = cachefilepath.c_str();
259
// ok, we got another cache file path via the configuration
260
// that means we need to remove the old lock file, assemble
261
// the new lock file's name, and then try to lock it.
262
utils::remove_fs_lock(lock_file);
263
lock_file = std::string(cache_file) + LOCK_SUFFIX;
266
if (!utils::try_fs_lock(lock_file, pid)) {
268
GetLogger().log(LOG_ERROR,"an instance is already running: pid = %u",pid);
270
GetLogger().log(LOG_ERROR,"something went wrong with the lock: %s", strerror(errno));
272
std::cout << utils::strprintf(_("Error: an instance of %s is already running (PID: %u)"), PROGRAM_NAME, pid) << std::endl;
261
278
std::cout << _("Opening cache...");
262
279
std::cout.flush();
264
281
rsscache = new cache(cache_file,cfg);
266
283
std::cout << _("done.") << std::endl;
270
288
std::string type = cfg->get_configvalue("urls-source");
271
289
if (type == "local") {
272
290
urlcfg = new file_urlreader(url_file);
292
309
std::cout << _("done.") << std::endl;
296
snprintf(msgbuf,sizeof(msgbuf), _("Loading URLs from %s..."), urlcfg->get_source().c_str());
312
if (!do_export && !silent) {
313
std::cout << utils::strprintf(_("Loading URLs from %s..."), urlcfg->get_source().c_str());
298
314
std::cout.flush();
300
316
urlcfg->reload();
317
if (!do_export && !silent) {
302
318
std::cout << _("done.") << std::endl;
306
322
if (urlcfg->get_urls().size() == 0) {
307
323
GetLogger().log(LOG_ERROR,"no URLs configured.");
308
325
if (type == "local") {
309
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());
326
msg = utils::strprintf(_("Error: no URLs configured. Please fill the file %s with RSS feed URLs or import an OPML file."), url_file.c_str());
310
327
} else if (type == "bloglines") {
311
snprintf(msgbuf, sizeof(msgbuf), _("It looks like you haven't configured any feeds in your bloglines account. Please do so, and try again."));
328
msg = utils::strprintf(_("It looks like you haven't configured any feeds in your bloglines account. Please do so, and try again."));
312
329
} else if (type == "opml") {
313
snprintf(msgbuf, sizeof(msgbuf), _("It looks like the OPML feed you subscribed contains no feeds. Please fill it with feeds, and try again."));
330
msg = utils::strprintf(_("It looks like the OPML feed you subscribed contains no feeds. Please fill it with feeds, and try again."));
315
332
assert(0); // shouldn't happen
317
std::cout << msgbuf << std::endl << std::endl;
334
std::cout << msg << std::endl << std::endl;
321
if (!do_export && !do_vacuum)
338
if (!do_export && !do_vacuum && !silent)
322
339
std::cout << _("Loading articles from cache...");
324
341
std::cout << _("Opening cache...");
381
execute_commands(argv, optind);
382
utils::remove_fs_lock(lock_file);
363
386
// if the user wants to refresh on startup via configuration file, then do so,
364
387
// but only if -r hasn't been supplied.
365
388
if (!refresh_on_start && cfg->get_configvalue_as_bool("refresh-on-startup")) {
391
413
v->set_feedlist(feeds);
416
void controller::update_visible_feeds() {
417
v->update_visible_feeds(feeds);
394
420
void controller::catchup_all() {
396
422
rsscache->catchup_all();
397
423
} catch (const dbexception& e) {
399
snprintf(buf, sizeof(buf), _("Error: couldn't mark all feeds read: %s"), e.what());
424
v->show_error(utils::strprintf(_("Error: couldn't mark all feeds read: %s"), e.what()));
403
427
for (std::vector<rss_feed>::iterator it=feeds.begin();it!=feeds.end();++it) {
428
void controller::reload(unsigned int pos, unsigned int max) {
452
void controller::reload(unsigned int pos, unsigned int max, bool unattended) {
429
453
GetLogger().log(LOG_DEBUG, "controller::reload: pos = %u max = %u", pos, max);
431
454
if (pos < feeds.size()) {
432
455
rss_feed feed = feeds[pos];
436
std::ostringstream posstr;
438
msg.append(posstr.str());
440
std::ostringstream maxstr;
442
msg.append(maxstr.str());
458
msg = utils::strprintf("(%u/%u) ", pos+1, max);
445
snprintf(msgbuf, sizeof(msgbuf), _("%sLoading %s..."), msg.c_str(), feed.rssurl().c_str());
446
460
GetLogger().log(LOG_DEBUG, "controller::reload: before setting status");
447
v->set_status(msgbuf);
462
v->set_status(utils::strprintf(_("%sLoading %s..."), msg.c_str(), feed.rssurl().c_str()));
448
463
GetLogger().log(LOG_DEBUG, "controller::reload: after setting status");
450
465
rss_parser parser(feed.rssurl().c_str(), rsscache, cfg, &ign);
483
v->set_feedlist(feeds);
484
GetLogger().log(LOG_DEBUG, "controller::reload: after set_feedlist");
499
v->set_feedlist(feeds);
500
GetLogger().log(LOG_DEBUG, "controller::reload: after set_feedlist");
486
503
v->set_status("");
487
504
} catch (const dbexception& e) {
489
snprintf(buf, sizeof(buf), _("Error while retrieving %s: %s"), feed.rssurl().c_str(), e.what());
505
v->set_status(utils::strprintf(_("Error while retrieving %s: %s"), feed.rssurl().c_str(), e.what()));
491
506
} catch (const std::string& errmsg) {
493
snprintf(buf, sizeof(buf), _("Error while retrieving %s: %s"), feed.rssurl().c_str(), errmsg.c_str());
507
v->set_status(utils::strprintf(_("Error while retrieving %s: %s"), feed.rssurl().c_str(), errmsg.c_str()));
497
510
v->show_error(_("Error: invalid feed!"));
505
518
return &(feeds[pos]);
508
void controller::reload_all() {
521
void controller::reload_indexes(const std::vector<int>& indexes, bool unattended) {
522
scope_measure m1("controller::reload_indexes");
523
unsigned int unread_feeds, unread_articles;
524
compute_unread_numbers(unread_feeds, unread_articles);
526
for (std::vector<int>::const_iterator it=indexes.begin();it!=indexes.end();++it) {
527
this->reload(*it,feeds.size(), unattended);
530
unsigned int unread_feeds2, unread_articles2;
531
compute_unread_numbers(unread_feeds2, unread_articles2);
532
if (unread_feeds2 != unread_feeds || unread_articles2 != unread_articles) {
533
fmtstr_formatter fmt;
534
fmt.register_fmt('f', utils::to_s(unread_feeds2));
535
fmt.register_fmt('n', utils::to_s(unread_articles2));
536
fmt.register_fmt('d', utils::to_s(unread_articles2 - unread_articles));
537
fmt.register_fmt('D', utils::to_s(unread_feeds2 - unread_feeds));
538
this->notify(fmt.do_format(cfg->get_configvalue("notify-format")));
544
void controller::reload_all(bool unattended) {
509
545
GetLogger().log(LOG_DEBUG,"controller::reload_all: starting with reload all...");
510
546
unsigned int unread_feeds, unread_articles;
511
547
compute_unread_numbers(unread_feeds, unread_articles);
514
550
for (unsigned int i=0;i<feeds.size();++i) {
515
551
GetLogger().log(LOG_DEBUG, "controller::reload_all: reloading feed #%u", i);
516
this->reload(i,feeds.size());
552
this->reload(i,feeds.size(), unattended);
522
558
unsigned int unread_feeds2, unread_articles2;
523
559
compute_unread_numbers(unread_feeds2, unread_articles2);
524
560
if (unread_feeds2 != unread_feeds || unread_articles2 != unread_articles) {
526
snprintf(buf,sizeof(buf),_("newsbeuter: finished reload, %u unread feeds (%u unread articles total)"), unread_feeds2, unread_articles2);
561
fmtstr_formatter fmt;
562
fmt.register_fmt('f', utils::to_s(unread_feeds2));
563
fmt.register_fmt('n', utils::to_s(unread_articles2));
564
fmt.register_fmt('d', utils::to_s(unread_articles2 - unread_articles));
565
fmt.register_fmt('D', utils::to_s(unread_feeds2 - unread_feeds));
566
this->notify(fmt.do_format(cfg->get_configvalue("notify-format")));
544
583
GetLogger().log(LOG_DEBUG, "controller:notify: notifying external program `%s'", prog.c_str());
545
584
utils::run_command(prog, msg);
547
/* // TODO: implement Growl support
548
if (cfg->get_configvalue_as_bool("notify-growl")) {
549
std::vector<std::string> tokens = utils::tokenize(cfg->get_configvalue("growl-config"), ":");
550
if (tokens.size() >= 1) {
551
std::string hostname = tokens[0];
552
std::string password;
553
if (tokens.size() >= 2) {
554
password = tokens[1];
556
growlnotifier->send_notify(hostname, password, "newsbeuter", msg);
562
588
void controller::compute_unread_numbers(unsigned int& unread_feeds, unsigned int& unread_articles) {
583
void controller::start_reload_all_thread() {
609
void controller::start_reload_all_thread(std::vector<int> * indexes) {
584
610
GetLogger().log(LOG_INFO,"starting reload all thread");
585
thread * dlt = new downloadthread(this);
611
thread * dlt = new downloadthread(this, indexes);
589
615
void controller::version_information() {
590
616
std::cout << PROGRAM_NAME << " " << PROGRAM_VERSION << " - " << PROGRAM_URL << std::endl;
591
std::cout << "Copyright (C) 2006-2007 Andreas Krennmair" << std::endl << std::endl;
617
std::cout << "Copyright (C) 2006-2008 Andreas Krennmair" << std::endl << std::endl;
593
619
struct utsname xuts;
607
633
void controller::usage(char * argv0) {
609
snprintf(buf, sizeof(buf),
610
_("%s %s\nusage: %s [-i <file>|-e] [-u <urlfile>] [-c <cachefile>] [-h]\n"
634
std::cout << utils::strprintf(_("%s %s\nusage: %s [-i <file>|-e] [-u <urlfile>] [-c <cachefile>] [-x <command> ...] [-h]\n"
611
635
"-e export OPML feed to stdout\n"
612
636
"-r refresh feeds on start\n"
613
637
"-i <file> import OPML file\n"
615
639
"-c <cachefile> use <cachefile> as cache file\n"
616
640
"-C <configfile> read configuration from <configfile>\n"
617
641
"-v clean up cache thoroughly\n"
642
"-x <command>... execute list of commands\n"
618
643
"-o activate offline mode (only applies to bloglines synchronization mode)\n"
619
644
"-V get version information\n"
620
645
"-h this help\n"), PROGRAM_NAME, PROGRAM_VERSION, argv0);
622
646
::exit(EXIT_FAILURE);
655
snprintf(buf, sizeof(buf), _("Import of %s finished."), filename);
656
std::cout << buf << std::endl;
678
std::cout << utils::strprintf(_("Import of %s finished."), filename) << std::endl;
659
681
void controller::export_opml() {
859
void controller::execute_commands(char ** argv, unsigned int i) {
860
v->pop_current_formaction();
862
GetLogger().log(LOG_DEBUG, "controller::execute_commands: executing `%s'", argv[i]);
863
std::string cmd(argv[i]);
864
if (cmd == "reload") {
866
} else if (cmd == "print-unread") {
867
std::cout << utils::strprintf(_("%u unread articles"), rsscache->get_unread_count()) << std::endl;
872
void controller::write_item(const rss_item& item, const std::string& filename) {
873
std::vector<std::string> lines;
874
std::vector<linkpair> links; // not used
876
std::string title(_("Title: "));
877
title.append(item.title());
878
lines.push_back(title);
880
std::string author(_("Author: "));
881
author.append(item.author());
882
lines.push_back(author);
884
std::string date(_("Date: "));
885
date.append(item.pubDate());
886
lines.push_back(date);
888
std::string link(_("Link: "));
889
link.append(item.link());
890
lines.push_back(link);
892
lines.push_back(std::string(""));
894
unsigned int width = cfg->get_configvalue_as_int("text-width");
897
htmlrenderer rnd(width);
898
rnd.render(item.description(), lines, links, item.feedurl());
901
f.open(filename.c_str(),std::fstream::out);
903
throw exception(errno);
905
for (std::vector<std::string>::iterator it=lines.begin();it!=lines.end();++it) {
906
f << *it << std::endl;
910
void controller::mark_deleted(const std::string& guid, bool b) {
911
rsscache->mark_item_deleted(guid, b);