diff --git a/language/duchain/repositories/itemrepository.cpp b/language/duchain/repositories/itemrepository.cpp index b1d222b9d1..e79d160a79 100644 --- a/language/duchain/repositories/itemrepository.cpp +++ b/language/duchain/repositories/itemrepository.cpp @@ -1,376 +1,355 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemrepository.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "../duchain.h" namespace KDevelop { //If KDevelop crashed this many times consicutively, clean up the repository const int crashesBeforeCleanup = 2; //The global item-reposity registry ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = 0; uint staticItemRepositoryVersion() { //Increase this to reset incompatible item-repositories return 72; } AbstractItemRepository::~AbstractItemRepository() { } ItemRepositoryRegistry::ItemRepositoryRegistry() : m_mutex(QMutex::Recursive) { Q_ASSERT( ICore::self() ); Q_ASSERT( ICore::self()->activeSession() ); QString repositoryPath = repositoryPathForSession( ICore::self()->activeSession() ); open( repositoryPath, false ); } ItemRepositoryRegistry* ItemRepositoryRegistry::self() { if( !m_self ) { ///We intentionally leak the registry, to prevent problems in the destruction order, where ///the actual repositories might get deleted later than the repository registry. m_self = new ItemRepositoryRegistry; } return m_self; } QString ItemRepositoryRegistry::repositoryPathForSession(ISession* session) { QString xdgCacheDir = QProcessEnvironment::systemEnvironment().value( "XDG_CACHE_HOME", QDir::homePath() + "/.cache" ) + "/kdevduchain"; QString baseDir = QProcessEnvironment::systemEnvironment().value( "KDEV_DUCHAIN_DIR", xdgCacheDir ); baseDir += QString( "/%1-%2" ).arg( qAppName() ).arg( session->id().toString() ); KStandardDirs::makeDir( baseDir ); return baseDir; } void ItemRepositoryRegistry::deleteRepositoryFromDisk(ISession* session) { QString repositoryPath = repositoryPathForSession( session ); // Now, as we have only the global item-repository registry, assume that if and only if // the given session is ours, its cache path is used by the said global item-repository registry. if( m_self && m_self->m_path == repositoryPath ) { m_self->m_shallDelete = true; return; } // Otherwise, given session is not ours. // Try to lock it; and, if locking succeeds, remove its item-repository directory directly. SessionController::LockSessionState state = SessionController::tryLockSession( session->id(), true ); if( state ) { removeDirectory( repositoryPath ); // Then unlock the session. QDBusConnection::sessionBus().unregisterService( state.DBusService ); state.lockFile->unlock(); } } QMutex& ItemRepositoryRegistry::mutex() { return m_mutex; } QAtomicInt& ItemRepositoryRegistry::getCustomCounter(const QString& identity, int initialValue) { if(!m_customCounters.contains(identity)) m_customCounters.insert(identity, new QAtomicInt(initialValue)); return *m_customCounters[identity]; } ///The global item-repository registry that is used by default ItemRepositoryRegistry& globalItemRepositoryRegistry() { return *ItemRepositoryRegistry::self(); } void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository, AbstractRepositoryManager* manager) { QMutexLocker lock(&m_mutex); m_repositories.insert(repository, manager); if(!m_path.isEmpty()) { if(!repository->open(m_path)) { deleteDataDirectory(); kError() << "failed to open a repository"; abort(); } } } QString ItemRepositoryRegistry::path() const { //We cannot lock the mutex here, since this may be called with one of the repositories locked, //and that may lead to a deadlock when at the same time a storing is requested return m_path; } void ItemRepositoryRegistry::lockForWriting() { QMutexLocker lock(&m_mutex); //Create is_writing QFile f(m_path + "/is_writing"); f.open(QIODevice::WriteOnly); f.close(); } void ItemRepositoryRegistry::unlockForWriting() { QMutexLocker lock(&m_mutex); //Delete is_writing QFile::remove(m_path + "/is_writing"); } void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) { QMutexLocker lock(&m_mutex); Q_ASSERT(m_repositories.contains(repository)); repository->close(); m_repositories.remove(repository); } //After calling this, the data-directory may be a new one void ItemRepositoryRegistry::deleteDataDirectory(bool recreate) { QMutexLocker lock(&m_mutex); //lockForWriting creates a file, that prevents any other KDevelop instance from using the directory as it is. //Instead, the other instance will try to delete the directory as well. lockForWriting(); bool result = removeDirectory(m_path); Q_ASSERT(result); Q_UNUSED(result); // Just recreate the directory then; leave old path (as it is dependent on appname and session only). if(recreate) { KStandardDirs::makeDir(m_path); } } void setCrashCounter(QFile& crashesFile, int count) { crashesFile.close(); crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); QDataStream writeStream(&crashesFile); writeStream << count; } bool ItemRepositoryRegistry::open(const QString& path, bool clear) { QMutexLocker mlock(&m_mutex); if(m_path == path && !clear) { return true; } m_path = path; if(QFile::exists(m_path + "/is_writing")) { kWarning() << "repository" << m_path << "was write-locked, it probably is inconsistent"; clear = true; } QDir pathDir(m_path); pathDir.setFilter(QDir::Files); // If there is no files in the repository, it has been just cleared if(!pathDir.count()) { if(!QFile::exists( m_path + QString("/version_%1").arg(staticItemRepositoryVersion()) )) { kWarning() << "version-hint not found, seems to be an old version"; clear = true; } else if(getenv("CLEAR_DUCHAIN_DIR")) { kWarning() << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; clear = true; } } QFile crashesFile(m_path + QString("/crash_counter")); if(crashesFile.open(QIODevice::ReadOnly)) { int count; QDataStream stream(&crashesFile); stream >> count; kDebug() << "current count of crashes: " << count; if(count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { - int userAnswer = 0; - ///NOTE: we don't want to crash our beloved tools when run in no-gui mode - ///NOTE 2: create a better, reusable version of the below for other tools - if (QApplication::type() == QApplication::Tty) { - // no ui-mode e.g. for duchainify and other tools - QTextStream out(stdout); - out << i18np("Session crashed %1 time in a row", "Session crashed %1 times in a row", count) << endl; - out << endl; - QTextStream in(stdin); - QString input; - while(true) { - out << i18n("Clear cache: [Y/n] ") << flush; - input = in.readLine().trimmed(); - if (input.toLower() == "y" || input.isEmpty()) { - userAnswer = KMessageBox::Yes; - break; - } else if (input.toLower() == "n") { - userAnswer = KMessageBox::No; - break; - } - } - } else { - userAnswer = KMessageBox::questionYesNo(0, - i18np("The Session crashed once.", "The Session crashed %1 times in a row.", count) + "\n\n" + i18n("The crash may be caused by a corruption of cached data.\n\nPress OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin."), - i18n("Session crashed"), - KStandardGuiItem::ok(), - KStandardGuiItem::cancel()); - } - if (userAnswer == KMessageBox::Yes) { + bool userAnswer = askUser( i18np( "Session crashed %1 time in a row", "Session crashed %1 times in a row", count ), + i18nc( "@action", "Clear cache" ), + i18nc( "@title", "Session crashed" ), + i18n("The crash may be caused by a corruption of cached data.\n\n" + "Press OK if you want KDevelop to clear the cache, otherwise press Cancel if you are sure the crash has another origin."), + true ); + if(userAnswer) { clear = true; kDebug() << "User chose to clean repository"; } else { setCrashCounter(crashesFile, 1); kDebug() << "User chose to reset crash counter"; } } else { ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. setCrashCounter(crashesFile, ++count); } } else { setCrashCounter(crashesFile, 1); } if(clear) { kWarning() << QString("The data-repository at %1 has to be cleared.").arg(m_path); // KMessageBox::information( 0, i18n("The data-repository at %1 has to be cleared. Either the disk format has changed, or KDevelop crashed while writing the repository.", m_path ) ); #ifdef Q_OS_WIN /// on Windows a file can't be deleted unless the last file handle gets closed /// deleteDataDirectory would enter a never ending loop crashesFile.close(); #endif deleteDataDirectory(); } foreach(AbstractItemRepository* repository, m_repositories.keys()) { if(!repository->open(path)) { deleteDataDirectory(); kError() << "failed to open a repository"; abort(); } } QFile f(path + "/Counters"); if(f.open(QIODevice::ReadOnly)) { QDataStream stream(&f); while(!stream.atEnd()) { //Read in all custom counter values QString counterName; stream >> counterName; int counterValue; stream >> counterValue; if(m_customCounters.contains(counterName)) *m_customCounters[counterName] = counterValue; else getCustomCounter(counterName, 0) = counterValue; } }else{ // kDebug() << "Could not open counter file"; } return true; } void ItemRepositoryRegistry::store() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) repository->store(); QFile versionFile(m_path + QString("/version_%1").arg(staticItemRepositoryVersion())); if(versionFile.open(QIODevice::WriteOnly)) { versionFile.close(); }else{ kWarning() << "Could not open version file for writing"; } //Store all custom counter values QFile f(m_path + "/Counters"); if(f.open(QIODevice::WriteOnly)) { f.resize(0); QDataStream stream(&f); for(QMap::const_iterator it = m_customCounters.constBegin(); it != m_customCounters.constEnd(); ++it) { stream << it.key(); stream << it.value()->fetchAndAddRelaxed(0); } }else{ kWarning() << "Could not open counter file for writing"; } } void ItemRepositoryRegistry::printAllStatistics() const { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) { kDebug() << "statistics in" << repository->repositoryName() << ":"; kDebug() << repository->printStatistics(); } } int ItemRepositoryRegistry::finalCleanup() { QMutexLocker lock(&m_mutex); int changed = false; foreach(AbstractItemRepository* repository, m_repositories.keys()) { int added = repository->finalCleanup(); changed += added; kDebug() << "cleaned in" << repository->repositoryName() << ":" << added; } return changed; } void ItemRepositoryRegistry::close() { QMutexLocker lock(&m_mutex); foreach(AbstractItemRepository* repository, m_repositories.keys()) repository->close(); m_path.clear(); } ItemRepositoryRegistry::~ItemRepositoryRegistry() { close(); QMutexLocker lock(&m_mutex); foreach(QAtomicInt* counter, m_customCounters) delete counter; } void ItemRepositoryRegistry::shutdown() { if(m_shallDelete) { deleteDataDirectory(false); } else { QFile::remove(m_path + QString("/crash_counter")); } } } diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 29de12617b..48147726f6 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,96 +1,97 @@ add_definitions( -DKDE_DEFAULT_DEBUG_AREA=9508 ) ########### next target ############### set(kdevplatformutil_LIB_SRCS formattinghelpers.cpp richtextpushbutton.cpp richtexttoolbutton.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp commandexecutor.cpp environmentconfigurebutton.cpp environmentselectionwidget.cpp environmentgrouplist.cpp activetooltip.cpp executecompositejob.cpp fileutils.cpp + shellutils.cpp sequentiallyrunjobs.cpp multilevellistview.cpp projecttestjob.cpp ) set (kdevplatformutil_LIB_UI runoptions.ui ) if(NOT WIN32) add_subdirectory(dbus_socket_transformer) endif(NOT WIN32) add_subdirectory(duchainify) add_subdirectory(tests) kde4_add_ui_files(kdevplatformutil_LIB_SRCS ${kdevplatformutil_LIB_US}) kde4_add_library(kdevplatformutil SHARED ${kdevplatformutil_LIB_SRCS}) target_link_libraries(kdevplatformutil ${KDE4_KDEUI_LIBS} ${KDE4_KCMUTILS_LIBRARY} kdevplatforminterfaces ) # Might want to add kdevplatform* when they're exported targets target_link_libraries(kdevplatformutil LINK_INTERFACE_LIBRARIES ${KDE4_KDEUI_LIBS}) set_target_properties(kdevplatformutil PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION}) install(TARGETS kdevplatformutil EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES kdevplatform_shell_environment.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) install( FILES kdev_format_source.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) ########### install files ############### install( FILES formattinghelpers.h richtextpushbutton.h richtexttoolbutton.h kdevstringhandler.h ksharedobject.h focusedtreeview.h activetooltip.h processlinemaker.h commandexecutor.h utilexport.h environmentconfigurebutton.h environmentselectionwidget.h environmentgrouplist.h pushvalue.h kdevvarlengtharray.h embeddedfreetree.h executecompositejob.h convenientfreelist.h spinlock.h fileutils.h sequentiallyrunjobs.h multilevellistview.h projecttestjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util COMPONENT Devel) install( FILES google/dense_hash_map google/dense_hash_set google/sparse_hash_map google/sparse_hash_set google/sparsetable google/type_traits.h google/hash_fun.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util/google COMPONENT Devel) install( FILES google/sparsehash/densehashtable.h google/sparsehash/sparseconfig.h google/sparsehash/sparseconfig_windows.h google/sparsehash/sparsehashtable.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util/google/sparsehash COMPONENT Devel) diff --git a/util/shellutils.cpp b/util/shellutils.cpp new file mode 100644 index 0000000000..3b508019f5 --- /dev/null +++ b/util/shellutils.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of KDevelop + * + * Copyright 2012 Ivan Shapovalov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "shellutils.h" +#include +#include +#include +#include + +namespace KDevelop { + +bool askUser( const QString& mainText, + const QString& ttyPrompt, + const QString& mboxTitle, + const QString& mboxAdditionalText, + bool ttyDefaultToYes ) +{ + if( QApplication::type() == QApplication::Tty ) { + // no ui-mode e.g. for duchainify and other tools + QTextStream out( stdout ); + out << mainText << endl; + QTextStream in( stdin ); + QString input; + forever { + if( ttyDefaultToYes ) { + out << QString( "%1: [Y/n] " ).arg( ttyPrompt ); + } else { + out << QString( "%1: [y/N] ").arg( ttyPrompt ); + } + input = in.readLine().trimmed(); + if( input.isEmpty() ) { + return ttyDefaultToYes; + } else if( input.toLower() == "y" ) { + return true; + } else if( input.toLower() == "n" ) { + return false; + } + } + } else { + int userAnswer = KMessageBox::questionYesNo( 0, + mainText + "\n\n" + mboxAdditionalText, + mboxTitle, + KStandardGuiItem::ok(), + KStandardGuiItem::cancel() ); + return userAnswer == KMessageBox::Yes; + } +} + + +} diff --git a/util/shellutils.h b/util/shellutils.h new file mode 100644 index 0000000000..d8622bbf8c --- /dev/null +++ b/util/shellutils.h @@ -0,0 +1,43 @@ +/* + * This file is part of KDevelop + * + * Copyright 2012 Ivan Shapovalov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SHELLUTILS_H +#define SHELLUTILS_H + +#include "utilexport.h" + +class QString; + +namespace KDevelop { + +/** +* Asks user of an arbitary question by using either a \ref KMessageBox or stdin/stderr. +* +* @return @c true if user chose "Yes" and @c false otherwise. +*/ +bool KDEVPLATFORMUTIL_EXPORT askUser( const QString& mainText, + const QString& ttyPrompt, + const QString& mboxTitle, + const QString& mboxAdditionalText, + bool ttyDefaultToYes = true ); +} + +#endif // SHELLUTILS_H