Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:12.1:Update
kdebase4-runtime
4_7_BRANCH.diff
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 4_7_BRANCH.diff of Package kdebase4-runtime
--- a/BRANCH_STATUS +++ b/BRANCH_STATUS @@ -0,0 +1,2 @@ +current HEAD: 3bb47ee3b3326105e8fe5b1be6c9ca059dfff626 +git diff v4.7.2..origin/KDE/4.7 diff --git a/activitymanager/kactivitymanagerd.desktop b/activitymanager/kactivitymanagerd.desktop index 6218dd5..d6d197f 100644 --- a/activitymanager/kactivitymanagerd.desktop +++ b/activitymanager/kactivitymanagerd.desktop @@ -78,7 +78,7 @@ Comment[bg]=Ядро за управление на дейности Comment[bs]=Pozadina za upravljanje aktivnostima Comment[ca]=Dorsal de gestió d'activitats Comment[ca@valencia]=Dorsal de gestió d'activitats -Comment[cs]=Backend pro správu aktivit +Comment[cs]=Podpůrná vrstva pro správu aktivit Comment[da]=Motor til aktivitetshåndtering Comment[de]=Backend zur Aktivitätenverwaltung Comment[el]=Σύστημα διαχείρισης δραστηριότητας diff --git a/kioslave/thumbnail/cursorthumbnail.desktop b/kioslave/thumbnail/cursorthumbnail.desktop index 1f50bf6..094d688 100644 --- a/kioslave/thumbnail/cursorthumbnail.desktop +++ b/kioslave/thumbnail/cursorthumbnail.desktop @@ -81,7 +81,7 @@ Name[te]=ములుకు దస్త్రాలు Name[tg]=Файлҳои курсор Name[th]=แฟ้มเคอร์เซอร์ต่างๆ Name[tr]=İmleç Dosyaları -Name[ug]=نۇر بەلگە ھۆججەتلەر +Name[ug]=نۇربەلگە ھۆججەتلەر Name[uk]=Файли курсорів Name[uz]=Kursor fayllari Name[uz@cyrillic]=Курсор файллари diff --git a/knotify/hardwarenotifications.notifyrc b/knotify/hardwarenotifications.notifyrc index d6589e3..f8f0e6c 100644 --- a/knotify/hardwarenotifications.notifyrc +++ b/knotify/hardwarenotifications.notifyrc @@ -188,7 +188,7 @@ Name[ta]=கருவி நோட்டம் Name[tg]=Извещение о новых устройствах Name[th]=แจ้งให้ทราบถึงอุปกรณ์ Name[tr]=Aygıt Bildirici -Name[ug]=ئۈسكىنە ئۇقتۇرۇشى +Name[ug]=ئۈسكۈنە ئۇقتۇرۇشى Name[uk]=Сповіщення про пристрої Name[wa]=Notifiaedje d' éndjin Name[x-test]=xxDevice Notifierxx @@ -241,7 +241,7 @@ Comment[sr@latin]=Plasma izveštavač o uređajima je prisutan Comment[sv]=Plasma enhetsunderrättelser är tillgänglig Comment[th]=แสดงตัวแจ้งเกี่ยวกับอุปกรณ์บนพลาสมาอยู่ Comment[tr]=Plasma aygıt bildiricisi mevcut -Comment[ug]=پىلازما ئۈسكىنىسىنىڭ بىلدۈرگۈسى مەۋجۇت +Comment[ug]=پىلازما ئۈسكۈنىسىنىڭ بىلدۈرگۈسى مەۋجۇت Comment[uk]=Сповіщувач Плазми про пристрої увімкнено Comment[x-test]=xxThe Plasma device notifier is presentxx Comment[zh_CN]=显示 Plasma 设备通知 @@ -424,7 +424,7 @@ Name[sr@latin]=Uređaj se može bezbedno ukloniti Name[sv]=Enheten kan urkopplas säkert Name[th]=สามารถถอดอุปกรณ์ได้อย่างปลอดภัย Name[tr]=Aygıt güvenli bir şekilde kaldırılabilir -Name[ug]=ئۈسكىنىنى ھازىر بىخەتەر چىقىرىشقا بولىدۇ +Name[ug]=ئۈسكۈنىنى ھازىر بىخەتەر چىقىرىشقا بولىدۇ Name[uk]=Пристрій можна безпечно від’єднувати Name[x-test]=xxThe device can be safely removedxx Name[zh_CN]=可以安全的移除设备了 @@ -475,7 +475,7 @@ Comment[sr@latin]=Upravo demontirani uređaj može se sada bezbedno ukloniti. Comment[sv]=Enheten som precis har avmonterats kan nu urkopplas säkert. Comment[th]=อุปกรณ์ที่เพิ่งยกเลิกการเมานท์สามารถถอดออกได้อย่างปลอดภัย Comment[tr]=Çıkarılan aygıt güvenli bir şekilde kaldırılabilir -Comment[ug]=ھازىر ئېگەرسىزلىگەن ئۈسكىنىنى بىخەتەر چىقىرىشقا بولىدۇ +Comment[ug]=ھازىر ئېگەرسىزلىگەن ئۈسكۈنىنى بىخەتەر چىقىرىشقا بولىدۇ Comment[uk]=Пристрій, який щойно було демонтовано можна безпечно від’єднувати. Comment[x-test]=xxThe device which has been just unmounted is now safe to remove.xx Comment[zh_CN]=已经卸载了设备,可以安全移除了。 diff --git a/knotify/notifybypopup.cpp b/knotify/notifybypopup.cpp index 3b93c24..213421c 100644 --- a/knotify/notifybypopup.cpp +++ b/knotify/notifybypopup.cpp @@ -68,6 +68,10 @@ NotifyByPopup::NotifyByPopup(QObject *parent) watcher->addWatchedService(dbusServiceName); connect(watcher, SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)), SLOT(slotServiceOwnerChanged(const QString&, const QString&, const QString&))); +#ifdef Q_WS_WIN + if(!m_dbusServiceExists) + QDBusConnection::sessionBus().interface()->startService("org.freedesktop.Notifications"); +#endif } diff --git a/kurifilter-plugins/ikws/ikwsopts.cpp b/kurifilter-plugins/ikws/ikwsopts.cpp index ceea753..7aa3004 100644 --- a/kurifilter-plugins/ikws/ikwsopts.cpp +++ b/kurifilter-plugins/ikws/ikwsopts.cpp @@ -25,6 +25,7 @@ #include "searchprovider.h" #include "searchproviderdlg.h" +#include <KDE/KDebug> #include <KDE/KStandardDirs> #include <KDE/KServiceTypeTrader> #include <KDE/KBuildSycocaProgressDialog> @@ -102,8 +103,7 @@ QVariant ProvidersModel::data(const QModelIndex& index, int role) const void ProvidersModel::setProviders(const QList<SearchProvider*>& providers, const QStringList& favoriteEngines) { m_providers = providers; - m_favoriteEngines = QSet<QString>::fromList(favoriteEngines); - reset(); + setFavoriteProviders(favoriteEngines); } void ProvidersModel::setFavoriteProviders(const QStringList& favoriteEngines) @@ -148,7 +148,7 @@ void ProvidersModel::deleteProvider(SearchProvider* p) void ProvidersModel::addProvider(SearchProvider* p) { beginInsertRows(QModelIndex(), m_providers.size(), m_providers.size()); - m_providers.append(p); + m_providers.append(p); endInsertRows(); emit dataModified(); } @@ -276,7 +276,7 @@ void FilterOptions::load() Q_FOREACH(const KService::Ptr &service, services) { - SearchProvider* provider=new SearchProvider(service); + SearchProvider* provider = new SearchProvider(service); if (defaultSearchEngine == provider->desktopEntryName()) defaultProviderIndex = providers.size(); providers.append(provider); @@ -318,58 +318,26 @@ void FilterOptions::save() group.writeEntry("FavoriteSearchEngines", m_providersModel->favoriteEngines()); group.writeEntry("UseSelectedProvidersOnly", m_dlg.cbUseSelectedShortcutsOnly->isChecked()); - QList<SearchProvider*> providers = m_providersModel->providers(); - QString path = KGlobal::mainComponent().dirs()->saveLocation("services", "searchproviders/"); int changedProviderCount = 0; + QList<SearchProvider*> providers = m_providersModel->providers(); + const QString path = KGlobal::mainComponent().dirs()->saveLocation("services", "searchproviders/"); + Q_FOREACH(SearchProvider* provider, providers) { if (!provider->isDirty()) continue; - changedProviderCount++; - - QString name = provider->desktopEntryName(); - if (name.isEmpty()) - { - // New provider - // Take the longest search shortcut as filename, - // if such a file already exists, append a number and increase it - // until the name is unique - Q_FOREACH(const QString& key, provider->keys()) - { - if (key.length() > name.length()) - name = key.toLower(); - } - for (int suffix = 0; ; ++suffix) - { - QString located, check = name; - if (suffix) - check += QString().setNum(suffix); - if ((located = KStandardDirs::locate("services", "searchproviders/" + check + ".desktop")).isEmpty()) - { - name = check; - break; - } - else if (located.startsWith(path)) - { - // If it's a deleted (hidden) entry, overwrite it - if (KService(located).isDeleted()) - break; - } - } - } + changedProviderCount++; - KConfig _service(path + name + ".desktop", KConfig::SimpleConfig ); + KConfig _service(path + provider->desktopEntryName() + ".desktop", KConfig::SimpleConfig ); KConfigGroup service(&_service, "Desktop Entry"); - service.writeEntry("Type", "Service"); - service.writeEntry("ServiceTypes", "SearchProvider"); - service.writeEntry("Name", provider->name()); - service.writeEntry("Query", provider->query()); - service.writeEntry("Keys", provider->keys()); - service.writeEntry("Charset", provider->charset()); - - // we might be overwriting a hidden entry - service.writeEntry("Hidden", false); + service.writeEntry("Type", "Service"); + service.writeEntry("ServiceTypes", "SearchProvider"); + service.writeEntry("Name", provider->name()); + service.writeEntry("Query", provider->query()); + service.writeEntry("Keys", provider->keys()); + service.writeEntry("Charset", provider->charset()); + service.writeEntry("Hidden", false); // we might be overwriting a hidden entry } Q_FOREACH(const QString& providerName, m_deletedProviders) @@ -381,6 +349,7 @@ void FilterOptions::save() continue; changedProviderCount++; + if (matches.size() == 1 && matches.first().startsWith(path)) { // If only the local copy existed, unlink it @@ -388,6 +357,7 @@ void FilterOptions::save() QFile::remove(matches.first()); continue; } + KConfig _service(path + providerName + ".desktop", KConfig::SimpleConfig ); KConfigGroup service(&_service, "Desktop Entry"); service.writeEntry("Type", "Service"); diff --git a/kurifilter-plugins/ikws/kuriikwsfilter.cpp b/kurifilter-plugins/ikws/kuriikwsfilter.cpp index a95f1f3..0a343ac 100644 --- a/kurifilter-plugins/ikws/kuriikwsfilter.cpp +++ b/kurifilter-plugins/ikws/kuriikwsfilter.cpp @@ -46,7 +46,7 @@ KAutoWebSearch::KAutoWebSearch(QObject *parent, const QVariantList&) :KUriFilterPlugin( "kuriikwsfilter", parent ) { KGlobal::locale()->insertCatalog("kurifilter"); - QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KUriFilterPlugin", + QDBusConnection::sessionBus().connect(QString(), "/", "org.kde.KUriFilterPlugin", "configure", this, SLOT(configure())); } diff --git a/kurifilter-plugins/ikws/kurisearchfilter.cpp b/kurifilter-plugins/ikws/kurisearchfilter.cpp index 33bffcf..17b6660 100644 --- a/kurifilter-plugins/ikws/kurisearchfilter.cpp +++ b/kurifilter-plugins/ikws/kurisearchfilter.cpp @@ -39,7 +39,7 @@ KUriSearchFilter::KUriSearchFilter(QObject *parent, const QVariantList &) :KUriFilterPlugin( "kurisearchfilter", parent ) { KGlobal::locale()->insertCatalog("kurifilter"); - QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KUriFilterPlugin", + QDBusConnection::sessionBus().connect(QString(), "/", "org.kde.KUriFilterPlugin", "configure", this, SLOT(configure())); } diff --git a/kurifilter-plugins/ikws/searchprovider.cpp b/kurifilter-plugins/ikws/searchprovider.cpp index 5b66ebd..5ea3608 100644 --- a/kurifilter-plugins/ikws/searchprovider.cpp +++ b/kurifilter-plugins/ikws/searchprovider.cpp @@ -18,6 +18,8 @@ #include "searchprovider.h" +#include <krandom.h> +#include <kstandarddirs.h> #include <kservicetypetrader.h> SearchProvider::SearchProvider(const KService::Ptr service) @@ -52,7 +54,47 @@ void SearchProvider::setKeys(const QStringList &keys) if (KUriFilterSearchProvider::keys() == keys) return; - KUriFilterSearchProvider::setKeys(keys); + KUriFilterSearchProvider::setKeys(keys); + + QString name = desktopEntryName(); + if (!name.isEmpty()) + return; + + // New provider. Set the desktopEntryName. + // Take the longest search shortcut as filename, + // if such a file already exists, append a number and increase it + // until the name is unique + Q_FOREACH(const QString& key, keys) + { + if (key.length() > name.length()) + name = key.toLower(); + } + + const QString path (KGlobal::mainComponent().dirs()->saveLocation("services", "searchproviders/")); + bool firstRun = true; + + while (true) + { + QString check(name); + + if (!firstRun) + check += KRandom::randomString(4); + + const QString located = KStandardDirs::locate("services", "searchproviders/" + check + ".desktop"); + if (located.isEmpty()) + { + name = check; + break; + } + else if (located.startsWith(path)) + { + // If it's a deleted (hidden) entry, overwrite it + if (KService(located).isDeleted()) + break; + } + } + + setDesktopEntryName(name); } void SearchProvider::setCharset(const QString &charset) diff --git a/kurifilter-plugins/ikws/searchproviders/call.desktop b/kurifilter-plugins/ikws/searchproviders/call.desktop index 54edd61..4a2fc0b 100644 --- a/kurifilter-plugins/ikws/searchproviders/call.desktop +++ b/kurifilter-plugins/ikws/searchproviders/call.desktop @@ -90,94 +90,6 @@ Name[wa]=Båzes di dnêyes des indicatifs di houcaedje (avions) QRZ.com Name[x-test]=xxQRZ.com Callsign Databasexx Name[zh_CN]=QRZ.com Callsign 数据库 Name[zh_TW]=QRZ.com Callsign 資料庫 -Query=http://www.qrz.com/database?callsign=\\{@} -Query[af]=http://www.qrz.com/database?callsign=\\{@} -Query[ar]=http://www.qrz.com/database?callsign=\\{@} -Query[as]=http://www.qrz.com/database?callsign=\\{@} -Query[ast]=http://www.qrz.com/database?callsign=\\{@} -Query[be]=http://www.qrz.com/database?callsign=\\{@} -Query[be@latin]=http://www.qrz.com/database?callsign=\\{@} -Query[bg]=http://www.qrz.com/database?callsign=\\{@} -Query[bn]=http://www.qrz.com/database?callsign=\\{@} -Query[bn_IN]=http://www.qrz.com/database?callsign=\\{@} -Query[br]=http://www.qrz.com/database?callsign=\\{@} -Query[bs]=http://www.qrz.com/database?callsign=\\{@} -Query[ca]=http://www.qrz.com/database?callsign=\\{@} -Query[ca@valencia]=http://www.qrz.com/database?callsign=\\{@} -Query[cs]=http://www.qrz.com/database?callsign=\\{@} -Query[csb]=http://www.qrz.com/database?callsign=\\{@} -Query[da]=http://www.qrz.com/database?callsign=\\{@} -Query[de]=http://www.qrz.com/database?callsign=\\{@} -Query[el]=http://www.qrz.com/database?callsign=\\{@} -Query[en_GB]=http://www.qrz.com/database?callsign=\\{@} -Query[eo]=http://www.qrz.com/database?callsign=\\{@} -Query[es]=http://www.qrz.com/database?callsign=\\{@} -Query[et]=http://www.qrz.com/database?callsign=\\{@} -Query[eu]=http://www.qrz.com/database?callsign=\\{@} -Query[fi]=http://www.qrz.com/database?callsign=\\{@} -Query[fr]=http://www.qrz.com/database?callsign=\\{@} -Query[fy]=http://www.qrz.com/database?callsign=\\{@} -Query[ga]=http://www.qrz.com/database?callsign=\\{@} -Query[gl]=http://www.qrz.com/database?callsign=\\{@} -Query[gu]=http://www.qrz.com/database?callsign=\\{@} -Query[he]=http://www.qrz.com/database?callsign=\\{@} -Query[hi]=http://www.qrz.com/database?callsign=\\{@} -Query[hne]=http://www.qrz.com/database?callsign=\\{@} -Query[hr]=http://www.qrz.com/database?callsign=\\{@} -Query[hsb]=http://www.qrz.com/database?callsign=\\{@} -Query[hu]=http://www.qrz.com/database?callsign=\\{@} -Query[ia]=http://www.qrz.com/database?callsign=\\{@} -Query[id]=http://www.qrz.com/database?callsign=\\{@} -Query[is]=http://www.qrz.com/database?callsign=\\{@} -Query[it]=http://www.qrz.com/database?callsign=\\{@} -Query[ja]=http://www.qrz.com/database?callsign=\\{@} -Query[ka]=http://www.qrz.com/database?callsign=\\{@} -Query[kk]=http://www.qrz.com/database?callsign=\\{@} -Query[km]=http://www.qrz.com/database?callsign=\\{@} -Query[kn]=http://www.qrz.com/database?callsign=\\{@} -Query[ko]=http://www.qrz.com/database?callsign=\\{@} -Query[ku]=http://www.qrz.com/database?callsign=\\{@} -Query[lt]=http://www.qrz.com/database?callsign=\\{@} -Query[lv]=http://www.qrz.com/database?callsign=\\{@} -Query[mai]=http://www.qrz.com/database?callsign=\\{@} -Query[ml]=http://www.qrz.com/database?callsign=\\{@} -Query[mr]=http://www.qrz.com/database?callsign=\\{@} -Query[ms]=http://www.qrz.com/database?callsign=\\{@} -Query[nb]=http://www.qrz.com/database?callsign=\\{@} -Query[nds]=http://www.qrz.com/database?callsign=\\{@} -Query[ne]=http://www.qrz.com/database?callsign=\\{@} -Query[nl]=http://www.qrz.com/database?callsign=\\{@} -Query[nn]=http://www.qrz.com/database?callsign=\\{@} -Query[oc]=http://www.qrz.com/database?callsign=\\{@} -Query[or]=http://www.qrz.com/database?callsign=\\{@} -Query[pa]=http://www.qrz.com/database?callsign=\\{@} -Query[pl]=http://www.qrz.com/database?callsign=\\{@} -Query[pt]=http://www.qrz.com/database?callsign=\\{@} -Query[pt_BR]=http://www.qrz.com/database?callsign=\\{@} -Query[ro]=http://www.qrz.com/database?callsign=\\{@} -Query[ru]=http://www.qrz.com/database?callsign=\\{@} -Query[se]=http://www.qrz.com/database?callsign=\\{@} -Query[si]=http://www.qrz.com/database?callsign=\\{@} -Query[sk]=http://www.qrz.com/database?callsign=\\{@} -Query[sl]=http://www.qrz.com/database?callsign=\\{@} -Query[sr]=http://www.qrz.com/database?callsign=\\{@} -Query[sr@ijekavian]=http://www.qrz.com/database?callsign=\\{@} -Query[sr@ijekavianlatin]=http://www.qrz.com/database?callsign=\\{@} -Query[sr@latin]=http://www.qrz.com/database?callsign=\\{@} -Query[sv]=http://www.qrz.com/database?callsign=\\{@} -Query[ta]=http://www.qrz.com/database?callsign=\\{@} -Query[te]=http://www.qrz.com/database?callsign=\\{@} -Query[tg]=http://www.qrz.com/database?callsign=\\{@} -Query[th]=http://www.qrz.com/database?callsign=\\{@} -Query[tr]=http://www.qrz.com/database?callsign=\\{@} -Query[ug]=http://www.qrz.com/database?callsign=\\{@} -Query[uk]=http://www.qrz.com/database?callsign=\\{@} -Query[uz]=http://www.qrz.com/database?callsign=\\{@} -Query[uz@cyrillic]=http://www.qrz.com/database?callsign=\\{@} -Query[vi]=http://www.qrz.com/database?callsign=\\{@} -Query[wa]=http://www.qrz.com/database?callsign=\\{@} -Query[x-test]=xxhttp://www.qrz.com/database?callsign=\\{@}xx -Query[zh_CN]=http://www.qrz.com/database?callsign=\\{@} -Query[zh_TW]=http://www.qrz.com/database?callsign=\\{@} +Query=http://www.qrz.com/db/\\{@} X-KDE-ServiceTypes=SearchProvider Type=Service diff --git a/kurifilter-plugins/shorturi/kshorturifilter.cpp b/kurifilter-plugins/shorturi/kshorturifilter.cpp index a7e23ec..802b325 100644 --- a/kurifilter-plugins/shorturi/kshorturifilter.cpp +++ b/kurifilter-plugins/shorturi/kshorturifilter.cpp @@ -96,7 +96,7 @@ static QString removeArgs( const QString& _cmd ) KShortUriFilter::KShortUriFilter( QObject *parent, const QVariantList & /*args*/ ) :KUriFilterPlugin( "kshorturifilter", parent ) { - QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KUriFilterPlugin", + QDBusConnection::sessionBus().connect(QString(), "/", "org.kde.KUriFilterPlugin", "configure", this, SLOT(configure())); configure(); } diff --git a/nepomuk/common/fileexcludefilters.cpp b/nepomuk/common/fileexcludefilters.cpp index 2172b52..079332a 100644 --- a/nepomuk/common/fileexcludefilters.cpp +++ b/nepomuk/common/fileexcludefilters.cpp @@ -59,11 +59,14 @@ namespace { "litmain.sh", "*.orig", ".histfile.*", + ".xsession-errors*", // end of list 0 }; + const int s_defaultFileExcludeFiltersVersion = 2; + const char* s_defaultFolderExcludeFilters[] = { "po", @@ -85,6 +88,8 @@ namespace { // end of list 0 }; + + const int s_defaultFolderExcludeFiltersVersion = 1; } @@ -97,3 +102,8 @@ QStringList Nepomuk::defaultExcludeFilterList() l << QLatin1String( s_defaultFolderExcludeFilters[i] ); return l; } + +int Nepomuk::defaultExcludeFilterListVersion() +{ + return qMax(s_defaultFileExcludeFiltersVersion, s_defaultFolderExcludeFiltersVersion); +} diff --git a/nepomuk/common/fileexcludefilters.h b/nepomuk/common/fileexcludefilters.h index 91eb088..311c0fe 100644 --- a/nepomuk/common/fileexcludefilters.h +++ b/nepomuk/common/fileexcludefilters.h @@ -31,6 +31,12 @@ namespace Nepomuk { * user configurable exclude filters in the strigi service. */ NEPOMUKCOMMON_EXPORT QStringList defaultExcludeFilterList(); + + /** + * \return The version of the default exclude filter list. + * This is increased whenever the list changes. + */ + NEPOMUKCOMMON_EXPORT int defaultExcludeFilterListVersion(); } #endif diff --git a/nepomuk/common/removablemediacache.cpp b/nepomuk/common/removablemediacache.cpp index 54afdc2..dbb31c8 100644 --- a/nepomuk/common/removablemediacache.cpp +++ b/nepomuk/common/removablemediacache.cpp @@ -254,8 +254,8 @@ QString Nepomuk::RemovableMediaCache::Entry::constructLocalPath( const KUrl& fil QString path( sa->filePath() ); if ( path.endsWith( QLatin1String( "/" ) ) ) path.truncate( path.length()-1 ); - - return path + filexUrl.url().mid(m_urlPrefix.count()); + // We use QUrl::toString instead of KUrl::url to resolve any encoded chars + return path + QUrl(filexUrl).toString().mid(m_urlPrefix.count()); } } diff --git a/nepomuk/services/filewatch/CMakeLists.txt b/nepomuk/services/filewatch/CMakeLists.txt index d480b3c..3cda68b 100644 --- a/nepomuk/services/filewatch/CMakeLists.txt +++ b/nepomuk/services/filewatch/CMakeLists.txt @@ -20,6 +20,7 @@ set(SRCS invalidfileresourcecleaner.cpp ../strigi/strigiserviceconfig.cpp removabledeviceindexnotification.cpp + activefilequeue.cpp ) qt4_add_dbus_interface(SRCS ../../interfaces/org.kde.nepomuk.Strigi.xml strigiserviceinterface) diff --git a/nepomuk/services/filewatch/activefilequeue.cpp b/nepomuk/services/filewatch/activefilequeue.cpp new file mode 100644 index 0000000..8bb54ed --- /dev/null +++ b/nepomuk/services/filewatch/activefilequeue.cpp @@ -0,0 +1,130 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "activefilequeue.h" + +#include <QtCore/QQueue> +#include <QtCore/QTimer> + + +namespace { + class Entry { + public: + Entry(const KUrl& url, int c); + bool operator==(const Entry& other) const; + + /// The file url + KUrl url; + /// The seconds left in this entry + int cnt; + }; + + Entry::Entry(const KUrl &u, int c) + : url(u), + cnt(c) + { + } + + bool Entry::operator==(const Entry &other) const + { + // we ignore the counter since we need this for the search in queueUrl only + return url == other.url; + } +} + +Q_DECLARE_TYPEINFO(Entry, Q_MOVABLE_TYPE); + + +class ActiveFileQueue::Private +{ +public: + QQueue<Entry> m_queue; + QTimer m_queueTimer; + int m_timeout; +}; + + +ActiveFileQueue::ActiveFileQueue(QObject *parent) + : QObject(parent), + d(new Private()) +{ + // we default to 5 seconds + d->m_timeout = 5; + + // setup the timer + connect(&d->m_queueTimer, SIGNAL(timeout()), + this, SLOT(slotTimer())); + + // we check in 1 sec intervals + d->m_queueTimer.setInterval(1000); +} + +ActiveFileQueue::~ActiveFileQueue() +{ + delete d; +} + +void ActiveFileQueue::enqueueUrl(const KUrl &url) +{ + Entry defaultEntry(url, d->m_timeout); + QQueue<Entry>::iterator it = qFind(d->m_queue.begin(), d->m_queue.end(), defaultEntry); + if(it == d->m_queue.end()) { + // if this is a new url add it with the default timeout + d->m_queue.enqueue(defaultEntry); + } + else { + // If the url is already in the queue update its timestamp + it->cnt = d->m_timeout; + } + + // make sure the timer is running + if(!d->m_queueTimer.isActive()) { + d->m_queueTimer.start(); + } +} + +void ActiveFileQueue::setTimeout(int seconds) +{ + d->m_timeout = seconds; +} + +void ActiveFileQueue::slotTimer() +{ + // we run through the queue, decrease each counter and emit each entry which has a count of 0 + QQueue<Entry>::iterator it = d->m_queue.begin(); + while(it != d->m_queue.end()) { + it->cnt--; + if(it->cnt == 0) { + emit urlTimeout(it->url); + it = d->m_queue.erase(it); + } + else { + ++it; + } + } + + // stop the timer in case we have nothing left to do + if(d->m_queue.isEmpty()) { + d->m_queueTimer.stop(); + } +} + +#include "activefilequeue.moc" diff --git a/nepomuk/services/filewatch/activefilequeue.h b/nepomuk/services/filewatch/activefilequeue.h new file mode 100644 index 0000000..19d4813 --- /dev/null +++ b/nepomuk/services/filewatch/activefilequeue.h @@ -0,0 +1,71 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef ACTIVEFILEQUEUE_H +#define ACTIVEFILEQUEUE_H + +#include <QtCore/QObject> + +#include <KUrl> + +/** + * The active file queue maintains a queue of file paths + * with a timestamp. It will signal the timout of a file after + * a given time. As soon as a file is queued again its timestamp + * will be reset and the timing restarts. + * + * This allows to "compress" file modification events of downloads + * and the like into a single event resulting in a smoother + * experience for the user. + * + * \author Sebastian Trueg <trueg@ĸde.org> + */ +class ActiveFileQueue : public QObject +{ + Q_OBJECT + +public: + ActiveFileQueue(QObject *parent = 0); + ~ActiveFileQueue(); + +signals: + void urlTimeout(const KUrl& url); + +public slots: + void enqueueUrl(const KUrl& url); + + /** + * Set the timeout in seconds. Be aware that the timeout + * will not be exact. For internal reasons the queue tries + * to roughly match the configured timeout. It only guarantees + * that the timeout will be between \p seconds and \p seconds+1. + */ + void setTimeout(int seconds); + +private slots: + void slotTimer(); + +private: + class Private; + Private* const d; +}; + +#endif // ACTIVEFILEQUEUE_H diff --git a/nepomuk/services/filewatch/metadatamover.cpp b/nepomuk/services/filewatch/metadatamover.cpp index 8db2ecf..eb4f357 100644 --- a/nepomuk/services/filewatch/metadatamover.cpp +++ b/nepomuk/services/filewatch/metadatamover.cpp @@ -213,7 +213,7 @@ void Nepomuk::MetadataMover::updateMetadata( const KUrl& from, const KUrl& to ) // If we have no metadata yet we need to tell strigi (if running) so it can // create the metadata in case the target folder is configured to be indexed. // - emit movedWithoutData( to.directory( KUrl::IgnoreTrailingSlash ) ); + emit movedWithoutData( to.path() ); } } diff --git a/nepomuk/services/filewatch/nepomukfilewatch.cpp b/nepomuk/services/filewatch/nepomukfilewatch.cpp index fad7d0d..33e1e92 100644 --- a/nepomuk/services/filewatch/nepomukfilewatch.cpp +++ b/nepomuk/services/filewatch/nepomukfilewatch.cpp @@ -24,6 +24,7 @@ #include "removabledeviceindexnotification.h" #include "removablemediacache.h" #include "../strigi/strigiserviceconfig.h" +#include "activefilequeue.h" #ifdef BUILD_KINOTIFY #include "kinotify.h" @@ -98,10 +99,12 @@ namespace { if( Nepomuk::StrigiServiceConfig::self()->shouldFolderBeIndexed( path ) ) { modes |= KInotify::EventCreate; modes |= KInotify::EventModify; + modes |= KInotify::EventCloseWrite; } else { modes &= (~KInotify::EventCreate); modes &= (~KInotify::EventModify); + modes &= (~KInotify::EventCloseWrite); } return true; @@ -113,6 +116,10 @@ namespace { Nepomuk::FileWatch::FileWatch( QObject* parent, const QList<QVariant>& ) : Service( parent ) { + // Create the configuration instance singleton (for thread-safety) + // ============================================================== + (void)new StrigiServiceConfig(this); + // the list of default exclude filters we use here differs from those // that can be configured for the strigi service // the default list should only contain files and folders that users are @@ -129,6 +136,10 @@ Nepomuk::FileWatch::FileWatch( QObject* parent, const QList<QVariant>& ) Qt::QueuedConnection ); m_metadataMover->start(); + m_fileModificationQueue = new ActiveFileQueue(this); + connect(m_fileModificationQueue, SIGNAL(urlTimeout(KUrl)), + this, SLOT(slotActiveFileQueueTimeout(KUrl))); + #ifdef BUILD_KINOTIFY // monitor the file system for changes (restricted by the inotify limit) m_dirWatch = new IgnoringKInotify( m_pathExcludeRegExpCache, this ); @@ -141,6 +152,8 @@ Nepomuk::FileWatch::FileWatch( QObject* parent, const QList<QVariant>& ) this, SLOT( slotFileCreated( QString ) ) ); connect( m_dirWatch, SIGNAL( modified( QString ) ), this, SLOT( slotFileModified( QString ) ) ); + connect( m_dirWatch, SIGNAL( closedWrite( QString ) ), + this, SLOT( slotFileClosedAfterWrite( QString ) ) ); connect( m_dirWatch, SIGNAL( watchUserLimitReached() ), this, SLOT( slotInotifyWatchUserLimitReached() ) ); @@ -185,7 +198,7 @@ void Nepomuk::FileWatch::watchFolder( const QString& path ) #ifdef BUILD_KINOTIFY if ( m_dirWatch && !m_dirWatch->watchingPath( path ) ) m_dirWatch->addWatch( path, - KInotify::WatchEvents( KInotify::EventMove|KInotify::EventDelete|KInotify::EventDeleteSelf|KInotify::EventCreate|KInotify::EventModify ), + KInotify::WatchEvents( KInotify::EventMove|KInotify::EventDelete|KInotify::EventDeleteSelf|KInotify::EventCreate|KInotify::EventModify|KInotify::EventCloseWrite ), KInotify::WatchFlags() ); #endif } @@ -193,7 +206,7 @@ void Nepomuk::FileWatch::watchFolder( const QString& path ) void Nepomuk::FileWatch::slotFileMoved( const QString& urlFrom, const QString& urlTo ) { - if( !ignorePath( urlFrom ) ) { + if( !ignorePath( urlFrom ) || !ignorePath( urlTo ) ) { KUrl from( urlFrom ); KUrl to( urlTo ); @@ -233,20 +246,36 @@ void Nepomuk::FileWatch::slotFileDeleted( const QString& urlString, bool isDir ) void Nepomuk::FileWatch::slotFileCreated( const QString& path ) { - kDebug() << path; - updateFileViaStrigi( path ); + if( StrigiServiceConfig::self()->shouldBeIndexed(path) ) { + // we only cache the file and wait until it has been closed, ie. the writing has been finished + m_modifiedFilesCache.insert(path); + } } void Nepomuk::FileWatch::slotFileModified( const QString& path ) { - updateFileViaStrigi( path ); + if( StrigiServiceConfig::self()->shouldBeIndexed(path) ) { + // we only cache the file and wait until it has been closed, ie. the writing has been finished + m_modifiedFilesCache.insert(path); + } } +void Nepomuk::FileWatch::slotFileClosedAfterWrite( const QString& path ) +{ + // we only need to update the file if it has actually been modified + QSet<KUrl>::iterator it = m_modifiedFilesCache.find(path); + if(it != m_modifiedFilesCache.end()) { + // we do not tell the file indexer right away but wait a short while in case the file is modified very often (irc logs for example) + m_fileModificationQueue->enqueueUrl( path ); + m_modifiedFilesCache.erase(it); + } +} + void Nepomuk::FileWatch::slotMovedWithoutData( const QString& path ) { - updateFolderViaStrigi( path ); + updateFileViaStrigi( path ); } @@ -378,4 +407,9 @@ void Nepomuk::FileWatch::slotDeviceMounted(const Nepomuk::RemovableMediaCache::E watchFolder(entry->mountPath()); } +void Nepomuk::FileWatch::slotActiveFileQueueTimeout(const KUrl &url) +{ + updateFileViaStrigi(url.toLocalFile()); +} + #include "nepomukfilewatch.moc" diff --git a/nepomuk/services/filewatch/nepomukfilewatch.h b/nepomuk/services/filewatch/nepomukfilewatch.h index 3e61b91..a85c349 100644 --- a/nepomuk/services/filewatch/nepomukfilewatch.h +++ b/nepomuk/services/filewatch/nepomukfilewatch.h @@ -37,6 +37,7 @@ namespace Soprano { class KInotify; class KUrl; class RegExpCache; +class ActiveFileQueue; namespace Nepomuk { @@ -72,6 +73,7 @@ namespace Nepomuk { void slotFilesDeleted( const QStringList& path ); void slotFileCreated( const QString& ); void slotFileModified( const QString& ); + void slotFileClosedAfterWrite( const QString& ); void slotMovedWithoutData( const QString& ); void connectToKDirWatch(); #ifdef BUILD_KINOTIFY @@ -91,6 +93,8 @@ namespace Nepomuk { */ void slotDeviceMounted( const Nepomuk::RemovableMediaCache::Entry* ); + void slotActiveFileQueueTimeout(const KUrl& url); + private: /** * Adds watches for all mounted removable media. @@ -112,6 +116,12 @@ namespace Nepomuk { RegExpCache* m_pathExcludeRegExpCache; RemovableMediaCache* m_removableMediaCache; + + /// stores all the file URLs that have been modified but not closed yet + QSet<KUrl> m_modifiedFilesCache; + + /// queue used to "compress" constant file modifications like downloads + ActiveFileQueue* m_fileModificationQueue; }; } diff --git a/nepomuk/services/filewatch/test/CMakeLists.txt b/nepomuk/services/filewatch/test/CMakeLists.txt index ab30228..9d518a3 100644 --- a/nepomuk/services/filewatch/test/CMakeLists.txt +++ b/nepomuk/services/filewatch/test/CMakeLists.txt @@ -10,3 +10,10 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux") ${KDE4_KDECORE_LIBS} ) endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +set(activefilequeuetest_SRC activefilequeuetest.cpp ../activefilequeue.cpp) +kde4_add_unit_test(activefilequeuetest TESTNAME nepomuk-activefilequeuetest NOGUI ${activefilequeuetest_SRC}) +target_link_libraries(activefilequeuetest + ${QT_QTTEST_LIBRARY} + ${KDE4_KDECORE_LIBS} +) diff --git a/nepomuk/services/filewatch/test/activefilequeuetest.cpp b/nepomuk/services/filewatch/test/activefilequeuetest.cpp new file mode 100644 index 0000000..0445e8f --- /dev/null +++ b/nepomuk/services/filewatch/test/activefilequeuetest.cpp @@ -0,0 +1,100 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "activefilequeuetest.h" +#include "../activefilequeue.h" + +#include <QtTest> +#include <qtest_kde.h> + + +namespace { + void loopWait(int msecs) { + QEventLoop loop; + QTimer::singleShot(msecs, &loop, SLOT(quit())); + loop.exec(); + } +} + +ActiveFileQueueTest::ActiveFileQueueTest() +{ + qRegisterMetaType<KUrl>(); +} + +void ActiveFileQueueTest::testTimeout() +{ + KUrl myUrl("/tmp"); + + // enqueue one url and then make sure it is not emitted before the timeout + ActiveFileQueue queue; + queue.setTimeout(3); + queue.enqueueUrl(myUrl); + + QSignalSpy spy( &queue, SIGNAL(urlTimeout(KUrl)) ); + + // wait for 1 seconds + loopWait(1000); + + // the signal should not have been emitted yet + QVERIFY(spy.isEmpty()); + + // wait another 2 seconds + loopWait(2000); + + // now the signal should have been emitted + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().first().value<KUrl>(), myUrl); +} + +void ActiveFileQueueTest::testRequeue() +{ + KUrl myUrl("/tmp"); + + // enqueue one url and then make sure it is not emitted before the timeout + ActiveFileQueue queue; + queue.setTimeout(3); + queue.enqueueUrl(myUrl); + + QSignalSpy spy( &queue, SIGNAL(urlTimeout(KUrl)) ); + + // wait for 2 seconds + loopWait(1000); + + // re-queue the url + queue.enqueueUrl(myUrl); + + // wait another 2 seconds + loopWait(2000); + + // the signal should not have been emitted yet + QVERIFY(spy.isEmpty()); + + // wait another 2 seconds + loopWait(2000); + + // now the signal should have been emitted + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.takeFirst().first().value<KUrl>(), myUrl); +} + +QTEST_MAIN(ActiveFileQueueTest) + +#include "activefilequeuetest.moc" diff --git a/nepomuk/services/filewatch/test/activefilequeuetest.h b/nepomuk/services/filewatch/test/activefilequeuetest.h new file mode 100644 index 0000000..34610be --- /dev/null +++ b/nepomuk/services/filewatch/test/activefilequeuetest.h @@ -0,0 +1,39 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef ACTIVEFILEQUEUETEST_H +#define ACTIVEFILEQUEUETEST_H + +#include <QObject> + +class ActiveFileQueueTest : public QObject +{ + Q_OBJECT + +public: + ActiveFileQueueTest(); + +private slots: + void testTimeout(); + void testRequeue(); +}; + +#endif // ACTIVEFILEQUEUETEST_H diff --git a/nepomuk/services/storage/datamanagementmodel.cpp b/nepomuk/services/storage/datamanagementmodel.cpp index af653f9..fe68e29 100644 --- a/nepomuk/services/storage/datamanagementmodel.cpp +++ b/nepomuk/services/storage/datamanagementmodel.cpp @@ -1063,17 +1063,18 @@ void Nepomuk::DataManagementModel::removeDataByApplication(const QList<QUrl> &re // // Gather the resources that we actually change, ie. those which have non-metadata props in the removed graphs // - Soprano::QueryResultIterator mResIt - = executeQuery(QString::fromLatin1("select ?r where { graph ?g { ?r ?p ?o . FILTER(?r in (%1)) . FILTER(%2) . } . FILTER(?g in (%3)) . }") - .arg(resourcesToN3(resolvedResources).join(QLatin1String(",")), - createResourceMetadataPropertyFilter(QLatin1String("?p"), true), - resourcesToN3(graphsToRemove).join(QLatin1String(","))), - Soprano::Query::QueryLanguageSparql); - while(mResIt.next()) { - modifiedResources.insert(mResIt[0].uri()); + if( !graphsToRemove.isEmpty() ) { + Soprano::QueryResultIterator mResIt + = executeQuery(QString::fromLatin1("select ?r where { graph ?g { ?r ?p ?o . FILTER(?r in (%1)) . FILTER(%2) . } . FILTER(?g in (%3)) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(",")), + createResourceMetadataPropertyFilter(QLatin1String("?p"), true), + resourcesToN3(graphsToRemove).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(mResIt.next()) { + modifiedResources.insert(mResIt[0].uri()); + } } - // // Remove the actual data. This has to be done using removeAllStatements. Otherwise the crappy inferencer cannot follow the changes. // diff --git a/nepomuk/services/storage/datamanagementmodel.h b/nepomuk/services/storage/datamanagementmodel.h index 9722157..67fcbeb 100644 --- a/nepomuk/services/storage/datamanagementmodel.h +++ b/nepomuk/services/storage/datamanagementmodel.h @@ -156,7 +156,7 @@ public Q_SLOTS: const QString& app, Nepomuk::StoreIdentificationMode identificationMode = Nepomuk::IdentifyNew, Nepomuk::StoreResourcesFlags flags = Nepomuk::NoStoreResourcesFlags, - const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>() ); + const QHash<QUrl, QVariant>& additionalMetadata = (QHash<QUrl, QVariant>())); /** * Merges two resources into one. Properties from \p resource1 @@ -188,7 +188,7 @@ public Q_SLOTS: const QString& userSerialization = QString(), Nepomuk::StoreIdentificationMode identificationMode = Nepomuk::IdentifyNew, Nepomuk::StoreResourcesFlags flags = Nepomuk::NoStoreResourcesFlags, - const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>()); + const QHash<QUrl, QVariant>& additionalMetadata = (QHash<QUrl, QVariant>())); /** * Describe a set of resources, i.e. retrieve all their properties. @@ -200,7 +200,7 @@ public Q_SLOTS: //@} private: - QUrl createGraph(const QString& app = QString(), const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>()); + QUrl createGraph(const QString& app = QString(), const QHash<QUrl, QVariant>& additionalMetadata = (QHash<QUrl, QVariant>())); QUrl createGraph(const QString& app, const QMultiHash<QUrl, Soprano::Node>& additionalMetadata); /** diff --git a/nepomuk/services/storage/repository.cpp b/nepomuk/services/storage/repository.cpp index 128c3d6..b9b2051 100644 --- a/nepomuk/services/storage/repository.cpp +++ b/nepomuk/services/storage/repository.cpp @@ -62,6 +62,9 @@ Nepomuk::Repository::Repository( const QString& name ) m_model( 0 ), m_inferencer( 0 ), m_removableStorageModel( 0 ), + m_dataManagementModel( 0 ), + m_dataManagementAdaptor( 0 ), + m_nrlModel( 0 ), m_backend( 0 ), m_modelCopyJob( 0 ), m_oldStorageBackend( 0 ) diff --git a/nepomuk/services/storage/test/removablemediamodeltest.cpp b/nepomuk/services/storage/test/removablemediamodeltest.cpp index 8bf3ab1..3a4abee 100644 --- a/nepomuk/services/storage/test/removablemediamodeltest.cpp +++ b/nepomuk/services/storage/test/removablemediamodeltest.cpp @@ -122,6 +122,14 @@ void RemovableMediaModelTest::testConvertFileUrlsInStatement_data() const Statement convertableFileObjectWithNieUrl4_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs")); const Statement convertableFileObjectWithNieUrl4_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path")); QTest::newRow("convertableFileUrlInObjectWithNieUrl4") << convertableFileObjectWithNieUrl4_original << convertableFileObjectWithNieUrl4_converted; + + const Statement convertableFileObjectWithNieUrl5_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4/file with spaces.txt")); + const Statement convertableFileObjectWithNieUrl5_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123/file with spaces.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl5") << convertableFileObjectWithNieUrl5_original << convertableFileObjectWithNieUrl5_converted; + + const Statement convertableFileObjectWithNieUrl6_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/whatever with spaces/file with spaces.txt")); + const Statement convertableFileObjectWithNieUrl6_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://whatever/file with spaces.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl6") << convertableFileObjectWithNieUrl6_original << convertableFileObjectWithNieUrl6_converted; } @@ -180,6 +188,12 @@ void RemovableMediaModelTest::testConvertFilxUrl_data() const Node convertFilex2(QUrl("filex://xyz-123")); QTest::newRow("convertFilex2") << convertFilex2 << Node(QUrl("file:///media/XO-Y4")); + const Node convertFilex3(QUrl("filex://xyz-123/hello world")); + QTest::newRow("convertFilex3") << convertFilex3 << Node(QUrl("file:///media/XO-Y4/hello world")); + + const Node convertFilex4(QUrl("filex://whatever/hello world")); + QTest::newRow("convertFilex4") << convertFilex4 << Node(QUrl("file:///media/whatever with spaces/hello world")); + const Node convertnfs(QUrl("nfs://thehost/solid-path")); QTest::newRow("convertnfs") << convertnfs << Node(QUrl("file:///media/nfs")); } @@ -224,6 +238,14 @@ void RemovableMediaModelTest::testConvertFilxUrls_data() const Statement convertableFilexObjectWithNieUrl4_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path")); const Statement convertableFilexObjectWithNieUrl4_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs")); QTest::newRow("convertableFileUrlInObjectWithNieUrl4") << convertableFilexObjectWithNieUrl4_original << convertableFilexObjectWithNieUrl4_converted; + + const Statement convertableFilexObjectWithNieUrl5_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123/file with spaces.txt")); + const Statement convertableFilexObjectWithNieUrl5_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4/file with spaces.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl5") << convertableFilexObjectWithNieUrl5_original << convertableFilexObjectWithNieUrl5_converted; + + const Statement convertableFileObjectWithNieUrl6_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://whatever/file with spaces.txt")); + const Statement convertableFileObjectWithNieUrl6_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/whatever with spaces/file with spaces.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl6") << convertableFileObjectWithNieUrl6_original << convertableFileObjectWithNieUrl6_converted; } void RemovableMediaModelTest::testConvertFilxUrls() diff --git a/nepomuk/services/storage/test/solid/fakecomputer.xml b/nepomuk/services/storage/test/solid/fakecomputer.xml index e3fcfb8..67b374f 100644 --- a/nepomuk/services/storage/test/solid/fakecomputer.xml +++ b/nepomuk/services/storage/test/solid/fakecomputer.xml @@ -437,6 +437,69 @@ <property key="size">993284096</property> </device> + <!-- USB Device --> + <device udi="/org/kde/solid/fakehw/usb_device_whatever"> + <property key="name">Acme whatever</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_whatever</property> + </device> + <!-- Mass Storage Interface --> + <device udi="/org/kde/solid/fakehw/usb_device_whatever_if0"> + <property key="name">USB Mass Storage Inferface</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_whatever</property> + </device> + <!-- SCSI Adapter --> + <device udi="/org/kde/solid/fakehw/usb_device_whatever_if0_scsi_host"> + <property key="name">SCSI Host Adapter</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_whatever_if0</property> + </device> + <!-- SCSI Device --> + <device udi="/org/kde/solid/fakehw/usb_device_whatever_if0_scsi_host_scsi_device_lun0"> + <property key="name">SCSI Device</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_whatever_if0_scsi_host</property> + </device> + <!-- We finally find the storage device, which is a portable media player... --> + <device udi="/org/kde/solid/fakehw/storage_serial_whatever"> + <property key="name">whatever</property> + <property key="vendor">Acme Electronics</property> + <property key="interfaces">StorageDrive,Block,PortableMediaPlayer</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_whatever_if0_scsi_host_scsi_device_lun0</property> + + <property key="minor">0</property> + <property key="major">8</property> + <property key="device">/dev/sdb</property> + + <property key="bus">usb</property> + <property key="driveType">disk</property> + <property key="isRemovable">true</property> + <property key="isEjectRequired">true</property> + <property key="isHotpluggable">true</property> + <property key="isMediaCheckEnabled">true</property> + <property key="product">whatever</property> + + <property key="accessMethod">MassStorage</property> + <property key="outputFormats">audio/x-mp3</property> + <property key="inputFormats">audio/x-wav,audio/x-mp3,audio/vorbis</property> + <property key="playlistFormats">audio/x-mpegurl</property> + </device> + <!-- ... with a partition since it's a USB Mass Storage device --> + <device udi="/org/kde/solid/fakehw/volume_part1_whatever"> + <property key="name">StorageVolume (vfat)</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_whatever</property> + <property key="uuid">whatever</property> + + <property key="minor">1</property> + <property key="major">8</property> + <property key="device">/dev/sdb1</property> + + <property key="isIgnored">false</property> + <property key="isMounted">true</property> + <property key="mountPoint">/media/whatever with spaces</property> + <property key="usage">filesystem</property> + <property key="fsType">vfat</property> + <property key="size">993284096</property> + </device> + <!-- Second USB Controller --> diff --git a/nepomuk/services/strigi/eventmonitor.cpp b/nepomuk/services/strigi/eventmonitor.cpp index 621cb25..7c72c96 100644 --- a/nepomuk/services/strigi/eventmonitor.cpp +++ b/nepomuk/services/strigi/eventmonitor.cpp @@ -98,6 +98,7 @@ void Nepomuk::EventMonitor::slotPowerManagementStatusChanged( bool conserveResou sendEvent( "indexingResumed", i18n("Resuming indexing of files for fast searching."), "battery-charging" ); } else if ( conserveResources && + !StrigiServiceConfig::self()->suspendOnPowerSaveDisabled() && !m_indexScheduler->isSuspended() ) { kDebug() << "Pausing indexer due to power management"; m_wasIndexingWhenPaused = m_indexScheduler->isIndexing(); diff --git a/nepomuk/services/strigi/indexscheduler.cpp b/nepomuk/services/strigi/indexscheduler.cpp index 323169e..ba4ab55 100644 --- a/nepomuk/services/strigi/indexscheduler.cpp +++ b/nepomuk/services/strigi/indexscheduler.cpp @@ -173,10 +173,6 @@ Nepomuk::IndexScheduler::IndexScheduler( QObject* parent ) connect( StrigiServiceConfig::self(), SIGNAL( configChanged() ), this, SLOT( slotConfigChanged() ) ); - - // start the initial indexing - queueAllFoldersForUpdate(); - callDoIndexing(); } @@ -342,6 +338,8 @@ void Nepomuk::IndexScheduler::doIndexing() m_currentMutex.unlock(); setIndexingStarted( false ); + + emit indexingDone(); } } diff --git a/nepomuk/services/strigi/indexscheduler.h b/nepomuk/services/strigi/indexscheduler.h index 53db1ab..2b328e6 100644 --- a/nepomuk/services/strigi/indexscheduler.h +++ b/nepomuk/services/strigi/indexscheduler.h @@ -171,6 +171,9 @@ namespace Nepomuk { void indexingFile( const QString & ); void indexingSuspended( bool suspended ); + /// emitted once the indexing is done and the queue is empty + void indexingDone(); + private Q_SLOTS: void slotConfigChanged(); void slotCleaningDone(); diff --git a/nepomuk/services/strigi/nepomukindexer.cpp b/nepomuk/services/strigi/nepomukindexer.cpp index d796983..ff981e6 100644 --- a/nepomuk/services/strigi/nepomukindexer.cpp +++ b/nepomuk/services/strigi/nepomukindexer.cpp @@ -30,41 +30,56 @@ #include <KStandardDirs> #include <QtCore/QFileInfo> +#include <QtCore/QTimer> -Nepomuk::Indexer::Indexer(const KUrl& localUrl, QObject* parent) - : KJob(parent), - m_url( localUrl ), - m_exitCode( -1 ) -{ -} - Nepomuk::Indexer::Indexer(const QFileInfo& info, QObject* parent) : KJob(parent), m_url( info.absoluteFilePath() ), m_exitCode( -1 ) { + // setup the timer used to kill the indexer process if it seems to get stuck + m_processTimer = new QTimer(this); + m_processTimer->setSingleShot(true); + connect(m_processTimer, SIGNAL(timeout()), + this, SLOT(slotProcessTimerTimeout())); } void Nepomuk::Indexer::start() { + // setup the external process which does the actual indexing const QString exe = KStandardDirs::findExe(QLatin1String("nepomukindexer")); m_process = new KProcess( this ); m_process->setProgram( exe, QStringList() << m_url.toLocalFile() ); + // start the process kDebug() << "Running" << exe << m_url.toLocalFile(); - connect( m_process, SIGNAL(finished(int)), this, SLOT(slotIndexedFile(int)) ); m_process->start(); + + // start the timer which will kill the process if it does not terminate after 5 minutes + m_processTimer->start(5*60*1000); } void Nepomuk::Indexer::slotIndexedFile(int exitCode) { + // stop the timer since there is no need to kill the process anymore + m_processTimer->stop(); + kDebug() << "Indexing of " << m_url.toLocalFile() << "finished with exit code" << exitCode; m_exitCode = exitCode; emitResult(); } +void Nepomuk::Indexer::slotProcessTimerTimeout() +{ + kDebug() << "Killing the indexer process which seems stuck for" << m_url; + m_process->disconnect(this); + m_process->kill(); + m_process->waitForFinished(); + emitResult(); +} + #include "nepomukindexer.moc" diff --git a/nepomuk/services/strigi/nepomukindexer.h b/nepomuk/services/strigi/nepomukindexer.h index 5b14787..3188eef 100644 --- a/nepomuk/services/strigi/nepomukindexer.h +++ b/nepomuk/services/strigi/nepomukindexer.h @@ -27,6 +27,7 @@ class KProcess; class QFileInfo; +class QTimer; namespace Nepomuk { @@ -53,7 +54,6 @@ namespace Nepomuk { public: Indexer( const QFileInfo& info, QObject* parent = 0 ); - Indexer( const KUrl& localUrl, QObject* parent = 0 ); KUrl url() const { return m_url; } @@ -74,11 +74,13 @@ namespace Nepomuk { private slots: void slotIndexedFile(int exitCode); + void slotProcessTimerTimeout(); private: KUrl m_url; KProcess* m_process; int m_exitCode; + QTimer* m_processTimer; }; } diff --git a/nepomuk/services/strigi/strigiservice.cpp b/nepomuk/services/strigi/strigiservice.cpp index 0189679..0880381 100644 --- a/nepomuk/services/strigi/strigiservice.cpp +++ b/nepomuk/services/strigi/strigiservice.cpp @@ -38,6 +38,10 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) : Service( parent ) { + // Create the configuration instance singleton (for thread-safety) + // ============================================================== + (void)new StrigiServiceConfig(this); + // setup the actual index scheduler including strigi stuff // ============================================================== m_schedulingThread = new QThread( this ); @@ -61,6 +65,8 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) this, SIGNAL( statusStringChanged() ) ); connect( m_indexScheduler, SIGNAL( indexingStopped() ), this, SIGNAL( statusStringChanged() ) ); + connect( m_indexScheduler, SIGNAL( indexingDone() ), + this, SLOT( slotIndexingDone() ) ); connect( m_indexScheduler, SIGNAL( indexingFolder(QString) ), this, SIGNAL( statusStringChanged() ) ); connect( m_indexScheduler, SIGNAL( indexingFile(QString) ), @@ -72,6 +78,12 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) // this is done for KDE startup - to not slow that down too much m_indexScheduler->setIndexingSpeed( IndexScheduler::SnailPace ); + // start initial indexing honoring the hidden config option to disable it + if(StrigiServiceConfig::self()->isInitialRun() || + !StrigiServiceConfig::self()->initialUpdateDisabled()) { + m_indexScheduler->updateAll(); + } + // delayed init for the rest which uses IO and CPU // FIXME: do not use a random delay value but wait for KDE to be started completely (using the session manager) QTimer::singleShot( 2*60*1000, this, SLOT( finishInitialization() ) ); @@ -126,6 +138,12 @@ void Nepomuk::StrigiService::slotIdleTimerResume() } +void Nepomuk::StrigiService::slotIndexingDone() +{ + StrigiServiceConfig::self()->setInitialRun(true); +} + + void Nepomuk::StrigiService::updateWatches() { org::kde::nepomuk::FileWatch filewatch( "org.kde.nepomuk.services.nepomukfilewatch", diff --git a/nepomuk/services/strigi/strigiservice.h b/nepomuk/services/strigi/strigiservice.h index 2eb7eac..acca179 100644 --- a/nepomuk/services/strigi/strigiservice.h +++ b/nepomuk/services/strigi/strigiservice.h @@ -97,6 +97,7 @@ namespace Nepomuk { void updateWatches(); void slotIdleTimeoutReached(); void slotIdleTimerResume(); + void slotIndexingDone(); private: void updateStrigiConfig(); diff --git a/nepomuk/services/strigi/strigiserviceconfig.cpp b/nepomuk/services/strigi/strigiserviceconfig.cpp index 4a71cf9..0fc5fa2 100644 --- a/nepomuk/services/strigi/strigiserviceconfig.cpp +++ b/nepomuk/services/strigi/strigiserviceconfig.cpp @@ -25,12 +25,36 @@ #include <KDirWatch> #include <KStandardDirs> #include <KConfigGroup> +#include <KDebug> -Nepomuk::StrigiServiceConfig::StrigiServiceConfig() - : QObject(), +namespace { + /// recursively check if a folder is hidden + bool isDirHidden( QDir& dir ) { + if ( QFileInfo( dir.path() ).isHidden() ) + return true; + else if ( dir.cdUp() ) + return isDirHidden( dir ); + else + return false; + } + + bool isDirHidden(const QString& path) { + QDir dir(path); + return isDirHidden(dir); + } +} + +Nepomuk::StrigiServiceConfig* Nepomuk::StrigiServiceConfig::s_self = 0; + +Nepomuk::StrigiServiceConfig::StrigiServiceConfig(QObject* parent) + : QObject(parent), m_config( "nepomukstrigirc" ) { + if(!s_self) { + s_self = this; + } + KDirWatch* dirWatch = KDirWatch::self(); connect( dirWatch, SIGNAL( dirty( const QString& ) ), this, SLOT( slotConfigDirty() ) ); @@ -45,14 +69,12 @@ Nepomuk::StrigiServiceConfig::StrigiServiceConfig() Nepomuk::StrigiServiceConfig::~StrigiServiceConfig() { - m_config.group( "General" ).writeEntry( "first run", false ); } Nepomuk::StrigiServiceConfig* Nepomuk::StrigiServiceConfig::self() { - K_GLOBAL_STATIC( StrigiServiceConfig, _self ); - return _self; + return s_self; } @@ -86,7 +108,24 @@ QStringList Nepomuk::StrigiServiceConfig::excludeFolders() const QStringList Nepomuk::StrigiServiceConfig::excludeFilters() const { - return m_config.group( "General" ).readEntry( "exclude filters", defaultExcludeFilterList() ); + KConfigGroup cfg = m_config.group( "General" ); + + // read configured exclude filters + QSet<QString> filters = cfg.readEntry( "exclude filters", defaultExcludeFilterList() ).toSet(); + + // make sure we always keep the latest default exclude filters + // TODO: there is one problem here. What if the user removed some of the default filters? + if(cfg.readEntry("exclude filters version", 0) < defaultExcludeFilterListVersion()) { + filters += defaultExcludeFilterList().toSet(); + + // write the config directly since the KCM does not have support for the version yet + // TODO: make this class public and use it in the KCM + cfg.writeEntry("exclude filters", QStringList::fromSet(filters)); + cfg.writeEntry("exclude filters version", defaultExcludeFilterListVersion()); + } + + // remove duplicates + return QStringList::fromSet(filters); } @@ -132,18 +171,6 @@ bool Nepomuk::StrigiServiceConfig::shouldBeIndexed( const QString& path ) const } -namespace { - /// recursively check if a folder is hidden - bool isDirHidden( QDir& dir ) { - if ( QFileInfo( dir.path() ).isHidden() ) - return true; - else if ( dir.cdUp() ) - return isDirHidden( dir ); - else - return false; - } -} - bool Nepomuk::StrigiServiceConfig::shouldFolderBeIndexed( const QString& path ) const { bool exact = false; @@ -202,15 +229,19 @@ bool Nepomuk::StrigiServiceConfig::folderInFolderList( const QString& path, bool namespace { /** * Returns true if the specified folder f would already be included or excluded using the list - * folders + * folders. Hidden folders are a special case because of the index hidden files setting. + * We always keep hidden folders in the list if they are forced to be indexed. */ bool alreadyInList( const QList<QPair<QString, bool> >& folders, const QString& f, bool include ) { bool included = false; + const bool hidden = isDirHidden(f); for ( int i = 0; i < folders.count(); ++i ) { if ( f != folders[i].first && - f.startsWith( KUrl( folders[i].first ).path( KUrl::AddTrailingSlash ) ) ) + f.startsWith( KUrl( folders[i].first ).path( KUrl::AddTrailingSlash ) ) && + !hidden ) { included = folders[i].second; + } } return included == include; } @@ -269,4 +300,19 @@ void Nepomuk::StrigiServiceConfig::buildExcludeFilterRegExpCache() m_excludeFilterRegExpCache.rebuildCacheFromFilterList( excludeFilters() ); } +void Nepomuk::StrigiServiceConfig::setInitialRun(bool isInitialRun) +{ + m_config.group( "General" ).writeEntry( "first run", isInitialRun ); +} + +bool Nepomuk::StrigiServiceConfig::initialUpdateDisabled() const +{ + return m_config.group( "General" ).readEntry( "disable initial update", false ); +} + +bool Nepomuk::StrigiServiceConfig::suspendOnPowerSaveDisabled() const +{ + return m_config.group( "General" ).readEntry( "disable suspend on powersave", false ); +} + #include "strigiserviceconfig.moc" diff --git a/nepomuk/services/strigi/strigiserviceconfig.h b/nepomuk/services/strigi/strigiserviceconfig.h index 03d827f..88a1153 100644 --- a/nepomuk/services/strigi/strigiserviceconfig.h +++ b/nepomuk/services/strigi/strigiserviceconfig.h @@ -39,7 +39,16 @@ namespace Nepomuk { Q_OBJECT public: + /** + * Create a new file indexr config. The first instance to be + * created will be accessible via self(). + */ + StrigiServiceConfig(QObject* parent = 0); ~StrigiServiceConfig(); + + /** + * Get the first created instance of StrigiServiceConfig + */ static StrigiServiceConfig* self(); /** @@ -77,6 +86,25 @@ namespace Nepomuk { bool isInitialRun() const; /** + * A "hidden" config option which allows to disable the initial + * update of all indexed folders. + * + * This should be used in combination with isInitialRun() to make + * sure all folders are at least indexed once. + */ + bool initialUpdateDisabled() const; + + /** + * A "hidden" config option which allows to disable the feature + * where the file indexing is suspended when in powersave mode. + * This is especially useful for mobile devices which always run + * on battery. + * + * At some point this should be a finer grained configuration. + */ + bool suspendOnPowerSaveDisabled() const; + + /** * Check if \p path should be indexed taking into account * the includeFolders(), the excludeFolders(), and the * excludeFilters(). @@ -114,12 +142,17 @@ namespace Nepomuk { Q_SIGNALS: void configChanged(); + public Q_SLOTS: + /** + * Should be called once the initial indexing is done, ie. all folders + * have been indexed. + */ + void setInitialRun(bool isInitialRun); + private Q_SLOTS: void slotConfigDirty(); private: - StrigiServiceConfig(); - /** * Check if \p path is in the list of folders to be indexed taking * include and exclude folders into account. @@ -130,7 +163,7 @@ namespace Nepomuk { void buildFolderCache(); void buildExcludeFilterRegExpCache(); - KConfig m_config; + mutable KConfig m_config; /// Caching cleaned up list (no duplicates, no useless entries, etc.) QList<QPair<QString, bool> > m_folderCache; @@ -140,6 +173,8 @@ namespace Nepomuk { RegExpCache m_excludeFilterRegExpCache; mutable QMutex m_folderCacheMutex; + + static StrigiServiceConfig* s_self; }; } diff --git a/nepomuk/servicestub/main.cpp b/nepomuk/servicestub/main.cpp index e0d754c..40cad5b 100644 --- a/nepomuk/servicestub/main.cpp +++ b/nepomuk/servicestub/main.cpp @@ -38,6 +38,8 @@ #include "priority.h" namespace { + Nepomuk::ServiceControl* s_control = 0; + #ifndef Q_OS_WIN void signalHandler( int signal ) { @@ -45,6 +47,7 @@ namespace { case SIGHUP: case SIGQUIT: case SIGINT: + delete s_control; QCoreApplication::exit( 0 ); } } @@ -157,12 +160,12 @@ int main( int argc, char** argv ) // register the service control // ==================================== - Nepomuk::ServiceControl* control = new Nepomuk::ServiceControl( serviceName, service, &app ); + s_control = new Nepomuk::ServiceControl( serviceName, service, &app ); // start the service (queued since we need an event loop) // ==================================== - QTimer::singleShot( 0, control, SLOT( start() ) ); + QTimer::singleShot( 0, s_control, SLOT( start() ) ); return app.exec(); } diff --git a/phonon/kcm/xine/kcm_phononxine.desktop b/phonon/kcm/xine/kcm_phononxine.desktop index 757ab6b..ace5892 100644 --- a/phonon/kcm/xine/kcm_phononxine.desktop +++ b/phonon/kcm/xine/kcm_phononxine.desktop @@ -98,7 +98,7 @@ Comment[bn_IN]=Xine ব্যাক-এন্ডের কনফিগারে Comment[bs]=Postava pozadine Ksin Comment[ca]=Configuració del dorsal del Xine Comment[ca@valencia]=Configuració del dorsal del Xine -Comment[cs]=Nastavení backendu Xine +Comment[cs]=Nastavení podpůrné vrstvy Xine Comment[csb]=Kònfigùracëjô czérownika Xine Comment[da]=Konfiguration af Xine-motor Comment[de]=Xine-Treiber-Einrichtung diff --git a/phonon/platform_kde/phononbackend.desktop b/phonon/platform_kde/phononbackend.desktop index 1c1e2c9..5df5d13 100644 --- a/phonon/platform_kde/phononbackend.desktop +++ b/phonon/platform_kde/phononbackend.desktop @@ -11,7 +11,7 @@ Name[bn_IN]=KDE মাল্টিমিডিয়া ব্যাক-এন্ Name[bs]=KDE‑ova multimedijska pozadina Name[ca]=Dorsal multimèdia del KDE Name[ca@valencia]=Dorsal multimèdia del KDE -Name[cs]=Multimediální backend KDE +Name[cs]=Multimediální podpůrná vrstva KDE Name[csb]=Òbsłużënk mùltimediów Name[da]=KDE multimedie-backend Name[de]=KDE-Multimedia-Unterstützung /space/work/OBS/kdf/kdebase4-runtime
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor