~ubuntu-branches/ubuntu/maverick/newsbeuter/maverick

« back to all changes in this revision

Viewing changes to src/controller.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Nico Golde
  • Date: 2007-04-21 19:44:35 UTC
  • Revision ID: james.westby@ubuntu.com-20070421194435-21g6134ws2yvarlt
Tags: upstream-0.3
ImportĀ upstreamĀ versionĀ 0.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include <view.h>
 
2
#include <controller.h>
 
3
#include <configparser.h>
 
4
#include <configcontainer.h>
 
5
#include <exceptions.h>
 
6
#include <downloadthread.h>
 
7
#include <colormanager.h>
 
8
#include <logger.h>
 
9
#include <utils.h>
 
10
#include <stflpp.h>
 
11
#include <sstream>
 
12
#include <cstdlib>
 
13
#include <iostream>
 
14
#include <fstream>
 
15
 
 
16
#include <sys/time.h>
 
17
#include <ctime>
 
18
#include <signal.h>
 
19
 
 
20
#include <nxml.h>
 
21
 
 
22
#include <sys/types.h>
 
23
#include <pwd.h>
 
24
 
 
25
#include <config.h>
 
26
 
 
27
using namespace newsbeuter;
 
28
 
 
29
static std::string lock_file = "lock.pid";
 
30
 
 
31
void ctrl_c_action(int sig) {
 
32
        GetLogger().log(LOG_DEBUG,"caugh signal %d",sig);
 
33
        stfl::reset();
 
34
        ::unlink(lock_file.c_str());
 
35
        if (SIGSEGV == sig) {
 
36
                fprintf(stderr,"%s\n", _("Segmentation fault."));
 
37
        }
 
38
        ::exit(EXIT_FAILURE);
 
39
}
 
40
 
 
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;
 
43
 
 
44
        char * cfgdir;
 
45
        if (!(cfgdir = ::getenv("HOME"))) {
 
46
                struct passwd * spw = ::getpwuid(::getuid());
 
47
                if (spw) {
 
48
                        cfgdir = spw->pw_dir;
 
49
                } else {
 
50
                        std::cout << _("Fatal error: couldn't determine home directory!") << std::endl;
 
51
                        char buf[1024];
 
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;
 
54
                        ::exit(EXIT_FAILURE);
 
55
                }
 
56
        }
 
57
        config_dir = cfgdir;
 
58
 
 
59
 
 
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
 
63
 
 
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();
 
70
}
 
71
 
 
72
controller::~controller() {
 
73
        delete rsscache;
 
74
        delete reload_mutex;
 
75
        delete cfg;
 
76
}
 
77
 
 
78
void controller::set_view(view * vv) {
 
79
        v = vv;
 
80
}
 
81
 
 
82
void controller::run(int argc, char * argv[]) {
 
83
        int c;
 
84
        char msgbuf[1024];
 
85
 
 
86
        ::signal(SIGINT, ctrl_c_action);
 
87
#ifndef DEBUG
 
88
        ::signal(SIGSEGV, ctrl_c_action);
 
89
#endif
 
90
 
 
91
        bool do_import = false, do_export = false;
 
92
        std::string importfile;
 
93
 
 
94
        do {
 
95
                if((c = ::getopt(argc,argv,"i:erhu:c:C:d:l:"))<0)
 
96
                        continue;
 
97
                switch (c) {
 
98
                        case ':': /* fall-through */
 
99
                        case '?': /* missing option */
 
100
                                usage(argv[0]);
 
101
                                break;
 
102
                        case 'i':
 
103
                                if (do_export)
 
104
                                        usage(argv[0]);
 
105
                                do_import = true;
 
106
                                importfile = optarg;
 
107
                                break;
 
108
                        case 'r':
 
109
                                refresh_on_start = true;
 
110
                                break;
 
111
                        case 'e':
 
112
                                if (do_import)
 
113
                                        usage(argv[0]);
 
114
                                do_export = true;
 
115
                                break;
 
116
                        case 'h':
 
117
                                usage(argv[0]);
 
118
                                break;
 
119
                        case 'u':
 
120
                                url_file = optarg;
 
121
                                break;
 
122
                        case 'c':
 
123
                                cache_file = optarg;
 
124
                                break;
 
125
                        case 'C':
 
126
                                config_file = optarg;
 
127
                                break;
 
128
                        case 'd': // this is an undocumented debug commandline option!
 
129
                                GetLogger().set_logfile(optarg);
 
130
                                break;
 
131
                        case 'l': // this is an undocumented debug commandline option!
 
132
                                {
 
133
                                        loglevel level = static_cast<loglevel>(atoi(optarg));
 
134
                                        if (level > LOG_NONE && level <= LOG_DEBUG)
 
135
                                                GetLogger().set_loglevel(level);
 
136
                                }
 
137
                                break;
 
138
                        default:
 
139
                                snprintf(msgbuf, sizeof(msgbuf), _("%s: unknown option - %c"), argv[0], static_cast<char>(c));
 
140
                                std::cout << msgbuf << std::endl;
 
141
                                usage(argv[0]);
 
142
                                break;
 
143
                }
 
144
        } while (c != -1);
 
145
 
 
146
        urlcfg.load_config(url_file);
 
147
 
 
148
        if (do_import) {
 
149
                GetLogger().log(LOG_INFO,"Importing OPML file from %s",importfile.c_str());
 
150
                import_opml(importfile.c_str());
 
151
                return;
 
152
        }
 
153
 
 
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;
 
158
                usage(argv[0]);
 
159
        }
 
160
 
 
161
        if (!do_export) {
 
162
                snprintf(msgbuf, sizeof(msgbuf), _("Starting %s %s..."), PROGRAM_NAME, PROGRAM_VERSION);
 
163
                std::cout << msgbuf << std::endl;
 
164
 
 
165
                pid_t pid;
 
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;
 
170
                        return;
 
171
                }
 
172
        }
 
173
        
 
174
        if (!do_export)
 
175
                std::cout << _("Loading configuration...");
 
176
        std::cout.flush();
 
177
        
 
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);
 
183
 
 
184
        keymap keys;
 
185
        cfgparser.register_handler("bind-key",&keys);
 
186
        cfgparser.register_handler("unbind-key",&keys);
 
187
 
 
188
        try {
 
189
                cfgparser.parse();
 
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);
 
194
                return; 
 
195
        }
 
196
 
 
197
        if (colorman->colors_loaded())
 
198
                colorman->set_colors(v);
 
199
        delete colorman;
 
200
        
 
201
        if (!do_export)
 
202
                std::cout << _("done.") << std::endl;
 
203
 
 
204
        if (!do_export)
 
205
                std::cout << _("Loading articles from cache...");
 
206
        std::cout.flush();
 
207
 
 
208
        rsscache = new cache(cache_file,cfg);
 
209
 
 
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);
 
216
        }
 
217
 
 
218
        std::vector<std::string> tags = urlcfg.get_alltags();
 
219
 
 
220
        if (!do_export)
 
221
                std::cout << _("done.") << std::endl;
 
222
 
 
223
        if (do_export) {
 
224
                export_opml();
 
225
                utils::remove_fs_lock(lock_file);
 
226
                return;
 
227
        }
 
228
 
 
229
        v->set_config_container(cfg);
 
230
        v->set_keymap(&keys);
 
231
        v->set_feedlist(feeds);
 
232
        v->run_feedlist(tags);
 
233
 
 
234
        std::cout << _("Cleaning up cache...");
 
235
        std::cout.flush();
 
236
        rsscache->cleanup_cache(feeds);
 
237
        /*
 
238
        for (std::vector<rss_feed>::iterator it=feeds.begin(); it != feeds.end(); ++it) {
 
239
                // rsscache->externalize_rssfeed(*it);
 
240
        }
 
241
        */
 
242
        std::cout << _("done.") << std::endl;
 
243
 
 
244
        utils::remove_fs_lock(lock_file);
 
245
}
 
246
 
 
247
void controller::update_feedlist() {
 
248
        v->set_feedlist(feeds);
 
249
}
 
250
 
 
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;
 
255
}
 
256
 
 
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);
 
263
                        }
 
264
                }
 
265
        }
 
266
}
 
267
 
 
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);
 
275
                        }
 
276
                }
 
277
        }
 
278
}
 
279
 
 
280
bool controller::open_feed(unsigned int pos, bool auto_open) {
 
281
        bool retval = false;
 
282
        if (pos < feeds.size()) {
 
283
                if (!auto_open)
 
284
                        v->set_status(_("Opening feed..."));
 
285
 
 
286
                rss_feed& feed = feeds[pos];
 
287
 
 
288
                if (!auto_open)
 
289
                        v->set_status("");
 
290
 
 
291
                if (feed.items().size() == 0) {
 
292
                        v->show_error(_("Error: feed contains no items!"));
 
293
                } else {
 
294
                        retval = v->run_itemlist(pos, auto_open);
 
295
                        v->set_feedlist(feeds);
 
296
                }
 
297
        } else {
 
298
                v->show_error(_("Error: invalid feed!"));
 
299
        }
 
300
        return retval;
 
301
}
 
302
 
 
303
void controller::reload(unsigned int pos, unsigned int max) {
 
304
        char msgbuf[1024];
 
305
        if (pos < feeds.size()) {
 
306
                rss_feed feed = feeds[pos];
 
307
                std::string msg;
 
308
                if (max > 0) {
 
309
                        msg.append("(");
 
310
                        std::ostringstream posstr;
 
311
                        posstr << (pos+1);
 
312
                        msg.append(posstr.str());
 
313
                        msg.append("/");
 
314
                        std::ostringstream maxstr;
 
315
                        maxstr << max;
 
316
                        msg.append(maxstr.str());
 
317
                        msg.append(") ");
 
318
                }
 
319
                snprintf(msgbuf, sizeof(msgbuf), _("%sLoading %s..."), msg.c_str(), feed.rssurl().c_str());
 
320
                v->set_status(msgbuf);
 
321
                                
 
322
                rss_parser parser(feed.rssurl().c_str(), rsscache, cfg);
 
323
                try {
 
324
                        feed = parser.parse();
 
325
                        
 
326
                        rsscache->externalize_rssfeed(feed);
 
327
 
 
328
                        rsscache->internalize_rssfeed(feed);
 
329
                        feed.set_tags(urlcfg.get_tags(feed.rssurl()));
 
330
                        feeds[pos] = feed;
 
331
 
 
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());
 
340
                                        }
 
341
                                }
 
342
                        }
 
343
 
 
344
                        
 
345
                        v->set_feedlist(feeds);
 
346
                        v->set_status("");
 
347
                } catch (const std::string& errmsg) {
 
348
                        char buf[1024];
 
349
                        snprintf(buf, sizeof(buf), _("Error while retrieving %s: %s"), feed.rssurl().c_str(), errmsg.c_str());
 
350
                        v->set_status(buf);
 
351
                }
 
352
        } else {
 
353
                v->show_error(_("Error: invalid feed!"));
 
354
        }
 
355
}
 
356
 
 
357
rss_feed& controller::get_feed(unsigned int pos) {
 
358
        if (pos >= feeds.size()) {
 
359
                throw std::out_of_range(_("invalid feed index (bug)"));
 
360
        }
 
361
        return feeds[pos];
 
362
}
 
363
 
 
364
void controller::reload_all() {
 
365
        for (unsigned int i=0;i<feeds.size();++i) {
 
366
                this->reload(i,feeds.size());
 
367
        }
 
368
}
 
369
 
 
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);
 
374
                dlt->start();
 
375
        } else {
 
376
                GetLogger().log(LOG_INFO,"reload mutex is currently locked");
 
377
        }
 
378
}
 
379
 
 
380
void controller::usage(char * argv0) {
 
381
        char buf[2048];
 
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);
 
391
        std::cout << buf;
 
392
        ::exit(EXIT_FAILURE);
 
393
}
 
394
 
 
395
void controller::import_opml(const char * filename) {
 
396
        nxml_t *data;
 
397
        nxml_data_t * root, * body;
 
398
        nxml_error_t ret;
 
399
 
 
400
        ret = nxml_new (&data);
 
401
        if (ret != NXML_OK) {
 
402
                puts (nxml_strerror (ret));
 
403
                return;
 
404
        }
 
405
 
 
406
        ret = nxml_parse_file (data, const_cast<char *>(filename));
 
407
        if (ret != NXML_OK) {
 
408
                puts (nxml_strerror (ret));
 
409
                return;
 
410
        }
 
411
 
 
412
        nxml_root_element (data, &root);
 
413
 
 
414
        if (root) {
 
415
                body = nxmle_find_element(data, root, "body", NULL);
 
416
                if (body) {
 
417
                        rec_find_rss_outlines(body, "");
 
418
                        urlcfg.write_config();
 
419
                }
 
420
        }
 
421
 
 
422
        nxml_free(data);
 
423
        char buf[1024];
 
424
        snprintf(buf, sizeof(buf), _("Import of %s finished."), filename);
 
425
        std::cout << buf << std::endl;
 
426
}
 
427
 
 
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;
 
435
        }
 
436
        std::cout << "\t</body>" << std::endl;
 
437
        std::cout << "</opml>" << std::endl;
 
438
}
 
439
 
 
440
void controller::rec_find_rss_outlines(nxml_data_t * node, std::string tag) {
 
441
        while (node) {
 
442
                char * url = nxmle_find_attribute(node, "xmlUrl", NULL);
 
443
                char * type = nxmle_find_attribute(node, "type", NULL);
 
444
                std::string newtag = tag;
 
445
 
 
446
                if (!url) {
 
447
                        url = nxmle_find_attribute(node, "url", NULL);
 
448
                }
 
449
 
 
450
                if (node->type == NXML_TYPE_ELEMENT && strcmp(node->value,"outline")==0) {
 
451
                        if (type && (strcmp(type,"rss")==0 || strcmp(type,"link")==0)) {
 
452
                                if (url) {
 
453
 
 
454
                                        GetLogger().log(LOG_DEBUG,"OPML import: found RSS outline with url = %s",url);
 
455
 
 
456
                                        bool found = false;
 
457
 
 
458
                                        for (std::vector<std::string>::iterator it = urlcfg.get_urls().begin(); it != urlcfg.get_urls().end(); ++it) {
 
459
                                                if (*it == url) {
 
460
                                                        found = true;
 
461
                                                }
 
462
                                        }
 
463
 
 
464
                                        if (!found) {
 
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);
 
470
                                                }
 
471
                                        } else {
 
472
                                                GetLogger().log(LOG_DEBUG,"OPML import: url = %s is already in list",url);
 
473
                                        }
 
474
                                }
 
475
                        } else {
 
476
                                char * text = nxmle_find_attribute(node, "text", NULL);
 
477
                                if (text) {
 
478
                                        if (newtag.length() > 0) {
 
479
                                                newtag.append("/");
 
480
                                        }
 
481
                                        newtag.append(text);
 
482
                                }
 
483
                        }
 
484
                }
 
485
                rec_find_rss_outlines(node->children, newtag);
 
486
 
 
487
                node = node->next;
 
488
        }
 
489
}
 
490
 
 
491
 
 
492
 
 
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);
 
495
}
 
496
 
 
497
rss_feed controller::get_feed_by_url(const std::string& feedurl) {
 
498
        return rsscache->get_feed_by_url(feedurl);
 
499
}
 
500
 
 
501
bool controller::is_valid_podcast_type(const std::string& mimetype) {
 
502
        return mimetype == "audio/mpeg" || mimetype == "video/x-m4v" || mimetype == "audio/x-mpeg";
 
503
}
 
504
 
 
505
void controller::enqueue_url(const std::string& url) {
 
506
        bool url_found = false;
 
507
        std::fstream f;
 
508
        f.open(queue_file.c_str(), std::fstream::in);
 
509
        if (f.is_open()) {
 
510
                do {
 
511
                        std::string line;
 
512
                        getline(f, line);
 
513
                        if (!f.eof() && line.length() > 0) {
 
514
                                if (line == url) {
 
515
                                        url_found = true;
 
516
                                        break;
 
517
                                }
 
518
                        }
 
519
                } while (!f.eof());
 
520
                f.close();
 
521
        }
 
522
        if (!url_found) {
 
523
                f.open(queue_file.c_str(), std::fstream::app | std::fstream::out);
 
524
                f << url << std::endl;
 
525
                f.close();
 
526
        }
 
527
}