339 lines
9.4 KiB
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;
|
|
}
|