1 /*
2 * Copyright 2005-2021 Fabrice Colin
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 #include <stdlib.h>
20 #include <signal.h>
21 #include <unistd.h>
22 #include <libintl.h>
23 #include <getopt.h>
24 #include <strings.h>
25 #include <iostream>
26 #include <fstream>
27 #include <giomm/init.h>
28 #include <glibmm.h>
29 #include <glibmm/thread.h>
30 #include <glibmm/ustring.h>
31 #include <glibmm/miscutils.h>
32 #include <glibmm/convert.h>
33 #include "config.h"
34 #include <gtkmm/main.h>
35
36 #include "NLS.h"
37 #include "FilterFactory.h"
38 #include "Languages.h"
39 #include "MIMEScanner.h"
40 #include "ModuleFactory.h"
41 #include "ActionQueue.h"
42 #include "QueryHistory.h"
43 #include "ViewHistory.h"
44 #include "DownloaderInterface.h"
45 #ifdef HAVE_DBUS
46 #include "DBusIndex.h"
47 #endif
48 #include "PinotSettings.h"
49 #include "MainWindow.h"
50 #include "PrefsWindow.h"
51
52 #define EXPIRY_PERIOD (3600 * 24 * 30 * 6)
53
54 using namespace std;
55
56 static ofstream g_outputFile;
57 static streambuf *g_coutBuf = NULL;
58 static streambuf *g_cerrBuf = NULL;
59 static streambuf *g_clogBuf = NULL;
60 static struct option g_longOptions[] = {
61 {"help", 0, 0, 'h'},
62 {"preferences", 0, 0, 'p'},
63 {"query", 0, 0, 'q'},
64 {"version", 0, 0, 'v'},
65 {0, 0, 0, 0}
66 };
67
closeAll(void)68 static void closeAll(void)
69 {
70 clog << "Exiting..." << endl;
71
72 // Close everything
73 ModuleFactory::unloadModules();
74
75 // Restore the stream buffers
76 if (g_coutBuf != NULL)
77 {
78 clog.rdbuf(g_coutBuf);
79 }
80 if (g_cerrBuf != NULL)
81 {
82 clog.rdbuf(g_cerrBuf);
83 }
84 if (g_clogBuf != NULL)
85 {
86 clog.rdbuf(g_clogBuf);
87 }
88 g_outputFile.close();
89
90 DownloaderInterface::shutdown();
91 MIMEScanner::shutdown();
92 }
93
quitAll(int sigNum)94 static void quitAll(int sigNum)
95 {
96 clog << "Quitting..." << endl;
97
98 Gtk::Main::quit();
99 }
100
main(int argc,char ** argv)101 int main(int argc, char **argv)
102 {
103 string prefixDir(PREFIX), queryTerms;
104 Glib::ustring statusText;
105 int longOptionIndex = 0;
106 bool warnAboutVersion = false, prefsMode = false;
107
108 // Look at the options
109 int optionChar = getopt_long(argc, argv, "hpq:v", g_longOptions, &longOptionIndex);
110 while (optionChar != -1)
111 {
112 switch (optionChar)
113 {
114 case 'h':
115 // Help
116 clog << "pinot - Desktop search and metasearch\n\n"
117 << "Usage: pinot [OPTIONS]\n\n"
118 << "Options:\n"
119 << " -h, --help display this help and exit\n"
120 << " -p, --preferences show preferences\n"
121 << " -q, --query open with query terms\n"
122 << " -v, --version output version information and exit\n"
123 << "\nReport bugs to " << PACKAGE_BUGREPORT << endl;
124 return EXIT_SUCCESS;
125 case 'p':
126 prefsMode = true;
127 break;
128 case 'q':
129 if (optarg != NULL)
130 {
131 queryTerms = optarg;
132 }
133 break;
134 case 'v':
135 clog << "pinot - " << PACKAGE_STRING << "\n\n"
136 << "This is free software. You may redistribute copies of it under the terms of\n"
137 << "the GNU General Public License <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.\n"
138 << "There is NO WARRANTY, to the extent permitted by law." << endl;
139 return EXIT_SUCCESS;
140 default:
141 return EXIT_FAILURE;
142 }
143
144 // Next option
145 optionChar = getopt_long(argc, argv, "hpq:v", g_longOptions, &longOptionIndex);
146 }
147
148 string programName(argv[0]);
149 if ((programName.length() >= 11) &&
150 (programName.substr(programName.length() - 11) == "pinot-prefs"))
151 {
152 prefsMode = true;
153 }
154
155 #if defined(ENABLE_NLS)
156 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
157 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
158 textdomain(GETTEXT_PACKAGE);
159 #endif //ENABLE_NLS
160
161 Glib::init();
162 Gio::init();
163 // Initialize threads support before doing anything else
164 if (Glib::thread_supported() == false)
165 {
166 Glib::thread_init();
167 }
168 // Initialize GType
169 #if !GLIB_CHECK_VERSION(2,35,0)
170 g_type_init();
171 #endif
172
173 Gtk::Main m(&argc, &argv);
174 if (prefsMode == false)
175 {
176 Glib::set_application_name("Pinot");
177 }
178 else
179 {
180 Glib::set_application_name("Pinot Preferences");
181 }
182
183 // This will be useful for indexes served by xapian-progsrv+ssh
184 Glib::setenv("SSH_ASKPASS", prefixDir + "/libexec/openssh/ssh-askpass");
185
186 // This is a hack to force the locale to UTF-8
187 char *pLocale = setlocale(LC_ALL, NULL);
188 if (pLocale != NULL)
189 {
190 string locale(pLocale);
191
192 if (locale != "C")
193 {
194 bool appendUTF8 = false;
195
196 string::size_type pos = locale.find_last_of(".");
197 if ((pos != string::npos) &&
198 ((strcasecmp(locale.substr(pos).c_str(), ".utf8") != 0) &&
199 (strcasecmp(locale.substr(pos).c_str(), ".utf-8") != 0)))
200 {
201 locale.resize(pos);
202 appendUTF8 = true;
203 }
204
205 if (appendUTF8 == true)
206 {
207 locale += ".UTF-8";
208
209 pLocale = setlocale(LC_ALL, locale.c_str());
210 if (pLocale != NULL)
211 {
212 #ifdef DEBUG
213 clog << "Changed locale to " << pLocale << endl;
214 #endif
215 }
216 }
217 }
218 }
219
220 // This will create the necessary directories on the first run
221 PinotSettings &settings = PinotSettings::getInstance();
222 #ifdef HAVE_DBUS
223 // Talk to the daemon through DBus
224 settings.setClientMode(true);
225 #endif
226
227 string confDirectory(PinotSettings::getConfigurationDirectory());
228 if (chdir(confDirectory.c_str()) == 0)
229 {
230 // Redirect cout, cerr and clog to a file
231 string logFileName = confDirectory;
232 if (prefsMode == false)
233 {
234 logFileName += "/pinot.log";
235 }
236 else
237 {
238 logFileName += "/pinot-prefs.log";
239 }
240 g_outputFile.open(logFileName.c_str());
241 g_coutBuf = clog.rdbuf();
242 g_cerrBuf = clog.rdbuf();
243 g_clogBuf = clog.rdbuf();
244 clog.rdbuf(g_outputFile.rdbuf());
245 clog.rdbuf(g_outputFile.rdbuf());
246 clog.rdbuf(g_outputFile.rdbuf());
247 }
248
249 // Initialize utility classes
250 if (MIMEScanner::initialize(PinotSettings::getHomeDirectory() + "/.local",
251 string(SHARED_MIME_INFO_PREFIX)) == false)
252 {
253 clog << "Couldn't load MIME settings" << endl;
254 }
255 DownloaderInterface::initialize();
256 // Load filter libraries, if any
257 Dijon::FilterFactory::loadFilters(string(LIBDIR) + "/pinot/filters");
258 Dijon::FilterFactory::loadFilters(confDirectory + "/filters");
259 // Load backends, if any
260 ModuleFactory::loadModules(string(LIBDIR) + "/pinot/backends");
261 ModuleFactory::loadModules(confDirectory + "/backends");
262
263 // Localize language names
264 Languages::setIntlName(0, _("Unknown"));
265 Languages::setIntlName(1, _("Danish"));
266 Languages::setIntlName(2, _("Dutch"));
267 Languages::setIntlName(3, _("English"));
268 Languages::setIntlName(4, _("Finnish"));
269 Languages::setIntlName(5, _("French"));
270 Languages::setIntlName(6, _("German"));
271 Languages::setIntlName(7, _("Hungarian"));
272 Languages::setIntlName(8, _("Italian"));
273 Languages::setIntlName(9, _("Norwegian"));
274 Languages::setIntlName(10, _("Portuguese"));
275 Languages::setIntlName(11, _("Romanian"));
276 Languages::setIntlName(12, _("Russian"));
277 Languages::setIntlName(13, _("Spanish"));
278 Languages::setIntlName(14, _("Swedish"));
279 Languages::setIntlName(15, _("Turkish"));
280
281 // Load the settings
282 settings.load(PinotSettings::LOAD_ALL);
283
284 // Catch interrupts
285 #ifdef HAVE_SIGACTION
286 struct sigaction newAction;
287 sigemptyset(&newAction.sa_mask);
288 newAction.sa_flags = 0;
289 newAction.sa_handler = quitAll;
290 sigaction(SIGINT, &newAction, NULL);
291 sigaction(SIGQUIT, &newAction, NULL);
292 sigaction(SIGTERM, &newAction, NULL);
293 #else
294 signal(SIGINT, quitAll);
295 #ifdef SIGQUIT
296 signal(SIGQUIT, quitAll);
297 #endif
298 signal(SIGTERM, quitAll);
299 #endif
300
301 // Open this index read-write, unless we are in preferences mode
302 bool wasObsoleteFormat = false;
303 if (ModuleFactory::openOrCreateIndex(settings.m_defaultBackend, settings.m_docsIndexLocation, wasObsoleteFormat, prefsMode) == false)
304 {
305 statusText = _("Couldn't open index");
306 statusText += " ";
307 statusText += settings.m_docsIndexLocation;
308 }
309 else
310 {
311 warnAboutVersion = wasObsoleteFormat;
312 }
313 // ...and the daemon index in read-only mode
314 // If it can't be open, it just means the daemon has not yet created it
315 ModuleFactory::openOrCreateIndex(settings.m_defaultBackend, settings.m_daemonIndexLocation, wasObsoleteFormat, true);
316 // Merge these two, this will be useful later
317 ModuleFactory::mergeIndexes(settings.m_defaultBackend, "MERGED", settings.m_docsIndexLocation, settings.m_daemonIndexLocation);
318
319 // Do the same for the history database
320 string historyDatabase(settings.getHistoryDatabaseName());
321 if ((historyDatabase.empty() == true) ||
322 (ActionQueue::create(historyDatabase) == false) ||
323 (QueryHistory::create(historyDatabase) == false) ||
324 (ViewHistory::create(historyDatabase) == false))
325 {
326 statusText = _("Couldn't create history database");
327 statusText += " ";
328 statusText += historyDatabase;
329 }
330 else
331 {
332 ActionQueue actionQueue(historyDatabase, Glib::get_prgname());
333 QueryHistory queryHistory(historyDatabase);
334 ViewHistory viewHistory(historyDatabase);
335 time_t timeNow = time(NULL);
336
337 // Expire all actions left from last time
338 actionQueue.expireItems(timeNow);
339 // Expire items
340 queryHistory.expireItems(timeNow - EXPIRY_PERIOD);
341 viewHistory.expireItems(timeNow - EXPIRY_PERIOD);
342 }
343
344 atexit(closeAll);
345
346 if (prefsMode == false)
347 {
348 IndexInterface *pIndex = settings.getIndex(settings.m_docsIndexLocation);
349 if (pIndex != NULL)
350 {
351 string indexVersion(pIndex->getMetadata("version"));
352
353 // What version is the index at ?
354 if (indexVersion.empty() == true)
355 {
356 indexVersion = "0.0";
357 }
358 // Is an upgrade necessary ?
359 if ((indexVersion < PINOT_INDEX_MIN_VERSION) &&
360 (pIndex->getDocumentsCount() > 0))
361 {
362 warnAboutVersion = true;
363 }
364 #ifdef DEBUG
365 clog << "My Web Pages was set to version " << indexVersion << endl;
366 #endif
367 pIndex->setMetadata("version", VERSION);
368
369 delete pIndex;
370 }
371 if (warnAboutVersion == true)
372 {
373 settings.m_warnAboutVersion = warnAboutVersion;
374 }
375 }
376
377 try
378 {
379 // Set an icon for all windows
380 Gtk::Window::set_default_icon_from_file(prefixDir + "/share/icons/hicolor/48x48/apps/pinot.png");
381 // Get a builder
382 Glib::RefPtr<Gtk::Builder> refBuilder = Gtk::Builder::create_from_file(prefixDir + "/share/pinot/metase-gtk3.gtkbuilder");
383
384 refBuilder->set_translation_domain(GETTEXT_PACKAGE);
385
386 // Create and open the window
387 if (prefsMode == false)
388 {
389 MainWindow *pMainBox = NULL;
390 refBuilder->get_widget_derived<MainWindow>("mainWindow", pMainBox, statusText);
391
392 if (pMainBox != NULL)
393 {
394 pMainBox->setQueryTerms(queryTerms);
395
396 m.run(*pMainBox);
397 }
398 }
399 else
400 {
401 PrefsWindow *pPrefsBox = NULL;
402 refBuilder->get_widget_derived<PrefsWindow>("prefsWindow", pPrefsBox);
403
404 if (pPrefsBox != NULL)
405 {
406 m.run(*pPrefsBox);
407 }
408 }
409 }
410 catch (const Glib::Exception &e)
411 {
412 clog << "Caught exception: " << e.what() << endl;
413 return EXIT_FAILURE;
414 }
415 catch (const char *pMsg)
416 {
417 clog << "Caught error: " << pMsg << endl;
418 cout << "Caught error: " << pMsg << endl;
419 cerr << "Caught error: " << pMsg << endl;
420 return EXIT_FAILURE;
421 }
422 catch (...)
423 {
424 clog << "Unknown exception" << endl;
425 return EXIT_FAILURE;
426 }
427
428 return EXIT_SUCCESS;
429 }
430