snownews/main.c

339 lines
9.4 KiB
C

// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
// Copyright (c) 2021 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Snownews. If not, see http://www.gnu.org/licenses/.
#include "main.h"
#include "ui.h"
#include "feedio.h"
#include "setup.h"
#include "uiutil.h"
#include <ncurses.h>
#include <signal.h>
#include <sys/wait.h>
//{{{ Global variables -------------------------------------------------
struct feed* _feed_list = NULL;
struct feed* _unfiltered_feed_list = NULL; // Backup first pointer for filter mode.
// Needs to be global so it can be used in the signal handler.
// Must be set to NULL by default and whenever it's not used anymore!
bool _feed_list_changed = false;
struct settings _settings = {
.keybindings = {
//{{{2 Default values for keybindings.
// If some are defined differently in the keybindings file
// they will be overwritten. If some are missing or broken
// these are sane defaults.
.about = 'A',
.addfeed = 'a',
.andxor = 'X',
.categorize = 'C',
.changefeedname = 'c',
.deletefeed = 'D',
.dfltbrowser = 'B',
.end = '>',
.enter = 'l',
.feedinfo = 'i',
.filter = 'f',
.filtercurrent = 'g',
.forcereload = 'T',
.help = 'h',
.home = '<',
.markallread = 'm',
.markread = 'm',
.markunread = 'M',
.movedown = 'N',
.moveup = 'P',
.newheadlines = 'H',
.next = 'n',
.nofilter = 'F',
.pdown = ' ',
.perfeedfilter = 'e',
.prev = 'p',
.prevmenu = 'q',
.pup = 'b',
.quit = 'q',
.reload = 'r',
.reloadall = 'R',
.sortfeeds = 's',
.typeahead = '/',
.urljump = 'o',
.urljump2 = 'O'
//}}}2
},
.color = {
.feedtitle = -1,
.feedtitlebold = 0,
.newitems = 5,
.newitemsbold = 0,
.urljump = 4,
.urljumpbold = 0 },
};
//----------------------------------------------------------------------
static int last_signal = 0;
//}}}-------------------------------------------------------------------
//{{{ PID file management
enum EPIDAction { pid_file_delete, pid_file_create };
static void make_pidfile_name (char* fnbuf, size_t fnbufsz)
{
const char* rundir = getenv ("XDG_RUNTIME_DIR");
if (!rundir)
rundir = getenv ("TMPDIR");
if (!rundir)
rundir = "/tmp";
snprintf (fnbuf, fnbufsz, "%s/snownews.pid", rundir);
}
static void modifyPIDFile (enum EPIDAction action)
{
char pid_path[PATH_MAX];
make_pidfile_name (pid_path, sizeof (pid_path));
if (action == pid_file_create) {
FILE* file = fopen (pid_path, "w");
if (!file) {
printf (_("Unable to write PID file %s!"), pid_path);
return;
}
fprintf (file, "%d", getpid());
fclose (file);
} else
unlink (pid_path);
}
static void checkPIDFile (void)
{
char pid_path[PATH_MAX];
make_pidfile_name (pid_path, sizeof (pid_path));
FILE* pidfile = fopen (pid_path, "r");
if (!pidfile)
return;
char pidbuf[12];
fgets (pidbuf, sizeof (pidbuf), pidfile);
fclose (pidfile);
pid_t pid = atoi (pidbuf);
if (kill (pid, 0) == 0) {
printf (_("Snownews seems to be already running with process ID %d.\n"
"A pid file exists at \"%s\".\n"), pid, pid_path);
exit (2);
} else {
printf (_("A pid file exists at \"%s\",\n"
"but snownews doesn't seem to be running.\n"
"Delete that file and start it again.\n"
"Continue anyway? (y/n) "), pid_path);
char ybuf[2] = { };
fgets (ybuf, sizeof (ybuf), stdin);
if (ybuf[0] != 'y')
exit (2);
modifyPIDFile (pid_file_delete);
}
}
//}}}-------------------------------------------------------------------
//{{{ stderr log redirection
static int MakeStderrLogFileName (char* logfile, size_t logfilesz)
{
const char* tmpdir = getenv ("TMPDIR");
if (!tmpdir)
tmpdir = "/tmp";
unsigned r = snprintf (logfile, logfilesz, "%s/" SNOWNEWS_NAME ".log", tmpdir);
return r >= logfilesz ? -1 : 0;
}
// If the log file is empty at exit, delete it
static void CleanupStderrLog (void)
{
char logfile[PATH_MAX];
if (0 == MakeStderrLogFileName (logfile, sizeof (logfile))) {
struct stat st;
if (0 == stat (logfile, &st) && st.st_size == 0)
unlink (logfile);
}
}
// Redirects stderr into a log file in /tmp
static void RedirectStderrToLog (void)
{
char logfile[PATH_MAX];
if (0 > MakeStderrLogFileName (logfile, sizeof (logfile)))
return;
int fd = open (logfile, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
return;
dup2 (fd, STDERR_FILENO);
close (fd);
atexit (CleanupStderrLog);
}
//}}}-------------------------------------------------------------------
//{{{ Signal handling
// Deinit ncurses and quit.
_Noreturn void MainQuit (const char* func, const char* error)
{
if (!error) // Only save settings if we didn't exit on error.
WriteCache();
endwin(); // Make sure no ncurses function is called after this point!
modifyPIDFile (pid_file_delete);
if (!error)
exit (EXIT_SUCCESS);
if (last_signal) {
#if __linux__
printf ("Exiting via signal %s.\n", strsignal (last_signal));
#else
printf ("Exiting via signal %d.\n", last_signal);
#endif
}
printf (_("Aborting program execution!\n"
"snownews quit without saving changes due to internal error!\n"
"Please submit a bugreport at https://github.com/msharov/snownews/issues\n"));
printf ("----\n");
printf (_("While executing: %s\nError: %s\n\n"), func, error);
exit (EXIT_FAILURE);
}
// Signal handler function.
static _Noreturn void MainSignalHandler (int sig)
{
last_signal = sig;
// If there is a _unfiltered_feed_list!=NULL a filter is set. Reset _feed_list
// so the correct list gets written on the disk when exiting via SIGINT.
if (_unfiltered_feed_list)
_feed_list = _unfiltered_feed_list;
MainQuit ("MainSignalHandler", (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) ? NULL : "Signal");
}
// Automatic child reaper.
static void sigChildHandler (int sig __attribute__((unused)))
{
// Wait for any child without blocking
waitpid (-1, NULL, WNOHANG);
}
static void InstallSignalHandlers (void)
{
signal (SIGHUP, MainSignalHandler);
signal (SIGINT, MainSignalHandler);
signal (SIGQUIT, MainSignalHandler);
signal (SIGTERM, MainSignalHandler);
signal (SIGABRT, MainSignalHandler);
signal (SIGSEGV, MainSignalHandler);
// Un-broken pipify
signal (SIGPIPE, SIG_IGN);
signal (SIGCHLD, sigChildHandler);
#ifdef SIGWINCH
signal (SIGWINCH, sig_winch);
#endif
}
//}}}-------------------------------------------------------------------
//{{{ Command line options help
static void printHelp (void)
{
printf ("Snownews " SNOWNEWS_VERSTRING "\n\n");
printf (_("Usage: snownews [-chuV]\n\n"
" -h, --help\t\tPrint this help message.\n"
" -V, --version\t\tPrint version number and exit.\n"
" -c, --cursor-on\tForce cursor always visible.\n"
" -u, --update\t\tAutomatically update every feed.\n"));
}
//}}}-------------------------------------------------------------------
//{{{ srandrand
inline static uint32_t ror32 (uint32_t v, unsigned n)
{
return (v >> n) | (v << (32 - n));
}
// Randomly initializes the random number generator
static void srandrand (void)
{
struct timespec now;
clock_gettime (CLOCK_REALTIME, &now);
uint32_t seed = now.tv_sec;
seed ^= getppid();
seed = ror32 (seed, 16);
seed ^= getpid();
seed ^= now.tv_nsec;
srand (seed);
}
//}}}-------------------------------------------------------------------
int main (int argc, char* argv[])
{
InstallSignalHandlers();
srandrand();
setlocale (LC_ALL, "");
#ifdef LOCALEPATH
bindtextdomain (SNOWNEWS_NAME, LOCALEPATH);
textdomain (SNOWNEWS_NAME);
#endif
RedirectStderrToLog();
bool autoupdate = false; // Automatically update feeds on app start... or not if set to 0.
for (int i = 1; i < argc; ++i) {
char* arg = argv[i];
if (strcmp (arg, "-u") == 0 || strcmp (arg, "--update") == 0)
autoupdate = true;
else if (strcmp (arg, "-c") == 0 || strcmp (arg, "--cursor-on") == 0)
_settings.cursor_always_visible = true;
else if (strcmp (arg, "--version") == 0 || strcmp (arg, "-V") == 0) {
printf ("snownews " SNOWNEWS_VERSTRING "\n");
return EXIT_SUCCESS;
} else if (strcmp (arg, "-h") == 0 || strcmp (arg, "--help") == 0) {
printHelp();
return EXIT_SUCCESS;
} else {
printf (_("Unknown option \"%s\"\n"), arg);
printHelp();
return EXIT_SUCCESS;
}
}
// Create PID file.
checkPIDFile();
modifyPIDFile (pid_file_create);
InitCurses();
// Check if configfiles exist and create/read them.
LoadAllFeeds (Config());
if (autoupdate)
UpdateAllFeeds();
// Give control to main program loop.
UIMainInterface();
// We really shouldn't be here at all... ah well.
return EXIT_SUCCESS;
}