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:
@@ -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}"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
+33
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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[] = {
|
||||
{">-(", "😠"},
|
||||
{">:(", "😠"},
|
||||
{">:)", "😈"},
|
||||
{">:-(", "😠"},
|
||||
{">w<", "😟"},
|
||||
{"<-.->", "😴"},
|
||||
{"<3", "♥️"},
|
||||
{"<]:o){", "🤡"},
|
||||
{"<|:^0|", "🤡"},
|
||||
{"()-()", "🤓"},
|
||||
{"(-_o)zzZ", "😴"},
|
||||
{"(:|", "🥱"},
|
||||
{"(@_@)", "😕"},
|
||||
{"(c:>*", "🤡"},
|
||||
{"({)", "🤗"},
|
||||
{"(})", "🤗"},
|
||||
{"*<:^)", "🤡"},
|
||||
{"*<:o)", "🤡"},
|
||||
{"*:o)", "🤡"},
|
||||
{"*:oB", "🤡"},
|
||||
{"*:oP", "🤡"},
|
||||
{"+o(", "🤢"},
|
||||
{",':(", "😕"},
|
||||
{"-_-", "😴"},
|
||||
{"-_-+", "😠"},
|
||||
{"-o-o-", "🤓"},
|
||||
{"/00\\", "😟"},
|
||||
{"0:)", "😇"},
|
||||
{"0:-)", "😇"},
|
||||
{"0;)", "😇"},
|
||||
{"0=)", "😇"},
|
||||
{"3:)", "😈"},
|
||||
{"8)", "😎"},
|
||||
{"8-)", "😎"},
|
||||
{"8:::(", "😭"},
|
||||
{":\"-(", "😢"},
|
||||
{":'(", "😢"},
|
||||
{":'-(", "😢"},
|
||||
{":'D", "😆"},
|
||||
{":(", "🙁"},
|
||||
{":((", "😢"},
|
||||
{":)", "🙂"},
|
||||
{":))", "😆"},
|
||||
{":*", "😗"},
|
||||
{":*(", "😢"},
|
||||
{":*)", "😗"},
|
||||
{":-$", "😯"},
|
||||
{":-&", "🤢"},
|
||||
{":->", "☺️"},
|
||||
{":->>", "☺️"},
|
||||
{":-(", "🙁"},
|
||||
{":-)", "🙂"},
|
||||
{":-))", "😀"},
|
||||
{":-)*", "😗"},
|
||||
{":-*", "😗"},
|
||||
{":-/", "😕"},
|
||||
{":-@", "😠"},
|
||||
{":-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/", "🤓"},
|
||||
{"\\~/", "🤓"},
|
||||
{"]:->", "😈"},
|
||||
{"^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 >
|
||||
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 "<tt>case $v in pat)</tt>" syntax. Use
|
||||
* "<tt>case $v in (pat)</tt>" 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 -
|
||||
* "<tt>sh -c 'foo \%f'</tt>" is taboo.
|
||||
* "<tt>file=\%f sh -c 'foo "$file"'</tt>" 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 - "<tt>for /f ...</tt>" 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(" ");
|
||||
}
|
||||
} 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(" ");
|
||||
++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(" ");
|
||||
}
|
||||
|
||||
if (startOfLine) {
|
||||
startOfLine = false;
|
||||
}
|
||||
continue;
|
||||
} else if (ch == QLatin1Char('\t')) {
|
||||
do {
|
||||
result += QLatin1String(" ");
|
||||
++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("&");
|
||||
} else if (ch == QLatin1Char('"')) {
|
||||
result += QLatin1String(""");
|
||||
} else if (ch == QLatin1Char('<')) {
|
||||
result += QLatin1String("<");
|
||||
} else if (ch == QLatin1Char('>')) {
|
||||
result += QLatin1String(">");
|
||||
} 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("&");
|
||||
} else if (chBadUrl == QLatin1Char('"')) {
|
||||
resultBadUrl += QLatin1String(""");
|
||||
} else if (chBadUrl == QLatin1Char('<')) {
|
||||
resultBadUrl += QLatin1String("<");
|
||||
} else if (chBadUrl == QLatin1Char('>')) {
|
||||
resultBadUrl += QLatin1String(">");
|
||||
} 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
|
||||
// & 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
Reference in New Issue
Block a user