X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcommon%2Fquassel.cpp;h=bf63798433ed7e6200e4d31e4e43208752c4715c;hp=29b6e55ec87ee428708134ad65b1921d13dbeb7e;hb=fea3d6e449325a4b97c98beebe520fe78a4b9415;hpb=f5d08a1e466b69f8c35e3b30e4cf898a71bc8b83 diff --git a/src/common/quassel.cpp b/src/common/quassel.cpp index 29b6e55e..bf637984 100644 --- a/src/common/quassel.cpp +++ b/src/common/quassel.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-08 by the Quassel IRC Team * + * Copyright (C) 2005-09 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -20,12 +20,18 @@ #include "quassel.h" +#include #include +#if !defined Q_OS_WIN32 && !defined Q_OS_MAC +# include +#endif #include #include -#include -#include +#include +#include +#include +#include #include "message.h" #include "identity.h" @@ -35,34 +41,24 @@ #include "syncableobject.h" Quassel::BuildInfo Quassel::_buildInfo; -CliParser *Quassel::_cliParser = 0; +AbstractCliParser *Quassel::_cliParser = 0; Quassel::RunMode Quassel::_runMode; +QString Quassel::_configDirPath; +QString Quassel::_translationDirPath; +QStringList Quassel::_dataDirPaths; bool Quassel::_initialized = false; bool Quassel::DEBUG = false; QString Quassel::_coreDumpFileName; +Quassel *Quassel::_instance = 0; +bool Quassel::_handleCrashes = true; Quassel::Quassel() { - Q_INIT_RESOURCE(i18n); + Q_ASSERT(!_instance); + _instance = this; // We catch SIGTERM and SIGINT (caused by Ctrl+C) to graceful shutdown Quassel. signal(SIGTERM, handleSignal); signal(SIGINT, handleSignal); - - // we have crashhandler for win32 and unix (based on execinfo). - // on mac os we use it's integrated backtrace generator -#if defined(Q_OS_WIN32) || (defined(HAVE_EXECINFO) && !defined(Q_OS_MAC)) - signal(SIGABRT, handleSignal); - signal(SIGSEGV, handleSignal); -# ifndef Q_OS_WIN32 - signal(SIGBUS, handleSignal); -# endif -#endif - - _cliParser = new CliParser(); - - // put shared client&core arguments here - cliParser()->addSwitch("debug",'d', tr("Enable debug output")); - cliParser()->addSwitch("help",'h', tr("Display this help and exit")); } Quassel::~Quassel() { @@ -73,45 +69,70 @@ bool Quassel::init() { if(_initialized) return true; // allow multiple invocations because of MonolithicApplication + if (_handleCrashes) { + // we have crashhandler for win32 and unix (based on execinfo). +#if defined(Q_OS_WIN32) || defined(HAVE_EXECINFO) +# ifndef Q_OS_WIN32 + // we only handle crashes ourselves if coredumps are disabled + struct rlimit *limit = (rlimit *) malloc(sizeof(struct rlimit)); + int rc = getrlimit(RLIMIT_CORE, limit); + + if(rc == -1 || !((long)limit->rlim_cur > 0 || limit->rlim_cur == RLIM_INFINITY)) { +# endif /* Q_OS_WIN32 */ + signal(SIGABRT, handleSignal); + signal(SIGSEGV, handleSignal); +# ifndef Q_OS_WIN32 + signal(SIGBUS, handleSignal); + } + free(limit); +# endif /* Q_OS_WIN32 */ +#endif /* Q_OS_WIN32 || HAVE_EXECINFO */ + } + _initialized = true; qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); registerMetaTypes(); - setupTranslations(); - - QCoreApplication::setApplicationName(buildInfo().applicationName); - QCoreApplication::setOrganizationName(buildInfo().organizationName); - QCoreApplication::setOrganizationDomain(buildInfo().organizationDomain); Network::setDefaultCodecForServer("ISO-8859-1"); Network::setDefaultCodecForEncoding("UTF-8"); Network::setDefaultCodecForDecoding("ISO-8859-15"); - if(!cliParser()->parse(QCoreApplication::arguments()) || isOptionSet("help")) { + if(isOptionSet("help")) { cliParser()->usage(); return false; } + + if(isOptionSet("version")) { + std::cout << qPrintable("Quassel IRC: " + Quassel::buildInfo().plainVersionString) << std::endl; + return false; + } + DEBUG = isOptionSet("debug"); return true; } +void Quassel::quit() { + QCoreApplication::quit(); +} + //! Register our custom types with Qt's Meta Object System. /** This makes them available for QVariant and in signals/slots, among other things. * */ void Quassel::registerMetaTypes() { // Complex types - qRegisterMetaType("QVariant"); qRegisterMetaType("Message"); qRegisterMetaType("BufferInfo"); qRegisterMetaType("NetworkInfo"); + qRegisterMetaType("Network::Server"); qRegisterMetaType("Identity"); qRegisterMetaType("Network::ConnectionState"); - qRegisterMetaTypeStreamOperators("QVariant"); qRegisterMetaTypeStreamOperators("Message"); qRegisterMetaTypeStreamOperators("BufferInfo"); qRegisterMetaTypeStreamOperators("NetworkInfo"); + qRegisterMetaTypeStreamOperators("Network::Server"); qRegisterMetaTypeStreamOperators("Identity"); qRegisterMetaTypeStreamOperators("Network::ConnectionState"); @@ -128,27 +149,18 @@ void Quassel::registerMetaTypes() { qRegisterMetaTypeStreamOperators("UserId"); qRegisterMetaTypeStreamOperators("AccountId"); qRegisterMetaTypeStreamOperators("MsgId"); -} - -void Quassel::setupTranslations() { - // Set up i18n support - QLocale locale = QLocale::system(); - - QTranslator *qtTranslator = new QTranslator(qApp); - qtTranslator->setObjectName("QtTr"); - qtTranslator->load(QString(":i18n/qt_%1").arg(locale.name())); - qApp->installTranslator(qtTranslator); - QTranslator *quasselTranslator = new QTranslator(qApp); - quasselTranslator->setObjectName("QuasselTr"); - quasselTranslator->load(QString(":i18n/quassel_%1").arg(locale.name())); - qApp->installTranslator(quasselTranslator); + // Versions of Qt prior to 4.7 didn't define QVariant as a meta type + if(!QMetaType::type("QVariant")) { + qRegisterMetaType("QVariant"); + qRegisterMetaTypeStreamOperators("QVariant"); + } } void Quassel::setupBuildInfo(const QString &generated) { _buildInfo.applicationName = "Quassel IRC"; - _buildInfo.coreApplicationName = "Quassel Core"; - _buildInfo.clientApplicationName = "Quassel Client"; + _buildInfo.coreApplicationName = "quasselcore"; + _buildInfo.clientApplicationName = "quasselclient"; _buildInfo.organizationName = "Quassel Project"; _buildInfo.organizationDomain = "quassel-irc.org"; @@ -170,8 +182,7 @@ void Quassel::setupBuildInfo(const QString &generated) { _buildInfo.plainVersionString = QString("v%1 (dist-%2)") .arg(_buildInfo.baseVersion) .arg(_buildInfo.commitHash.left(7)); - _buildInfo.fancyVersionString - = QString("v%1 (dist-%2)") + _buildInfo.fancyVersionString = QString("v%1 (dist-%2)") .arg(_buildInfo.baseVersion) .arg(_buildInfo.commitHash.left(7)) .arg(_buildInfo.commitHash); @@ -183,13 +194,13 @@ void Quassel::setupBuildInfo(const QString &generated) { // analyze what we got from git-describe QRegExp rx("(.*)-(\\d+)-g([0-9a-f]+)$"); if(rx.exactMatch(_buildInfo.generatedVersion)) { - QString distance = rx.cap(2) == "0" ? QString() : QString(" [+%1]").arg(rx.cap(2)); - _buildInfo.plainVersionString = QString("v%1%2 (git-%3%4)") - .arg(rx.cap(1), distance, rx.cap(3)) + QString distance = rx.cap(2) == "0" ? QString() : QString("%1+%2 ").arg(rx.cap(1), rx.cap(2)); + _buildInfo.plainVersionString = QString("v%1 (%2git-%3%4)") + .arg(_buildInfo.baseVersion, distance, rx.cap(3)) .arg(_buildInfo.isSourceDirty ? "*" : ""); if(!_buildInfo.commitHash.isEmpty()) { - _buildInfo.fancyVersionString = QString("v%1%2 (git-%3%4)") - .arg(rx.cap(1), distance, rx.cap(3)) + _buildInfo.fancyVersionString = QString("v%1 (%2git-%3%4)") + .arg(_buildInfo.baseVersion, distance, rx.cap(3)) .arg(_buildInfo.isSourceDirty ? "*" : "") .arg(_buildInfo.commitHash); } @@ -207,7 +218,10 @@ void Quassel::handleSignal(int sig) { case SIGTERM: case SIGINT: qWarning("%s", qPrintable(QString("Caught signal %1 - exiting.").arg(sig))); - QCoreApplication::quit(); + if(_instance) + _instance->quit(); + else + QCoreApplication::quit(); break; case SIGABRT: case SIGSEGV: @@ -215,6 +229,7 @@ void Quassel::handleSignal(int sig) { case SIGBUS: #endif logBacktrace(coreDumpFileName()); + exit(EXIT_FAILURE); break; default: break; @@ -228,16 +243,25 @@ void Quassel::logFatalMessage(const char *msg) { QFile dumpFile(coreDumpFileName()); dumpFile.open(QIODevice::Append); QTextStream dumpStream(&dumpFile); - + dumpStream << "Fatal: " << msg << '\n'; dumpStream.flush(); dumpFile.close(); #endif } +Quassel::Features Quassel::features() { + Features feats = 0; + for(int i = 1; i <= NumFeatures; i<<=1) + feats |= (Feature) i; + + return feats; +} + const QString &Quassel::coreDumpFileName() { if(_coreDumpFileName.isEmpty()) { - _coreDumpFileName = QString("Quassel-Crash-%1.log").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmm")); + QDir configDir(configDirPath()); + _coreDumpFileName = configDir.absoluteFilePath(QString("Quassel-Crash-%1.log").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmm"))); QFile dumpFile(_coreDumpFileName); dumpFile.open(QIODevice::Append); QTextStream dumpStream(&dumpFile); @@ -248,3 +272,160 @@ const QString &Quassel::coreDumpFileName() { } return _coreDumpFileName; } + +QString Quassel::configDirPath() { + if(!_configDirPath.isEmpty()) + return _configDirPath; + + if(Quassel::isOptionSet("datadir")) { + qWarning() << "Obsolete option --datadir used!"; + _configDirPath = Quassel::optionValue("datadir"); + } else if(Quassel::isOptionSet("configdir")) { + _configDirPath = Quassel::optionValue("configdir"); + } else { + +#ifdef Q_WS_MAC + // On Mac, the path is always the same + _configDirPath = QDir::homePath() + "/Library/Application Support/Quassel/"; +#else + // We abuse QSettings to find us a sensible path on the other platforms +# ifdef Q_WS_WIN + // don't use the registry + QSettings::Format format = QSettings::IniFormat; +# else + QSettings::Format format = QSettings::NativeFormat; +# endif + QSettings s(format, QSettings::UserScope, QCoreApplication::organizationDomain(), buildInfo().applicationName); + QFileInfo fileInfo(s.fileName()); + _configDirPath = fileInfo.dir().absolutePath(); +#endif /* Q_WS_MAC */ + } + + if(!_configDirPath.endsWith(QDir::separator()) && !_configDirPath.endsWith('/')) + _configDirPath += QDir::separator(); + + QDir qDir(_configDirPath); + if(!qDir.exists(_configDirPath)) { + if(!qDir.mkpath(_configDirPath)) { + qCritical() << "Unable to create Quassel config directory:" << qPrintable(qDir.absolutePath()); + return QString(); + } + } + + return _configDirPath; +} + +QStringList Quassel::dataDirPaths() { + return _dataDirPaths; +} + +QStringList Quassel::findDataDirPaths() const { + QStringList dataDirNames = QString(qgetenv("XDG_DATA_DIRS")).split(':', QString::SkipEmptyParts); + + if(!dataDirNames.isEmpty()) { + for(int i = 0; i < dataDirNames.count(); i++) + dataDirNames[i].append("/apps/quassel/"); + } else { + // Provide a fallback +#ifdef Q_OS_WIN32 + dataDirNames << qgetenv("APPDATA") + QCoreApplication::organizationDomain() + "/share/apps/quassel/" + << qgetenv("APPDATA") + QCoreApplication::organizationDomain() + << QCoreApplication::applicationDirPath(); + } +#elif defined Q_WS_MAC + dataDirNames << QDir::homePath() + "/Library/Application Support/Quassel/" + << QCoreApplication::applicationDirPath(); + } +#else + dataDirNames.append("/usr/share/apps/quassel/"); + } + // on UNIX, we always check our install prefix + QString appDir = QCoreApplication::applicationDirPath(); + int binpos = appDir.lastIndexOf("/bin"); + if(binpos >= 0) { + appDir.replace(binpos, 4, "/share"); + appDir.append("/apps/quassel/"); + if(!dataDirNames.contains(appDir)) + dataDirNames.append(appDir); + } +#endif + + // add resource path and workdir just in case + dataDirNames << QCoreApplication::applicationDirPath() + "/data/" + << ":/data/"; + + // append trailing '/' and check for existence + QStringList::Iterator iter = dataDirNames.begin(); + while(iter != dataDirNames.end()) { + if(!iter->endsWith(QDir::separator()) && !iter->endsWith('/')) + iter->append(QDir::separator()); + if(!QFile::exists(*iter)) + iter = dataDirNames.erase(iter); + else + ++iter; + } + + return dataDirNames; +} + +QString Quassel::findDataFilePath(const QString &fileName) { + QStringList dataDirs = dataDirPaths(); + foreach(QString dataDir, dataDirs) { + QString path = dataDir + fileName; + if(QFile::exists(path)) + return path; + } + return QString(); +} + +QStringList Quassel::scriptDirPaths() { + QStringList res(configDirPath() + "scripts/"); + foreach(QString path, dataDirPaths()) + res << path + "scripts/"; + return res; +} + +QString Quassel::translationDirPath() { + if(_translationDirPath.isEmpty()) { + // We support only one translation dir; fallback mechanisms wouldn't work else. + // This means that if we have a $data/translations dir, the internal :/i18n resource won't be considered. + foreach(const QString &dir, dataDirPaths()) { + if(QFile::exists(dir + "translations/")) { + _translationDirPath = dir + "translations/"; + break; + } + } + if(_translationDirPath.isEmpty()) + _translationDirPath = ":/i18n/"; + } + return _translationDirPath; +} + +void Quassel::loadTranslation(const QLocale &locale) { + QTranslator *qtTranslator = QCoreApplication::instance()->findChild("QtTr"); + QTranslator *quasselTranslator = QCoreApplication::instance()->findChild("QuasselTr"); + + if(qtTranslator) + qApp->removeTranslator(qtTranslator); + if(quasselTranslator) + qApp->removeTranslator(quasselTranslator); + + // We use QLocale::C to indicate that we don't want a translation + if(locale.language() == QLocale::C) + return; + + qtTranslator = new QTranslator(qApp); + qtTranslator->setObjectName("QtTr"); + qApp->installTranslator(qtTranslator); + + quasselTranslator = new QTranslator(qApp); + quasselTranslator->setObjectName("QuasselTr"); + qApp->installTranslator(quasselTranslator); + + QLocale::setDefault(locale); + + bool success = qtTranslator->load(QString("qt_%1").arg(locale.name()), translationDirPath()); + if(!success) + qtTranslator->load(QString("qt_%1").arg(locale.name()), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + quasselTranslator->load(QString("%1").arg(locale.name()), translationDirPath()); +}