1 #include <iostream>
2 #include <exception>
3 #include <stdexcept>
4 #include <string>
5 #include <iterator>
6 #include <algorithm>
7 #include <ios>
8 #include <locale>
9 #include <fftw3.h>
10 
11 #include <QSharedMemory>
12 #include <QProcessEnvironment>
13 #include <QTemporaryFile>
14 #include <QDateTime>
15 #include <QLocale>
16 #include <QTranslator>
17 #include <QRegularExpression>
18 #include <QObject>
19 #include <QSettings>
20 #include <QSysInfo>
21 #include <QDir>
22 #include <QDirIterator>
23 #include <QStandardPaths>
24 #include <QStringList>
25 #include <QLockFile>
26 #include <QSplashScreen>
27 #include <QCommandLineParser>
28 #include <QCommandLineOption>
29 #include <QSqlDatabase>
30 #include <QSqlQuery>
31 #include <QSqlError>
32 #include <QVariant>
33 #include <QByteArray>
34 #include <QBitArray>
35 #include <QMetaType>
36 
37 #include "ExceptionCatchingApplication.hpp"
38 #include "Logger.hpp"
39 #include "revision_utils.hpp"
40 #include "MetaDataRegistry.hpp"
41 #include "qt_helpers.hpp"
42 #include "L10nLoader.hpp"
43 #include "SettingsGroup.hpp"
44 //#include "TraceFile.hpp"
45 #include "WSJTXLogging.hpp"
46 #include "MultiSettings.hpp"
47 #include "widgets/mainwindow.h"
48 #include "commons.h"
49 #include "lib/init_random_seed.h"
50 #include "Radio.hpp"
51 #include "models/FrequencyList.hpp"
52 #include "widgets/SplashScreen.hpp"
53 #include "widgets/MessageBox.hpp"       // last to avoid nasty MS macro definitions
54 
55 extern "C" {
56   // Fortran procedures we need
57   void four2a_(_Complex float *, int * nfft, int * ndim, int * isign, int * iform, int len);
58 }
59 
60 namespace
61 {
62 #if QT_VERSION < QT_VERSION_CHECK (5, 15, 0)
63   struct RNGSetup
64   {
RNGSetup__anonb8f43d6e0111::RNGSetup65     RNGSetup ()
66     {
67       // one time seed of pseudo RNGs from current time
68       auto seed = QDateTime::currentMSecsSinceEpoch ();
69       qsrand (seed);            // this is good for rand() as well
70     }
71   } seeding;
72 #endif
73 
safe_stream_QVariant(boost::log::record_ostream & os,QVariant const & v)74   void safe_stream_QVariant (boost::log::record_ostream& os, QVariant const& v)
75   {
76     switch (static_cast<QMetaType::Type> (v.type ()))
77       {
78       case QMetaType::QByteArray:
79         os << "0x"
80 #if QT_VERSION >= QT_VERSION_CHECK (5, 9, 0)
81            << v.toByteArray ().toHex (':').toStdString ()
82 #else
83            << v.toByteArray ().toHex ().toStdString ()
84 #endif
85           ;
86         break;
87 
88       case QMetaType::QBitArray:
89         {
90           auto const& bits = v.toBitArray ();
91           os << "0b";
92           for (int i = 0; i < bits.size (); ++ i)
93             {
94               os << (bits[i] ? '1' : '0');
95             }
96         }
97         break;
98 
99       default:
100         os << v.toString ();
101       }
102   }
103 }
104 
main(int argc,char * argv[])105 int main(int argc, char *argv[])
106 {
107   init_random_seed ();
108 
109   // make the Qt type magic happen
110   Radio::register_types ();
111   register_types ();
112 
113   // Multiple instances communicate with jt9 via this
114   QSharedMemory mem_jt9;
115 
116   auto const env = QProcessEnvironment::systemEnvironment ();
117 
118   ExceptionCatchingApplication a(argc, argv);
119   try
120     {
121       // LOG_INfO ("+++++++++++++++++++++++++++ Resources ++++++++++++++++++++++++++++");
122       // {
123       //   QDirIterator resources_iter {":/", QDirIterator::Subdirectories};
124       //   while (resources_iter.hasNext ())
125       //     {
126       //       LOG_INFO (resources_iter.next ());
127       //     }
128       // }
129       // LOG_INFO ("--------------------------- Resources ----------------------------");
130 
131       QLocale locale;              // get the current system locale
132 
133       // reset the C+ & C global locales to the classic C locale
134       std::locale::global (std::locale::classic ());
135 
136       // Override programs executable basename as application name.
137       a.setApplicationName ("WSJT-X");
138       a.setApplicationVersion (version ());
139 
140       QCommandLineParser parser;
141       parser.setApplicationDescription ("\n" PROJECT_DESCRIPTION);
142       auto help_option = parser.addHelpOption ();
143       auto version_option = parser.addVersionOption ();
144 
145       // support for multiple instances running from a single installation
146       QCommandLineOption rig_option (QStringList {} << "r" << "rig-name"
147                                      , "Where <rig-name> is for multi-instance support."
148                                      , "rig-name");
149       parser.addOption (rig_option);
150 
151       // support for start up configuration
152       QCommandLineOption cfg_option (QStringList {} << "c" << "config"
153                                      , "Where <configuration> is an existing one."
154                                      , "configuration");
155       parser.addOption (cfg_option);
156 
157       // support for UI language override (useful on Windows)
158       QCommandLineOption lang_option (QStringList {} << "l" << "language"
159                                      , "Where <language> is <lang-code>[-<country-code>]."
160                                      , "language");
161       parser.addOption (lang_option);
162 
163       QCommandLineOption test_option (QStringList {} << "test-mode"
164                                       , "Writable files in test location.  Use with caution, for testing only.");
165       parser.addOption (test_option);
166 
167       if (!parser.parse (a.arguments ()))
168         {
169           MessageBox::critical_message (nullptr, "Command line error", parser.errorText ());
170           return -1;
171         }
172       else
173         {
174           if (parser.isSet (help_option))
175             {
176               MessageBox::information_message (nullptr, "Command line help", parser.helpText ());
177               return 0;
178             }
179           else if (parser.isSet (version_option))
180             {
181               MessageBox::information_message (nullptr, "Application version", a.applicationVersion ());
182               return 0;
183             }
184         }
185 
186       QStandardPaths::setTestModeEnabled (parser.isSet (test_option));
187 
188       // support for multiple instances running from a single installation
189       bool multiple {false};
190       if (parser.isSet (rig_option) || parser.isSet (test_option))
191         {
192           auto temp_name = parser.value (rig_option);
193           if (!temp_name.isEmpty ())
194             {
195               if (temp_name.contains (QRegularExpression {R"([\\/,])"}))
196                 {
197                   std::cerr << "Invalid rig name - \\ & / not allowed" << std::endl;
198                   parser.showHelp (-1);
199                 }
200 
201               a.setApplicationName (a.applicationName () + " - " + temp_name);
202             }
203 
204           if (parser.isSet (test_option))
205             {
206               a.setApplicationName (a.applicationName () + " - test");
207             }
208 
209           multiple = true;
210         }
211 
212       // now we have the application name we can open the logging and settings
213       WSJTXLogging lg;
214       LOG_INFO (program_title (revision ()) << " - Program startup");
215       MultiSettings multi_settings {parser.value (cfg_option)};
216 
217       // find the temporary files path
218       QDir temp_dir {QStandardPaths::writableLocation (QStandardPaths::TempLocation)};
219       Q_ASSERT (temp_dir.exists ()); // sanity check
220 
221       // disallow multiple instances with same instance key
222       QLockFile instance_lock {temp_dir.absoluteFilePath (a.applicationName () + ".lock")};
223       instance_lock.setStaleLockTime (0);
224       while (!instance_lock.tryLock ())
225         {
226           if (QLockFile::LockFailedError == instance_lock.error ())
227             {
228               auto button = MessageBox::query_message (nullptr
229                                                        , "Another instance may be running"
230                                                        , "try to remove stale lock file?"
231                                                        , QString {}
232                                                        , MessageBox::Yes | MessageBox::Retry | MessageBox::No
233                                                        , MessageBox::Yes);
234               switch (button)
235                 {
236                 case MessageBox::Yes:
237                   instance_lock.removeStaleLockFile ();
238                   break;
239 
240                 case MessageBox::Retry:
241                   break;
242 
243                 default:
244                   throw std::runtime_error {"Multiple instances must have unique rig names"};
245                 }
246             }
247           else
248             {
249               throw std::runtime_error {"Failed to access lock file"};
250             }
251         }
252 
253       // load UI translations
254       L10nLoader l10n {&a, locale, parser.value (lang_option)};
255 
256       // Create a unique writeable temporary directory in a suitable location
257       bool temp_ok {false};
258       QString unique_directory {ExceptionCatchingApplication::applicationName ()};
259       do
260         {
261           if (!temp_dir.mkpath (unique_directory)
262               || !temp_dir.cd (unique_directory))
263             {
264               MessageBox::critical_message (nullptr,
265                                             a.translate ("main", "Failed to create a temporary directory"),
266                                             a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()));
267               throw std::runtime_error {"Failed to create a temporary directory"};
268             }
269           if (!temp_dir.isReadable () || !(temp_ok = QTemporaryFile {temp_dir.absoluteFilePath ("test")}.open ()))
270             {
271               auto button =  MessageBox::critical_message (nullptr,
272                                                            a.translate ("main", "Failed to create a usable temporary directory"),
273                                                            a.translate ("main", "Another application may be locking the directory"),
274                                                            a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()),
275                                                            MessageBox::Retry | MessageBox::Cancel);
276               if (MessageBox::Cancel == button)
277                 {
278                   throw std::runtime_error {"Failed to create a usable temporary directory"};
279                 }
280               temp_dir.cdUp ();  // revert to parent as this one is no good
281             }
282         }
283       while (!temp_ok);
284 
285       SplashScreen splash;
286       {
287         // change this key if you want to force a new splash screen
288         // for a new version, the user will be able to re-disable it
289         // if they wish
290         // Z
291         QString splash_flag_name {"Splash_v2.5-1.10"};
292         if (multi_settings.common_value (splash_flag_name, true).toBool ())
293           {
294             QObject::connect (&splash, &SplashScreen::disabled, [&, splash_flag_name] {
295                 multi_settings.set_common_value (splash_flag_name, false);
296                 splash.close ();
297               });
298             splash.show ();
299             a.processEvents ();
300           }
301       }
302 
303       // create writeable data directory if not already there
304       auto writeable_data_dir = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
305       if (!writeable_data_dir.mkpath ("."))
306         {
307           MessageBox::critical_message (nullptr, a.translate ("main", "Failed to create data directory"),
308                                         a.translate ("main", "path: \"%1\"").arg (writeable_data_dir.absolutePath ()));
309           throw std::runtime_error {"Failed to create data directory"};
310         }
311 
312       // set up SQLite database
313       if (!QSqlDatabase::drivers ().contains ("QSQLITE"))
314         {
315           throw std::runtime_error {"Failed to find SQLite Qt driver"};
316         }
317       auto db = QSqlDatabase::addDatabase ("QSQLITE");
318       db.setDatabaseName (writeable_data_dir.absoluteFilePath ("db.sqlite"));
319       if (!db.open ())
320         {
321           throw std::runtime_error {("Database Error: " + db.lastError ().text ()).toStdString ()};
322         }
323 
324       // better performance traded for a risk of d/b corruption
325       // on system crash or application crash
326       // db.exec ("PRAGMA synchronous=OFF"); // system crash risk
327       // db.exec ("PRAGMA journal_mode=MEMORY"); // application crash risk
328       db.exec ("PRAGMA locking_mode=EXCLUSIVE");
329 
330       int result;
331       auto const& original_style_sheet = a.styleSheet ();
332       do
333         {
334           // dump settings
335           auto sys_lg = sys::get ();
336           if (auto rec = sys_lg.open_record
337               (
338                boost::log::keywords::severity = boost::log::trivial::trace)
339               )
340             {
341               boost::log::record_ostream strm (rec);
342               strm << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++\n";
343               for (auto const& key: multi_settings.settings ()->allKeys ())
344                 {
345                   if (!key.contains (QRegularExpression {"^MultiSettings/[^/]*/"}))
346                     {
347                       auto const& value = multi_settings.settings ()->value (key);
348                       if (value.canConvert<QVariantList> ())
349                         {
350                           auto const sequence = value.value<QSequentialIterable> ();
351                           strm << key << ":\n";
352                           for (auto const& item: sequence)
353                             {
354                               strm << "\t";
355                               safe_stream_QVariant (strm, item);
356                               strm << '\n';
357                             }
358                         }
359                       else
360                         {
361                           strm << key << ": ";
362                           safe_stream_QVariant (strm, value);
363                           strm << '\n';
364                         }
365                     }
366                 }
367               strm << "---------------------------- Settings ----------------------------\n";
368               strm.flush ();
369               sys_lg.push_record (boost::move (rec));
370             }
371 
372           // Create and initialize shared memory segment
373           // Multiple instances: use rig_name as shared memory key
374           mem_jt9.setKey(a.applicationName ());
375 
376           // try and shut down any orphaned jt9 process
377           for (int i = 3; i; --i) // three tries to close old jt9
378             {
379               if (mem_jt9.attach ()) // shared memory presence implies
380                                      // orphaned jt9 sub-process
381                 {
382                   dec_data_t * dd = reinterpret_cast<dec_data_t *> (mem_jt9.data());
383                   mem_jt9.lock ();
384                   dd->ipc[1] = 999; // tell jt9 to shut down
385                   mem_jt9.unlock ();
386                   mem_jt9.detach (); // start again
387                 }
388               else
389                 {
390                   break;        // good to go
391                 }
392               QThread::sleep (1); // wait for jt9 to end
393             }
394           if (!mem_jt9.attach ())
395             {
396               if (!mem_jt9.create (sizeof (dec_data)))
397               {
398                 splash.hide ();
399                 MessageBox::critical_message (nullptr, a.translate ("main", "Shared memory error"),
400                                               a.translate ("main", "Unable to create shared memory segment"));
401                 throw std::runtime_error {"Shared memory error"};
402               }
403               LOG_INFO ("shmem size: " << mem_jt9.size ());
404             }
405           else
406             {
407               splash.hide ();
408               MessageBox::critical_message (nullptr, a.translate ("main", "Sub-process error"),
409                                             a.translate ("main", "Failed to close orphaned jt9 process"));
410               throw std::runtime_error {"Sub-process error"};
411             }
412           mem_jt9.lock ();
413           memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory
414           mem_jt9.unlock ();
415 
416           unsigned downSampleFactor;
417           {
418             SettingsGroup {multi_settings.settings (), "Tune"};
419 
420             // deal with Windows Vista and earlier input audio rate
421             // converter problems
422             downSampleFactor = multi_settings.settings ()->value ("Audio/DisableInputResampling",
423 #if defined (Q_OS_WIN)
424                                                                   // default to true for
425                                                                   // Windows Vista and older
426                                                                   QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false
427 #else
428                                                                   false
429 #endif
430                                                                   ).toBool () ? 1u : 4u;
431           }
432 
433           // run the application UI
434           MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash, env);
435           w.show();
436           splash.raise ();
437           QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));
438           result = a.exec();
439 
440           // ensure config switches start with the right style sheet
441           a.setStyleSheet (original_style_sheet);
442         }
443       while (!result && !multi_settings.exit ());
444 
445       // clean up lazily initialized resources
446       {
447         int nfft {-1};
448         int ndim {1};
449         int isign {1};
450         int iform {1};
451         // free FFT plan resources
452         four2a_ (nullptr, &nfft, &ndim, &isign, &iform, 0);
453       }
454       fftwf_forget_wisdom ();
455       fftwf_cleanup ();
456 
457       temp_dir.removeRecursively (); // clean up temp files
458       return result;
459     }
460   catch (std::exception const& e)
461     {
462       MessageBox::critical_message (nullptr, "Fatal error", e.what ());
463       std::cerr << "Error: " << e.what () << '\n';
464     }
465   catch (...)
466     {
467       MessageBox::critical_message (nullptr, "Unexpected fatal error");
468       std::cerr << "Unexpected fatal error\n";
469       throw;			// hoping the runtime might tell us more about the exception
470     }
471   return -1;
472 }
473