Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,11 @@
add_subdirectory(lib)
add_subdirectory(mimetypes)
if (KCOREADDONS_USE_QML)
add_subdirectory(qml)
endif()
ecm_qt_install_logging_categories(
EXPORT KCOREADDONS
FILE kcoreaddons.categories
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Extract strings from all source files.
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
# lconvert.
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/kcoreaddons6_qt.pot
@@ -0,0 +1,391 @@
# Configure checks for the caching subdir
include(CheckIncludeFiles)
check_include_files("sys/types.h;sys/mman.h" HAVE_SYS_MMAN_H)
configure_file(caching/config-caching.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-caching.h)
include(CheckSymbolExists)
check_symbol_exists("getgrouplist" "grp.h" HAVE_GETGROUPLIST)
if(UNIX)
function(check_dladdr) # use a function to scope the variables!
set(CMAKE_REQUIRED_FLAGS ${DLADDR_LINK_FLAGS})
set(CMAKE_REQUIRED_LIBRARIES ${DLADDR_LINK_LIBRARIES})
check_symbol_exists("dladdr" "dlfcn.h" HAVE_DLADDR)
set(HAVE_DLADDR ${HAVE_DLADDR} PARENT_SCOPE)
endfunction()
if(CMAKE_SYSTEM_NAME MATCHES "Linux") # on linux dladdr isn't part of libc, on freebsd and osx it is
set(DLADDR_LINK_FLAGS "-D_GNU_SOURCE")
set(DLADDR_LINK_LIBRARIES "dl")
endif()
check_dladdr()
add_feature_info(dladdr ${HAVE_DLADDR} "Can resolve shared library paths and by extension libexec paths at runtime using dladdr() API")
endif()
set(ACCOUNTS_SERVICE_ICON_DIR "/var/lib/AccountsService/icons" CACHE STRING "Accounts Services icon storage directory")
configure_file(util/config-util.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-util.h)
add_library(KF6CoreAddons)
add_library(KF6::CoreAddons ALIAS KF6CoreAddons)
set_target_properties(KF6CoreAddons PROPERTIES
VERSION ${KCOREADDONS_VERSION}
SOVERSION ${KCOREADDONS_SOVERSION}
EXPORT_NAME CoreAddons
)
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(KF6CoreAddons PUBLIC -DKCOREADDONS_STATIC)
endif()
ecm_create_qm_loader(KF6CoreAddons kcoreaddons6_qt)
if (Inotify_FOUND)
target_include_directories(KF6CoreAddons PRIVATE ${Inotify_INCLUDE_DIRS})
target_link_libraries(KF6CoreAddons PRIVATE ${Inotify_LIBRARIES})
endif ()
if(NOT WIN32)
target_sources(KF6CoreAddons PRIVATE
caching/kshareddatacache.cpp
caching/kshareddatacache.h
caching/ksdclock.cpp
caching/ksdcmemory.cpp
)
set_source_files_properties(caching/kshareddatacache.cpp
PROPERTIES COMPILE_FLAGS -fexceptions)
target_link_libraries(KF6CoreAddons PRIVATE Threads::Threads)
else()
target_sources(KF6CoreAddons PRIVATE
caching/kshareddatacache_win.cpp
)
endif()
if (WIN32)
target_sources(KF6CoreAddons PRIVATE
text/kmacroexpander_win.cpp
util/klistopenfilesjob_win.cpp
util/kprocesslist_win.cpp
util/kshell_win.cpp
util/kuser_win.cpp
)
endif ()
if (UNIX AND NOT IOS)
target_sources(KF6CoreAddons PRIVATE
text/kmacroexpander_unix.cpp
#util/klistopenfilesjob_unix.cpp
util/kuser_unix.cpp
util/kshell_unix.cpp
)
if (HAVE_PROCSTAT)
target_sources(KF6CoreAddons PRIVATE
util/kprocesslist_unix_procstat.cpp
)
target_compile_definitions(KF6CoreAddons PRIVATE HAVE_PROCSTAT=1)
else ()
target_sources(KF6CoreAddons PRIVATE
#util/kprocesslist_unix.cpp
)
endif ()
endif ()
if(HAVE_QTDBUS)
set(_dbus_SRCS)
set_source_files_properties(io/org.freedesktop.portal.FileTransfer.xml PROPERTIES INCLUDE io/dbustypes_p.h)
qt_add_dbus_interface(_dbus_SRCS io/org.freedesktop.portal.FileTransfer.xml io/org.freedesktop.portal.FileTransfer)
set_source_files_properties(io/org.kde.KIOFuse.VFS.xml PROPERTIES NO_NAMESPACE TRUE)
qt_add_dbus_interface(_dbus_SRCS io/org.kde.KIOFuse.VFS.xml io/org.kde.KIOFuse.VFS)
target_sources(KF6CoreAddons PRIVATE ${_dbus_SRCS})
target_link_libraries(KF6CoreAddons PRIVATE Qt6::DBus)
endif()
if (TARGET UDev::UDev)
target_link_libraries(KF6CoreAddons PRIVATE UDev::UDev)
endif()
target_sources(KF6CoreAddons PRIVATE
licenses/licenses.qrc
kaboutdata.cpp
kcoreaddons.cpp
io/kautosavefile.cpp
io/kdirwatch.cpp
io/kfilesystemtype.cpp
io/kbackup.cpp
io/kurlmimedata.cpp
io/kfileutils.cpp
io/knetworkmounts.cpp
jobs/kcompositejob.cpp
jobs/kjob.cpp
jobs/kjobtrackerinterface.cpp
jobs/kjobuidelegate.cpp
plugin/kpluginfactory.cpp
plugin/kpluginmetadata.cpp
plugin/kstaticpluginhelpers.cpp
randomness/krandom.cpp
text/kemoticonsparser.cpp
text/kjsonutils.cpp
text/kfuzzymatcher.cpp
text/kmacroexpander.cpp
text/kstringhandler.cpp
text/ktexttohtml.cpp
util/kformat.cpp
util/kformatprivate.cpp
util/kosrelease.cpp
util/kprocesslist.cpp
util/kshell.cpp
util/klibexec.cpp
util/ksignalhandler.cpp
util/kmemoryinfo.cpp
util/kruntimeplatform.cpp
kaboutdata.h
kcoreaddons.h
io/kautosavefile.h
io/kdirwatch.h
io/kfilesystemtype.h
io/kbackup.h
io/kurlmimedata.h
io/kfileutils.h
io/knetworkmounts.h
jobs/kcompositejob.h
jobs/kjob.h
jobs/kjobtrackerinterface.h
jobs/kjobuidelegate.h
plugin/kpluginfactory.h
plugin/kpluginmetadata.h
randomness/krandom.h
text/kjsonutils.h
text/kfuzzymatcher.h
text/kmacroexpander.h
text/kstringhandler.h
text/ktexttohtml.h
util/kformat.h
util/kosrelease.h
util/kprocesslist.h
util/kshell.h
util/klibexec.h
util/ksignalhandler.h
util/kmemoryinfo.h
util/kruntimeplatform.h
)
if(NOT IOS)
target_sources(KF6CoreAddons PRIVATE
io/kprocess.cpp
util/ksandbox.cpp
io/kprocess.h
util/ksandbox.h
)
endif()
if (ENABLE_PCH)
target_precompile_headers(KF6CoreAddons PRIVATE
<QObject>
<QLoggingCategory>
<QString>
)
endif()
set(kcoreaddons_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/caching/
${CMAKE_CURRENT_BINARY_DIR}/io/
${CMAKE_CURRENT_SOURCE_DIR}/io/
${CMAKE_CURRENT_SOURCE_DIR}/jobs/
${CMAKE_CURRENT_SOURCE_DIR}/plugin/
${CMAKE_CURRENT_SOURCE_DIR}/randomness/
${CMAKE_CURRENT_SOURCE_DIR}/text/
${CMAKE_CURRENT_SOURCE_DIR}/util/
)
ecm_qt_export_logging_category(
IDENTIFIER KDIRWATCH
CATEGORY_NAME kf.coreaddons.kdirwatch
OLD_CATEGORY_NAMES kf5.kcoreaddons.kdirwatch
DEFAULT_SEVERITY Warning
DESCRIPTION "KDirWatch (KCoreAddons)"
EXPORT KCOREADDONS
)
ecm_qt_export_logging_category(
IDENTIFIER KABOUTDATA
CATEGORY_NAME kf.coreaddons.kaboutdata
OLD_CATEGORY_NAMES kf5.kcoreaddons.kaboutdata
DESCRIPTION "KAboutData (KCoreAddons)"
EXPORT KCOREADDONS
)
ecm_qt_export_logging_category(
IDENTIFIER LOG_KMEMORYINFO
CATEGORY_NAME kf.coreaddons.kmemoryinfo
DESCRIPTION "KMemoryInfo (KCoreAddons)"
EXPORT KCOREADDONS
)
ecm_qt_declare_logging_category(KF6CoreAddons
HEADER kcoreaddons_debug.h
IDENTIFIER KCOREADDONS_DEBUG
CATEGORY_NAME kf.coreaddons
OLD_CATEGORY_NAMES org.kde.kcoreaddons
DESCRIPTION "kcoreaddons (kcoreaddons lib)"
EXPORT KCOREADDONS
)
ecm_generate_export_header(KF6CoreAddons
BASE_NAME KCoreAddons
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
DEPRECATION_VERSIONS 6.9
)
target_include_directories(KF6CoreAddons PUBLIC "$<BUILD_INTERFACE:${kcoreaddons_INCLUDE_DIRS}>")
target_link_libraries(KF6CoreAddons
PUBLIC
Qt6::Core
)
if(WIN32)
target_link_libraries(KF6CoreAddons PRIVATE netapi32 userenv psapi)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
target_link_libraries(KF6CoreAddons PRIVATE kvm)
endif()
if (HAVE_PROCSTAT)
target_link_libraries(KF6CoreAddons PRIVATE Procstat::Procstat)
endif()
if(HAIKU)
# socketpair should be part of the C library according to POSIX.1-2008,
# but Haiku has it in a separate library
target_link_libraries(KF6CoreAddons PRIVATE network)
endif()
if(HAVE_DLADDR)
set_source_files_properties(SOURCE util/klibexec.cpp PROPERTIES COMPILE_FLAGS "${DLADDR_LINK_FLAGS}") # ensure _GNU_SOURCE on Linux
target_link_libraries(KF6CoreAddons PRIVATE ${DLADDR_LINK_LIBRARIES})
endif()
target_include_directories(KF6CoreAddons INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCoreAddons>")
target_compile_definitions(KF6CoreAddons INTERFACE "$<INSTALL_INTERFACE:KCOREADDONS_LIB>")
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KAboutData,KAboutPerson,KAboutLicense,KAboutComponent
KCoreAddons
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES KSharedDataCache
RELATIVE caching
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KAutoSaveFile
KDirWatch
KProcess
KBackup
KUrlMimeData
KFileSystemType
KFileUtils
KNetworkMounts
RELATIVE io
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KCompositeJob
KJob
KJobTrackerInterface
KJobUiDelegate
RELATIVE jobs
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KPluginFactory
KPluginMetaData
RELATIVE plugin
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KRandom
RELATIVE randomness
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KJsonUtils
KFuzzyMatcher
KMacroExpander
KStringHandler
KTextToHTML
RELATIVE text
REQUIRED_HEADERS KCoreAddons_HEADERS
)
ecm_generate_headers(KCoreAddons_HEADERS
HEADER_NAMES
KFormat
KOSRelease
KUser
KShell
KProcessList
KListOpenFilesJob
KLibexec
KSignalHandler
KRuntimePlatform
KSandbox
KMemoryInfo
RELATIVE util
REQUIRED_HEADERS KCoreAddons_HEADERS
)
install(TARGETS KF6CoreAddons EXPORT KF6CoreAddonsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${KCoreAddons_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kcoreaddons_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KCoreAddons COMPONENT Devel
)
install(FILES plugin/kpluginmetadata.schema.json DESTINATION ${KDE_INSTALL_DATADIR}/kf6/jsonschema COMPONENT Devel)
if(BUILD_QCH)
ecm_add_qch(
KF6CoreAddons_QCH
NAME KCoreAddons
BASE_NAME KF6CoreAddons
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KCoreAddons_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
${kcoreaddons_INCLUDE_DIRS}
BLANK_MACROS
KCOREADDONS_EXPORT
KCOREADDONS_DEPRECATED
KCOREADDONS_DEPRECATED_EXPORT
"KCOREADDONS_DEPRECATED_VERSION(x, y, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,2 @@
#cmakedefine01 HAVE_SYS_MMAN_H
@@ -0,0 +1,116 @@
/*
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
* SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ksdclock_p.h"
#include "kcoreaddons_debug.h"
#include <memory>
/**
* This is a method to determine the best lock type to use for a
* shared cache, based on local support. An identifier to the appropriate
* SharedLockId is returned, which can be passed to createLockFromId().
*/
SharedLockId findBestSharedLock()
{
// We would prefer a process-shared capability that also supports
// timeouts. Failing that, process-shared is preferred over timeout
// support. Failing that we'll go thread-local
bool timeoutsSupported = false;
bool pthreadsProcessShared = false;
bool semaphoresProcessShared = false;
#ifdef KSDC_TIMEOUTS_SUPPORTED
timeoutsSupported = ::sysconf(_SC_TIMEOUTS) >= 200112L;
#endif
// Now that we've queried timeouts, try actually creating real locks and
// seeing if there's issues with that.
#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
{
pthread_mutex_t tempMutex;
std::unique_ptr<KSDCLock> tempLock;
if (timeoutsSupported) {
#ifdef KSDC_TIMEOUTS_SUPPORTED
tempLock = std::make_unique<pthreadTimedLock>(tempMutex);
#endif
} else {
tempLock = std::make_unique<pthreadLock>(tempMutex);
}
tempLock->initialize(pthreadsProcessShared);
}
#endif // KSDC_THREAD_PROCESS_SHARED_SUPPORTED
// Our first choice is pthread_mutex_t for compatibility.
if (timeoutsSupported && pthreadsProcessShared) {
return LOCKTYPE_MUTEX;
}
#ifdef KSDC_SEMAPHORES_SUPPORTED
{
sem_t tempSemaphore;
std::unique_ptr<KSDCLock> tempLock;
if (timeoutsSupported) {
tempLock = std::make_unique<semaphoreTimedLock>(tempSemaphore);
} else {
tempLock = std::make_unique<semaphoreLock>(tempSemaphore);
}
tempLock->initialize(semaphoresProcessShared);
}
#endif // KSDC_SEMAPHORES_SUPPORTED
if (timeoutsSupported && semaphoresProcessShared) {
return LOCKTYPE_SEMAPHORE;
} else if (pthreadsProcessShared) {
return LOCKTYPE_MUTEX;
} else if (semaphoresProcessShared) {
return LOCKTYPE_SEMAPHORE;
}
// Fallback to a dumb-simple but possibly-CPU-wasteful solution.
return LOCKTYPE_SPINLOCK;
}
KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock)
{
switch (id) {
#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
case LOCKTYPE_MUTEX:
#ifdef KSDC_TIMEOUTS_SUPPORTED
if (::sysconf(_SC_TIMEOUTS) >= 200112L) {
return new pthreadTimedLock(lock.mutex);
}
#endif
return new pthreadLock(lock.mutex);
break;
#endif // KSDC_THREAD_PROCESS_SHARED_SUPPORTED
#ifdef KSDC_SEMAPHORES_SUPPORTED
case LOCKTYPE_SEMAPHORE:
#ifdef KSDC_TIMEOUTS_SUPPORTED
if (::sysconf(_SC_SEMAPHORES) >= 200112L) {
return new semaphoreTimedLock(lock.semaphore);
}
#endif
return new semaphoreLock(lock.semaphore);
break;
#endif // KSDC_SEMAPHORES_SUPPORTED
case LOCKTYPE_SPINLOCK:
return new simpleSpinLock(lock.spinlock);
break;
default:
qCCritical(KCOREADDONS_DEBUG) << "Creating shell of a lock!";
return new KSDCLock;
}
}
@@ -0,0 +1,322 @@
/*
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
* SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSDCLOCK_P_H
#define KSDCLOCK_P_H
#include <qbasicatomic.h>
#include <sched.h> // sched_yield
#include <unistd.h> // Check for sched_yield
// Mac OS X, for all its POSIX compliance, does not support timeouts on its
// mutexes, which is kind of a disaster for cross-process support. However
// synchronization primitives still work, they just might hang if the cache is
// corrupted, so keep going.
#if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L))
#define KSDC_TIMEOUTS_SUPPORTED 1
#endif
#if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED)
#warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt"
#endif
#if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__)
#include <pthread.h>
#define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1
#endif
#if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L))
#include <semaphore.h>
#define KSDC_SEMAPHORES_SUPPORTED 1
#endif
#if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
#warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless."
#endif
/**
* This class defines an interface used by KSharedDataCache::Private to offload
* proper locking and unlocking depending on what the platform supports at
* runtime and compile-time.
*/
class KSDCLock
{
public:
virtual ~KSDCLock()
{
}
// Return value indicates if the mutex was properly initialized (including
// threads-only as a fallback).
virtual bool initialize(bool &processSharingSupported)
{
processSharingSupported = false;
return false;
}
virtual bool lock()
{
return false;
}
virtual void unlock()
{
}
};
/**
* This is a very basic lock that should work on any system where GCC atomic
* intrinsics are supported. It can waste CPU so better primitives should be
* used if available on the system.
*/
class simpleSpinLock : public KSDCLock
{
public:
simpleSpinLock(QBasicAtomicInt &spinlock)
: m_spinlock(spinlock)
{
}
bool initialize(bool &processSharingSupported) override
{
// Clear the spinlock
m_spinlock.storeRelaxed(0);
processSharingSupported = true;
return true;
}
bool lock() override
{
// Spin a few times attempting to gain the lock, as upper-level code won't
// attempt again without assuming the cache is corrupt.
for (unsigned i = 50; i > 0; --i) {
if (m_spinlock.testAndSetAcquire(0, 1)) {
return true;
}
// Don't steal the processor and starve the thread we're waiting
// on.
loopSpinPause();
}
return false;
}
void unlock() override
{
m_spinlock.testAndSetRelease(1, 0);
}
private:
#ifdef Q_CC_GNU
__attribute__((always_inline,
gnu_inline
#if !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG)
,
artificial
#endif
))
#endif
static inline void
loopSpinPause()
{
// TODO: Spinning might be better in multi-core systems... but that means
// figuring how to find numbers of CPUs in a cross-platform way.
#ifdef _POSIX_PRIORITY_SCHEDULING
sched_yield();
#else
// Sleep for shortest possible time (nanosleep should round-up).
struct timespec wait_time = {0 /* sec */, 100 /* ns */};
::nanosleep(&wait_time, static_cast<struct timespec *>(0));
#endif
}
QBasicAtomicInt &m_spinlock;
};
#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
class pthreadLock : public KSDCLock
{
public:
pthreadLock(pthread_mutex_t &mutex)
: m_mutex(mutex)
{
}
bool initialize(bool &processSharingSupported) override
{
// Setup process-sharing.
pthread_mutexattr_t mutexAttr;
processSharingSupported = false;
// Initialize attributes, enable process-shared primitives, and setup
// the mutex.
if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(&mutexAttr) == 0) {
if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && pthread_mutex_init(&m_mutex, &mutexAttr) == 0) {
processSharingSupported = true;
}
pthread_mutexattr_destroy(&mutexAttr);
}
// Attempt to setup for thread-only synchronization.
if (!processSharingSupported && pthread_mutex_init(&m_mutex, nullptr) != 0) {
return false;
}
return true;
}
bool lock() override
{
return pthread_mutex_lock(&m_mutex) == 0;
}
void unlock() override
{
pthread_mutex_unlock(&m_mutex);
}
protected:
pthread_mutex_t &m_mutex;
};
#endif // KSDC_THREAD_PROCESS_SHARED_SUPPORTED
#if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
class pthreadTimedLock : public pthreadLock
{
public:
pthreadTimedLock(pthread_mutex_t &mutex)
: pthreadLock(mutex)
{
}
bool lock() override
{
struct timespec timeout;
// Long timeout, but if we fail to meet this timeout it's probably a cache
// corruption (and if we take 8 seconds then it should be much much quicker
// the next time anyways since we'd be paged back in from disk)
timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now
timeout.tv_nsec = 0;
return pthread_mutex_timedlock(&m_mutex, &timeout) == 0;
}
};
#endif // defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
#ifdef KSDC_SEMAPHORES_SUPPORTED
class semaphoreLock : public KSDCLock
{
public:
semaphoreLock(sem_t &semaphore)
: m_semaphore(semaphore)
{
}
bool initialize(bool &processSharingSupported) override
{
processSharingSupported = false;
if (::sysconf(_SC_SEMAPHORES) < 200112L) {
return false;
}
// sem_init sets up process-sharing for us.
if (sem_init(&m_semaphore, 1, 1) == 0) {
processSharingSupported = true;
}
// If not successful try falling back to thread-shared.
else if (sem_init(&m_semaphore, 0, 1) != 0) {
return false;
}
return true;
}
bool lock() override
{
return sem_wait(&m_semaphore) == 0;
}
void unlock() override
{
sem_post(&m_semaphore);
}
protected:
sem_t &m_semaphore;
};
#endif // KSDC_SEMAPHORES_SUPPORTED
#if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
class semaphoreTimedLock : public semaphoreLock
{
public:
semaphoreTimedLock(sem_t &semaphore)
: semaphoreLock(semaphore)
{
}
bool lock() override
{
struct timespec timeout;
// Long timeout, but if we fail to meet this timeout it's probably a cache
// corruption (and if we take 8 seconds then it should be much much quicker
// the next time anyways since we'd be paged back in from disk)
timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now
timeout.tv_nsec = 0;
return sem_timedwait(&m_semaphore, &timeout) == 0;
}
};
#endif // defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
// This enum controls the type of the locking used for the cache to allow
// for as much portability as possible. This value will be stored in the
// cache and used by multiple processes, therefore you should consider this
// a versioned field, do not re-arrange.
enum SharedLockId {
LOCKTYPE_INVALID = 0,
LOCKTYPE_MUTEX = 1, // pthread_mutex
LOCKTYPE_SEMAPHORE = 2, // sem_t
LOCKTYPE_SPINLOCK = 3, // atomic int in shared memory
};
// This type is a union of all possible lock types, with a SharedLockId used
// to choose which one is actually in use.
struct SharedLock {
union {
#if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
pthread_mutex_t mutex;
#endif
#if defined(KSDC_SEMAPHORES_SUPPORTED)
sem_t semaphore;
#endif
QBasicAtomicInt spinlock;
// It would be highly unfortunate if a simple glibc upgrade or kernel
// addition caused this structure to change size when an existing
// lock was thought present, so reserve enough size to cover any
// reasonable locking structure
char unused[64];
};
SharedLockId type;
};
/**
* This is a method to determine the best lock type to use for a
* shared cache, based on local support. An identifier to the appropriate
* SharedLockId is returned, which can be passed to createLockFromId().
*/
SharedLockId findBestSharedLock();
KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock);
#endif /* KSDCLOCK_P_H */
@@ -0,0 +1,319 @@
/*
* This file is part of the KDE project.
*
* SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSDCMAPPING_P_H
#define KSDCMAPPING_P_H
#include "kcoreaddons_debug.h"
#include "ksdcmemory_p.h"
#include "kshareddatacache.h"
#include <config-caching.h> // HAVE_SYS_MMAN_H
#include <QFile>
#include <QtGlobal>
#include <qplatformdefs.h>
#include <sys/resource.h>
#if defined(_POSIX_MAPPED_FILES) && ((_POSIX_MAPPED_FILES == 0) || (_POSIX_MAPPED_FILES >= 200112L))
#define KSDC_MAPPED_FILES_SUPPORTED 1
#endif
#if defined(_POSIX_SYNCHRONIZED_IO) && ((_POSIX_SYNCHRONIZED_IO == 0) || (_POSIX_SYNCHRONIZED_IO >= 200112L))
#define KSDC_SYNCHRONIZED_IO_SUPPORTED 1
#endif
// msync(2) requires both MAPPED_FILES and SYNCHRONIZED_IO POSIX options
#if defined(KSDC_MAPPED_FILES_SUPPORTED) && defined(KSDC_SYNCHRONIZED_IO_SUPPORTED)
#define KSDC_MSYNC_SUPPORTED
#endif
// BSD/Mac OS X compat
#if HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
class Q_DECL_HIDDEN KSDCMapping
{
public:
KSDCMapping(const QFile *file, const uint size, const uint cacheSize, const uint pageSize)
: m_mapped(nullptr)
, m_lock()
, m_mapSize(size)
, m_expectedType(LOCKTYPE_INVALID)
{
mapSharedMemory(file, size, cacheSize, pageSize);
}
~KSDCMapping()
{
detachFromSharedMemory(true);
}
bool isValid()
{
return !!m_mapped;
}
bool lock() const
{
if (Q_UNLIKELY(!m_mapped)) {
return false;
}
if (Q_LIKELY(m_mapped->shmLock.type == m_expectedType)) {
return m_lock->lock();
}
// Wrong type --> corrupt!
throw KSDCCorrupted("Invalid cache lock type!");
}
void unlock() const
{
if (Q_LIKELY(m_lock)) {
m_lock->unlock();
}
}
// This should be called for any memory access to shared memory. This
// function will verify that the bytes [base, base+accessLength) are
// actually mapped to m_mapped. The cache itself may have incorrect cache
// page sizes, incorrect cache size, etc. so this function should be called
// despite the cache data indicating it should be safe.
//
// If the access is /not/ safe then a KSDCCorrupted exception will be
// thrown, so be ready to catch that.
void verifyProposedMemoryAccess(const void *base, unsigned accessLength) const
{
quintptr startOfAccess = reinterpret_cast<quintptr>(base);
quintptr startOfShm = reinterpret_cast<quintptr>(m_mapped);
if (Q_UNLIKELY(startOfAccess < startOfShm)) {
throw KSDCCorrupted();
}
quintptr endOfShm = startOfShm + m_mapSize;
quintptr endOfAccess = startOfAccess + accessLength;
// Check for unsigned integer wraparound, and then
// bounds access
if (Q_UNLIKELY((endOfShm < startOfShm) || (endOfAccess < startOfAccess) || (endOfAccess > endOfShm))) {
throw KSDCCorrupted();
}
}
// Runs a quick battery of tests on an already-locked cache and returns
// false as soon as a sanity check fails. The cache remains locked in this
// situation.
bool isLockedCacheSafe() const
{
if (Q_UNLIKELY(!m_mapped)) {
return false;
}
// Note that cachePageSize() itself runs a check that can throw.
uint testSize = SharedMemory::totalSize(m_mapped->cacheSize, m_mapped->cachePageSize());
if (Q_UNLIKELY(m_mapSize != testSize)) {
return false;
}
if (Q_UNLIKELY(m_mapped->version != SharedMemory::PIXMAP_CACHE_VERSION)) {
return false;
}
switch (m_mapped->evictionPolicy.loadRelaxed()) {
case KSharedDataCache::NoEvictionPreference: // fallthrough
case KSharedDataCache::EvictLeastRecentlyUsed: // fallthrough
case KSharedDataCache::EvictLeastOftenUsed: // fallthrough
case KSharedDataCache::EvictOldest:
break;
default:
return false;
}
return true;
}
SharedMemory *m_mapped;
private:
// Put the cache in a condition to be able to call mapSharedMemory() by
// completely detaching from shared memory (such as to respond to an
// unrecoverable error).
// m_mapSize must already be set to the amount of memory mapped to m_mapped.
void detachFromSharedMemory(const bool flush = false)
{
// The lock holds a reference into shared memory, so this must be
// cleared before m_mapped is removed.
m_lock.reset();
// Note that there is no other actions required to separate from the
// shared memory segment, simply unmapping is enough. This makes things
// *much* easier so I'd recommend maintaining this ideal.
if (m_mapped) {
#ifdef KSDC_MSYNC_SUPPORTED
if (flush) {
::msync(m_mapped, m_mapSize, MS_INVALIDATE | MS_ASYNC);
}
#endif
::munmap(m_mapped, m_mapSize);
if (0 != ::munmap(m_mapped, m_mapSize)) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to unmap shared memory segment" << static_cast<void *>(m_mapped) << ":" << ::strerror(errno);
}
}
// Do not delete m_mapped, it was never constructed, it's just an alias.
m_mapped = nullptr;
m_mapSize = 0;
}
// This function does a lot of the important work, attempting to connect to shared
// memory, a private anonymous mapping if that fails, and failing that, nothing (but
// the cache remains "valid", we just don't actually do anything).
void mapSharedMemory(const QFile *file, uint size, uint cacheSize, uint pageSize)
{
void *mapAddress = MAP_FAILED;
if (file) {
// Use mmap directly instead of QFile::map since the QFile (and its
// shared mapping) will disappear unless we hang onto the QFile for no
// reason (see the note below, we don't care about the file per se...)
mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->handle(), 0);
// So... it is possible that someone else has mapped this cache already
// with a larger size. If that's the case we need to at least match
// the size to be able to access every entry, so fixup the mapping.
if (mapAddress != MAP_FAILED) {
// Successful mmap doesn't actually mean that whole range is readable so ensure it is
struct rlimit memlock;
if (getrlimit(RLIMIT_MEMLOCK, &memlock) == 0 && memlock.rlim_cur >= 2) {
// Half of limit in case something else has already locked some mem
uint lockSize = qMin(memlock.rlim_cur / 2, (rlim_t)size);
// Note that lockSize might be less than what we need to mmap
// and so this doesn't guarantee that later parts will be readable
// but that's fine, at least we know we will succeed here
if (mlock(mapAddress, lockSize)) {
throw KSDCCorrupted(QLatin1String("Cache is inaccessible ") + file->fileName());
}
if (munlock(mapAddress, lockSize) != 0) {
qCDebug(KCOREADDONS_DEBUG) << "Failed to munlock!";
}
} else {
qCWarning(KCOREADDONS_DEBUG) << "Failed to get RLIMIT_MEMLOCK!";
}
SharedMemory *mapped = reinterpret_cast<SharedMemory *>(mapAddress);
// First make sure that the version of the cache on disk is
// valid. We also need to check that version != 0 to
// disambiguate against an uninitialized cache.
if (mapped->version != SharedMemory::PIXMAP_CACHE_VERSION && mapped->version > 0) {
detachFromSharedMemory(false);
throw KSDCCorrupted(QLatin1String("Wrong version of cache ") + file->fileName());
} else if (mapped->cacheSize > cacheSize) {
// This order is very important. We must save the cache size
// before we remove the mapping, but unmap before overwriting
// the previous mapping size...
auto actualCacheSize = mapped->cacheSize;
auto actualPageSize = mapped->cachePageSize();
::munmap(mapAddress, size);
size = SharedMemory::totalSize(cacheSize, pageSize);
mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->handle(), 0);
if (mapAddress != MAP_FAILED) {
cacheSize = actualCacheSize;
pageSize = actualPageSize;
}
}
}
}
// We could be here without the mapping established if:
// 1) Process-shared synchronization is not supported, either at compile or run time,
// 2) Unable to open the required file.
// 3) Unable to resize the file to be large enough.
// 4) Establishing the mapping failed.
// 5) The mapping succeeded, but the size was wrong and we were unable to map when
// we tried again.
// 6) The incorrect version of the cache was detected.
// 7) The file could be created, but posix_fallocate failed to commit it fully to disk.
// In any of these cases, attempt to fallback to the
// better-supported anonymous page style of mmap.
// NOTE: We never use the on-disk representation independently of the
// shared memory. If we don't get shared memory the disk info is ignored,
// if we do get shared memory we never look at disk again.
if (!file || mapAddress == MAP_FAILED) {
qCWarning(KCOREADDONS_DEBUG) << "Couldn't establish file backed memory mapping, will fallback"
<< "to anonymous memory";
mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}
// Well now we're really hosed. We can still work, but we can't even cache
// data.
if (mapAddress == MAP_FAILED) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to allocate shared memory segment for shared data cache" << file->fileName() << "of size" << m_mapSize;
m_mapped = nullptr;
m_mapSize = 0;
return;
}
m_mapSize = size;
// We never actually construct m_mapped, but we assign it the same address as the
// shared memory we just mapped, so effectively m_mapped is now a SharedMemory that
// happens to be located at mapAddress.
m_mapped = reinterpret_cast<SharedMemory *>(mapAddress);
// If we were first to create this memory map, all data will be 0.
// Therefore if ready == 0 we're not initialized. A fully initialized
// header will have ready == 2. Why?
// Because 0 means "safe to initialize"
// 1 means "in progress of initing"
// 2 means "ready"
uint usecSleepTime = 8; // Start by sleeping for 8 microseconds
while (m_mapped->ready.loadRelaxed() != 2) {
if (Q_UNLIKELY(usecSleepTime >= (1 << 21))) {
// Didn't acquire within ~8 seconds? Assume an issue exists
detachFromSharedMemory(false);
throw KSDCCorrupted("Unable to acquire shared lock, is the cache corrupt?");
}
if (m_mapped->ready.testAndSetAcquire(0, 1)) {
if (!m_mapped->performInitialSetup(cacheSize, pageSize)) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to perform initial setup, this system probably "
"does not really support process-shared pthreads or "
"semaphores, even though it claims otherwise.";
detachFromSharedMemory(false);
return;
}
} else {
usleep(usecSleepTime); // spin
// Exponential fallback as in Ethernet and similar collision resolution methods
usecSleepTime *= 2;
}
}
m_expectedType = m_mapped->shmLock.type;
m_lock.reset(createLockFromId(m_expectedType, m_mapped->shmLock));
bool isProcessSharingSupported = false;
if (!m_lock->initialize(isProcessSharingSupported)) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to setup shared cache lock, although it worked when created.";
detachFromSharedMemory(false);
return;
}
}
std::unique_ptr<KSDCLock> m_lock;
uint m_mapSize;
SharedLockId m_expectedType;
};
#endif /* KSDCMEMORY_P_H */
@@ -0,0 +1,879 @@
/*
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
* SPDX-License-Identifier: LGPL-2.0-only
*
* This library includes "MurmurHash" code from Austin Appleby, which is
* placed in the public domain. See http://sites.google.com/site/murmurhash/
*/
#include "kcoreaddons_debug.h"
#include "ksdcmemory_p.h"
#include <QByteArray>
//-----------------------------------------------------------------------------
// MurmurHashAligned, by Austin Appleby
// (Released to the public domain, or licensed under the MIT license where
// software may not be released to the public domain. See
// http://sites.google.com/site/murmurhash/)
// Same algorithm as MurmurHash, but only does aligned reads - should be safer
// on certain platforms.
static unsigned int MurmurHashAligned(const void *key, int len, unsigned int seed)
{
const unsigned int m = 0xc6a4a793;
const int r = 16;
const unsigned char *data = reinterpret_cast<const unsigned char *>(key);
unsigned int h = seed ^ (len * m);
int align = reinterpret_cast<quintptr>(data) & 3;
if (align && len >= 4) {
// Pre-load the temp registers
unsigned int t = 0;
unsigned int d = 0;
switch (align) {
case 1:
t |= data[2] << 16;
Q_FALLTHROUGH();
case 2:
t |= data[1] << 8;
Q_FALLTHROUGH();
case 3:
t |= data[0];
}
t <<= (8 * align);
data += 4 - align;
len -= 4 - align;
int sl = 8 * (4 - align);
int sr = 8 * align;
// Mix
while (len >= 4) {
d = *reinterpret_cast<const unsigned int *>(data);
t = (t >> sr) | (d << sl);
h += t;
h *= m;
h ^= h >> r;
t = d;
data += 4;
len -= 4;
}
// Handle leftover data in temp registers
int pack = len < align ? len : align;
d = 0;
switch (pack) {
case 3:
d |= data[2] << 16;
Q_FALLTHROUGH();
case 2:
d |= data[1] << 8;
Q_FALLTHROUGH();
case 1:
d |= data[0];
Q_FALLTHROUGH();
case 0:
h += (t >> sr) | (d << sl);
h *= m;
h ^= h >> r;
}
data += pack;
len -= pack;
} else {
while (len >= 4) {
h += *reinterpret_cast<const unsigned int *>(data);
h *= m;
h ^= h >> r;
data += 4;
len -= 4;
}
}
//----------
// Handle tail bytes
switch (len) {
case 3:
h += data[2] << 16;
Q_FALLTHROUGH();
case 2:
h += data[1] << 8;
Q_FALLTHROUGH();
case 1:
h += data[0];
h *= m;
h ^= h >> r;
};
h *= m;
h ^= h >> 10;
h *= m;
h ^= h >> 17;
return h;
}
/**
* This is the hash function used for our data to hopefully make the
* hashing used to place the QByteArrays as efficient as possible.
*/
quint32 SharedMemory::generateHash(const QByteArray &buffer)
{
// The final constant is the "seed" for MurmurHash. Do *not* change it
// without incrementing the cache version.
return MurmurHashAligned(buffer.data(), buffer.size(), 0xF0F00F0F);
}
// Alignment concerns become a big deal when we're dealing with shared memory,
// since trying to access a structure sized at, say 8 bytes at an address that
// is not evenly divisible by 8 is a crash-inducing error on some
// architectures. The compiler would normally take care of this, but with
// shared memory the compiler will not necessarily know the alignment expected,
// so make sure we account for this ourselves. To do so we need a way to find
// out the expected alignment. Enter ALIGNOF...
#ifndef ALIGNOF
#if defined(Q_CC_GNU) || defined(Q_CC_SUN)
#define ALIGNOF(x) (__alignof__(x)) // GCC provides what we want directly
#else
#include <stddef.h> // offsetof
template<class T>
struct __alignmentHack {
char firstEntry;
T obj;
static const size_t size = offsetof(__alignmentHack, obj);
};
#define ALIGNOF(x) (__alignmentHack<x>::size)
#endif // Non gcc
#endif // ALIGNOF undefined
// Returns a pointer properly aligned to handle size alignment.
// size should be a power of 2. start is assumed to be the lowest
// permissible address, therefore the return value will be >= start.
template<class T>
T *alignTo(const void *start, uint size = ALIGNOF(T))
{
quintptr mask = size - 1;
// Cast to int-type to handle bit-twiddling
quintptr basePointer = reinterpret_cast<quintptr>(start);
// If (and only if) we are already aligned, adding mask into basePointer
// will not increment any of the bits in ~mask and we get the right answer.
basePointer = (basePointer + mask) & ~mask;
return reinterpret_cast<T *>(basePointer);
}
/**
* Returns a pointer to a const object of type T, assumed to be @p offset
* *BYTES* greater than the base address. Note that in order to meet alignment
* requirements for T, it is possible that the returned pointer points greater
* than @p offset into @p base.
*/
template<class T>
const T *offsetAs(const void *const base, qint32 offset)
{
const char *ptr = reinterpret_cast<const char *>(base);
return alignTo<const T>(ptr + offset);
}
// Same as above, but for non-const objects
template<class T>
T *offsetAs(void *const base, qint32 offset)
{
char *ptr = reinterpret_cast<char *>(base);
return alignTo<T>(ptr + offset);
}
/**
* @return the smallest integer greater than or equal to (@p a / @p b).
* @param a Numerator, should be ≥ 0.
* @param b Denominator, should be > 0.
*/
unsigned SharedMemory::intCeil(unsigned a, unsigned b)
{
// The overflow check is unsigned and so is actually defined behavior.
if (Q_UNLIKELY(b == 0 || ((a + b) < a))) {
throw KSDCCorrupted();
}
return (a + b - 1) / b;
}
/**
* @return number of set bits in @p value (see also "Hamming weight")
*/
static unsigned countSetBits(unsigned value)
{
// K&R / Wegner's algorithm used. GCC supports __builtin_popcount but we
// expect there to always be only 1 bit set so this should be perhaps a bit
// faster 99.9% of the time.
unsigned count = 0;
for (count = 0; value != 0; count++) {
value &= (value - 1); // Clears least-significant set bit.
}
return count;
}
/**
* Converts the given average item size into an appropriate page size.
*/
unsigned SharedMemory::equivalentPageSize(unsigned itemSize)
{
if (itemSize == 0) {
return 4096; // Default average item size.
}
int log2OfSize = 0;
while ((itemSize >>= 1) != 0) {
log2OfSize++;
}
// Bound page size between 512 bytes and 256 KiB.
// If this is adjusted, also alter validSizeMask in cachePageSize
log2OfSize = qBound(9, log2OfSize, 18);
return (1 << log2OfSize);
}
// Returns pageSize in unsigned format.
unsigned SharedMemory::cachePageSize() const
{
unsigned _pageSize = static_cast<unsigned>(pageSize.loadRelaxed());
// bits 9-18 may be set.
static const unsigned validSizeMask = 0x7FE00u;
// Check for page sizes that are not a power-of-2, or are too low/high.
if (Q_UNLIKELY(countSetBits(_pageSize) != 1 || (_pageSize & ~validSizeMask))) {
throw KSDCCorrupted();
}
return _pageSize;
}
/**
* This is effectively the class ctor. But since we're in shared memory,
* there's a few rules:
*
* 1. To allow for some form of locking in the initial-setup case, we
* use an atomic int, which will be initialized to 0 by mmap(). Then to
* take the lock we atomically increment the 0 to 1. If we end up calling
* the QAtomicInt constructor we can mess that up, so we can't use a
* constructor for this class either.
* 2. Any member variable you add takes up space in shared memory as well,
* so make sure you need it.
*/
bool SharedMemory::performInitialSetup(uint _cacheSize, uint _pageSize)
{
if (_cacheSize < MINIMUM_CACHE_SIZE) {
qCCritical(KCOREADDONS_DEBUG) << "Internal error: Attempted to create a cache sized < " << MINIMUM_CACHE_SIZE;
return false;
}
if (_pageSize == 0) {
qCCritical(KCOREADDONS_DEBUG) << "Internal error: Attempted to create a cache with 0-sized pages.";
return false;
}
shmLock.type = findBestSharedLock();
if (shmLock.type == LOCKTYPE_INVALID) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to find an appropriate lock to guard the shared cache. "
<< "This *should* be essentially impossible. :(";
return false;
}
bool isProcessShared = false;
std::unique_ptr<KSDCLock> tempLock(createLockFromId(shmLock.type, shmLock));
if (!tempLock->initialize(isProcessShared)) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to initialize the lock for the cache!";
return false;
}
if (!isProcessShared) {
qCWarning(KCOREADDONS_DEBUG) << "Cache initialized, but does not support being"
<< "shared across processes.";
}
// These must be updated to make some of our auxiliary functions
// work right since their values will be based on the cache size.
cacheSize = _cacheSize;
pageSize = _pageSize;
version = PIXMAP_CACHE_VERSION;
cacheTimestamp = static_cast<unsigned>(::time(nullptr));
clearInternalTables();
// Unlock the mini-lock, and introduce a total memory barrier to make
// sure all changes have propagated even without a mutex.
return ready.ref();
}
void SharedMemory::clearInternalTables()
{
// Assumes we're already locked somehow.
cacheAvail = pageTableSize();
// Setup page tables to point nowhere
PageTableEntry *table = pageTable();
for (uint i = 0; i < pageTableSize(); ++i) {
table[i].index = -1;
}
// Setup index tables to be accurate.
IndexTableEntry *indices = indexTable();
for (uint i = 0; i < indexTableSize(); ++i) {
indices[i].firstPage = -1;
indices[i].useCount = 0;
indices[i].fileNameHash = 0;
indices[i].totalItemSize = 0;
indices[i].addTime = 0;
indices[i].lastUsedTime = 0;
}
}
const IndexTableEntry *SharedMemory::indexTable() const
{
// Index Table goes immediately after this struct, at the first byte
// where alignment constraints are met (accounted for by offsetAs).
return offsetAs<IndexTableEntry>(this, sizeof(*this));
}
const PageTableEntry *SharedMemory::pageTable() const
{
const IndexTableEntry *base = indexTable();
base += indexTableSize();
// Let's call wherever we end up the start of the page table...
return alignTo<PageTableEntry>(base);
}
const void *SharedMemory::cachePages() const
{
const PageTableEntry *tableStart = pageTable();
tableStart += pageTableSize();
// Let's call wherever we end up the start of the data...
return alignTo<void>(tableStart, cachePageSize());
}
const void *SharedMemory::page(pageID at) const
{
if (static_cast<uint>(at) >= pageTableSize()) {
return nullptr;
}
// We must manually calculate this one since pageSize varies.
const char *pageStart = reinterpret_cast<const char *>(cachePages());
pageStart += (at * cachePageSize());
return reinterpret_cast<const void *>(pageStart);
}
// The following are non-const versions of some of the methods defined
// above. They use const_cast<> because I feel that is better than
// duplicating the code. I suppose template member functions (?)
// may work, may investigate later.
IndexTableEntry *SharedMemory::indexTable()
{
const SharedMemory *that = const_cast<const SharedMemory *>(this);
return const_cast<IndexTableEntry *>(that->indexTable());
}
PageTableEntry *SharedMemory::pageTable()
{
const SharedMemory *that = const_cast<const SharedMemory *>(this);
return const_cast<PageTableEntry *>(that->pageTable());
}
void *SharedMemory::cachePages()
{
const SharedMemory *that = const_cast<const SharedMemory *>(this);
return const_cast<void *>(that->cachePages());
}
void *SharedMemory::page(pageID at)
{
const SharedMemory *that = const_cast<const SharedMemory *>(this);
return const_cast<void *>(that->page(at));
}
uint SharedMemory::pageTableSize() const
{
return cacheSize / cachePageSize();
}
uint SharedMemory::indexTableSize() const
{
// Assume 2 pages on average are needed -> the number of entries
// would be half of the number of pages.
return pageTableSize() / 2;
}
/**
* @return the index of the first page, for the set of contiguous
* pages that can hold @p pagesNeeded PAGES.
*/
pageID SharedMemory::findEmptyPages(uint pagesNeeded) const
{
if (Q_UNLIKELY(pagesNeeded > pageTableSize())) {
return pageTableSize();
}
// Loop through the page table, find the first empty page, and just
// makes sure that there are enough free pages.
const PageTableEntry *table = pageTable();
uint contiguousPagesFound = 0;
pageID base = 0;
for (pageID i = 0; i < static_cast<int>(pageTableSize()); ++i) {
if (table[i].index < 0) {
if (contiguousPagesFound == 0) {
base = i;
}
contiguousPagesFound++;
} else {
contiguousPagesFound = 0;
}
if (contiguousPagesFound == pagesNeeded) {
return base;
}
}
return pageTableSize();
}
// left < right?
bool SharedMemory::lruCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Most recently used will have the highest absolute time =>
// least recently used (lowest) should go first => use left < right
return l.lastUsedTime < r.lastUsedTime;
}
// left < right?
bool SharedMemory::seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Put lowest use count at start by using left < right
return l.useCount < r.useCount;
}
// left < right?
bool SharedMemory::ageCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Oldest entries die first -- they have the lowest absolute add time,
// so just like the others use left < right
return l.addTime < r.addTime;
}
void SharedMemory::defragment()
{
if (cacheAvail * cachePageSize() == cacheSize) {
return; // That was easy
}
qCDebug(KCOREADDONS_DEBUG) << "Defragmenting the shared cache";
// Just do a linear scan, and anytime there is free space, swap it
// with the pages to its right. In order to meet the precondition
// we need to skip any used pages first.
pageID currentPage = 0;
pageID idLimit = static_cast<pageID>(pageTableSize());
PageTableEntry *pages = pageTable();
if (Q_UNLIKELY(!pages || idLimit <= 0)) {
throw KSDCCorrupted();
}
// Skip used pages
while (currentPage < idLimit && pages[currentPage].index >= 0) {
++currentPage;
}
pageID freeSpot = currentPage;
// Main loop, starting from a free page, skip to the used pages and
// move them back.
while (currentPage < idLimit) {
// Find the next used page
while (currentPage < idLimit && pages[currentPage].index < 0) {
++currentPage;
}
if (currentPage >= idLimit) {
break;
}
// Found an entry, move it.
qint32 affectedIndex = pages[currentPage].index;
if (Q_UNLIKELY(affectedIndex < 0 || affectedIndex >= idLimit || indexTable()[affectedIndex].firstPage != currentPage)) {
throw KSDCCorrupted();
}
indexTable()[affectedIndex].firstPage = freeSpot;
// Moving one page at a time guarantees we can use memcpy safely
// (in other words, the source and destination will not overlap).
while (currentPage < idLimit && pages[currentPage].index >= 0) {
const void *const sourcePage = page(currentPage);
void *const destinationPage = page(freeSpot);
// We always move used pages into previously-found empty spots,
// so check ordering as well for logic errors.
if (Q_UNLIKELY(!sourcePage || !destinationPage || sourcePage < destinationPage)) {
throw KSDCCorrupted();
}
::memcpy(destinationPage, sourcePage, cachePageSize());
pages[freeSpot].index = affectedIndex;
pages[currentPage].index = -1;
++currentPage;
++freeSpot;
// If we've just moved the very last page and it happened to
// be at the very end of the cache then we're done.
if (currentPage >= idLimit) {
break;
}
// We're moving consecutive used pages whether they belong to
// our affected entry or not, so detect if we've started moving
// the data for a different entry and adjust if necessary.
if (affectedIndex != pages[currentPage].index) {
indexTable()[pages[currentPage].index].firstPage = freeSpot;
}
affectedIndex = pages[currentPage].index;
}
// At this point currentPage is on a page that is unused, and the
// cycle repeats. However, currentPage is not the first unused
// page, freeSpot is, so leave it alone.
}
}
/**
* Finds the index entry for a given key.
* @param key UTF-8 encoded key to search for.
* @return The index of the entry in the cache named by @p key. Returns
* <0 if no such entry is present.
*/
qint32 SharedMemory::findNamedEntry(const QByteArray &key) const
{
uint keyHash = SharedMemory::generateHash(key);
uint position = keyHash % indexTableSize();
uint probeNumber = 1; // See insert() for description
// Imagine 3 entries A, B, C in this logical probing chain. If B is
// later removed then we can't find C either. So, we must keep
// searching for probeNumber number of tries (or we find the item,
// obviously).
while (indexTable()[position].fileNameHash != keyHash && probeNumber < MAX_PROBE_COUNT) {
position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % indexTableSize();
probeNumber++;
}
if (indexTable()[position].fileNameHash == keyHash) {
pageID firstPage = indexTable()[position].firstPage;
if (firstPage < 0 || static_cast<uint>(firstPage) >= pageTableSize()) {
return -1;
}
const void *resultPage = page(firstPage);
if (Q_UNLIKELY(!resultPage)) {
throw KSDCCorrupted();
}
const char *utf8FileName = reinterpret_cast<const char *>(resultPage);
if (qstrncmp(utf8FileName, key.constData(), cachePageSize()) == 0) {
return position;
}
}
return -1; // Not found, or a different one found.
}
// Function to use with std::unique_ptr in removeUsedPages below...
void SharedMemory::deleteTable(IndexTableEntry *table)
{
delete[] table;
}
/**
* Removes the requested number of pages.
*
* @param numberNeeded the number of pages required to fulfill a current request.
* This number should be <0 and <= the number of pages in the cache.
* @return The identifier of the beginning of a consecutive block of pages able
* to fill the request. Returns a value >= pageTableSize() if no such
* request can be filled.
* @internal
*/
uint SharedMemory::removeUsedPages(uint numberNeeded)
{
if (numberNeeded == 0) {
qCCritical(KCOREADDONS_DEBUG) << "Internal error: Asked to remove exactly 0 pages for some reason.";
throw KSDCCorrupted();
}
if (numberNeeded > pageTableSize()) {
qCCritical(KCOREADDONS_DEBUG) << "Internal error: Requested more space than exists in the cache.";
qCCritical(KCOREADDONS_DEBUG) << numberNeeded << "requested, " << pageTableSize() << "is the total possible.";
throw KSDCCorrupted();
}
// If the cache free space is large enough we will defragment first
// instead since it's likely we're highly fragmented.
// Otherwise, we will (eventually) simply remove entries per the
// eviction order set for the cache until there is enough room
// available to hold the number of pages we need.
qCDebug(KCOREADDONS_DEBUG) << "Removing old entries to free up" << numberNeeded << "pages," << cacheAvail << "are already theoretically available.";
if (cacheAvail > 3 * numberNeeded) {
defragment();
uint result = findEmptyPages(numberNeeded);
if (result < pageTableSize()) {
return result;
} else {
qCCritical(KCOREADDONS_DEBUG) << "Just defragmented a locked cache, but still there"
<< "isn't enough room for the current request.";
}
}
// At this point we know we'll have to free some space up, so sort our
// list of entries by whatever the current criteria are and start
// killing expired entries.
std::unique_ptr<IndexTableEntry, decltype(deleteTable) *> tablePtr(new IndexTableEntry[indexTableSize()], deleteTable);
if (!tablePtr) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to allocate temporary memory for sorting the cache!";
clearInternalTables();
throw KSDCCorrupted();
}
// We use tablePtr to ensure the data is destroyed, but do the access
// via a helper pointer to allow for array ops.
IndexTableEntry *table = tablePtr.get();
::memcpy(table, indexTable(), sizeof(IndexTableEntry) * indexTableSize());
// Our entry ID is simply its index into the
// index table, which qSort will rearrange all willy-nilly, so first
// we'll save the *real* entry ID into firstPage (which is useless in
// our copy of the index table). On the other hand if the entry is not
// used then we note that with -1.
for (uint i = 0; i < indexTableSize(); ++i) {
table[i].firstPage = table[i].useCount > 0 ? static_cast<pageID>(i) : -1;
}
// Declare the comparison function that we'll use to pass to qSort,
// based on our cache eviction policy.
bool (*compareFunction)(const IndexTableEntry &, const IndexTableEntry &);
switch (evictionPolicy.loadRelaxed()) {
case KSharedDataCache::EvictLeastOftenUsed:
case KSharedDataCache::NoEvictionPreference:
default:
compareFunction = seldomUsedCompare;
break;
case KSharedDataCache::EvictLeastRecentlyUsed:
compareFunction = lruCompare;
break;
case KSharedDataCache::EvictOldest:
compareFunction = ageCompare;
break;
}
std::sort(table, table + indexTableSize(), compareFunction);
// Least recently used entries will be in the front.
// Start killing until we have room.
// Note on removeEntry: It expects an index into the index table,
// but our sorted list is all jumbled. But we stored the real index
// in the firstPage member.
// Remove entries until we've removed at least the required number
// of pages.
uint i = 0;
while (i < indexTableSize() && numberNeeded > cacheAvail) {
int curIndex = table[i++].firstPage; // Really an index, not a page
// Removed everything, still no luck (or curIndex is set but too high).
if (curIndex < 0 || static_cast<uint>(curIndex) >= indexTableSize()) {
qCCritical(KCOREADDONS_DEBUG) << "Trying to remove index" << curIndex << "out-of-bounds for index table of size" << indexTableSize();
throw KSDCCorrupted();
}
qCDebug(KCOREADDONS_DEBUG) << "Removing entry of" << indexTable()[curIndex].totalItemSize << "size";
removeEntry(curIndex);
}
// At this point let's see if we have freed up enough data by
// defragmenting first and seeing if we can find that free space.
defragment();
pageID result = pageTableSize();
while (i < indexTableSize() && (static_cast<uint>(result = findEmptyPages(numberNeeded))) >= pageTableSize()) {
int curIndex = table[i++].firstPage;
if (curIndex < 0) {
// One last shot.
defragment();
return findEmptyPages(numberNeeded);
}
if (Q_UNLIKELY(static_cast<uint>(curIndex) >= indexTableSize())) {
throw KSDCCorrupted();
}
removeEntry(curIndex);
}
// Whew.
return result;
}
// Returns the total size required for a given cache size.
uint SharedMemory::totalSize(uint cacheSize, uint effectivePageSize)
{
uint numberPages = intCeil(cacheSize, effectivePageSize);
uint indexTableSize = numberPages / 2;
// Knowing the number of pages, we can determine what addresses we'd be
// using (properly aligned), and from there determine how much memory
// we'd use.
IndexTableEntry *indexTableStart = offsetAs<IndexTableEntry>(static_cast<void *>(nullptr), sizeof(SharedMemory));
indexTableStart += indexTableSize;
PageTableEntry *pageTableStart = reinterpret_cast<PageTableEntry *>(indexTableStart);
pageTableStart = alignTo<PageTableEntry>(pageTableStart);
pageTableStart += numberPages;
// The weird part, we must manually adjust the pointer based on the page size.
char *cacheStart = reinterpret_cast<char *>(pageTableStart);
cacheStart += (numberPages * effectivePageSize);
// ALIGNOF gives pointer alignment
cacheStart = alignTo<char>(cacheStart, ALIGNOF(void *));
// We've traversed the header, index, page table, and cache.
// Wherever we're at now is the size of the enchilada.
return static_cast<uint>(reinterpret_cast<quintptr>(cacheStart));
}
uint SharedMemory::fileNameHash(const QByteArray &utf8FileName) const
{
return generateHash(utf8FileName) % indexTableSize();
}
void SharedMemory::clear()
{
clearInternalTables();
}
// Must be called while the lock is already held!
void SharedMemory::removeEntry(uint index)
{
if (index >= indexTableSize() || cacheAvail > pageTableSize()) {
throw KSDCCorrupted();
}
PageTableEntry *pageTableEntries = pageTable();
IndexTableEntry *entriesIndex = indexTable();
// Update page table first
pageID firstPage = entriesIndex[index].firstPage;
if (firstPage < 0 || static_cast<quint32>(firstPage) >= pageTableSize()) {
qCDebug(KCOREADDONS_DEBUG) << "Trying to remove an entry which is already invalid. This "
<< "cache is likely corrupt.";
throw KSDCCorrupted();
}
if (index != static_cast<uint>(pageTableEntries[firstPage].index)) {
qCCritical(KCOREADDONS_DEBUG) << "Removing entry" << index << "but the matching data"
<< "doesn't link back -- cache is corrupt, clearing.";
throw KSDCCorrupted();
}
uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize());
uint savedCacheSize = cacheAvail;
for (uint i = firstPage; i < pageTableSize() && static_cast<uint>(pageTableEntries[i].index) == index; ++i) {
pageTableEntries[i].index = -1;
cacheAvail++;
}
if ((cacheAvail - savedCacheSize) != entriesToRemove) {
qCCritical(KCOREADDONS_DEBUG) << "We somehow did not remove" << entriesToRemove << "when removing entry" << index << ", instead we removed"
<< (cacheAvail - savedCacheSize);
throw KSDCCorrupted();
}
// For debugging
#ifdef NDEBUG
void *const startOfData = page(firstPage);
if (startOfData) {
QByteArray str((const char *)startOfData);
str.prepend(" REMOVED: ");
str.prepend(QByteArray::number(index));
str.prepend("ENTRY ");
::memcpy(startOfData, str.constData(), str.size() + 1);
}
#endif
// Update the index
entriesIndex[index].fileNameHash = 0;
entriesIndex[index].totalItemSize = 0;
entriesIndex[index].useCount = 0;
entriesIndex[index].lastUsedTime = 0;
entriesIndex[index].addTime = 0;
entriesIndex[index].firstPage = -1;
}
@@ -0,0 +1,240 @@
/*
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
* SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSDCMEMORY_P_H
#define KSDCMEMORY_P_H
#include "kcoreaddons_debug.h"
#include "ksdclock_p.h"
#include "kshareddatacache.h"
/**
* A very simple class whose only purpose is to be thrown as an exception from
* underlying code to indicate that the shared cache is apparently corrupt.
* This must be caught by top-level library code and used to unlink the cache
* in this circumstance.
*
* @internal
*/
class KSDCCorrupted
{
public:
KSDCCorrupted()
{
qCWarning(KCOREADDONS_DEBUG) << "Error detected in cache!";
}
KSDCCorrupted(const QString message)
{
qCWarning(KCOREADDONS_DEBUG).noquote() << message;
}
KSDCCorrupted(const char *message)
{
KSDCCorrupted(QLatin1String(message));
}
};
typedef qint32 pageID;
// =========================================================================
// Description of the cache:
//
// The shared memory cache is designed to be handled as two separate objects,
// all contained in the same global memory segment. First off, there is the
// basic header data, consisting of the global header followed by the
// accounting data necessary to hold items (described in more detail
// momentarily). Following the accounting data is the start of the "page table"
// (essentially just as you'd see it in an Operating Systems text).
//
// The page table contains shared memory split into fixed-size pages, with a
// configurable page size. In the event that the data is too large to fit into
// a single logical page, it will need to occupy consecutive pages of memory.
//
// The accounting data that was referenced earlier is split into two:
//
// 1. index table, containing a fixed-size list of possible cache entries.
// Each index entry is of type IndexTableEntry (below), and holds the various
// accounting data and a pointer to the first page.
//
// 2. page table, which is used to speed up the process of searching for
// free pages of memory. There is one entry for every page in the page table,
// and it contains the index of the one entry in the index table actually
// holding the page (or <0 if the page is free).
//
// The entire segment looks like so:
// ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
// ? Header │ Index Table │ Page Table ? Pages │ │ │ │...?
// ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
// =========================================================================
// All elements of this struct must be "plain old data" (POD) types since it
// will be in shared memory. In addition, no pointers! To point to something
// you must use relative offsets since the pointer start addresses will be
// different in each process.
struct IndexTableEntry {
uint fileNameHash;
uint totalItemSize; // in bytes
mutable uint useCount;
time_t addTime;
mutable time_t lastUsedTime;
pageID firstPage;
};
// Page table entry
struct PageTableEntry {
// int so we can use values <0 for unassigned pages.
qint32 index;
};
// Each individual page contains the cached data. The first page starts off with
// the utf8-encoded key, a null '\0', and then the data follows immediately
// from the next byte, possibly crossing consecutive page boundaries to hold
// all of the data.
// There is, however, no specific struct for a page, it is simply a location in
// memory.
// This is effectively the layout of the shared memory segment. The variables
// contained within form the header, data contained afterwards is pointed to
// by using special accessor functions.
struct SharedMemory {
/**
* Note to downstream packagers: This version flag is intended to be
* machine-specific. The KDE-provided source code will not set the lower
* two bits to allow for distribution-specific needs, with the exception
* of version 1 which was already defined in KDE Platform 4.5.
* e.g. the next version bump will be from 4 to 8, then 12, etc.
*/
enum {
PIXMAP_CACHE_VERSION = 12,
MINIMUM_CACHE_SIZE = 4096,
};
/// The maximum number of probes to make while searching for a bucket in
/// the presence of collisions in the cache index table.
static const uint MAX_PROBE_COUNT = 6;
// Note to those who follow me. You should not, under any circumstances, ever
// re-arrange the following two fields, even if you change the version number
// for later revisions of this code.
QAtomicInt ready; ///< DO NOT INITIALIZE
quint8 version;
// See kshareddatacache_p.h
SharedLock shmLock;
uint cacheSize;
uint cacheAvail;
QAtomicInt evictionPolicy;
// pageSize and cacheSize determine the number of pages. The number of
// pages determine the page table size and (indirectly) the index table
// size.
QAtomicInt pageSize;
// This variable is added to reserve space for later cache timestamping
// support. The idea is this variable will be updated when the cache is
// written to, to allow clients to detect a changed cache quickly.
QAtomicInt cacheTimestamp;
/**
* Converts the given average item size into an appropriate page size.
*/
static unsigned equivalentPageSize(unsigned itemSize);
// Returns pageSize in unsigned format.
unsigned cachePageSize() const;
/**
* This is effectively the class ctor. But since we're in shared memory,
* there's a few rules:
*
* 1. To allow for some form of locking in the initial-setup case, we
* use an atomic int, which will be initialized to 0 by mmap(). Then to
* take the lock we atomically increment the 0 to 1. If we end up calling
* the QAtomicInt constructor we can mess that up, so we can't use a
* constructor for this class either.
* 2. Any member variable you add takes up space in shared memory as well,
* so make sure you need it.
*/
bool performInitialSetup(uint _cacheSize, uint _pageSize);
void clearInternalTables();
const IndexTableEntry *indexTable() const;
const PageTableEntry *pageTable() const;
const void *cachePages() const;
const void *page(pageID at) const;
// The following are non-const versions of some of the methods defined
// above. They use const_cast<> because I feel that is better than
// duplicating the code. I suppose template member functions (?)
// may work, may investigate later.
IndexTableEntry *indexTable();
PageTableEntry *pageTable();
void *cachePages();
void *page(pageID at);
uint pageTableSize() const;
uint indexTableSize() const;
/**
* @return the index of the first page, for the set of contiguous
* pages that can hold @p pagesNeeded PAGES.
*/
pageID findEmptyPages(uint pagesNeeded) const;
// left < right?
static bool lruCompare(const IndexTableEntry &l, const IndexTableEntry &r);
// left < right?
static bool seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r);
// left < right?
static bool ageCompare(const IndexTableEntry &l, const IndexTableEntry &r);
void defragment();
/**
* Finds the index entry for a given key.
* @param key UTF-8 encoded key to search for.
* @return The index of the entry in the cache named by @p key. Returns
* <0 if no such entry is present.
*/
qint32 findNamedEntry(const QByteArray &key) const;
// Function to use with std::unique_ptr in removeUsedPages below...
static void deleteTable(IndexTableEntry *table);
/**
* Removes the requested number of pages.
*
* @param numberNeeded the number of pages required to fulfill a current request.
* This number should be <0 and <= the number of pages in the cache.
* @return The identifier of the beginning of a consecutive block of pages able
* to fill the request. Returns a value >= pageTableSize() if no such
* request can be filled.
* @internal
*/
uint removeUsedPages(uint numberNeeded);
// Returns the total size required for a given cache size.
static uint totalSize(uint cacheSize, uint effectivePageSize);
uint fileNameHash(const QByteArray &utf8FileName) const;
void clear();
void removeEntry(uint index);
static quint32 generateHash(const QByteArray &buffer);
/**
* @return the smallest integer greater than or equal to (@p a / @p b).
* @param a Numerator, should be ≥ 0.
* @param b Denominator, should be > 0.
*/
static unsigned intCeil(unsigned a, unsigned b);
};
#endif /* KSDCMEMORY_P_H */
@@ -0,0 +1,483 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010, 2012 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2012 Ralf Jung <ralfjung-e@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kshareddatacache.h"
#include "kcoreaddons_debug.h"
#include "ksdcmapping_p.h"
#include "ksdcmemory_p.h"
#include "kshareddatacache_p.h" // Various auxiliary support code
#include <QByteArray>
#include <QDir>
#include <QFile>
#include <QRandomGenerator>
#include <QStandardPaths>
// The per-instance private data, such as map size, whether
// attached or not, pointer to shared memory, etc.
class Q_DECL_HIDDEN KSharedDataCache::Private
{
public:
Private(const QString &name, unsigned defaultCacheSize, unsigned expectedItemSize)
: m_cacheName(name)
, shm(nullptr)
, m_mapping(nullptr)
, m_defaultCacheSize(defaultCacheSize)
, m_expectedItemSize(expectedItemSize)
{
createMemoryMapping();
}
void createMemoryMapping()
{
shm = nullptr;
m_mapping.reset();
// 0-sized caches are fairly useless.
unsigned cacheSize = qMax(m_defaultCacheSize, uint(SharedMemory::MINIMUM_CACHE_SIZE));
unsigned pageSize = SharedMemory::equivalentPageSize(m_expectedItemSize);
// Ensure that the cache is sized such that there is a minimum number of
// pages available. (i.e. a cache consisting of only 1 page is fairly
// useless and probably crash-prone).
cacheSize = qMax(pageSize * 256, cacheSize);
// The m_cacheName is used to find the file to store the cache in.
const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
QString cacheName = cacheDir + QLatin1String("/") + m_cacheName + QLatin1String(".kcache");
QFile file(cacheName);
QFileInfo fileInfo(file);
if (!QDir().mkpath(fileInfo.absolutePath())) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to create cache dir" << fileInfo.absolutePath();
}
// The basic idea is to open the file that we want to map into shared
// memory, and then actually establish the mapping. Once we have mapped the
// file into shared memory we can close the file handle, the mapping will
// still be maintained (unless the file is resized to be shorter than
// expected, which we don't handle yet :-( )
// size accounts for the overhead over the desired cacheSize
uint size = SharedMemory::totalSize(cacheSize, pageSize);
Q_ASSERT(size >= cacheSize);
// Open the file and resize to some sane value if the file is too small.
if (file.open(QIODevice::ReadWrite) && (file.size() >= size || (ensureFileAllocated(file.handle(), size) && file.resize(size)))) {
try {
m_mapping.reset(new KSDCMapping(&file, size, cacheSize, pageSize));
shm = m_mapping->m_mapped;
} catch (KSDCCorrupted) {
shm = nullptr;
m_mapping.reset();
qCWarning(KCOREADDONS_DEBUG) << "Deleting corrupted cache" << cacheName;
file.remove();
QFile file(cacheName);
if (file.open(QIODevice::ReadWrite) && ensureFileAllocated(file.handle(), size) && file.resize(size)) {
try {
m_mapping.reset(new KSDCMapping(&file, size, cacheSize, pageSize));
} catch (KSDCCorrupted) {
m_mapping.reset();
qCCritical(KCOREADDONS_DEBUG) << "Even a brand-new cache starts off corrupted, something is"
<< "seriously wrong. :-(";
}
}
}
}
if (!m_mapping) {
m_mapping.reset(new KSDCMapping(nullptr, size, cacheSize, pageSize));
shm = m_mapping->m_mapped;
}
}
// Called whenever the cache is apparently corrupt (for instance, a timeout trying to
// lock the cache). In this situation it is safer just to destroy it all and try again.
void recoverCorruptedCache()
{
qCWarning(KCOREADDONS_DEBUG) << "Deleting corrupted cache" << m_cacheName;
KSharedDataCache::deleteCache(m_cacheName);
createMemoryMapping();
}
class CacheLocker
{
mutable Private *d;
bool cautiousLock()
{
int lockCount = 0;
// Locking can fail due to a timeout. If it happens too often even though
// we're taking corrective action assume there's some disastrous problem
// and give up.
while (!d->m_mapping->lock() && !d->m_mapping->isLockedCacheSafe()) {
d->recoverCorruptedCache();
if (!d->m_mapping->isValid()) {
qCWarning(KCOREADDONS_DEBUG) << "Lost the connection to shared memory for cache" << d->m_cacheName;
return false;
}
if (lockCount++ > 4) {
qCCritical(KCOREADDONS_DEBUG) << "There is a very serious problem with the KDE data cache" << d->m_cacheName
<< "giving up trying to access cache.";
return false;
}
}
return true;
}
public:
CacheLocker(const Private *_d)
: d(const_cast<Private *>(_d))
{
if (Q_UNLIKELY(!d || !cautiousLock())) {
d = nullptr;
}
}
~CacheLocker()
{
if (d) {
d->m_mapping->unlock();
}
}
CacheLocker(const CacheLocker &) = delete;
CacheLocker &operator=(const CacheLocker &) = delete;
bool failed() const
{
return !d;
}
};
QString m_cacheName;
SharedMemory *shm;
std::unique_ptr<KSDCMapping> m_mapping;
uint m_defaultCacheSize;
uint m_expectedItemSize;
};
KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize)
: d(nullptr)
{
try {
d = new Private(cacheName, defaultCacheSize, expectedItemSize);
} catch (KSDCCorrupted) {
qCCritical(KCOREADDONS_DEBUG) << "Failed to initialize KSharedDataCache!";
d = nullptr; // Just in case
}
}
KSharedDataCache::~KSharedDataCache()
{
if (!d) {
return;
}
delete d;
}
bool KSharedDataCache::insert(const QString &key, const QByteArray &data)
{
try {
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
QByteArray encodedKey = key.toUtf8();
uint keyHash = SharedMemory::generateHash(encodedKey);
uint position = keyHash % d->shm->indexTableSize();
// See if we're overwriting an existing entry.
IndexTableEntry *indices = d->shm->indexTable();
// In order to avoid the issue of a very long-lived cache having items
// with a use count of 1 near-permanently, we attempt to artifically
// reduce the use count of long-lived items when there is high load on
// the cache. We do this randomly, with a weighting that makes the event
// impossible if load < 0.5, and guaranteed if load >= 0.96.
const static double startCullPoint = 0.5l;
const static double mustCullPoint = 0.96l;
// cacheAvail is in pages, cacheSize is in bytes.
double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize() / d->shm->cacheSize);
bool cullCollisions = false;
if (Q_UNLIKELY(loadFactor >= mustCullPoint)) {
cullCollisions = true;
} else if (loadFactor > startCullPoint) {
const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint);
if (QRandomGenerator::global()->bounded(RAND_MAX) >= tripWireValue) {
cullCollisions = true;
}
}
// In case of collisions in the index table (i.e. identical positions), use
// quadratic chaining to attempt to find an empty slot. The equation we use
// is:
// position = (hash + (i + i*i) / 2) % size, where i is the probe number.
uint probeNumber = 1;
while (indices[position].useCount > 0 && probeNumber < SharedMemory::MAX_PROBE_COUNT) {
// If we actually stumbled upon an old version of the key we are
// overwriting, then use that position, do not skip over it.
if (Q_UNLIKELY(indices[position].fileNameHash == keyHash)) {
break;
}
// If we are "culling" old entries, see if this one is old and if so
// reduce its use count. If it reduces to zero then eliminate it and
// use its old spot.
if (cullCollisions && (::time(nullptr) - indices[position].lastUsedTime) > 60) {
indices[position].useCount >>= 1;
if (indices[position].useCount == 0) {
qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing old cached entry due to collision.";
d->shm->removeEntry(position); // Remove it first
break;
}
}
position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % d->shm->indexTableSize();
probeNumber++;
}
if (indices[position].useCount > 0 && indices[position].firstPage >= 0) {
qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing cached entry due to collision.";
d->shm->removeEntry(position); // Remove it first
}
// Data will be stored as fileNamefoo\0PNGimagedata.....
// So total size required is the length of the encoded file name + 1
// for the trailing null, and then the length of the image data.
uint fileNameLength = 1 + encodedKey.length();
uint requiredSize = fileNameLength + data.size();
uint pagesNeeded = SharedMemory::intCeil(requiredSize, d->shm->cachePageSize());
uint firstPage(-1);
if (pagesNeeded >= d->shm->pageTableSize()) {
qCWarning(KCOREADDONS_DEBUG) << key << "is too large to be cached.";
return false;
}
// If the cache has no room, or the fragmentation is too great to find
// the required number of consecutive free pages, take action.
if (pagesNeeded > d->shm->cacheAvail || (firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize()) {
// If we have enough free space just defragment
uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2);
if (d->shm->cacheAvail > freePagesDesired) {
// TODO: How the hell long does this actually take on real
// caches?
d->shm->defragment();
firstPage = d->shm->findEmptyPages(pagesNeeded);
} else {
// If we already have free pages we don't want to remove a ton
// extra. However we can't rely on the return value of
// removeUsedPages giving us a good location since we're not
// passing in the actual number of pages that we need.
d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize()) - d->shm->cacheAvail);
firstPage = d->shm->findEmptyPages(pagesNeeded);
}
if (firstPage >= d->shm->pageTableSize() || d->shm->cacheAvail < pagesNeeded) {
qCCritical(KCOREADDONS_DEBUG) << "Unable to free up memory for" << key;
return false;
}
}
// Update page table
PageTableEntry *table = d->shm->pageTable();
for (uint i = 0; i < pagesNeeded; ++i) {
table[firstPage + i].index = position;
}
// Update index
indices[position].fileNameHash = keyHash;
indices[position].totalItemSize = requiredSize;
indices[position].useCount = 1;
indices[position].addTime = ::time(nullptr);
indices[position].lastUsedTime = indices[position].addTime;
indices[position].firstPage = firstPage;
// Update cache
d->shm->cacheAvail -= pagesNeeded;
// Actually move the data in place
void *dataPage = d->shm->page(firstPage);
if (Q_UNLIKELY(!dataPage)) {
throw KSDCCorrupted();
}
// Verify it will all fit
d->m_mapping->verifyProposedMemoryAccess(dataPage, requiredSize);
// Cast for byte-sized pointer arithmetic
uchar *startOfPageData = reinterpret_cast<uchar *>(dataPage);
::memcpy(startOfPageData, encodedKey.constData(), fileNameLength);
::memcpy(startOfPageData + fileNameLength, data.constData(), data.size());
return true;
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
return false;
}
}
bool KSharedDataCache::find(const QString &key, QByteArray *destination) const
{
try {
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
// Search in the index for our data, hashed by key;
QByteArray encodedKey = key.toUtf8();
qint32 entry = d->shm->findNamedEntry(encodedKey);
if (entry >= 0) {
const IndexTableEntry *header = &d->shm->indexTable()[entry];
const void *resultPage = d->shm->page(header->firstPage);
if (Q_UNLIKELY(!resultPage)) {
throw KSDCCorrupted();
}
d->m_mapping->verifyProposedMemoryAccess(resultPage, header->totalItemSize);
header->useCount++;
header->lastUsedTime = ::time(nullptr);
// Our item is the key followed immediately by the data, so skip
// past the key.
const char *cacheData = reinterpret_cast<const char *>(resultPage);
cacheData += encodedKey.size();
cacheData++; // Skip trailing null -- now we're pointing to start of data
if (destination) {
*destination = QByteArray(cacheData, header->totalItemSize - encodedKey.size() - 1);
}
return true;
}
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
}
return false;
}
void KSharedDataCache::clear()
{
try {
Private::CacheLocker lock(d);
if (!lock.failed()) {
d->shm->clear();
}
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
}
}
bool KSharedDataCache::contains(const QString &key) const
{
try {
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
return d->shm->findNamedEntry(key.toUtf8()) >= 0;
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
return false;
}
}
void KSharedDataCache::deleteCache(const QString &cacheName)
{
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache");
// Note that it is important to simply unlink the file, and not truncate it
// smaller first to avoid SIGBUS errors and similar with shared memory
// attached to the underlying inode.
qCDebug(KCOREADDONS_DEBUG) << "Removing cache at" << cachePath;
QFile::remove(cachePath);
}
unsigned KSharedDataCache::totalSize() const
{
try {
Private::CacheLocker lock(d);
if (lock.failed()) {
return 0u;
}
return d->shm->cacheSize;
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
return 0u;
}
}
unsigned KSharedDataCache::freeSize() const
{
try {
Private::CacheLocker lock(d);
if (lock.failed()) {
return 0u;
}
return d->shm->cacheAvail * d->shm->cachePageSize();
} catch (KSDCCorrupted) {
d->recoverCorruptedCache();
return 0u;
}
}
KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const
{
if (d && d->shm) {
return static_cast<EvictionPolicy>(d->shm->evictionPolicy.fetchAndAddAcquire(0));
}
return NoEvictionPreference;
}
void KSharedDataCache::setEvictionPolicy(EvictionPolicy newPolicy)
{
if (d && d->shm) {
d->shm->evictionPolicy.fetchAndStoreRelease(static_cast<int>(newPolicy));
}
}
unsigned KSharedDataCache::timestamp() const
{
if (d && d->shm) {
return static_cast<unsigned>(d->shm->cacheTimestamp.fetchAndAddAcquire(0));
}
return 0;
}
void KSharedDataCache::setTimestamp(unsigned newTimestamp)
{
if (d && d->shm) {
d->shm->cacheTimestamp.fetchAndStoreRelease(static_cast<int>(newTimestamp));
}
}
@@ -0,0 +1,213 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSHAREDDATACACHE_H
#define KSHAREDDATACACHE_H
#include <kcoreaddons_export.h>
class QString;
class QByteArray;
/**
* @class KSharedDataCache kshareddatacache.h KSharedDataCache
*
* @brief A simple data cache which uses shared memory to quickly access data
* stored on disk.
*
* This class is meant to be used with KImageCache and similar classes but can
* be used directly if used with care.
*
* Example usage:
*
* @code
* QString loadTranslatedDocument(KSharedDataCache *cache) {
*
* // Find the data
* QByteArray document;
*
* if (!cache->find("translated-doc-template", &document)) {
* // Entry is not cached, manually generate and then add to cache.
* document = translateDocument(globalTemplate());
* cache->insert(document);
* }
*
* // Don't forget to encode/decode properly
* return QString::fromUtf8(document);
* }
* @endcode
*
* @author Michael Pyne <mpyne@kde.org>
* @see KImageCache
* @since 4.5
*/
class KCOREADDONS_EXPORT KSharedDataCache
{
public:
/**
* Attaches to a shared cache, creating it if necessary. If supported, this
* data cache will be shared across all processes using this cache (with
* subsequent memory savings). If shared memory is unsupported or a
* failure occurs, caching will still be supported, but only in the same
* process, and only using the same KSharedDataCache object.
*
* @param cacheName Name of the cache to use/share.
* @param defaultCacheSize Amount of data to be able to store, in bytes. The
* actual size will be slightly larger on disk due to accounting
* overhead. If the cache already existed then it <em>will not</em> be
* resized. For this reason you should specify some reasonable size.
* @param expectedItemSize The average size of an item that would be stored
* in the cache, in bytes. Choosing an average size of zero bytes causes
* KSharedDataCache to use whatever it feels is the best default for the
* system.
*/
KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize = 0);
~KSharedDataCache();
KSharedDataCache(const KSharedDataCache &) = delete;
KSharedDataCache &operator=(const KSharedDataCache &) = delete;
enum EvictionPolicy {
// The default value for data in our shared memory will be 0, so it is
// important that whatever we want for the default value is also 0.
NoEvictionPreference = 0,
EvictLeastRecentlyUsed,
EvictLeastOftenUsed,
EvictOldest,
};
/**
* @return The removal policy in use by the shared cache.
* @see EvictionPolicy
*/
EvictionPolicy evictionPolicy() const;
/**
* Sets the entry removal policy for the shared cache to
* @p newPolicy. The default is EvictionPolicy::NoEvictionPreference.
*
* @see EvictionPolicy
*/
void setEvictionPolicy(EvictionPolicy newPolicy);
/**
* Attempts to insert the entry @p data into the shared cache, named by
* @p key, and returns true only if successful.
*
* Note that even if the insert was successful, that the newly added entry
* may be evicted by other processes contending for the cache.
*/
bool insert(const QString &key, const QByteArray &data);
/**
* Returns the data in the cache named by @p key (even if it's some other
* process's data named with the same key!), stored in @p destination. If there is
* no entry named by @p key then @p destination is left unchanged. The return value
* is used to tell what happened.
*
* If you simply want to verify whether an entry is present in the cache then
* see contains().
*
* @param key The key to find in the cache.
* @param destination Is set to the value of @p key in the cache if @p key is
* present, left unchanged otherwise.
* @return true if @p key was present in the cache (@p destination will also be
* updated), false if @p key was not present (@p destination will be
* unchanged).
*/
bool find(const QString &key, QByteArray *destination) const;
/**
* Removes all entries from the cache.
*/
void clear();
/**
* Removes the underlying file from the cache. Note that this is *all* that this
* function does. The shared memory segment is still attached and will still contain
* all the data until all processes currently attached remove the mapping.
*
* In order to remove the data see clear().
*/
static void deleteCache(const QString &cacheName);
/**
* Returns true if the cache currently contains the image for the given
* filename.
*
* NOTE: Calling this function is threadsafe, but it is in general not
* possible to guarantee the image stays cached immediately afterwards,
* so if you need the result use find().
*/
bool contains(const QString &key) const;
/**
* Returns the usable cache size in bytes. The actual amount of memory
* used will be slightly larger than this to account for required
* accounting overhead.
*/
unsigned totalSize() const;
/**
* Returns the amount of free space in the cache, in bytes. Due to
* implementation details it is possible to still not be able to fit an
* entry in the cache at any given time even if it is smaller than the
* amount of space remaining.
*/
unsigned freeSize() const;
/**
* @return The shared timestamp of the cache. The interpretation of the
* timestamp returned is up to the application. KSharedDataCache
* will initialize the timestamp to the time returned by @c time(2)
* on cache creation, but KSharedDataCache will not touch the
* timestamp again.
* @see setTimestamp()
* @since 4.6
*/
unsigned timestamp() const;
/**
* Sets the shared timestamp of the cache. Timestamping is supported to
* allow applications to more effectively "version" the data stored in the
* cache. However, the timestamp is shared with <em>all</em> applications
* using the cache so you should always be prepared for invalid
* timestamps.
*
* When the cache is first created (note that this is different from
* attaching to an existing shared cache on disk), the cache timestamp is
* initialized to the time returned by @c time(2). KSharedDataCache will
* not update the timestamp again, it is only updated through this method.
*
* Example:
* @code
* QImage loadCachedImage(const QString &key)
* {
* // Check timestamp
* if (m_sharedCache->timestamp() < m_currentThemeTimestamp) {
* // Cache is stale, clean it out.
* m_sharedCache->clear();
* m_sharedCache->setTimestamp(m_currentThemeTimestamp);
* }
*
* // Check cache and load image as usual...
* }
* @endcode
*
* @param newTimestamp The new timestamp to mark the shared cache with.
* @see timestamp()
* @since 4.6
*/
void setTimestamp(unsigned newTimestamp);
private:
class Private;
Private *d;
};
#endif
@@ -0,0 +1,66 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSHAREDDATACACHE_P_H
#define KSHAREDDATACACHE_P_H
#include "kcoreaddons_debug.h"
#include <cerrno>
#include <fcntl.h>
#include <unistd.h> // for _POSIX_ADVISORY_INFO
// posix_fallocate is used to ensure that the file used for the cache is
// actually fully committed to disk before attempting to use the file.
#if defined(_POSIX_ADVISORY_INFO) && ((_POSIX_ADVISORY_INFO == 0) || (_POSIX_ADVISORY_INFO >= 200112L))
#define KSDC_POSIX_FALLOCATE_SUPPORTED 1
#endif
#ifdef Q_OS_OSX
#include "posix_fallocate_mac.h"
#define KSDC_POSIX_FALLOCATE_SUPPORTED 1
#endif
static bool ensureFileAllocated(int fd, size_t fileSize)
{
#ifdef KSDC_POSIX_FALLOCATE_SUPPORTED
int result;
while ((result = ::posix_fallocate(fd, 0, fileSize)) == EINTR) {
;
}
if (result != 0) {
if (result == ENOSPC) {
qCCritical(KCOREADDONS_DEBUG) << "No space left on device. Check filesystem free space at your XDG_CACHE_HOME!";
}
qCCritical(KCOREADDONS_DEBUG) << "The operating system is unable to promise" << fileSize
<< "bytes for mapped cache, "
"abandoning the cache for crash-safety.";
return false;
}
return true;
#else
#ifdef __GNUC__
#warning \
"This system does not seem to support posix_fallocate, which is needed to ensure KSharedDataCache's underlying files are fully committed to disk to avoid crashes with low disk space."
#endif
qCWarning(KCOREADDONS_DEBUG) << "This system misses support for posix_fallocate()"
" -- ensure this partition has room for at least"
<< fileSize << "bytes.";
// TODO: It's possible to emulate the functionality, but doing so
// overwrites the data in the file so we don't do this. If you were to add
// this emulation, you must ensure it only happens on initial creation of a
// new file and not just mapping an existing cache.
return true;
#endif // KSDC_POSIX_FALLOCATE_SUPPORTED
}
#endif /* KSHAREDDATACACHE_P_H */
@@ -0,0 +1,107 @@
/*
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
/**
* This is a horrifically simple implementation of KSharedDataCache that is
* basically missing the "shared" part to it, for use on Windows or other platforms
* that don't support POSIX.
*/
#include "kshareddatacache.h"
#include <QByteArray>
#include <QCache>
#include <QString>
class Q_DECL_HIDDEN KSharedDataCache::Private
{
public:
KSharedDataCache::EvictionPolicy evictionPolicy;
QCache<QString, QByteArray> cache;
};
KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize)
: d(new Private)
{
d->cache.setMaxCost(defaultCacheSize);
Q_UNUSED(cacheName);
Q_UNUSED(expectedItemSize);
}
KSharedDataCache::~KSharedDataCache()
{
delete d;
}
KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const
{
return d->evictionPolicy;
}
void KSharedDataCache::setEvictionPolicy(KSharedDataCache::EvictionPolicy newPolicy)
{
d->evictionPolicy = newPolicy;
}
bool KSharedDataCache::insert(const QString &key, const QByteArray &data)
{
return d->cache.insert(key, new QByteArray(data));
}
bool KSharedDataCache::find(const QString &key, QByteArray *destination) const
{
QByteArray *value = d->cache.object(key);
if (value) {
if (destination) {
*destination = *value;
}
return true;
} else {
return false;
}
}
void KSharedDataCache::clear()
{
d->cache.clear();
}
void KSharedDataCache::deleteCache(const QString &cacheName)
{
Q_UNUSED(cacheName);
}
bool KSharedDataCache::contains(const QString &key) const
{
return d->cache.contains(key);
}
unsigned KSharedDataCache::totalSize() const
{
return static_cast<unsigned>(d->cache.maxCost());
}
unsigned KSharedDataCache::freeSize() const
{
if (d->cache.totalCost() < d->cache.maxCost()) {
return static_cast<unsigned>(d->cache.maxCost() - d->cache.totalCost());
} else {
return 0;
}
}
unsigned KSharedDataCache::timestamp() const
{
return 0;
}
void KSharedDataCache::setTimestamp(unsigned newTimestamp)
{
Q_UNUSED(newTimestamp);
}
@@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
SPDX-FileCopyrightText: 2010 Mozilla Foundation
SPDX-FileContributor: Taras Glek <tglek@mozilla.com>
SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later
*/
#ifndef POSIX_FALLOCATE_MAC_H
#define POSIX_FALLOCATE_MAC_H
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// created from the OSX-specific code from Mozilla's mozilla::fallocation() function
// of which the licensing information is copied above.
// Adaptation (C) 2015,2016 R.J.V. Bertin
// From Linux `man posix_fallocate`:
// DESCRIPTION
// The function posix_fallocate() ensures that disk space is allocated for
// the file referred to by the descriptor fd for the bytes in the range
// starting at offset and continuing for len bytes. After a successful
// call to posix_fallocate(), subsequent writes to bytes in the specified
// range are guaranteed not to fail because of lack of disk space.
//
// If the size of the file is less than offset+len, then the file is
// increased to this size; otherwise the file size is left unchanged.
// From OS X man fcntl:
// F_PREALLOCATE Preallocate file storage space. Note: upon success, the space
// that is allocated can be the same size or larger than the space
// requested.
// The F_PREALLOCATE command operates on the following structure:
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
// F_ALLOCATECONTIG Allocate contiguous space.
// F_ALLOCATEALL Allocate all requested space or no space at all.
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use
// the offset field. The modes are as follows:
// F_PEOFPOSMODE Allocate from the physical end of file.
// F_VOLPOSMODE Allocate from the volume offset.
// From OS X man ftruncate:
// DESCRIPTION
// ftruncate() and truncate() cause the file named by path, or referenced by fildes, to
// be truncated (or extended) to length bytes in size. If the file size exceeds length,
// any extra data is discarded. If the file size is smaller than length, the file
// extended and filled with zeros to the indicated length. The ftruncate() form requires
// the file to be open for writing.
// Note: ftruncate() and truncate() do not modify the current file offset for any open
// file descriptions associated with the file.
static int posix_fallocate(int fd, off_t offset, off_t len)
{
off_t c_test;
int ret;
if (!__builtin_saddll_overflow(offset, len, &c_test)) {
fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, offset + len, 0};
// Try to get a continuous chunk of disk space
ret = fcntl(fd, F_PREALLOCATE, &store);
if (ret < 0) {
// OK, perhaps we are too fragmented, allocate non-continuous
store.fst_flags = F_ALLOCATEALL;
ret = fcntl(fd, F_PREALLOCATE, &store);
if (ret < 0) {
return ret;
}
}
ret = ftruncate(fd, offset + len);
} else {
// offset+len would overflow.
ret = -1;
}
return ret;
}
#endif
@@ -0,0 +1,3 @@
#cmakedefine01 HAVE_SYS_INOTIFY_H
#cmakedefine01 HAVE_QTDBUS
@@ -0,0 +1 @@
#cmakedefine01 HAVE_UDEV
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
// SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
#ifndef DBUSTYPES_P_H
#define DBUSTYPES_P_H
#include <QDBusUnixFileDescriptor>
#include <QList>
using FDList = QList<QDBusUnixFileDescriptor>;
#endif // DBUSTYPES_P_H
@@ -0,0 +1,232 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shafff@ukr.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kautosavefile.h"
#include <climits> // for NAME_MAX
#ifdef Q_OS_WIN
#include <stdlib.h> // for _MAX_FNAME
static const int maxNameLength = _MAX_FNAME;
#else
static const int maxNameLength = NAME_MAX;
#endif
#include "kcoreaddons_debug.h"
#include "krandom.h"
#include <QCoreApplication>
#include <QDir>
#include <QLatin1Char>
#include <QLockFile>
#include <QStandardPaths>
class KAutoSaveFilePrivate
{
public:
enum {
NamePadding = 8,
};
QString tempFileName();
QUrl managedFile;
QLockFile *lock = nullptr;
bool managedFileNameChanged = false;
};
static QStringList findAllStales(const QString &appName)
{
const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
QStringList files;
const QString suffix = QLatin1String("/stalefiles/") + appName;
for (const QString &dir : dirs) {
QDir appDir(dir + suffix);
const QString absPath = appDir.absolutePath() + QLatin1Char('/');
qCDebug(KCOREADDONS_DEBUG) << "Looking in" << appDir.absolutePath();
QStringList listFiles = appDir.entryList(QDir::Files);
for (QString &file : listFiles) {
file.prepend(absPath);
}
files += listFiles;
}
return files;
}
QString KAutoSaveFilePrivate::tempFileName()
{
// Note: we drop any query string and user/pass info
const QString protocol(managedFile.scheme());
const QByteArray encodedDirectory = QUrl::toPercentEncoding(managedFile.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
const QString directory = QString::fromLatin1(encodedDirectory);
const QByteArray encodedFileName = QUrl::toPercentEncoding(managedFile.fileName());
QString fileName = QString::fromLatin1(encodedFileName);
// Remove any part of the path to the right if it is longer than the maximum file name length;
// note that "file name" in this context means the file name component only (e.g. test.txt), and
// not the whole path (e.g. /home/simba/text.txt).
// Ensure that the max. file name length takes into account the other parts of the tempFileName
// Subtract 1 for the _ char, 3 for the padding separator, 5 is for the .lock,
// 7 for QLockFile's internal code (adding tmp .rmlock) = 16
const int pathLengthLimit = maxNameLength - NamePadding - fileName.size() - protocol.size() - 16;
const QString junk = KRandom::randomString(NamePadding);
// This is done so that the separation between the filename and path can be determined
fileName += QStringView(junk).right(3) + protocol + QLatin1Char('_') + QStringView(directory).left(pathLengthLimit) + junk;
return fileName;
}
KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent)
: QFile(parent)
, d(new KAutoSaveFilePrivate)
{
setManagedFile(filename);
}
KAutoSaveFile::KAutoSaveFile(QObject *parent)
: QFile(parent)
, d(new KAutoSaveFilePrivate)
{
}
KAutoSaveFile::~KAutoSaveFile()
{
releaseLock();
delete d->lock;
}
QUrl KAutoSaveFile::managedFile() const
{
return d->managedFile;
}
void KAutoSaveFile::setManagedFile(const QUrl &filename)
{
releaseLock();
d->managedFile = filename;
d->managedFileNameChanged = true;
}
void KAutoSaveFile::releaseLock()
{
if (d->lock && d->lock->isLocked()) {
delete d->lock;
d->lock = nullptr;
if (!fileName().isEmpty()) {
remove();
}
}
}
bool KAutoSaveFile::open(OpenMode openmode)
{
if (d->managedFile.isEmpty()) {
return false;
}
QString tempFile;
if (d->managedFileNameChanged) {
QString staleFilesDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/stalefiles/")
+ QCoreApplication::instance()->applicationName();
if (!QDir().mkpath(staleFilesDir)) {
return false;
}
tempFile = staleFilesDir + QChar::fromLatin1('/') + d->tempFileName();
} else {
tempFile = fileName();
}
d->managedFileNameChanged = false;
setFileName(tempFile);
if (QFile::open(openmode)) {
if (!d->lock) {
d->lock = new QLockFile(tempFile + QLatin1String(".lock"));
d->lock->setStaleLockTime(60 * 1000); // HARDCODE, 1 minute
}
if (d->lock->isLocked() || d->lock->tryLock()) {
return true;
} else {
qCWarning(KCOREADDONS_DEBUG) << "Could not lock file:" << tempFile;
close();
}
}
return false;
}
static QUrl extractManagedFilePath(const QString &staleFileName)
{
const QStringView stale{staleFileName};
// Warning, if we had a long path, it was truncated by tempFileName()
// So in that case, extractManagedFilePath will return an incorrect truncated path for original source
const auto sep = stale.right(3);
const int sepPos = staleFileName.indexOf(sep);
const QByteArray managedFilename = stale.left(sepPos).toLatin1();
const int pathPos = staleFileName.indexOf(QChar::fromLatin1('_'), sepPos);
QUrl managedFileName;
// name.setScheme(file.mid(sepPos + 3, pathPos - sep.size() - 3));
const QByteArray encodedPath = stale.mid(pathPos + 1, staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
managedFileName.setPath(QUrl::fromPercentEncoding(encodedPath) + QLatin1Char('/') + QFileInfo(QUrl::fromPercentEncoding(managedFilename)).fileName());
return managedFileName;
}
bool staleMatchesManaged(const QString &staleFileName, const QUrl &managedFile)
{
const QStringView stale{staleFileName};
const auto sep = stale.right(3);
int sepPos = staleFileName.indexOf(sep);
// Check filenames first
if (managedFile.fileName() != QUrl::fromPercentEncoding(stale.left(sepPos).toLatin1())) {
return false;
}
// Check paths
const int pathPos = staleFileName.indexOf(QChar::fromLatin1('_'), sepPos);
const QByteArray encodedPath = stale.mid(pathPos + 1, staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
return QUrl::toPercentEncoding(managedFile.path()).startsWith(encodedPath);
}
QList<KAutoSaveFile *> KAutoSaveFile::staleFiles(const QUrl &filename, const QString &applicationName)
{
QString appName(applicationName);
if (appName.isEmpty()) {
appName = QCoreApplication::instance()->applicationName();
}
// get stale files
const QStringList files = findAllStales(appName);
QList<KAutoSaveFile *> list;
// contruct a KAutoSaveFile for stale files corresponding given filename
for (const QString &file : files) {
if (file.endsWith(QLatin1String(".lock")) || (!filename.isEmpty() && !staleMatchesManaged(QFileInfo(file).fileName(), filename))) {
continue;
}
// sets managedFile
KAutoSaveFile *asFile = new KAutoSaveFile(filename.isEmpty() ? extractManagedFilePath(file) : filename);
asFile->setFileName(file);
asFile->d->managedFileNameChanged = false; // do not regenerate tempfile name
list.append(asFile);
}
return list;
}
QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName)
{
return staleFiles(QUrl(), applicationName);
}
#include "moc_kautosavefile.cpp"
@@ -0,0 +1,246 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KAUTOSAVEFILE_H
#define KAUTOSAVEFILE_H
#include <kcoreaddons_export.h>
#include <QFile>
#include <QList>
#include <QUrl>
#include <memory>
class KAutoSaveFilePrivate;
/**
* \class KAutoSaveFile kautosavefile.h <KAutoSaveFile>
*
* @brief Creates and manages a temporary "auto-save" file.
* Autosave files are temporary files that applications use to store
* the unsaved data in a file they have open for
* editing. KAutoSaveFile allows you to easily create and manage such
* files, as well as to recover the unsaved data left over by a
* crashed or otherwise gone process.
*
* Each KAutoSaveFile object is associated with one specific file that
* the application holds open. KAutoSaveFile is also a QObject, so it
* can be reparented to the actual opened file object, so as to manage
* the lifetime of the temporary file.
*
* Typical use consists of:
* - verifying whether stale autosave files exist for the opened file
* - deciding whether to recover the old, autosaved data
* - if not recovering, creating a KAutoSaveFile object for the opened file
* - during normal execution of the program, periodically save unsaved
* data into the KAutoSaveFile file.
*
* KAutoSaveFile holds a lock on the autosave file, so it's safe to
* delete the file and recreate it later. Because of that, disposing
* of stale autosave files should be done with releaseLock(). No lock is
* held on the managed file.
*
* Examples:
* Opening a new file:
* @code
* void Document::open(const QUrl &url)
* {
* // check whether autosave files exist:
* const QList<KAutoSaveFile *> staleFiles = KAutoSaveFile::staleFiles(url);
* if (!staleFiles.isEmpty()) {
* if (KMessageBox::questionTwoActions(parent,
* "Auto-saved files exist. Do you want to recover them now?",
* "File Recovery",
* KGuiItem("Recover"), KGuiItem("Do Not recover")) == KMessageBox::PrimaryAction) {
* recoverFiles(staleFiles);
* return;
* } else {
* // remove the stale files
* for (KAutoSaveFile *stale : staleFiles) {
* stale->open(QIODevice::ReadWrite);
* delete stale;
* }
* }
* }
*
* // create new autosave object
* m_autosave = new KAutoSaveFile(url, this);
*
* // continue the process of opening file 'url'
* ...
* }
* @endcode
*
* The function recoverFiles could loop over the list of files and do this:
* @code
* for (KAutoSaveFile *stale : staleFiles) {
* if (!stale->open(QIODevice::ReadWrite)) {
* // show an error message; we could not steal the lockfile
* // maybe another application got to the file before us?
* delete stale;
* continue;
* }
* Document *doc = new Document;
* doc->m_autosave = stale;
* stale->setParent(doc); // reparent
*
* doc->setUrl(stale->managedFile());
* doc->setContents(stale->readAll());
* doc->setState(Document::Modified); // mark it as modified and unsaved
*
* documentManager->addDocument(doc);
* }
* @endcode
*
* If the file is unsaved, periodically write the contents to the save file:
* @code
* if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) {
* // show error: could not open the autosave file
* }
* m_autosave->write(contents());
* @endcode
*
* When the user saves the file, the autosaved file is no longer
* necessary and can be removed or emptied.
* @code
* m_autosave->resize(0); // leaves the file open
* @endcode
*
* @code
* m_autosave->remove(); // closes the file
* @endcode
*
* @author Jacob R Rideout <kde@jacobrideout.net>
*/
class KCOREADDONS_EXPORT KAutoSaveFile : public QFile
{
Q_OBJECT
public:
/**
* Constructs a KAutoSaveFile for file @p filename. The temporary
* file is not opened or created until actually needed. The file
* @p filename does not have to exist for KAutoSaveFile to be
* constructed (if it exists, it will not be touched).
*
* @param filename the filename that this KAutoSaveFile refers to
* @param parent the parent object
*/
explicit KAutoSaveFile(const QUrl &filename, QObject *parent = nullptr);
/**
* @overload
* Constructs a KAutoSaveFile object. Note that you need to call
* setManagedFile() before calling open().
*
* @param parent the parent object
*/
explicit KAutoSaveFile(QObject *parent = nullptr);
/**
* Destroys the KAutoSaveFile object, removes the autosave
* file and drops the lock being held (if any).
*/
~KAutoSaveFile() override;
/**
* Retrieves the URL of the file managed by KAutoSaveFile. This
* is the same URL that was given to setManagedFile() or the
* KAutoSaveFile constructor.
*
* This is the name of the real file being edited by the
* application. To get the name of the temporary file where data
* can be saved, use fileName() (after you have called open()).
*/
QUrl managedFile() const;
/**
* Sets the URL of the file managed by KAutoSaveFile. This should
* be the name of the real file being edited by the application.
* If the file was previously set, this function calls releaseLock().
*
* @param filename the filename that this KAutoSaveFile refers to
*/
void setManagedFile(const QUrl &filename);
/**
* Closes the autosave file resource and removes the lock
* file. The file name returned by fileName() will no longer be
* protected and can be overwritten by another application at any
* time. To obtain a new lock, call open() again.
*
* This function calls remove(), so the autosave temporary file
* will be removed too.
*/
virtual void releaseLock();
/**
* Opens the autosave file and locks it if it wasn't already
* locked. The name of the temporary file where data can be saved
* to will be set by this function and can be retrieved with
* fileName(). It will not change unless releaseLock() is called. No
* other application will attempt to edit such a file either while
* the lock is held.
*
* @param openmode the mode that should be used to open the file,
* probably QIODevice::ReadWrite
* @returns true if the file could be opened (= locked and
* created), false if the operation failed
*/
bool open(OpenMode openmode) override;
/**
* Checks for stale autosave files for the file @p url. Returns a list
* of autosave files that contain autosaved data left behind by
* other instances of the application, due to crashing or
* otherwise uncleanly exiting.
*
* It is the application's job to determine what to do with such
* unsaved data. Generally, this is done by asking the user if he
* wants to see the recovered data, and then allowing the user to
* save if he wants to.
*
* If not given, the application name is obtained from
* QCoreApplication, so be sure to have set it correctly before
* calling this function.
*
* This function returns a list of unopened KAutoSaveFile
* objects. By calling open() on them, the application will steal
* the lock. Subsequent releaseLock() or deleting of the object will
* then erase the stale autosave file.
*
* The application owns all returned KAutoSaveFile objects and is
* responsible for deleting them when no longer needed. Remember that
* deleting the KAutoSaveFile will release the file lock and remove the
* stale autosave file.
*/
static QList<KAutoSaveFile *> staleFiles(const QUrl &url, const QString &applicationName = QString());
/**
* Returns all stale autosave files left behind by crashed or
* otherwise gone instances of this application.
*
* If not given, the application name is obtained from
* QCoreApplication, so be sure to have set it correctly before
* calling this function.
*
* See staleFiles() for information on the returned objects.
*
* The application owns all returned KAutoSaveFile objects and is
* responsible for deleting them when no longer needed. Remember that
* deleting the KAutoSaveFile will release the file lock and remove the
* stale autosave file.
*/
static QList<KAutoSaveFile *> allStaleFiles(const QString &applicationName = QString());
private:
Q_DISABLE_COPY(KAutoSaveFile)
friend class KAutoSaveFilePrivate;
std::unique_ptr<KAutoSaveFilePrivate> const d;
};
#endif // KAUTOSAVEFILE_H
@@ -0,0 +1,91 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2006 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2006 Gregory S. Hayes <syncomm@kde.org>
SPDX-FileCopyrightText: 2006 Jaison Lee <lee.jaison@gmail.com>
SPDX-FileCopyrightText: 2011 Romain Perier <bambi@ubuntu.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kbackup.h"
#include <QDir>
#include <QFileInfo>
namespace KBackup
{
bool simpleBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension)
{
QString backupFileName = qFilename + backupExtension;
if (!backupDir.isEmpty()) {
QFileInfo fileInfo(qFilename);
backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension;
}
// qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << backupFileName;
QFile::remove(backupFileName);
return QFile::copy(qFilename, backupFileName);
}
bool numberedBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension, const uint maxBackups)
{
QFileInfo fileInfo(qFilename);
// The backup file name template.
QString sTemplate;
if (backupDir.isEmpty()) {
sTemplate = qFilename + QLatin1String(".%1") + backupExtension;
} else {
sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension;
}
// First, search backupDir for numbered backup files to remove.
// Remove all with number 'maxBackups' and greater.
QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir;
d.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
const QStringList nameFilters = QStringList(fileInfo.fileName() + QLatin1String(".*") + backupExtension);
d.setNameFilters(nameFilters);
d.setSorting(QDir::Name);
uint maxBackupFound = 0;
const QFileInfoList infoList = d.entryInfoList();
for (const QFileInfo &fi : infoList) {
if (fi.fileName().endsWith(backupExtension)) {
// sTemp holds the file name, without the ending backupExtension
QString sTemp = fi.fileName();
sTemp.truncate(fi.fileName().length() - backupExtension.length());
// compute the backup number
int idex = sTemp.lastIndexOf(QLatin1Char('.'));
if (idex > 0) {
bool ok;
const uint num = QStringView(sTemp).mid(idex + 1).toUInt(&ok);
if (ok) {
if (num >= maxBackups) {
QFile::remove(fi.filePath());
} else {
maxBackupFound = qMax(maxBackupFound, num);
}
}
}
}
}
// Next, rename max-1 to max, max-2 to max-1, etc.
QString to = sTemplate.arg(maxBackupFound + 1);
for (int i = maxBackupFound; i > 0; i--) {
QString from = sTemplate.arg(i);
// qCDebug(KCOREADDONS_DEBUG) << "KBackup renaming " << from << " to " << to;
QFile::rename(from, to);
to = from;
}
// Finally create most recent backup by copying the file to backup number 1.
// qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << sTemplate.arg(1);
return QFile::copy(qFilename, sTemplate.arg(1));
}
}
@@ -0,0 +1,69 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2006 Jaison Lee <lee.jaison@gmail.com>
SPDX-FileCopyrightText: 2011 Romain Perier <bambi@ubuntu.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KBACKUP_H
#define KBACKUP_H
#include <QString>
#include <kcoreaddons_export.h>
/**
* @namespace KBackup
* Provides utility functions for backup of files.
*/
namespace KBackup
{
/**
* @brief Function to create a backup file for a given filename.
*
* This function creates a backup file from the given filename.
* You can use this method even if you don't use KSaveFile.
* @param filename the file to backup
* @param backupDir optional directory where to save the backup file in.
* If empty (the default), the backup will be in the same directory as @p filename.
* @param backupExtension the extension to append to @p filename, "~" by default.
* @return true if successful, or false if an error has occurred.
*/
KCOREADDONS_EXPORT bool simpleBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupExtension = QStringLiteral("~"));
/**
* @brief Function to create a backup file for a given filename.
*
* This function creates a series of numbered backup files from the
* given filename.
*
* The backup file names will be of the form:
* \<name\>.\<number\>\<extension\>
* for instance
* \verbatim chat.3.log \endverbatim
*
* The new backup file will be have the backup number 1.
* Each existing backup file will have its number incremented by 1.
* Any backup files with numbers greater than the maximum number
* permitted (@p maxBackups) will be removed.
* You can use this method even if you don't use KSaveFile.
*
* @param filename the file to backup
* @param backupDir optional directory where to save the backup file in.
* If empty (the default), the backup will be in the same directory as
* @p filename.
* @param backupExtension the extension to append to @p filename,
* which is "~" by default. Do not use an extension containing digits.
* @param maxBackups the maximum number of backup files permitted.
* For best performance a small number (10) is recommended.
* @return true if successful, or false if an error has occurred.
*/
KCOREADDONS_EXPORT bool numberedBackupFile(const QString &filename,
const QString &backupDir = QString(),
const QString &backupExtension = QStringLiteral("~"),
const uint maxBackups = 10);
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,311 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef _KDIRWATCH_H
#define _KDIRWATCH_H
#include <QDateTime>
#include <QObject>
#include <QString>
#include <kcoreaddons_export.h>
class KDirWatchPrivate;
/**
* @class KDirWatch kdirwatch.h KDirWatch
*
* @short Class for watching directory and file changes.
*
* Watch directories and files for changes.
* The watched directories or files don't have to exist yet.
*
* When a watched directory is changed, i.e. when files therein are
* created or deleted, KDirWatch will emit the signal dirty().
*
* When a watched, but previously not existing directory gets created,
* KDirWatch will emit the signal created().
*
* When a watched directory gets deleted, KDirWatch will emit the
* signal deleted(). The directory is still watched for new
* creation.
*
* When a watched file is changed, i.e. attributes changed or written
* to, KDirWatch will emit the signal dirty().
*
* Scanning of particular directories or files can be stopped temporarily
* and restarted. The whole class can be stopped and restarted.
* Directories and files can be added/removed from the list in any state.
*
* The implementation uses the INOTIFY functionality on LINUX.
* As a last resort, a regular polling for change of modification times
* is done; the polling interval is a global config option:
* DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted
* directories.
* The choice of implementation can be adjusted by the user, with the key
* [DirWatch] PreferredMethod={Stat|QFSWatch|inotify}
*
* @see self()
* @author Sven Radej (in 1998)
*/
class KCOREADDONS_EXPORT KDirWatch : public QObject
{
Q_OBJECT
public:
/**
* Available watch modes for directory monitoring
* @see WatchModes
**/
enum WatchMode {
WatchDirOnly = 0, ///< Watch just the specified directory
WatchFiles = 0x01, ///< Watch also all files contained by the directory
WatchSubDirs = 0x02, ///< Watch also all the subdirs contained by the directory
};
/**
* Stores a combination of #WatchMode values.
*/
Q_DECLARE_FLAGS(WatchModes, WatchMode)
/**
* Constructor.
*
* Scanning begins immediately when a dir/file watch
* is added.
* @param parent the parent of the QObject (or @c nullptr for parent-less KDataTools)
*/
explicit KDirWatch(QObject *parent = nullptr);
/**
* Destructor.
*
* Stops scanning and cleans up.
*/
~KDirWatch() override;
/**
* Adds a directory to be watched.
*
* The directory does not have to exist. When @p watchModes is set to
* WatchDirOnly (the default), the signals dirty(), created(), deleted()
* can be emitted, all for the watched directory.
* When @p watchModes is set to WatchFiles, all files in the watched
* directory are watched for changes, too. Thus, the signals dirty(),
* created(), deleted() can be emitted.
* When @p watchModes is set to WatchSubDirs, all subdirs are watched using
* the same flags specified in @p watchModes (symlinks aren't followed).
* If the @p path points to a symlink to a directory, the target directory
* is watched instead. If you want to watch the link, use @p addFile().
*
* @param path the path to watch
* @param watchModes watch modes
*
* @sa KDirWatch::WatchMode
*/
void addDir(const QString &path, WatchModes watchModes = WatchDirOnly);
/**
* Adds a file to be watched.
* If it's a symlink to a directory, it watches the symlink itself.
* @param file the file to watch
*/
void addFile(const QString &file);
/**
* Returns the time the directory/file was last changed.
* @param path the file to check
* @return the date of the last modification
*/
QDateTime ctime(const QString &path) const;
/**
* Removes a directory from the list of scanned directories.
*
* If specified path is not in the list this does nothing.
* @param path the path of the dir to be removed from the list
*/
void removeDir(const QString &path);
/**
* Removes a file from the list of watched files.
*
* If specified path is not in the list this does nothing.
* @param file the file to be removed from the list
*/
void removeFile(const QString &file);
/**
* Stops scanning the specified path.
*
* The @p path is not deleted from the internal list, it is just skipped.
* Call this function when you perform an huge operation
* on this directory (copy/move big files or many files). When finished,
* call restartDirScan(path).
*
* @param path the path to skip
* @return true if the @p path is being watched, otherwise false
* @see restartDirScan()
*/
bool stopDirScan(const QString &path);
/**
* Restarts scanning for specified path.
*
* It doesn't notify about the changes (by emitting a signal).
* The ctime value is reset.
*
* Call it when you are finished with big operations on that path,
* @em and when @em you have refreshed that path.
*
* @param path the path to restart scanning
* @return true if the @p path is being watched, otherwise false
* @see stopDirScan()
*/
bool restartDirScan(const QString &path);
/**
* Starts scanning of all dirs in list.
*
* @param notify If true, all changed directories (since
* stopScan() call) will be notified for refresh. If notify is
* false, all ctimes will be reset (except those who are stopped,
* but only if @p skippedToo is false) and changed dirs won't be
* notified. You can start scanning even if the list is
* empty. First call should be called with @p false or else all
* directories
* in list will be notified.
* @param skippedToo if true, the skipped directories (scanning of which was
* stopped with stopDirScan() ) will be reset and notified
* for change. Otherwise, stopped directories will continue to be
* unnotified.
*/
void startScan(bool notify = false, bool skippedToo = false);
/**
* Stops scanning of all directories in internal list.
*
* The timer is stopped, but the list is not cleared.
*/
void stopScan();
/**
* Is scanning stopped?
* After creation of a KDirWatch instance, this is false.
* @return true when scanning stopped
*/
bool isStopped();
/**
* Check if a directory is being watched by this KDirWatch instance
* @param path the directory to check
* @return true if the directory is being watched
*/
bool contains(const QString &path) const;
enum Method {
INotify,
Stat,
QFSWatch,
};
/**
* Returns the preferred internal method to
* watch for changes.
*/
Method internalMethod() const;
/**
* The KDirWatch instance usually globally used in an application.
* It is automatically deleted when the application exits.
*
* However, you can create an arbitrary number of KDirWatch instances
* aside from this one - for those you have to take care of memory management.
*
* This function returns an instance of KDirWatch. If there is none, it
* will be created.
*
* @return a KDirWatch instance
*/
static KDirWatch *self();
/**
* Returns true if there is an instance of KDirWatch.
* @return true if there is an instance of KDirWatch.
* @see KDirWatch::self()
*/
static bool exists();
/**
* @brief Trivial override. See QObject::event.
*/
bool event(QEvent *event) override;
public Q_SLOTS:
/**
* Emits created().
* @param path the path of the file or directory
*/
void setCreated(const QString &path);
/**
* Emits dirty().
* @param path the path of the file or directory
*/
void setDirty(const QString &path);
/**
* Emits deleted().
* @param path the path of the file or directory
*/
void setDeleted(const QString &path);
Q_SIGNALS:
/**
* Emitted when a watched object is changed.
* For a directory this signal is emitted when files
* therein are created or deleted.
* For a file this signal is emitted when its size or attributes change.
*
* When you watch a directory, changes in the size or attributes of
* contained files may or may not trigger this signal to be emitted
* depending on which backend is used by KDirWatch.
*
* The new ctime is set before the signal is emitted.
* @param path the path of the file or directory
*/
void dirty(const QString &path);
/**
* Emitted when a file or directory (being watched explicitly) is created.
* This is not emitted when creating a file is created in a watched directory.
* @param path the path of the file or directory
*/
void created(const QString &path);
/**
* Emitted when a file or directory is deleted.
*
* The object is still watched for new creation.
* @param path the path of the file or directory
*/
void deleted(const QString &path);
private:
KDirWatchPrivate *d;
friend class KDirWatchPrivate;
friend class KDirWatch_UnitTest;
};
/**
* Dump debug information about the KDirWatch::self() instance.
* This checks for consistency, too.
*/
KCOREADDONS_EXPORT QDebug operator<<(QDebug debug, const KDirWatch &watch);
Q_DECLARE_OPERATORS_FOR_FLAGS(KDirWatch::WatchModes)
#endif
@@ -0,0 +1,242 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
SPDX-FileCopyrightText: 2006 Dirk Mueller <mueller@kde.org>
SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com>
SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
Private Header for class of KDirWatchPrivate
this separate header file is needed for MOC processing
because KDirWatchPrivate has signals and slots
*/
#ifndef KDIRWATCH_P_H
#define KDIRWATCH_P_H
#include "kdirwatch.h"
#include <io/config-kdirwatch.h>
#ifndef QT_NO_FILESYSTEMWATCHER
#define HAVE_QFILESYSTEMWATCHER 1
#else
#define HAVE_QFILESYSTEMWATCHER 0
#endif
#include <QList>
#include <QMap>
#include <QObject>
#include <QSet>
#include <QString>
#include <QTimer>
class QSocketNotifier;
#include <ctime>
#include <sys/types.h> // time_t, ino_t
#define invalid_ctime (static_cast<time_t>(-1))
#if HAVE_QFILESYSTEMWATCHER
#include <QFileSystemWatcher>
#endif // HAVE_QFILESYSTEMWATCHER
#if HAVE_SYS_INOTIFY_H
struct inotify_event;
#endif
/* KDirWatchPrivate is a singleton and does the watching
* for every KDirWatch instance in the application.
*/
class KDirWatchPrivate : public QObject
{
Q_OBJECT
public:
enum entryStatus {
Normal = 0,
NonExistent,
};
enum entryMode {
UnknownMode = 0,
StatMode,
INotifyMode,
QFSWatchMode,
};
enum {
NoChange = 0,
Changed = 1,
Created = 2,
Deleted = 4,
};
struct Client {
Client(KDirWatch *inst, KDirWatch::WatchModes watchModes)
: instance(inst)
, count(1)
, watchingStopped(inst->isStopped())
, pending(NoChange)
, m_watchModes(watchModes)
{
}
// The compiler needs a copy ctor for Client when Entry is inserted into m_mapEntries
// (even though the vector of clients is empty at that point, so no performance penalty there)
// Client(const Client &) = delete;
// Client &operator=(const Client &) = delete;
// Client(Client &&) = default;
// Client &operator=(Client &&) = default;
KDirWatch *instance;
int count;
// did the instance stop watching
bool watchingStopped;
// events blocked when stopped
int pending;
KDirWatch::WatchModes m_watchModes;
};
class Entry
{
public:
~Entry();
// instances interested in events
std::vector<Client> m_clients;
// nonexistent entries of this directory
QList<Entry *> m_entries;
QString path;
// the last observed modification time
time_t m_ctime;
// last observed inode
ino_t m_ino;
// the last observed link count
int m_nlink;
entryStatus m_status;
entryMode m_mode;
int msecLeft, freq;
bool isDir;
QString parentDirectory() const;
void addClient(KDirWatch *, KDirWatch::WatchModes);
void removeClient(KDirWatch *);
int clientCount() const;
bool isValid()
{
return !m_clients.empty() || !m_entries.empty();
}
Entry *findSubEntry(const QString &path) const
{
for (Entry *sub_entry : std::as_const(m_entries)) {
if (sub_entry->path == path) {
return sub_entry;
}
}
return nullptr;
}
bool dirty;
void propagate_dirty();
QList<const Client *> clientsForFileOrDir(const QString &tpath, bool *isDir) const;
QList<const Client *> inotifyClientsForFileOrDir(bool isDir) const;
std::vector<Client>::iterator findInstance(KDirWatch *other)
{
return std::find_if(m_clients.begin(), m_clients.end(), [other](const Client &client) {
return client.instance == other;
});
}
#if HAVE_SYS_INOTIFY_H
int wd;
// Creation and Deletion of files happens infrequently, so
// can safely be reported as they occur. File changes i.e. those that emit "dirty()" can
// happen many times per second, though, so maintain a list of files in this directory
// that can be emitted and flushed at the next slotRescan(...).
// This will be unused if the Entry is not a directory.
QList<QString> m_pendingFileChanges;
#endif
};
typedef QMap<QString, Entry> EntryMap;
KDirWatchPrivate();
~KDirWatchPrivate() override;
void resetList(KDirWatch *instance, bool skippedToo);
void useFreq(Entry *e, int newFreq);
void addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes = KDirWatch::WatchDirOnly);
void removeEntry(KDirWatch *instance, const QString &path, Entry *sub_entry);
void removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry);
bool stopEntryScan(KDirWatch *instance, Entry *e);
bool restartEntryScan(KDirWatch *instance, Entry *e, bool notify);
void stopScan(KDirWatch *instance);
void startScan(KDirWatch *instance, bool notify, bool skippedToo);
void removeEntries(KDirWatch *instance);
void addWatch(Entry *entry);
void removeWatch(Entry *entry);
Entry *entry(const QString &_path);
int scanEntry(Entry *e);
void emitEvent(Entry *e, int event, const QString &fileName = QString());
static bool isNoisyFile(const char *filename);
void ref(KDirWatch *watch);
void unref(KDirWatch *watch);
#if HAVE_SYS_INOTIFY_H
QString inotifyEventName(const inotify_event *event) const;
#endif
public Q_SLOTS:
void slotRescan();
void inotifyEventReceived(); // for inotify
void slotRemoveDelayed();
void fswEventReceived(const QString &path); // for QFileSystemWatcher
public:
QTimer m_statRescanTimer;
EntryMap m_mapEntries;
KDirWatch::Method m_preferredMethod, m_nfsPreferredMethod;
int freq;
int statEntries;
int m_nfsPollInterval, m_PollInterval;
bool useStat(Entry *e);
// removeList is allowed to contain any entry at most once
QSet<Entry *> removeList;
bool delayRemove;
bool rescan_all;
QTimer rescan_timer;
#if HAVE_SYS_INOTIFY_H
QSocketNotifier *mSn;
bool supports_inotify;
int m_inotify_fd;
QHash<int, Entry *> m_inotify_wd_to_entry;
bool useINotify(Entry *e);
#endif
#if HAVE_QFILESYSTEMWATCHER
QFileSystemWatcher *fsWatcher;
bool useQFSWatch(Entry *e);
#endif
bool _isStopped;
private:
// Public objects that reference this thread-local private instance.
QList<KDirWatch *> m_referencesObjects;
};
QDebug operator<<(QDebug debug, const KDirWatchPrivate &dwp);
QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry);
#endif // KDIRWATCH_P_H
@@ -0,0 +1,278 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2011 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.1-only
*/
#include "kfilesystemtype.h"
#include "knetworkmounts.h"
#include <config-kfilesystemtype.h>
#if HAVE_UDEV
#include <libudev.h>
#endif
#include <QCoreApplication>
#include <QFile>
#include <array>
struct FsInfo {
KFileSystemType::Type type = KFileSystemType::Unknown;
const char *name = nullptr;
};
#ifndef Q_OS_WIN
// clang-format off
static const std::array<FsInfo, 19> s_fsMap = {{
{KFileSystemType::Nfs, "nfs"},
{KFileSystemType::Nfs, "nfs4"},
{KFileSystemType::Smb, "smb"},
{KFileSystemType::Fat, "fat"},
{KFileSystemType::Ramfs, "ramfs"},
{KFileSystemType::Other, "other"},
{KFileSystemType::Ntfs, "ntfs"},
{KFileSystemType::Ntfs, "ntfs3"},
{KFileSystemType::Exfat, "exfat"},
{KFileSystemType::Unknown, "unknown"},
{KFileSystemType::Nfs, "autofs"},
{KFileSystemType::Nfs, "cachefs"},
{KFileSystemType::Nfs, "fuse.sshfs"},
{KFileSystemType::Nfs, "xtreemfs@"}, // #178678
{KFileSystemType::Smb, "smbfs"},
{KFileSystemType::Smb, "cifs"},
{KFileSystemType::Fat, "vfat"},
{KFileSystemType::Fat, "msdos"},
{KFileSystemType::Fuse, "fuseblk"},
}};
// clang-format on
inline KFileSystemType::Type kde_typeFromName(const QLatin1String name)
{
auto it = std::find_if(s_fsMap.cbegin(), s_fsMap.cend(), [name](const auto &fsInfo) {
return QLatin1String(fsInfo.name) == name;
});
return it != s_fsMap.cend() ? it->type : KFileSystemType::Other;
}
inline KFileSystemType::Type kde_typeFromName(const char *c)
{
return kde_typeFromName(QLatin1String(c));
}
#if defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD)
#include <sys/mount.h>
#include <sys/param.h>
KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path)
{
struct statfs buf;
if (statfs(path.constData(), &buf) != 0) {
return KFileSystemType::Unknown;
}
return kde_typeFromName(buf.f_fstypename);
}
#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
#include <sys/statfs.h>
#ifdef Q_OS_LINUX
#include <linux/magic.h> // A lot of the filesystem superblock MAGIC numbers
#include <sys/stat.h>
#endif
// Add known missig magics
// Can use https://github.com/systemd/systemd/blob/main/src/basic/missing_magic.h
// as reference
// From linux/fs/ntfs/ntfs.h
#ifndef NTFS_SB_MAGIC
#define NTFS_SB_MAGIC 0x5346544e
#endif
// From linux/fs/ntfs3/super.c
#ifndef NTFS3_MAGIC
#define NTFS3_MAGIC 0x7366746E
#endif
// From linux/fs/exfat/exfat_fs.h
#ifndef EXFAT_SUPER_MAGIC
#define EXFAT_SUPER_MAGIC 0x2011BAB0UL
#endif
// From linux/fs/cifs/smb2glob.h
#ifndef SMB2_MAGIC_NUMBER
#define SMB2_MAGIC_NUMBER 0xFE534D42
#endif
// From linux/fs/cifs/cifsglob.h
#ifndef CIFS_MAGIC_NUMBER
#define CIFS_MAGIC_NUMBER 0xFF534D42
#endif
// From linux/fs/fuse/inode.c
#ifndef FUSE_SUPER_MAGIC
#define FUSE_SUPER_MAGIC 0x65735546
#endif
#ifndef AUTOFSNG_SUPER_MAGIC
#define AUTOFSNG_SUPER_MAGIC 0x7d92b1a0
#endif
#ifdef Q_OS_HURD
#ifndef NFS_SUPER_MAGIC
#define NFS_SUPER_MAGIC 0x00006969
#endif
#ifndef AUTOFS_SUPER_MAGIC
#define AUTOFS_SUPER_MAGIC 0x00000187
#endif
#ifndef MSDOS_SUPER_MAGIC
#define MSDOS_SUPER_MAGIC 0x00004d44
#endif
#ifndef SMB_SUPER_MAGIC
#define SMB_SUPER_MAGIC 0x0000517B
#endif
#ifndef RAMFS_MAGIC
#define RAMFS_MAGIC 0x858458F6
#endif
#endif
KFileSystemType::Type probeFuseBlkType(const QByteArray &path)
{
using namespace KFileSystemType;
#if HAVE_UDEV
struct stat buf;
if (stat(path.constData(), &buf) != 0) {
return Fuse;
}
using UdevPtr = std::unique_ptr<struct udev, decltype(&udev_unref)>;
using UDevicePtr = std::unique_ptr<struct udev_device, decltype(&udev_device_unref)>;
// Code originally copied from util-linux/misc-utils/lsblk.c
auto udevP = UdevPtr(udev_new(), udev_unref);
if (!udevP) {
return Fuse;
}
// 'b' for block devices
auto devPtr = UDevicePtr(udev_device_new_from_devnum(udevP.get(), 'b', buf.st_dev), udev_device_unref);
if (!devPtr) {
// If is not block device, assume conservatively it is a remote FS under FUSE.
return Nfs;
}
const QLatin1String fsType(udev_device_get_property_value(devPtr.get(), "ID_FS_TYPE"));
return kde_typeFromName(fsType);
#endif
return Fuse;
}
// Reverse-engineering without C++ code:
// strace stat -f /mnt 2>&1|grep statfs|grep mnt, and look for f_type
//
// Also grep'ing in /usr/src/<kernel-version>/fs/
static KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path)
{
struct statfs buf;
if (statfs(path.constData(), &buf) != 0) {
return KFileSystemType::Unknown;
}
switch (static_cast<unsigned long>(buf.f_type)) {
case NFS_SUPER_MAGIC:
case AUTOFS_SUPER_MAGIC:
case AUTOFSNG_SUPER_MAGIC:
return KFileSystemType::Nfs;
case FUSE_SUPER_MAGIC:
return probeFuseBlkType(path);
case SMB_SUPER_MAGIC:
case SMB2_MAGIC_NUMBER:
case CIFS_MAGIC_NUMBER:
return KFileSystemType::Smb;
case MSDOS_SUPER_MAGIC:
return KFileSystemType::Fat;
case NTFS_SB_MAGIC:
case NTFS3_MAGIC:
return KFileSystemType::Ntfs;
case EXFAT_SUPER_MAGIC:
return KFileSystemType::Exfat;
case RAMFS_MAGIC:
return KFileSystemType::Ramfs;
default:
return KFileSystemType::Other;
}
}
#elif defined(Q_OS_AIX) || defined(Q_OS_HPUX) || defined(Q_OS_QNX) || defined(Q_OS_SCO) || defined(Q_OS_UNIXWARE) || defined(Q_OS_RELIANT) \
|| defined(Q_OS_NETBSD)
#include <sys/statvfs.h>
KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path)
{
struct statvfs buf;
if (statvfs(path.constData(), &buf) != 0) {
return KFileSystemType::Unknown;
}
#if defined(Q_OS_NETBSD)
return kde_typeFromName(buf.f_fstypename);
#else
return kde_typeFromName(buf.f_basetype);
#endif
}
#endif
#else
KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path)
{
Q_UNUSED(path);
return KFileSystemType::Unknown;
}
#endif
KFileSystemType::Type determineFileSystemTypeImpl(const QByteArray &path) {
Q_UNUSED(path);
return KFileSystemType::Unknown;
}
KFileSystemType::Type KFileSystemType::fileSystemType(const QString &path)
{
if (KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::KNetworkMountsType::SmbPaths)) {
return KFileSystemType::Smb;
} else if (KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::KNetworkMountsType::NfsPaths)) {
return KFileSystemType::Nfs;
} else {
return determineFileSystemTypeImpl(QFile::encodeName(path));
}
}
QString KFileSystemType::fileSystemName(KFileSystemType::Type type)
{
switch (type) {
case KFileSystemType::Nfs:
return QCoreApplication::translate("KFileSystemType", "NFS");
case KFileSystemType::Smb:
return QCoreApplication::translate("KFileSystemType", "SMB");
case KFileSystemType::Fat:
return QCoreApplication::translate("KFileSystemType", "FAT");
case KFileSystemType::Ramfs:
return QCoreApplication::translate("KFileSystemType", "RAMFS");
case KFileSystemType::Other:
return QCoreApplication::translate("KFileSystemType", "Other");
case KFileSystemType::Ntfs:
return QCoreApplication::translate("KFileSystemType", "NTFS");
case KFileSystemType::Exfat:
return QCoreApplication::translate("KFileSystemType", "ExFAT");
case KFileSystemType::Fuse:
return QCoreApplication::translate("KFileSystemType", "FUSE");
case KFileSystemType::Unknown:
return QCoreApplication::translate("KFileSystemType", "Unknown");
}
Q_UNREACHABLE();
return {};
}
@@ -0,0 +1,57 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2011 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.1-only
*/
#ifndef KFILESYSTEMTYPE_P_H
#define KFILESYSTEMTYPE_P_H
#include <kcoreaddons_export.h>
#include <QString>
/**
* @namespace KFileSystemType
* Provides utility functions for the type of file systems.
*/
namespace KFileSystemType
{
enum Type {
Unknown,
Nfs, ///< NFS or other full-featured networked filesystems (autofs, subfs, cachefs, sshfs)
Smb, ///< SMB/CIFS mount (networked but with some FAT-like behavior)
Fat, ///< FAT or similar (msdos, FAT, VFAT)
Ramfs, ///< RAMDISK mount
Other, ///< Ext3, Ext4, ReiserFs, and so on. "Normal" local filesystems.
Ntfs, ///< NTFS filesystem @since 5.85
Exfat, ///< ExFat filesystem @since 5.86
/**
* FUSE (Filesystem in USErspace), this is used for a variety of underlying
* filesystems.
*
* @since 5.100
*/
Fuse,
};
/**
* For a given @p path, returns the filesystem type, one of @ref KFileSystemType::Type
* values. If the type can't be determined, @c KFileSystemType::Unknown is returned.
*
* @since 5.0
*/
KCOREADDONS_EXPORT Type fileSystemType(const QString &path);
/**
* Returns the possibly translated name of a filesystem corresponding to a
* value from @ref KFileSystemType::Type.
*
* @since 5.86
*/
KCOREADDONS_EXPORT QString fileSystemName(KFileSystemType::Type type);
}
#endif
@@ -0,0 +1,87 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfileutils.h"
#include <QDirIterator>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <set>
QString KFileUtils::makeSuggestedName(const QString &oldName)
{
QString basename;
// Extract the original file extension from the filename
QMimeDatabase db;
QString nameSuffix = db.suffixForFileName(oldName);
if (oldName.lastIndexOf(QLatin1Char('.')) == 0) {
basename = QStringLiteral(".");
nameSuffix = oldName;
} else if (nameSuffix.isEmpty()) {
const int lastDot = oldName.lastIndexOf(QLatin1Char('.'));
if (lastDot == -1) {
basename = oldName;
} else {
basename = oldName.left(lastDot);
nameSuffix = oldName.mid(lastDot);
}
} else {
nameSuffix.prepend(QLatin1Char('.'));
basename = oldName.left(oldName.length() - nameSuffix.length());
}
// check if (number) exists at the end of the basename and increment that number
const static QRegularExpression re(QStringLiteral(" \\((\\d+)\\)"));
QRegularExpressionMatch rmatch;
if (basename.lastIndexOf(re, -1, &rmatch) != -1) {
const int currentNum = rmatch.captured(1).toInt();
const QString number = QString::number(currentNum + 1);
basename.replace(rmatch.capturedStart(1), rmatch.capturedLength(1), number);
} else {
// number does not exist, so just append " (1)" to filename
basename += QLatin1String(" (1)");
}
return basename + nameSuffix;
}
QString KFileUtils::suggestName(const QUrl &baseURL, const QString &oldName)
{
QString suggestedName = makeSuggestedName(oldName);
if (baseURL.isLocalFile()) {
const QString basePath = baseURL.toLocalFile() + QLatin1Char('/');
while (QFileInfo::exists(basePath + suggestedName)) {
suggestedName = makeSuggestedName(suggestedName);
}
}
return suggestedName;
}
QStringList KFileUtils::findAllUniqueFiles(const QStringList &dirs, const QStringList &nameFilters)
{
QStringList foundFilePaths;
std::set<QString> foundFileNames;
for (const QString &dir : dirs) {
QDirIterator it(dir, nameFilters, QDir::Files);
while (it.hasNext()) {
it.next();
const auto [iter, isFirstSeen] = foundFileNames.insert(it.fileName());
if (isFirstSeen) {
foundFilePaths << it.filePath();
}
}
}
return foundFilePaths;
}
@@ -0,0 +1,74 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEUTILS_H
#define KFILEUTILS_H
#include "kcoreaddons_export.h"
#include <QString>
#include <QUrl>
/**
* @short A namespace for KFileUtils globals
*
*/
namespace KFileUtils
{
/**
* Given a directory path and a string representing a file or directory
* (which usually exist already), this function returns a suggested name
* for a file/directory that doesn't exist in @p baseURL.
*
* The suggested file name is of the form "foo (1)", "foo (2)" etc.
*
* For local URLs, this function will check if there is already a file/directory
* with the new suggested name and will keep incrementing the number in the above
* format until it finds one that doesn't exist. Note that this function uses a
* blocking I/O call (using QFileInfo) to check the existence of the file/directory,
* this could be problematic for network mounts (e.g. SMB, NFS) as these are treated
* as local files by the upstream QFile code. An alternative is to use makeSuggestedName()
* and use KIO to stat the new file/directory in an asynchronous way.
*
* @since 5.61
*/
KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName);
/**
* Given a string, "foo", representing a file/directory (which usually exists already),
* this function returns a suggested name for a file/directory in the form "foo (1)",
* "foo (2)" etc.
*
* Unlike the suggestName() method, this function doesn't check if there is a file/directory
* with the newly suggested name; the idea being that this responsibility falls on
* the caller, e.g. one can use KIO::stat() to check asynchronously whether the new
* name already exists (in its parent directory) or not.
*
* @since 5.76
*/
KCOREADDONS_EXPORT QString makeSuggestedName(const QString &oldName);
/**
* Locates all files matching the @p nameFilters in the given @p dirs
* The returned list does not contain duplicate file names.
* In case there are multiple files the one which comes first in the dirs list is returned.
* For example:
* @code
QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("krunner/dbusplugins"), QStandardPaths::LocateDirectory);
KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.desktop")});
* @endcode
* @param location standard location for the dir
* @param dir directory in which the files are located
* @param nameFilters filters that get passed to the QDirIterator that is used internally to
* iterate over the files in each dir in the list
* @return list of absolute file paths
* @since 5.85
*/
KCOREADDONS_EXPORT QStringList findAllUniqueFiles(const QStringList &dirs, const QStringList &nameFilters = {});
}
#endif
@@ -0,0 +1,219 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knetworkmounts.h"
#include "knetworkmounts_p.h"
#include <QCoreApplication>
#include <QGlobalStatic>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
KNetworkMountsPrivate::KNetworkMountsPrivate(KNetworkMounts *qq)
: q(qq)
{
}
KNetworkMounts *KNetworkMounts::self()
{
static KNetworkMounts s_self;
return &s_self;
}
KNetworkMounts::KNetworkMounts()
: d(new KNetworkMountsPrivate(this))
{
const QString configFileName = QStringLiteral("%1/network_mounts").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
d->m_settings = new QSettings(configFileName, QSettings::Format::IniFormat, this);
for (const auto type : {KNetworkMounts::NfsPaths, KNetworkMounts::SmbPaths, KNetworkMounts::SymlinkDirectory, KNetworkMounts::SymlinkToNetworkMount}) {
QString typeStr = enumToString(type);
QStringList slowPaths = d->m_settings->value(typeStr, QStringList()).toStringList();
if (ensureTrailingSlashes(&slowPaths)) {
d->m_settings->setValue(typeStr, slowPaths);
}
}
}
KNetworkMounts::~KNetworkMounts()
{
}
bool KNetworkMounts::isSlowPath(const QString &path, KNetworkMountsType type)
{
return !getMatchingPath(path, paths(type)).isEmpty();
}
bool KNetworkMounts::isOptionEnabledForPath(const QString &path, KNetworkMountOption option)
{
if (!isEnabled()) {
return false;
}
if (!isSlowPath(path)) {
return false;
}
return isOptionEnabled(option, true);
}
bool KNetworkMounts::isEnabled() const
{
return d->m_settings->value(QStringLiteral("EnableOptimizations"), false).toBool();
}
void KNetworkMounts::setEnabled(const bool value)
{
d->m_settings->setValue(QStringLiteral("EnableOptimizations"), value);
}
bool KNetworkMounts::isOptionEnabled(const KNetworkMountOption option, const bool defaultValue) const
{
return d->m_settings->value(enumToString(option), defaultValue).toBool();
}
void KNetworkMounts::setOption(const KNetworkMountOption option, const bool value)
{
d->m_settings->setValue(enumToString(option), value);
}
QStringList KNetworkMounts::paths(KNetworkMountsType type) const
{
if (type == Any) {
QStringList paths;
paths.reserve(4);
for (const auto networkMountType :
{KNetworkMounts::NfsPaths, KNetworkMounts::SmbPaths, KNetworkMounts::SymlinkDirectory, KNetworkMounts::SymlinkToNetworkMount}) {
paths.append(d->m_settings->value(enumToString(networkMountType), QStringList()).toStringList());
}
return paths;
} else {
return d->m_settings->value(enumToString(type), QStringList()).toStringList();
}
}
void KNetworkMounts::setPaths(const QStringList &paths, KNetworkMountsType type)
{
QStringList _paths(paths);
ensureTrailingSlashes(&_paths);
d->m_settings->setValue(enumToString(type), _paths);
}
void KNetworkMounts::addPath(const QString &path, KNetworkMountsType type)
{
QString _path(path);
ensureTrailingSlash(&_path);
QStringList newPaths = paths(type);
newPaths.append(_path);
d->m_settings->setValue(enumToString(type), newPaths);
}
typedef QHash<QString /*symlink*/, QString /*canonical path*/> symlinkCanonicalPathHash;
Q_GLOBAL_STATIC(symlinkCanonicalPathHash, s_canonicalLinkSpacePaths)
QString KNetworkMounts::canonicalSymlinkPath(const QString &path)
{
bool useCache = isOptionEnabled(KNetworkMountOption::SymlinkPathsUseCache, true);
if (useCache) {
const QString resolved = s_canonicalLinkSpacePaths->value(path);
if (!resolved.isEmpty()) {
return resolved;
}
}
QString symlinkPath = getMatchingPath(path, paths(KNetworkMountsType::SymlinkToNetworkMount));
if (!symlinkPath.isEmpty()) {
// remove trailing slash
symlinkPath.chop(1);
QFileInfo link(symlinkPath);
QString linkPath(path);
QString target = link.symLinkTarget();
if (target.isEmpty()) {
// not a symlink
if (useCache) {
s_canonicalLinkSpacePaths->insert(path, path);
}
return path;
} else {
// symlink
// replace only the first occurence of symlinkPath in linkPath with the link target
// linkPath.startsWith(symlinkPath) because of getMatchingPath
linkPath.replace(0, symlinkPath.size(), target);
if (useCache) {
s_canonicalLinkSpacePaths->insert(path, linkPath);
}
return linkPath;
}
}
QString linkSpacePath = getMatchingPath(path, paths(KNetworkMountsType::SymlinkDirectory));
if (!linkSpacePath.isEmpty()) {
QString _path = path;
if (!_path.endsWith(QLatin1Char('/'))) {
_path.append(QLatin1Char('/'));
}
if (_path == linkSpacePath) {
if (useCache) {
s_canonicalLinkSpacePaths->insert(path, path);
}
return path;
}
// search for symlink, linkSpacePath always ends with '/'
int linkIndex = path.indexOf(QLatin1Char('/'), linkSpacePath.length());
const QString symlink = path.left(linkIndex);
if (useCache && s_canonicalLinkSpacePaths->contains(symlink)) {
QString linkPath(path);
// replace only the first occurence of symlink in linkPath
linkPath.replace(0, symlink.size(), s_canonicalLinkSpacePaths->value(symlink));
s_canonicalLinkSpacePaths->insert(path, linkPath);
return linkPath;
} else {
QFileInfo link(symlink);
if (link.isSymLink()) {
QString linkPath(path);
// replace only the first occurence of symlink in linkPath
linkPath.replace(0, symlink.size(), link.symLinkTarget());
if (useCache) {
s_canonicalLinkSpacePaths->insert(path, linkPath);
}
return linkPath;
} else {
if (useCache) {
s_canonicalLinkSpacePaths->insert(path, path);
}
}
}
}
return path;
}
void KNetworkMounts::clearCache()
{
if (s_canonicalLinkSpacePaths.exists()) {
s_canonicalLinkSpacePaths->clear();
}
}
void KNetworkMounts::sync()
{
d->m_settings->sync();
}
#include "moc_knetworkmounts.cpp"
@@ -0,0 +1,283 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTS_H
#define KNETWORKMOUNTS_H
#include <memory>
#include <QObject>
#include <kcoreaddons_export.h>
/**
* \class KNetworkMounts knetworkmounts.h <KNetworkMounts>
*
* Performance control on network mounts.
*
* This class provides methods for deciding whether operations
* on slow network mounts should be performed or not.
*
* Configuration is read from a configuration file network_mounts in
* the user's QStandardPaths::ConfigLocation. This file can be filled by using
* the network mounts performance configuration module or directly via @ref setEnabled,
* @ref setPaths, @ref addPath and @ref setOption
* @code
* KNetworkMounts::self()->setEnabled(true);
* KNetworkMounts::self()->setOption(KNetworkMounts::LowSideEffectsOptimizations, true);
* KNetworkMounts::self()->addPath(path1, KNetworkMounts::NfsPaths);
* KNetworkMounts::self()->addPath(path2, KNetworkMounts::NfsPaths);
* KNetworkMounts::self()->setPaths(listOfPaths, KNetworkMounts::SmbPaths);
* @endcode
*
* Use KNetworkMounts like this to check if the given url is on a
* configured slow path and the KNetworkMountOption LowSideEffectsOptimizations
* is enabled:
* @code
* if (KNetworkMounts::self()->isOptionEnabledForPath(url.toLocalFile(),
* KNetworkMounts::LowSideEffectsOptimizations))
* {
* // skip operations which are slow on the given url if
* // KNetworkMountOption LowSideEffectsOptimizations is enabled
* } else {
* // given url is not configured being slow or the KNetworkMountOption
* // LowSideEffectsOptimizations is not enabled
* }
* @endcode
*
* If called for the first time, this creates a singleton instance and reads
* the config file. Subsequent calls just use this instance without reading
* the config file again.
*
* @author Robert Hoffmann <robert@roberthoffmann.de>
*
* @since 5.85
**/
class KCOREADDONS_EXPORT KNetworkMounts : public QObject
{
Q_OBJECT
public:
/**
* Returns (and creates if necessary) the singleton instance
*
* @return the singleton instance
*/
static KNetworkMounts *self();
/**
* The KNetworkMountOption enum
*
* Uses are:
*/
enum KNetworkMountOption {
LowSideEffectsOptimizations, ///< Don't run KDiskFreeSpaceInfo if slow path.<br>
///< Don't check for manually mounted drives.<br>
///< Don't check with QFileInfo::isWritable if it is writable, if not yet known, return true.<br>
///< Don't check with QFileInfo::isReadable if it is readable, return false.<br>
///< Don't check for desktop files just return false.<br>
///< Ignore .hidden files on slow paths.<br>
///< Don't read mime comment from .desktop or .directory files.<br>
///< Don't get the size with QFileInfo::size, just return 0, if not yet known.<br>
///< Don't determine mime type from file content, use file extension.<br>
///< Don't check for desktop files just return false.<br>
///< Don't call KFileSystemType::fileSystemType to check if the filesystem is slow, just return true.<br>
///< Don't count files/directories in subdirectories.<br>
///< Don't calculate sizes of subdirectories.<br>
///< Avoid check for dir at Kate startup
MediumSideEffectsOptimizations, ///< Don't return project for dir, avoid QFileInfo().absoluteDir()<br>
///< Don't search for .kateconfig recursively<br>
///< Ignore recent files on slow paths
StrongSideEffectsOptimizations, ///< Turn off symbolic link resolution
KDirWatchDontAddWatches, ///< Disables dir watching completely for slow paths, avoids stat() calls on added dirs and subdirs
SymlinkPathsUseCache ///< Cache resolved symlink paths
};
Q_ENUM(KNetworkMountOption)
/**
* The KNetworkMountsType enum
*/
enum KNetworkMountsType {
NfsPaths, ///< NFS paths
SmbPaths, ///< SMB paths
SymlinkDirectory, ///< Paths to directories which contain symbolic links to network mounts
SymlinkToNetworkMount, ///< Paths which are symbolic links to network mounts
Any ///< Any slow path type. Do not use with @ref setPaths or @ref addPath
};
Q_ENUM(KNetworkMountsType)
/**
* Query if @p path is configured to be a slow path of type @p type
*
* @param path the path to query
* @param type the type to query. If omitted, any type matches
* @return @c true if @p path is a configured slow path of type @p type
*
* This function is also used to determine the filesystem type in @ref KFileSystemType::fileSystemType
* (KFileSystemType::Smb or KFileSystemType::Nfs) without an expensive call to stafs(). For this
* to work the types of paths need to be correctly assigned in @ref setPath or @ref addPath
*/
bool isSlowPath(const QString &path, KNetworkMountsType type = Any);
/**
* Query if @p path is configured to be a slow path and @p option is enabled
*
* @param path the path to query
* @param option the option to query
* @return @c true if @p path is a configured slow path and option @p option is enabled
*/
bool isOptionEnabledForPath(const QString &path, KNetworkMountOption option);
/**
* Query if the performance optimizations are switched on
*
* @return @c true if on, @c false otherwise
*/
bool isEnabled() const;
/**
* Switch the performance optimizations on or off
*
* @param value the value to set
*/
void setEnabled(bool value);
/**
* Query a performance option
*
* @param option the option to query
* @param defaultValue the value to return if the option is not configured
* @return @c true if option is on, @c false if not
* @see KNetworkMountOption
*/
bool isOptionEnabled(const KNetworkMountOption option, const bool defaultValue = false) const;
/**
* Switch a performance option on or off
*
* @param option the option to change
* @param value the value to set
* @see KNetworkMountOption
*/
void setOption(const KNetworkMountOption option, const bool value);
/**
* Query the configured paths for which optimizations are to take place
*
* @return a list of paths
*/
QStringList paths(KNetworkMountsType type = Any) const;
/**
* Set the paths for which optimizations are to take place
*
* @param paths the paths to set
* @param type the type of paths. Do not use @ref Any
* @see KNetworkMountsType
*/
void setPaths(const QStringList &paths, KNetworkMountsType type);
/**
* Add a path for which optimizations are to take place
*
* @param path the path to add
* @param type the type of the path. Do not use @ref Any
* @see KNetworkMountsType
*/
void addPath(const QString &path, KNetworkMountsType type);
/**
* Resolves a @p path that may contain symbolic links to mounted network shares.
*
* A symlink path is either a directory which contains symbolic links to slow network mounts
* (@ref SymlinkDirectory) or a direct symbolic link to a slow network mount (@ref
* SymlinkToNfsOrSmbPaths).
*
* Example:
* There are some Samba shares mounted below /mnt. These are @ref paths of type @ref SmbPaths
* @code
* /mnt/server1/share1
* /mnt/server1/share2
* /mnt/server2/share3
* @endcode
*
* A (logged in) user may have symbolic links to them in his home directory below netshares. The
* directory /home/user/netshares is a @ref SymlinkDirectory:
* @code
* /home/user/netshares/share1 -> /mnt/server1/share1
* /home/user/netshares/share2 -> /mnt/server1/share2
* /home/user/netshares/share3 -> /mnt/server2/share3
* @endcode
*
* There is a direct symbolic link from /home/user/share1 to /mnt/server1/share1. This is of type
* @ref SymlinkToNfsOrSmbPaths:
* @code
* /home/user/share1 -> /mnt/server1/share1
* @endcode
*
* Both types of symbolic links from symlink paths to the real mounted shares are resolved even if
* KNetworkMountOption @ref StrongSideEffectsOptimizations is enabled.
* If the setup is like above a @p path
* @code
* /home/user/netshares/share1/Documents/file.txt
* @endcode
*
* would be resolved to
* @code
* /mnt/server1/share1/Documents/file.txt
* @endcode
*
* and a @p path
* @code
* /home/user/share1/Documents/file.txt
* @endcode
*
* would also be resolved to
* @code
* /mnt/server1/share1/Documents/file.txt
* @endcode
*
* Resolved paths are cached in a hash.
*
* @param path the path to resolve
* @return the resolved path or @p path if @p path is not a symlink path or no symlink found
* @see KNetworkMountsType
* @see clearCache
* @see isSlowPath
*/
QString canonicalSymlinkPath(const QString &path);
/**
* Clears the canonical symlink path cache
*
* Call this if directory structures on mounted network drives changed. Don't enable the
* cache (@ref SymlinkPathsUseCache) if this happens often and the drives are usually accessed
* via the symlinks. This method exists mainly for the KCM.
* @see canonicalSymlinkPath
*/
void clearCache();
/**
* Synchronizes to config file
*
* QSettings synchronization also takes place automatically at regular intervals and from
* QSettings destructor, see QSettings::sync() documentation.
*
* Calls QSettings::sync()
*/
void sync();
private:
/// Creates a new KNetworkMounts object
KCOREADDONS_NO_EXPORT KNetworkMounts();
/// Destructor
KCOREADDONS_NO_EXPORT ~KNetworkMounts() override;
std::unique_ptr<class KNetworkMountsPrivate> const d;
};
#endif // KNETWORKMOUNTS_H
@@ -0,0 +1,81 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTS_P_H
#define KNETWORKMOUNTS_P_H
#include "knetworkmounts.h"
#include <QMetaEnum>
#include <QSettings>
class KNetworkMountsPrivate
{
public:
KNetworkMountsPrivate(KNetworkMounts *);
KNetworkMounts *q;
QSettings *m_settings = nullptr;
private:
};
// Append trailing slashes to path string if missing
static bool ensureTrailingSlash(QString *path)
{
bool changed = false;
if (!path->isEmpty() && !path->endsWith(QLatin1Char('/'))) {
path->append(QLatin1Char('/'));
changed = true;
}
return changed;
}
// Append trailing slashes to path strings if missing
static bool ensureTrailingSlashes(QStringList *paths)
{
bool changed = false;
for (QString &path : *paths) {
if (ensureTrailingSlash(&path)) {
changed = true;
}
}
return changed;
}
// Return the matching configured slow path
static QString getMatchingPath(const QString &_path, const QStringList &slowPaths)
{
if (slowPaths.isEmpty()) {
return QString();
}
QString path = _path;
if (!path.endsWith(QLatin1Char('/'))) {
path.append(QLatin1Char('/'));
}
for (const QString &slp : slowPaths) {
if (path.startsWith(slp)) {
return slp;
}
}
return QString();
}
// Convert the enums KNetworkMountsType and KNetworkMountOption to QString
template<typename EnumType>
static QString enumToString(EnumType type)
{
const int intValue = static_cast<int>(type);
return QString::fromUtf8(QMetaEnum::fromType<EnumType>().valueToKey(intValue));
}
#endif
@@ -0,0 +1,308 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcoreaddons_debug.h"
#include "kprocess_p.h"
#include <QStandardPaths>
#include <kshell.h>
#include <qplatformdefs.h>
#ifdef Q_OS_WIN
#include <kshell_p.h>
#include <qt_windows.h>
#endif
#include <QFile>
/////////////////////////////
// public member functions //
/////////////////////////////
KProcess::KProcess(QObject *parent)
: QProcess(parent)
, d_ptr(new KProcessPrivate(this))
{
setOutputChannelMode(ForwardedChannels);
}
KProcess::KProcess(KProcessPrivate *d, QObject *parent)
: QProcess(parent)
, d_ptr(d)
{
d_ptr->q_ptr = this;
setOutputChannelMode(ForwardedChannels);
}
KProcess::~KProcess() = default;
void KProcess::setOutputChannelMode(OutputChannelMode mode)
{
QProcess::setProcessChannelMode(static_cast<ProcessChannelMode>(mode));
}
KProcess::OutputChannelMode KProcess::outputChannelMode() const
{
return static_cast<OutputChannelMode>(QProcess::processChannelMode());
}
void KProcess::setNextOpenMode(QIODevice::OpenMode mode)
{
Q_D(KProcess);
d->openMode = mode;
}
#define DUMMYENV "_KPROCESS_DUMMY_="
void KProcess::clearEnvironment()
{
setEnvironment(QStringList{QStringLiteral(DUMMYENV)});
}
void KProcess::setEnv(const QString &name, const QString &value, bool overwrite)
{
QStringList env = environment();
if (env.isEmpty()) {
env = systemEnvironment();
env.removeAll(QStringLiteral(DUMMYENV));
}
QString fname(name);
fname.append(QLatin1Char('='));
auto it = std::find_if(env.begin(), env.end(), [&fname](const QString &s) {
return s.startsWith(fname);
});
if (it != env.end()) {
if (overwrite) {
*it = fname.append(value);
setEnvironment(env);
}
return;
}
env.append(fname.append(value));
setEnvironment(env);
}
void KProcess::unsetEnv(const QString &name)
{
QStringList env = environment();
if (env.isEmpty()) {
env = systemEnvironment();
env.removeAll(QStringLiteral(DUMMYENV));
}
QString fname(name);
fname.append(QLatin1Char('='));
auto it = std::find_if(env.begin(), env.end(), [&fname](const QString &s) {
return s.startsWith(fname);
});
if (it != env.end()) {
env.erase(it);
if (env.isEmpty()) {
env.append(QStringLiteral(DUMMYENV));
}
setEnvironment(env);
}
}
void KProcess::setProgram(const QString &exe, const QStringList &args)
{
QProcess::setProgram(exe);
QProcess::setArguments(args);
#ifdef Q_OS_WIN
setNativeArguments(QString());
#endif
}
void KProcess::setProgram(const QStringList &argv)
{
if (argv.isEmpty()) {
qCWarning(KCOREADDONS_DEBUG) << "KProcess::setProgram(const QStringList &argv) called on an empty string list, no process will be started.";
clearProgram();
return;
}
QStringList args = argv;
QProcess::setProgram(args.takeFirst());
QProcess::setArguments(args);
#ifdef Q_OS_WIN
setNativeArguments(QString());
#endif
}
KProcess &KProcess::operator<<(const QString &arg)
{
if (QProcess::program().isEmpty()) {
QProcess::setProgram(arg);
} else {
setArguments(arguments() << arg);
}
return *this;
}
KProcess &KProcess::operator<<(const QStringList &args)
{
if (QProcess::program().isEmpty()) {
setProgram(args);
} else {
setArguments(arguments() << args);
}
return *this;
}
void KProcess::clearProgram()
{
QProcess::setProgram({});
QProcess::setArguments({});
#ifdef Q_OS_WIN
setNativeArguments(QString());
#endif
}
// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh
#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) \
&& !defined(__APPLE__)
const static bool s_nonFreeUnix = true;
#else
const static bool s_nonFreeUnix = false;
#endif
void KProcess::setShellCommand(const QString &cmd)
{
KShell::Errors err = KShell::NoError;
auto args = KShell::splitArgs(cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err);
if (err == KShell::NoError && !args.isEmpty()) {
QProcess::setProgram(QStandardPaths::findExecutable(args.takeFirst()));
if (!QProcess::program().isEmpty()) {
setArguments(args);
#ifdef Q_OS_WIN
setNativeArguments(QString());
#endif
return;
}
}
setArguments({});
#ifdef Q_OS_UNIX
if (s_nonFreeUnix) {
// If /bin/sh is a symlink, we can be pretty sure that it points to a
// POSIX shell - the original bourne shell is about the only non-POSIX
// shell still in use and it is always installed natively as /bin/sh.
QString shell = QFile::symLinkTarget(QStringLiteral("/bin/sh"));
if (shell.isEmpty()) {
// Try some known POSIX shells.
static const char *s_knownShells[] = {"ksh", "ash", "bash", "zsh"};
for (const auto str : s_knownShells) {
shell = QStandardPaths::findExecutable(QLatin1String(str));
if (!shell.isEmpty()) {
break;
}
}
}
if (shell.isEmpty()) { // We're pretty much screwed, to be honest ...
shell = QStringLiteral("/bin/sh");
}
QProcess::setProgram(shell);
} else {
QProcess::setProgram((QStringLiteral("/bin/sh")));
}
setArguments(arguments() << QStringLiteral("-c") << cmd);
#else // Q_OS_UNIX
// KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and
// KShell::joinArgs() may generate these for security reasons.
setEnv(PERCENT_VARIABLE, QStringLiteral("%"));
#ifndef _WIN32_WCE
WCHAR sysdir[MAX_PATH + 1];
UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1);
QProcess::setProgram(QString::fromUtf16((const char16_t *)sysdir, size) + QLatin1String("\\cmd.exe"));
setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"'));
#else
QProcess::setProgram(QStringLiteral("\\windows\\cmd.exe"));
setNativeArguments(QStringLiteral("/S /C \"") + cmd + QLatin1Char('"'));
#endif
#endif
}
QStringList KProcess::program() const
{
QStringList argv = arguments();
argv.prepend(QProcess::program());
return argv;
}
void KProcess::start()
{
Q_D(KProcess);
QProcess::start(d->openMode);
}
int KProcess::execute(int msecs)
{
start();
if (!waitForFinished(msecs)) {
kill();
waitForFinished(-1);
return -2;
}
return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1;
}
// static
int KProcess::execute(const QString &exe, const QStringList &args, int msecs)
{
KProcess p;
p.setProgram(exe, args);
return p.execute(msecs);
}
// static
int KProcess::execute(const QStringList &argv, int msecs)
{
KProcess p;
p.setProgram(argv);
return p.execute(msecs);
}
int KProcess::startDetached()
{
qint64 pid;
if (!QProcess::startDetached(QProcess::program(), QProcess::arguments(), workingDirectory(), &pid)) {
return 0;
}
return static_cast<int>(pid);
}
// static
int KProcess::startDetached(const QString &exe, const QStringList &args)
{
qint64 pid;
if (!QProcess::startDetached(exe, args, QString(), &pid)) {
return 0;
}
return static_cast<int>(pid);
}
// static
int KProcess::startDetached(const QStringList &argv)
{
if (argv.isEmpty()) {
qCWarning(KCOREADDONS_DEBUG) << "KProcess::startDetached(const QStringList &argv) called on an empty string list, no process will be started.";
return 0;
}
QStringList args = argv;
QString prog = args.takeFirst();
return startDetached(prog, args);
}
#include "moc_kprocess.cpp"
@@ -0,0 +1,312 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPROCESS_H
#define KPROCESS_H
#include <kcoreaddons_export.h>
#include <QProcess>
#include <memory>
class KProcessPrivate;
/**
* \class KProcess kprocess.h <KProcess>
*
* Child process invocation, monitoring and control.
*
* This class extends QProcess by some useful functionality, overrides
* some defaults with saner values and wraps parts of the API into a more
* accessible one.
* Only use KProcess if you need the extra features, otherwise QProcess
* is the preferred way of spawning child processes.
*
* @author Oswald Buddenhagen <ossi@kde.org>
**/
class KCOREADDONS_EXPORT KProcess : public QProcess
{
Q_OBJECT
Q_DECLARE_PRIVATE(KProcess)
public:
/**
* Modes in which the output channels can be opened.
*/
enum OutputChannelMode {
SeparateChannels = QProcess::SeparateChannels,
/**< Standard output and standard error are handled by KProcess
as separate channels */
MergedChannels = QProcess::MergedChannels,
/**< Standard output and standard error are handled by KProcess
as one channel */
ForwardedChannels = QProcess::ForwardedChannels,
/**< Both standard output and standard error are forwarded
to the parent process' respective channel */
OnlyStdoutChannel = QProcess::ForwardedErrorChannel,
/**< Only standard output is handled; standard error is forwarded */
OnlyStderrChannel = QProcess::ForwardedOutputChannel,
/**< Only standard error is handled; standard output is forwarded */
};
/**
* Constructor
*/
explicit KProcess(QObject *parent = nullptr);
/**
* Destructor
*/
~KProcess() override;
/**
* Set how to handle the output channels of the child process.
*
* The default is ForwardedChannels, which is unlike in QProcess.
* Do not request more than you actually handle, as this output is
* simply lost otherwise.
*
* This function must be called before starting the process.
*
* @param mode the output channel handling mode
*/
void setOutputChannelMode(OutputChannelMode mode);
/**
* Query how the output channels of the child process are handled.
*
* @return the output channel handling mode
*/
OutputChannelMode outputChannelMode() const;
/**
* Set the QIODevice open mode the process will be opened in.
*
* This function must be called before starting the process, obviously.
*
* @param mode the open mode. Note that this mode is automatically
* "reduced" according to the channel modes and redirections.
* The default is QIODevice::ReadWrite.
*/
void setNextOpenMode(QIODevice::OpenMode mode);
/**
* Adds the variable @p name to the process' environment.
*
* This function must be called before starting the process.
*
* @param name the name of the environment variable
* @param value the new value for the environment variable
* @param overwrite if @c false and the environment variable is already
* set, the old value will be preserved
*/
void setEnv(const QString &name, const QString &value, bool overwrite = true);
/**
* Removes the variable @p name from the process' environment.
*
* This function must be called before starting the process.
*
* @param name the name of the environment variable
*/
void unsetEnv(const QString &name);
/**
* Empties the process' environment.
*
* Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added
* on *NIX.
*
* This function must be called before starting the process.
*/
void clearEnvironment();
/**
* Set the program and the command line arguments.
*
* This function must be called before starting the process, obviously.
*
* @param exe the program to execute
* @param args the command line arguments for the program,
* one per list element
*/
void setProgram(const QString &exe, const QStringList &args = QStringList());
/**
* @overload
*
* @param argv the program to execute and the command line arguments
* for the program, one per list element
*/
void setProgram(const QStringList &argv);
/**
* Append an element to the command line argument list for this process.
*
* If no executable is set yet, it will be set instead.
*
* For example, doing an "ls -l /usr/local/bin" can be achieved by:
* \code
* KProcess p;
* p << "ls" << "-l" << "/usr/local/bin";
* ...
* \endcode
*
* This function must be called before starting the process, obviously.
*
* @param arg the argument to add
* @return a reference to this KProcess
*/
KProcess &operator<<(const QString &arg);
/**
* @overload
*
* @param args the arguments to add
* @return a reference to this KProcess
*/
KProcess &operator<<(const QStringList &args);
/**
* Clear the program and command line argument list.
*/
void clearProgram();
/**
* Set a command to execute through a shell (a POSIX sh on *NIX
* and cmd.exe on Windows).
*
* Using this for anything but user-supplied commands is usually a bad
* idea, as the command's syntax depends on the platform.
* Redirections including pipes, etc. are better handled by the
* respective functions provided by QProcess.
*
* If KProcess determines that the command does not really need a
* shell, it will transparently execute it without one for performance
* reasons.
*
* This function must be called before starting the process, obviously.
*
* @param cmd the command to execute through a shell.
* The caller must make sure that all filenames etc. are properly
* quoted when passed as argument. Failure to do so often results in
* serious security holes. See KShell::quoteArg().
*/
void setShellCommand(const QString &cmd);
/**
* Obtain the currently set program and arguments.
*
* @return a list, the first element being the program, the remaining ones
* being command line arguments to the program.
*/
QStringList program() const;
/**
* Start the process.
*
* @see QProcess::start(const QString &, const QStringList &, OpenMode)
*/
void start();
/**
* Start the process, wait for it to finish, and return the exit code.
*
* This method is roughly equivalent to the sequence:
* @code
* start();
* waitForFinished(msecs);
* return exitCode();
* @endcode
*
* Unlike the other execute() variants this method is not static,
* so the process can be parametrized properly and talked to.
*
* @param msecs time to wait for process to exit before killing it
* @return -2 if the process could not be started, -1 if it crashed,
* otherwise its exit code
*/
int execute(int msecs = -1);
/**
* @overload
*
* @param exe the program to execute
* @param args the command line arguments for the program,
* one per list element
* @param msecs time to wait for process to exit before killing it
* @return -2 if the process could not be started, -1 if it crashed,
* otherwise its exit code
*/
static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1);
/**
* @overload
*
* @param argv the program to execute and the command line arguments
* for the program, one per list element
* @param msecs time to wait for process to exit before killing it
* @return -2 if the process could not be started, -1 if it crashed,
* otherwise its exit code
*/
static int execute(const QStringList &argv, int msecs = -1);
/**
* Start the process and detach from it. See QProcess::startDetached()
* for details.
*
* Unlike the other startDetached() variants this method is not static,
* so the process can be parametrized properly.
* @note Currently, only the setProgram()/setShellCommand() and
* setWorkingDirectory() parametrizations are supported.
*
* The KProcess object may be re-used immediately after calling this
* function.
*
* @return the PID of the started process or 0 on error
*/
int startDetached();
/**
* @overload
*
* @param exe the program to start
* @param args the command line arguments for the program,
* one per list element
* @return the PID of the started process or 0 on error
*/
static int startDetached(const QString &exe, const QStringList &args = QStringList());
/**
* @overload
*
* @param argv the program to start and the command line arguments
* for the program, one per list element
* @return the PID of the started process or 0 on error
*/
static int startDetached(const QStringList &argv);
protected:
/**
* @internal
*/
KCOREADDONS_NO_EXPORT KProcess(KProcessPrivate *d, QObject *parent);
/**
* @internal
*/
std::unique_ptr<KProcessPrivate> const d_ptr;
private:
// hide those
using QProcess::processChannelMode;
using QProcess::setProcessChannelMode;
};
#endif
@@ -0,0 +1,29 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPROCESS_P_H
#define KPROCESS_P_H
#include "kprocess.h"
class KProcessPrivate
{
Q_DECLARE_PUBLIC(KProcess)
protected:
KProcessPrivate(KProcess *qq)
: openMode(QIODevice::ReadWrite)
, q_ptr(qq)
{
}
QIODevice::OpenMode openMode;
KProcess *q_ptr;
};
#endif
@@ -0,0 +1,401 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005-2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022-2023 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlmimedata.h"
#include "config-kdirwatch.h"
#if HAVE_QTDBUS // not used outside dbus/xdg-portal related code
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <optional>
#include <QMimeData>
#include <QStringList>
#include "kcoreaddons_debug.h"
#if HAVE_QTDBUS
#include "org.freedesktop.portal.FileTransfer.h"
#include "org.kde.KIOFuse.VFS.h"
#endif
#include "kurlmimedata_p.h"
static QString kdeUriListMime()
{
return QStringLiteral("application/x-kde4-urilist");
} // keep this name "kde4" for compat.
static QByteArray uriListData(const QList<QUrl> &urls)
{
// compatible with qmimedata.cpp encoding of QUrls
QByteArray result;
for (int i = 0; i < urls.size(); ++i) {
result += urls.at(i).toEncoded();
result += "\r\n";
}
return result;
}
void KUrlMimeData::setUrls(const QList<QUrl> &urls, const QList<QUrl> &mostLocalUrls, QMimeData *mimeData)
{
// Export the most local urls as text/uri-list and plain text, for non KDE apps.
mimeData->setUrls(mostLocalUrls); // set text/uri-list and text/plain
// Export the real KIO urls as a kde-specific mimetype
mimeData->setData(kdeUriListMime(), uriListData(urls));
}
void KUrlMimeData::setMetaData(const MetaDataMap &metaData, QMimeData *mimeData)
{
QByteArray metaDataData; // :)
for (auto it = metaData.cbegin(); it != metaData.cend(); ++it) {
metaDataData += it.key().toUtf8();
metaDataData += "$@@$";
metaDataData += it.value().toUtf8();
metaDataData += "$@@$";
}
mimeData->setData(QStringLiteral("application/x-kio-metadata"), metaDataData);
}
QStringList KUrlMimeData::mimeDataTypes()
{
return QStringList{kdeUriListMime(), QStringLiteral("text/uri-list")};
}
static QList<QUrl> extractKdeUriList(const QMimeData *mimeData)
{
QList<QUrl> uris;
const QByteArray ba = mimeData->data(kdeUriListMime());
// Code from qmimedata.cpp
QList<QByteArray> urls = ba.split('\n');
uris.reserve(urls.size());
for (int i = 0; i < urls.size(); ++i) {
QByteArray data = urls.at(i).trimmed();
if (!data.isEmpty()) {
uris.append(QUrl::fromEncoded(data));
}
}
return uris;
}
#if HAVE_QTDBUS
static QString kioFuseServiceName()
{
return QStringLiteral("org.kde.KIOFuse");
}
static QString portalServiceName()
{
return QStringLiteral("org.freedesktop.portal.Documents");
}
static bool isKIOFuseAvailable()
{
static bool available = QDBusConnection::sessionBus().interface()
&& QDBusConnection::sessionBus().interface()->activatableServiceNames().value().contains(kioFuseServiceName());
return available;
}
bool KUrlMimeData::isDocumentsPortalAvailable()
{
static bool available =
QDBusConnection::sessionBus().interface() && QDBusConnection::sessionBus().interface()->activatableServiceNames().value().contains(portalServiceName());
return available;
}
static QString portalFormat()
{
return QStringLiteral("application/vnd.portal.filetransfer");
}
static QList<QUrl> extractPortalUriList(const QMimeData *mimeData)
{
Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
static std::pair<QByteArray, QList<QUrl>> cache;
const auto transferId = mimeData->data(portalFormat());
qCDebug(KCOREADDONS_DEBUG) << "Picking up portal urls from transfer" << transferId;
if (std::get<QByteArray>(cache) == transferId) {
const auto uris = std::get<QList<QUrl>>(cache);
qCDebug(KCOREADDONS_DEBUG) << "Urls from portal cache" << uris;
return uris;
}
auto iface =
new OrgFreedesktopPortalFileTransferInterface(portalServiceName(), QStringLiteral("/org/freedesktop/portal/documents"), QDBusConnection::sessionBus());
const QDBusReply<QStringList> reply = iface->RetrieveFiles(QString::fromUtf8(transferId), {});
if (!reply.isValid()) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to retrieve files from portal:" << reply.error();
return {};
}
const QStringList list = reply.value();
QList<QUrl> uris;
uris.reserve(list.size());
for (const auto &path : list) {
uris.append(QUrl::fromLocalFile(path));
}
qCDebug(KCOREADDONS_DEBUG) << "Urls from portal" << uris;
cache = std::make_pair(transferId, uris);
return uris;
}
static QString sourceIdMime()
{
return QStringLiteral("application/x-kde-source-id");
}
static QString sourceId()
{
return QDBusConnection::sessionBus().baseService();
}
void KUrlMimeData::setSourceId(QMimeData *mimeData)
{
mimeData->setData(sourceIdMime(), sourceId().toUtf8());
}
static bool hasSameSourceId(const QMimeData *mimeData)
{
return mimeData->hasFormat(sourceIdMime()) && mimeData->data(sourceIdMime()) == sourceId().toUtf8();
}
#endif
QList<QUrl> KUrlMimeData::urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions, MetaDataMap *metaData)
{
QList<QUrl> uris;
#if HAVE_QTDBUS
if (!hasSameSourceId(mimeData) && isDocumentsPortalAvailable() && mimeData->hasFormat(portalFormat())) {
uris = extractPortalUriList(mimeData);
if (static const auto force = qEnvironmentVariableIntValue("KCOREADDONS_FORCE_DOCUMENTS_PORTAL"); force == 1) {
// The environment variable is FOR TESTING ONLY!
// It is used to prevent the fallback logic from running.
return uris;
}
}
#endif
if (uris.isEmpty()) {
if (decodeOptions.testFlag(PreferLocalUrls)) {
// Extracting uris from text/uri-list, use the much faster QMimeData method urls()
uris = mimeData->urls();
if (uris.isEmpty()) {
uris = extractKdeUriList(mimeData);
}
} else {
uris = extractKdeUriList(mimeData);
if (uris.isEmpty()) {
uris = mimeData->urls();
}
}
}
if (metaData) {
const QByteArray metaDataPayload = mimeData->data(QStringLiteral("application/x-kio-metadata"));
if (!metaDataPayload.isEmpty()) {
QString str = QString::fromUtf8(metaDataPayload.constData());
Q_ASSERT(str.endsWith(QLatin1String("$@@$")));
str.chop(4);
const QStringList lst = str.split(QStringLiteral("$@@$"));
bool readingKey = true; // true, then false, then true, etc.
QString key;
for (const QString &s : lst) {
if (readingKey) {
key = s;
} else {
metaData->insert(key, s);
}
readingKey = !readingKey;
}
Q_ASSERT(readingKey); // an odd number of items would be, well, odd ;-)
}
}
return uris;
}
#if HAVE_QTDBUS
static QStringList urlListToStringList(const QList<QUrl> urls)
{
QStringList list;
for (const auto &url : urls) {
list << url.toLocalFile();
}
return list;
}
static std::optional<QStringList> fuseRedirect(QList<QUrl> urls, bool onlyLocalFiles)
{
qCDebug(KCOREADDONS_DEBUG) << "mounting urls with fuse" << urls;
// Fuse redirection only applies if the list contains non-local files.
if (onlyLocalFiles) {
return urlListToStringList(urls);
}
OrgKdeKIOFuseVFSInterface kiofuse_iface(kioFuseServiceName(), QStringLiteral("/org/kde/KIOFuse"), QDBusConnection::sessionBus());
struct MountRequest {
QDBusPendingReply<QString> reply;
int urlIndex;
QString basename;
};
QList<MountRequest> requests;
requests.reserve(urls.count());
for (int i = 0; i < urls.count(); ++i) {
QUrl url = urls.at(i);
if (!url.isLocalFile()) {
const QString path(url.path());
const int slashes = path.count(QLatin1Char('/'));
QString basename;
if (slashes > 1) {
url.setPath(path.section(QLatin1Char('/'), 0, slashes - 1));
basename = path.section(QLatin1Char('/'), slashes, slashes);
}
requests.push_back({kiofuse_iface.mountUrl(url.toString()), i, basename});
}
}
for (auto &request : requests) {
request.reply.waitForFinished();
if (request.reply.isError()) {
qWarning() << "FUSE request failed:" << request.reply.error();
return std::nullopt;
}
urls[request.urlIndex] = QUrl::fromLocalFile(request.reply.value() + QLatin1Char('/') + request.basename);
};
qCDebug(KCOREADDONS_DEBUG) << "mounted urls with fuse, maybe" << urls;
return urlListToStringList(urls);
}
#endif
bool KUrlMimeData::exportUrlsToPortal(QMimeData *mimeData)
{
#if HAVE_QTDBUS
if (!isDocumentsPortalAvailable()) {
return false;
}
QList<QUrl> urls = mimeData->urls();
bool onlyLocalFiles = true;
for (const auto &url : urls) {
const auto isLocal = url.isLocalFile();
if (!isLocal) {
onlyLocalFiles = false;
// For the time being the fuse redirection is opt-in because we later need to open() the files
// and this is an insanely expensive operation involving a stat() for remote URLs that we can't
// really get rid of. We'll need a way to avoid the open().
// https://bugs.kde.org/show_bug.cgi?id=457529
// https://github.com/flatpak/xdg-desktop-portal/issues/961
static const auto fuseRedirect = qEnvironmentVariableIntValue("KCOREADDONS_FUSE_REDIRECT");
if (!fuseRedirect) {
return false;
}
// some remotes, fusing is enabled, but kio-fuse is unavailable -> cannot run this url list through the portal
if (!isKIOFuseAvailable()) {
qWarning() << "kio-fuse is missing";
return false;
}
} else {
const QFileInfo info(url.toLocalFile());
if (info.isSymbolicLink()) {
// XDG Document Portal also doesn't support symlinks since it doesn't let us open the fd O_NOFOLLOW.
// https://github.com/flatpak/xdg-desktop-portal/issues/961#issuecomment-1573646299
return false;
}
}
}
auto iface =
new OrgFreedesktopPortalFileTransferInterface(portalServiceName(), QStringLiteral("/org/freedesktop/portal/documents"), QDBusConnection::sessionBus());
// Do not autostop, we'll stop once our mimedata disappears (i.e. the drag operation has finished);
// Otherwise not-wellbehaved clients that read the urls multiple times will trip the automatic-transfer-
// closing-upon-read inside the portal and have any reads, but the first, not properly resolve anymore.
const QString transferId = iface->StartTransfer({{QStringLiteral("autostop"), QVariant::fromValue(false)}});
auto cleanup = qScopeGuard([transferId, iface] {
iface->StopTransfer(transferId);
iface->deleteLater();
});
auto optionalPaths = fuseRedirect(urls, onlyLocalFiles);
if (!optionalPaths.has_value()) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to mount with fuse!";
return false;
}
// Prevent running into "too many open files" errors.
// Because submission of calls happens on the qdbus thread we may be feeding
// it QDBusUnixFileDescriptors faster than it can submit them over the wire, this would eventually
// lead to running into the open file cap since the QDBusUnixFileDescriptor hold
// an open FD until their call has been made.
// To prevent this from happening we collect a submission batch, make the call and **wait** for
// the call to succeed.
FDList pendingFds;
static constexpr decltype(pendingFds.size()) maximumBatchSize = 16;
pendingFds.reserve(maximumBatchSize);
const auto addFilesAndClear = [transferId, &iface, &pendingFds]() {
if (pendingFds.isEmpty()) {
return true;
}
auto reply = iface->AddFiles(transferId, pendingFds, {});
reply.waitForFinished();
if (reply.isError()) {
qCWarning(KCOREADDONS_DEBUG) << "Some files could not be exported. " << reply.error();
return false;
}
pendingFds.clear();
return true;
};
for (const auto &path : optionalPaths.value()) {
const int fd = open(QFile::encodeName(path).constData(), O_RDONLY | O_CLOEXEC | O_NONBLOCK);
if (fd == -1) {
const int error = errno;
qCWarning(KCOREADDONS_DEBUG) << "Failed to open" << path << strerror(error);
return false;
}
pendingFds << QDBusUnixFileDescriptor(fd);
close(fd);
if (pendingFds.size() >= maximumBatchSize) {
if (!addFilesAndClear()) {
return false;
}
}
}
if (!addFilesAndClear()) {
return false;
}
cleanup.dismiss();
QObject::connect(mimeData, &QObject::destroyed, iface, [transferId, iface] {
iface->StopTransfer(transferId);
iface->deleteLater();
});
QObject::connect(iface, &OrgFreedesktopPortalFileTransferInterface::TransferClosed, mimeData, [iface]() {
iface->deleteLater();
});
mimeData->setData(QStringLiteral("application/vnd.portal.filetransfer"), QFile::encodeName(transferId));
setSourceId(mimeData);
return true;
#else
Q_UNUSED(mimeData);
return false;
#endif
}
@@ -0,0 +1,110 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005-2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLMIMEDATA_H
#define KURLMIMEDATA_H
#include "kcoreaddons_export.h"
#include <QFlags>
#include <QMap>
#include <QUrl>
QT_BEGIN_NAMESPACE
class QMimeData;
QT_END_NAMESPACE
/**
* Utility functions for using URLs in QMimeData.
* In addition to QMimeData::setUrls() and QMimeData::urls(), these functions allow to:
*
* - Store two sets of URLs, the KDE-specific URLs and the equivalent local-file URLs
* for compatibility with non-KDE applications
* - Store KIO metadata, such as the HTTP referrer for a given URL (some websites
* require it for downloading e.g. an image)
*
* @since 5.0
*/
namespace KUrlMimeData
{
typedef QMap<QString, QString> MetaDataMap;
/**
* Adds URLs and KIO metadata into the given QMimeData.
*
* WARNING: do not call this method multiple times on the same mimedata object,
* you can add urls only once. But you can add other things, e.g. images, XML...
*
* @param mimeData the QMimeData instance used to drag or copy this URL
*/
KCOREADDONS_EXPORT void setUrls(const QList<QUrl> &urls, const QList<QUrl> &mostLocalUrls, QMimeData *mimeData);
/**
* Export URLs through the XDG Documents Portal to allow interaction from/with sandbox code.
* This implements the application/vnd.portal.filetransfer mimetype extension.
* When built without dbus support or the portal isn't installed on the target system, then this
* is no-op and returns false.
* Remote URLs are automatically mounted into the file system using kio-fuse
* @returns whether all URLS were exported through the portal
*/
KCOREADDONS_EXPORT bool exportUrlsToPortal(QMimeData *mimeData);
/**
* @param metaData KIO metadata shipped in the mime data, which is used for instance to
* set a correct HTTP referrer (some websites require it for downloading e.g. an image)
*/
KCOREADDONS_EXPORT void setMetaData(const MetaDataMap &metaData, QMimeData *mimeData);
/**
* Return the list of mimeTypes that can be decoded by urlsFromMimeData
*/
KCOREADDONS_EXPORT QStringList mimeDataTypes();
/**
* Flags to be used in urlsFromMimeData.
*/
enum DecodeOption {
/**
* When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and
* the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo),
* decode it as the KDE-style URL. Useful in DnD code e.g. when moving icons,
* and the kde-style url is used as identifier for the icons.
*/
PreferKdeUrls = 0,
/**
* When the mimedata contains both KDE-style URLs (eg: desktop:/foo) and
* the "most local" version of the URLs (eg: file:///home/dfaure/Desktop/foo),
* decode it as local urls. Useful in paste/drop operations that end up calling KIO,
* so that urls from other users work as well.
*/
PreferLocalUrls = 1,
};
Q_DECLARE_FLAGS(DecodeOptions, DecodeOption)
Q_DECLARE_OPERATORS_FOR_FLAGS(DecodeOptions)
/**
* Extract a list of urls from the contents of @p mimeData.
*
* Compared to QMimeData::urls(), this method has support for retrieving KDE-specific URLs
* when urls() would retrieve "most local URLs" instead as well as support for the XDG Documents Portal
* via the application/vnd.portal.filetransfer mimedata extension.
*
* Decoding will fail if @p mimeData does not contain any URLs, or if at
* least one extracted URL is not valid.
*
* When application/vnd.portal.filetransfer is set you'll only receive URLs retrieved from the XDG Documents Portal.
* When the portal is not available application/vnd.portal.filetransfer gets ignored.
*
* @param mimeData the mime data to extract from; cannot be 0
* @param decodeOptions options for decoding
* @param metaData optional pointer to a map which will hold the metadata after this call
* @return the list of urls
*/
KCOREADDONS_EXPORT QList<QUrl> urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions = PreferKdeUrls, MetaDataMap *metaData = nullptr);
}
#endif /* KURLMIMEDATA_H */
@@ -0,0 +1,19 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2023 Jin Liu <m.liu.jin@gmial.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
// Internal functions for tests
namespace KUrlMimeData
{
#if HAVE_QTDBUS
KCOREADDONS_EXPORT bool isDocumentsPortalAvailable();
KCOREADDONS_EXPORT void setSourceId(QMimeData *mimeData);
#endif // HAVE_QTDBUS
}
@@ -0,0 +1,33 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
SPDX-License-Identifier: CC0-1.0
SPDX-FileCopyrightText: none
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="org.freedesktop.portal.FileTransfer">
<method name="StartTransfer">
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap" />
<arg type="s" name="key" direction="out"/>
</method>
<method name="AddFiles">
<arg type="s" name="key" direction="in"/>
<arg type="ah" name="fds" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="FDList" />
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap" />
</method>
<method name="RetrieveFiles">
<arg type="s" name="key" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap" />
<arg type="as" name="files" direction="out"/>
</method>
<method name="StopTransfer">
<arg type="s" name="key" direction="in"/>
</method>
<signal name="TransferClosed">
<arg type="s" name="key"/>
</signal>
</interface>
</node>
@@ -0,0 +1,17 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
SPDX-License-Identifier: CC0-1.0
SPDX-FileCopyrightText: none
-->
<node>
<interface name="org.kde.KIOFuse.VFS">
<method name="remoteUrl">
<arg type="s" direction="out"/>
<arg name="localPath" type="s" direction="in"/>
</method>
<method name="mountUrl">
<arg name="remoteUrl" type="s" direction="in"/>
<arg type="s" direction="out"/>
</method>
</interface>
</node>
@@ -0,0 +1,105 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcompositejob.h"
#include "kcompositejob_p.h"
KCompositeJobPrivate::KCompositeJobPrivate()
{
}
KCompositeJobPrivate::~KCompositeJobPrivate()
{
}
KCompositeJob::KCompositeJob(QObject *parent)
: KJob(*new KCompositeJobPrivate, parent)
{
}
KCompositeJob::KCompositeJob(KCompositeJobPrivate &dd, QObject *parent)
: KJob(dd, parent)
{
}
KCompositeJob::~KCompositeJob()
{
}
bool KCompositeJob::addSubjob(KJob *job)
{
Q_D(KCompositeJob);
if (job == nullptr || d->subjobs.contains(job)) {
return false;
}
job->setParent(this);
d->subjobs.append(job);
connect(job, &KJob::result, this, &KCompositeJob::slotResult);
// Forward information from that subjob.
connect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage);
return true;
}
bool KCompositeJob::removeSubjob(KJob *job)
{
Q_D(KCompositeJob);
// remove only Subjobs that are on the list
if (d->subjobs.removeAll(job) > 0) {
job->setParent(nullptr);
disconnect(job, &KJob::result, this, &KCompositeJob::slotResult);
disconnect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage);
return true;
}
return false;
}
bool KCompositeJob::hasSubjobs() const
{
return !d_func()->subjobs.isEmpty();
}
const QList<KJob *> &KCompositeJob::subjobs() const
{
return d_func()->subjobs;
}
void KCompositeJob::clearSubjobs()
{
Q_D(KCompositeJob);
for (KJob *job : std::as_const(d->subjobs)) {
job->setParent(nullptr);
disconnect(job, &KJob::result, this, &KCompositeJob::slotResult);
disconnect(job, &KJob::infoMessage, this, &KCompositeJob::slotInfoMessage);
}
d->subjobs.clear();
}
void KCompositeJob::slotResult(KJob *job)
{
// Did job have an error ?
if (job->error() && !error()) {
// Store it in the parent only if first error
setError(job->error());
setErrorText(job->errorText());
// Finish this job
emitResult();
}
// After a subjob is done, we might want to start another one.
// Therefore do not emitResult
removeSubjob(job);
}
void KCompositeJob::slotInfoMessage(KJob *job, const QString &message)
{
Q_EMIT infoMessage(job, message);
}
#include "moc_kcompositejob.cpp"
@@ -0,0 +1,112 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOMPOSITEJOB_H
#define KCOMPOSITEJOB_H
#include <kcoreaddons_export.h>
#include <kjob.h>
#include <QList>
class KCompositeJobPrivate;
/**
* @class KCompositeJob kcompositejob.h KCompositeJob
*
* The base class for all jobs able to be composed of one
* or more subjobs.
*/
class KCOREADDONS_EXPORT KCompositeJob : public KJob
{
Q_OBJECT
public:
/**
* Creates a new KCompositeJob object.
*
* @param parent the parent QObject
*/
explicit KCompositeJob(QObject *parent = nullptr);
/**
* Destroys a KCompositeJob object.
*/
~KCompositeJob() override;
protected:
/**
* Add a job that has to be finished before a result
* is emitted. This has obviously to be called before
* the result has been emitted by the job.
*
* Note that the composite job takes ownership of @p job
*
* @param job the subjob to add
* @return true if the job has been added correctly, false otherwise
*/
virtual bool addSubjob(KJob *job);
/**
* Mark a sub job as being done.
*
* The ownership of @p job is passed on to the caller.
*
* @param job the subjob to remove
* @return true if the job has been removed correctly, false otherwise
*/
virtual bool removeSubjob(KJob *job);
/**
* Checks if this job has subjobs running.
*
* @return true if we still have subjobs running, false otherwise
*/
bool hasSubjobs() const;
/**
* Retrieves the list of the subjobs.
*
* @return the full list of sub jobs
*/
const QList<KJob *> &subjobs() const;
/**
* Clears the list of subjobs.
*
* Note that this will *not* delete the subjobs.
* Ownership of the subjobs is passed on to the caller.
*/
void clearSubjobs();
protected Q_SLOTS:
/**
* Called whenever a subjob finishes.
* Default implementation checks for errors and propagates
* to parent job, and in all cases it calls removeSubjob.
*
* @param job the subjob
*/
virtual void slotResult(KJob *job);
/**
* Forward signal from subjob.
*
* @param job the subjob
* @param message the info message
* @see infoMessage()
*/
virtual void slotInfoMessage(KJob *job, const QString &message);
protected:
KCOREADDONS_NO_EXPORT KCompositeJob(KCompositeJobPrivate &dd, QObject *parent);
private:
Q_DECLARE_PRIVATE(KCompositeJob)
};
#endif
@@ -0,0 +1,30 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOMPOSITEJOB_P_H
#define KCOMPOSITEJOB_P_H
#include "kcompositejob.h"
#include "kjob_p.h"
// This is a private class, but it's exported for
// KIO::Job's usage. Other Job classes in kdelibs may
// use it too.
class KCOREADDONS_EXPORT KCompositeJobPrivate : public KJobPrivate
{
public:
KCompositeJobPrivate();
~KCompositeJobPrivate() override;
QList<KJob *> subjobs;
Q_DECLARE_PUBLIC(KCompositeJob)
};
#endif
@@ -0,0 +1,421 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kjob.h"
#include "kjob_p.h"
#include "kcoreaddons_debug.h"
#include "kjobuidelegate.h"
#include <QElapsedTimer>
#include <QEventLoop>
#include <QTimer>
KJobPrivate::KJobPrivate()
{
}
KJobPrivate::~KJobPrivate()
{
}
KJob::KJob(QObject *parent)
: QObject(parent)
, d_ptr(new KJobPrivate)
{
d_ptr->q_ptr = this;
}
KJob::KJob(KJobPrivate &dd, QObject *parent)
: QObject(parent)
, d_ptr(&dd)
{
d_ptr->q_ptr = this;
}
KJob::~KJob()
{
if (!d_ptr->isFinished) {
d_ptr->isFinished = true;
Q_EMIT finished(this, QPrivateSignal());
}
delete d_ptr->speedTimer;
delete d_ptr->uiDelegate;
}
void KJob::setUiDelegate(KJobUiDelegate *delegate)
{
Q_D(KJob);
if (!delegate) {
delete d->uiDelegate;
d->uiDelegate = nullptr;
return;
}
if (delegate->setJob(this)) {
delete d->uiDelegate;
d->uiDelegate = delegate;
d->uiDelegate->connectJob(this);
}
}
qint64 KJob::elapsedTime() const
{
Q_D(const KJob);
return d->accumulatedElapsedTime + (d->elapsedTimer ? d->elapsedTimer->elapsed() : 0);
}
void KJob::startElapsedTimer()
{
Q_D(KJob);
if (!d->elapsedTimer) {
d->elapsedTimer = std::make_unique<QElapsedTimer>();
}
d->elapsedTimer->start();
d->accumulatedElapsedTime = 0;
}
KJobUiDelegate *KJob::uiDelegate() const
{
return d_func()->uiDelegate;
}
KJob::Capabilities KJob::capabilities() const
{
return d_func()->capabilities;
}
bool KJob::isSuspended() const
{
return d_func()->suspended;
}
void KJob::finishJob(bool emitResult)
{
Q_D(KJob);
Q_ASSERT(!d->isFinished);
d->isFinished = true;
if (d->eventLoop) {
d->eventLoop->quit();
}
// If we are displaying a progress dialog, remove it first.
Q_EMIT finished(this, QPrivateSignal());
if (emitResult) {
Q_EMIT result(this, QPrivateSignal());
}
if (isAutoDelete()) {
deleteLater();
}
}
bool KJob::kill(KillVerbosity verbosity)
{
Q_D(KJob);
if (d->isFinished) {
return true;
}
if (doKill()) {
// A subclass can (but should not) call emitResult() or kill()
// from doKill() and thus set isFinished to true.
if (!d->isFinished) {
setError(KilledJobError);
finishJob(verbosity != Quietly);
}
return true;
} else {
return false;
}
}
bool KJob::suspend()
{
Q_D(KJob);
if (!d->suspended) {
if (doSuspend()) {
d->suspended = true;
if (d->elapsedTimer) {
d->accumulatedElapsedTime += d->elapsedTimer->elapsed();
}
d->elapsedTimer.reset();
Q_EMIT suspended(this, QPrivateSignal());
return true;
}
}
return false;
}
bool KJob::resume()
{
Q_D(KJob);
if (d->suspended) {
if (doResume()) {
d->suspended = false;
// If the timer was never started previously, the reported time is wrong, so we rather keep it at zero.
if (d->accumulatedElapsedTime > 0) {
d->elapsedTimer = std::make_unique<QElapsedTimer>();
d->elapsedTimer->start();
}
Q_EMIT resumed(this, QPrivateSignal());
return true;
}
}
return false;
}
bool KJob::doKill()
{
return false;
}
bool KJob::doSuspend()
{
return false;
}
bool KJob::doResume()
{
return false;
}
void KJob::setCapabilities(KJob::Capabilities capabilities)
{
Q_D(KJob);
d->capabilities = capabilities;
}
bool KJob::exec()
{
Q_D(KJob);
// Usually this job would delete itself, via deleteLater() just after
// emitting result() (unless configured otherwise). Since we use an event
// loop below, that event loop will process the deletion event and we'll
// have been deleted when exec() returns. This crashes, so temporarily
// suspend autodeletion and manually do it afterwards.
const bool wasAutoDelete = isAutoDelete();
setAutoDelete(false);
Q_ASSERT(!d->eventLoop);
QEventLoop loop(this);
d->eventLoop = &loop;
start();
if (!d->isFinished) {
d->m_startedWithExec = true;
d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents);
}
d->eventLoop = nullptr;
if (wasAutoDelete) {
deleteLater();
}
return (d->error == NoError);
}
int KJob::error() const
{
return d_func()->error;
}
QString KJob::errorText() const
{
return d_func()->errorText;
}
QString KJob::errorString() const
{
return d_func()->errorText;
}
qulonglong KJob::processedAmount(Unit unit) const
{
if (unit >= UnitsCount) {
qCWarning(KCOREADDONS_DEBUG) << "KJob::processedAmount() was called on an invalid Unit" << unit;
return 0;
}
return d_func()->m_jobAmounts[unit].processedAmount;
}
qulonglong KJob::totalAmount(Unit unit) const
{
if (unit >= UnitsCount) {
qCWarning(KCOREADDONS_DEBUG) << "KJob::totalAmount() was called on an invalid Unit" << unit;
return 0;
}
return d_func()->m_jobAmounts[unit].totalAmount;
}
unsigned long KJob::percent() const
{
return d_func()->percentage;
}
bool KJob::isFinished() const
{
return d_func()->isFinished;
}
void KJob::setError(int errorCode)
{
Q_D(KJob);
d->error = errorCode;
}
void KJob::setErrorText(const QString &errorText)
{
Q_D(KJob);
d->errorText = errorText;
}
void KJob::setProcessedAmount(Unit unit, qulonglong amount)
{
if (unit >= UnitsCount) {
qCWarning(KCOREADDONS_DEBUG) << "KJob::setProcessedAmount() was called on an invalid Unit" << unit;
return;
}
Q_D(KJob);
auto &[processed, total] = d->m_jobAmounts[unit];
const bool should_emit = (processed != amount);
processed = amount;
if (should_emit) {
Q_EMIT processedAmountChanged(this, unit, amount, QPrivateSignal{});
if (unit == d->progressUnit) {
Q_EMIT processedSize(this, amount);
emitPercent(processed, total);
}
}
}
void KJob::setTotalAmount(Unit unit, qulonglong amount)
{
if (unit >= UnitsCount) {
qCWarning(KCOREADDONS_DEBUG) << "KJob::setTotalAmount() was called on an invalid Unit" << unit;
return;
}
Q_D(KJob);
auto &[processed, total] = d->m_jobAmounts[unit];
const bool should_emit = (total != amount);
total = amount;
if (should_emit) {
Q_EMIT totalAmountChanged(this, unit, amount, QPrivateSignal{});
if (unit == d->progressUnit) {
Q_EMIT totalSize(this, amount);
emitPercent(processed, total);
}
}
}
void KJob::setProgressUnit(Unit unit)
{
Q_D(KJob);
d->progressUnit = unit;
}
void KJob::setPercent(unsigned long percentage)
{
Q_D(KJob);
if (d->percentage != percentage) {
d->percentage = percentage;
Q_EMIT percentChanged(this, percentage, QPrivateSignal{});
}
}
void KJob::emitResult()
{
if (!d_func()->isFinished) {
finishJob(true);
}
}
void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount)
{
if (totalAmount != 0) {
setPercent(100.0 * processedAmount / totalAmount);
}
}
void KJob::emitSpeed(unsigned long value)
{
Q_D(KJob);
if (!d->speedTimer) {
d->speedTimer = new QTimer(this);
connect(d->speedTimer, &QTimer::timeout, this, [d]() {
d->speedTimeout();
});
}
Q_EMIT speed(this, value);
d->speedTimer->start(5000); // 5 seconds interval should be enough
}
void KJobPrivate::speedTimeout()
{
Q_Q(KJob);
// send 0 and stop the timer
// timer will be restarted only when we receive another speed event
Q_EMIT q->speed(q, 0);
speedTimer->stop();
}
bool KJob::isAutoDelete() const
{
Q_D(const KJob);
return d->isAutoDelete;
}
void KJob::setAutoDelete(bool autodelete)
{
Q_D(KJob);
d->isAutoDelete = autodelete;
}
void KJob::setFinishedNotificationHidden(bool hide)
{
Q_D(KJob);
d->m_hideFinishedNotification = hide;
}
bool KJob::isFinishedNotificationHidden() const
{
Q_D(const KJob);
return d->m_hideFinishedNotification;
}
bool KJob::isStartedWithExec() const
{
Q_D(const KJob);
return d->m_startedWithExec;
}
#include "moc_kjob.cpp"
@@ -0,0 +1,787 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KJOB_H
#define KJOB_H
#include <QObject>
#include <QPair>
#include <kcoreaddons_export.h>
#include <memory>
class KJobUiDelegate;
class KJobPrivate;
/**
* @class KJob kjob.h KJob
*
* The base class for all jobs.
* For all jobs created in an application, the code looks like
*
* \code
* void SomeClass::methodWithAsynchronousJobCall()
* {
* KJob *job = someoperation(some parameters);
* connect(job, &KJob::result, this, &SomeClass::handleResult);
* job->start();
* }
* \endcode
* (other connects, specific to the job)
*
* And handleResult is usually at least:
*
* \code
* void SomeClass::handleResult(KJob *job)
* {
* if (job->error()) {
* doSomething();
* }
* }
* \endcode
*
* With the synchronous interface the code looks like
*
* \code
* void SomeClass::methodWithSynchronousJobCall()
* {
* KJob *job = someoperation( some parameters );
* if (!job->exec()) {
* // An error occurred
* } else {
* // Do something
* }
* }
* \endcode
*
* Subclasses must implement start(), which should trigger the execution of
* the job (although the work should be done asynchronously). errorString()
* should also be reimplemented by any subclasses that introduce new error
* codes.
*
* @note KJob and its subclasses are meant to be used in a fire-and-forget way.
* Jobs will delete themselves when they finish using deleteLater() (although
* this behaviour can be changed), so a job instance will disappear after the
* next event loop run.
*/
class KCOREADDONS_EXPORT KJob : public QObject
{
Q_OBJECT
Q_PROPERTY(int error READ error NOTIFY result)
Q_PROPERTY(QString errorText READ errorText NOTIFY result)
Q_PROPERTY(QString errorString READ errorString NOTIFY result)
Q_PROPERTY(ulong percent READ percent NOTIFY percentChanged) // KF6 TODO: make "int", is enough
Q_PROPERTY(Capabilities capabilities READ capabilities CONSTANT)
public:
/**
* Describes the unit used in the methods that handle reporting the job progress info.
* @see totalAmount
*/
enum Unit {
Bytes = 0, ///< Directory and file sizes in bytes
Files, ///< The number of files handled by the job
Directories, ///< The number of directories handled by the job
Items, ///< The number of items (e.g. both directories and files) handled by the job
///< @since 5.72
UnitsCount, ///< @internal since 5.87, used internally only, do not use.
};
Q_ENUM(Unit)
/**
* @see Capabilities
*/
enum Capability {
NoCapabilities = 0x0000, ///< None of the capabilities exist
Killable = 0x0001, ///< The job can be killed
Suspendable = 0x0002, ///< The job can be suspended
};
Q_ENUM(Capability)
/**
* Stores a combination of #Capability values.
*/
Q_DECLARE_FLAGS(Capabilities, Capability)
Q_FLAG(Capabilities)
/**
* Creates a new KJob object.
*
* @param parent the parent QObject
*/
explicit KJob(QObject *parent = nullptr);
/**
* Destroys a KJob object.
*/
~KJob() override;
/**
* Attach a UI delegate to this job.
*
* If the job had another UI delegate, it's automatically deleted. Once
* attached to the job, the UI delegate will be deleted with the job.
*
* @param delegate the new UI delegate to use
* @see KJobUiDelegate
*/
void setUiDelegate(KJobUiDelegate *delegate);
/**
* Retrieves the delegate attached to this job.
*
* @return the delegate attached to this job, or @c nullptr if there's no such delegate
*/
KJobUiDelegate *uiDelegate() const;
/**
* Returns the capabilities of this job.
*
* @return the capabilities that this job supports
* @see setCapabilities()
*/
Capabilities capabilities() const;
/**
* Returns if the job was suspended with the suspend() call.
*
* @return if the job was suspended
* @see suspend() resume()
*/
bool isSuspended() const;
/**
* Starts the job asynchronously.
*
* When the job is finished, result() is emitted.
*
* Warning: Never implement any synchronous workload in this method. This method
* should just trigger the job startup, not do any work itself. It is expected to
* be non-blocking.
*
* This is the method all subclasses need to implement.
* It should setup and trigger the workload of the job. It should not do any
* work itself. This includes all signals and terminating the job, e.g. by
* emitResult(). The workload, which could be another method of the
* subclass, is to be triggered using the event loop, e.g. by code like:
* \code
* void ExampleJob::start()
* {
* QTimer::singleShot(0, this, &ExampleJob::doWork);
* }
* \endcode
*/
// TODO KF7 make it non-virtual and expose a doStart protected instead
Q_SCRIPTABLE virtual void start() = 0;
enum KillVerbosity {
Quietly,
EmitResult,
};
Q_ENUM(KillVerbosity)
public Q_SLOTS:
/**
* Aborts this job.
*
* This kills and deletes the job.
*
* @param verbosity if equals to EmitResult, Job will emit signal result
* and ask uiserver to close the progress window.
* @p verbosity is set to EmitResult for subjobs. Whether applications
* should call with Quietly or EmitResult depends on whether they rely
* on result being emitted or not. Please notice that if @p verbosity is
* set to Quietly, signal result will NOT be emitted.
* @return true if the operation is supported and succeeded, false otherwise
*/
bool kill(KJob::KillVerbosity verbosity = KJob::Quietly);
/**
* Suspends this job.
* The job should be kept in a state in which it is possible to resume it.
*
* @return true if the operation is supported and succeeded, false otherwise
*/
bool suspend();
/**
* Resumes this job.
*
* @return true if the operation is supported and succeeded, false otherwise
*/
bool resume();
protected:
/**
* Aborts this job quietly.
*
* This simply kills the job, no error reporting or job deletion should be involved.
*
* @return true if the operation is supported and succeeded, false otherwise
*/
virtual bool doKill();
/**
* Suspends this job.
*
* @return true if the operation is supported and succeeded, false otherwise
*/
virtual bool doSuspend();
/**
* Resumes this job.
*
* @return true if the operation is supported and succeeded, false otherwise
*/
virtual bool doResume();
/**
* Sets the capabilities for this job.
*
* @param capabilities are the capabilities supported by this job
* @see capabilities()
*/
void setCapabilities(Capabilities capabilities);
public:
/**
* Executes the job synchronously.
*
* This will start a nested QEventLoop internally. Nested event loop can be dangerous and
* can have unintended side effects, you should avoid calling exec() whenever you can and use the
* asynchronous interface of KJob instead.
*
* Should you indeed call this method, you need to make sure that all callers are reentrant,
* so that events delivered by the inner event loop don't cause non-reentrant functions to be
* called, which usually wreaks havoc.
*
* Note that the event loop started by this method does not process user input events, which means
* your user interface will effectively be blocked. Other events like paint or network events are
* still being processed. The advantage of not processing user input events is that the chance of
* accidental reentrance is greatly reduced. Still you should avoid calling this function.
*
* @return true if the job has been executed without error, false otherwise
*/
bool exec();
enum {
/*** Indicates there is no error */
NoError = 0,
/*** Indicates the job was killed */
KilledJobError = 1,
/*** Subclasses should define error codes starting at this value */
UserDefinedError = 100,
};
/**
* Returns the error code, if there has been an error.
*
* Only call this method from the slot connected to result().
*
* @return the error code for this job, 0 if no error.
*/
int error() const;
/**
* Returns the error text if there has been an error.
*
* Only call if error is not 0.
*
* This is usually some extra data associated with the error,
* such as a URL. Use errorString() to get a human-readable,
* translated message.
*
* @return a string to help understand the error
*/
QString errorText() const;
/**
* A human-readable error message.
*
* This provides a translated, human-readable description of the
* error. Only call if error is not 0.
*
* Subclasses should implement this to create a translated
* error message from the error code and error text.
* For example:
* \code
* if (error() == ReadFailed) {
* i18n("Could not read \"%1\"", errorText());
* }
* \endcode
*
* @return a translated error message, providing error() is 0
*/
virtual QString errorString() const;
/**
* Returns the processed amount of a given unit for this job.
*
* @param unit the unit of the requested amount
* @return the processed size
*/
Q_SCRIPTABLE qulonglong processedAmount(Unit unit) const;
/**
* Returns the total amount of a given unit for this job.
*
* @param unit the unit of the requested amount
* @return the total size
*/
Q_SCRIPTABLE qulonglong totalAmount(Unit unit) const;
/**
* Returns the overall progress of this job.
*
* @return the overall progress of this job
*/
unsigned long percent() const;
/**
* Sets the auto-delete property of the job. If @p autodelete is
* set to @c false the job will not delete itself once it is finished.
*
* The default for any KJob is to automatically delete itself, which
* implies that the job was created on the heap (using <tt>new</tt>).
* If the job is created on the stack (which isn't the typical use-case
* for a job) then you must set auto-delete to @c false, otherwise you
* could get a crash when the job finishes and tries to delete itself.
*
* @note If you set auto-delete to @c false then you need to kill the
* job manually, ideally by calling kill().
*
* @param autodelete set to @c false to disable automatic deletion
* of the job.
*/
void setAutoDelete(bool autodelete);
/**
* Returns whether this job automatically deletes itself once
* the job is finished.
*
* @return whether the job is deleted automatically after
* finishing.
*/
bool isAutoDelete() const;
/**
* This method can be used to indicate to classes listening to signals from a job
* that they should ideally show a progress bar, but not a finished notification.
*
* For example when opening a remote URL, a job will emit the progress of the
* download, which can be used to show a progress dialog or a Plasma notification,
* then when the job is done it'll emit e.g. the finished signal. Showing the user the
* progress dialog is useful, however the dialog/notification about the download being
* finished isn't of much interest, because the user can see the application that invoked
* the job opening the actual file that was downloaded.
*
* @since 5.92
*/
void setFinishedNotificationHidden(bool hide = true);
/**
* Whether to <em>not</em> show a finished notification when a job's finished
* signal is emitted.
*
* @see setFinishedNotificationHidden()
*
* @since 5.92
*/
bool isFinishedNotificationHidden() const;
/**
* Returns @c true if this job was started with exec(), which starts a nested event-loop
* (with QEventLoop::ExcludeUserInputEvents, which blocks the GUI), otherwise returns
* @c false which indicates this job was started asynchronously with start().
*
* This is useful for code that for example shows a dialog to ask the user a question,
* and that would be no-op since the user cannot interact with the dialog.
*
* @since 5.95
*/
bool isStartedWithExec() const;
/**
* The number of milliseconds the job has been running for.
* Starting from the last `start()` call.
*
* Sub-classes must call startElapsedTimer() from their `start()` implementation, to get `elapsedTime()` measurement.
* Otherwise this method will always return 0.
*
* The time when paused is excluded.
*
* @since 6.8
*/
qint64 elapsedTime() const;
Q_SIGNALS:
/**
* Emitted when the job is finished, in any case. It is used to notify
* observers that the job is terminated and that progress can be hidden.
*
* Since 5.75 this signal is guaranteed to be emitted exactly once.
*
* This is a private signal, it can't be emitted directly by subclasses of
* KJob, use emitResult() instead.
*
* In general, to be notified of a job's completion, client code should connect to result()
* rather than finished(), so that kill(Quietly) is indeed quiet.
* However if you store a list of jobs and they might get killed silently,
* then you must connect to this instead of result(), to avoid dangling pointers in your list.
*
* @param job the job that emitted this signal
* @internal
*
* @see result
*/
void finished(KJob *job
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted when the job is suspended.
*
* This is a private signal, it can't be emitted directly by subclasses of
* KJob.
*
* @param job the job that emitted this signal
*/
void suspended(KJob *job
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted when the job is resumed.
*
* This is a private signal, it can't be emitted directly by subclasses of
* KJob.
*
* @param job the job that emitted this signal
*/
void resumed(KJob *job
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted when the job is finished (except when killed with KJob::Quietly).
*
* Since 5.75 this signal is guaranteed to be emitted at most once.
*
* Use error to know if the job was finished with error.
*
* This is a private signal, it can't be emitted directly by subclasses of
* KJob, use emitResult() instead.
*
* Please connect to this signal instead of finished.
*
* @param job the job that emitted this signal
*
* @see kill
*/
void result(KJob *job
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted to display general description of this job. A description has
* a title and two optional fields which can be used to complete the
* description.
*
* Examples of titles are "Copying", "Creating resource", etc.
* The fields of the description can be "Source" with an URL, and,
* "Destination" with an URL for a "Copying" description.
* @param job the job that emitted this signal
* @param title the general description of the job
* @param field1 first field (localized name and value)
* @param field2 second field (localized name and value)
*/
void description(KJob *job,
const QString &title,
const QPair<QString, QString> &field1 = QPair<QString, QString>(),
const QPair<QString, QString> &field2 = QPair<QString, QString>());
/**
* Emitted to display state information about this job.
* Examples of message are "Resolving host", "Connecting to host...", etc.
*
* @param job the job that emitted this signal
* @param message the info message
*/
void infoMessage(KJob *job, const QString &message);
/**
* Emitted to display a warning about this job.
*
* @param job the job that emitted this signal
* @param message the warning message
*/
void warning(KJob *job, const QString &message);
Q_SIGNALS:
// These signals must be connected from KIO::KCoreDirLister (among others),
// therefore they must be public.
/**
* Emitted when we know the amount the job will have to process. The unit of this
* amount is sent too. It can be emitted several times if the job manages several
* different units.
*
* @note This is a private signal, it shouldn't be emitted directly by subclasses of
* KJob, use setTotalAmount() instead.
*
* @param job the job that emitted this signal
* @param unit the unit of the total amount
* @param amount the total amount
*
* @since 5.80
*/
void totalAmountChanged(KJob *job,
KJob::Unit unit,
qulonglong amount
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Regularly emitted to show the progress of this job by giving the current amount.
* The unit of this amount is sent too. It can be emitted several times if the job
* manages several different units.
*
* @note This is a private signal, it shouldn't be emitted directly by subclasses of
* KJob, use setProcessedAmount() instead.
*
* @param job the job that emitted this signal
* @param unit the unit of the processed amount
* @param amount the processed amount
*
* @since 5.80
*/
void processedAmountChanged(KJob *job,
KJob::Unit unit,
qulonglong amount
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted when we know the size of this job (data size in bytes for transfers,
* number of entries for listings, etc).
*
* @note This is a private signal, it shouldn't be emitted directly by subclasses of
* KJob, use setTotalAmount() instead.
*
* @param job the job that emitted this signal
* @param size the total size
*/
void totalSize(KJob *job, qulonglong size);
/**
* Regularly emitted to show the progress of this job
* (current data size in bytes for transfers, entries listed, etc.).
*
* @note This is a private signal, it shouldn't be emitted directly by subclasses of
* KJob, use setProcessedAmount() instead.
*
* @param job the job that emitted this signal
* @param size the processed size
*/
void processedSize(KJob *job, qulonglong size);
/**
* Progress signal showing the overall progress of the job. This is
* valid for any kind of job, and allows using a progress bar very
* easily. (see KProgressBar).
*
* Note that this signal is not emitted for finished jobs.
*
* @note This is a private signal, it shouldn't be emitted directly
* by subclasses of KJob, use emitPercent(), setPercent() setTotalAmount()
* or setProcessedAmount() instead.
*
* @param job the job that emitted this signal
* @param percent the percentage
*
* @since 5.80
*/
void percentChanged(KJob *job,
unsigned long percent
#if !defined(K_DOXYGEN)
,
QPrivateSignal
#endif
);
/**
* Emitted to display information about the speed of this job.
*
* @note This is a private signal, it shouldn't be emitted directly by subclasses of
* KJob, use emitSpeed() instead.
*
* @param job the job that emitted this signal
* @param speed the speed in bytes/s
*/
void speed(KJob *job, unsigned long speed);
protected:
/**
* Returns if the job has been finished and has emitted the finished() signal.
*
* @return if the job has been finished
* @see finished()
* @since 5.75
*/
// KF6 TODO: make public. Useful at least for unittests that run multiple jobs in parallel.
bool isFinished() const;
/**
* Sets the error code.
*
* It should be called when an error
* is encountered in the job, just before calling emitResult().
*
* You should define an (anonymous) enum of error codes,
* with values starting at KJob::UserDefinedError, and use
* those. For example,
* @code
* enum {
* InvalidFoo = UserDefinedError,
* BarNotFound,
* };
* @endcode
*
* @param errorCode the error code
* @see emitResult()
*/
void setError(int errorCode);
/**
* Sets the error text.
*
* It should be called when an error
* is encountered in the job, just before calling emitResult().
*
* Provides extra information about the error that cannot be
* determined directly from the error code. For example, a
* URL or filename. This string is not normally translatable.
*
* @param errorText the error text
* @see emitResult(), errorString(), setError()
*/
void setErrorText(const QString &errorText);
/**
* Sets the processed size. The processedAmount() and percent() signals
* are emitted if the values changed. The percent() signal is emitted
* only for the progress unit.
*
* @param unit the unit of the new processed amount
* @param amount the new processed amount
*/
void setProcessedAmount(Unit unit, qulonglong amount);
/**
* Sets the total size. The totalSize() and percent() signals
* are emitted if the values changed. The percent() signal is emitted
* only for the progress unit.
*
* @param unit the unit of the new total amount
* @param amount the new total amount
*/
void setTotalAmount(Unit unit, qulonglong amount);
/**
* Sets the unit that will be used internally to calculate
* the progress percentage.
* The default progress unit is Bytes.
* @since 5.76
*/
void setProgressUnit(Unit unit);
/**
* Sets the overall progress of the job. The percent() signal
* is emitted if the value changed.
*
* The job takes care of this if you call setProcessedAmount
* in Bytes (or the unit set by setProgressUnit).
* This method allows you to set your own progress, as an alternative.
*
* @param percentage the new overall progress
*/
void setPercent(unsigned long percentage);
/**
* Utility function to emit the result signal, and end this job.
* It first notifies the observers to hide the progress for this job using
* the finished() signal.
*
* @note Deletes this job using deleteLater().
*
* @see result()
* @see finished()
*/
void emitResult();
/**
* Utility function for inherited jobs.
* Emits the percent signal if bigger than previous value,
* after calculating it from the parameters.
*
* @param processedAmount the processed amount
* @param totalAmount the total amount
* @see percent()
*/
void emitPercent(qulonglong processedAmount, qulonglong totalAmount);
/**
* Utility function for inherited jobs.
* Emits the speed signal and starts the timer for removing that info
*
* @param speed the speed in bytes/s
*/
void emitSpeed(unsigned long speed);
/**
* Starts the internal elapsed time measurement timer.
*
* Sub-classes must call startElapsedTimer() from their `start()` implementation, to get `elapsedTime()` measurement.
* Otherwise `elapsedTimer()` method will always return 0.
*
* @since 6.8
*/
void startElapsedTimer();
protected:
std::unique_ptr<KJobPrivate> const d_ptr;
KCOREADDONS_NO_EXPORT KJob(KJobPrivate &dd, QObject *parent);
private:
KCOREADDONS_NO_EXPORT void finishJob(bool emitResult);
Q_DECLARE_PRIVATE(KJob)
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KJob::Capabilities)
#endif
@@ -0,0 +1,70 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KJOB_P_H
#define KJOB_P_H
#include "kjob.h"
#include <QEventLoopLocker>
#include <QMap>
#include <array>
class KJobUiDelegate;
class QTimer;
class QEventLoop;
class QElapsedTimer;
// This is a private class, but it's exported for
// KIO::Job's usage. Other Job classes in kdelibs may
// use it too.
class KCOREADDONS_EXPORT KJobPrivate
{
public:
KJobPrivate();
virtual ~KJobPrivate();
void speedTimeout();
KJob *q_ptr = nullptr;
KJobUiDelegate *uiDelegate = nullptr;
QString errorText;
int error = KJob::NoError;
KJob::Unit progressUnit = KJob::Bytes;
struct Amounts {
qulonglong processedAmount = 0;
qulonglong totalAmount = 0;
};
std::array<Amounts, KJob::UnitsCount> m_jobAmounts;
unsigned long percentage = 0;
QTimer *speedTimer = nullptr;
std::unique_ptr<QElapsedTimer> elapsedTimer;
qint64 accumulatedElapsedTime = 0;
QEventLoop *eventLoop = nullptr;
// eventLoopLocker prevents QCoreApplication from exiting when the last
// window is closed until the job has finished running
QEventLoopLocker eventLoopLocker;
KJob::Capabilities capabilities = KJob::NoCapabilities;
bool suspended = false;
bool isAutoDelete = true;
bool m_hideFinishedNotification = false;
bool isFinished = false;
bool m_startedWithExec = false;
Q_DECLARE_PUBLIC(KJob)
};
#endif
@@ -0,0 +1,116 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kjobtrackerinterface.h"
#include "kjob.h"
class KJobTrackerInterfacePrivate
{
public:
KJobTrackerInterfacePrivate(KJobTrackerInterface *interface)
: q(interface)
{
}
KJobTrackerInterface *const q;
};
KJobTrackerInterface::KJobTrackerInterface(QObject *parent)
: QObject(parent)
, d(new KJobTrackerInterfacePrivate(this))
{
qRegisterMetaType<KJob::Unit>();
qRegisterMetaType<QPair<QString, QString>>();
}
KJobTrackerInterface::~KJobTrackerInterface() = default;
void KJobTrackerInterface::registerJob(KJob *job)
{
connect(job, &KJob::finished, this, &KJobTrackerInterface::unregisterJob);
connect(job, &KJob::finished, this, &KJobTrackerInterface::finished);
connect(job, &KJob::suspended, this, &KJobTrackerInterface::suspended);
connect(job, &KJob::resumed, this, &KJobTrackerInterface::resumed);
connect(job, &KJob::description, this, &KJobTrackerInterface::description);
connect(job, &KJob::infoMessage, this, &KJobTrackerInterface::infoMessage);
connect(job, &KJob::warning, this, &KJobTrackerInterface::warning);
connect(job, &KJob::totalAmountChanged, this, &KJobTrackerInterface::totalAmount);
connect(job, &KJob::processedAmountChanged, this, &KJobTrackerInterface::processedAmount);
connect(job, &KJob::percentChanged, this, &KJobTrackerInterface::percent);
connect(job, &KJob::speed, this, &KJobTrackerInterface::speed);
}
void KJobTrackerInterface::unregisterJob(KJob *job)
{
job->disconnect(this);
}
void KJobTrackerInterface::finished(KJob *job)
{
Q_UNUSED(job)
}
void KJobTrackerInterface::suspended(KJob *job)
{
Q_UNUSED(job)
}
void KJobTrackerInterface::resumed(KJob *job)
{
Q_UNUSED(job)
}
void KJobTrackerInterface::description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
{
Q_UNUSED(job)
Q_UNUSED(title)
Q_UNUSED(field1)
Q_UNUSED(field2)
}
void KJobTrackerInterface::infoMessage(KJob *job, const QString &text)
{
Q_UNUSED(job)
Q_UNUSED(text)
}
void KJobTrackerInterface::warning(KJob *job, const QString &message)
{
Q_UNUSED(job)
Q_UNUSED(message)
}
void KJobTrackerInterface::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount)
{
Q_UNUSED(job)
Q_UNUSED(unit)
Q_UNUSED(amount)
}
void KJobTrackerInterface::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount)
{
Q_UNUSED(job)
Q_UNUSED(unit)
Q_UNUSED(amount)
}
void KJobTrackerInterface::percent(KJob *job, unsigned long percent)
{
Q_UNUSED(job)
Q_UNUSED(percent)
}
void KJobTrackerInterface::speed(KJob *job, unsigned long value)
{
Q_UNUSED(job)
Q_UNUSED(value)
}
#include "moc_kjobtrackerinterface.cpp"
@@ -0,0 +1,179 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KJOBTRACKERINTERFACE_H
#define KJOBTRACKERINTERFACE_H
#include <kcoreaddons_export.h>
#include <kjob.h>
#include <QObject>
#include <QPair>
#include <memory>
/**
* @class KJobTrackerInterface kjobtrackerinterface.h KJobTrackerInterface
*
* The interface to implement to track the progresses of a job.
*/
class KCOREADDONS_EXPORT KJobTrackerInterface : public QObject
{
Q_OBJECT
public:
/**
* Creates a new KJobTrackerInterface
*
* @param parent the parent object
*/
explicit KJobTrackerInterface(QObject *parent = nullptr);
/**
* Destroys a KJobTrackerInterface
*/
~KJobTrackerInterface() override;
public Q_SLOTS:
/**
* Register a new job in this tracker.
* The default implementation connects the following KJob signals
* to the respective protected slots of this class:
* - finished() (also connected to the unregisterJob() slot)
* - suspended()
* - resumed()
* - description()
* - infoMessage()
* - totalAmount()
* - processedAmount()
* - percent()
* - speed()
*
* If you re-implement this method, you may want to call the default
* implementation or add at least:
*
* @code
* connect(job, &KJob::finished, this, &MyJobTracker::unregisterJob);
* @endcode
*
* so that you won't have to manually call unregisterJob().
*
* @param job the job to register
* @see unregisterJob()
*/
virtual void registerJob(KJob *job);
/**
* Unregister a job from this tracker.
* @note You need to manually call this method only if you re-implemented
* registerJob() without connecting KJob::finished to this slot.
*
* @param job the job to unregister
* @see registerJob()
*/
virtual void unregisterJob(KJob *job);
protected Q_SLOTS:
/**
* Called when a job is finished, in any case. It is used to notify
* that the job is terminated and that progress UI (if any) can be hidden.
*
* @param job the job that emitted this signal
*/
virtual void finished(KJob *job);
/**
* Called when a job is suspended.
*
* @param job the job that emitted this signal
*/
virtual void suspended(KJob *job);
/**
* Called when a job is resumed.
*
* @param job the job that emitted this signal
*/
virtual void resumed(KJob *job);
/**
* Called to display general description of a job. A description has
* a title and two optional fields which can be used to complete the
* description.
*
* Examples of titles are "Copying", "Creating resource", etc.
* The fields of the description can be "Source" with an URL, and,
* "Destination" with an URL for a "Copying" description.
* @param job the job that emitted this signal
* @param title the general description of the job
* @param field1 first field (localized name and value)
* @param field2 second field (localized name and value)
*/
virtual void description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2);
/**
* Called to display state information about a job.
* Examples of message are "Resolving host", "Connecting to host...", etc.
*
* @param job the job that emitted this signal
* @param message the info message
*/
virtual void infoMessage(KJob *job, const QString &message);
/**
* Emitted to display a warning about a job.
*
* @param job the job that emitted this signal
* @param message the warning message
*/
virtual void warning(KJob *job, const QString &message);
/**
* Called when we know the amount a job will have to process. The unit of this
* amount is provided too. It can be called several times for a given job if the job
* manages several different units.
*
* @param job the job that emitted this signal
* @param unit the unit of the total amount
* @param amount the total amount
*/
virtual void totalAmount(KJob *job, KJob::Unit unit, qulonglong amount);
/**
* Regularly called to show the progress of a job by giving the current amount.
* The unit of this amount is provided too. It can be called several times for a given
* job if the job manages several different units.
*
* @param job the job that emitted this signal
* @param unit the unit of the processed amount
* @param amount the processed amount
*/
virtual void processedAmount(KJob *job, KJob::Unit unit, qulonglong amount);
/**
* Called to show the overall progress of the job.
* Note that this is not called for finished jobs.
*
* @param job the job that emitted this signal
* @param percent the percentage
*/
virtual void percent(KJob *job, unsigned long percent);
/**
* Called to show the speed of the job.
*
* @param job the job that emitted this signal
* @param value the current speed of the job
*/
virtual void speed(KJob *job, unsigned long value);
private:
std::unique_ptr<class KJobTrackerInterfacePrivate> const d;
};
#endif
@@ -0,0 +1,118 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kjobuidelegate.h"
#include "kcoreaddons_debug.h"
#include "kjob.h"
#include <QDebug>
class KJobUiDelegatePrivate
{
public:
KJobUiDelegatePrivate(KJobUiDelegate *delegate)
: q(delegate)
, autoErrorHandling(false)
, autoWarningHandling(true)
{
}
KJobUiDelegate *const q;
KJob *job = nullptr;
bool autoErrorHandling : 1;
bool autoWarningHandling : 1;
void connectJob(KJob *job);
void _k_result();
};
KJobUiDelegate::KJobUiDelegate(Flags flags)
: QObject()
, d(new KJobUiDelegatePrivate(this))
{
if (flags & AutoErrorHandlingEnabled) {
d->autoErrorHandling = true;
}
if (flags & AutoWarningHandlingEnabled) {
d->autoWarningHandling = true;
}
}
KJobUiDelegate::~KJobUiDelegate() = default;
bool KJobUiDelegate::setJob(KJob *job)
{
if (d->job != nullptr) {
qCWarning(KCOREADDONS_DEBUG) << "Trying to attach UI delegate:" << this << "to job" << job //
<< "but this delegate is already attached to a different job" << d->job;
return false;
}
d->job = job;
setParent(job);
return true;
}
KJob *KJobUiDelegate::job() const
{
return d->job;
}
void KJobUiDelegate::showErrorMessage()
{
if (d->job->error() != KJob::KilledJobError) {
qWarning() << d->job->errorString();
}
}
void KJobUiDelegate::setAutoErrorHandlingEnabled(bool enable)
{
d->autoErrorHandling = enable;
}
bool KJobUiDelegate::isAutoErrorHandlingEnabled() const
{
return d->autoErrorHandling;
}
void KJobUiDelegate::setAutoWarningHandlingEnabled(bool enable)
{
d->autoWarningHandling = enable;
}
bool KJobUiDelegate::isAutoWarningHandlingEnabled() const
{
return d->autoWarningHandling;
}
void KJobUiDelegate::slotWarning(KJob *job, const QString &message)
{
Q_UNUSED(job)
Q_UNUSED(message)
}
void KJobUiDelegate::connectJob(KJob *job)
{
connect(job, &KJob::result, this, [this]() {
d->_k_result();
});
connect(job, &KJob::warning, this, &KJobUiDelegate::slotWarning);
}
void KJobUiDelegatePrivate::_k_result()
{
if (job->error() && autoErrorHandling) {
q->showErrorMessage();
}
}
#include "moc_kjobuidelegate.cpp"
@@ -0,0 +1,154 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KJOBUIDELEGATE_H
#define KJOBUIDELEGATE_H
#include <QObject>
#include <kcoreaddons_export.h>
#include <memory>
class KJob;
/**
* @class KJobUiDelegate kjobuidelegate.h KJobUiDelegate
*
* The base class for all KJob UI delegate.
*
* A UI delegate is responsible for the events of a
* job and provides a UI for them (an error message
* box or warning etc.).
*
* @see KJob
*/
class KCOREADDONS_EXPORT KJobUiDelegate : public QObject
{
Q_OBJECT
public:
/**
* Flags for the constructor, to enable automatic handling of errors and/or warnings
* @see Flags
* @since 5.70
*/
enum Flag {
AutoHandlingDisabled = 0, ///< No automatic handling (default)
AutoErrorHandlingEnabled = 1, ///< Equivalent to setAutoErrorHandlingEnabled(true)
AutoWarningHandlingEnabled = 2, ///< Equivalent to setAutoWarningHandlingEnabled(true)
AutoHandlingEnabled = AutoErrorHandlingEnabled | AutoWarningHandlingEnabled, ///< Enables both error and warning handling
};
/**
* Stores a combination of #Flag values.
*/
Q_DECLARE_FLAGS(Flags, Flag)
/**
* Constructs a new KJobUiDelegate with a flags argument.
* @param flags allows to enable automatic error/warning handling
* @since 5.70
*/
explicit KJobUiDelegate(Flags flags = {KJobUiDelegate::AutoHandlingDisabled});
/**
* Destroys a KJobUiDelegate.
*/
~KJobUiDelegate() override;
protected:
/**
* Attach this UI delegate to a job. Once attached it'll track the job events.
*
* @return @c true if this UI delegate was successfully attached to @p job, @c false otherwise
*
* @note if this UI delegate is already attached to a job, calling this method will return
* @c false.
*/
virtual bool setJob(KJob *job);
protected:
/**
* Retrieves the current job this UI delegate is attached to.
*
* @return current job this UI delegate is attached to, or @c nullptr if
* this UI delegate is not tracking any job
*/
KJob *job() const;
friend class KJob;
public:
/**
* Display to the user the error given by this job.
* The default implementation uses qWarning(). Subclasses
* reimplement this to use something more user-visible such
* as a message box.
*
* Only call this method if error is not 0, and only in the
* slot connected to result.
*/
virtual void showErrorMessage();
/**
* Enable or disable the automatic error handling. When automatic
* error handling is enabled and an error occurs, then showErrorDialog()
* is called, right before the emission of the result signal.
*
* The default is false.
*
* See also isAutoErrorHandlingEnabled , showErrorDialog
*
* @param enable enable or disable automatic error handling
* @see isAutoErrorHandlingEnabled()
*/
void setAutoErrorHandlingEnabled(bool enable);
/**
* Returns whether automatic error handling is enabled or disabled.
* See also setAutoErrorHandlingEnabled .
* @return true if automatic error handling is enabled
* @see setAutoErrorHandlingEnabled()
*/
bool isAutoErrorHandlingEnabled() const;
/**
* Enable or disable the automatic warning handling. When automatic
* warning handling is enabled and an error occurs, then a message box
* is displayed with the warning message
*
* The default is true.
*
* See also isAutoWarningHandlingEnabled , showErrorDialog
*
* @param enable enable or disable automatic warning handling
* @see isAutoWarningHandlingEnabled()
*/
void setAutoWarningHandlingEnabled(bool enable);
/**
* Returns whether automatic warning handling is enabled or disabled.
* See also setAutoWarningHandlingEnabled .
* @return true if automatic warning handling is enabled
* @see setAutoWarningHandlingEnabled()
*/
bool isAutoWarningHandlingEnabled() const;
protected Q_SLOTS:
virtual void slotWarning(KJob *job, const QString &message);
private:
KCOREADDONS_NO_EXPORT void connectJob(KJob *job);
private:
std::unique_ptr<class KJobUiDelegatePrivate> const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KJobUiDelegate::Flags)
#endif // KJOBUIDELEGATE_H
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
/*
This file is part of the KDE Libraries
SPDX-FileCopyrightText: 2016 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcoreaddons.h"
#include "kcoreaddons_version.h"
QString KCoreAddons::versionString()
{
return QStringLiteral(KCOREADDONS_VERSION_STRING);
}
uint KCoreAddons::version()
{
return KCOREADDONS_VERSION;
}
@@ -0,0 +1,44 @@
/*
This file is part of the KDE Libraries
SPDX-FileCopyrightText: 2016 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOREADDONS_H
#define KCOREADDONS_H
#include <QString>
#include <kcoreaddons_export.h>
/**
* @namespace KCoreAddons
* Provides utility functions for metadata about the KCoreAddons library.
*/
namespace KCoreAddons
{
/**
* Returns the version number of KCoreAddons at run-time as a string (for example, "5.19.0").
* This may be a different version than the version the application was compiled against.
* @since 5.20
*/
KCOREADDONS_EXPORT QString versionString();
/**
* Returns a numerical version number of KCoreAddons at run-time in the form 0xMMNNPP
* (MM = major, NN = minor, PP = patch)
* This can be compared using the macro QT_VERSION_CHECK.
*
* For example:
* \code
* if (KCoreAddons::version() < QT_VERSION_CHECK(5,19,0))
* \endcode
*
* This may be a different version than the version the application was compiled against.
* @since 5.20
*/
KCOREADDONS_EXPORT unsigned int version();
}
#endif
@@ -0,0 +1,124 @@
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package,
while giving the users of the package the right to use and distribute
the Package in a more-or-less customary fashion, plus the right to
make reasonable modifications.
Definitions
"Package" refers to the collection of files distributed by the
Copyright Holder, and derivatives of that collection of files
created through textual modification.
"Standard Version" refers to such a Package if it has not been
modified, or has been modified in accordance with the wishes of the
Copyright Holder as specified below.
"Copyright Holder" is whoever is named in the copyright or
copyrights for the package.
"You" is you, if you're thinking about copying or distributing this
Package.
"Reasonable copying fee" is whatever you can justify on the basis
of media cost, duplication charges, time of people involved, and so
on. (You will not be required to justify it to the Copyright
Holder, but only to the computing community at large as a market
that must bear the fee.)
"Freely Available" means that no fee is charged for the item
itself, though there may be fees involved in handling the item. It
also means that recipients of the item may redistribute it under
the same conditions they received it.
1. You may make and give away verbatim copies of the source form of
the Standard Version of this Package without restriction, provided
that you duplicate all of the original copyright notices and
associated disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A
Package modified in such a way shall still be considered the
Standard Version.
3. You may otherwise modify your copy of this Package in any way,
provided that you insert a prominent notice in each changed file
stating how and when you changed that file, and provided that you
do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise make
them Freely Available, such as by posting said modifications to
Usenet or an equivalent medium, or placing the modifications on a
major archive site such as uunet.uu.net, or by allowing the
Copyright Holder to include your modifications in the Standard
Version of the Package.
b. use the modified Package only within your corporation or
organization.
c. rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and
provide a separate manual page for each non-standard executable
that clearly documents how it differs from the Standard Version.
d. make other distribution arrangements with the Copyright Holder.
You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a. distribute a Standard Version of the executables and library
files, together with instructions (in the manual page or
equivalent) on where to get the Standard Version.
b. accompany the distribution with the machine-readable source of the
Package with your modifications.
c. give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent), together
with instructions on where to get the Standard Version.
d. make other distribution arrangements with the Copyright Holder.
You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial)
software distribution provided that you do not advertise this Package
as a product of your own. You may embed this Package's interpreter
within an executable of yours (by linking); this shall be construed as
a mere form of aggregation, provided that the complete Standard
Version of the interpreter is so embedded.
The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whomever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing a
binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you
do not represent such an executable image as a Standard Version of
this Package.
C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that is,
when no overt attempt is made to make this Package's interfaces
visible to the end user of the commercial distribution. Such use shall
not be construed as a distribution of this Package.
The name of the Copyright Holder may not be used to endorse or
promote products derived from this software without specific prior
written permission.
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End
@@ -0,0 +1,20 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU 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.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
@@ -0,0 +1,481 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the library GPL. It is
numbered 2 because it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Library General Public License, applies to some
specially designated Free Software Foundation software, and to any
other libraries whose authors decide to use it. You can use it for
your libraries, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link a program with the library, you must provide
complete object files to the recipients so that they can relink them
with the library, after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
Our method of protecting your rights has two steps: (1) copyright
the library, and (2) offer you this license which gives you legal
permission to copy, distribute and/or modify the library.
Also, for each distributor's protection, we want to make certain
that everyone understands that there is no warranty for this free
library. If the library is modified by someone else and passed on, we
want its recipients to know that what they have is not the original
version, so that any problems introduced by others will not reflect on
the original authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that companies distributing free
software will individually obtain patent licenses, thus in effect
transforming the program into proprietary software. To prevent this,
we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary
GNU General Public License, which was designed for utility programs. This
license, the GNU Library General Public License, applies to certain
designated libraries. This license is quite different from the ordinary
one; be sure to read it in full, and don't assume that anything in it is
the same as in the ordinary license.
The reason we have a separate public license for some libraries is that
they blur the distinction we usually make between modifying or adding to a
program and simply using it. Linking a program with a library, without
changing the library, is in some sense simply using the library, and is
analogous to running a utility program or application program. However, in
a textual and legal sense, the linked executable is a combined work, a
derivative of the original library, and the ordinary General Public License
treats it as such.
Because of this blurred distinction, using the ordinary General
Public License for libraries did not effectively promote software
sharing, because most developers did not use the libraries. We
concluded that weaker conditions might promote sharing better.
However, unrestricted linking of non-free programs would deprive the
users of those programs of all benefit from the free status of the
libraries themselves. This Library General Public License is intended to
permit developers of non-free programs to use free libraries, while
preserving your freedom as a user of such programs to change the free
libraries that are incorporated in them. (We have not seen how to achieve
this as regards changes in header files, but we have achieved it as regards
changes in the actual functions of the Library.) The hope is that this
will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, while the latter only
works together with the library.
Note that it is possible for a library to be covered by the ordinary
General Public License rather than by this special one.
GNU LIBRARY GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which
contains a notice placed by the copyright holder or other authorized
party saying it may be distributed under the terms of this Library
General Public License (also called "this License"). Each licensee is
addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also compile or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
d) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the source code distributed need not include anything that is normally
distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Library General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library 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 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; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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) any later version.
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,13 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/org.kde.kcoreaddons/licenses">
<file>BSD</file>
<file>GPL_V2</file>
<file>GPL_V3</file>
<file>LGPL_V2</file>
<file>LGPL_V21</file>
<file>LGPL_V3</file>
<file>ARTISTIC</file>
<file>MIT</file>
</qresource>
</RCC>
@@ -0,0 +1,126 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginfactory.h"
#include "kpluginfactory_p.h"
#include "kcoreaddons_debug.h"
#include <QPluginLoader>
#include <algorithm>
KPluginFactory::KPluginFactory()
: d(new KPluginFactoryPrivate)
{
}
KPluginFactory::~KPluginFactory() = default;
KPluginFactory::Result<KPluginFactory> KPluginFactory::loadFactory(const KPluginMetaData &data)
{
Result<KPluginFactory> result;
QObject *obj = nullptr;
if (data.isStaticPlugin()) {
obj = data.staticPlugin().instance();
} else {
if (data.fileName().isEmpty()) {
result.errorString = tr("Could not find plugin %1").arg(data.requestedFileName());
result.errorText = QStringLiteral("Could not find plugin %1").arg(data.requestedFileName());
result.errorReason = INVALID_PLUGIN;
qCWarning(KCOREADDONS_DEBUG) << result.errorText;
return result;
}
QPluginLoader loader(data.fileName());
obj = loader.instance();
if (!obj) {
result.errorString = tr("Could not load plugin from %1: %2").arg(data.fileName(), loader.errorString());
result.errorText = QStringLiteral("Could not load plugin from %1: %2").arg(data.fileName(), loader.errorString());
result.errorReason = INVALID_PLUGIN;
qCWarning(KCOREADDONS_DEBUG) << result.errorText;
return result;
}
}
KPluginFactory *factory = qobject_cast<KPluginFactory *>(obj);
if (factory == nullptr) {
result.errorString = tr("The library %1 does not offer a KPluginFactory.").arg(data.fileName());
result.errorReason = INVALID_FACTORY;
qCWarning(KCOREADDONS_DEBUG) << "Expected a KPluginFactory, got a" << obj->metaObject()->className();
delete obj;
return result;
}
factory->setMetaData(data);
result.plugin = factory;
return result;
}
KPluginMetaData KPluginFactory::metaData() const
{
return d->metaData;
}
void KPluginFactory::setMetaData(const KPluginMetaData &metaData)
{
d->metaData = metaData;
}
void KPluginFactory::registerPlugin(const QMetaObject *metaObject, CreateInstanceWithMetaDataFunction instanceFunction)
{
Q_ASSERT(metaObject);
const QMetaObject *superClass = metaObject->superClass();
Q_ASSERT(superClass);
for (const KPluginFactoryPrivate::PluginWithMetadata &plugin : d->createInstanceWithMetaDataHash) {
for (const QMetaObject *otherSuper = plugin.first->superClass(); otherSuper; otherSuper = otherSuper->superClass()) {
if (superClass == otherSuper) {
qCWarning(KCOREADDONS_DEBUG).nospace() << "Two plugins with the same interface (" << superClass->className()
<< ") were registered in the KPluginFactory " << this->metaObject()->className() << ". "
<< "This might be due to a missing Q_OBJECT macro in one of the registered classes";
}
}
}
// check hierarchy of newly newly registered plugin against all registered classes
for (const KPluginFactoryPrivate::PluginWithMetadata &plugin : d->createInstanceWithMetaDataHash) {
superClass = plugin.first->superClass();
for (const QMetaObject *otherSuper = metaObject->superClass(); otherSuper; otherSuper = otherSuper->superClass()) {
if (superClass == otherSuper) {
qCWarning(KCOREADDONS_DEBUG).nospace() << "Two plugins with the same interface (" << superClass->className()
<< ") were registered in the KPluginFactory " << this->metaObject()->className() << ". "
<< "This might be due to a missing Q_OBJECT macro in one of the registered classes";
}
}
}
d->createInstanceWithMetaDataHash.push_back({metaObject, instanceFunction});
}
void KPluginFactory::logFailedInstantiationMessage(KPluginMetaData data)
{
qCWarning(KCOREADDONS_DEBUG) << "KPluginFactory could not load the plugin" << data.fileName();
}
void KPluginFactory::logFailedInstantiationMessage(const char *className, KPluginMetaData data)
{
qCWarning(KCOREADDONS_DEBUG) << "KPluginFactory could not create a" << className << "instance from" << data.fileName();
}
QObject *KPluginFactory::create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args)
{
for (const KPluginFactoryPrivate::PluginWithMetadata &plugin : d->createInstanceWithMetaDataHash) {
for (const QMetaObject *current = plugin.first; current; current = current->superClass()) {
if (0 == qstrcmp(iface, current->className())) {
return plugin.second(parentWidget, parent, d->metaData, args);
}
}
}
return nullptr;
}
#include "moc_kpluginfactory.cpp"
@@ -0,0 +1,657 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de>
SPDX-FileCopyrightText: 2021-2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINFACTORY_H
#define KPLUGINFACTORY_H
#include "kcoreaddons_export.h"
#include "kpluginmetadata.h"
#include <QObject>
#include <QVariant>
#include <memory>
#include <type_traits>
class QWidget;
class KPluginFactoryPrivate;
namespace KParts
{
class Part;
}
#define KPluginFactory_iid "org.kde.KPluginFactory"
// Internal macro that generated the KPluginFactory subclass
#define __K_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations, ...) \
class name : public KPluginFactory \
{ \
Q_OBJECT \
Q_INTERFACES(KPluginFactory) \
Q_PLUGIN_METADATA(__VA_ARGS__) \
public: \
explicit name() \
{ \
pluginRegistrations \
} \
~name() { }; \
};
/**
* @relates KPluginFactory
*
* Create a KPluginFactory subclass and export it as the root plugin object.
*
* @param name the name of the KPluginFactory derived class.
*
* @param pluginRegistrations code to be inserted into the constructor of the
* class. Usually a series of registerPlugin() calls.
*
* @note K_PLUGIN_FACTORY declares the subclass including a Q_OBJECT macro.
* So you need to make sure to have Qt's moc run also for the source file
* where you use the macro. E.g. in projects using CMake and it's automoc feature,
* as usual you need to have a line
* @code
* #include <myplugin.moc>
* @endcode
* in the same source file when that one has the name "myplugin.cpp".
*
* Example:
* @code
* #include <KPluginFactory>
* #include <plugininterface.h>
*
* class MyPlugin : public PluginInterface
* {
* public:
* MyPlugin(QObject *parent, const QVariantList &args)
* : PluginInterface(parent)
* {}
* };
*
* K_PLUGIN_FACTORY(MyPluginFactory, registerPlugin<MyPlugin>();)
*
* #include <myplugin.moc>
* @endcode
*
* If you want to compile a .json file into the plugin, use K_PLUGIN_FACTORY_WITH_JSON.
*
* @see K_PLUGIN_FACTORY_WITH_JSON
* @see K_PLUGIN_FACTORY_DECLARATION
* @see K_PLUGIN_FACTORY_DEFINITION
*/
#define K_PLUGIN_FACTORY(name, pluginRegistrations) __K_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations, IID KPluginFactory_iid)
/**
* @relates KPluginFactory
*
* Create a KPluginFactory subclass and export it as the root plugin object with
* JSON metadata.
*
* This macro does the same as K_PLUGIN_FACTORY, but adds a JSON file as plugin
* metadata. See Q_PLUGIN_METADATA() for more information.
*
* @param name the name of the KPluginFactory derived class.
*
* @param pluginRegistrations code to be inserted into the constructor of the
* class. Usually a series of registerPlugin() calls.
*
* @param jsonFile name of the JSON file to be compiled into the plugin as metadata
*
* @note K_PLUGIN_FACTORY_WITH_JSON declares the subclass including a Q_OBJECT macro.
* So you need to make sure to have Qt's moc run also for the source file
* where you use the macro. E.g. in projects using CMake and its automoc feature,
* as usual you need to have a line
* @code
* #include <myplugin.moc>
* @endcode
* in the same source file when that one has the name "myplugin.cpp".
*
* Example:
* @code
* #include <KPluginFactory>
* #include <plugininterface.h>
*
* class MyPlugin : public PluginInterface
* {
* public:
* MyPlugin(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* : PluginInterface(parent)
* {}
* };
*
* K_PLUGIN_FACTORY_WITH_JSON(MyPluginFactory,
* "metadata.json",
* registerPlugin<MyPlugin>();
* )
*
* #include <myplugin.moc>
* @endcode
*
* @see K_PLUGIN_FACTORY
* @see K_PLUGIN_FACTORY_DECLARATION
* @see K_PLUGIN_FACTORY_DEFINITION
*
* @since 5.0
*/
#define K_PLUGIN_FACTORY_WITH_JSON(name, jsonFile, pluginRegistrations) \
__K_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations, IID KPluginFactory_iid FILE jsonFile)
/**
* @relates KPluginFactory
*
* Create a KPluginFactory subclass and export it as the root plugin object with
* JSON metadata.
*
* This macro does the same as K_PLUGIN_FACTORY_WITH_JSON, but you only have to pass the class name and the json file.
* The factory name and registerPlugin call are deduced from the class name.
*
* @code
* #include <myplugin.moc>
* @endcode
* in the same source file when that one has the name "myplugin.cpp".
*
* Example:
* @code
* #include <KPluginFactory>
* #include <plugininterface.h>
*
* class MyPlugin : public PluginInterface
* {
* public:
* MyPlugin(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* : PluginInterface(parent)
* {}
* };
*
* K_PLUGIN_CLASS_WITH_JSON(MyPlugin, "metadata.json")
*
* #include <myplugin.moc>
* @endcode
*
* @see K_PLUGIN_FACTORY_WITH_JSON
*
* @since 5.44
*/
#ifdef KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME
#define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile) \
K_PLUGIN_FACTORY_WITH_JSON(KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME, jsonFile, registerPlugin<classname>();)
#else
#define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile) K_PLUGIN_FACTORY_WITH_JSON(classname##Factory, jsonFile, registerPlugin<classname>();)
#endif
/**
* @relates KPluginFactory
*
* Creates a KPluginFactory subclass and exports it as the root plugin object.
* Unlike @ref K_PLUGIN_CLASS_WITH_JSON, this macro does not require json meta data.
*
* This macro does the same as K_PLUGIN_FACTORY, but you only have to pass the class name.
* The factory name and registerPlugin call are deduced from the class name.
* This is also useful if you want to use static plugins, see the kcoreaddons_add_plugin CMake method.
* @since 5.90
*/
#ifdef KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME
#define K_PLUGIN_CLASS(classname) K_PLUGIN_FACTORY(KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME, registerPlugin<classname>();)
#else
#define K_PLUGIN_CLASS(classname) K_PLUGIN_FACTORY(classname##Factory, registerPlugin<classname>();)
#endif
/**
* @class KPluginFactory kpluginfactory.h <KPluginFactory>
*
* KPluginFactory provides a convenient way to provide factory-style plugins.
* Qt plugins provide a singleton object, but a common pattern is for plugins
* to generate as many objects of a particular type as the application requires.
* By using KPluginFactory, you can avoid implementing the factory pattern
* yourself.
*
* KPluginFactory also allows plugins to provide multiple different object
* types, indexed by keywords.
*
* The objects created by KPluginFactory must inherit QObject, and must have a
* standard constructor pattern:
* @li if the object is a KPart::Part, it must be of the form
* @code
* T(QWidget *parentWidget, QObject *parent, const QVariantList &args)
* @endcode
* or
* @code
* T(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
* @li if it is a QWidget, it must be of the form
* @code
* T(QWidget *parent, const QVariantList &args)
* @endcode
* or
* @code
* T(QWidget *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
* @li otherwise it must be of the form
* @code
* T(QObject *parent, const QVariantList &args)
* @endcode
* or
* @code
* T(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
*
* You should typically use either K_PLUGIN_CLASS() or
* K_PLUGIN_CLASS_WITH_JSON() in your plugin code to generate a factory.
* The typical pattern is:
*
* @code
* #include <KPluginFactory>
* #include <plugininterface.h>
*
* class MyPlugin : public PluginInterface
* {
* public:
* MyPlugin(QObject *parent, const QVariantList &args)
* : PluginInterface(parent)
* {}
* };
*
* K_PLUGIN_CLASS(MyPlugin)
* #include <myplugin.moc>
* @endcode
*
* If you want to write a custom KPluginFactory not using the standard macro(s)
* you can reimplement the
* create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args)
* method.
*
* Example:
* @code
* class SomeScriptLanguageFactory : public KPluginFactory
* {
* Q_OBJECT
* public:
* SomeScriptLanguageFactory()
* {}
*
* protected:
* virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args)
* {
* // Create an identifier based on the iface and given pluginId
* const QString identifier = QLatin1String(iface) + QLatin1Char('_') + metaData().pluginId();
* // load scripting language module from the information in identifier and return it:
* return object;
* }
* };
* @endcode
*
* To load the KPluginFactory from an installed plugin you can use @ref loadFactory and for
* directly creating a plugin instance from it @ref instantiatePlugin
*
* @author Matthias Kretz <kretz@kde.org>
* @author Bernhard Loos <nhuh.put@web.de>
* @author Alexander Lohnau <alexander.lohnau@gmx.de>
*/
class KCOREADDONS_EXPORT KPluginFactory : public QObject
{
Q_OBJECT
public:
/**
* This constructor creates a factory for a plugin.
*/
explicit KPluginFactory();
/**
* This destroys the PluginFactory.
*/
~KPluginFactory() override;
/// @since 5.86
enum ResultErrorReason {
NO_PLUGIN_ERROR = 0,
INVALID_PLUGIN,
INVALID_FACTORY,
INVALID_KPLUGINFACTORY_INSTANTIATION,
};
/**
* Holds the result of a plugin load operation, i.e. the loaded plugin on success or information about the error on failure
* @since 5.86
*/
template<typename T>
class Result
{
public:
T *plugin = nullptr;
/// translated, user-visible error string
QString errorString;
/// untranslated error text
QString errorText;
ResultErrorReason errorReason = NO_PLUGIN_ERROR;
explicit operator bool() const
{
return plugin != nullptr;
}
};
/**
* Attempts to load the KPluginFactory from the given metadata.
* The errors will be logged using the `kf.coreaddons` debug category.
* @param data KPluginMetaData from which the plugin should be loaded
* @return Result object which contains the plugin instance and potentially error information
* @since 5.86
*/
static Result<KPluginFactory> loadFactory(const KPluginMetaData &data);
/**
* Attempts to load the KPluginFactory and create a @p T instance from the given metadata
* KCoreAddons will log error messages automatically, meaning you only need to implement your
* own logging in case you want to give it more context info or have a custom category.
* @code
if (auto result = KPluginFactory::instantiatePlugin<MyClass>(metaData, parent, args)) {
// The plugin is valid and result.plugin contains the object
} else {
// We can access the error related properties, but result.plugin is a nullptr
qCWarning(MYCATEGORY) << result.errorString;
}
* @endcode
* If there is no extra error handling needed the plugin can be directly accessed and checked if it is a nullptr
* @code
if (auto plugin = KPluginFactory::instantiatePlugin<MyClass>(metaData, parent, args).plugin) {
}
* @endcode
* @param data KPluginMetaData from which the plugin should be loaded
* @param args arguments which get passed to the plugin's constructor
* @return Result object which contains the plugin instance and potentially error information
* @since 5.86
*/
template<typename T>
static Result<T> instantiatePlugin(const KPluginMetaData &data, QObject *parent = nullptr, const QVariantList &args = {})
{
Result<T> result;
KPluginFactory::Result<KPluginFactory> factoryResult = loadFactory(data);
if (!factoryResult.plugin) {
result.errorString = factoryResult.errorString;
result.errorText = factoryResult.errorText;
result.errorReason = factoryResult.errorReason;
return result;
}
T *instance = factoryResult.plugin->create<T>(parent, args);
if (!instance) {
const QLatin1String className(T::staticMetaObject.className());
result.errorString = tr("KPluginFactory could not create a %1 instance from %2").arg(className, data.fileName());
result.errorText = QStringLiteral("KPluginFactory could not create a %1 instance from %2").arg(className, data.fileName());
result.errorReason = INVALID_KPLUGINFACTORY_INSTANTIATION;
logFailedInstantiationMessage(T::staticMetaObject.className(), data);
} else {
result.plugin = instance;
}
return result;
}
/**
* Use this method to create an object. It will try to create an object which inherits
* @p T. If it has multiple choices it's not defined which object will be returned, so be careful
* to request a unique interface or use keywords.
*
* @tparam T the interface for which an object should be created. The object will inherit @p T.
* @param parent the parent of the object. If @p parent is a widget type, it will also passed
* to the parentWidget argument of the CreateInstanceFunction for the object.
* @param args additional arguments which will be passed to the object.
* @returns pointer to the created object is returned, or @c nullptr if an error occurred.
*/
template<typename T>
T *create(QObject *parent = nullptr, const QVariantList &args = {});
/**
* Use this method to create an object. It will try to create an object which inherits @p T
* This overload has an additional @p parentWidget argument, which is used by some plugins (e.g. Parts).
* @tparam T the interface for which an object should be created. The object will inherit @p T.
* @param parentWidget an additional parent widget.
* @param parent the parent of the object. If @p parent is a widget type, it will also passed
* to the parentWidget argument of the CreateInstanceFunction for the object.
* @param args additional arguments which will be passed to the object. Since 5.93 this has a default arg.
* @returns pointer to the created object is returned, or @c nullptr if an error occurred.
*/
template<typename T>
T *create(QWidget *parentWidget, QObject *parent, const QVariantList &args = {});
/**
* @returns the metadata of the plugin
*
* @since 5.77
*/
KPluginMetaData metaData() const;
/**
* Set the metadata about the plugin this factory generates.
*
* @param metaData the metadata about the plugin
*
* @since 5.77
*/
void setMetaData(const KPluginMetaData &metaData);
protected:
/**
* Function pointer type to a function that instantiates a plugin
* For plugins that don't support a KPluginMetaData parameter it is discarded
* @since 5.77
*/
using CreateInstanceWithMetaDataFunction = QObject *(*)(QWidget *, QObject *, const KPluginMetaData &, const QVariantList &);
/**
* This is used to detect the arguments need for the constructor of metadata-taking plugin classes.
* You can inherit it, if you want to add new classes and still keep support for the old ones.
*/
template<class impl>
struct InheritanceWithMetaDataChecker {
/// property to control the availability of the registerPlugin overload taking default values
static constexpr bool enabled = std::is_constructible<impl, QWidget *, QObject *, KPluginMetaData, QVariantList>::value // KParts
|| std::is_constructible<impl, QWidget *, QObject *, KPluginMetaData>::value
|| std::is_constructible<impl, QWidget *, KPluginMetaData, QVariantList>::value // QWidgets
|| std::is_constructible<impl, QWidget *, KPluginMetaData>::value
|| std::is_constructible<impl, QObject *, KPluginMetaData, QVariantList>::value // Nomal QObjects
|| std::is_constructible<impl, QObject *, KPluginMetaData>::value;
CreateInstanceWithMetaDataFunction createInstanceFunction(KParts::Part *)
{
return &createPartWithMetaDataInstance<impl>;
}
CreateInstanceWithMetaDataFunction createInstanceFunction(QWidget *)
{
return &createWithMetaDataInstance<impl, QWidget>;
}
CreateInstanceWithMetaDataFunction createInstanceFunction(...)
{
return &createWithMetaDataInstance<impl, QObject>;
}
};
/**
* This is used to detect the arguments need for the constructor of metadata-less plugin classes.
* You can inherit it, if you want to add new classes and still keep support for the old ones.
*/
template<class impl>
struct InheritanceChecker {
/// property to control the availability of the registerPlugin overload taking default values
static constexpr bool _canConstruct = std::is_constructible<impl, QWidget *, QVariantList>::value // QWidget plugin
|| std::is_constructible<impl, QWidget *>::value //
|| std::is_constructible<impl, QObject *, QVariantList>::value // QObject plugins
|| std::is_constructible<impl, QObject *>::value;
static constexpr bool enabled = _canConstruct && !InheritanceWithMetaDataChecker<impl>::enabled; // Avoid ambiguity in case of default arguments
CreateInstanceWithMetaDataFunction createInstanceFunction(QWidget *)
{
return &createInstance<impl, QWidget>;
}
CreateInstanceWithMetaDataFunction createInstanceFunction(...)
{
return &createInstance<impl, QObject>;
}
};
// Use std::enable_if_t once C++14 can be relied on
template<bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
/**
* Uses a default instance creation function depending on the type of interface. If the
* interface inherits from
* @li @c KParts::Part the function will call
* @code
* new T(QWidget *parentWidget, QObject *parent, const QVariantList &args)
* @endcode
* @li @c QWidget the function will call
* @code
* new T(QWidget *parent, const QVariantList &args)
* @endcode
* @li else the function will call
* @code
* new T(QObject *parent, const QVariantList &args)
* @endcode
*
* If those constructor methods are not callable this overload is not available.
*/
template<class T, enable_if_t<InheritanceChecker<T>::enabled, int> = 0>
void registerPlugin()
{
CreateInstanceWithMetaDataFunction instanceFunction = InheritanceChecker<T>().createInstanceFunction(static_cast<T *>(nullptr));
registerPlugin(&T::staticMetaObject, instanceFunction);
}
/**
* Uses a default instance creation function depending on the type of interface. If the
* interface inherits from
* @li @c KParts::Part the function will call
* @code
* new T(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
* @li @c QWidget the function will call
* @code
* new T(QWidget *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
* @li else the function will call
* @code
* new T(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
* @endcode
*
* If those constructor methods are not callable this overload is not available.
*/
template<class T, enable_if_t<InheritanceWithMetaDataChecker<T>::enabled, int> = 0>
void registerPlugin()
{
CreateInstanceWithMetaDataFunction instanceFunction = InheritanceWithMetaDataChecker<T>().createInstanceFunction(static_cast<T *>(nullptr));
registerPlugin(&T::staticMetaObject, instanceFunction);
}
/**
* Registers a plugin with the factory. Call this function from the constructor of the
* KPluginFactory subclass to make the create function able to instantiate the plugin when asked
* for an interface the plugin implements.
*
* @param T the name of the plugin class
* @param instanceFunction A function pointer to a function that creates an instance of the plugin.
* @since 5.96
*/
template<class T>
void registerPlugin(CreateInstanceWithMetaDataFunction instanceFunction)
{
registerPlugin(&T::staticMetaObject, instanceFunction);
}
/**
* This function is called when the factory asked to create an Object.
*
* You may reimplement it to provide a very flexible factory. This is especially useful to
* provide generic factories for plugins implemented using a scripting language.
*
* @param iface the staticMetaObject::className() string identifying the plugin interface that
* was requested. E.g. for KCModule plugins this string will be "KCModule".
* @param parentWidget only used if the requested plugin is a KPart.
* @param parent the parent object for the plugin object.
* @param args a plugin specific list of arbitrary arguments.
*/
virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args);
template<class impl, class ParentType>
static QObject *createInstance(QWidget * /*parentWidget*/, QObject *parent, const KPluginMetaData & /*metaData*/, const QVariantList &args)
{
ParentType *p = nullptr;
if (parent) {
p = qobject_cast<ParentType *>(parent);
Q_ASSERT(p);
}
if constexpr (std::is_constructible<impl, ParentType *, QVariantList>::value) {
return new impl(p, args);
} else {
return new impl(p);
}
}
template<class impl, class ParentType>
static QObject *createWithMetaDataInstance(QWidget * /*parentWidget*/, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
{
ParentType *p = nullptr;
if (parent) {
p = qobject_cast<ParentType *>(parent);
Q_ASSERT(p);
}
if constexpr (std::is_constructible<impl, ParentType *, KPluginMetaData, QVariantList>::value) {
return new impl(p, metaData, args);
} else {
return new impl(p, metaData);
}
}
template<class impl>
static QObject *createPartWithMetaDataInstance(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
{
if constexpr (std::is_constructible<impl, QWidget *, QObject *, KPluginMetaData, QVariantList>::value) {
return new impl(parentWidget, parent, metaData, args);
} else {
return new impl(parentWidget, parent, metaData);
}
}
private:
friend KPluginFactoryPrivate;
std::unique_ptr<KPluginFactoryPrivate> const d;
void registerPlugin(const QMetaObject *metaObject, CreateInstanceWithMetaDataFunction instanceFunction);
// The logging categories are not part of the public API, consequently this needs to be a private function
static void logFailedInstantiationMessage(KPluginMetaData data);
static void logFailedInstantiationMessage(const char *className, KPluginMetaData data);
};
template<typename T>
inline T *KPluginFactory::create(QObject *parent, const QVariantList &args)
{
QObject *o = create(T::staticMetaObject.className(), parent && parent->isWidgetType() ? reinterpret_cast<QWidget *>(parent) : nullptr, parent, args);
T *t = qobject_cast<T *>(o);
if (!t) {
delete o;
}
return t;
}
template<typename T>
inline T *KPluginFactory::create(QWidget *parentWidget, QObject *parent, const QVariantList &args)
{
QObject *o = create(T::staticMetaObject.className(), parentWidget, parent, args);
T *t = qobject_cast<T *>(o);
if (!t) {
delete o;
}
return t;
}
Q_DECLARE_INTERFACE(KPluginFactory, KPluginFactory_iid)
#endif // KPLUGINFACTORY_H
@@ -0,0 +1,25 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINFACTORY_P_H
#define KPLUGINFACTORY_P_H
#include "kpluginfactory.h"
#include <KPluginMetaData>
class KPluginFactoryPrivate
{
public:
using PluginWithMetadata = QPair<const QMetaObject *, KPluginFactory::CreateInstanceWithMetaDataFunction>;
KPluginMetaData metaData;
std::vector<PluginWithMetadata> createInstanceWithMetaDataHash;
};
#endif // KPLUGINFACTORY_P_H
@@ -0,0 +1,580 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginmetadata.h"
#include "kstaticpluginhelpers_p.h"
#include "kcoreaddons_debug.h"
#include "kjsonutils.h"
#include <QCoreApplication>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QLocale>
#include <QMimeDatabase>
#include <QPluginLoader>
#include <QStandardPaths>
#include "kaboutdata.h"
#include <optional>
#include <unordered_map>
using PluginCache = std::unordered_map<QString, std::vector<KPluginMetaData>>;
Q_GLOBAL_STATIC(PluginCache, s_pluginNamespaceCache)
class KPluginMetaDataPrivate : public QSharedData
{
public:
KPluginMetaDataPrivate(const QJsonObject &obj, const QString &fileName, KPluginMetaData::KPluginMetaDataOptions options = {})
: m_metaData(obj)
, m_rootObj(obj.value(QLatin1String("KPlugin")).toObject())
, m_fileName(fileName)
, m_options(options)
{
}
const QJsonObject m_metaData;
const QJsonObject m_rootObj;
// If we want to load a file, but it does not exist we want to keep the requested file name for logging
QString m_requestedFileName;
const QString m_fileName;
const KPluginMetaData::KPluginMetaDataOptions m_options;
std::optional<QStaticPlugin> staticPlugin = std::nullopt;
// We determine this once and reuse the value. It can never change during the
// lifetime of the KPluginMetaData object
QString m_pluginId;
qint64 m_lastQueriedTs = 0;
static void forEachPlugin(const QString &directory, std::function<void(const QFileInfo &)> callback)
{
QStringList dirsToCheck;
#ifdef Q_OS_ANDROID
dirsToCheck << QCoreApplication::libraryPaths();
#else
if (QDir::isAbsolutePath(directory)) {
dirsToCheck << directory;
} else {
dirsToCheck = QCoreApplication::libraryPaths();
const QString appDirPath = QCoreApplication::applicationDirPath();
dirsToCheck.removeOne(appDirPath);
dirsToCheck.prepend(appDirPath);
for (QString &libDir : dirsToCheck) {
libDir += QLatin1Char('/') + directory;
}
}
#endif
qCDebug(KCOREADDONS_DEBUG) << "Checking for plugins in" << dirsToCheck;
for (const QString &dir : std::as_const(dirsToCheck)) {
QDirIterator it(dir, QDir::Files);
while (it.hasNext()) {
it.next();
#ifdef Q_OS_ANDROID
QString prefix(QLatin1String("libplugins_") + QString(directory).replace(QLatin1Char('/'), QLatin1String("_")));
if (!prefix.endsWith(QLatin1Char('_'))) {
prefix.append(QLatin1Char('_'));
}
if (it.fileName().startsWith(prefix) && QLibrary::isLibrary(it.fileName())) {
#else
if (QLibrary::isLibrary(it.fileName())) {
#endif
callback(it.fileInfo());
}
}
}
}
struct StaticPluginLoadResult {
QString fileName;
QJsonObject metaData;
};
// This is only relevant in the findPlugins context and thus internal API.
// If one has a static plugin from QPluginLoader::staticPlugins and does not
// want it to have metadata, using KPluginMetaData makes no sense
static KPluginMetaData
ofStaticPlugin(const QString &pluginNamespace, const QString &fileName, KPluginMetaData::KPluginMetaDataOptions options, QStaticPlugin plugin)
{
QString pluginPath = pluginNamespace + u'/' + fileName;
auto d = new KPluginMetaDataPrivate(plugin.metaData().value(QLatin1String("MetaData")).toObject(), pluginPath, options);
d->staticPlugin = plugin;
d->m_pluginId = fileName;
KPluginMetaData data;
data.d = d;
return data;
}
static void pluginLoaderForPath(QPluginLoader &loader, const QString &path)
{
if (path.startsWith(QLatin1Char('/'))) { // Absolute path, use as it is
loader.setFileName(path);
} else {
loader.setFileName(QCoreApplication::applicationDirPath() + QLatin1Char('/') + path);
if (loader.fileName().isEmpty()) {
loader.setFileName(path);
}
}
}
static KPluginMetaDataPrivate *ofPath(const QString &path, KPluginMetaData::KPluginMetaDataOptions options)
{
QPluginLoader loader;
pluginLoaderForPath(loader, path);
const QJsonObject metaData = loader.metaData();
if (metaData.isEmpty()) {
qCDebug(KCOREADDONS_DEBUG) << "no metadata found in" << loader.fileName() << loader.errorString();
}
auto ret = new KPluginMetaDataPrivate(metaData.value(QLatin1String("MetaData")).toObject(), //
QFileInfo(loader.fileName()).absoluteFilePath(),
options);
ret->m_requestedFileName = path;
return ret;
}
};
KPluginMetaData::KPluginMetaData()
: d(new KPluginMetaDataPrivate(QJsonObject(), QString()))
{
}
KPluginMetaData::KPluginMetaData(const KPluginMetaData &other)
: d(other.d)
{
}
KPluginMetaData &KPluginMetaData::operator=(const KPluginMetaData &other)
{
d = other.d;
return *this;
}
KPluginMetaData::~KPluginMetaData() = default;
KPluginMetaData::KPluginMetaData(const QString &pluginFile, KPluginMetaDataOptions options)
: d(KPluginMetaDataPrivate::ofPath(pluginFile, options))
{
// passing QFileInfo an empty string gives the CWD, which is not what we want
if (!d->m_fileName.isEmpty()) {
d->m_pluginId = QFileInfo(d->m_fileName).completeBaseName();
}
if (d->m_metaData.isEmpty() && !options.testFlags(KPluginMetaDataOption::AllowEmptyMetaData)) {
qCDebug(KCOREADDONS_DEBUG) << "plugin metadata in" << pluginFile << "does not have a valid 'MetaData' object";
}
if (const QString id = d->m_rootObj[QLatin1String("Id")].toString(); !id.isEmpty()) {
if (id != d->m_pluginId) {
qWarning(KCOREADDONS_DEBUG) << "The plugin" << pluginFile
<< "explicitly states an Id in the embedded metadata, which is different from the one derived from the filename"
<< "The Id field from the KPlugin object in the metadata should be removed";
} else {
qInfo(KCOREADDONS_DEBUG) << "The plugin" << pluginFile << "explicitly states an 'Id' in the embedded metadata."
<< "This value should be removed, the resulting pluginId will not be affected by it";
}
}
}
KPluginMetaData::KPluginMetaData(const QPluginLoader &loader, KPluginMetaDataOptions options)
: d(new KPluginMetaDataPrivate(loader.metaData().value(QLatin1String("MetaData")).toObject(), loader.fileName(), options))
{
if (!loader.fileName().isEmpty()) {
d->m_pluginId = QFileInfo(loader.fileName()).completeBaseName();
}
}
KPluginMetaData::KPluginMetaData(const QJsonObject &metaData, const QString &fileName)
: d(new KPluginMetaDataPrivate(metaData, fileName))
{
auto nameFromMetaData = d->m_rootObj.constFind(QStringLiteral("Id"));
if (nameFromMetaData != d->m_rootObj.constEnd()) {
d->m_pluginId = nameFromMetaData.value().toString();
}
if (d->m_pluginId.isEmpty()) {
d->m_pluginId = QFileInfo(d->m_fileName).completeBaseName();
}
}
KPluginMetaData KPluginMetaData::findPluginById(const QString &directory, const QString &pluginId, KPluginMetaDataOptions options)
{
QPluginLoader loader;
const QString fileName = directory + QLatin1Char('/') + pluginId;
KPluginMetaDataPrivate::pluginLoaderForPath(loader, fileName);
if (loader.load()) {
if (KPluginMetaData metaData(loader, options); metaData.isValid()) {
return metaData;
}
}
if (const auto staticOptional = KStaticPluginHelpers::findById(directory, pluginId)) {
KPluginMetaData data = KPluginMetaDataPrivate::ofStaticPlugin(directory, pluginId, options, staticOptional.value());
Q_ASSERT(data.fileName() == fileName);
return data;
}
return KPluginMetaData{};
}
KPluginMetaData KPluginMetaData::fromJsonFile(const QString &file)
{
QFile f(file);
bool b = f.open(QIODevice::ReadOnly);
if (!b) {
qCWarning(KCOREADDONS_DEBUG) << "Couldn't open" << file;
return {};
}
QJsonParseError error;
const QJsonObject metaData = QJsonDocument::fromJson(f.readAll(), &error).object();
if (error.error) {
qCWarning(KCOREADDONS_DEBUG) << "error parsing" << file << error.errorString();
}
return KPluginMetaData(metaData, QFileInfo(file).absoluteFilePath());
}
QJsonObject KPluginMetaData::rawData() const
{
return d->m_metaData;
}
QString KPluginMetaData::fileName() const
{
return d->m_fileName;
}
QList<KPluginMetaData>
KPluginMetaData::findPlugins(const QString &directory, std::function<bool(const KPluginMetaData &)> filter, KPluginMetaDataOptions options)
{
QList<KPluginMetaData> ret;
const auto staticPlugins = KStaticPluginHelpers::staticPlugins(directory);
for (auto it = staticPlugins.begin(); it != staticPlugins.end(); ++it) {
KPluginMetaData metaData = KPluginMetaDataPrivate::ofStaticPlugin(directory, it.key(), options, it.value());
if (metaData.isValid()) {
if (!filter || filter(metaData)) {
ret << metaData;
}
}
}
QSet<QString> addedPluginIds;
const qint64 nowTs = QDateTime::currentMSecsSinceEpoch(); // For the initial load, stating all files is not needed
const bool checkCache = options.testFlags(KPluginMetaData::CacheMetaData);
std::vector<KPluginMetaData> &cache = (*s_pluginNamespaceCache)[directory];
KPluginMetaDataPrivate::forEachPlugin(directory, [&](const QFileInfo &pluginInfo) {
const QString pluginFile = pluginInfo.absoluteFilePath();
KPluginMetaData metadata;
if (checkCache) {
const auto it = std::find_if(cache.begin(), cache.end(), [&pluginFile](const KPluginMetaData &data) {
return pluginFile == data.fileName();
});
bool isNew = it == cache.cend();
if (!isNew) {
const qint64 lastQueried = (*it).d->m_lastQueriedTs;
Q_ASSERT(lastQueried > 0);
isNew = lastQueried < pluginInfo.lastModified().toMSecsSinceEpoch();
}
if (!isNew) {
metadata = *it;
} else {
metadata = KPluginMetaData(pluginFile, options);
metadata.d->m_lastQueriedTs = nowTs;
cache.push_back(metadata);
}
} else {
metadata = KPluginMetaData(pluginFile, options);
}
if (!metadata.isValid()) {
qCDebug(KCOREADDONS_DEBUG) << pluginFile << "does not contain valid JSON metadata";
return;
}
if (addedPluginIds.contains(metadata.pluginId())) {
return;
}
if (filter && !filter(metadata)) {
return;
}
addedPluginIds << metadata.pluginId();
ret.append(metadata);
});
return ret;
}
bool KPluginMetaData::isValid() const
{
// it can be valid even if m_fileName is empty (as long as the plugin id is
// set)
return !pluginId().isEmpty() && (!d->m_metaData.isEmpty() || d->m_options.testFlags(AllowEmptyMetaData));
}
bool KPluginMetaData::isHidden() const
{
return d->m_rootObj[QLatin1String("Hidden")].toBool();
}
static inline void addPersonFromJson(const QJsonObject &obj, QList<KAboutPerson> *out)
{
KAboutPerson person = KAboutPerson::fromJSON(obj);
if (person.name().isEmpty()) {
qCWarning(KCOREADDONS_DEBUG) << "Invalid plugin metadata: Attempting to create a KAboutPerson from JSON without 'Name' property:" << obj;
return;
}
out->append(person);
}
static QList<KAboutPerson> aboutPersonFromJSON(const QJsonValue &people)
{
QList<KAboutPerson> ret;
if (people.isObject()) {
// single author
addPersonFromJson(people.toObject(), &ret);
} else if (people.isArray()) {
const QJsonArray peopleArray = people.toArray();
for (const QJsonValue &val : peopleArray) {
if (val.isObject()) {
addPersonFromJson(val.toObject(), &ret);
}
}
}
return ret;
}
QList<KAboutPerson> KPluginMetaData::authors() const
{
return aboutPersonFromJSON(d->m_rootObj[QLatin1String("Authors")]);
}
QList<KAboutPerson> KPluginMetaData::translators() const
{
return aboutPersonFromJSON(d->m_rootObj[QLatin1String("Translators")]);
}
QList<KAboutPerson> KPluginMetaData::otherContributors() const
{
return aboutPersonFromJSON(d->m_rootObj[QLatin1String("OtherContributors")]);
}
QString KPluginMetaData::category() const
{
return d->m_rootObj[QLatin1String("Category")].toString();
}
QString KPluginMetaData::description() const
{
return KJsonUtils::readTranslatedString(d->m_rootObj, QStringLiteral("Description"));
}
QString KPluginMetaData::iconName() const
{
return d->m_rootObj[QLatin1String("Icon")].toString();
}
QString KPluginMetaData::license() const
{
return d->m_rootObj[QLatin1String("License")].toString();
}
QString KPluginMetaData::licenseText() const
{
return KAboutLicense::byKeyword(license()).text();
}
QString KPluginMetaData::name() const
{
return KJsonUtils::readTranslatedString(d->m_rootObj, QStringLiteral("Name"));
}
QString KPluginMetaData::copyrightText() const
{
return KJsonUtils::readTranslatedString(d->m_rootObj, QStringLiteral("Copyright"));
}
QString KPluginMetaData::pluginId() const
{
return d->m_pluginId;
}
QString KPluginMetaData::version() const
{
return d->m_rootObj[QLatin1String("Version")].toString();
}
QString KPluginMetaData::website() const
{
return d->m_rootObj[QLatin1String("Website")].toString();
}
QString KPluginMetaData::bugReportUrl() const
{
return d->m_rootObj[QLatin1String("BugReportUrl")].toString();
}
QStringList KPluginMetaData::mimeTypes() const
{
return d->m_rootObj[QLatin1String("MimeTypes")].toVariant().toStringList();
}
bool KPluginMetaData::supportsMimeType(const QString &mimeType) const
{
// Check for exact matches first. This can delay parsing the full MIME
// database until later and noticeably speed up application startup on
// slower systems.
const QStringList mimes = mimeTypes();
if (mimes.contains(mimeType)) {
return true;
}
// Now check for MIME type inheritance to find non-exact matches:
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForName(mimeType);
if (!mime.isValid()) {
return false;
}
return std::any_of(mimes.begin(), mimes.end(), [&](const QString &supportedMimeName) {
return mime.inherits(supportedMimeName);
});
}
QStringList KPluginMetaData::formFactors() const
{
return d->m_rootObj.value(QLatin1String("FormFactors")).toVariant().toStringList();
}
bool KPluginMetaData::isEnabledByDefault() const
{
const QLatin1String key("EnabledByDefault");
const QJsonValue val = d->m_rootObj[key];
if (val.isBool()) {
return val.toBool();
} else if (val.isString()) {
qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "in" << d->m_fileName << "to be boolean, but it was a string";
return val.toString() == QLatin1String("true");
}
return false;
}
QString KPluginMetaData::value(QStringView key, const QString &defaultValue) const
{
const QJsonValue value = d->m_metaData.value(key);
if (value.isString()) {
return value.toString(defaultValue);
} else if (value.isArray()) {
qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "in" << d->m_fileName << "to be a single string, but it is an array";
return value.toVariant().toStringList().join(QChar::fromLatin1(','));
} else if (value.isBool()) {
qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "in" << d->m_fileName << "to be a single string, but it is a bool";
return value.toBool() ? QStringLiteral("true") : QStringLiteral("false");
}
return defaultValue;
}
QString KPluginMetaData::value(const QString &key, const QString &defaultValue) const
{
return value(QStringView(key), defaultValue);
}
bool KPluginMetaData::value(QStringView key, bool defaultValue) const
{
const QJsonValue value = d->m_metaData.value(key);
if (value.isBool()) {
return value.toBool();
} else if (value.isString()) {
return value.toString() == QLatin1String("true");
} else {
return defaultValue;
}
}
bool KPluginMetaData::value(const QString &key, bool defaultValue) const
{
return value(QStringView(key), defaultValue);
}
int KPluginMetaData::value(QStringView key, int defaultValue) const
{
const QJsonValue value = d->m_metaData.value(key);
if (value.isDouble()) {
return value.toInt();
} else if (value.isString()) {
const QString intString = value.toString();
bool ok;
int convertedIntValue = intString.toInt(&ok);
if (ok) {
return convertedIntValue;
} else {
qCWarning(KCOREADDONS_DEBUG) << "Expected" << key << "to be an int, instead" << intString << "was specified in the JSON metadata" << d->m_fileName;
return defaultValue;
}
} else {
return defaultValue;
}
}
int KPluginMetaData::value(const QString &key, int defaultValue) const
{
return value(QStringView(key), defaultValue);
}
QStringList KPluginMetaData::value(QStringView key, const QStringList &defaultValue) const
{
const QJsonValue value = d->m_metaData.value(key);
if (value.isUndefined() || value.isNull()) {
return defaultValue;
} else if (value.isObject()) {
qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a string list, instead an object was specified in" << d->m_fileName;
return defaultValue;
} else if (value.isArray()) {
return value.toVariant().toStringList();
} else {
const QString asString = value.isString() ? value.toString() : value.toVariant().toString();
if (asString.isEmpty()) {
return defaultValue;
}
qCDebug(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a string list in" << d->m_fileName
<< "Treating it as a list with a single entry:" << asString;
return QStringList(asString);
}
}
QStringList KPluginMetaData::value(const QString &key, const QStringList &defaultValue) const
{
return value(QStringView(key), defaultValue);
}
bool KPluginMetaData::operator==(const KPluginMetaData &other) const
{
return d->m_fileName == other.d->m_fileName && d->m_metaData == other.d->m_metaData;
}
bool KPluginMetaData::isStaticPlugin() const
{
return d->staticPlugin.has_value();
}
QString KPluginMetaData::requestedFileName() const
{
return d->m_requestedFileName;
}
QStaticPlugin KPluginMetaData::staticPlugin() const
{
Q_ASSERT(d);
Q_ASSERT(d->staticPlugin.has_value());
return d->staticPlugin.value();
}
QDebug operator<<(QDebug debug, const KPluginMetaData &metaData)
{
QDebugStateSaver saver(debug);
debug.nospace() << "KPluginMetaData(pluginId:" << metaData.pluginId() << ", fileName: " << metaData.fileName() << ')';
return debug;
}
#include "moc_kpluginmetadata.cpp"
@@ -0,0 +1,438 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINMETADATA_H
#define KPLUGINMETADATA_H
#include "kcoreaddons_export.h"
#include <QExplicitlySharedDataPointer>
#include <QJsonObject>
#include <QMetaType>
#include <QString>
#include <QStringList>
#include <functional>
class QPluginLoader;
class QStaticPlugin;
class KPluginMetaDataPrivate;
class KAboutPerson;
/**
* @class KPluginMetaData kpluginmetadata.h KPluginMetaData
*
* This class allows easily accessing some standardized values from the JSON metadata that
* can be embedded into Qt plugins. Additional plugin-specific metadata can be retrieved by
* directly reading from the QJsonObject returned by KPluginMetaData::rawData().
*
* For embedded metadata, you should not specify an id manually. Instead the id will
* be derived from the file basename.
*
* The following keys will be read from an object "KPlugin" inside the metadata JSON:
*
* Key | Accessor function | JSON Type
* -------------------| -------------------- | ---------------------
* Name | name() | string
* Description | description() | string
* Icon | iconName() | string
* Authors | authors() | object array (KAboutPerson)
* Category | category() | string
* License | license() | string
* Copyright | copyrightText() | string
* Id | pluginId() | string
* Version | version() | string
* Website | website() | string
* BugReportUrl | bugReportUrl() | string
* EnabledByDefault | isEnabledByDefault() | bool
* MimeTypes | mimeTypes() | string array
* FormFactors | formFactors() | string array
* Translators | translators() | object array (KAboutPerson)
* OtherContributors | otherContributors() | object array (KAboutPerson)
*
* The Authors, Translators and OtherContributors keys are expected to be
* list of objects that match the structure expected by KAboutPerson::fromJSON().
*
* An example metadata json file could look like this:
* @verbatim
{
"KPlugin": {
"Name": "Date and Time",
"Description": "Date and time by timezone",
"Icon": "preferences-system-time",
"Authors": [ { "Name": "Aaron Seigo", "Email": "aseigo@kde.org" } ],
"Category": "Date and Time",
"EnabledByDefault": "true",
"License": "LGPL",
"Version": "1.0",
"Website": "https://plasma.kde.org/"
}
}
@endverbatim
*
* @sa KAboutPerson::fromJSON()
* @since 5.1
*/
class KCOREADDONS_EXPORT KPluginMetaData
{
Q_GADGET
Q_PROPERTY(bool isValid READ isValid CONSTANT)
Q_PROPERTY(bool isHidden READ isHidden CONSTANT)
Q_PROPERTY(QString fileName READ fileName CONSTANT)
Q_PROPERTY(QJsonObject rawData READ rawData CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString description READ description CONSTANT)
Q_PROPERTY(QList<KAboutPerson> authors READ authors CONSTANT)
Q_PROPERTY(QList<KAboutPerson> translators READ translators CONSTANT)
Q_PROPERTY(QList<KAboutPerson> otherContributors READ otherContributors CONSTANT)
Q_PROPERTY(QString category READ category CONSTANT)
Q_PROPERTY(QString iconName READ iconName CONSTANT)
Q_PROPERTY(QString license READ license CONSTANT)
Q_PROPERTY(QString licenseText READ licenseText CONSTANT)
Q_PROPERTY(QString copyrightText READ copyrightText CONSTANT)
Q_PROPERTY(QString pluginId READ pluginId CONSTANT)
Q_PROPERTY(QString version READ version CONSTANT)
Q_PROPERTY(QString website READ website CONSTANT)
Q_PROPERTY(QString bugReportUrl READ bugReportUrl CONSTANT)
Q_PROPERTY(QStringList mimeTypes READ mimeTypes CONSTANT)
Q_PROPERTY(QStringList formFactors READ formFactors CONSTANT)
Q_PROPERTY(bool isEnabledByDefault READ isEnabledByDefault CONSTANT)
public:
/**
* Options for creating a KPluginMetaData object.
* @since 5.91
*/
enum KPluginMetaDataOption {
AllowEmptyMetaData = 1, ///< Plugins with empty metaData are considered valid
/**
* If KCoreAddons should keep metadata in cache. This makes querying the namespace again faster. Consider using this if you need revalidation of plugins
* @since 6.0
*/
CacheMetaData = 2,
};
Q_DECLARE_FLAGS(KPluginMetaDataOptions, KPluginMetaDataOption)
Q_FLAG(KPluginMetaDataOption)
/** Creates an invalid KPluginMetaData instance */
KPluginMetaData();
/**
* Reads the plugin metadata from a QPluginLoader instance. You must call QPluginLoader::setFileName()
* or use the appropriate constructor on @p loader before calling this.
* @param option Added in 6.0, see enum docs
*/
KPluginMetaData(const QPluginLoader &loader, KPluginMetaDataOptions options = {});
/**
* Reads the plugin metadata from a plugin which can be loaded from @p file.
*
* Platform-specific library suffixes may be omitted since @p file will be resolved
* using the same logic as QPluginLoader.
*
* @see QPluginLoader::setFileName()
*/
KPluginMetaData(const QString &pluginFile, KPluginMetaDataOptions options = {});
/**
* Creates a KPluginMetaData from a QJsonObject holding the metadata and a file name
* This can be used if the data is not retrieved from a Qt C++ plugin library but from some
* other source.
*
* @param metaData the JSON metadata to use for this object
* @param pluginFile the file that the plugin can be loaded from
*
* @since 6.0
*/
KPluginMetaData(const QJsonObject &metaData, const QString &fileName);
/**
* Copy contructor
*/
KPluginMetaData(const KPluginMetaData &);
/**
* Copy assignment
*/
KPluginMetaData &operator=(const KPluginMetaData &);
/**
* Destructor
*/
~KPluginMetaData();
/**
* Load a KPluginMetaData instance from a .json file. Unlike the constructor with a single file argument,
* this ensure that only JSON format plugins are loaded and any other type is rejected.
*
* @param jsonFile the .json file to load
* @since 5.91
*/
static KPluginMetaData fromJsonFile(const QString &jsonFile);
/**
* @param directory The directory to search for plugins. If a relative path is given for @p directory,
* all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a
* subdirectory. If an absolute path is given only that directory will be searched.
* @note Check if the returned KPluginMetaData is valid before continuing to use it.
*
* @param pluginId The Id of the plugin. The id should be the same as the filename, see KPluginMetaData::pluginId()
* @param option Added in 6.0, see enum docs
* @since 5.84
*/
static KPluginMetaData findPluginById(const QString &directory, const QString &pluginId, KPluginMetaDataOptions options = {});
/**
* Find all plugins inside @p directory. Only plugins which have JSON metadata will be considered.
*
* @param directory The directory to search for plugins. If a relative path is given for @p directory,
* all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a
* subdirectory. If an absolute path is given only that directory will be searched.
*
* @param filter a callback function that returns @c true if the found plugin should be loaded
* and @c false if it should be skipped. If this argument is omitted all plugins will be loaded
* @param option Weather or not allow plugins with empty metadata to be considered valid
*
* @return all plugins found in @p directory that fulfil the constraints of @p filter
* @since 5.86
*/
static QList<KPluginMetaData>
findPlugins(const QString &directory, std::function<bool(const KPluginMetaData &)> filter = {}, KPluginMetaDataOptions options = {});
/**
* @return whether this object holds valid information about a plugin.
* If this is @c true pluginId() will return a non-empty string.
*/
bool isValid() const;
/**
* @return whether this object should be hidden
*
* @since 5.8
*/
bool isHidden() const;
/**
* @return the path to the plugin.
* When the KPluginMetaData(QJsonObject, QString) constructor is used, the string is not modified.
* Otherwise, the path is resolved using QPluginLoader.
* For static plugins the fileName is the namespace and pluginId concatenated
*
* @note It is not guaranteed that this is a valid path to a shared library (i.e. loadable
* by QPluginLoader) since the metadata could also refer to a non-C++ plugin.
*/
QString fileName() const;
/**
* @return the full metadata stored inside the plugin file.
*/
QJsonObject rawData() const;
/**
* @return the user visible name of the plugin.
*/
QString name() const;
/**
* @return a short description of the plugin.
*/
QString description() const;
/**
* @return the author(s) of this plugin.
*/
QList<KAboutPerson> authors() const;
/**
* @return the translator(s) of this plugin.
*
* @since 5.18
*/
QList<KAboutPerson> translators() const;
/**
* @return a list of people that contributed to this plugin (other than the authors and translators).
*
* @since 5.18
*/
QList<KAboutPerson> otherContributors() const;
/**
* @return the categories of this plugin (e.g. "playlist/skin").
*/
QString category() const;
/**
* @return the icon name for this plugin
* @see QIcon::fromTheme()
*/
QString iconName() const;
/**
* @return the short license identifier (e.g. LGPL).
* @see KAboutLicense::byKeyword() for retrieving the full license information
*/
QString license() const;
/**
* @return the text of the license, equivalent to KAboutLicense::byKeyword(license()).text()
* @since 5.73
*/
QString licenseText() const;
/**
* @return a short copyright statement
*
* @since 5.18
*/
QString copyrightText() const;
/**
* @return the unique identifier within the namespace of the plugin
*
* For C++ plugins, this ID is derived from the filename.
* It should not be set in the metadata explicitly.
*
* When using @ref KPluginMetaData::fromJsonFile or @ref KPluginMetaData(QJsonObject, QString),
* the "Id" of the "KPlugin" object will be used. If unset, it will be derived
* from the filename.
*/
QString pluginId() const;
/**
* @return the version of the plugin.
*/
QString version() const;
/**
* @return the website of the plugin.
*/
QString website() const;
/**
* @return the website where people can report a bug found in this plugin
* @since 5.99
*/
QString bugReportUrl() const;
/**
* @return a list of MIME types this plugin can handle (e.g. "application/pdf", "image/png", etc.)
* @since 5.16
*/
QStringList mimeTypes() const;
/**
* @return true if this plugin can handle the given mimetype
* This is more accurate than mimeTypes().contains(mimeType) because it also
* takes MIME type inheritance into account.
* @since 5.66
*/
bool supportsMimeType(const QString &mimeType) const;
/**
* @return A string list of formfactors this plugin is useful for, e.g. desktop, handset or mediacenter.
* The keys for this are not formally defined, though the above-mentioned values should be used when applicable.
*
* @since 5.12
*/
QStringList formFactors() const;
/**
* @return whether the plugin should be enabled by default.
* This is only a recommendation, applications can ignore this value if they want to.
*/
bool isEnabledByDefault() const;
/**
* Returns @c true if the plugin is enabled in @p config, otherwise returns isEnabledByDefault().
* This can be used in conjunction with KPluginWidget.
*
* The @p config param should be a KConfigGroup object, because KCoreAddons can not depend
* on KConfig directly, this parameter is a template.
* @param config KConfigGroup where the enabled state is stored
* @since 5.89
*/
template<typename T>
bool isEnabled(const T &config) const
{
Q_ASSERT(config.isValid());
return config.readEntry(pluginId() + QLatin1String("Enabled"), isEnabledByDefault());
}
/**
* @return the string value for @p key from the metadata or @p defaultValue if the key does not exist
*
* if QString is not the correct type for @p key you should use the other overloads or @ref KPluginMetaData::rawData
*/
QString value(QStringView key, const QString &defaultValue = QString()) const;
QString value(const QString &key, const QString &defaultValue = QString()) const; // TODO KF7: remove
QString value(const QString &key, const char *ch) const = delete;
/**
* @overload
* @since 5.88
*/
bool value(QStringView key, bool defaultValue) const;
bool value(const QString &key, bool defaultValue) const; // TODO KF7: remove
/**
* @overload
* @since 5.88
*/
int value(QStringView key, int defaultValue) const;
int value(const QString &key, int defaultValue) const; // TODO KF7: remove
/** @return the value for @p key from the metadata or @p defaultValue if the key does not exist.
* If the type of @p key is string, a list containing just that string will be returned.
* If the type is array, the list will contain one entry for each array member.
* @overload
* @since 5.88
*/
QStringList value(QStringView key, const QStringList &defaultValue) const;
QStringList value(const QString &key, const QStringList &defaultValue) const; // TODO KF7: remove
/**
* @return @c true if this object is equal to @p other, otherwise @c false
*/
bool operator==(const KPluginMetaData &other) const;
/**
* @return @c true if this object is not equal to @p other, otherwise @c false.
*/
inline bool operator!=(const KPluginMetaData &other) const
{
return !(*this == other);
}
/**
* @note for loading plugin the plugin independently of it being static/dynamic
* use @ref KPluginFactory::loadFactory or @ref KPluginFactory::instantiatePlugin.
* @return true if the instance represents a static plugin
* @since 5.89
*/
bool isStaticPlugin() const;
private:
KCOREADDONS_NO_EXPORT QStaticPlugin staticPlugin() const;
KCOREADDONS_NO_EXPORT QString requestedFileName() const;
QExplicitlySharedDataPointer<KPluginMetaDataPrivate> d;
friend class KPluginFactory;
friend class KPluginMetaDataPrivate;
};
inline size_t qHash(const KPluginMetaData &md, size_t seed)
{
return qHash(md.pluginId(), seed);
}
/// @since 6.0
KCOREADDONS_EXPORT QDebug operator<<(QDebug debug, const KPluginMetaData &metaData);
Q_DECLARE_TYPEINFO(KPluginMetaData, Q_RELOCATABLE_TYPE);
Q_DECLARE_OPERATORS_FOR_FLAGS(KPluginMetaData::KPluginMetaDataOptions)
#endif // KPLUGINMETADATA_H
@@ -0,0 +1,99 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"bla": "",
"properties": {
"KPlugin": {
"properties": {
"Name": {
"type": "string"
},
"Description": {
"type": "string"
},
"Icon": {
"type": "string"
},
"Authors": {
"type": "array",
"items": {
"$ref": "#/definitions/KAboutPerson"
}
},
"Category": {
"type": "string"
},
"License": {
"type": "string"
},
"Copyright": {
"type": "string"
},
"Id": {
"type": "string"
},
"Version": {
"type": "string"
},
"Website": {
"type": "string"
},
"BugReportUrl": {
"type": "string"
},
"EnabledByDefault": {
"type": "boolean"
},
"MimeTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"FormFactors": {
"type": "array",
"items": {
"type": "string"
}
},
"Translators": {
"type": "array",
"items": {
"$ref": "#/definitions/KAboutPerson"
}
},
"OtherContributors": {
"type": "array",
"items": {
"$ref": "#/definitions/KAboutPerson"
}
}
}
}
},
"definitions": {
"KAboutPerson": {
"type": "object",
"properties": {
"Name": {
"type": "string"
},
"Email": {
"type": "string"
},
"Task": {
"type": "string"
},
"Website": {
"type": "string"
},
"AvatarUrl": {
"type": "string"
}
},
"required": [
"Name"
]
}
}
}
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2021-2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcoreaddons_export.h"
#include "kstaticpluginhelpers_p.h"
typedef QHash<QString, QMap<QString, QStaticPlugin>> StaticPluginMap;
Q_GLOBAL_STATIC(StaticPluginMap, s_staticPlugins)
QMap<QString, QStaticPlugin> KStaticPluginHelpers::staticPlugins(const QString &directory)
{
return s_staticPlugins->value(directory);
}
std::optional<QStaticPlugin> KStaticPluginHelpers::findById(const QString &directory, const QString &pluginId)
{
const auto staticPlugins = s_staticPlugins->value(directory);
const auto it = staticPlugins.constFind(pluginId);
return it == staticPlugins.end() ? std::nullopt : std::optional(it.value());
}
// Used in autogenerated code, see kcoreaddons_target_static_plugins
KCOREADDONS_EXPORT void kRegisterStaticPluginFunction(const QString &pluginId, const QString &directory, QStaticPlugin plugin)
{
(*s_staticPlugins)[directory].insert(pluginId, plugin);
}
@@ -0,0 +1,19 @@
/*
SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QPluginLoader>
#include <optional>
namespace KStaticPluginHelpers
{
/**
* This is an implementations detail since consumers should only interact with KPluginMetaData::findPlugin*
* to query the available plugins!
*/
std::optional<QStaticPlugin> findById(const QString &directory, const QString &pluginId);
/// Map of pluginId and actual plugin
QMap<QString, QStaticPlugin> staticPlugins(const QString &directory);
}
@@ -0,0 +1,52 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Matthias Kalle Dalheimer <kalle@kde.org>
SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
SPDX-FileCopyrightText: 2005 Joseph Wenninger <kde@jowenn.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "krandom.h"
#include <stdlib.h>
#ifdef Q_OS_WIN
#include <process.h>
#else // Q_OS_WIN
#include <unistd.h>
#endif // Q_OS_WIN
#include <stdio.h>
#include <time.h>
#ifndef Q_OS_WIN
#include <sys/time.h>
#endif // Q_OS_WIN
#include <fcntl.h>
#include <QFile>
#include <QThread>
#include <QThreadStorage>
QString KRandom::randomString(int length)
{
if (length <= 0) {
return QString();
}
QString str;
str.resize(length);
int i = 0;
while (length--) {
int r = QRandomGenerator::global()->bounded(62);
r += 48;
if (r > 57) {
r += 7;
}
if (r > 90) {
r += 6;
}
str[i++] = QLatin1Char(char(r));
// so what if I work backwards?
}
return str;
}
@@ -0,0 +1,72 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Matthias Kalle Dalheimer <kalle@kde.org>
SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
SPDX-FileCopyrightText: 2005 Joseph Wenninger <kde@jowenn.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KRANDOM_H
#define KRANDOM_H
#include <kcoreaddons_export.h>
#include <QRandomGenerator>
#include <QString>
#include <limits>
/**
* \headerfile krandom.h <KRandom>
*
* @short Helper class to create random data
*
* This namespace provides methods which generate random data.
* KRandom is not recommended for serious random-number generation needs,
* like cryptography.
*/
namespace KRandom
{
/**
* Generates a random string. It operates in the range [A-Za-z0-9]
* @param length Generate a string of this length.
* @return the random string
*/
KCOREADDONS_EXPORT QString randomString(int length);
/**
* Reorders the elements of the given container randomly using the given random number generator.
*
* The container needs to implement size() and T &operator[]
*
* @since 5.73
*/
template<typename T>
void shuffle(T &container, QRandomGenerator *generator)
{
Q_ASSERT(container.size() <= std::numeric_limits<int>::max());
// Fisher-Yates algorithm
for (int index = container.size() - 1; index > 0; --index) {
const int swapIndex = generator->bounded(index + 1);
qSwap(container[index], container[swapIndex]);
}
}
/**
* Reorders the elements of the given container randomly.
*
* The container needs to implement size() and T &operator[]
*
* @since 5.73
*/
template<typename T>
void shuffle(T &container)
{
shuffle(container, QRandomGenerator::global());
}
}
#endif
@@ -0,0 +1,269 @@
/*
SPDX-FileCopyrightText: 2002-2008 The Kopete developers <kopete-devel@kde.org>
SPDX-FileCopyrightText: 2008 Carlo Segato <brandon.ml@gmail.com>
SPDX-FileCopyrightText: 2002-2003 Stefan Gehn <metz@gehn.net>
SPDX-FileCopyrightText: 2005 Engin AYDOGAN <engin@bzzzt.biz>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "kemoticonsparser_p.h"
#include <QDebug>
#include <QString>
#include <cstring>
// ### keep sorted by first column and HTML entity-encoded!
struct Emoticon {
const char *match;
const char *replacement;
};
// clang-format off
static constexpr const Emoticon emoticons_map[] = {
{"&gt;-(", "😠"},
{"&gt;:(", "😠"},
{"&gt;:)", "😈"},
{"&gt;:-(", "😠"},
{"&gt;w&lt;", "😟"},
{"&lt;-.-&gt;", "😴"},
{"&lt;3", "♥️"},
{"&lt;]:o){", "🤡"},
{"&lt;|:^0|", "🤡"},
{"()-()", "🤓"},
{"(-_o)zzZ", "😴"},
{"(:|", "🥱"},
{"(@_@)", "😕"},
{"(c:&gt;*", "🤡"},
{"({)", "🤗"},
{"(})", "🤗"},
{"*&lt;:^)", "🤡"},
{"*&lt;:o)", "🤡"},
{"*:o)", "🤡"},
{"*:oB", "🤡"},
{"*:oP", "🤡"},
{"+o(", "🤢"},
{",':(", "😕"},
{"-_-", "😴"},
{"-_-+", "😠"},
{"-o-o-", "🤓"},
{"/00\\", "😟"},
{"0:)", "😇"},
{"0:-)", "😇"},
{"0;)", "😇"},
{"0=)", "😇"},
{"3:)", "😈"},
{"8)", "😎"},
{"8-)", "😎"},
{"8:::(", "😭"},
{":\"-(", "😢"},
{":'(", "😢"},
{":'-(", "😢"},
{":'D", "😆"},
{":(", "🙁"},
{":((", "😢"},
{":)", "🙂"},
{":))", "😆"},
{":*", "😗"},
{":*(", "😢"},
{":*)", "😗"},
{":-$", "😯"},
{":-&amp;", "🤢"},
{":-&gt;", "☺️"},
{":-&gt;&gt;", "☺️"},
{":-(", "🙁"},
{":-)", "🙂"},
{":-))", "😀"},
{":-)*", "😗"},
{":-*", "😗"},
{":-/", "😕"},
{":-@", "😠"},
{":-D", "😀"},
{":-O", "😮"},
{":-P", "😛"},
{":-Q", "😕"},
{":-S", "😕"},
{":-X", "🤫"},
{":-[", "😯"},
{":-o", "😮"},
{":-p", "😛"},
{":-s", "😕"},
{":-t", "😛"},
{":-x", "🤫"},
{":-|", "😐"},
{":-||", "😠"},
{":/", "🫤"},
{":@", "😠"},
{":C", "☹️"},
{":D", "😀"},
{":O", "😮"},
{":P", "😛"},
{":S", "😕"},
{":X", "🤫"},
{":\\", "🫤"},
{":_(", "😢"},
{":c", "☹️"},
{":o", "😮"},
{":o)", "🤡"},
{":p", "😛"},
{":s", "😕"},
{":x", "🤫"},
{":|))", "😀"},
{";(", "😢"},
{";)", "😉"},
{";-(!)", "😗"},
{";-(", "😢"},
{";-)", "😉"},
{";_;", "😢"},
{"= #", "😗"},
{"='(", "😢"},
{"=(", "🙁"},
{"=[", "🙁"},
{"=^D", "😆"},
{"B-)", "😎"},
{"D:", "🙁"},
{"D=", "🙁"},
{"O-)", "😇"},
{"O.o", "🤔"},
{"O.o?", "🤔"},
{"O:)", "😇"},
{"O:-)", "😇"},
{"O;", "😇"},
{"T.T", "🙁"},
{"T_T", "😭"},
{"X-(", "😠"},
{"Y_Y", "🙁"},
{"Z_Z", "😴"},
{"\\o-o/", "🤓"},
{"\\~/", "🤓"},
{"]:-&gt;", "😈"},
{"^j^", "😇"},
{"i_i", "😭"},
{"t.t", "🙁"},
{"y_y", "🙁"},
{"|-O", "🥱"},
{"}:-)", "😈"},
};
// clang-format on
static const Emoticon *findEmoticon(QStringView s)
{
auto it = std::lower_bound(std::begin(emoticons_map), std::end(emoticons_map), s, [](const auto &emoticon, auto s) {
return QLatin1String(emoticon.match) < s;
});
if (it != std::end(emoticons_map) && s.startsWith(QLatin1String((*it).match))) {
return it;
}
// if we don't have an exact match but a prefix, that will be in the item before the one returned by lower_bound
if (it != std::begin(emoticons_map)) {
it = std::prev(it);
if (s.startsWith(QLatin1String((*it).match))) {
return it;
}
}
return nullptr;
}
QString KEmoticonsParser::parseEmoticons(const QString &message)
{
QString result;
/* previous char, in the firs iteration assume that it is space since we want
* to let emoticons at the beginning, the very first previous QChar must be a space. */
QChar p = QLatin1Char(' ');
int pos = 0;
int previousPos = 0;
bool inHTMLTag = false;
bool inHTMLLink = false;
bool inHTMLEntity = false;
for (; pos < message.length(); ++pos) {
const QChar c = message[pos];
if (!inHTMLTag) { // Are we already in an HTML tag ?
if (c == QLatin1Char('<')) { // If not check if are going into one
inHTMLTag = true; // If we are, change the state to inHTML
p = c;
continue;
}
} else { // We are already in a HTML tag
if (c == QLatin1Char('>')) { // Check if it ends
inHTMLTag = false; // If so, change the state
if (p == QLatin1Char('a')) {
inHTMLLink = false;
}
} else if (c == QLatin1Char('a') && p == QLatin1Char('<')) { // check if we just entered an anchor tag
inHTMLLink = true; // don't put smileys in urls
}
p = c;
continue;
}
if (!inHTMLEntity) { // are we
if (c == QLatin1Char('&')) {
inHTMLEntity = true;
}
}
if (inHTMLLink) { // i can't think of any situation where a link address might need emoticons
p = c;
continue;
}
if (!p.isSpace() && p != QLatin1Char('>')) { // '>' may mark the end of an html tag
p = c;
continue;
} /* strict requires space before the emoticon */
const auto emoticon = findEmoticon(QStringView(message).mid(pos));
if (emoticon) {
bool found = true;
/* check if the character after this match is space or end of string*/
const int matchLen = std::strlen(emoticon->match);
if (message.length() > pos + matchLen) {
const QChar n = message[pos + matchLen];
//<br/> marks the end of a line
if (n != QLatin1Char('<') && !n.isSpace() && !n.isNull() && n != QLatin1Char('&')) {
found = false;
}
}
if (found) {
result += QStringView(message).mid(previousPos, pos - previousPos);
result += QString::fromUtf8(emoticon->replacement);
/* Skip the matched emoticon's matchText */
pos += matchLen - 1;
previousPos = pos + 1;
} else {
if (inHTMLEntity) {
// If we are in an HTML entity such as &gt;
const int htmlEnd = message.indexOf(QLatin1Char(';'), pos);
// Search for where it ends
if (htmlEnd == -1) {
// Apparently this HTML entity isn't ended, something is wrong, try skip the '&'
// and continue
// qCDebug(KEMOTICONS_CORE) << "Broken HTML entity, trying to recover.";
inHTMLEntity = false;
pos++;
} else {
pos = htmlEnd;
inHTMLEntity = false;
}
}
}
} /* else no emoticons begin with this character, so don't do anything */
p = c;
}
if (result.isEmpty()) {
return message;
}
if (previousPos < message.length()) {
result += QStringView(message).mid(previousPos);
}
return result;
}
@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2002-2008 The Kopete developers <kopete-devel@kde.org>
SPDX-FileCopyrightText: 2008 Carlo Segato <brandon.ml@gmail.com>
SPDX-FileCopyrightText: 2002-2003 Stefan Gehn <metz@gehn.net>
SPDX-FileCopyrightText: 2005 Engin AYDOGAN <engin@bzzzt.biz>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KEMOTICONSPARSER_P_H
#define KEMOTICONSPARSER_P_H
class QString;
/** ASCII art smily replacement with Unicode emojis.
* Taken from former KEmoticons, which has been deprecated for KF6.
*/
namespace KEmoticonsParser
{
/**
* Parses emoticons in text @p text.
* @param text the text to parse
* @return the text with emoticons replaced by Unicode emojis
*/
QString parseEmoticons(const QString &text);
}
#endif
@@ -0,0 +1,304 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Forrest Smith <forrestthewoods@gmail.com>
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfuzzymatcher.h"
#include <QList>
#include <QString>
#include <QStringView>
/**
* Custom toLower function which is much faster than
* c.toLower() directly
*/
static inline constexpr QChar toLower(QChar c)
{
return c.isLower() ? c : c.toLower();
}
// internal
// clang-format off
static bool match_recursive(QStringView::const_iterator pattern,
QStringView::const_iterator str,
int &outScore,
const QStringView::const_iterator strBegin,
const QStringView::const_iterator strEnd,
const QStringView::const_iterator patternEnd,
const uint8_t *srcMatches,
uint8_t *matches,
int nextMatch,
int &totalMatches,
int &recursionCount)
{
static constexpr int recursionLimit = 10;
// max number of matches allowed, this should be enough
static constexpr int maxMatches = 256;
// Count recursions
++recursionCount;
if (recursionCount >= recursionLimit) {
return false;
}
// Detect end of strings
if (pattern == patternEnd || str == strEnd) {
return false;
}
// Recursion params
bool recursiveMatch = false;
uint8_t bestRecursiveMatches[maxMatches];
int bestRecursiveScore = 0;
// Loop through pattern and str looking for a match
bool firstMatch = true;
QChar currentPatternChar = toLower(*pattern);
// Are we matching in sequence start from start?
bool matchingInSequence = true;
while (pattern != patternEnd && str != strEnd) {
// Found match
if (currentPatternChar == toLower(*str)) {
// Supplied matches buffer was too short
if (nextMatch >= maxMatches) {
return false;
}
// "Copy-on-Write" srcMatches into matches
if (firstMatch && srcMatches) {
memcpy(matches, srcMatches, nextMatch);
firstMatch = false;
}
// Recursive call that "skips" this match
uint8_t recursiveMatches[maxMatches];
int recursiveScore = 0;
const auto strNextChar = std::next(str);
if (!matchingInSequence && match_recursive(pattern, strNextChar, recursiveScore, strBegin,
strEnd, patternEnd, matches, recursiveMatches,
nextMatch, totalMatches, recursionCount)) {
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
memcpy(bestRecursiveMatches, recursiveMatches, maxMatches);
bestRecursiveScore = recursiveScore;
}
recursiveMatch = true;
}
// Advance
matches[nextMatch++] = (uint8_t)(std::distance(strBegin, str));
++pattern;
currentPatternChar = toLower(*pattern);
} else {
matchingInSequence = false;
}
++str;
}
// Determine if full pattern was matched
const bool matched = pattern == patternEnd;
// Calculate score
if (matched) {
static constexpr int sequentialBonus = 25;
static constexpr int separatorBonus = 25; // bonus if match occurs after a separator
static constexpr int camelBonus = 25; // bonus if match is uppercase and prev is lower
static constexpr int firstLetterBonus = 15; // bonus if the first letter is matched
static constexpr int leadingLetterPenalty = -5; // penalty applied for every letter in str before the first match
static constexpr int maxLeadingLetterPenalty = -15; // maximum penalty for leading letters
static constexpr int unmatchedLetterPenalty = -1; // penalty for every letter that doesn't matter
static constexpr int nonBeginSequenceBonus = 10;
// Initialize score
outScore = 100;
// Apply leading letter penalty
const int penalty = std::max(leadingLetterPenalty * matches[0], maxLeadingLetterPenalty);
outScore += penalty;
// Apply unmatched penalty
const int unmatched = (int)(std::distance(strBegin, strEnd)) - nextMatch;
outScore += unmatchedLetterPenalty * unmatched;
uint8_t seqs[maxMatches] = {0};
// Apply ordering bonuses
int j = 0;
for (int i = 0; i < nextMatch; ++i) {
const uint8_t currIdx = matches[i];
if (i > 0) {
const uint8_t prevIdx = matches[i - 1];
// Sequential
if (currIdx == (prevIdx + 1)) {
if (j > 0 && seqs[j - 1] == i - 1){
outScore += sequentialBonus;
seqs[j++] = i;
} else {
// In sequence, but from first char
outScore += nonBeginSequenceBonus;
}
}
}
// Check for bonuses based on neighbor character value
if (currIdx > 0) {
// Camel case
const QChar neighbor = *(strBegin + currIdx - 1);
const QChar curr = *(strBegin + currIdx);
if (neighbor.isLower() && curr.isUpper()) {
outScore += camelBonus;
}
// Separator
const bool neighborSeparator = neighbor == QLatin1Char('_') || neighbor == QLatin1Char(' ');
if (neighborSeparator) {
outScore += separatorBonus;
}
} else {
// First letter
outScore += firstLetterBonus;
seqs[j++] = i;
}
}
}
totalMatches = nextMatch;
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
// Recursive score is better than "this"
memcpy(matches, bestRecursiveMatches, maxMatches);
outScore = bestRecursiveScore;
return true;
} else if (matched) {
// "this" score is better than recursive
return true;
} else {
// no match
return false;
}
}
// clang-format on
static bool match_internal(QStringView pattern, QStringView str, int &outScore, unsigned char *matches)
{
if (pattern.isEmpty()) {
return true;
}
int recursionCount = 0;
auto strIt = str.cbegin();
auto patternIt = pattern.cbegin();
const auto patternEnd = pattern.cend();
const auto strEnd = str.cend();
int total = 0;
return match_recursive(patternIt, strIt, outScore, strIt, strEnd, patternEnd, nullptr, matches, 0, total, recursionCount);
}
/**************************************************************/
bool KFuzzyMatcher::matchSimple(QStringView pattern, QStringView str)
{
auto patternIt = pattern.cbegin();
/**
* Instead of doing
*
* strIt.toLower() == patternIt.toLower()
*
* we convert patternIt to Upper / Lower as needed and compare with strIt. This
* saves us from calling toLower() on both strings, making things a little bit faster
*/
bool lower = patternIt->isLower();
QChar cUp = lower ? patternIt->toUpper() : *patternIt;
QChar cLow = lower ? *patternIt : patternIt->toLower();
for (auto strIt = str.cbegin(); strIt != str.cend() && patternIt != pattern.cend(); ++strIt) {
if (*strIt == cLow || *strIt == cUp) {
++patternIt;
lower = patternIt->isLower();
cUp = lower ? patternIt->toUpper() : *patternIt;
cLow = lower ? *patternIt : patternIt->toLower();
}
}
return patternIt == pattern.cend();
}
KFuzzyMatcher::Result KFuzzyMatcher::match(QStringView pattern, QStringView str)
{
/**
* Simple substring matching to flush out non-matching strings
*/
const bool simpleMatch = matchSimple(pattern, str);
KFuzzyMatcher::Result result;
result.matched = false;
result.score = 0;
if (!simpleMatch) {
return result;
}
// actual algorithm
int score = 0;
uint8_t matches[256];
const bool matched = match_internal(pattern, str, score, matches);
result.matched = matched;
result.score = score;
return result;
}
QList<KFuzzyMatcher::Range> KFuzzyMatcher::matchedRanges(QStringView pattern, QStringView str, RangeType type)
{
QList<KFuzzyMatcher::Range> ranges;
if (pattern.isEmpty()) {
return ranges;
}
int totalMatches = 0;
int score = 0;
int recursionCount = 0;
auto strIt = str.cbegin();
auto patternIt = pattern.cbegin();
const auto patternEnd = pattern.cend();
const auto strEnd = str.cend();
uint8_t matches[256];
auto res = match_recursive(patternIt, strIt, score, strIt, strEnd, patternEnd, nullptr, matches, 0, totalMatches, recursionCount);
// didn't match? => We don't care about results
if (!res && type == RangeType::FullyMatched) {
return {};
}
int previousMatch = 0;
for (int i = 0; i < totalMatches; ++i) {
auto matchPos = matches[i];
/**
* Check if this match is part of the previous
* match. If it is, we increase the length of
* the last range.
*/
if (!ranges.isEmpty() && matchPos == previousMatch + 1) {
ranges.last().length++;
} else {
/**
* This is a new match inside the string
*/
ranges.push_back({/* start: */ matchPos, /* length: */ 1});
}
previousMatch = matchPos;
}
return ranges;
}
@@ -0,0 +1,228 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Forrest Smith <forrestthewoods@gmail.com>
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFUZZYMATCHER_H
#define KFUZZYMATCHER_H
#include <kcoreaddons_export.h>
#include <QtContainerFwd>
#include <QtGlobal>
class QString;
class QStringView;
/**
* This namespace contains functions for fuzzy matching a list of strings
* against a pattern.
*
* This code is ported to Qt from lib_fts:
* https://github.com/forrestthewoods/lib_fts
* which tries to replicate SublimeText like fuzzy matching.
*
* @note
* All character matches will happen sequentially. That means that this function is not
* typo tolerant i.e., "gti" will not match "git", but "gt" will. All methods in here are
* stateless i.e., the input string will not be modified. Also note that strings in all the
* functions in this namespace will be matched case-insensitively.
*
* Limitations:
* - Currently this will match only strings with length < 256 correctly. This is because we
* intend on matching a pattern against words / short strings and not paragraphs.
* - No more than 256 matches will happen.
*
* If you are using this with @c QSortFilterProxyModel, you need to override both
* @c QSortFilterProxyModel::lessThan and @c QSortFilterProxyModel::filterAcceptsRow.
* A simple example:
*
* \code
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
{
int score = 0;
const auto idx = sourceModel()->index(sourceRow, 0, sourceParent);
const auto actionName = idx.data().toString().splitRef(QLatin1Char(':')).at(1);
const bool res = kfts::fuzzy_match_sequential(m_pattern, actionName, score);
// store the score in the source model
sourceModel()->setData(idx, score, ScoreRole);
return res;
}
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
{
// use the score here to sort
const int l = sourceLeft.data(ScoreRole).toInt();
const int r = sourceRight.data(ScoreRole).toInt();
return l < r;
}
* \endcode
*
* Additionally you must not use @c invalidateFilter() if you go with the above approach. Instead
* use @c beginResetModel()/@c endResetModel():
*
* \code
* Q_SLOT void setFilterString(const QString &string)
{
beginResetModel();
m_pattern = string;
endResetModel();
}
* \endcode
*
* @short Namespace for fuzzy matching of strings
* @author Waqar Ahmed <waqar.17a@gmail.com>
*/
namespace KFuzzyMatcher
{
/**
* @brief The result of a fuzzy match
*/
struct KCOREADDONS_EXPORT Result {
/**
* Score of this match. This can be negative.if matched is @c false
* then the score will be zero.
*/
int score = 0;
/** @c true if match was successful */
bool matched = false;
};
/**
* @brief A range representing a matched sequence in a string
*
* @since 5.84
*/
struct KCOREADDONS_EXPORT Range {
int start;
int length;
};
/**
* @brief The type of matches to consider when requesting for ranges.
* @see matchedRanges
*
* @since 5.84
*/
enum class RangeType : unsigned char {
/**
* We want ranges only where the pattern fully matches the user
* supplied string
*/
FullyMatched,
/**
* We want ranges for all matches, even if the pattern partially
* matched the user supplied string
*/
All
};
/**
* @brief Simple fuzzy matching of chars in @p pattern with chars in @p str
* sequentially. If there is a match, it will return true and false otherwise.
* There is no scoring. You should use this if score is not important for you
* and only matching is important.
*
* If @p pattern is empty, the function will return @c true
*
* @param pattern to search for. For e.g., text entered by a user to filter a
* list
* @param str the current string from your list of strings
* @return @c true on sucessful match
*
* @since 5.79
*/
KCOREADDONS_EXPORT bool matchSimple(QStringView pattern, QStringView str);
/**
* @brief This is the main function which does scored fuzzy matching.
*
* The return value of this function contains Result#score which should be used to
* sort the results. Without sorting of the results this function won't very effective.
*
* If @p pattern is empty, the function will return @c true
*
* @param pattern to search for. For e.g., text entered by a user to filter a
* list or model
* @param str the current string from your list of strings
* @return A Result type with score of this match and whether the match was
* successful. If there is no match, score is zero. If the match is successful,
* score must be used to sort the results.
*
* @since 5.79
*/
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str);
/**
* @brief A function which returns the positions + lengths where the @p pattern matched
* inside the @p str. The resulting ranges can then be utilized to show the user where
* the matches occurred. Example:
*
* @code
* String: hello
* Pattern: Hlo
*
* Output: [Range{0, 1}, Range{3, 2}]
* @endcode
*
* In the above example @c "Hlo" matched inside the string @c "hello" in two places i.e.,
* position 0 and position 3. At position 0 it matched 'h', and at position 3 it
* matched 'lo'.
*
* The ranges themeselves can't do much so you will have to make the result useful
* in your own way. Some possible uses can be:
* - Transform the result into a vector of @c QTextLayout::FormatRange and then paint
* them in the view
* - Use the result to transform the string into html, for example conver the string from
* above example to "\<b\>H\</b\>el\<b\>lo\</b\>, and then use @c QTextDocument
* to paint it into your view.
*
* Example with the first method:
* @code
* auto ranges = KFuzzyMatcher::matchedRanges(pattern, string);
* QList<QTextLayout::FormatRange> out;
* std::transform(ranges.begin(), ranges.end(), std::back_inserter(out), [](const KFuzzyMatcher::Range &fr){
* return QTextLayout::FormatRange{fr.start, fr.length, QTextCharFormat()};
* });
*
* QTextLayout layout(text, font);
* layout.beginLayout();
* QTextLine line = layout.createLine();
* //...
* layout.endLayout();
*
* layout.setFormats(layout.formats() + out);
* layout.draw(painter, position);
* @endcode
*
* If @p pattern is empty, the function will return an empty vector
*
* if @p type is @c RangeType::All, the function will try to get ranges even if
* the pattern didn't fully match. For example:
* @code
* pattern: "git"
* string: "gti"
* RangeType: All
*
* Output: [Range{0, 1}, Range{2, 1}]
* @endcode
*
* @param pattern to search for. For e.g., text entered by a user to filter a
* list or model
* @param str the current string from your list of strings
* @param type whether to consider ranges from full matches only or all matches including partial matches
* @return A vector of ranges containing positions and lengths where the pattern
* matched. If there was no match, the vector will be empty
*
* @since 5.84
*/
KCOREADDONS_EXPORT QList<KFuzzyMatcher::Range> matchedRanges(QStringView pattern, QStringView str, RangeType type = RangeType::FullyMatched);
} // namespace KFuzzyMatcher
Q_DECLARE_TYPEINFO(KFuzzyMatcher::Range, Q_PRIMITIVE_TYPE);
#endif // KFUZZYMATCHER_H
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kjsonutils.h"
#include <QJsonObject>
QJsonValue KJsonUtils::readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue)
{
QString languageWithCountry = QLocale().name();
auto it = jo.constFind(key + QLatin1Char('[') + languageWithCountry + QLatin1Char(']'));
if (it != jo.constEnd()) {
return it.value();
}
const QStringView language = QStringView(languageWithCountry).mid(0, languageWithCountry.indexOf(QLatin1Char('_')));
it = jo.constFind(key + QLatin1Char('[') + language + QLatin1Char(']'));
if (it != jo.constEnd()) {
return it.value();
}
// no translated value found -> check key
it = jo.constFind(key);
if (it != jo.constEnd()) {
return jo.value(key);
}
return defaultValue;
}
QString KJsonUtils::readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue)
{
return KJsonUtils::readTranslatedValue(jo, key, defaultValue).toString(defaultValue);
}
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KJSONUTILS_H
#define KJSONUTILS_H
#include "kcoreaddons_export.h"
#include <QJsonValue>
class QJsonObject;
class QString;
namespace KJsonUtils
{
/**
* Reads a value from @p jo but unlike QJsonObject::value() it allows different entries for each locale
* This is done by appending the locale identifier in brackets to the key (e.g. "[de_DE]" or "[es]")
* When looking for a key "foo" with German (Germany) locale we will first attempt to read "foo[de_DE]",
* if that does not exist "foo[de]", finally falling back to "foo" if that also doesn't exist.
* @return the translated value for @p key from @p jo or @p defaultValue if @p key was not found
* @since 5.88
*/
KCOREADDONS_EXPORT QJsonValue readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue = QJsonValue());
/**
* @return the translated value of @p key from @p jo as a string or @p defaultValue if @p key was not found
* or the value for @p key is not of type string
* @see KPluginMetaData::readTranslatedValue(const QJsonObject &jo, const QString &key)
* @since 5.88
*/
KCOREADDONS_EXPORT QString readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue = QString());
}
#endif // KJSONUTILS_H
@@ -0,0 +1,378 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmacroexpander_p.h"
#include <QHash>
KMacroExpanderBase::KMacroExpanderBase(QChar c)
: d(new KMacroExpanderBasePrivate(c))
{
}
KMacroExpanderBase::~KMacroExpanderBase() = default;
void KMacroExpanderBase::setEscapeChar(QChar c)
{
d->escapechar = c;
}
QChar KMacroExpanderBase::escapeChar() const
{
return d->escapechar;
}
void KMacroExpanderBase::expandMacros(QString &str)
{
int pos;
int len;
ushort ec = d->escapechar.unicode();
QStringList rst;
QString rsts;
for (pos = 0; pos < str.length();) {
if (ec != 0) {
if (str.unicode()[pos].unicode() != ec) {
goto nohit;
}
if (!(len = expandEscapedMacro(str, pos, rst))) {
goto nohit;
}
} else {
if (!(len = expandPlainMacro(str, pos, rst))) {
goto nohit;
}
}
if (len < 0) {
pos -= len;
continue;
}
rsts = rst.join(QLatin1Char(' '));
rst.clear();
str.replace(pos, len, rsts);
pos += rsts.length();
continue;
nohit:
pos++;
}
}
bool KMacroExpanderBase::expandMacrosShellQuote(QString &str)
{
int pos = 0;
return expandMacrosShellQuote(str, pos) && pos == str.length();
}
int KMacroExpanderBase::expandPlainMacro(const QString &, int, QStringList &)
{
qFatal("KMacroExpanderBase::expandPlainMacro called!");
return 0;
}
int KMacroExpanderBase::expandEscapedMacro(const QString &, int, QStringList &)
{
qFatal("KMacroExpanderBase::expandEscapedMacro called!");
return 0;
}
//////////////////////////////////////////////////
template<typename KT, typename VT>
class KMacroMapExpander : public KMacroExpanderBase
{
public:
KMacroMapExpander(const QHash<KT, VT> &map, QChar c = QLatin1Char('%'))
: KMacroExpanderBase(c)
, macromap(map)
{
}
protected:
int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
private:
QHash<KT, VT> macromap;
};
////////
static bool isIdentifier(ushort c)
{
return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}
////////
template<typename VT>
class KMacroMapExpander<QChar, VT> : public KMacroExpanderBase
{
public:
KMacroMapExpander(const QHash<QChar, VT> &map, QChar c = QLatin1Char('%'))
: KMacroExpanderBase(c)
, macromap(map)
{
}
protected:
int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
private:
QHash<QChar, VT> macromap;
};
template<typename VT>
int KMacroMapExpander<QChar, VT>::expandPlainMacro(const QString &str, int pos, QStringList &ret)
{
typename QHash<QChar, VT>::const_iterator it = macromap.constFind(str.unicode()[pos]);
if (it != macromap.constEnd()) {
ret += it.value();
return 1;
}
return 0;
}
template<typename VT>
int KMacroMapExpander<QChar, VT>::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
if (str.length() <= pos + 1) {
return 0;
}
if (str.unicode()[pos + 1] == escapeChar()) {
ret += QString(escapeChar());
return 2;
}
typename QHash<QChar, VT>::const_iterator it = macromap.constFind(str.unicode()[pos + 1]);
if (it != macromap.constEnd()) {
ret += it.value();
return 2;
}
return 0;
}
template<typename VT>
class KMacroMapExpander<QString, VT> : public KMacroExpanderBase
{
public:
KMacroMapExpander(const QHash<QString, VT> &map, QChar c = QLatin1Char('%'))
: KMacroExpanderBase(c)
, macromap(map)
{
}
protected:
int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
private:
QHash<QString, VT> macromap;
};
template<typename VT>
int KMacroMapExpander<QString, VT>::expandPlainMacro(const QString &str, int pos, QStringList &ret)
{
if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) {
return 0;
}
int sl;
for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) {
;
}
if (!sl) {
return 0;
}
typename QHash<QString, VT>::const_iterator it = macromap.constFind(str.mid(pos, sl));
if (it != macromap.constEnd()) {
ret += it.value();
return sl;
}
return 0;
}
template<typename VT>
int KMacroMapExpander<QString, VT>::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
if (str.length() <= pos + 1) {
return 0;
}
if (str.unicode()[pos + 1] == escapeChar()) {
ret += QString(escapeChar());
return 2;
}
int sl;
int rsl;
int rpos;
if (str.unicode()[pos + 1].unicode() == '{') {
rpos = pos + 2;
if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) {
return 0;
}
sl -= rpos;
rsl = sl + 3;
} else {
rpos = pos + 1;
for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) {
;
}
rsl = sl + 1;
}
if (!sl) {
return 0;
}
typename QHash<QString, VT>::const_iterator it = macromap.constFind(str.mid(rpos, sl));
if (it != macromap.constEnd()) {
ret += it.value();
return rsl;
}
return 0;
}
////////////
int KCharMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret)
{
if (expandMacro(str.unicode()[pos], ret)) {
return 1;
}
return 0;
}
int KCharMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
if (str.length() <= pos + 1) {
return 0;
}
if (str.unicode()[pos + 1] == escapeChar()) {
ret += QString(escapeChar());
return 2;
}
if (expandMacro(str.unicode()[pos + 1], ret)) {
return 2;
}
return 0;
}
int KWordMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret)
{
if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) {
return 0;
}
int sl;
for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) {
;
}
if (!sl) {
return 0;
}
if (expandMacro(str.mid(pos, sl), ret)) {
return sl;
}
return 0;
}
int KWordMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
if (str.length() <= pos + 1) {
return 0;
}
if (str.unicode()[pos + 1] == escapeChar()) {
ret += QString(escapeChar());
return 2;
}
int sl;
int rsl;
int rpos;
if (str.unicode()[pos + 1].unicode() == '{') {
rpos = pos + 2;
if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) {
return 0;
}
sl -= rpos;
rsl = sl + 3;
} else {
rpos = pos + 1;
for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) {
;
}
rsl = sl + 1;
}
if (!sl) {
return 0;
}
if (expandMacro(str.mid(rpos, sl), ret)) {
return rsl;
}
return 0;
}
////////////
template<typename KT, typename VT>
inline QString TexpandMacros(const QString &ostr, const QHash<KT, VT> &map, QChar c)
{
QString str(ostr);
KMacroMapExpander<KT, VT> kmx(map, c);
kmx.expandMacros(str);
return str;
}
template<typename KT, typename VT>
inline QString TexpandMacrosShellQuote(const QString &ostr, const QHash<KT, VT> &map, QChar c)
{
QString str(ostr);
KMacroMapExpander<KT, VT> kmx(map, c);
if (!kmx.expandMacrosShellQuote(str)) {
return QString();
}
return str;
}
// public API
namespace KMacroExpander
{
QString expandMacros(const QString &ostr, const QHash<QChar, QString> &map, QChar c)
{
return TexpandMacros(ostr, map, c);
}
QString expandMacrosShellQuote(const QString &ostr, const QHash<QChar, QString> &map, QChar c)
{
return TexpandMacrosShellQuote(ostr, map, c);
}
QString expandMacros(const QString &ostr, const QHash<QString, QString> &map, QChar c)
{
return TexpandMacros(ostr, map, c);
}
QString expandMacrosShellQuote(const QString &ostr, const QHash<QString, QString> &map, QChar c)
{
return TexpandMacrosShellQuote(ostr, map, c);
}
QString expandMacros(const QString &ostr, const QHash<QChar, QStringList> &map, QChar c)
{
return TexpandMacros(ostr, map, c);
}
QString expandMacrosShellQuote(const QString &ostr, const QHash<QChar, QStringList> &map, QChar c)
{
return TexpandMacrosShellQuote(ostr, map, c);
}
QString expandMacros(const QString &ostr, const QHash<QString, QStringList> &map, QChar c)
{
return TexpandMacros(ostr, map, c);
}
QString expandMacrosShellQuote(const QString &ostr, const QHash<QString, QStringList> &map, QChar c)
{
return TexpandMacrosShellQuote(ostr, map, c);
}
} // namespace
@@ -0,0 +1,398 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMACROEXPANDER_H
#define KMACROEXPANDER_H
#include <QChar>
#include <QStringList>
#include <kcoreaddons_export.h>
#include <memory>
class QString;
template<typename KT, typename VT>
class QHash;
class KMacroExpanderBasePrivate;
/**
* \class KMacroExpanderBase kmacroexpander.h <KMacroExpander>
*
* Abstract base class for the worker classes behind the KMacroExpander namespace
* and the KCharMacroExpander and KWordMacroExpander classes.
*
* @author Oswald Buddenhagen <ossi@kde.org>
*/
class KCOREADDONS_EXPORT KMacroExpanderBase
{
public:
/**
* Constructor.
* @param c escape char indicating start of macros, or QChar::null for none
*/
explicit KMacroExpanderBase(QChar c = QLatin1Char('%'));
/**
* Destructor.
*/
virtual ~KMacroExpanderBase();
/**
* Perform safe macro expansion (substitution) on a string.
*
* @param str the string in which macros are expanded in-place
*/
void expandMacros(QString &str);
// TODO: This documentation is relevant for end-users. Where to put it?
/**
* Perform safe macro expansion (substitution) on a string for use
* in shell commands.
*
* <h3>*NIX notes</h3>
*
* Explicitly supported shell constructs:
* \ '' "" $'' $"" {} () $(()) ${} $() ``
*
* Implicitly supported shell constructs:
* (())
*
* Unsupported shell constructs that will cause problems:
* Shortened &quot;<tt>case $v in pat)</tt>&quot; syntax. Use
* &quot;<tt>case $v in (pat)</tt>&quot; instead.
*
* The rest of the shell (incl. bash) syntax is simply ignored,
* as it is not expected to cause problems.
*
* Note that bash contains a bug which makes macro expansion within
* double quoted substitutions (<tt>"${VAR:-%macro}"</tt>) inherently
* insecure.
*
* For security reasons, @em never put expandos in command line arguments
* that are shell commands by themselves -
* &quot;<tt>sh -c 'foo \%f'</tt>&quot; is taboo.
* &quot;<tt>file=\%f sh -c 'foo "$file"'</tt>&quot; is OK.
*
* <h3>Windows notes</h3>
*
* All quoting syntax supported by KShell is supported here as well.
* Additionally, command grouping via parentheses is recognized - note
* however, that the parser is much stricter about unquoted parentheses
* than cmd itself.
* The rest of the cmd syntax is simply ignored, as it is not expected
* to cause problems - do not use commands that embed other commands,
* though - &quot;<tt>for /f ...</tt>&quot; is taboo.
*
* @param str the string in which macros are expanded in-place
* @param pos the position inside the string at which parsing/substitution
* should start, and upon exit where processing stopped
* @return false if the string could not be parsed and therefore no safe
* substitution was possible. Note that macros will have been processed
* up to the point where the error occurred. An unmatched closing paren
* or brace outside any shell construct is @em not an error (unlike in
* the function below), but still prematurely terminates processing.
*/
bool expandMacrosShellQuote(QString &str, int &pos);
/**
* Same as above, but always starts at position 0, and unmatched closing
* parens and braces are treated as errors.
*/
bool expandMacrosShellQuote(QString &str);
/**
* Set the macro escape character.
* @param c escape char indicating start of macros, or QChar::null if none
*/
void setEscapeChar(QChar c);
/**
* Obtain the macro escape character.
* @return escape char indicating start of macros, or QChar::null if none
*/
QChar escapeChar() const;
protected:
/**
* This function is called for every single char within the string if
* the escape char is QChar::null. It should determine whether the
* string starting at @p pos within @p str is a valid macro and return
* the substitution value for it if so.
* @param str the input string
* @param pos the offset within @p str
* @param ret return value: the string to substitute for the macro
* @return If greater than zero, the number of chars at @p pos in @p str
* to substitute with @p ret (i.e., a valid macro was found). If less
* than zero, subtract this value from @p pos (to skip a macro, i.e.,
* substitute it with itself). If zero, no macro starts at @p pos.
*/
virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret);
/**
* This function is called every time the escape char is found if it is
* not QChar::null. It should determine whether the
* string starting at @p pos witin @p str is a valid macro and return
* the substitution value for it if so.
* @param str the input string
* @param pos the offset within @p str. Note that this is the position of
* the occurrence of the escape char
* @param ret return value: the string to substitute for the macro
* @return If greater than zero, the number of chars at @p pos in @p str
* to substitute with @p ret (i.e., a valid macro was found). If less
* than zero, subtract this value from @p pos (to skip a macro, i.e.,
* substitute it with itself). If zero, scanning continues as if no
* escape char was encountered at all.
*/
virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
private:
std::unique_ptr<KMacroExpanderBasePrivate> const d;
};
/**
* \class KWordMacroExpander kmacroexpander.h <KMacroExpanderBase>
*
* Abstract base class for simple word macro substitutors. Use this instead of
* the functions in the KMacroExpander namespace if speculatively pre-filling
* the substitution map would be too expensive.
*
* A typical application:
*
* \code
* class MyClass {
* ...
* private:
* QString m_str;
* ...
* friend class MyExpander;
* };
*
* class MyExpander : public KWordMacroExpander {
* public:
* MyExpander( MyClass *_that ) : KWordMacroExpander(), that( _that ) {}
* protected:
* virtual bool expandMacro( const QString &str, QStringList &ret );
* private:
* MyClass *that;
* };
*
* bool MyExpander::expandMacro( const QString &str, QStringList &ret )
* {
* if (str == "macro") {
* ret += complexOperation( that->m_str );
* return true;
* }
* return false;
* }
*
* ... MyClass::...(...)
* {
* QString str;
* ...
* MyExpander mx( this );
* mx.expandMacrosShellQuote( str );
* ...
* }
* \endcode
*
* Alternatively MyClass could inherit from KWordMacroExpander directly.
*
* @author Oswald Buddenhagen <ossi@kde.org>
*/
class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase
{
public:
/**
* Constructor.
* @param c escape char indicating start of macros, or QChar::null for none
*/
explicit KWordMacroExpander(QChar c = QLatin1Char('%'))
: KMacroExpanderBase(c)
{
}
protected:
/** \internal Not to be called or reimplemented. */
int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
/** \internal Not to be called or reimplemented. */
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
/**
* Return substitution list @p ret for string macro @p str.
* @param str the macro to expand
* @param ret return variable reference. It is guaranteed to be empty
* when expandMacro is entered.
* @return @c true iff @p str was a recognized macro name
*/
virtual bool expandMacro(const QString &str, QStringList &ret) = 0;
};
/**
* \class KCharMacroExpander kmacroexpander.h <KMacroExpanderBase>
*
* Abstract base class for single char macro substitutors. Use this instead of
* the functions in the KMacroExpander namespace if speculatively pre-filling
* the substitution map would be too expensive.
*
* See KWordMacroExpander for a sample application.
*
* @author Oswald Buddenhagen <ossi@kde.org>
*/
class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase
{
public:
/**
* Constructor.
* @param c escape char indicating start of macros, or QChar::null for none
*/
explicit KCharMacroExpander(QChar c = QLatin1Char('%'))
: KMacroExpanderBase(c)
{
}
protected:
/** \internal Not to be called or reimplemented. */
int expandPlainMacro(const QString &str, int pos, QStringList &ret) override;
/** \internal Not to be called or reimplemented. */
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
/**
* Return substitution list @p ret for single-character macro @p chr.
* @param chr the macro to expand
* @param ret return variable reference. It is guaranteed to be empty
* when expandMacro is entered.
* @return @c true iff @p chr was a recognized macro name
*/
virtual bool expandMacro(QChar chr, QStringList &ret) = 0;
};
/**
* A group of functions providing macro expansion (substitution) in strings,
* optionally with quoting appropriate for shell execution.
*/
namespace KMacroExpander
{
/**
* Perform safe macro expansion (substitution) on a string.
* The escape char must be quoted with itself to obtain its literal
* representation in the resulting string.
*
* @param str The string to expand
* @param map map with substitutions
* @param c escape char indicating start of macro, or QChar::null if none
* @return the string with all valid macros expanded
*
* \code
* // Code example
* QHash<QChar,QString> map;
* map.insert('u', "/tmp/myfile.txt");
* map.insert('n', "My File");
* QString s = "%% Title: %u:%n";
* s = KMacroExpander::expandMacros(s, map);
* // s is now "% Title: /tmp/myfile.txt:My File";
* \endcode
*/
KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%'));
/**
* Perform safe macro expansion (substitution) on a string for use
* in shell commands.
* The escape char must be quoted with itself to obtain its literal
* representation in the resulting string.
*
* @param str The string to expand
* @param map map with substitutions
* @param c escape char indicating start of macro, or QChar::null if none
* @return the string with all valid macros expanded, or a null string
* if a shell syntax error was detected in the command
*
* \code
* // Code example
* QHash<QChar,QString> map;
* map.insert('u', "/tmp/myfile.txt");
* map.insert('n', "My File");
* QString s = "kwrite --qwindowtitle %n %u";
* s = KMacroExpander::expandMacrosShellQuote(s, map);
* // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'";
* system(QFile::encodeName(s));
* \endcode
*/
KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QString> &map, QChar c = QLatin1Char('%'));
/**
* Perform safe macro expansion (substitution) on a string.
* The escape char must be quoted with itself to obtain its literal
* representation in the resulting string.
* Macro names can consist of chars in the range [A-Za-z0-9_];
* use braces to delimit macros from following words starting
* with these chars, or to use other chars for macro names.
*
* @param str The string to expand
* @param map map with substitutions
* @param c escape char indicating start of macro, or QChar::null if none
* @return the string with all valid macros expanded
*
* \code
* // Code example
* QHash<QString,QString> map;
* map.insert("url", "/tmp/myfile.txt");
* map.insert("name", "My File");
* QString s = "Title: %{url}-%name";
* s = KMacroExpander::expandMacros(s, map);
* // s is now "Title: /tmp/myfile.txt-My File";
* \endcode
*/
KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%'));
/**
* Perform safe macro expansion (substitution) on a string for use
* in shell commands. See KMacroExpanderBase::expandMacrosShellQuote()
* for the exact semantics.
* The escape char must be quoted with itself to obtain its literal
* representation in the resulting string.
* Macro names can consist of chars in the range [A-Za-z0-9_];
* use braces to delimit macros from following words starting
* with these chars, or to use other chars for macro names.
*
* @param str The string to expand
* @param map map with substitutions
* @param c escape char indicating start of macro, or QChar::null if none
* @return the string with all valid macros expanded, or a null string
* if a shell syntax error was detected in the command
*
* \code
* // Code example
* QHash<QString,QString> map;
* map.insert("url", "/tmp/myfile.txt");
* map.insert("name", "My File");
* QString s = "kwrite --qwindowtitle %name %{url}";
* s = KMacroExpander::expandMacrosShellQuote(s, map);
* // s is now "kwrite --qwindowtitle 'My File' '/tmp/myfile.txt'";
* system(QFile::encodeName(s));
* \endcode
*/
KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QString> &map, QChar c = QLatin1Char('%'));
/**
* Same as above, except that the macros expand to string lists that
* are simply join(" ")ed together.
*/
KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%'));
/**
* Same as above, except that the macros expand to string lists.
* If the macro appears inside a quoted string, the list is simply
* join(" ")ed together; otherwise every element expands to a separate
* quoted string.
*/
KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QChar, QStringList> &map, QChar c = QLatin1Char('%'));
KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash<QString, QStringList> &map, QChar c = QLatin1Char('%'));
}
#endif /* KMACROEXPANDER_H */
@@ -0,0 +1,24 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002-2003, 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMACROEXPANDER_P_H
#define KMACROEXPANDER_P_H
#include "kmacroexpander.h"
class KMacroExpanderBasePrivate
{
public:
KMacroExpanderBasePrivate(QChar c)
: escapechar(c)
{
}
QChar escapechar;
};
#endif
@@ -0,0 +1,252 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002-2003 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmacroexpander_p.h"
#include <QRegularExpression>
#include <QStack>
#include <QStringList>
namespace KMacroExpander
{
enum Quoting {
noquote,
singlequote,
doublequote,
dollarquote,
paren,
subst,
group,
math,
};
typedef struct {
Quoting current;
bool dquote;
} State;
typedef struct {
QString str;
int pos;
} Save;
}
using namespace KMacroExpander;
// #pragma message("TODO: Import these methods into Qt")
inline static bool isSpecial(QChar cUnicode)
{
static const uchar iqm[] = {0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78}; // 0-32 \'"$`<>|;&(){}*?#!~[]
uint c = cUnicode.unicode();
return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}
static QString quoteArg(const QString &arg)
{
if (!arg.length()) {
return QStringLiteral("''");
}
for (int i = 0; i < arg.length(); i++) {
if (isSpecial(arg.unicode()[i])) {
QChar q(QLatin1Char('\''));
return q + QString(arg).replace(q, QLatin1String("'\\''")) + q;
}
}
return arg;
}
static QString joinArgs(const QStringList &args)
{
QString ret;
for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
if (!ret.isEmpty()) {
ret.append(QLatin1Char(' '));
}
ret.append(quoteArg(*it));
}
return ret;
}
bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos)
{
int len;
int pos2;
ushort ec = d->escapechar.unicode();
State state = {noquote, false};
QStack<State> sstack;
QStack<Save> ostack;
QStringList rst;
QString rsts;
while (pos < str.length()) {
ushort cc = str.unicode()[pos].unicode();
if (ec != 0) {
if (cc != ec) {
goto nohit;
}
if (!(len = expandEscapedMacro(str, pos, rst))) {
goto nohit;
}
} else {
if (!(len = expandPlainMacro(str, pos, rst))) {
goto nohit;
}
}
if (len < 0) {
pos -= len;
continue;
}
if (state.dquote) {
rsts = rst.join(QLatin1Char(' '));
const static QRegularExpression regex(QStringLiteral("([$`\"\\\\])"));
rsts.replace(regex, QStringLiteral("\\\\1"));
} else if (state.current == dollarquote) {
rsts = rst.join(QLatin1Char(' '));
const static QRegularExpression regex(QStringLiteral("(['\\\\])"));
rsts.replace(regex, QStringLiteral("\\\\1"));
} else if (state.current == singlequote) {
rsts = rst.join(QLatin1Char(' '));
rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
} else {
if (rst.isEmpty()) {
str.remove(pos, len);
continue;
} else {
rsts = joinArgs(rst);
}
}
rst.clear();
str.replace(pos, len, rsts);
pos += rsts.length();
continue;
nohit:
if (state.current == singlequote) {
if (cc == '\'') {
state = sstack.pop();
}
} else if (cc == '\\') {
// always swallow the char -> prevent anomalies due to expansion
pos += 2;
continue;
} else if (state.current == dollarquote) {
if (cc == '\'') {
state = sstack.pop();
}
} else if (cc == '$') {
cc = str.unicode()[++pos].unicode();
if (cc == '(') {
sstack.push(state);
if (str.unicode()[pos + 1].unicode() == '(') {
Save sav = {str, pos + 2};
ostack.push(sav);
state.current = math;
pos += 2;
continue;
} else {
state.current = paren;
state.dquote = false;
}
} else if (cc == '{') {
sstack.push(state);
state.current = subst;
} else if (!state.dquote) {
if (cc == '\'') {
sstack.push(state);
state.current = dollarquote;
} else if (cc == '"') {
sstack.push(state);
state.current = doublequote;
state.dquote = true;
}
}
// always swallow the char -> prevent anomalies due to expansion
} else if (cc == '`') {
str.replace(pos, 1, QStringLiteral("$( ")); // add space -> avoid creating $((
pos2 = pos += 3;
for (;;) {
if (pos2 >= str.length()) {
pos = pos2;
return false;
}
cc = str.unicode()[pos2].unicode();
if (cc == '`') {
break;
}
if (cc == '\\') {
cc = str.unicode()[++pos2].unicode();
if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) {
str.remove(pos2 - 1, 1);
continue;
}
}
pos2++;
}
str[pos2] = QLatin1Char(')');
sstack.push(state);
state.current = paren;
state.dquote = false;
continue;
} else if (state.current == doublequote) {
if (cc == '"') {
state = sstack.pop();
}
} else if (cc == '\'') {
if (!state.dquote) {
sstack.push(state);
state.current = singlequote;
}
} else if (cc == '"') {
if (!state.dquote) {
sstack.push(state);
state.current = doublequote;
state.dquote = true;
}
} else if (state.current == subst) {
if (cc == '}') {
state = sstack.pop();
}
} else if (cc == ')') {
if (state.current == math) {
if (str.unicode()[pos + 1].unicode() == ')') {
state = sstack.pop();
pos += 2;
} else {
// false hit: the $(( was a $( ( in fact
// ash does not care, but bash does
pos = ostack.top().pos;
str = ostack.top().str;
ostack.pop();
state.current = paren;
state.dquote = false;
sstack.push(state);
}
continue;
} else if (state.current == paren) {
state = sstack.pop();
} else {
break;
}
} else if (cc == '}') {
if (state.current == KMacroExpander::group) {
state = sstack.pop();
} else {
break;
}
} else if (cc == '(') {
sstack.push(state);
state.current = paren;
} else if (cc == '{') {
sstack.push(state);
state.current = KMacroExpander::group;
}
pos++;
}
return sstack.empty();
}
@@ -0,0 +1,111 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmacroexpander_p.h"
#include "kshell_p.h"
#include "kshell.h"
#include <QString>
#include <QStringList>
bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos)
{
int len;
int pos2;
ushort uc;
ushort ec = d->escapechar.unicode();
bool shellQuote = false; // shell is in quoted state
bool crtQuote = false; // c runtime is in quoted state
bool escaped = false; // previous char was a circumflex
int bslashes = 0; // previous chars were backslashes
int parens = 0; // parentheses nesting level
QStringList rst;
QString rsts;
while (pos < str.length()) {
ushort cc = str.unicode()[pos].unicode();
if (escaped) { // prevent anomalies due to expansion
goto notcf;
}
if (ec != 0) {
if (cc != ec) {
goto nohit;
}
if (!(len = expandEscapedMacro(str, pos, rst))) {
goto nohit;
}
} else {
if (!(len = expandPlainMacro(str, pos, rst))) {
goto nohit;
}
}
if (len < 0) {
pos -= len;
continue;
}
if (shellQuote != crtQuote) { // Silly, isn't it? Ahoy to Redmond.
return false;
}
if (shellQuote) {
rsts = KShell::quoteArgInternal(rst.join(QLatin1Char(' ')), true);
} else {
if (rst.isEmpty()) {
str.remove(pos, len);
continue;
}
rsts = KShell::joinArgs(rst);
}
pos2 = 0;
while (pos2 < rsts.length() && ((uc = rsts.unicode()[pos2].unicode()) == '\\' || uc == '^')) {
pos2++;
}
if (pos2 < rsts.length() && rsts.unicode()[pos2].unicode() == '"') {
QString bsl;
bsl.reserve(bslashes);
for (; bslashes; bslashes--) {
bsl.append(QLatin1String("\\"));
}
rsts.prepend(bsl);
}
bslashes = 0;
rst.clear();
str.replace(pos, len, rsts);
pos += rsts.length();
continue;
nohit:
if (!escaped && !shellQuote && cc == '^') {
escaped = true;
} else {
notcf:
if (cc == '\\') {
bslashes++;
} else {
if (cc == '"') {
if (!escaped) {
shellQuote = !shellQuote;
}
if (!(bslashes & 1)) {
crtQuote = !crtQuote;
}
} else if (!shellQuote) {
if (cc == '(') {
parens++;
} else if (cc == ')')
if (--parens < 0) {
break;
}
}
bslashes = 0;
}
escaped = false;
}
pos++;
}
return true;
}
@@ -0,0 +1,253 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Ian Zepp <icszepp@islc.net>
SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kstringhandler.h"
#include <stdlib.h> // random()
#include <QList>
#include <QRegularExpression>
//
// Capitalization routines
//
QString KStringHandler::capwords(const QString &text)
{
if (text.isEmpty()) {
return text;
}
const QString strippedText = text.trimmed();
const QString space = QString(QLatin1Char(' '));
const QStringList words = capwords(strippedText.split(space));
QString result = text;
result.replace(strippedText, words.join(space));
return result;
}
QStringList KStringHandler::capwords(const QStringList &list)
{
QStringList tmp = list;
for (auto &str : tmp) {
str[0] = str.at(0).toUpper();
}
return tmp;
}
QString KStringHandler::lsqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen) {
const int part = maxlen - 3;
return QLatin1String("...") + QStringView(str).right(part);
} else {
return str;
}
}
QString KStringHandler::csqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen && maxlen > 3) {
const int part = (maxlen - 3) / 2;
const QStringView strView{str};
return strView.left(part) + QLatin1String("...") + strView.right(part);
} else {
return str;
}
}
QString KStringHandler::rsqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen) {
const int part = maxlen - 3;
return QStringView(str).left(part) + QLatin1String("...");
} else {
return str;
}
}
QStringList KStringHandler::perlSplit(const QStringView sep, const QStringView str, int max)
{
const bool ignoreMax = max == 0;
const int sepLength = sep.size();
QStringList list;
int searchStart = 0;
int sepIndex = str.indexOf(sep, searchStart);
while (sepIndex != -1 && (ignoreMax || list.count() < max - 1)) {
const auto chunk = str.mid(searchStart, sepIndex - searchStart);
if (!chunk.isEmpty()) {
list.append(chunk.toString());
}
searchStart = sepIndex + sepLength;
sepIndex = str.indexOf(sep, searchStart);
}
const auto lastChunk = str.mid(searchStart, str.length() - searchStart);
if (!lastChunk.isEmpty()) {
list.append(lastChunk.toString());
}
return list;
}
QStringList KStringHandler::perlSplit(const QString &sep, const QString &s, int max)
{
return perlSplit(QStringView(sep), QStringView(s), max);
}
QStringList KStringHandler::perlSplit(const QChar &sep, const QString &str, int max)
{
return perlSplit(QStringView(&sep, 1), QStringView(str), max);
}
QStringList KStringHandler::perlSplit(const QRegularExpression &sep, const QString &str, int max)
{
// nothing to split
if (str.isEmpty()) {
return QStringList();
}
const bool ignoreMax = max == 0;
QStringList list;
int start = 0;
const QStringView strView(str);
QRegularExpression separator(sep);
separator.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
QRegularExpressionMatchIterator iter = separator.globalMatchView(strView);
QRegularExpressionMatch match;
while (iter.hasNext() && (ignoreMax || list.count() < max - 1)) {
match = iter.next();
const QStringView chunk = strView.mid(start, match.capturedStart() - start);
if (!chunk.isEmpty()) {
list.append(chunk.toString());
}
start = match.capturedEnd();
}
// catch the remainder
const QStringView lastChunk = strView.mid(start, strView.size() - start);
if (!lastChunk.isEmpty()) {
list.append(lastChunk.toString());
}
return list;
}
QString KStringHandler::tagUrls(const QString &text)
{
QString richText(text);
static const QRegularExpression urlEx(QStringLiteral(R"((www\.(?!\.)|(fish|ftp|http|https)://[\d\w./,:_~?=&;#@\-+%$()]+))"),
QRegularExpression::UseUnicodePropertiesOption);
// The reference \1 is going to be replaced by the matched url
richText.replace(urlEx, QStringLiteral("<a href=\"\\1\">\\1</a>"));
return richText;
}
QString KStringHandler::obscure(const QString &str)
{
QString result;
for (const QChar ch : str) {
// yes, no typo. can't encode ' ' or '!' because
// they're the unicode BOM. stupid scrambling. stupid.
const ushort uc = ch.unicode();
result += (uc <= 0x21) ? ch : QChar(0x1001F - uc);
}
return result;
}
static inline bool containsSpaces(const QString &text)
{
for (int i = 0; i < text.length(); i++) {
const QChar c = text[i];
if (c.isSpace()) {
return true;
}
}
return false;
}
QString KStringHandler::preProcessWrap(const QString &text)
{
const QChar zwsp(0x200b);
QString result;
result.reserve(text.length());
const bool containsSpaces = ::containsSpaces(text);
for (int i = 0; i < text.length(); i++) {
const QChar c = text[i];
const bool openingParens = (c == QLatin1Char('(') || c == QLatin1Char('{') || c == QLatin1Char('['));
const bool singleQuote = (c == QLatin1Char('\''));
const bool closingParens = (c == QLatin1Char(')') || c == QLatin1Char('}') || c == QLatin1Char(']'));
const bool breakAfter = (closingParens || c.isPunct() || c.isSymbol());
const bool isLastChar = i == (text.length() - 1);
const bool isLower = c.isLower();
const bool nextIsUpper = !isLastChar && text[i + 1].isUpper(); // false by default
const bool nextIsSpace = isLastChar || text[i + 1].isSpace(); // true by default
const bool prevIsSpace = (i == 0 || text[i - 1].isSpace() || result[result.length() - 1] == zwsp);
// Provide a breaking opportunity before opening parenthesis
if (openingParens && !prevIsSpace) {
result += zwsp;
}
// Provide a word joiner before the single quote
if (singleQuote && !prevIsSpace) {
result += QChar(0x2060);
}
result += c;
// Provide a breaking opportunity between camelCase and PascalCase sub-words;
// but if source string contains whitespaces, then it should be sufficiently wrappable on its own
const bool isCamelCase = !containsSpaces && isLower && nextIsUpper;
if (isCamelCase || (breakAfter && !openingParens && !nextIsSpace && !singleQuote)) {
result += zwsp;
}
}
return result;
}
int KStringHandler::logicalLength(const QString &text)
{
int length = 0;
const auto chrs = text.toUcs4();
for (const auto chr : chrs) {
const auto script = QChar::script(chr);
/* clang-format off */
if (script == QChar::Script_Han
|| script == QChar::Script_Hangul
|| script == QChar::Script_Hiragana
|| script == QChar::Script_Katakana
|| script == QChar::Script_Yi
|| QChar::isHighSurrogate(chr)) { /* clang-format on */
length += 2;
} else {
length += 1;
}
}
return length;
}
@@ -0,0 +1,216 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Ian Zepp <icszepp@islc.net>
SPDX-FileCopyrightText: 2000 Rik Hemsley (rikkus) <rik@kde.org>
SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSTRINGHANDLER_H
#define KSTRINGHANDLER_H
#include <kcoreaddons_export.h>
#include <QStringList>
#include <qnamespace.h>
class QChar;
class QRegularExpression;
class QString;
/**
* This namespace contains utility functions for handling strings.
*
* The functions here are intended to provide an easy way to
* cut/slice/splice words inside sentences in whatever order desired.
* While the main focus of KStringHandler is words (ie characters
* separated by spaces/tabs), the two core functions here (split()
* and join()) will allow you to use any character as a separator
* This will make it easy to redefine what a 'word' means in the
* future if needed.
*
* The function names and calling styles are based on python and mIRC's
* scripting support.
*
* The ranges are a fairly powerful way of getting/stripping words from
* a string. These ranges function, for the large part, as they would in
* python. See the word(const QString&, int) and remword(const QString&, int)
* functions for more detail.
*
* The methods here are completely stateless. All strings are cut
* on the fly and returned as new qstrings/qstringlists.
*
* @short Namespace for manipulating words and sentences in strings
* @author Ian Zepp <icszepp@islc.net>
* @see KShell
*/
namespace KStringHandler
{
/** Capitalizes each word in the string
* "hello there" becomes "Hello There" (string)
* @param text the text to capitalize
* @return the resulting string
*/
KCOREADDONS_EXPORT QString capwords(const QString &text);
/** Capitalizes each word in the list
* [hello, there] becomes [Hello, There] (list)
* @param list the list to capitalize
* @return the resulting list
*/
KCOREADDONS_EXPORT QStringList capwords(const QStringList &list);
/** Substitute characters at the beginning of a string by "...".
* @param str is the string to modify
* @param maxlen is the maximum length the modified string will have
* If the original string is shorter than "maxlen", it is returned verbatim
* @return the modified string
*/
KCOREADDONS_EXPORT QString lsqueeze(const QString &str, int maxlen = 40);
/** Substitute characters at the middle of a string by "...".
* @param str is the string to modify
* @param maxlen is the maximum length the modified string will have
* If the original string is shorter than "maxlen", it is returned verbatim
* @return the modified string
*/
KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen = 40);
/** Substitute characters at the end of a string by "...".
* @param str is the string to modify
* @param maxlen is the maximum length the modified string will have
* If the original string is shorter than "maxlen", it is returned verbatim
* @return the modified string
*/
KCOREADDONS_EXPORT QString rsqueeze(const QString &str, int maxlen = 40);
/**
* Split a string into a QStringList in a similar fashion to the static
* QStringList function in Qt, except you can specify a maximum number
* of tokens. If max is specified (!= 0) then only that number of tokens
* will be extracted. The final token will be the remainder of the string.
*
* Example:
* @code
* perlSplit("__", "some__string__for__you__here", 4)
* QStringList contains: "some", "string", "for", "you__here"
* @endcode
*
* @param sep is the string to use to delimit @p str
* @param str the string to split
* @param max the maximum number of extractions to perform, or 0
* @return A QStringList containing tokens extracted from @p str
*
* @since 5.87
*/
KCOREADDONS_EXPORT QStringList perlSplit(const QStringView sep, const QStringView str, int max);
/**
* Split a QString into a QStringList in a similar fashion to the static
* QStringList function in Qt, except you can specify a maximum number
* of tokens. If max is specified (!= 0) then only that number of tokens
* will be extracted. The final token will be the remainder of the string.
*
* Example:
* \code
* perlSplit("__", "some__string__for__you__here", 4)
* QStringList contains: "some", "string", "for", "you__here"
* \endcode
*
* @param sep is the string to use to delimit s.
* @param s is the input string
* @param max is the maximum number of extractions to perform, or 0.
* @return A QStringList containing tokens extracted from s.
*/
KCOREADDONS_EXPORT QStringList perlSplit(const QString &sep, const QString &s, int max = 0);
/**
* Split a QString into a QStringList in a similar fashion to the static
* QStringList function in Qt, except you can specify a maximum number
* of tokens. If max is specified (!= 0) then only that number of tokens
* will be extracted. The final token will be the remainder of the string.
*
* Example:
* \code
* perlSplit(' ', "kparts reaches the parts other parts can't", 3)
* QStringList contains: "kparts", "reaches", "the parts other parts can't"
* \endcode
*
* @param sep is the character to use to delimit s.
* @param s is the input string
* @param max is the maximum number of extractions to perform, or 0.
* @return A QStringList containing tokens extracted from s.
*/
KCOREADDONS_EXPORT QStringList perlSplit(const QChar &sep, const QString &s, int max = 0);
/**
* Split a QString into a QStringList in a similar fashion to the static
* QStringList function in Qt, except you can specify a maximum number
* of tokens. If max is specified (!= 0) then only that number of tokens
* will be extracted. The final token will be the remainder of the string.
*
* Example:
* \code
* perlSplit(QRegularExpression("[! ]"), "Split me up ! I'm bored ! OK ?", 3)
* QStringList contains: "Split", "me", "up ! I'm bored ! OK ?"
* \endcode
*
* @param sep is the regular expression to use to delimit s.
* @param s is the input string
* @param max is the maximum number of extractions to perform, or 0.
* @return A QStringList containing tokens extracted from s.
*
* @since 5.67
*/
KCOREADDONS_EXPORT QStringList perlSplit(const QRegularExpression &sep, const QString &s, int max = 0);
/**
* This method auto-detects URLs in strings, and adds HTML markup to them
* so that richtext or HTML-enabled widgets will display the URL correctly.
* @param text the string which may contain URLs
* @return the resulting text
*/
KCOREADDONS_EXPORT QString tagUrls(const QString &text);
/**
Obscure string by using a simple symmetric encryption. Applying the
function to a string obscured by this function will result in the original
string.
The function can be used to obscure passwords stored to configuration
files. Note that this won't give you any more security than preventing
that the password is directly copied and pasted.
@param str string to be obscured
@return obscured string
*/
KCOREADDONS_EXPORT QString obscure(const QString &str);
/**
Preprocesses the given string in order to provide additional line breaking
opportunities for QTextLayout.
This is done by inserting ZWSP (Zero-width space) characters in the string
at points that wouldn't normally be considered word boundaries by QTextLayout,
but where wrapping the text will produce good results.
Examples of such points includes after punctuation signs, underscores and
dashes, that aren't followed by spaces.
@since 4.4
*/
KCOREADDONS_EXPORT QString preProcessWrap(const QString &text);
/**
Returns the length that reflects the density of information in the text. In
general the character from CJK languages are assigned with weight 2, while
other Latin characters are assigned with 1.
@since 5.41
*/
KCOREADDONS_EXPORT int logicalLength(const QString &text);
}
#endif
@@ -0,0 +1,567 @@
/*
SPDX-FileCopyrightText: 2002 Dave Corrie <kde@davecorrie.com>
SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktexttohtml.h"
#include "kemoticonsparser_p.h"
#include "ktexttohtml_p.h"
#include <QCoreApplication>
#include <QFile>
#include <QRegularExpression>
#include <QStringList>
#include <limits.h>
KTextToHTMLHelper::KTextToHTMLHelper(const QString &plainText, int pos, int maxUrlLen, int maxAddressLen)
: mText(plainText)
, mMaxUrlLen(maxUrlLen)
, mMaxAddressLen(maxAddressLen)
, mPos(pos)
{
}
QString KTextToHTMLHelper::getEmailAddress()
{
QString address;
if (mPos < mText.length() && mText.at(mPos) == QLatin1Char('@')) {
// the following characters are allowed in a dot-atom (RFC 2822):
// a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~");
// determine the local part of the email address
int start = mPos - 1;
while (start >= 0 && mText.at(start).unicode() < 128
&& (mText.at(start).isLetterOrNumber() //
|| mText.at(start) == QLatin1Char('@') // allow @ to find invalid email addresses
|| allowedSpecialChars.indexOf(mText.at(start)) != -1)) {
if (mText.at(start) == QLatin1Char('@')) {
return QString(); // local part contains '@' -> no email address
}
--start;
}
++start;
// we assume that an email address starts with a letter or a digit
while ((start < mPos) && !mText.at(start).isLetterOrNumber()) {
++start;
}
if (start == mPos) {
return QString(); // local part is empty -> no email address
}
// determine the domain part of the email address
int dotPos = INT_MAX;
int end = mPos + 1;
while (end < mText.length()
&& (mText.at(end).isLetterOrNumber() //
|| mText.at(end) == QLatin1Char('@') // allow @ to find invalid email addresses
|| mText.at(end) == QLatin1Char('.') //
|| mText.at(end) == QLatin1Char('-'))) {
if (mText.at(end) == QLatin1Char('@')) {
return QString(); // domain part contains '@' -> no email address
}
if (mText.at(end) == QLatin1Char('.')) {
dotPos = qMin(dotPos, end); // remember index of first dot in domain
}
++end;
}
// we assume that an email address ends with a letter or a digit
while ((end > mPos) && !mText.at(end - 1).isLetterOrNumber()) {
--end;
}
if (end == mPos) {
return QString(); // domain part is empty -> no email address
}
if (dotPos >= end) {
return QString(); // domain part doesn't contain a dot
}
if (end - start > mMaxAddressLen) {
return QString(); // too long -> most likely no email address
}
address = mText.mid(start, end - start);
mPos = end - 1;
}
return address;
}
QString KTextToHTMLHelper::getPhoneNumber()
{
if (!mText.at(mPos).isDigit() && mText.at(mPos) != QLatin1Char('+')) {
return {};
}
const QString allowedBeginSeparators = QStringLiteral(" \r\t\n:");
if (mPos > 0 && !allowedBeginSeparators.contains(mText.at(mPos - 1))) {
return {};
}
// this isn't 100% accurate, we filter stuff below that is too hard to capture with a regexp
static const QRegularExpression telPattern(QStringLiteral(R"([+0](( |( ?[/-] ?)?)\(?\d+\)?+){6,30})"));
const auto match = telPattern.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchorAtOffsetMatchOption);
if (match.hasMatch()) {
QStringView matchedText = match.capturedView();
// check for maximum number of digits (15), see https://en.wikipedia.org/wiki/Telephone_numbering_plan
const int digitsCount = std::count_if(matchedText.cbegin(), matchedText.cend(), [](const QChar c) {
return c.isDigit();
});
if (digitsCount > 15) {
return {};
}
// only one / is allowed, otherwise we trigger on dates
if (matchedText.count(QLatin1Char('/')) > 1) {
return {};
}
// parenthesis need to be balanced, and must not be nested
int openIdx = -1;
for (int i = 0, size = matchedText.size(); i < size; ++i) {
const QChar ch = matchedText.at(i);
if ((ch == QLatin1Char('(') && openIdx >= 0) || (ch == QLatin1Char(')') && openIdx < 0)) {
return {};
}
if (ch == QLatin1Char('(')) {
openIdx = i;
} else if (ch == QLatin1Char(')')) {
openIdx = -1;
}
}
if (openIdx > 0) {
matchedText.truncate(openIdx - 1);
matchedText = matchedText.trimmed();
}
// check if there's a plausible separator at the end
const int matchedTextLength = matchedText.size();
const int endIdx = mPos + matchedTextLength;
if (endIdx < mText.size() && !QStringView(u" \r\t\n,.").contains(mText.at(endIdx))) {
return {};
}
mPos += matchedTextLength - 1;
return matchedText.toString();
}
return {};
}
static QString normalizePhoneNumber(const QString &str)
{
QString res;
res.reserve(str.size());
for (const auto c : str) {
if (c.isDigit() || c == QLatin1Char('+')) {
res.push_back(c);
}
}
return res;
}
// The following characters are allowed in a dot-atom (RFC 2822):
// a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
static const char s_allowedSpecialChars[] = ".!#$%&'*+-/=?^_`{|}~";
bool KTextToHTMLHelper::atUrl() const
{
// The character directly before the URL must not be a letter, a number or
// any other character allowed in a dot-atom (RFC 2822).
if (mPos > 0) {
const auto chBefore = mText.at(mPos - 1);
if (chBefore.isLetterOrNumber() || QLatin1String(s_allowedSpecialChars).contains(chBefore)) {
return false;
}
}
const auto segment = QStringView(mText).mid(mPos);
/* clang-format off */
return segment.startsWith(QLatin1String("http://"))
|| segment.startsWith(QLatin1String("https://"))
|| segment.startsWith(QLatin1String("vnc://"))
|| segment.startsWith(QLatin1String("fish://"))
|| segment.startsWith(QLatin1String("ftp://"))
|| segment.startsWith(QLatin1String("ftps://"))
|| segment.startsWith(QLatin1String("sftp://"))
|| segment.startsWith(QLatin1String("smb://"))
|| segment.startsWith(QLatin1String("irc://"))
|| segment.startsWith(QLatin1String("ircs://"))
|| segment.startsWith(QLatin1String("mailto:"))
|| segment.startsWith(QLatin1String("www."))
|| segment.startsWith(QLatin1String("ftp."))
|| segment.startsWith(QLatin1String("file://"))
|| segment.startsWith(QLatin1String("news:"))
|| segment.startsWith(QLatin1String("tel:"))
|| segment.startsWith(QLatin1String("xmpp:"));
/* clang-format on */
}
bool KTextToHTMLHelper::isEmptyUrl(const QString &url) const
{
/* clang-format off */
return url.isEmpty()
|| url == QLatin1String("http://")
|| url == QLatin1String("https://")
|| url == QLatin1String("fish://")
|| url == QLatin1String("ftp://")
|| url == QLatin1String("ftps://")
|| url == QLatin1String("sftp://")
|| url == QLatin1String("smb://")
|| url == QLatin1String("vnc://")
|| url == QLatin1String("irc://")
|| url == QLatin1String("ircs://")
|| url == QLatin1String("mailto")
|| url == QLatin1String("mailto:")
|| url == QLatin1String("www")
|| url == QLatin1String("ftp")
|| url == QLatin1String("news:")
|| url == QLatin1String("news://")
|| url == QLatin1String("tel")
|| url == QLatin1String("tel:")
|| url == QLatin1String("xmpp:");
/* clang-format on */
}
QString KTextToHTMLHelper::getUrl(bool *badurl)
{
QString url;
if (atUrl()) {
// NOTE: see http://tools.ietf.org/html/rfc3986#appendix-A and especially appendix-C
// Appendix-C mainly says, that when extracting URLs from plain text, line breaks shall
// be allowed and should be ignored when the URI is extracted.
// This implementation follows this recommendation and
// allows the URL to be enclosed within different kind of brackets/quotes
// If an URL is enclosed, whitespace characters are allowed and removed, otherwise
// the URL ends with the first whitespace
// Also, if the URL is enclosed in brackets, the URL itself is not allowed
// to contain the closing bracket, as this would be detected as the end of the URL
QChar beforeUrl;
QChar afterUrl;
// detect if the url has been surrounded by brackets or quotes
if (mPos > 0) {
beforeUrl = mText.at(mPos - 1);
/*if ( beforeUrl == '(' ) {
afterUrl = ')';
} else */
if (beforeUrl == QLatin1Char('[')) {
afterUrl = QLatin1Char(']');
} else if (beforeUrl == QLatin1Char('<')) {
afterUrl = QLatin1Char('>');
} else if (beforeUrl == QLatin1Char('>')) { // for e.g. <link>http://.....</link>
afterUrl = QLatin1Char('<');
} else if (beforeUrl == QLatin1Char('"')) {
afterUrl = QLatin1Char('"');
}
}
url.reserve(mMaxUrlLen); // avoid allocs
int start = mPos;
bool previousCharIsSpace = false;
bool previousCharIsADoubleQuote = false;
bool previousIsAnAnchor = false;
/* clang-format off */
while (mPos < mText.length() //
&& (mText.at(mPos).isPrint() || mText.at(mPos).isSpace())
&& ((afterUrl.isNull() && !mText.at(mPos).isSpace())
|| (!afterUrl.isNull() && mText.at(mPos) != afterUrl))) {
if (!previousCharIsSpace
&& mText.at(mPos) == QLatin1Char('<')
&& (mPos + 1) < mText.length()) { /* clang-format on */
// Fix Bug #346132: allow "http://www.foo.bar<http://foo.bar/>"
// < inside a URL is not allowed, however there is a test which
// checks that "http://some<Host>/path" should be allowed
// Therefore: check if what follows is another URL and if so, stop here
mPos++;
if (atUrl()) {
mPos--;
break;
}
mPos--;
}
if (!previousCharIsSpace && (mText.at(mPos) == QLatin1Char(' ')) && ((mPos + 1) < mText.length())) {
// Fix kmail bug: allow "http://www.foo.bar http://foo.bar/"
// Therefore: check if what follows is another URL and if so, stop here
mPos++;
if (atUrl()) {
mPos--;
break;
}
mPos--;
}
if (mText.at(mPos).isSpace()) {
previousCharIsSpace = true;
} else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char('[')) {
break;
} else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char(']')) {
break;
} else { // skip whitespace
if (previousCharIsSpace && mText.at(mPos) == QLatin1Char('<')) {
url.append(QLatin1Char(' '));
break;
}
previousCharIsSpace = false;
if (mText.at(mPos) == QLatin1Char('>') && previousCharIsADoubleQuote) {
// it's an invalid url
if (badurl) {
*badurl = true;
}
return QString();
}
if (mText.at(mPos) == QLatin1Char('"')) {
previousCharIsADoubleQuote = true;
} else {
previousCharIsADoubleQuote = false;
}
if (mText.at(mPos) == QLatin1Char('#')) {
previousIsAnAnchor = true;
}
url.append(mText.at(mPos));
if (url.length() > mMaxUrlLen) {
break;
}
}
++mPos;
}
if (isEmptyUrl(url) || (url.length() > mMaxUrlLen)) {
mPos = start;
url.clear();
return url;
} else {
--mPos;
}
}
// HACK: This is actually against the RFC. However, most people don't properly escape the URL in
// their text with "" or <>. That leads to people writing an url, followed immediately by
// a dot to finish the sentence. That would lead the parser to include the dot in the url,
// even though that is not wanted. So work around that here.
// Most real-life URLs hopefully don't end with dots or commas.
QString wordBoundaries = QStringLiteral(".,:!?>");
bool hasOpenParenthese = url.contains(QLatin1Char('('));
if (!hasOpenParenthese) {
wordBoundaries += QLatin1Char(')');
}
if (url.length() > 1) {
do {
const QChar charact{url.at(url.length() - 1)};
if (wordBoundaries.contains(charact)) {
url.chop(1);
--mPos;
} else if (hasOpenParenthese && (charact == QLatin1Char(')'))) {
if (url.length() > 2) {
if (url.at(url.length() - 2) == QLatin1Char(')')) {
url.chop(1);
--mPos;
hasOpenParenthese = false;
} else {
break;
}
} else {
break;
}
} else {
break;
}
} while (url.length() > 1);
}
return url;
}
QString KTextToHTMLHelper::highlightedText()
{
// formating symbols must be prepended with a whitespace
if ((mPos > 0) && !mText.at(mPos - 1).isSpace()) {
return QString();
}
const QChar ch = mText.at(mPos);
if (ch != QLatin1Char('/') && ch != QLatin1Char('*') && ch != QLatin1Char('_') && ch != QLatin1Char('-')) {
return QString();
}
const QRegularExpression re(QStringLiteral("\\%1([^\\s|^\\%1].*[^\\s|^\\%1])\\%1").arg(ch), QRegularExpression::InvertedGreedinessOption);
const auto match =
re.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchorAtOffsetMatchOption); // clazy:exclude=use-static-qregularexpression
if (match.hasMatch()) {
if (match.capturedStart() == mPos) {
int length = match.capturedLength();
// there must be a whitespace after the closing formating symbol
if (mPos + length < mText.length() && !mText.at(mPos + length).isSpace()) {
return QString();
}
mPos += length - 1;
switch (ch.toLatin1()) {
case '*':
return QLatin1String("<b>*") + match.capturedView(1) + QLatin1String("*</b>");
case '_':
return QLatin1String("<u>_") + match.capturedView(1) + QLatin1String("_</u>");
case '/':
return QLatin1String("<i>/") + match.capturedView(1) + QLatin1String("/</i>");
case '-':
return QLatin1String("<s>-") + match.capturedView(1) + QLatin1String("-</s>");
}
}
}
return QString();
}
QString KTextToHTML::convertToHtml(const QString &plainText, const KTextToHTML::Options &flags, int maxUrlLen, int maxAddressLen)
{
KTextToHTMLHelper helper(plainText, 0, maxUrlLen, maxAddressLen);
QString str;
QString result(static_cast<QChar *>(nullptr), helper.mText.length() * 2);
QChar ch;
int x;
bool startOfLine = true;
for (helper.mPos = 0, x = 0; helper.mPos < helper.mText.length(); ++helper.mPos, ++x) {
ch = helper.mText.at(helper.mPos);
if (flags & PreserveSpaces) {
if (ch == QLatin1Char(' ')) {
if (helper.mPos + 1 < helper.mText.length()) {
if (helper.mText.at(helper.mPos + 1) != QLatin1Char(' ')) {
// A single space, make it breaking if not at the start or end of the line
const bool endOfLine = helper.mText.at(helper.mPos + 1) == QLatin1Char('\n');
if (!startOfLine && !endOfLine) {
result += QLatin1Char(' ');
} else {
result += QLatin1String("&nbsp;");
}
} else {
// Whitespace of more than one space, make it all non-breaking
while (helper.mPos < helper.mText.length() && helper.mText.at(helper.mPos) == QLatin1Char(' ')) {
result += QLatin1String("&nbsp;");
++helper.mPos;
++x;
}
// We incremented once to often, undo that
--helper.mPos;
--x;
}
} else {
// Last space in the text, it is non-breaking
result += QLatin1String("&nbsp;");
}
if (startOfLine) {
startOfLine = false;
}
continue;
} else if (ch == QLatin1Char('\t')) {
do {
result += QLatin1String("&nbsp;");
++x;
} while ((x & 7) != 0);
--x;
startOfLine = false;
continue;
}
}
if (ch == QLatin1Char('\n')) {
result += QLatin1String("<br />\n"); // Keep the \n, so apps can figure out the quoting levels correctly.
startOfLine = true;
x = -1;
continue;
}
startOfLine = false;
if (ch == QLatin1Char('&')) {
result += QLatin1String("&amp;");
} else if (ch == QLatin1Char('"')) {
result += QLatin1String("&quot;");
} else if (ch == QLatin1Char('<')) {
result += QLatin1String("&lt;");
} else if (ch == QLatin1Char('>')) {
result += QLatin1String("&gt;");
} else {
const int start = helper.mPos;
if (!(flags & IgnoreUrls)) {
bool badUrl = false;
str = helper.getUrl(&badUrl);
if (badUrl) {
QString resultBadUrl;
for (const QChar chBadUrl : std::as_const(helper.mText)) {
if (chBadUrl == QLatin1Char('&')) {
resultBadUrl += QLatin1String("&amp;");
} else if (chBadUrl == QLatin1Char('"')) {
resultBadUrl += QLatin1String("&quot;");
} else if (chBadUrl == QLatin1Char('<')) {
resultBadUrl += QLatin1String("&lt;");
} else if (chBadUrl == QLatin1Char('>')) {
resultBadUrl += QLatin1String("&gt;");
} else {
resultBadUrl += chBadUrl;
}
}
return resultBadUrl;
}
if (!str.isEmpty()) {
QString hyperlink;
if (str.startsWith(QLatin1String("www."))) {
hyperlink = QLatin1String("http://") + str;
} else if (str.startsWith(QLatin1String("ftp."))) {
hyperlink = QLatin1String("ftp://") + str;
} else {
hyperlink = str;
}
result += QLatin1String("<a href=\"") + hyperlink + QLatin1String("\">") + str.toHtmlEscaped() + QLatin1String("</a>");
x += helper.mPos - start;
continue;
}
str = helper.getEmailAddress();
if (!str.isEmpty()) {
// len is the length of the local part
int len = str.indexOf(QLatin1Char('@'));
QString localPart = str.left(len);
// remove the local part from the result (as '&'s have been expanded to
// &amp; we have to take care of the 4 additional characters per '&')
result.truncate(result.length() - len - (localPart.count(QLatin1Char('&')) * 4));
x -= len;
result += QLatin1String("<a href=\"mailto:") + str + QLatin1String("\">") + str + QLatin1String("</a>");
x += str.length() - 1;
continue;
}
if (flags & ConvertPhoneNumbers) {
str = helper.getPhoneNumber();
if (!str.isEmpty()) {
result += QLatin1String("<a href=\"tel:") + normalizePhoneNumber(str) + QLatin1String("\">") + str + QLatin1String("</a>");
x += str.length() - 1;
continue;
}
}
}
if (flags & HighlightText) {
str = helper.highlightedText();
if (!str.isEmpty()) {
result += str;
x += helper.mPos - start;
continue;
}
}
result += ch;
}
}
if (flags & ReplaceSmileys) {
result = KEmoticonsParser::parseEmoticons(result);
}
return result;
}
@@ -0,0 +1,81 @@
/*
SPDX-FileCopyrightText: 2002 Dave Corrie <kde@davecorrie.com>
SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOREADDONS_KTEXTTOHTML_H
#define KCOREADDONS_KTEXTTOHTML_H
#include <kcoreaddons_export.h>
#include <QString>
/**
* @author Dave Corrie \<kde@davecorrie.com\>
*/
namespace KTextToHTML
{
/**
* @see Options
* @since 5.5.0
*/
enum Option {
/**
* Preserve white-space formatting of the text
*/
PreserveSpaces = 1 << 1,
/**
* Replace text emoticons smileys by emoticons images.
*/
ReplaceSmileys = 1 << 2,
/**
* Don't parse and replace any URLs.
*/
IgnoreUrls = 1 << 3,
/**
* Interpret text highlighting markup, like *bold*, _underline_ and /italic/,
* and wrap them in corresponding HTML entities.
*/
HighlightText = 1 << 4,
/**
* Replace phone numbers with tel: links.
* @since 5.56.0
*/
ConvertPhoneNumbers = 1 << 5,
};
/**
* Stores a combination of #Option values.
*/
Q_DECLARE_FLAGS(Options, Option)
Q_DECLARE_OPERATORS_FOR_FLAGS(Options)
/**
* Converts plaintext into html. The following characters are converted
* to HTML entities: & " < >. Newlines are also preserved.
*
* @param plainText The text to be converted into HTML.
* @param options The options to use when processing @p plainText.
* @param maxUrlLen The maximum length of permitted URLs. The reason for
* this limit is that there may be possible security
* implications in handling URLs of unlimited length.
* @param maxAddressLen The maximum length of permitted email addresses.
* The reason for this limit is that there may be possible
* security implications in handling addresses of unlimited
* length.
*
* @return An HTML version of the text supplied in the 'plainText'
* parameter, suitable for inclusion in the BODY of an HTML document.
*
* @since 5.5.0
*/
KCOREADDONS_EXPORT QString convertToHtml(const QString &plainText, const KTextToHTML::Options &options, int maxUrlLen = 4096, int maxAddressLen = 255);
}
#endif
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2002 Dave Corrie <kde@davecorrie.com>
SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTEXTTOHTML_P_H
#define KTEXTTOHTML_P_H
#include "kcoreaddons_export.h"
class KTextToHTMLHelper
{
public:
KTextToHTMLHelper(const QString &plainText, int pos = 0, int maxUrlLen = 4096, int maxAddressLen = 255);
QString getEmailAddress();
QString getPhoneNumber();
bool atUrl() const;
bool isEmptyUrl(const QString &url) const;
QString getUrl(bool *badurl = nullptr);
QString highlightedText();
QString mText;
int mMaxUrlLen;
int mMaxAddressLen;
int mPos;
};
#endif
@@ -0,0 +1,5 @@
#define ACCOUNTS_SERVICE_ICON_DIR "${ACCOUNTS_SERVICE_ICON_DIR}"
#cmakedefine01 HAVE_GETGROUPLIST
#cmakedefine01 HAVE_DLADDR
@@ -0,0 +1,80 @@
/* This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kformatprivate_p.h"
KFormat::KFormat(const QLocale &locale)
: d(new KFormatPrivate(locale))
{
}
KFormat::KFormat(const KFormat &other)
: d(other.d)
{
}
KFormat &KFormat::operator=(const KFormat &other)
{
d = other.d;
return *this;
}
KFormat::~KFormat()
{
}
QString KFormat::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const
{
return d->formatByteSize(size, precision, dialect, units);
}
QString KFormat::formatValue(double value, KFormat::Unit unit, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const
{
return d->formatValue(value, unit, QString(), precision, prefix, dialect);
}
QString KFormat::formatValue(double value, const QString &unit, int precision, KFormat::UnitPrefix prefix) const
{
return d->formatValue(value, KFormat::Unit::Other, unit, precision, prefix, MetricBinaryDialect);
}
// TODO KF6 Merge both methods
QString KFormat::formatValue(double value, const QString &unit, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const
{
return d->formatValue(value, KFormat::Unit::Other, unit, precision, prefix, dialect);
}
QString KFormat::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
{
return d->formatDuration(msecs, options);
}
QString KFormat::formatDecimalDuration(quint64 msecs, int decimalPlaces) const
{
return d->formatDecimalDuration(msecs, decimalPlaces);
}
QString KFormat::formatSpelloutDuration(quint64 msecs) const
{
return d->formatSpelloutDuration(msecs);
}
QString KFormat::formatRelativeDate(const QDate &date, QLocale::FormatType format) const
{
return d->formatRelativeDate(date, format);
}
QString KFormat::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
{
return d->formatRelativeDateTime(dateTime, format);
}
#include "moc_kformat.cpp"
@@ -0,0 +1,429 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFORMAT_H
#define KFORMAT_H
#include <kcoreaddons_export.h>
#include <QLocale>
#include <QSharedPointer>
#include <QString>
class QDate;
class QDateTime;
class KFormatPrivate;
/**
* \file kformat.h
*/
/*
The code in this class was copied from the old KLocale and modified
by John Layt (and also Alex Merry) in the KDELIBS 4 to KDE
Frameworks 5 transition in 2013.
Albert Astals Cid is the original author of formatSpelloutDuration()
originally named KLocale::prettyFormatDuration().
Michael Pyne is the original author of formatByteSize().
Michael Leupold is the original author of formatRelativeDate(()
originally part of KFormat::formatDate().
*/
/**
* @class KFormat kformat.h KFormat
*
* KFormat provides support for formatting numbers and datetimes in
* formats that are not supported by QLocale.
*
* @author John Layt <jlayt@kde.org>,
* Michael Pyne <mpyne@kde.org>,
* Albert Astals Cid <aacid@kde.org>,
*
* @short Class for formatting numbers and datetimes.
* @since 5.0
*/
class KCOREADDONS_EXPORT KFormat final
{
Q_GADGET
public:
/**
* These binary units are used in KDE by the formatByteSize()
* function.
*
* NOTE: There are several different units standards:
* 1) SI (i.e. metric), powers-of-10.
* 2) IEC, powers-of-2, with specific units KiB, MiB, etc.
* 3) JEDEC, powers-of-2, used for solid state memory sizing which
* is why you see flash cards labels as e.g. 4GB. These (ab)use
* the metric units. Although JEDEC only defines KB, MB, GB, if
* JEDEC is selected all units will be powers-of-2 with metric
* prefixes for clarity in the event of sizes larger than 1024 GB.
*
* Although 3 different dialects are possible this enum only uses
* metric names since adding all 3 different names of essentially the same
* unit would be pointless. Use BinaryUnitDialect to control the exact
* units returned.
*
* @see BinaryUnitDialect
* @see formatByteSize
*/
enum BinarySizeUnits {
/// Auto-choose a unit such that the result is in the range [0, 1000 or 1024)
DefaultBinaryUnits = -1,
// The first real unit must be 0 for the current implementation!
UnitByte, ///< B 1 byte
UnitKiloByte, ///< KiB/KB/kB 1024/1000 bytes.
UnitMegaByte, ///< MiB/MB/MB 2^20/10^06 bytes.
UnitGigaByte, ///< GiB/GB/GB 2^30/10^09 bytes.
UnitTeraByte, ///< TiB/TB/TB 2^40/10^12 bytes.
UnitPetaByte, ///< PiB/PB/PB 2^50/10^15 bytes.
UnitExaByte, ///< EiB/EB/EB 2^60/10^18 bytes.
UnitZettaByte, ///< ZiB/ZB/ZB 2^70/10^21 bytes.
UnitYottaByte, ///< YiB/YB/YB 2^80/10^24 bytes.
UnitLastUnit = UnitYottaByte,
};
/**
* These units are used in KDE by the formatValue() function.
*
* @see formatValue
* @since 5.49
*/
enum class Unit {
Other,
Bit, ///< "bit"
Byte, ///< "B"
Meter, ///< "m"
Hertz, ///< "Hz"
};
/**
* These prefixes are used in KDE by the formatValue()
* function.
*
* IEC prefixes are only defined for integral units of information, e.g.
* bits and bytes.
*
* @see BinarySizeUnits
* @see formatValue
* @since 5.49
*/
enum class UnitPrefix {
/// Auto-choose a unit such that the result is in the range [0, 1000 or 1024)
AutoAdjust = -128,
Yocto = 0, ///< --/-/y 10^-24
Zepto, ///< --/-/z 10^-21
Atto, ///< --/-/a 10^-18
Femto, ///< --/-/f 10^-15
Pico, ///< --/-/p 10^-12
Nano, ///< --/-/n 10^-9
Micro, ///< --/-/µ 10^-6
Milli, ///< --/-/m 10^-3
Centi, ///< --/-/c 0.01
Deci, ///< --/-/d 0.1
Unity, ///< "" 1
Deca, ///< --/-/da 10
Hecto, ///< --/-/h 100
Kilo, ///< Ki/K/k 1024/1000
Mega, ///< Mi/M/M 2^20/10^06
Giga, ///< Gi/G/G 2^30/10^09
Tera, ///< Ti/T/T 2^40/10^12
Peta, ///< Pi/P/P 2^50/10^15
Exa, ///< Ei/E/E 2^60/10^18
Zetta, ///< Zi/Z/Z 2^70/10^21
Yotta, ///< Yi/Y/Y 2^80/10^24
};
/**
* This enum chooses what dialect is used for binary units.
*
* Note: Although JEDEC abuses the metric prefixes and can therefore be
* confusing, it has been used to describe *memory* sizes for quite some time
* and programs should therefore use either Default, JEDEC, or IEC 60027-2
* for memory sizes.
*
* On the other hand network transmission rates are typically in metric so
* Default, Metric, or IEC (which is unambiguous) should be chosen.
*
* Normally choosing DefaultBinaryDialect is the best option as that uses
* the user's selection for units. If the user has not selected a preference,
* IECBinaryDialect will typically be used.
*
* @see BinarySizeUnits
* @see formatByteSize
*/
enum BinaryUnitDialect {
DefaultBinaryDialect = -1, ///< Used if no specific preference
IECBinaryDialect, ///< KiB, MiB, etc. 2^(10*n)
JEDECBinaryDialect, ///< KB, MB, etc. 2^(10*n)
MetricBinaryDialect, ///< SI Units, kB, MB, etc. 10^(3*n)
LastBinaryDialect = MetricBinaryDialect,
};
/**
* Format flags for formatDuration()
* @see DurationFormatOptions
*/
enum DurationFormatOption {
DefaultDuration = 0x0, ///< Default formatting in localized 1:23:45 format
InitialDuration = 0x1, ///< Default formatting in localized 1h23m45s format
ShowMilliseconds = 0x2, ///< Include milliseconds in format, e.g. 1:23:45.678
HideSeconds = 0x4, ///< Hide the seconds, e.g. 1:23 or 1h23m, overrides ShowMilliseconds
FoldHours = 0x8, ///< Fold the hours into the minutes, e.g. 83:45 or 83m45s, overrides HideSeconds
};
/**
* Stores a combination of #DurationFormatOption values.
*/
Q_DECLARE_FLAGS(DurationFormatOptions, DurationFormatOption)
Q_FLAG(DurationFormatOption)
/**
* Constructs a KFormat.
*
* @param locale the locale to use, defaults to the system locale
*/
explicit KFormat(const QLocale &locale = QLocale());
/**
* Copy constructor
*/
KFormat(const KFormat &other);
KFormat &operator=(const KFormat &other);
/**
* Destructor
*/
~KFormat();
/**
* Converts @p size from bytes to the appropriate string representation
* using the binary unit dialect @p dialect and the specific units @p units.
*
* Example:
* @code
* QString metric, iec, jedec, small;
* metric = formatByteSize(1000, 1, KFormat::MetricBinaryDialect, KFormat::UnitKiloByte);
* iec = formatByteSize(1024, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte);
* jedec = formatByteSize(1024, 1, KFormat::JEDECBinaryDialect, KFormat::UnitKiloByte);
* small = formatByteSize(100);
* // metric == "1.0 kB", iec == "1.0 KiB", jedec == "1.0 KB", small == "100 B"
* @endcode
*
* @param size size in bytes
* @param precision number of places after the decimal point to use. KDE uses
* 1 by default so when in doubt use 1. Whenever KFormat::UnitByte is used
* (either explicitly or autoselected from KFormat::DefaultBinaryUnits),
* the fractional part is always omitted.
* @param dialect binary unit standard to use. Use DefaultBinaryDialect to
* use the localized user selection unless you need to use a specific
* unit type (such as displaying a flash memory size in JEDEC).
* @param units specific unit size to use in result. Use
* DefaultBinaryUnits to automatically select a unit that will return
* a sanely-sized number.
* @return converted size as a translated string including the units.
* E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric).
* @see BinarySizeUnits
* @see BinaryUnitDialect
*/
QString formatByteSize(double size,
int precision = 1,
KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect,
KFormat::BinarySizeUnits units = KFormat::DefaultBinaryUnits) const;
/**
* Given a number of milliseconds, converts that to a string containing
* the localized equivalent, e.g. 1:23:45
*
* @param msecs Time duration in milliseconds
* @param options options to use in the duration format
* @return converted duration as a string - e.g. "1:23:45" "1h23m"
*/
QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
/**
* Given a number of milliseconds, converts that to a string containing
* the localized equivalent to the requested decimal places.
*
* e.g. given formatDuration(60000), returns "1.0 minutes"
*
* @param msecs Time duration in milliseconds
* @param decimalPlaces Decimal places to round off to, defaults to 2
* @return converted duration as a string - e.g. "5.5 seconds" "23.0 minutes"
*/
QString formatDecimalDuration(quint64 msecs, int decimalPlaces = 2) const;
/**
* Given a number of milliseconds, converts that to a spell-out string containing
* the localized equivalent.
*
* e.g. given formatSpelloutDuration(60001) returns "1 minute"
* given formatSpelloutDuration(62005) returns "1 minute and 2 seconds"
* given formatSpelloutDuration(90060000) returns "1 day and 1 hour"
*
* @param msecs Time duration in milliseconds
* @return converted duration as a string.
* Units not interesting to the user, for example seconds or minutes when the first
* unit is day, are not returned because they are irrelevant. The same applies for
* seconds when the first unit is hour.
*/
QString formatSpelloutDuration(quint64 msecs) const;
/**
* Returns a string formatted to a relative date style.
*
* If the @p date falls within one week before or after the current date
* then a relative date string will be returned, such as:
* * Yesterday
* * Today
* * Tomorrow
* * Last Tuesday
* * Next Wednesday
*
* If the @p date falls outside this period then the @p format is used.
*
* @param date the date to be formatted
* @param format the date format to use
*
* @return the date as a string
*/
QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const;
/**
* Returns a string formatted to a relative datetime style.
*
* If the @p dateTime falls within one week before or after the current date
* then a relative date string will be returned, such as:
* * Yesterday at 3:00pm
* * Today at 3:00pm
* * Tomorrow at 3:00pm
* * Last Tuesday at 3:00pm
* * Next Wednesday at 3:00pm
*
* If the @p dateTime falls within one hour of the current time.
* Then a shorter version is displayed:
* * Just a moment ago (for within the same minute)
* * 15 minutes ago
*
* If the @p dateTime falls outside this period then the date is rendered as:
* * Monday, 7 September, 2021 at 7:00 PM : date formatted @p format + " at " + time formatted with @p format
*
* With @p format LongFormat, time format used is set to ShortFormat (to omit timezone and seconds).
*
* First character is capitalized.
*
* @param dateTime the date to be formatted
* @param format the date format to use
*
* @return the date as a string
*/
QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const;
/**
* Converts @p value to the appropriate string representation
*
* Example:
* @code
* // sets formatted to "1.0 kbit"
* auto formatted = format.formatValue(1000, KFormat::Unit::Bit, 1, KFormat::UnitPrefix::Kilo);
* @endcode
*
* @param value value to be formatted
* @param precision number of places after the decimal point to use. KDE uses
* 1 by default so when in doubt use 1.
* @param unit unit to use in result.
* @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust
* to automatically select an appropriate prefix.
* @param dialect prefix standard to use. Use DefaultBinaryDialect to
* use the localized user selection unless you need to use a specific
* unit type. Only meaningful for KFormat::Unit::Byte, and ignored for
* all other units.
* @return converted size as a translated string including prefix and unit.
* E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric), "1.2 kbit".
* @see Unit
* @see UnitPrefix
* @see BinaryUnitDialect
* @since 5.49
*/
QString formatValue(double value,
KFormat::Unit unit,
int precision = 1,
KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust,
KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect) const;
/**
* Converts @p value to the appropriate string representation
*
* Example:
* @code
* QString bits, slow, fast;
* // sets bits to "1.0 kbit", slow to "1.0 kbit/s" and fast to "12.3 Mbit/s".
* bits = format.formatValue(1000, QStringLiteral("bit"), 1, KFormat::UnitPrefix::Kilo);
* slow = format.formatValue(1000, QStringLiteral("bit/s");
* fast = format.formatValue(12.3e6, QStringLiteral("bit/s");
* @endcode
*
* @param value value to be formatted
* @param precision number of places after the decimal point to use. KDE uses
* 1 by default so when in doubt use 1.
* @param unit unit to use in result.
* @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust
* to automatically select an appropriate prefix.
* @return converted size as a translated string including prefix and unit.
* E.g. "1.2 kbit", "2.4 kB", "12.3 Mbit/s"
* @see UnitPrefix
* @since 5.49
*/
QString formatValue(double value, const QString &unit, int precision = 1, KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust) const;
/**
* Converts @p value to the appropriate string representation.
*
* Example:
* @code
* QString iec, jedec, metric;
* // Sets iec to "1.0 KiB/s", jedec to "1.0 KB/s" and metric to "1.0 kB/s"
* iec = format.formatValue(1024, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::IECBinaryDialect);
* jedec = format.formatValue(1024, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::JEDECBinaryDialect);
* metric = format.formatValue(1000, QStringLiteral("B/s"), 1, KFormat::UnitPrefix::AutoAdjust, KFormat::MetricBinaryDialect);
* @endcode
*
* @param value value to be formatted
* @param precision number of places after the decimal point to use. 1 is used by default; when
* in doubt use 1
* @param unit unit to use in result
* @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust
* to automatically select an appropriate prefix
* @param dialect prefix standard to use. Use DefaultBinaryDialect to
* use the localized user selection unless you need to use a specific
* unit type
* @return converted size as a translated string including prefix and unit.
* E.g. "1.2 kbit", "2.4 kB", "12.3 Mbit/s"
* @see UnitPrefix
* @since 5.74
*/
QString formatValue(double value, const QString &unit, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const;
private:
QSharedDataPointer<KFormatPrivate> d;
};
#endif // KFORMAT_H
@@ -0,0 +1,540 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kformatprivate_p.h"
#include <QDateTime>
#include <QSettings>
#include <QStandardPaths>
#include <math.h>
KFormatPrivate::KFormatPrivate(const QLocale &locale)
: m_locale(locale)
{
}
KFormatPrivate::~KFormatPrivate()
{
}
constexpr double bpow(int exp)
{
return (exp > 0) ? 2.0 * bpow(exp - 1) : (exp < 0) ? 0.5 * bpow(exp + 1) : 1.0;
}
QString KFormatPrivate::formatValue(double value,
KFormat::Unit unit,
QString unitString,
int precision,
KFormat::UnitPrefix prefix,
KFormat::BinaryUnitDialect dialect) const
{
if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
dialect = KFormat::IECBinaryDialect;
}
if (static_cast<int>(prefix) < static_cast<int>(KFormat::UnitPrefix::Yocto) || static_cast<int>(prefix) > static_cast<int>(KFormat::UnitPrefix::Yotta)) {
prefix = KFormat::UnitPrefix::AutoAdjust;
}
double multiplier = 1024.0;
if (dialect == KFormat::MetricBinaryDialect) {
multiplier = 1000.0;
}
if (prefix == KFormat::UnitPrefix::AutoAdjust) {
int power = 0;
double adjustValue = qAbs(value);
while (adjustValue >= multiplier) {
adjustValue /= multiplier;
power += 1;
}
while (adjustValue && adjustValue < 1.0) {
adjustValue *= multiplier;
power -= 1;
}
const KFormat::UnitPrefix map[] = {
KFormat::UnitPrefix::Yocto, // -8
KFormat::UnitPrefix::Zepto,
KFormat::UnitPrefix::Atto,
KFormat::UnitPrefix::Femto,
KFormat::UnitPrefix::Pico,
KFormat::UnitPrefix::Nano,
KFormat::UnitPrefix::Micro,
KFormat::UnitPrefix::Milli,
KFormat::UnitPrefix::Unity, // 0
KFormat::UnitPrefix::Kilo,
KFormat::UnitPrefix::Mega,
KFormat::UnitPrefix::Giga,
KFormat::UnitPrefix::Tera,
KFormat::UnitPrefix::Peta,
KFormat::UnitPrefix::Exa,
KFormat::UnitPrefix::Zetta,
KFormat::UnitPrefix::Yotta, // 8
};
power = std::max(-8, std::min(8, power));
prefix = map[power + 8];
}
if (prefix == KFormat::UnitPrefix::Unity && unit == KFormat::Unit::Byte) {
precision = 0;
}
struct PrefixMapEntry {
KFormat::UnitPrefix prefix;
double decimalFactor;
double binaryFactor;
QString prefixCharSI;
QString prefixCharIEC;
};
const PrefixMapEntry map[] = {
{KFormat::UnitPrefix::Yocto, 1e-24, bpow(-80), tr("y", "SI prefix for 10^⁻24"), QString()},
{KFormat::UnitPrefix::Zepto, 1e-21, bpow(-70), tr("z", "SI prefix for 10^⁻21"), QString()},
{KFormat::UnitPrefix::Atto, 1e-18, bpow(-60), tr("a", "SI prefix for 10^⁻18"), QString()},
{KFormat::UnitPrefix::Femto, 1e-15, bpow(-50), tr("f", "SI prefix for 10^⁻15"), QString()},
{KFormat::UnitPrefix::Pico, 1e-12, bpow(-40), tr("p", "SI prefix for 10^⁻12"), QString()},
{KFormat::UnitPrefix::Nano, 1e-9, bpow(-30), tr("n", "SI prefix for 10^⁻9"), QString()},
{KFormat::UnitPrefix::Micro, 1e-6, bpow(-20), tr("µ", "SI prefix for 10^⁻6"), QString()},
{KFormat::UnitPrefix::Milli, 1e-3, bpow(-10), tr("m", "SI prefix for 10^⁻3"), QString()},
{KFormat::UnitPrefix::Unity, 1.0, 1.0, QString(), QString()},
{KFormat::UnitPrefix::Kilo, 1e3, bpow(10), tr("k", "SI prefix for 10^3"), tr("Ki", "IEC binary prefix for 2^10")},
{KFormat::UnitPrefix::Mega, 1e6, bpow(20), tr("M", "SI prefix for 10^6"), tr("Mi", "IEC binary prefix for 2^20")},
{KFormat::UnitPrefix::Giga, 1e9, bpow(30), tr("G", "SI prefix for 10^9"), tr("Gi", "IEC binary prefix for 2^30")},
{KFormat::UnitPrefix::Tera, 1e12, bpow(40), tr("T", "SI prefix for 10^12"), tr("Ti", "IEC binary prefix for 2^40")},
{KFormat::UnitPrefix::Peta, 1e15, bpow(50), tr("P", "SI prefix for 10^15"), tr("Pi", "IEC binary prefix for 2^50")},
{KFormat::UnitPrefix::Exa, 1e18, bpow(60), tr("E", "SI prefix for 10^18"), tr("Ei", "IEC binary prefix for 2^60")},
{KFormat::UnitPrefix::Zetta, 1e21, bpow(70), tr("Z", "SI prefix for 10^21"), tr("Zi", "IEC binary prefix for 2^70")},
{KFormat::UnitPrefix::Yotta, 1e24, bpow(80), tr("Y", "SI prefix for 10^24"), tr("Yi", "IEC binary prefix for 2^80")},
};
auto entry = std::find_if(std::begin(map), std::end(map), [prefix](const PrefixMapEntry &e) {
return e.prefix == prefix;
});
switch (unit) {
case KFormat::Unit::Bit:
unitString = tr("bit", "Symbol of binary digit");
break;
case KFormat::Unit::Byte:
unitString = tr("B", "Symbol of byte");
break;
case KFormat::Unit::Meter:
unitString = tr("m", "Symbol of meter");
break;
case KFormat::Unit::Hertz:
unitString = tr("Hz", "Symbol of hertz");
break;
case KFormat::Unit::Other:
break;
}
if (prefix == KFormat::UnitPrefix::Unity) {
QString numString = m_locale.toString(value, 'f', precision);
//: value without prefix, format "<val> <unit>"
return tr("%1 %2", "no Prefix").arg(numString, unitString);
}
QString prefixString;
if (dialect == KFormat::MetricBinaryDialect) {
value /= entry->decimalFactor;
prefixString = entry->prefixCharSI;
} else {
value /= entry->binaryFactor;
if (dialect == KFormat::IECBinaryDialect) {
prefixString = entry->prefixCharIEC;
} else {
prefixString = entry->prefixCharSI.toUpper();
}
}
QString numString = m_locale.toString(value, 'f', precision);
//: value with prefix, format "<val> <prefix><unit>"
return tr("%1 %2%3", "MetricBinaryDialect").arg(numString, prefixString, unitString);
}
QString KFormatPrivate::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const
{
// Current KDE default is IECBinaryDialect
const auto fallbackDialect = KFormat::IECBinaryDialect;
if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
const auto kdeglobals = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
QSettings settings(kdeglobals, QSettings::IniFormat);
dialect = static_cast<KFormat::BinaryUnitDialect>(settings.value("Locale/BinaryUnitDialect", fallbackDialect).toInt());
}
// Current KDE default is to auto-adjust so the size falls in the range 0 to 1000/1024
if (units < KFormat::DefaultBinaryUnits || units > KFormat::UnitLastUnit) {
units = KFormat::DefaultBinaryUnits;
}
int unit = 0; // Selects what unit to use
double multiplier = 1024.0;
if (dialect == KFormat::MetricBinaryDialect) {
multiplier = 1000.0;
}
// If a specific unit conversion is given, use it directly. Otherwise
// search until the result is in [0, multiplier] (or out of our range).
if (units == KFormat::DefaultBinaryUnits) {
while (qAbs(size) >= multiplier && unit < int(KFormat::UnitYottaByte)) {
size /= multiplier;
++unit;
}
} else {
// A specific unit is in use
unit = static_cast<int>(units);
if (unit > 0) {
size /= pow(multiplier, unit);
}
}
// Bytes, no rounding
if (unit == 0) {
precision = 0;
}
QString numString = m_locale.toString(size, 'f', precision);
// Do not remove "//:" comments below, they are used by the translators.
// NB: we cannot pass pluralization arguments, as the size may be negative
if (dialect == KFormat::MetricBinaryDialect) {
switch (unit) {
case KFormat::UnitByte:
//: MetricBinaryDialect size in bytes
return tr("%1 B", "MetricBinaryDialect").arg(numString);
case KFormat::UnitKiloByte:
//: MetricBinaryDialect size in 1000 bytes
return tr("%1 kB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitMegaByte:
//: MetricBinaryDialect size in 10^6 bytes
return tr("%1 MB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitGigaByte:
//: MetricBinaryDialect size in 10^9 bytes
return tr("%1 GB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitTeraByte:
//: MetricBinaryDialect size in 10^12 bytes
return tr("%1 TB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitPetaByte:
//: MetricBinaryDialect size in 10^15 bytes
return tr("%1 PB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitExaByte:
//: MetricBinaryDialect size in 10^18 byte
return tr("%1 EB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitZettaByte:
//: MetricBinaryDialect size in 10^21 bytes
return tr("%1 ZB", "MetricBinaryDialect").arg(numString);
case KFormat::UnitYottaByte:
//: MetricBinaryDialect size in 10^24 bytes
return tr("%1 YB", "MetricBinaryDialect").arg(numString);
}
} else if (dialect == KFormat::JEDECBinaryDialect) {
switch (unit) {
case KFormat::UnitByte:
//: JEDECBinaryDialect memory size in bytes
return tr("%1 B", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitKiloByte:
//: JEDECBinaryDialect memory size in 1024 bytes
return tr("%1 KB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitMegaByte:
//: JEDECBinaryDialect memory size in 10^20 bytes
return tr("%1 MB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitGigaByte:
//: JEDECBinaryDialect memory size in 10^30 bytes
return tr("%1 GB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitTeraByte:
//: JEDECBinaryDialect memory size in 10^40 bytes
return tr("%1 TB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitPetaByte:
//: JEDECBinaryDialect memory size in 10^50 bytes
return tr("%1 PB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitExaByte:
//: JEDECBinaryDialect memory size in 10^60 bytes
return tr("%1 EB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitZettaByte:
//: JEDECBinaryDialect memory size in 10^70 bytes
return tr("%1 ZB", "JEDECBinaryDialect").arg(numString);
case KFormat::UnitYottaByte:
//: JEDECBinaryDialect memory size in 10^80 bytes
return tr("%1 YB", "JEDECBinaryDialect").arg(numString);
}
} else { // KFormat::IECBinaryDialect, KFormat::DefaultBinaryDialect
switch (unit) {
case KFormat::UnitByte:
//: IECBinaryDialect size in bytes
return tr("%1 B", "IECBinaryDialect").arg(numString);
case KFormat::UnitKiloByte:
//: IECBinaryDialect size in 1024 bytes
return tr("%1 KiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitMegaByte:
//: IECBinaryDialect size in 10^20 bytes
return tr("%1 MiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitGigaByte:
//: IECBinaryDialect size in 10^30 bytes
return tr("%1 GiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitTeraByte:
//: IECBinaryDialect size in 10^40 bytes
return tr("%1 TiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitPetaByte:
//: IECBinaryDialect size in 10^50 bytes
return tr("%1 PiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitExaByte:
//: IECBinaryDialect size in 10^60 bytes
return tr("%1 EiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitZettaByte:
//: IECBinaryDialect size in 10^70 bytes
return tr("%1 ZiB", "IECBinaryDialect").arg(numString);
case KFormat::UnitYottaByte:
//: IECBinaryDialect size in 10^80 bytes
return tr("%1 YiB", "IECBinaryDialect").arg(numString);
}
}
// Should never reach here
Q_ASSERT(false);
return numString;
}
enum TimeConstants {
MSecsInDay = 86400000,
MSecsInHour = 3600000,
MSecsInMinute = 60000,
MSecsInSecond = 1000,
};
QString KFormatPrivate::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
{
quint64 ms = msecs;
if (options & KFormat::HideSeconds) {
// round to nearest minute
ms = qRound64(ms / (qreal)MSecsInMinute) * MSecsInMinute;
} else if (!(options & KFormat::ShowMilliseconds)) {
// round to nearest second
ms = qRound64(ms / (qreal)MSecsInSecond) * MSecsInSecond;
}
int hours = ms / MSecsInHour;
ms = ms % MSecsInHour;
int minutes = ms / MSecsInMinute;
ms = ms % MSecsInMinute;
int seconds = ms / MSecsInSecond;
ms = ms % MSecsInSecond;
if ((options & KFormat::InitialDuration) == KFormat::InitialDuration) {
if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
//: @item:intext Duration format minutes, seconds and milliseconds
return tr("%1m%2.%3s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')).arg(ms, 3, 10, QLatin1Char('0'));
} else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
//: @item:intext Duration format minutes and seconds
return tr("%1m%2s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
} else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
//: @item:intext Duration format hours and minutes
return tr("%1h%2m").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0'));
} else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
//: @item:intext Duration format hours, minutes, seconds, milliseconds
return tr("%1h%2m%3.%4s")
.arg(hours, 1, 10, QLatin1Char('0'))
.arg(minutes, 2, 10, QLatin1Char('0'))
.arg(seconds, 2, 10, QLatin1Char('0'))
.arg(ms, 3, 10, QLatin1Char('0'));
} else { // Default
//: @item:intext Duration format hours, minutes, seconds
return tr("%1h%2m%3s").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
}
} else {
if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
//: @item:intext Duration format minutes, seconds and milliseconds
return tr("%1:%2.%3").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')).arg(ms, 3, 10, QLatin1Char('0'));
} else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
//: @item:intext Duration format minutes and seconds
return tr("%1:%2").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
} else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
//: @item:intext Duration format hours and minutes
return tr("%1:%2").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0'));
} else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
//: @item:intext Duration format hours, minutes, seconds, milliseconds
return tr("%1:%2:%3.%4")
.arg(hours, 1, 10, QLatin1Char('0'))
.arg(minutes, 2, 10, QLatin1Char('0'))
.arg(seconds, 2, 10, QLatin1Char('0'))
.arg(ms, 3, 10, QLatin1Char('0'));
} else { // Default
//: @item:intext Duration format hours, minutes, seconds
return tr("%1:%2:%3").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
}
}
Q_UNREACHABLE();
return QString();
}
QString KFormatPrivate::formatDecimalDuration(quint64 msecs, int decimalPlaces) const
{
if (msecs >= MSecsInDay) {
//: @item:intext %1 is a real number, e.g. 1.23 days
return tr("%1 days").arg(m_locale.toString(msecs / (+MSecsInDay * 1.0), 'f', decimalPlaces));
} else if (msecs >= MSecsInHour) {
//: @item:intext %1 is a real number, e.g. 1.23 hours
return tr("%1 hours").arg(m_locale.toString(msecs / (+MSecsInHour * 1.0), 'f', decimalPlaces));
} else if (msecs >= MSecsInMinute) {
//: @item:intext %1 is a real number, e.g. 1.23 minutes
return tr("%1 minutes").arg(m_locale.toString(msecs / (+MSecsInMinute * 1.0), 'f', decimalPlaces));
} else if (msecs >= MSecsInSecond) {
//: @item:intext %1 is a real number, e.g. 1.23 seconds
return tr("%1 seconds").arg(m_locale.toString(msecs / (+MSecsInSecond * 1.0), 'f', decimalPlaces));
}
//: @item:intext %1 is a whole number
//~ singular %n millisecond
//~ plural %n milliseconds
return tr("%n millisecond(s)", nullptr, msecs);
}
enum DurationUnits {
Days = 0,
Hours,
Minutes,
Seconds,
};
static QString formatSingleDuration(DurationUnits units, int n)
{
// NB: n is guaranteed to be non-negative
switch (units) {
case Days:
//: @item:intext %n is a whole number
//~ singular %n day
//~ plural %n days
return KFormatPrivate::tr("%n day(s)", nullptr, n);
case Hours:
//: @item:intext %n is a whole number
//~ singular %n hour
//~ plural %n hours
return KFormatPrivate::tr("%n hour(s)", nullptr, n);
case Minutes:
//: @item:intext %n is a whole number
//~ singular %n minute
//~ plural %n minutes
return KFormatPrivate::tr("%n minute(s)", nullptr, n);
case Seconds:
//: @item:intext %n is a whole number
//~ singular %n second
//~ plural %n seconds
return KFormatPrivate::tr("%n second(s)", nullptr, n);
}
Q_ASSERT(false);
return QString();
}
QString KFormatPrivate::formatSpelloutDuration(quint64 msecs) const
{
quint64 ms = msecs;
int days = ms / MSecsInDay;
ms = ms % (MSecsInDay);
int hours = ms / MSecsInHour;
ms = ms % MSecsInHour;
int minutes = ms / MSecsInMinute;
ms = ms % MSecsInMinute;
int seconds = qRound(ms / 1000.0);
// Handle correctly problematic case #1 (look at KFormatTest::prettyFormatDuration())
if (seconds == 60) {
return formatSpelloutDuration(msecs - ms + MSecsInMinute);
}
if (days && hours) {
/*: @item:intext days and hours. This uses the previous item:intext messages.
If this does not fit the grammar of your language please contact the i18n team to solve the problem */
return tr("%1 and %2").arg(formatSingleDuration(Days, days), formatSingleDuration(Hours, hours));
} else if (days) {
return formatSingleDuration(Days, days);
} else if (hours && minutes) {
/*: @item:intext hours and minutes. This uses the previous item:intext messages.
If this does not fit the grammar of your language please contact the i18n team to solve the problem */
return tr("%1 and %2").arg(formatSingleDuration(Hours, hours), formatSingleDuration(Minutes, minutes));
} else if (hours) {
return formatSingleDuration(Hours, hours);
} else if (minutes && seconds) {
/*: @item:intext minutes and seconds. This uses the previous item:intext messages.
If this does not fit the grammar of your language please contact the i18n team to solve the problem */
return tr("%1 and %2").arg(formatSingleDuration(Minutes, minutes), formatSingleDuration(Seconds, seconds));
} else if (minutes) {
return formatSingleDuration(Minutes, minutes);
} else {
return formatSingleDuration(Seconds, seconds);
}
}
QString KFormatPrivate::formatRelativeDate(const QDate &date, QLocale::FormatType format) const
{
if (!date.isValid()) {
return tr("Invalid date", "used when a relative date string can't be generated because the date is invalid");
}
const qint64 daysTo = QDate::currentDate().daysTo(date);
if (daysTo > 2 || daysTo < -2) {
return m_locale.toString(date, format);
}
switch (daysTo) {
case 2:
return tr("In two days");
case 1:
return tr("Tomorrow");
case 0:
return tr("Today");
case -1:
return tr("Yesterday");
case -2:
return tr("Two days ago");
}
Q_UNREACHABLE();
}
QString KFormatPrivate::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
{
const QDateTime now = QDateTime::currentDateTime();
const auto secsToNow = dateTime.secsTo(now);
constexpr int secsInAHour = 60 * 60;
if (secsToNow >= 0 && secsToNow < secsInAHour) {
const int minutesToNow = secsToNow / 60;
if (minutesToNow <= 1) {
return tr("Just now");
} else {
//: @item:intext %1 is a whole number
//~ singular %n minute ago
//~ plural %n minutes ago
return tr("%n minute(s) ago", nullptr, minutesToNow);
}
}
const auto timeFormatType = format == QLocale::FormatType::LongFormat ? QLocale::FormatType::ShortFormat : format;
const qint64 daysToNow = dateTime.daysTo(now);
QString dateString;
if (daysToNow < 2 && daysToNow > -2) {
dateString = formatRelativeDate(dateTime.date(), format);
} else {
dateString = m_locale.toString(dateTime.date(), format);
}
/*: relative datetime with %1 result of QLocale.toString(date, format) or formatRelativeDate
and %2 result of QLocale.toString(time, timeformatType)
If this does not fit the grammar of your language please contact the i18n team to solve the problem */
QString formattedDate = tr("%1 at %2").arg(dateString, m_locale.toString(dateTime.time(), timeFormatType));
return formattedDate.replace(0, 1, formattedDate.at(0).toUpper());
}
@@ -0,0 +1,47 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFORMATPRIVATE_P_H
#define KFORMATPRIVATE_P_H
#include "kformat.h"
#include <QCoreApplication> // for Q_DECLARE_TR_FUNCTIONS
class KFormatPrivate : public QSharedData
{
Q_DECLARE_TR_FUNCTIONS(KFormat)
public:
explicit KFormatPrivate(const QLocale &locale);
virtual ~KFormatPrivate();
QString formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const;
QString
formatValue(double value, KFormat::Unit unit, QString unitString, int precision, KFormat::UnitPrefix prefix, KFormat::BinaryUnitDialect dialect) const;
QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const;
QString formatDecimalDuration(quint64 msecs, int decimalPlaces) const;
QString formatSpelloutDuration(quint64 msecs) const;
QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const;
QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const;
private:
QLocale m_locale;
};
#endif // KFORMATPRIVATE_P_H
@@ -0,0 +1,79 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
*/
#include "klibexec.h"
#include <config-util.h>
#if HAVE_DLADDR
#include <dlfcn.h>
#elif defined(Q_OS_WIN)
#include <windows.h>
#include <QVarLengthArray>
#endif
#include <QCoreApplication>
#include <QDir>
#include <QLibraryInfo>
#include <kcoreaddons_debug.h>
static QString libraryPathFromAddress(void *address)
{
#if HAVE_DLADDR
Dl_info info{};
if (dladdr(address, &info) == 0) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to match address to shared object.";
// Do not call dlerror. It's only expected to return something useful on freebsd!
return {};
}
return QFile::decodeName(info.dli_fname);
#elif defined(Q_OS_WIN)
HMODULE hModule = nullptr;
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast<LPWSTR>(address), &hModule)) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to GetModuleHandleExW" << GetLastError();
return {};
}
if (!hModule) {
qCWarning(KCOREADDONS_DEBUG) << "hModule null unexpectedly";
return {};
}
QVarLengthArray<wchar_t, MAX_PATH> pathArray;
DWORD pathSize = pathArray.size();
while (pathSize == pathArray.size()) { // pathSize doesn't include the null byte on success, so this only ever true if we need to grow
pathArray.resize(pathArray.size() + MAX_PATH);
pathSize = GetModuleFileNameW(hModule, pathArray.data(), pathArray.size());
if (pathSize == 0) {
qCWarning(KCOREADDONS_DEBUG) << "Failed to GetModuleFileNameW" << GetLastError();
return {};
}
}
return QDir::fromNativeSeparators(QString::fromWCharArray(pathArray.data()));
#else // unsupported
return {};
#endif
}
QString KLibexec::pathFromAddress(const QString &relativePath, void *address)
{
const QString libraryPath = libraryPathFromAddress(address);
const QString absoluteDirPath = QFileInfo(libraryPath).absolutePath();
const QString libexecPath = QFileInfo(absoluteDirPath + QLatin1Char('/') + relativePath).absoluteFilePath();
return libexecPath;
}
QStringList KLibexec::pathCandidates(const QString &relativePath)
{
const QString qLibexec = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath);
const QString qLibexecKF6 = qLibexec + QLatin1String("/kf6");
return {
QCoreApplication::applicationDirPath(), // look where our application binary is located
qLibexec, // look where libexec path is (can be set in qt.conf)
qLibexecKF6, // on !win32 we use a kf6 suffix
relativePath,
};
}
@@ -0,0 +1,76 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
*/
#ifndef KLIBEXEC_H
#define KLIBEXEC_H
#include <kcoreaddons_export.h>
#include <QString>
#include <QStringList>
/**
* @brief Utility functions around libexec.
*/
namespace KLibexec
{
#ifndef K_DOXYGEN
// Internal helpers. Do not use these but the inline variants.
KCOREADDONS_EXPORT QString pathFromAddress(const QString &relativePath, void *address);
KCOREADDONS_EXPORT QStringList pathCandidates(const QString &relativePath);
#endif
/**
* @brief Absolute libexec path resolved in relative relation to the current shared object.
*
* This function helps locate the absolute libexec path relative to the caller's binary artifact.
*
* For example:
*
* - Your source gets built with prefix /usr
* - Your binary artifact's presumed absolute path will be `/usr/lib/libfoobar.so`
* - You call `KLibexec::path("libexec/foobar")`
*
* Scenario 1 - The binaries are actually installed in /usr:
* - The function's output is `/usr/lib/libexec/foobar/` (resolved relatively from `/usr/lib/libfoobar.so`)
*
* Scenario 2 - The **same** binaries are installed in /opt (or moved there):
* - The function's output is `/opt/lib/libexec/foobar/` (resolved relatively from `/opt/lib/libfoobar.so`)
*
* @param relativePath relative element to append (e.g. "libexec/foobar" resulting in /usr/lib/libexec/foobar/ as output)
* when called with an empty string you effectively get the directory of your binary artifact.
* @return QString absolute libexec path or empty string if it cannot be resolved
* @since 5.91
*/
inline QString path(const QString &relativePath)
{
// this MUST be inline so that the marker address is in the calling object!
static int marker = 0;
return pathFromAddress(relativePath, &marker);
}
/**
* @brief default paths list for KDE Frameworks
*
* This function returns a fairly opinionated list of paths you can feed into QStandardPaths. The list includes
* various standard locations for Qt and KDE Frameworks and should generally be sensible for most use cases.
* You may wish to append the absolute installation path as final fallback.
*
* @warning The precise content and order of the list is an implementation detail and not expected to remain stable!
*
* @param relativePath see path() - not all paths get this appended!
* @return QStringList list of search paths
* @since 5.91
*/
inline QStringList kdeFrameworksPaths(const QString &relativePath)
{
// intentionally inline because path must be inline
return pathCandidates(path(relativePath));
}
} // namespace KLibexec
#endif // KLIBEXEC_H
@@ -0,0 +1,70 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2010 Jacopo De Simoi <wilderkde@gmail.com>
SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com>
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KLISTOPENFILESJOB_H
#define KLISTOPENFILESJOB_H
#include <QObject>
#include <QString>
#include <kcoreaddons_export.h>
#include <kjob.h>
#include <kprocesslist.h>
#include <memory>
class KListOpenFilesJobPrivate;
/**
* @brief Provides information about processes that have open files in a given path or subdirectory of path.
*
* When start() is invoked it starts to collect information about processes that have any files open in path or a
* subdirectory of path. When it is done the KJob::result signal is emitted and the result can be retrieved with the
* processInfoList function.
*
* On Unix like systems the lsof utility is used to get the list of processes.
* On Windows the listing always fails with error code NotSupported.
*
* @since 5.63
*/
class KCOREADDONS_EXPORT KListOpenFilesJob : public KJob
{
Q_OBJECT
public:
explicit KListOpenFilesJob(const QString &path);
~KListOpenFilesJob() override;
void start() override;
/**
* @brief Returns the list of processes with open files for the requested path
* @return The list of processes with open files for the requested path
*/
KProcessList::KProcessInfoList processInfoList() const;
public:
/**
* @brief Special error codes emitted by KListOpenFilesJob
*
* The KListOpenFilesJob uses the error codes defined here besides the standard error codes defined by KJob
*/
enum class Error {
/*** Indicates that the platform doesn't support listing open files by processes */
NotSupported = KJob::UserDefinedError + 1,
/*** Internal error has ocurred */
InternalError = KJob::UserDefinedError + 2,
/*** The specified path does not exist */
DoesNotExist = KJob::UserDefinedError + 11,
};
private:
friend class KListOpenFilesJobPrivate;
std::unique_ptr<KListOpenFilesJobPrivate> const d;
};
#endif // KLISTOPENFILESJOB_H
@@ -0,0 +1,134 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2010 Jacopo De Simoi <wilderkde@gmail.com>
SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com>
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "klistopenfilesjob.h"
#include <QDir>
#include <QList>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
class KListOpenFilesJobPrivate
{
public:
KListOpenFilesJobPrivate(KListOpenFilesJob *Job, const QDir &Path)
: job(Job)
, path(Path)
{
QObject::connect(&lsofProcess, &QProcess::errorOccurred, job, [this](QProcess::ProcessError error) {
lsofError(error);
});
QObject::connect(&lsofProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), job, [this](int exitCode, QProcess::ExitStatus exitStatus) {
lsofFinished(exitCode, exitStatus);
});
}
void start()
{
if (!path.exists()) {
emitResult(static_cast<int>(KListOpenFilesJob::Error::DoesNotExist), QObject::tr("Path %1 doesn't exist").arg(path.path()));
return;
}
const QString lsofExec = QStandardPaths::findExecutable(QStringLiteral("lsof"));
if (lsofExec.isEmpty()) {
const QString envPath = QString::fromLocal8Bit(qgetenv("PATH"));
emitResult(static_cast<int>(KListOpenFilesJob::Error::InternalError), QObject::tr("Could not find lsof executable in PATH:").arg(envPath));
return;
}
lsofProcess.start(lsofExec, {QStringLiteral("-t"), QStringLiteral("+d"), path.path()});
}
void lsofError(QProcess::ProcessError processError)
{
emitResult(static_cast<int>(KListOpenFilesJob::Error::InternalError), QObject::tr("Failed to execute `lsof'. Error code %1").arg(processError));
}
void lsofFinished(int, QProcess::ExitStatus);
void emitResult(int error, const QString &errorText);
KListOpenFilesJob *job;
const QDir path;
bool hasEmittedResult = false;
QProcess lsofProcess;
KProcessList::KProcessInfoList processInfoList;
};
static KProcessList::KProcessInfo findInfoForPid(qint64 pid)
{
#ifdef HAVE_PROCSTAT
// If HAVE_PROCSTAT is defined, then we're on a BSD, and there is a KProcessList implementation
// that efficiently lists all processes, but KProcessList::processInfo() is slow because
// it recalculates the list-of-all-processes on each iteration.
const auto allProcesses = KProcessList::processInfoList();
auto it = std::find_if(allProcesses.cbegin(), allProcesses.cend(), [pid](const KProcessList::KProcessInfo &info) {
return info.pid() == pid;
});
return it != allProcesses.cend() ? *it : KProcessList::KProcessInfo{};
#else
// Presumably Linux: processInfo(pid) is fine because it goes
// straight to /proc/<pid> for information.
return KProcessList::processInfo(pid);
#endif
}
void KListOpenFilesJobPrivate::lsofFinished(int, QProcess::ExitStatus)
{
if (hasEmittedResult) {
return;
}
const QString out(QString::fromLocal8Bit(lsofProcess.readAll()));
const QRegularExpression re(QStringLiteral("\\s+"));
const QList<QStringView> pidList = QStringView(out).split(re, Qt::SkipEmptyParts);
for (const auto &pidStr : pidList) {
const qint64 pid = pidStr.toLongLong();
if (pid) {
processInfoList << findInfoForPid(pid);
}
}
job->emitResult();
}
void KListOpenFilesJobPrivate::emitResult(int error, const QString &errorText)
{
if (hasEmittedResult) {
return;
}
job->setError(error);
job->setErrorText(errorText);
job->emitResult();
hasEmittedResult = true;
}
KListOpenFilesJob::KListOpenFilesJob(const QString &path)
: d(new KListOpenFilesJobPrivate(this, path))
{
}
KListOpenFilesJob::~KListOpenFilesJob() = default;
void KListOpenFilesJob::start()
{
d->start();
}
KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const
{
return d->processInfoList;
}
#include "moc_klistopenfilesjob.cpp"
@@ -0,0 +1,37 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "klistopenfilesjob.h"
#include <QTimer>
class KListOpenFilesJobPrivate
{
};
KListOpenFilesJob::KListOpenFilesJob(const QString &)
: d(nullptr)
{
}
KListOpenFilesJob::~KListOpenFilesJob() = default;
void KListOpenFilesJob::start()
{
QTimer::singleShot(0, [this]() {
setError(static_cast<int>(KListOpenFilesJob::Error::NotSupported));
setErrorText(QObject::tr("KListOpenFilesJob is not supported on Windows"));
emitResult();
});
}
KProcessList::KProcessInfoList KListOpenFilesJob::processInfoList() const
{
return KProcessList::KProcessInfoList();
}
#include "moc_klistopenfilesjob.cpp"
@@ -0,0 +1,516 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2022 Mirco Miranda
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmemoryinfo.h"
#include <QLoggingCategory>
#include <QSharedData>
Q_DECLARE_LOGGING_CATEGORY(LOG_KMEMORYINFO)
Q_LOGGING_CATEGORY(LOG_KMEMORYINFO, "kf.coreaddons.kmemoryinfo", QtWarningMsg)
// clang-format off
#if defined(Q_OS_WINDOWS)
#include <windows.h> // Windows.h must stay above Pspapi.h
#include <psapi.h>
#elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
#include <QByteArray>
#include <QFile>
#include <QByteArrayView>
#elif defined(Q_OS_MACOS)
#include <mach/mach.h>
#include <sys/sysctl.h>
#elif defined(Q_OS_FREEBSD)
#include <fcntl.h>
#include <kvm.h>
#include <sys/sysctl.h>
#elif defined(Q_OS_OPENBSD)
#include <sys/mount.h>
#include <sys/param.h> /* DEV_BSIZE PZERO */
#include <sys/swap.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#endif
// clang-format on
class KMemoryInfoPrivate : public QSharedData
{
public:
KMemoryInfoPrivate()
{
}
quint64 m_totalPhysical = 0;
quint64 m_availablePhysical = 0;
quint64 m_freePhysical = 0;
quint64 m_totalSwapFile = 0;
quint64 m_freeSwapFile = 0;
quint64 m_cached = 0;
quint64 m_buffers = 0;
};
KMemoryInfo::KMemoryInfo()
: d(new KMemoryInfoPrivate)
{
update();
}
KMemoryInfo::~KMemoryInfo()
{
}
KMemoryInfo::KMemoryInfo(const KMemoryInfo &other)
: d(other.d)
{
}
KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other)
{
d = other.d;
return *this;
}
bool KMemoryInfo::operator==(const KMemoryInfo &other) const
{
if (this == &other) {
return true;
}
// clang-format off
return (d->m_availablePhysical == other.d->m_availablePhysical
&& d->m_freePhysical == other.d->m_freePhysical
&& d->m_freeSwapFile == other.d->m_freeSwapFile
&& d->m_cached == other.d->m_cached
&& d->m_buffers == other.d->m_buffers
&& d->m_totalSwapFile == other.d->m_totalSwapFile
&& d->m_totalPhysical == other.d->m_totalPhysical);
// clang-format on
}
bool KMemoryInfo::operator!=(const KMemoryInfo &other) const
{
return !operator==(other);
}
bool KMemoryInfo::isNull() const
{
return d->m_totalPhysical == 0;
}
quint64 KMemoryInfo::totalPhysical() const
{
return d->m_totalPhysical;
}
quint64 KMemoryInfo::freePhysical() const
{
return d->m_freePhysical;
}
quint64 KMemoryInfo::availablePhysical() const
{
return d->m_availablePhysical;
}
quint64 KMemoryInfo::cached() const
{
return d->m_cached;
}
quint64 KMemoryInfo::buffers() const
{
return d->m_buffers;
}
quint64 KMemoryInfo::totalSwapFile() const
{
return d->m_totalSwapFile;
}
quint64 KMemoryInfo::freeSwapFile() const
{
return d->m_freeSwapFile;
}
#if defined(Q_OS_WINDOWS)
/*****************************************************************************
* Windows
****************************************************************************/
struct SwapInfo {
quint64 totalPageFilePages = 0;
quint64 freePageFilePages = 0;
};
BOOL __stdcall pageInfo(LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCWSTR lpFilename)
{
Q_UNUSED(lpFilename)
if (auto sw = static_cast<SwapInfo *>(pContext)) {
sw->totalPageFilePages += pPageFileInfo->TotalSize;
sw->freePageFilePages += (pPageFileInfo->TotalSize - pPageFileInfo->TotalInUse);
return true;
}
return false;
}
bool KMemoryInfo::update()
{
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (!GlobalMemoryStatusEx(&statex)) {
return false;
}
PERFORMANCE_INFORMATION pi;
DWORD pisz = sizeof(pi);
if (!GetPerformanceInfo(&pi, pisz)) {
return false;
}
SwapInfo si;
if (!EnumPageFiles(pageInfo, &si)) {
return false;
}
d->m_totalPhysical = statex.ullTotalPhys;
d->m_availablePhysical = statex.ullAvailPhys;
d->m_freePhysical = statex.ullAvailPhys;
d->m_totalSwapFile = si.totalPageFilePages * pi.PageSize;
d->m_freeSwapFile = si.freePageFilePages * pi.PageSize;
d->m_cached = pi.SystemCache * pi.PageSize;
d->m_buffers = 0;
return true;
}
#elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
/*****************************************************************************
* GNU/Linux
****************************************************************************/
using ByteArrayView = QByteArrayView;
bool extractBytes(quint64 &value, const QByteArray &buffer, const ByteArrayView &beginPattern, qsizetype &from)
{
ByteArrayView endPattern("kB");
auto beginIdx = buffer.indexOf(beginPattern, from);
if (beginIdx > -1) {
auto start = beginIdx + beginPattern.size();
auto endIdx = buffer.indexOf(endPattern, start);
if (endIdx > -1) {
from = endIdx + endPattern.size();
auto ok = false;
value = buffer.mid(start, endIdx - start).toULongLong(&ok) * 1024;
return ok;
}
}
if (from) { // Wrong order? Restart from the beginning
qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: extractBytes: wrong order when extracting" << beginPattern;
from = 0;
return extractBytes(value, buffer, beginPattern, from);
}
return false;
}
bool KMemoryInfo::update()
{
QFile file(QStringLiteral("/proc/meminfo"));
if (!file.open(QFile::ReadOnly)) {
return false;
}
auto meminfo = file.readAll();
file.close();
qsizetype miFrom = 0;
quint64 totalPhys = 0;
if (!extractBytes(totalPhys, meminfo, "MemTotal:", miFrom)) {
return false;
}
quint64 freePhys = 0;
if (!extractBytes(freePhys, meminfo, "MemFree:", miFrom)) {
return false;
}
quint64 availPhys = 0;
if (!extractBytes(availPhys, meminfo, "MemAvailable:", miFrom)) {
return false;
}
quint64 buffers = 0;
if (!extractBytes(buffers, meminfo, "Buffers:", miFrom)) {
return false;
}
quint64 cached = 0;
if (!extractBytes(cached, meminfo, "Cached:", miFrom)) {
return false;
}
quint64 swapTotal = 0;
if (!extractBytes(swapTotal, meminfo, "SwapTotal:", miFrom)) {
return false;
}
quint64 swapFree = 0;
if (!extractBytes(swapFree, meminfo, "SwapFree:", miFrom)) {
return false;
}
quint64 sharedMem = 0;
if (!extractBytes(sharedMem, meminfo, "Shmem:", miFrom)) {
return false;
}
quint64 sReclaimable = 0;
if (!extractBytes(sReclaimable, meminfo, "SReclaimable:", miFrom)) {
return false;
}
// Source HTOP: https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c
d->m_totalPhysical = totalPhys;
// NOTE: another viable solution: d->m_availablePhysical = std::min(availPhys, totalPhys - (committedAs - cached - (swapTotal - swapFree)))
d->m_availablePhysical = availPhys ? std::min(availPhys, totalPhys) : freePhys;
d->m_freePhysical = freePhys;
d->m_totalSwapFile = swapTotal;
d->m_freeSwapFile = swapFree;
d->m_cached = cached + sReclaimable - sharedMem;
d->m_buffers = buffers;
return true;
}
#elif defined(Q_OS_MACOS)
/*****************************************************************************
* macOS
****************************************************************************/
template<class T>
bool sysctlread(const char *name, T &var)
{
auto sz = sizeof(var);
return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
}
bool KMemoryInfo::update()
{
quint64 memSize = 0;
quint64 pageSize = 0;
xsw_usage swapUsage;
int mib[2];
size_t sz = 0;
mib[0] = CTL_HW;
mib[1] = HW_MEMSIZE;
sz = sizeof(memSize);
if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != KERN_SUCCESS) {
return false;
}
mib[0] = CTL_HW;
mib[1] = HW_PAGESIZE;
sz = sizeof(pageSize);
if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != KERN_SUCCESS) {
return false;
}
mib[0] = CTL_VM;
mib[1] = VM_SWAPUSAGE;
sz = sizeof(swapUsage);
if (sysctl(mib, 2, &swapUsage, &sz, NULL, 0) != KERN_SUCCESS) {
return false;
}
quint64 zfs_arcstats_size = 0;
if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) {
zfs_arcstats_size = 0; // no ZFS used
}
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
vm_statistics64_data_t vmstat;
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) {
return false;
}
d->m_totalPhysical = memSize;
d->m_availablePhysical = memSize - (vmstat.internal_page_count + vmstat.compressor_page_count + vmstat.wire_count) * pageSize;
d->m_freePhysical = vmstat.free_count * pageSize;
d->m_totalSwapFile = swapUsage.xsu_total;
d->m_freeSwapFile = swapUsage.xsu_avail;
d->m_cached = vmstat.external_page_count * pageSize + zfs_arcstats_size;
d->m_buffers = 0;
return true;
}
#elif defined(Q_OS_FREEBSD)
/*****************************************************************************
* FreeBSD
****************************************************************************/
template<class T>
bool sysctlread(const char *name, T &var)
{
auto sz = sizeof(var);
return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
}
bool KMemoryInfo::update()
{
quint64 memSize = 0;
quint64 pageSize = 0;
int mib[4];
size_t sz = 0;
mib[0] = CTL_HW;
mib[1] = HW_PHYSMEM;
sz = sizeof(memSize);
if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != 0) {
return false;
}
mib[0] = CTL_HW;
mib[1] = HW_PAGESIZE;
sz = sizeof(pageSize);
if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != 0) {
return false;
}
quint32 v_pageSize = 0;
if (sysctlread("vm.stats.vm.v_page_size", v_pageSize)) {
pageSize = v_pageSize;
}
quint64 zfs_arcstats_size = 0;
if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) {
zfs_arcstats_size = 0; // no ZFS used
}
quint32 v_cache_count = 0;
if (!sysctlread("vm.stats.vm.v_cache_count", v_cache_count)) {
return false;
}
quint32 v_inactive_count = 0;
if (!sysctlread("vm.stats.vm.v_inactive_count", v_inactive_count)) {
return false;
}
quint32 v_free_count = 0;
if (!sysctlread("vm.stats.vm.v_free_count", v_free_count)) {
return false;
}
quint64 vfs_bufspace = 0;
if (!sysctlread("vfs.bufspace", vfs_bufspace)) {
return false;
}
quint64 swap_tot = 0;
quint64 swap_free = 0;
if (auto kd = kvm_open("/dev/null", "/dev/null", "/dev/null", O_RDONLY, "kvm_open")) {
struct kvm_swap swap;
// if you specify a maxswap value of 1, the function will typically return the
// value 0 and the single kvm_swap structure will be filled with the grand total over all swap devices.
auto nswap = kvm_getswapinfo(kd, &swap, 1, 0);
if (nswap == 0) {
swap_tot = swap.ksw_total;
swap_free = swap.ksw_used;
}
swap_free = (swap_tot - swap_free) * pageSize;
swap_tot *= pageSize;
}
// Source HTOP: https://github.com/htop-dev/htop/blob/main/freebsd/FreeBSDProcessList.c
d->m_totalPhysical = memSize;
d->m_availablePhysical = pageSize * (v_cache_count + v_free_count + v_inactive_count) + vfs_bufspace + zfs_arcstats_size;
d->m_freePhysical = pageSize * v_free_count;
d->m_totalSwapFile = swap_tot;
d->m_freeSwapFile = swap_free;
d->m_cached = pageSize * v_cache_count + zfs_arcstats_size;
d->m_buffers = vfs_bufspace;
return true;
}
#elif defined(Q_OS_OPENBSD)
/*****************************************************************************
* OpenBSD
****************************************************************************/
// From src/usr.bin/top/machine.c
static int swap_usage(int *used, int *total)
{
struct swapent *swdev;
int nswap, rnswap, i;
nswap = swapctl(SWAP_NSWAP, nullptr, 0);
if (nswap == 0)
return 0;
swdev = static_cast<struct swapent *>(calloc(nswap, sizeof(*swdev)));
if (swdev == NULL)
return 0;
rnswap = swapctl(SWAP_STATS, swdev, nswap);
if (rnswap == -1) {
free(swdev);
return 0;
}
/* Total things up */
*total = *used = 0;
for (i = 0; i < nswap; i++) {
if (swdev[i].se_flags & SWF_ENABLE) {
*used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));
*total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));
}
}
free(swdev);
return 1;
}
bool KMemoryInfo::update()
{
// TODO: compute m_availablePhysical on OpenBSD
// tota phsycial memory
const long phys_pages = sysconf(_SC_PHYS_PAGES);
const long pagesize = sysconf(_SC_PAGESIZE);
if (phys_pages != -1 && pagesize != -1)
d->m_totalPhysical = ((uint64_t)phys_pages * (uint64_t)pagesize / 1024);
int swap_free = 0;
int swap_tot = 0;
if (swap_usage(&swap_free, &swap_tot)) {
d->m_totalSwapFile = swap_tot;
d->m_freeSwapFile = swap_free;
}
int uvmexp_mib[] = {CTL_VM, VM_UVMEXP};
struct uvmexp uvmexp;
size_t size = sizeof(uvmexp);
if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) {
bzero(&uvmexp, sizeof(uvmexp));
return false;
}
d->m_freePhysical = uvmexp.free * pagesize / 1024;
int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT};
struct bcachestats bcstats;
size = sizeof(bcstats);
if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) {
bzero(&bcstats, sizeof(bcstats));
return false;
}
d->m_cached = bcstats.numbufpages * pagesize / 1024;
return true;
}
#else
/*****************************************************************************
* Unsupported platform
****************************************************************************/
bool KMemoryInfo::update()
{
qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!";
return false;
}
#endif

Some files were not shown because too many files have changed in this diff Show More