Advance Wayland and KDE package bring-up

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,132 @@
add_subdirectory(core)
# KIOCore-only executables
if (NOT KIOCORE_ONLY)
if (NOT ANDROID)
add_subdirectory(kioworkers)
add_subdirectory(schemehandlers)
endif()
if (HAVE_QTDBUS)
add_subdirectory(kiod)
add_subdirectory(kssld)
endif()
add_subdirectory(kioworker)
endif()
if (NOT KIOCORE_ONLY)
add_subdirectory(gui)
add_subdirectory(widgets)
if (HAVE_QTDBUS)
add_subdirectory(kpasswdserver)
add_subdirectory(kioexec)
endif()
if (NOT ANDROID)
add_subdirectory(filewidgets)
add_subdirectory(urifilters)
endif()
set(NON_KIOCORE_LINK_QCHS
Qt6Widgets_QCH
Qt6Network_QCH
KF6Completion_QCH
KF6WidgetsAddons_QCH
KF6JobWidgets_QCH
KF6Bookmarks_QCH
KF6ItemViews_QCH
KF6Solid_QCH
KF6WindowSystem_QCH
)
if(BUILD_DESIGNERPLUGIN AND HAVE_QTDBUS)
add_subdirectory(designer)
endif()
endif()
ecm_qt_install_logging_categories(
EXPORT KIO
FILE kio.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
if (BUILD_QCH)
macro(_make_absolute var_name base_path)
set(_result)
foreach(_path ${${var_name}})
if(IS_ABSOLUTE "${_path}")
list(APPEND _result "${_path}")
else()
list(APPEND _result "${base_path}/${_path}")
endif()
endforeach()
set(${var_name} ${_result})
endmacro()
_make_absolute(KIOCore_QCH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/core")
_make_absolute(KIOGui_QCH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/gui")
_make_absolute(KIOWidgets_QCH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/widgets")
_make_absolute(KIOFileWidgets_QCH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/filewidgets")
ecm_add_qch(
KF6KIO_QCH
NAME KIO
BASE_NAME KF6KIO
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KIOCore_QCH_SOURCES}
${KIOGui_QCH_SOURCES}
${KIOWidgets_QCH_SOURCES}
${KIOFileWidgets_QCH_SOURCES}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
LINK_QCHS
KF6CoreAddons_QCH
KF6Service_QCH
${NON_KIOCORE_LINK_QCHS}
BLANK_MACROS
KIOCORE_EXPORT
KIOCORE_DEPRECATED_EXPORT
KIOCORE_DEPRECATED
"KIOCORE_DEPRECATED_VERSION(x, y, t)"
"KIOCORE_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
"KIOCORE_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
"KIOCORE_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
KIOGUI_EXPORT
KIOGUI_DEPRECATED_EXPORT
KIOGUI_DEPRECATED
"KIOGUI_DEPRECATED_VERSION(x, y, t)"
"KIOGUI_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t))"
"KIOGUI_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
"KIOGUI_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t))"
KIOWIDGETS_EXPORT
KIOWIDGETS_DEPRECATED_EXPORT
KIOWIDGETS_DEPRECATED
"KIOWIDGETS_DEPRECATED_VERSION(x, y, t)"
"KIOWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
"KIOWIDGETS_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
"KIOWIDGETS_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
KIOFILEWIDGETS_EXPORT
KIOFILEWIDGETS_DEPRECATED_EXPORT
KIOFILEWIDGETS_DEPRECATED
"KIOFILEWIDGETS_DEPRECATED_VERSION(x, y, t)"
"KIOFILEWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t))"
"KIOFILEWIDGETS_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
"KIOFILEWIDGETS_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t))"
PREDEFINED_MACROS
"KIOCORE_ENABLE_DEPRECATED_SINCE(x, y)=1"
"KIOCORE_BUILD_DEPRECATED_SINCE(x, y)=1"
"KIOGUI_ENABLE_DEPRECATED_SINCE(x, y)=1"
"KIOGUI_BUILD_DEPRECATED_SINCE(x, y)=1"
"KIOWIDGETS_ENABLE_DEPRECATED_SINCE(x, y)=1"
"KIOWIDGETS_BUILD_DEPRECATED_SINCE(x, y)=1"
"KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(x, y)=1"
"KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(x, y)=1"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,11 @@
#!/bin/sh
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
if [ -n "$lst" ] ; then
$EXTRACTRC $lst >> rc.cpp
fi
# Run xgettext to extract strings from all source files.
$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kio6.pot
@@ -0,0 +1,146 @@
/*
SPDX-FileCopyrightText: 2000-2002 Till Adam <adam@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef ACLHELPERS_P_H
#define ACLHELPERS_P_H
/*************************************
*
* ACL handling helpers
*
*************************************/
#include <config-kiocore.h>
#include "utils_p.h"
#include <KIO/UDSEntry>
#include <sys/types.h>
#if HAVE_SYS_ACL_H
#include <sys/acl.h>
#endif
#if HAVE_ACL_LIBACL_H
#include <acl/libacl.h>
#endif
namespace KIO
{
/**
* @internal
* WARNING: DO NOT USE outside KIO Framework
*/
namespace ACLPortability
{
/// @internal
__attribute__((unused)) static inline int acl_cmp(acl_t acl1, acl_t acl2)
{
#ifdef Q_OS_FREEBSD
return ::acl_cmp_np(acl1, acl2);
#else
return ::acl_cmp(acl1, acl2);
#endif
}
/// @internal
__attribute__((unused)) static inline acl_t acl_from_mode(const mode_t mode)
{
#ifdef Q_OS_FREEBSD
return ::acl_from_mode_np(mode);
#else
return ::acl_from_mode(mode);
#endif
}
/// @internal
static inline int acl_equiv_mode(acl_t acl, mode_t *mode_p)
{
#ifdef Q_OS_FREEBSD
return ::acl_equiv_mode_np(acl, mode_p);
#else
return ::acl_equiv_mode(acl, mode_p);
#endif
}
/// @internal
__attribute__((unused)) static inline int acl_get_perm(acl_permset_t permset_d, acl_perm_t perm)
{
#ifdef Q_OS_FREEBSD
return ::acl_get_perm_np(permset_d, perm);
#else
return ::acl_get_perm(permset_d, perm);
#endif
}
/// @internal
__attribute__((unused)) static inline int acl_extended_file(const char *path_p)
{
#ifdef Q_OS_FREEBSD
return ::acl_extended_file_np(path_p);
#else
return ::acl_extended_file(path_p);
#endif
}
} // namespace ACLPortability
} // namespace KIO
static QString aclToText(acl_t acl)
{
ssize_t size = 0;
char *txt = acl_to_text(acl, &size);
const QString ret = QString::fromLatin1(txt, size);
acl_free(txt);
return ret;
}
/* Append an atom indicating whether the file has extended acl information
* and if withACL is specified also one with the acl itself. If it's a directory
* and it has a default ACL, also append that. */
__attribute__((unused)) static void appendACLAtoms(const QByteArray &path, KIO::UDSEntry &entry, mode_t type)
{
// first check for a noop
if (KIO::ACLPortability::acl_extended_file(path.data()) == 0) {
return;
}
acl_t acl = nullptr;
acl_t defaultAcl = nullptr;
// do we have an acl for the file, and/or a default acl for the dir, if it is one?
acl = acl_get_file(path.data(), ACL_TYPE_ACCESS);
/* Sadly libacl does not provided a means of checking for extended ACL and default
* ACL separately. Since a directory can have both, we need to check again. */
if (Utils::isDirMask(type)) {
if (acl) {
if (KIO::ACLPortability::acl_equiv_mode(acl, nullptr) == 0) {
acl_free(acl);
acl = nullptr;
}
}
defaultAcl = acl_get_file(path.data(), ACL_TYPE_DEFAULT);
}
if (acl || defaultAcl) {
// qDebug() << path.constData() << "has extended ACL entries";
entry.replace(KIO::UDSEntry::UDS_EXTENDED_ACL, 1);
if (acl) {
const QString str = aclToText(acl);
entry.replace(KIO::UDSEntry::UDS_ACL_STRING, str);
// qDebug() << path.constData() << "ACL:" << str;
acl_free(acl);
}
if (defaultAcl) {
const QString str = aclToText(defaultAcl);
entry.replace(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING, str);
// qDebug() << path.constData() << "DEFAULT ACL:" << str;
acl_free(defaultAcl);
}
}
}
#endif // ACLHELPERS_P_H
@@ -0,0 +1,330 @@
include (ConfigureChecks.cmake)
configure_file(config-kiocore.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiocore.h )
configure_file(config-kmountpoint.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmountpoint.h)
configure_file(../kioworkers/file/config-stat-unix.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-stat-unix.h)
add_library(KF6KIOCore)
add_library(KF6::KIOCore ALIAS KF6KIOCore)
set_target_properties(KF6KIOCore PROPERTIES
VERSION ${KIO_VERSION}
SOVERSION ${KIO_SOVERSION}
EXPORT_NAME KIOCore
)
target_sources(KF6KIOCore PRIVATE
connectionbackend.cpp
connection.cpp
connectionserver.cpp
krecentdocument.cpp
krecentdirs.cpp
kfileitemlistproperties.cpp
directorysizejob.cpp
chmodjob.cpp
usernotificationhandler.cpp
ksambasharedata.cpp
ksambashare.cpp
knfsshare.cpp
kfileitem.cpp
davjob.cpp
deletejob.cpp
copyjob.cpp
filejob.cpp
mkdirjob.cpp
mkpathjob.cpp
kremoteencoding.cpp
slavebase.cpp
workerbase.cpp
dataworker.cpp
dataprotocol.cpp
desktopexecparser.cpp
emptytrashjob.cpp
authinfo.cpp
job_error.cpp
job.cpp
filecopyjob.cpp
listjob.cpp
mimetypejob.cpp
mimetypefinderjob.cpp
restorejob.cpp
simplejob.cpp
specialjob.cpp
statjob.cpp
namefinderjob.cpp
storedtransferjob.cpp
transferjob.cpp
filesystemfreespacejob.cpp
scheduler.cpp
kprotocolmanager.cpp
kurlauthorized.cpp
kacl.cpp
udsentry.cpp
global.cpp
metadata.cpp
kprotocolinfo.cpp
kprotocolinfofactory.cpp
jobtracker.cpp
jobuidelegateextension.cpp
jobuidelegatefactory.cpp
askuseractioninterface.cpp
kmountpoint.cpp
kcoredirlister.cpp
faviconscache.cpp
untrustedprogramhandlerinterface.cpp
kioglobal_p.cpp
batchrenamejob.cpp
worker.cpp
workerinterface.cpp
workerconfig.cpp
workerfactory.cpp
workerthread.cpp
kfilefilter.cpp
koverlayiconplugin.cpp
openwith.cpp
ksslerroruidata.cpp
)
set(KIOCore_HEADERS_dbus)
if (HAVE_QTDBUS)
target_sources(KF6KIOCore PRIVATE
kpasswdserverloop.cpp
kpasswdserverclient.cpp
kdirnotify.cpp
forwardingworkerbase.cpp
)
set(KIOCore_HEADERS_dbus KDirNotify)
endif()
ecm_qt_declare_logging_category(KF6KIOCore
HEADER kiocoredebug.h
IDENTIFIER KIO_CORE
CATEGORY_NAME kf.kio.core
OLD_CATEGORY_NAMES kf5.kio.core
DESCRIPTION "KIOCore (KIO)"
EXPORT KIO
)
ecm_qt_export_logging_category(
IDENTIFIER KIO_COPYJOB_DEBUG
CATEGORY_NAME kf.kio.core.copyjob
OLD_CATEGORY_NAMES kf5.kio.core.copyjob
DEFAULT_SEVERITY Warning
DESCRIPTION "KIO::CopyJob (KIO)"
EXPORT KIO
)
ecm_qt_export_logging_category(
IDENTIFIER KIO_CORE_DIRLISTER
CATEGORY_NAME kf.kio.core.dirlister
OLD_CATEGORY_NAMES kf5.kio.core.dirlister
DEFAULT_SEVERITY Warning
DESCRIPTION "KCoreDirLister (KIO)"
EXPORT KIO
)
ecm_qt_export_logging_category(
IDENTIFIER KIO_CORE_SAMBASHARE
CATEGORY_NAME kf.kio.core.sambashare
OLD_CATEGORY_NAMES kf5.kio.core.sambashare
DEFAULT_SEVERITY Warning
DESCRIPTION "sambashare (KIO)"
EXPORT KIO
)
ecm_qt_declare_logging_category(KF6KIOCore
HEADER kiocoreconnectiondebug.h
IDENTIFIER KIO_CORE_CONNECTION
CATEGORY_NAME kf.kio.core.connection
DEFAULT_SEVERITY Warning
DESCRIPTION "Connection (KIO)"
EXPORT KIO
)
if (UNIX)
target_sources(KF6KIOCore PRIVATE
kioglobal_p_unix.cpp
)
endif()
if (WIN32)
target_sources(KF6KIOCore PRIVATE
kioglobal_p_win.cpp
)
endif()
set(kiocore_dbus_SRCS)
if (HAVE_QTDBUS)
qt_add_dbus_interface(kiocore_dbus_SRCS org.kde.KIOFuse.VFS.xml kiofuse_interface)
set_source_files_properties(org.kde.KPasswdServer.xml
PROPERTIES INCLUDE authinfo.h
)
qt_add_dbus_interface(kiocore_dbus_SRCS org.kde.KPasswdServer.xml kpasswdserver_interface)
endif()
target_sources(KF6KIOCore PRIVATE
${kiocore_dbus_SRCS}
)
ecm_generate_export_header(KF6KIOCore
BASE_NAME KIOCore
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KIO
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.101 6.3 6.4 6.6 6.9
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6KIOCore PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/..>" # kio_version.h
)
target_include_directories(KF6KIOCore INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KIOCore>"
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KIO>"
)
target_link_libraries(KF6KIOCore
PUBLIC
KF6::CoreAddons # KJob
PRIVATE
Qt6::Xml # davjob.cpp uses QDom
Qt6::Concurrent
KF6::ConfigCore
KF6::I18n
KF6::Service
)
if (NOT ANDROID)
target_link_libraries(KF6KIOCore PRIVATE KF6::Crash)
endif()
if (HAVE_QTDBUS)
target_link_libraries(KF6KIOCore PUBLIC ${DBUS_LIB})
endif()
if (TARGET KF6::AuthCore)
target_link_libraries(KF6KIOCore PRIVATE KF6::AuthCore) #SlaveBase uses KAuth::Action
endif()
if(ACL_FOUND)
target_link_libraries(KF6KIOCore PRIVATE ${ACL_LIBS})
endif()
if(HAVE_LIB_MOUNT)
target_link_libraries(KF6KIOCore PRIVATE LibMount::LibMount)
endif()
# this should be done by cmake, see bug 371721
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h
COMMAND "${CMAKE_CXX_COMPILER}" "${CMAKE_CXX_COMPILER_ARG1}" "-dM" "-E" "-c" "${CMAKE_ROOT}/Modules/CMakeCXXCompilerABI.cpp" > ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h
)
set_property(TARGET KF6KIOCore APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h)
set_property(TARGET KF6KIOCore APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h)
endif()
# Headers prefixed with KIO/
ecm_generate_headers(KIOCore_CamelCase_HEADERS
HEADER_NAMES
DirectorySizeJob
Job # ### should forward to job_base.h, not job.h...
JobTracker
Global
ChmodJob
DeleteJob
CopyJob
EmptyTrashJob
FileJob
MkdirJob
MkpathJob
MetaData
UDSEntry
JobUiDelegateExtension
JobUiDelegateFactory
AskUserActionInterface
FileCopyJob
ListJob
MimetypeJob
MimeTypeFinderJob
RestoreJob
SimpleJob
SpecialJob
StatJob
NameFinderJob
StoredTransferJob
TransferJob
AuthInfo
DavJob
DesktopExecParser
FileSystemFreeSpaceJob
BatchRenameJob
WorkerBase
WorkerFactory
ForwardingWorkerBase
UntrustedProgramHandlerInterface
OpenWith
PREFIX KIO
REQUIRED_HEADERS KIO_namespaced_HEADERS
)
# Create local forwarding header for kio/job_base.h
set(REGULAR_HEADER_NAME ${CMAKE_CURRENT_BINARY_DIR}/kio/job_base.h)
if (NOT EXISTS ${REGULAR_HEADER_NAME})
file(WRITE ${REGULAR_HEADER_NAME} "#include \"${CMAKE_CURRENT_SOURCE_DIR}/job_base.h\"\n")
endif()
install(TARGETS KF6KIOCore EXPORT KF6KIOTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
list(APPEND KIO_namespaced_HEADERS
ioworker_defaults.h
job_base.h
)
install(FILES
${KIO_namespaced_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOCore/kio COMPONENT Devel
)
# Headers not prefixed with KIO/
ecm_generate_headers(KIOCore_HEADERS
HEADER_NAMES
KACL
KUrlAuthorized
KCoreDirLister
KRemoteEncoding
KFileItem
KFileItemListProperties
KMountPoint
KSambaShare
KSambaShareData
KPasswdServerClient
KProtocolInfo
KProtocolManager
KRecentDocument
KRecentDirs
KSslErrorUiData
KFileFilter
KOverlayIconPlugin
${KIOCore_HEADERS_dbus}
REQUIRED_HEADERS KIOCore_HEADERS
)
install(FILES ${KIOCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOCore/KIO COMPONENT Devel)
install(FILES
${KIOCore_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kiocore_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KIOCore COMPONENT Devel)
# make available to ecm_add_qch in parent folder
set(KIOCore_QCH_SOURCES ${KIOCore_HEADERS} ${KIO_namespaced_HEADERS} PARENT_SCOPE)
@@ -0,0 +1,42 @@
include(CheckIncludeFile)
include(CheckIncludeFiles)
include(CheckStructHasMember)
include(CheckFunctionExists)
include(CheckSymbolExists)
check_struct_has_member("struct sockaddr" sa_len "sys/socket.h" HAVE_STRUCT_SOCKADDR_SA_LEN)
### KMountPoint
check_function_exists(getmntinfo HAVE_GETMNTINFO)
check_include_files("sys/param.h;sys/mount.h" HAVE_SYS_MOUNT_H)
check_include_files(fstab.h HAVE_FSTAB_H)
check_include_files(sys/param.h HAVE_SYS_PARAM_H)
check_cxx_source_compiles("
#include <sys/types.h>
#include <sys/statvfs.h>
int main(){
struct statvfs *mntbufp;
int flags;
return getmntinfo(&mntbufp, flags);
}
" GETMNTINFO_USES_STATVFS )
check_symbol_exists("__GLIBC__" "stdlib.h" LIBC_IS_GLIBC)
if (LIBC_IS_GLIBC)
check_cxx_source_compiles("
#include <fcntl.h>
#include <sys/stat.h>
int main() {
struct statx buf;
statx(AT_FDCWD, \"/foo\", AT_EMPTY_PATH, STATX_BASIC_STATS, &buf);
return 0;
}
" HAVE_STATX)
else()
set(HAVE_STATX 0)
endif()
@@ -0,0 +1,27 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "askuseractioninterface.h"
class KIO::AskUserActionInterfacePrivate
{
public:
AskUserActionInterfacePrivate()
{
}
};
KIO::AskUserActionInterface::AskUserActionInterface(QObject *parent)
: QObject(parent)
{
}
KIO::AskUserActionInterface::~AskUserActionInterface()
{
}
#include "moc_askuseractioninterface.cpp"
@@ -0,0 +1,250 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef ASKUSERACTIONINTERFACE_H
#define ASKUSERACTIONINTERFACE_H
#include <kio/jobuidelegateextension.h> // RenameDialog_Result, SkipDialog_Result
#include <kiocore_export.h>
#include <QObject>
#include <QUrl>
#include <memory>
class KJob;
namespace KIO
{
class AskUserActionInterfacePrivate;
/**
* @class KIO::AskUserActionInterface askuseractioninterface.h <KIO/AskUserActionInterface>
*
* @brief The AskUserActionInterface class allows a KIO::Job to prompt the user
* for a decision when e.g.\ copying directories/files and there is a conflict
* (e.g.\ a file with the same name already exists at the destination).
*
* The methods in this interface are similar to their counterparts in
* KIO::JobUiDelegateExtension, the main difference is that AskUserActionInterface
* shows the dialogs using show() or open(), rather than exec(), the latter creates
* a nested event loop which could lead to crashes.
*
* @sa KIO::JobUiDelegateExtension
*
* @since 5.78
*/
class KIOCORE_EXPORT AskUserActionInterface : public QObject
{
Q_OBJECT
protected:
/**
* Constructor
*/
explicit AskUserActionInterface(QObject *parent = nullptr);
public:
/**
* Destructor
*/
~AskUserActionInterface() override;
/**
* @relates KIO::RenameDialog
*
* Constructs a modal, parent-less "rename" dialog, to prompt the user for a decision
* in case of conflicts, while copying/moving files. The dialog is shown using open(),
* rather than exec() (the latter creates a nested eventloop which could lead to crashes).
* You will need to connect to the askUserRenameResult() signal to get the dialog's result
* (exit code). The exit code is one of KIO::RenameDialog_Result.
*
* @see KIO::RenameDialog_Result enum.
*
* @param job the job that called this method
* @param title the title for the dialog box
* @param src the URL of the file/dir being copied/moved
* @param dest the URL of the destination file/dir, i.e. the one that already exists
* @param options parameters for the dialog (which buttons to show... etc), OR'ed values
* from the KIO::RenameDialog_Options enum
* @param sizeSrc size of the source file
* @param sizeDest size of the destination file
* @param ctimeSrc creation time of the source file
* @param ctimeDest creation time of the destination file
* @param mtimeSrc modification time of the source file
* @param mtimeDest modification time of the destination file
*/
virtual void askUserRename(KJob *job,
const QString &title,
const QUrl &src,
const QUrl &dest,
KIO::RenameDialog_Options options,
KIO::filesize_t sizeSrc,
KIO::filesize_t sizeDest,
const QDateTime &ctimeSrc = {},
const QDateTime &ctimeDest = {},
const QDateTime &mtimeSrc = {},
const QDateTime &mtimeDest = {}) = 0;
/**
* @relates KIO::SkipDialog
*
* You need to connect to the askUserSkipResult signal to get the dialog's
* result.
*
* @param job the job that called this method
* @param options parameters for the dialog (which buttons to show... etc), OR'ed
* values from the KIO::SkipDialog_Options enum
* @param error_text the error text to show to the user (usually the string returned
* by KJob::errorText())
*/
virtual void askUserSkip(KJob *job, KIO::SkipDialog_Options options, const QString &errorText) = 0;
/**
* The type of deletion.
*
* Used by askUserDelete().
*/
enum DeletionType {
Delete, /// Delete the files/directories directly, i.e. without moving them to Trash
Trash, /// Move the files/directories to Trash
EmptyTrash, /// Empty the Trash
/**
* This is the same as Delete, but more text is added to the message to inform
* the user that moving to Trash was tried but failed due to size constraints.
* Typical use case is re-asking the user about deleting instead of Trashing.
* @since 5.100
*/
DeleteInsteadOfTrash,
};
/**
* Deletion confirmation type.
*
* Used by askUserDelete().
*/
enum ConfirmationType {
DefaultConfirmation, ///< Do not ask if the user has previously set the "Do not ask again"
///< checkbox (which is is shown in the message dialog invoked by askUserDelete())
ForceConfirmation, ///< Always ask the user for confirmation
};
/**
* Ask for confirmation before moving @p urls (files/directories) to the Trash,
* emptying the Trash, or directly deleting files (i.e. without moving to Trash).
*
* Note that this method is not called automatically by KIO jobs. It's the
* application's responsibility to ask the user for confirmation before calling
* KIO::del() or KIO::trash().
*
* You need to connect to the askUserDeleteResult signal to get the dialog's result
* (exit code).
*
* @param urls the urls about to be moved to the Trash or deleted directly
* @param deletionType the type of deletion (Delete for real deletion, Trash otherwise),
* see the DeletionType enum
* @param confirmationType The type of deletion confirmation, see the ConfirmationType enum.
* Normally set to DefaultConfirmation
* @param parent the parent widget of the message box
*/
virtual void askUserDelete(const QList<QUrl> &urls,
DeletionType deletionType,
ConfirmationType confirmationType,
QWidget *parent = nullptr) = 0; // TODO KF6: replace QWidget* with QObject*
enum MessageDialogType {
QuestionTwoActions = 1, ///< @since 5.100
QuestionTwoActionsCancel = 2, ///< @since 5.100
WarningTwoActions = 3, ///< @since 5.100
WarningTwoActionsCancel = 4, ///< @since 5.100
WarningContinueCancel = 5,
Information = 7,
Error = 9,
};
/**
* This function allows for the delegation of user prompts from the KIO worker.
*
* @param type the desired type of message box, see the MessageDialogType enum
* @param text the message to show to the user
* @param title the title of the message dialog box
* @param primaryActionText the text for the primary action
* @param secondatyActionText the text for the secondary action
* @param primaryActionIconName the icon to show on the primary action
* @param secondatyActionIconName the icon to show on the secondary action
* @param dontAskAgainName the config key name used to store the result from
* 'Do not ask again' checkbox
* @param details more details about the message shown to the user
* @param parent the parent widget of the dialog
*/
virtual void requestUserMessageBox(MessageDialogType type,
const QString &text,
const QString &title,
const QString &primaryActionText,
const QString &secondatyActionText,
const QString &primaryActionIconName = {},
const QString &secondatyActionIconName = {},
const QString &dontAskAgainName = {},
const QString &details = {},
QWidget *parent = nullptr) = 0; // TODO KF6: replace QWidget* with QObject*, document "widget or window"
virtual void askIgnoreSslErrors(const QVariantMap &sslErrorData, QWidget *parent) = 0;
Q_SIGNALS:
/**
* Implementations of this interface must emit this signal when the rename dialog
* finishes, to notify the caller of the dialog's result.
*
* @param result the exit code from the rename dialog, one of the KIO::RenameDialog_Result
* enum
* @param newUrl the new destination URL set by the user
* @param parentJob the job that invoked the dialog
*/
void askUserRenameResult(KIO::RenameDialog_Result result, const QUrl &newUrl, KJob *parentJob);
/**
* Implementations of this interface must emit this signal when the skip dialog
* finishes, to notify the caller of the dialog's result.
*
* @param result the exit code from the skip dialog, one of the KIO::SkipDialog_Result enum
* @param parentJob the job that invoked the dialog
*/
void askUserSkipResult(KIO::SkipDialog_Result result, KJob *parentJob);
/**
* Implementations of this interface must emit this signal when the dialog invoked
* by askUserDelete() finishes, to notify the caller of the user's decision.
*
* @param allowDelete set to true if the user confirmed the delete operation, otherwise
* set to false
* @param urls a list of urls to delete/trash
* @param deletionType the deletion type to use, one of KIO::AskUserActionInterface::DeletionType
* @param parent the parent widget passed to askUserDelete(), for request identification
*/
void askUserDeleteResult(bool allowDelete,
const QList<QUrl> &urls,
KIO::AskUserActionInterface::DeletionType deletionType,
QWidget *parent); // TODO KF6: replace QWidget* with QObject*
/**
* Implementations of this interface must emit this signal when the dialog invoked
* by requestUserMessageBox() finishes, to notify the caller of the dialog's result
* (exit code).
*
* @param result the exit code of the dialog, one of KIO::WorkerBase::ButtonCode enum
*/
void messageBoxResult(int result); // TODO KF6: add a QObject* to identify requests? Or return an int from the request method and pass it back here?
void askIgnoreSslErrorsResult(int result);
private:
std::unique_ptr<AskUserActionInterfacePrivate> d;
};
} // namespace KIO
#endif // ASKUSERACTIONINTERFACE_H
@@ -0,0 +1,232 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2001 Dawit Alemayehu <adawit@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "authinfo.h"
#ifdef WITH_QTDBUS
#include <QDBusArgument>
#include <QDBusMetaType>
#endif
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QStandardPaths>
using namespace KIO;
//////
class ExtraField
{
public:
ExtraField()
: flags(AuthInfo::ExtraFieldNoFlags)
{
}
ExtraField(const ExtraField &other)
: customTitle(other.customTitle)
, flags(other.flags)
, value(other.value)
{
}
ExtraField &operator=(const ExtraField &other)
{
customTitle = other.customTitle;
flags = other.flags;
value = other.value;
return *this;
}
QString customTitle; // reserved for future use
AuthInfo::FieldFlags flags;
QVariant value;
};
Q_DECLARE_METATYPE(ExtraField)
static QDataStream &operator<<(QDataStream &s, const ExtraField &extraField)
{
s << extraField.customTitle;
s << static_cast<int>(extraField.flags);
s << extraField.value;
return s;
}
static QDataStream &operator>>(QDataStream &s, ExtraField &extraField)
{
s >> extraField.customTitle;
int i;
s >> i;
extraField.flags = AuthInfo::FieldFlags(i);
s >> extraField.value;
return s;
}
#ifdef WITH_QTDBUS
static QDBusArgument &operator<<(QDBusArgument &argument, const ExtraField &extraField)
{
argument.beginStructure();
argument << extraField.customTitle << static_cast<int>(extraField.flags) << QDBusVariant(extraField.value);
argument.endStructure();
return argument;
}
static const QDBusArgument &operator>>(const QDBusArgument &argument, ExtraField &extraField)
{
QDBusVariant value;
int flag;
argument.beginStructure();
argument >> extraField.customTitle >> flag >> value;
argument.endStructure();
extraField.value = value.variant();
extraField.flags = KIO::AuthInfo::FieldFlags(flag);
return argument;
}
#endif
class KIO::AuthInfoPrivate
{
public:
QMap<QString, ExtraField> extraFields;
};
//////
AuthInfo::AuthInfo()
: d(new AuthInfoPrivate())
{
modified = false;
readOnly = false;
verifyPath = false;
keepPassword = false;
AuthInfo::registerMetaTypes();
}
AuthInfo::AuthInfo(const AuthInfo &info)
: d(new AuthInfoPrivate())
{
(*this) = info;
AuthInfo::registerMetaTypes();
}
AuthInfo::~AuthInfo() = default;
AuthInfo &AuthInfo::operator=(const AuthInfo &info)
{
url = info.url;
username = info.username;
password = info.password;
prompt = info.prompt;
caption = info.caption;
comment = info.comment;
commentLabel = info.commentLabel;
realmValue = info.realmValue;
digestInfo = info.digestInfo;
verifyPath = info.verifyPath;
readOnly = info.readOnly;
keepPassword = info.keepPassword;
modified = info.modified;
d->extraFields = info.d->extraFields;
return *this;
}
bool AuthInfo::isModified() const
{
return modified;
}
void AuthInfo::setModified(bool flag)
{
modified = flag;
}
/////
void AuthInfo::setExtraField(const QString &fieldName, const QVariant &value)
{
d->extraFields[fieldName].value = value;
}
void AuthInfo::setExtraFieldFlags(const QString &fieldName, const FieldFlags flags)
{
d->extraFields[fieldName].flags = flags;
}
QVariant AuthInfo::getExtraField(const QString &fieldName) const
{
const auto it = d->extraFields.constFind(fieldName);
if (it == d->extraFields.constEnd()) {
return QVariant();
}
return it->value;
}
AuthInfo::FieldFlags AuthInfo::getExtraFieldFlags(const QString &fieldName) const
{
const auto it = d->extraFields.constFind(fieldName);
if (it == d->extraFields.constEnd()) {
return AuthInfo::ExtraFieldNoFlags;
}
return it->flags;
}
void AuthInfo::registerMetaTypes()
{
qRegisterMetaType<ExtraField>();
qRegisterMetaType<KIO::AuthInfo>();
#ifdef WITH_QTDBUS
qDBusRegisterMetaType<ExtraField>();
qDBusRegisterMetaType<KIO::AuthInfo>();
#endif
}
/////
QDataStream &KIO::operator<<(QDataStream &s, const AuthInfo &a)
{
s << quint8(1) << a.url << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue << a.digestInfo << a.verifyPath
<< a.readOnly << a.keepPassword << a.modified << a.d->extraFields;
return s;
}
QDataStream &KIO::operator>>(QDataStream &s, AuthInfo &a)
{
quint8 version;
s >> version >> a.url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo >> a.verifyPath
>> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields;
return s;
}
#ifdef WITH_QTDBUS
QDBusArgument &KIO::operator<<(QDBusArgument &argument, const AuthInfo &a)
{
argument.beginStructure();
argument << quint8(1) << a.url.toString() << a.username << a.password << a.prompt << a.caption << a.comment << a.commentLabel << a.realmValue
<< a.digestInfo << a.verifyPath << a.readOnly << a.keepPassword << a.modified << a.d->extraFields;
argument.endStructure();
return argument;
}
const QDBusArgument &KIO::operator>>(const QDBusArgument &argument, AuthInfo &a)
{
QString url;
quint8 version;
argument.beginStructure();
argument >> version >> url >> a.username >> a.password >> a.prompt >> a.caption >> a.comment >> a.commentLabel >> a.realmValue >> a.digestInfo
>> a.verifyPath >> a.readOnly >> a.keepPassword >> a.modified >> a.d->extraFields;
argument.endStructure();
a.url = QUrl(url);
return argument;
}
#endif
@@ -0,0 +1,283 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2001 Dawit Alemayehu <adawit@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_AUTHINFO_H
#define KIO_AUTHINFO_H
#include "kiocore_export.h"
#include <QList>
#include <QMap>
#include <QStringList>
#include <QUrl>
#include <QVariant> // Q_DECLARE_METATYPE
#include <memory>
class QDBusArgument;
namespace KIO
{
class AuthInfoPrivate;
/**
* @class KIO::AuthInfo authinfo.h <KIO/AuthInfo>
*
* This class is intended to make it easier to prompt for, cache
* and retrieve authorization information.
*
* When using this class to cache, retrieve or prompt authentication
* information, you only need to set the necessary attributes. For
* example, to check whether a password is already cached, the only
* required information is the URL of the resource and optionally
* whether or not a path match should be performed. Similarly, to
* prompt for password you only need to optionally set the prompt,
* username (if already supplied), comment and commentLabel fields.
*
* <em>SPECIAL NOTE:</em> If you extend this class to add additional
* parameters do not forget to overload the stream insertion and
* extraction operators ("<<" and ">>") so that the added data can
* be correctly serialized.
*
* @short A two way messaging class for passing authentication information.
* @author Dawit Alemayehu <adawit@kde.org>
*/
class KIOCORE_EXPORT AuthInfo
{
KIOCORE_EXPORT friend QDataStream &operator<<(QDataStream &s, const AuthInfo &a);
KIOCORE_EXPORT friend QDataStream &operator>>(QDataStream &s, AuthInfo &a);
KIOCORE_EXPORT friend QDBusArgument &operator<<(QDBusArgument &argument, const AuthInfo &a);
KIOCORE_EXPORT friend const QDBusArgument &operator>>(const QDBusArgument &argument, AuthInfo &a);
public:
/**
* Default constructor.
*/
AuthInfo();
/**
* Copy constructor.
*/
AuthInfo(const AuthInfo &info);
/**
* Destructor
*/
~AuthInfo();
/**
* Custom assignment operator.
*/
AuthInfo &operator=(const AuthInfo &info);
/**
* Use this method to check if the object was modified.
* @return true if the object has been modified
*/
bool isModified() const;
/**
* Use this method to indicate that this object has been modified.
* @param flag true to mark the object as modified, false to clear
*/
void setModified(bool flag);
/**
* The URL for which authentication is to be stored.
*
* This field is required when attempting to cache authorization
* and retrieve it. However, it is not needed when prompting
* the user for authorization info.
*
* This setting is @em required except when prompting the
* user for password.
*/
QUrl url;
/**
* This is @em required for caching.
*/
QString username;
/**
* This is @em required for caching.
*/
QString password;
/**
* Information to be displayed when prompting
* the user for authentication information.
*
* @note If this field is not set, the authentication
* dialog simply displays the preset default prompt.
*
* This setting is @em optional and empty by default.
*/
QString prompt;
/**
* The text to displayed in the title bar of
* the password prompting dialog.
*
* @note If this field is not set, the authentication
* dialog simply displays the preset default caption.
*
* This setting is @em optional and empty by default.
*/
QString caption;
/**
* Additional comment to be displayed when prompting
* the user for authentication information.
*
* This field allows you to display a short (no more than
* 80 characters) extra description in the password prompt
* dialog. For example, this field along with the
* commentLabel can be used to describe the server that
* requested the authentication:
*
* \code
* Server: Squid Proxy @ foo.com
* \endcode
*
* where "Server:" is the commentLabel and the rest is the
* actual comment. Note that it is always better to use
* the @p commentLabel field as it will be placed properly
* in the dialog rather than to include it within the actual
* comment.
*
* This setting is @em optional and empty by default.
*/
QString comment;
/**
* Descriptive label to be displayed in front of the
* comment when prompting the user for password.
*
* This setting is @em optional and only applicable when
* the comment field is also set.
*/
QString commentLabel;
/**
* A unique identifier that allows caching of multiple
* passwords for different resources in the same server.
*
* Mostly this setting is applicable to the HTTP protocol
* whose authentication scheme explicitly defines the use
* of such a unique key. However, any protocol that can
* generate or supply a unique id can effectively use it
* to distinguish passwords.
*
* This setting is @em optional and not set by default.
*/
QString realmValue;
/**
* Field to store any extra authentication information for
* protocols that need it.
*
* This setting is @em optional and mostly applicable for HTTP
* protocol. However, any protocol can make use of it to
* store extra info.
*/
QString digestInfo;
/**
* Flag that, if set, indicates whether a path match should be
* performed when requesting for cached authorization.
*
* A path is deemed to be a match if it is equal to or is a subset
* of the cached path. For example, if stored path is "/foo/bar"
* and the request's path set to "/foo/bar/acme", then it is a match
* whereas it would not if the request's path was set to "/foo".
*
* This setting is @em optional and false by default.
*/
bool verifyPath;
/**
* Flag which if set forces the username field to be read-only.
*
* This setting is @em optional and false by default.
*/
bool readOnly;
/**
* Flag to indicate the persistence of the given password.
*
* This is a two-way flag, when set before calling openPasswordDialog
* it makes the "keep Password" check box visible to the user.
* In return the flag will indicate the state of the check box.
* By default if the flag is checked the password will be cached
* for the entire life of the current KDE session otherwise the
* cached password is deleted right after the application using
* it has been closed.
*/
bool keepPassword;
/**
* Flags for extra fields
*/
enum FieldFlags {
ExtraFieldNoFlags = 0,
ExtraFieldReadOnly = 1 << 1,
ExtraFieldMandatory = 1 << 2,
};
/**
* Set Extra Field Value.
* Currently supported extra-fields:
* "domain" (QString),
* "anonymous" (bool)
* Setting it to an invalid QVariant() will disable the field.
* Extra Fields are disabled by default.
*/
void setExtraField(const QString &fieldName, const QVariant &value);
/**
* Set Extra Field Flags
*/
void setExtraFieldFlags(const QString &fieldName, const FieldFlags flags);
/**
* Get Extra Field Value
* Check QVariant::isValid() to find out if the field exists.
*/
QVariant getExtraField(const QString &fieldName) const;
/**
* Get Extra Field Flags
*/
AuthInfo::FieldFlags getExtraFieldFlags(const QString &fieldName) const;
/**
* Register the meta-types for AuthInfo. This is called from
* AuthInfo's constructor but needed by daemons on the D-Bus such
* as kpasswdserver.
*/
static void registerMetaTypes();
protected:
bool modified;
private:
friend class ::KIO::AuthInfoPrivate;
std::unique_ptr<AuthInfoPrivate> const d;
};
KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const AuthInfo &a);
KIOCORE_EXPORT QDataStream &operator>>(QDataStream &s, AuthInfo &a);
KIOCORE_EXPORT QDBusArgument &operator<<(QDBusArgument &argument, const AuthInfo &a);
KIOCORE_EXPORT const QDBusArgument &operator>>(const QDBusArgument &argument, AuthInfo &a);
}
Q_DECLARE_METATYPE(KIO::AuthInfo)
#endif
@@ -0,0 +1,222 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "batchrenamejob.h"
#include "copyjob.h"
#include "job_p.h"
#include <QMimeDatabase>
#include <QTimer>
#include <KLocalizedString>
#include <set>
using namespace KIO;
class KIO::BatchRenameJobPrivate : public KIO::JobPrivate
{
public:
BatchRenameJobPrivate(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
: JobPrivate()
, m_srcList(src)
, m_newName(newName)
, m_index(index)
, m_placeHolder(placeHolder)
, m_listIterator(m_srcList.constBegin())
, m_allExtensionsDifferent(true)
, m_useIndex(true)
, m_appendIndex(false)
, m_flags(flags)
{
// There occur four cases when renaming multiple files,
// 1. All files have different extension and $newName contains a valid placeholder.
// 2. At least two files have same extension and $newName contains a valid placeholder.
// In these two cases the placeholder character will be replaced by an integer($index).
// 3. All files have different extension and new name contains an invalid placeholder
// (this means either $newName doesn't contain the placeholder or the placeholders
// are not in a connected sequence).
// In this case nothing is substituted and all files have the same $newName.
// 4. At least two files have same extension and $newName contains an invalid placeholder.
// In this case $index is appended to $newName.
// Check for extensions.
std::set<QString> extensions;
QMimeDatabase db;
for (const QUrl &url : std::as_const(m_srcList)) {
const QString extension = db.suffixForFileName(url.path());
const auto [it, isInserted] = extensions.insert(extension);
if (!isInserted) {
m_allExtensionsDifferent = false;
break;
}
}
// Check for exactly one placeholder character or exactly one sequence of placeholders.
int pos = newName.indexOf(placeHolder);
if (pos != -1) {
while (pos < newName.size() && newName.at(pos) == placeHolder) {
pos++;
}
}
const bool validPlaceholder = (newName.indexOf(placeHolder, pos) == -1);
if (!validPlaceholder) {
if (!m_allExtensionsDifferent) {
m_appendIndex = true;
} else {
m_useIndex = false;
}
}
}
QList<QUrl> m_srcList;
QString m_newName;
int m_index;
QChar m_placeHolder;
QList<QUrl>::const_iterator m_listIterator;
bool m_allExtensionsDifferent;
bool m_useIndex;
bool m_appendIndex;
QUrl m_oldUrl;
QUrl m_newUrl; // for fileRenamed signal
const JobFlags m_flags;
QTimer m_reportTimer;
Q_DECLARE_PUBLIC(BatchRenameJob)
void slotStart();
void slotReport();
QString indexedName(const QString &name, int index, QChar placeHolder) const;
static inline BatchRenameJob *newJob(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
{
BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(src, newName, index, placeHolder, flags));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = Rename;
}
return job;
}
};
BatchRenameJob::BatchRenameJob(BatchRenameJobPrivate &dd)
: Job(dd)
{
Q_D(BatchRenameJob);
connect(&d->m_reportTimer, &QTimer::timeout, this, [this]() {
d_func()->slotReport();
});
d->m_reportTimer.start(200);
QTimer::singleShot(0, this, [this] {
d_func()->slotStart();
});
}
BatchRenameJob::~BatchRenameJob()
{
}
QString BatchRenameJobPrivate::indexedName(const QString &name, int index, QChar placeHolder) const
{
if (!m_useIndex) {
return name;
}
QString newName = name;
QString indexString = QString::number(index);
if (m_appendIndex) {
newName.append(indexString);
return newName;
}
// Insert leading zeros if necessary
const int minIndexLength = name.count(placeHolder);
indexString.prepend(QString(minIndexLength - indexString.length(), QLatin1Char('0')));
// Replace the index placeholders by the indexString
const int placeHolderStart = newName.indexOf(placeHolder);
newName.replace(placeHolderStart, minIndexLength, indexString);
return newName;
}
void BatchRenameJobPrivate::slotStart()
{
Q_Q(BatchRenameJob);
if (m_listIterator == m_srcList.constBegin()) { // emit total
q->setTotalAmount(KJob::Items, m_srcList.count());
}
if (m_listIterator != m_srcList.constEnd()) {
QString newName = indexedName(m_newName, m_index, m_placeHolder);
const QUrl oldUrl = *m_listIterator;
QMimeDatabase db;
const QString extension = db.suffixForFileName(oldUrl.path());
if (!extension.isEmpty()) {
newName += QLatin1Char('.') + extension;
}
m_oldUrl = oldUrl;
m_newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
m_newUrl.setPath(m_newUrl.path() + KIO::encodeFileName(newName));
KIO::Job *job = KIO::moveAs(oldUrl, m_newUrl, KIO::HideProgressInfo);
job->setParentJob(q);
q->addSubjob(job);
} else {
m_reportTimer.stop();
slotReport();
q->emitResult();
}
}
void BatchRenameJobPrivate::slotReport()
{
Q_Q(BatchRenameJob);
const auto processed = m_listIterator - m_srcList.constBegin();
q->setProcessedAmount(KJob::Items, processed);
q->emitPercent(processed, m_srcList.count());
emitRenaming(q, m_oldUrl, m_newUrl);
}
void BatchRenameJob::slotResult(KJob *job)
{
Q_D(BatchRenameJob);
if (job->error()) {
d->m_reportTimer.stop();
d->slotReport();
KIO::Job::slotResult(job);
return;
}
removeSubjob(job);
Q_EMIT fileRenamed(*d->m_listIterator, d->m_newUrl);
++d->m_listIterator;
++d->m_index;
d->slotStart();
}
BatchRenameJob *KIO::batchRename(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, KIO::JobFlags flags)
{
return BatchRenameJobPrivate::newJob(src, newName, index, placeHolder, flags);
}
#include "moc_batchrenamejob.cpp"
@@ -0,0 +1,73 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef BATCHRENAMEJOB_H
#define BATCHRENAMEJOB_H
#include "job_base.h"
#include "kiocore_export.h"
namespace KIO
{
class BatchRenameJobPrivate;
/**
* @class KIO::BatchRenameJob batchrenamejob.h <KIO/BatchRenameJob>
*
* A KIO job that renames multiple files in one go.
*
* @since 5.42
*/
class KIOCORE_EXPORT BatchRenameJob : public Job
{
Q_OBJECT
public:
~BatchRenameJob() override;
Q_SIGNALS:
/**
* Signals that a file was renamed.
*/
void fileRenamed(const QUrl &oldUrl, const QUrl &newUrl);
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
/// @internal
KIOCORE_NO_EXPORT explicit BatchRenameJob(BatchRenameJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(BatchRenameJob)
};
/**
* Renames multiple files at once.
*
* The new filename is obtained by replacing the characters represented by
* @p placeHolder by the index @p index.
* E.g. Calling batchRename({"file:///Test.jpg"}, "Test #" 12, '#') renames
* the file to "Test 12.jpg". A connected sequence of placeholders results in
* leading zeros. batchRename({"file:///Test.jpg"}, "Test ####" 12, '#') renames
* the file to "Test 0012.jpg". And if no placeholder is there then @p index is
* appended to @p newName. Calling batchRename({"file:///Test.jpg"}, "NewTest" 12, '#')
* renames the file to "NewTest12.jpg".
*
* @param src The list of items to rename.
* @param newName The base name to use in all new filenames.
* @param index The integer(incremented after renaming a file) to add to the base name.
* @param placeHolder The character(s) which @p index will replace.
*
* @return A pointer to the job handling the operation.
* @since 5.42
*/
KIOCORE_EXPORT BatchRenameJob *batchRename(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags = DefaultFlags);
}
#endif
@@ -0,0 +1,295 @@
/*
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: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "chmodjob.h"
#include "../utils_p.h"
#include <KLocalizedString>
#include <KUser>
#include <QDebug>
#include "askuseractioninterface.h"
#include "job_p.h"
#include "jobuidelegatefactory.h"
#include "kioglobal_p.h"
#include "listjob.h"
#include <stack>
namespace KIO
{
struct ChmodInfo {
QUrl url;
int permissions;
};
enum ChmodJobState {
CHMODJOB_STATE_LISTING,
CHMODJOB_STATE_CHMODING,
};
class ChmodJobPrivate : public KIO::JobPrivate
{
public:
ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive)
: state(CHMODJOB_STATE_LISTING)
, m_permissions(permissions)
, m_mask(mask)
, m_newOwner(newOwner)
, m_newGroup(newGroup)
, m_recursive(recursive)
, m_bAutoSkipFiles(false)
, m_lstItems(lstItems)
{
}
ChmodJobState state;
int m_permissions;
int m_mask;
KUserId m_newOwner;
KGroupId m_newGroup;
bool m_recursive;
bool m_bAutoSkipFiles;
KFileItemList m_lstItems;
std::stack<ChmodInfo> m_infos;
void chmodNextFile();
void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
void processList();
Q_DECLARE_PUBLIC(ChmodJob)
static inline ChmodJob *
newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags)
{
ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = ChangeAttr;
}
return job;
}
};
} // namespace KIO
using namespace KIO;
ChmodJob::ChmodJob(ChmodJobPrivate &dd)
: KIO::Job(dd)
{
Q_D(ChmodJob);
auto processFunc = [d]() {
d->processList();
};
QMetaObject::invokeMethod(this, processFunc, Qt::QueuedConnection);
}
ChmodJob::~ChmodJob()
{
}
void ChmodJobPrivate::processList()
{
Q_Q(ChmodJob);
while (!m_lstItems.isEmpty()) {
const KFileItem item = m_lstItems.first();
if (!item.isLink()) { // don't do anything with symlinks
// File or directory -> remember to chmod
ChmodInfo info;
info.url = item.url();
// This is a toplevel file, we apply changes directly (no +X emulation here)
const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags
info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask);
/*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8)
<< "\n wanted permission=" << QString::number(m_permissions,8)
<< "\n with mask=" << QString::number(m_mask,8)
<< "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8)
<< "\n bits we keep =" << QString::number(permissions & ~m_mask,8)
<< "\n new permissions = " << QString::number(info.permissions,8);*/
m_infos.push(std::move(info));
// qDebug() << "processList : Adding info for " << info.url;
// Directory and recursive -> list
if (item.isDir() && m_recursive) {
// qDebug() << "ChmodJob::processList dir -> listing";
KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo);
q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &entries) {
slotEntries(job, entries);
});
q->addSubjob(listJob);
return; // we'll come back later, when this one's finished
}
}
m_lstItems.removeFirst();
}
// qDebug() << "ChmodJob::processList -> going to STATE_CHMODING";
// We have finished, move on
state = CHMODJOB_STATE_CHMODING;
chmodNextFile();
}
void ChmodJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
{
KIO::UDSEntryList::ConstIterator it = list.begin();
KIO::UDSEntryList::ConstIterator end = list.end();
for (; it != end; ++it) {
const KIO::UDSEntry &entry = *it;
const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty();
const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME);
if (!isLink && relativePath != QLatin1String("..")) {
const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags
ChmodInfo info;
info.url = m_lstItems.first().url(); // base directory
info.url.setPath(Utils::concatPaths(info.url.path(), relativePath));
int mask = m_mask;
// Emulate -X: only give +x to files that had a +x bit already
// So the check is the opposite : if the file had no x bit, don't touch x bits
// For dirs this doesn't apply
if (!entry.isDir()) {
int newPerms = m_permissions & mask;
if ((newPerms & 0111) && !(permissions & 0111)) {
// don't interfere with mandatory file locking
if (newPerms & 02000) {
mask = mask & ~0101;
} else {
mask = mask & ~0111;
}
}
}
info.permissions = (m_permissions & mask) | (permissions & ~mask);
/*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8)
<< "\n wanted permission=" << QString::number(m_permissions,8)
<< "\n with mask=" << QString::number(mask,8)
<< "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8)
<< "\n bits we keep =" << QString::number(permissions & ~mask,8)
<< "\n new permissions = " << QString::number(info.permissions,8);*/
// Push this info on top of the stack so it's handled first.
// This way, the toplevel dirs are done last.
m_infos.push(std::move(info));
}
}
}
void ChmodJobPrivate::chmodNextFile()
{
auto processNextFunc = [this]() {
chmodNextFile();
};
Q_Q(ChmodJob);
if (!m_infos.empty()) {
ChmodInfo info = m_infos.top();
m_infos.pop();
// First update group / owner (if local file)
// (permissions have to be set after, in case of suid and sgid)
if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) {
QString path = info.url.toLocalFile();
if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) {
auto *askUserActionInterface = KIO::delegateExtension<AskUserActionInterface *>(q);
if (!askUserActionInterface) {
Q_EMIT q->warning(q, i18n("Could not modify the ownership of file %1", path));
} else if (!m_bAutoSkipFiles) {
SkipDialog_Options options;
if (m_infos.size() > 1) {
options |= SkipDialog_MultipleItems;
}
auto skipSignal = &AskUserActionInterface::askUserSkipResult;
q->connect(askUserActionInterface, skipSignal, q, [=, this](KIO::SkipDialog_Result result, KJob *parentJob) {
Q_ASSERT(q == parentJob);
q->disconnect(askUserActionInterface, skipSignal, q, nullptr);
switch (result) {
case Result_AutoSkip:
m_bAutoSkipFiles = true;
// fall through
Q_FALLTHROUGH();
case Result_Skip:
QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection);
return;
case Result_Retry:
m_infos.push(std::move(info));
QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection);
return;
case Result_Cancel:
default:
q->setError(ERR_USER_CANCELED);
q->emitResult();
return;
}
});
askUserActionInterface->askUserSkip(q,
options,
xi18n("Could not modify the ownership of file <filename>%1</filename>. You have "
"insufficient access to the file to perform the change.",
path));
return;
}
}
}
/*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/
KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions);
job->setParentJob(q);
// copy the metadata for acl and default acl
const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING"));
const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING"));
if (!aclString.isEmpty()) {
job->addMetaData(QStringLiteral("ACL_STRING"), aclString);
}
if (!defaultAclString.isEmpty()) {
job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString);
}
q->addSubjob(job);
} else { // We have finished
q->emitResult();
}
}
void ChmodJob::slotResult(KJob *job)
{
Q_D(ChmodJob);
removeSubjob(job);
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
emitResult();
return;
}
// qDebug() << "d->m_lstItems:" << d->m_lstItems.count();
switch (d->state) {
case CHMODJOB_STATE_LISTING:
d->m_lstItems.removeFirst();
// qDebug() << "-> processList";
d->processList();
return;
case CHMODJOB_STATE_CHMODING:
// qDebug() << "-> chmodNextFile";
d->chmodNextFile();
return;
default:
Q_ASSERT(false);
return;
}
}
ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags)
{
KUserId uid = KUserId::fromName(owner);
KGroupId gid = KGroupId::fromName(group);
return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags);
}
#include "moc_chmodjob.cpp"
@@ -0,0 +1,77 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_CHMODJOB_H
#define KIO_CHMODJOB_H
#include "global.h"
#include "job_base.h"
#include "kiocore_export.h"
#include <kfileitem.h>
namespace KIO
{
class ChmodJobPrivate;
/**
* @class KIO::ChmodJob chmodjob.h <KIO/ChmodJob>
*
* This job changes permissions on a list of files or directories,
* optionally in a recursive manner.
* @see KIO::chmod()
*/
class KIOCORE_EXPORT ChmodJob : public KIO::Job
{
Q_OBJECT
public:
~ChmodJob() override;
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOCORE_NO_EXPORT explicit ChmodJob(ChmodJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(ChmodJob)
};
/**
* Creates a job that changes permissions/ownership on several files or directories,
* optionally recursively.
* This version of chmod uses a KFileItemList so that it directly knows
* what to do with the items. TODO: a version that takes a QList<QUrl>,
* and a general job that stats each url and returns a KFileItemList.
*
* Note that change of ownership is only supported for local files.
*
* Inside directories, the "x" bits will only be changed for files that had
* at least one "x" bit before, and for directories.
* This emulates the behavior of chmod +X.
*
* @param lstItems The file items representing several files or directories.
* @param permissions the permissions we want to set
* @param mask the bits we are allowed to change.
* For instance, if mask is 0077, we don't change
* the "user" bits, only "group" and "others".
* @param newOwner If non-empty, the new owner for the files
* @param newGroup If non-empty, the new group for the files
* @param recursive whether to open directories recursively
* @param flags We support HideProgressInfo here
* @return The job handling the operation.
*/
KIOCORE_EXPORT ChmodJob *chmod(const KFileItemList &lstItems,
int permissions,
int mask,
const QString &newOwner,
const QString &newGroup,
bool recursive,
JobFlags flags = DefaultFlags);
}
#endif
@@ -0,0 +1,63 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_COMMANDS_P_H
#define KIO_COMMANDS_P_H
#include "kiocore_export.h"
namespace KIO
{
/**
* @internal
* Commands that can be invoked by a job.
*/
enum Command {
CMD_HOST = '0', // 48
CMD_CONNECT = '1', // 49
CMD_DISCONNECT = '2', // 50
CMD_WORKER_STATUS = '3', // 51
CMD_NONE = 'A', // 65
// Unused: 'B', // 66
CMD_GET = 'C', // 67
CMD_PUT = 'D', // 68
CMD_STAT = 'E', // 69
CMD_MIMETYPE = 'F', // 70
CMD_LISTDIR = 'G', // 71
CMD_MKDIR = 'H', // 72
CMD_RENAME = 'I', // 73
CMD_COPY = 'J', // 74
CMD_DEL = 'K', // 75
CMD_CHMOD = 'L', // 76
CMD_SPECIAL = 'M', // 77
CMD_SETMODIFICATIONTIME = 'N', // 78
CMD_REPARSECONFIGURATION = 'O', // 79
CMD_META_DATA = 'P', // 80
CMD_SYMLINK = 'Q', // 81
// Unused: 'R', // 82
CMD_MESSAGEBOXANSWER = 'S', // 83
CMD_RESUMEANSWER = 'T', // 84
CMD_CONFIG = 'U', // 85
// unused 'V', // 86
CMD_SETLINKDEST = 'W', // 87
CMD_OPEN = 'X', // 88
CMD_CHOWN = 'Y', // 89
CMD_READ = 'Z', // 90
CMD_WRITE = 91,
CMD_SEEK = 92,
CMD_CLOSE = 93,
CMD_HOST_INFO = 94,
CMD_FILESYSTEMFREESPACE = 95,
CMD_TRUNCATE = 96,
CMD_SSLERRORANSWER,
// Add new ones here once a release is done, to avoid breaking binary compatibility.
// Note that protocol-specific commands shouldn't be added here, but should use special.
};
} // namespace
#endif
@@ -0,0 +1,12 @@
#cmakedefine01 HAVE_STRUCT_SOCKADDR_SA_LEN
/* Defined if system has POSIX ACL support. */
#cmakedefine01 HAVE_POSIX_ACL
/* Defined if acl/libacl.h exists */
#cmakedefine01 HAVE_ACL_LIBACL_H
/* Defined if sys/acl.h exists */
#cmakedefine01 HAVE_SYS_ACL_H
#define KDE_INSTALL_FULL_LIBEXECDIR_KF "${KDE_INSTALL_FULL_LIBEXECDIR_KF}"
#cmakedefine01 KIO_ASSERT_WORKER_STATES
@@ -0,0 +1,10 @@
#cmakedefine01 HAVE_GETMNTINFO
#cmakedefine01 GETMNTINFO_USES_STATVFS
#cmakedefine01 HAVE_LIB_MOUNT
#cmakedefine01 HAVE_SYS_MOUNT_H
#cmakedefine01 HAVE_FSTAB_H
#cmakedefine01 HAVE_SYS_PARAM_H
#cmakedefine01 HAVE_VOLMGT
@@ -0,0 +1,249 @@
/*
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: 2007 Thiago Macieira <thiago@kde.org>
SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "connection_p.h"
#include "connectionbackend_p.h"
#include "kiocoredebug.h"
#include <QDebug>
#include <cerrno>
using namespace KIO;
void ConnectionPrivate::dequeue()
{
if (!backend || suspended) {
return;
}
for (const Task &task : std::as_const(outgoingTasks)) {
q->sendnow(task.cmd, task.data);
}
outgoingTasks.clear();
if (!incomingTasks.isEmpty()) {
Q_EMIT q->readyRead();
}
}
void ConnectionPrivate::commandReceived(const Task &task)
{
// qDebug() << this << "Command" << task.cmd << "added to the queue";
if (!suspended && incomingTasks.isEmpty() && readMode == Connection::ReadMode::EventDriven) {
auto dequeueFunc = [this]() {
dequeue();
};
QMetaObject::invokeMethod(q, dequeueFunc, Qt::QueuedConnection);
}
incomingTasks.append(task);
}
void ConnectionPrivate::disconnected()
{
q->close();
if (readMode == Connection::ReadMode::EventDriven) {
QMetaObject::invokeMethod(q, &Connection::readyRead, Qt::QueuedConnection);
}
}
void ConnectionPrivate::setBackend(ConnectionBackend *b)
{
delete backend;
backend = b;
if (backend) {
q->connect(backend, &ConnectionBackend::commandReceived, q, [this](const Task &task) {
commandReceived(task);
});
q->connect(backend, &ConnectionBackend::disconnected, q, [this]() {
disconnected();
});
backend->setSuspended(suspended);
}
}
Connection::Connection(Type type, QObject *parent)
: QObject(parent)
, d(new ConnectionPrivate)
, m_type(type)
{
d->q = this;
}
Connection::~Connection()
{
close();
}
void Connection::suspend()
{
// qDebug() << this << "Suspended";
d->suspended = true;
if (d->backend) {
d->backend->setSuspended(true);
}
}
void Connection::resume()
{
// send any outgoing or incoming commands that may be in queue
if (d->readMode == Connection::ReadMode::EventDriven) {
auto dequeueFunc = [this]() {
d->dequeue();
};
QMetaObject::invokeMethod(this, dequeueFunc, Qt::QueuedConnection);
}
// qDebug() << this << "Resumed";
d->suspended = false;
if (d->backend) {
d->backend->setSuspended(false);
}
}
void Connection::close()
{
if (d->backend) {
d->backend->disconnect(this);
d->backend->deleteLater();
d->backend = nullptr;
}
d->outgoingTasks.clear();
d->incomingTasks.clear();
}
bool Connection::isConnected() const
{
return d->backend && d->backend->state == ConnectionBackend::Connected;
}
bool Connection::inited() const
{
return d->backend;
}
bool Connection::suspended() const
{
return d->suspended;
}
void Connection::connectToRemote(const QUrl &address)
{
// qDebug() << "Connection requested to" << address;
const QString scheme = address.scheme();
if (scheme == QLatin1String("local")) {
d->setBackend(new ConnectionBackend(this));
} else {
qCWarning(KIO_CORE) << "Unknown protocol requested:" << scheme << "(" << address << ")";
Q_ASSERT(0);
return;
}
// connection succeeded
if (!d->backend->connectToRemote(address)) {
// qCWarning(KIO_CORE) << "could not connect to" << address << "using scheme" << scheme;
delete d->backend;
d->backend = nullptr;
return;
}
d->dequeue();
}
bool Connection::send(int cmd, const QByteArray &data)
{
// Remember that a Connection instance exists in the Application and the Worker. If the application terminates
// we potentially get disconnected while looping on data to send in the worker, terminate the worker when this
// happens. Specifically while reading a possible answer from the Application we may get socketDisconnected()
// we'll never get an answer in that case.
if (m_type == Type::Worker && !inited()) {
qCWarning(KIO_CORE) << "Connection::send() called with connection not inited";
return false;
}
if (!inited() || !d->outgoingTasks.isEmpty()) {
Task task;
task.cmd = cmd;
task.data = data;
d->outgoingTasks.append(std::move(task));
return true;
} else {
return sendnow(cmd, data);
}
}
bool Connection::sendnow(int cmd, const QByteArray &data)
{
if (!d->backend) {
qCWarning(KIO_CORE) << "Connection::sendnow has no backend";
return false;
}
if (data.size() > 0xffffff) {
qCWarning(KIO_CORE) << "Connection::sendnow too much data";
return false;
}
if (!isConnected()) {
qCWarning(KIO_CORE) << "Connection::sendnow not connected";
return false;
}
// qDebug() << this << "Sending command" << cmd << "of size" << data.size();
return d->backend->sendCommand(cmd, data);
}
bool Connection::hasTaskAvailable() const
{
return !d->incomingTasks.isEmpty();
}
bool Connection::waitForIncomingTask(int ms)
{
if (!isConnected()) {
return false;
}
if (d->backend) {
return d->backend->waitForIncomingTask(ms);
}
return false;
}
int Connection::read(int *_cmd, QByteArray &data)
{
// if it's still empty, then it's an error
if (d->incomingTasks.isEmpty()) {
// qCWarning(KIO_CORE) << this << "Task list is empty!";
return -1;
}
const Task &task = d->incomingTasks.constFirst();
// qDebug() << this << "Command" << task.cmd << "removed from the queue (size" << task.data.size() << ")";
*_cmd = task.cmd;
data = task.data;
d->incomingTasks.removeFirst();
// if we didn't empty our reading queue, emit again
if (!d->suspended && !d->incomingTasks.isEmpty() && d->readMode == Connection::ReadMode::EventDriven) {
auto dequeueFunc = [this]() {
d->dequeue();
};
QMetaObject::invokeMethod(this, dequeueFunc, Qt::QueuedConnection);
}
return data.size();
}
void Connection::setReadMode(ReadMode readMode)
{
d->readMode = readMode;
}
#include "moc_connection_p.cpp"
@@ -0,0 +1,170 @@
/*
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: 2024 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_CONNECTION_P_H
#define KIO_CONNECTION_P_H
#include "connectionbackend_p.h"
#include <QList>
#include <QObject>
#include <QString>
#include <QUrl>
#include <memory>
namespace KIO
{
class ConnectionServer;
class ConnectionPrivate;
/**
* @private
*
* This class provides a simple means for IPC between two applications
* via a pipe.
* It handles a queue of commands to be sent which makes it possible to
* queue data before an actual connection has been established.
*/
class Connection : public QObject
{
Q_OBJECT
public:
enum class ReadMode {
Polled, /// Any new tasks will be polled
EventDriven, /// We need to emit signals when we have pending events. Requires a working QEventLoop
};
enum class Type {
Application, /// This is the connection of the application side
Worker, /// This is the connection of the worker side
};
/**
* Creates a new connection.
* @see connectToRemote, listenForRemote
*/
explicit Connection(Type type, QObject *parent = nullptr);
~Connection() override;
/**
* Connects to the remote address.
* @param address a local:// or tcp:// URL.
*/
void connectToRemote(const QUrl &address);
/// Closes the connection.
void close();
bool isConnected() const;
/**
* Checks whether the connection has been initialized.
* @return true if the initialized
* @see init()
*/
bool inited() const;
/**
* Sends/queues the given command to be sent.
* @param cmd the command to set
* @param arr the bytes to send
* @return true if successful, false otherwise
*/
bool send(int cmd, const QByteArray &arr = QByteArray());
/**
* Sends the given command immediately.
* @param _cmd the command to set
* @param data the bytes to send
* @return true if successful, false otherwise
*/
bool sendnow(int _cmd, const QByteArray &data);
/**
* Returns true if there are packets to be read immediately,
* false if waitForIncomingTask must be called before more data
* is available.
*/
bool hasTaskAvailable() const;
/**
* Waits for one more command to be handled and ready.
*
* @param ms the time to wait in milliseconds
* @returns true if one command can be read, false if we timed out
*/
bool waitForIncomingTask(int ms = 30000);
/**
* Receive data.
*
* @param _cmd the received command will be written here
* @param data the received data will be written here
* @return >=0 indicates the received data size upon success
* -1 indicates error
*/
int read(int *_cmd, QByteArray &data);
/**
* Don't handle incoming data until resumed.
*/
void suspend();
/**
* Resume handling of incoming data.
*/
void resume();
/**
* Returns status of connection.
* @return true if suspended, false otherwise
*/
bool suspended() const;
void setReadMode(ReadMode mode);
Q_SIGNALS:
void readyRead();
private:
friend class ConnectionPrivate;
friend class ConnectionServer;
std::unique_ptr<class ConnectionPrivate> const d;
Type m_type;
};
// Separated from Connection only for historical reasons - they are both private now
class ConnectionPrivate
{
public:
inline ConnectionPrivate()
: backend(nullptr)
, q(nullptr)
, suspended(false)
, readMode(Connection::ReadMode::EventDriven)
{
}
void dequeue();
void commandReceived(const Task &task);
void disconnected();
void setBackend(ConnectionBackend *b);
QList<Task> outgoingTasks;
QList<Task> incomingTasks;
ConnectionBackend *backend;
Connection *q;
bool suspended;
Connection::ReadMode readMode;
};
class ConnectionServerPrivate;
}
#endif
@@ -0,0 +1,74 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000 David Faure <coolo@kde.org>
SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "connectionbackend_p.h"
#include <KLocalizedString>
using namespace KIO;
ConnectionBackend::ConnectionBackend(QObject *parent)
: QObject(parent)
, state(Idle)
, socket(nullptr)
, localServer(nullptr)
, signalEmitted(false)
{
}
ConnectionBackend::~ConnectionBackend() = default;
void ConnectionBackend::setSuspended(bool enable)
{
(void)enable;
}
bool ConnectionBackend::connectToRemote(const QUrl &url)
{
(void)url;
errorString = i18n("Local IPC is unavailable on Redox without QtNetwork");
state = Idle;
return false;
}
ConnectionBackend::ConnectionResult ConnectionBackend::listenForRemote()
{
state = Idle;
errorString = i18n("Local IPC is unavailable on Redox without QtNetwork");
return {false, errorString};
}
bool ConnectionBackend::waitForIncomingTask(int ms)
{
(void)ms;
return false;
}
bool ConnectionBackend::sendCommand(int cmd, const QByteArray &data) const
{
(void)cmd;
(void)data;
return false;
}
ConnectionBackend *ConnectionBackend::nextPendingConnection()
{
return nullptr;
}
void ConnectionBackend::socketReadyRead()
{
}
void ConnectionBackend::socketDisconnected()
{
state = Idle;
Q_EMIT disconnected();
}
@@ -0,0 +1,77 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_CONNECTIONBACKEND_P_H
#define KIO_CONNECTIONBACKEND_P_H
#include <QObject>
#include <QUrl>
class QLocalServer;
class QLocalSocket;
class QTcpServer;
namespace KIO
{
struct Task {
int cmd = -1;
long len = 0;
QByteArray data{};
};
class ConnectionBackend : public QObject
{
Q_OBJECT
public:
enum {
Idle,
Listening,
Connected
} state;
QUrl address;
QString errorString;
static const int HeaderSize = 10;
static const int StandardBufferSize = 32 * 1024;
private:
QLocalSocket *socket;
QLocalServer *localServer;
std::optional<Task> pendingTask = std::nullopt;
bool signalEmitted;
Q_SIGNALS:
void disconnected();
void commandReceived(const KIO::Task &task);
void newConnection();
public:
explicit ConnectionBackend(QObject *parent = nullptr);
~ConnectionBackend() override;
struct ConnectionResult {
bool success = true;
QString error;
};
void setSuspended(bool enable);
bool connectToRemote(const QUrl &url);
ConnectionResult listenForRemote();
bool waitForIncomingTask(int ms);
bool sendCommand(int command, const QByteArray &data) const;
ConnectionBackend *nextPendingConnection();
public Q_SLOTS:
void socketReadyRead();
void socketDisconnected();
};
}
#endif
@@ -0,0 +1,61 @@
/*
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-License-Identifier: LGPL-2.0-or-later
*/
#include "connectionserver.h"
#include "connection_p.h"
#include "connectionbackend_p.h"
#include "kiocoredebug.h"
using namespace KIO;
ConnectionServer::ConnectionServer(QObject *parent)
: QObject(parent)
{
}
ConnectionServer::~ConnectionServer() = default;
void ConnectionServer::listenForRemote()
{
backend = new ConnectionBackend(this);
if (auto result = backend->listenForRemote(); !result.success) {
qCWarning(KIO_CORE) << "ConnectionServer::listenForRemote failed:" << result.error;
delete backend;
backend = nullptr;
return;
}
connect(backend, &ConnectionBackend::newConnection, this, &ConnectionServer::newConnection);
// qDebug() << "Listening on" << d->backend->address;
}
QUrl ConnectionServer::address() const
{
if (backend) {
return backend->address;
}
return QUrl();
}
bool ConnectionServer::isListening() const
{
return backend && backend->state == ConnectionBackend::Listening;
}
void ConnectionServer::setNextPendingConnection(Connection *conn)
{
ConnectionBackend *newBackend = backend->nextPendingConnection();
Q_ASSERT(newBackend);
conn->d->setBackend(newBackend);
newBackend->setParent(conn);
conn->d->dequeue();
}
#include "moc_connectionserver.cpp"
@@ -0,0 +1,56 @@
/*
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-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_CONNECTIONSERVER_H
#define KIO_CONNECTIONSERVER_H
#include <QObject>
#include <QUrl>
#include "connectionbackend_p.h"
namespace KIO
{
class ConnectionServerPrivate;
class Connection;
/*
* This class provides a way to obtaining KIO::Connection connections.
*/
class ConnectionServer : public QObject
{
Q_OBJECT
public:
explicit ConnectionServer(QObject *parent = nullptr);
~ConnectionServer() override;
/**
* Sets this connection to listen mode. Use address() to obtain the
* address this is listening on.
*/
void listenForRemote();
bool isListening() const;
/**
* Returns the address for this connection if it is listening, an empty
* address if not.
*/
QUrl address() const;
void setNextPendingConnection(Connection *conn);
Q_SIGNALS:
void newConnection();
private:
ConnectionBackend *backend = nullptr;
};
} // namespace KIO
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,380 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_COPYJOB_H
#define KIO_COPYJOB_H
#include <QObject>
#include <QStringList>
#include <QUrl>
#include "job_base.h"
#include "kiocore_export.h"
class QDateTime;
namespace KIO
{
class CopyJobPrivate;
/**
* @class KIO::CopyJob copyjob.h <KIO/CopyJob>
*
* CopyJob is used to move, copy or symlink files and directories.
* Don't create the job directly, but use KIO::copy(),
* KIO::move(), KIO::link() and friends.
*
* @see KIO::copy()
* @see KIO::copyAs()
* @see KIO::move()
* @see KIO::moveAs()
* @see KIO::link()
* @see KIO::linkAs()
*/
class KIOCORE_EXPORT CopyJob : public Job
{
Q_OBJECT
public:
/**
* Defines the mode of the operation
*/
enum CopyMode {
Copy,
Move,
Link
};
~CopyJob() override;
/**
* Returns the mode of the operation (copy, move, or link),
* depending on whether KIO::copy(), KIO::move() or KIO::link() was called.
*/
CopyMode operationMode() const;
/**
* Returns the list of source URLs.
* @return the list of source URLs.
*/
QList<QUrl> srcUrls() const;
/**
* Returns the destination URL.
* @return the destination URL
*/
QUrl destUrl() const;
/**
* By default the permissions of the copied files will be those of the source files.
*
* But when copying "template" files to "new" files, people prefer the umask
* to apply, rather than the template's permissions.
* For that case, call setDefaultPermissions(true)
*/
void setDefaultPermissions(bool b);
/**
* Skip copying or moving any file when the destination already exists,
* instead of the default behavior (interactive mode: showing a dialog to the user,
* non-interactive mode: aborting with an error).
* Initially added for a unit test.
* \since 4.2
*/
void setAutoSkip(bool autoSkip);
/**
* Rename files automatically when the destination already exists,
* instead of the default behavior (interactive mode: showing a dialog to the user,
* non-interactive mode: aborting with an error).
* Initially added for a unit test.
* \since 4.7
*/
void setAutoRename(bool autoRename);
/**
* Reuse any directory that already exists, instead of the default behavior
* (interactive mode: showing a dialog to the user,
* non-interactive mode: aborting with an error).
* \since 4.2
*/
void setWriteIntoExistingDirectories(bool overwriteAllDirs);
/**
* Reimplemented for internal reasons
*/
bool doSuspend() override;
/**
* Reimplemented for internal reasons
*/
bool doResume() override;
Q_SIGNALS:
/**
* Sends the number of processed files.
* @param job the job that emitted this signal
* @param files the number of processed files
*/
void processedFiles(KIO::Job *job, unsigned long files);
/**
* Sends the number of processed directories.
* @param job the job that emitted this signal
* @param dirs the number of processed dirs
*/
void processedDirs(KIO::Job *job, unsigned long dirs);
/**
* The job is copying a file or directory.
*
* Note: This signal is used for progress dialogs, it's not emitted for
* every file or directory (this would be too slow), but every 200ms.
*
* @param job the job that emitted this signal
* @param src the URL of the file or directory that is currently
* being copied
* @param dest the destination of the current operation
*/
void copying(KIO::Job *job, const QUrl &src, const QUrl &dest);
/**
* The job is creating a symbolic link.
*
* Note: This signal is used for progress dialogs, it's not emitted for
* every file or directory (this would be too slow), but every 200ms.
*
* @param job the job that emitted this signal
* @param target the URL of the file or directory that is currently
* being linked
* @param to the destination of the current operation
*/
void linking(KIO::Job *job, const QString &target, const QUrl &to);
/**
* The job is moving a file or directory.
*
* Note: This signal is used for progress dialogs, it's not emitted for
* every file or directory (this would be too slow), but every 200ms.
*
* @param job the job that emitted this signal
* @param from the URL of the file or directory that is currently
* being moved
* @param to the destination of the current operation
*/
void moving(KIO::Job *job, const QUrl &from, const QUrl &to);
/**
* The job is creating the directory @p dir.
*
* This signal is emitted for every directory being created.
*
* @param job the job that emitted this signal
* @param dir the directory that is currently being created
*/
void creatingDir(KIO::Job *job, const QUrl &dir);
/**
* The user chose to rename @p from to @p to.
*
* @param job the job that emitted this signal
* @param from the original name
* @param to the new name
*/
void renamed(KIO::Job *job, const QUrl &from, const QUrl &to);
/**
* The job emits this signal when copying or moving a file or directory successfully finished.
* This signal is mainly for the Undo feature.
* If you simply want to know when a copy job is done, use result().
*
* @param job the job that emitted this signal
* @param from the source URL
* @param to the destination URL
* @param mtime the modification time of the source file, hopefully set on the destination file
* too (when the KIO worker supports it).
* @param directory indicates whether a file or directory was successfully copied/moved.
* true for a directory, false for file
* @param renamed indicates that the destination URL was created using a
* rename operation (i.e. fast directory moving). true if is has been renamed
*/
void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed);
/**
* The job is copying or moving a symbolic link, that points to target.
* The new link is created in @p to. The existing one is/was in @p from.
* This signal is mainly for the Undo feature.
* @param job the job that emitted this signal
* @param from the source URL
* @param target the target
* @param to the destination URL
*/
void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to);
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOCORE_NO_EXPORT explicit CopyJob(CopyJobPrivate &dd);
void emitResult();
private:
Q_DECLARE_PRIVATE(CopyJob)
};
/**
* Copy a file or directory @p src into the destination @p dest,
* which can be a file (including the final filename) or a directory
* (into which @p src will be copied).
*
* This emulates the cp command completely.
*
* @param src the file or directory to copy
* @param dest the destination
* @param flags copy() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files". However if "dest" exists, then src is copied
* into a subdir of dest, just like "cp" does. Use copyAs if you don't want that.
*
* @return the job handling the operation
* @see copyAs()
*/
KIOCORE_EXPORT CopyJob *copy(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Copy a file or directory @p src into the destination @p dest,
* which is the destination name in any case, even for a directory.
*
* As opposed to copy(), this doesn't emulate cp, but is the only
* way to copy a directory, giving it a new name and getting an error
* box if a directory already exists with the same name (or writing the
* contents of @p src into @p dest, when using Overwrite).
*
* @param src the file or directory to copy
* @param dest the destination
* @param flags copyAs() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files".
*
* * @return the job handling the operation
*/
KIOCORE_EXPORT CopyJob *copyAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Copy a list of file/dirs @p src into a destination directory @p dest.
*
* @param src the list of files and/or directories
* @param dest the destination
* @param flags copy() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files". However if "dest" exists, then src is copied
* into a subdir of dest, just like "cp" does.
* @return the job handling the operation
*/
KIOCORE_EXPORT CopyJob *copy(const QList<QUrl> &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Moves a file or directory @p src to the given destination @p dest.
*
* @param src the file or directory to copy
* @param dest the destination
* @param flags move() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files". However if "dest" exists, then src is copied
* into a subdir of dest, just like "cp" does.
* @return the job handling the operation
* @see copy()
* @see moveAs()
*/
KIOCORE_EXPORT CopyJob *move(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Moves a file or directory @p src to the given destination @p dest. Unlike move()
* this operation will not move @p src into @p dest when @p dest exists: it will
* either fail, or move the contents of @p src into it if Overwrite is set.
*
* @param src the file or directory to copy
* @param dest the destination
* @param flags moveAs() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files".
* @return the job handling the operation
* @see copyAs()
*/
KIOCORE_EXPORT CopyJob *moveAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Moves a list of files or directories @p src to the given destination @p dest.
*
* @param src the list of files or directories to copy
* @param dest the destination
* @param flags move() supports HideProgressInfo and Overwrite.
* Note: Overwrite has the meaning of both "write into existing directories" and
* "overwrite existing files". However if "dest" exists, then src is copied
* into a subdir of dest, just like "cp" does.
* @return the job handling the operation
* @see copy()
*/
KIOCORE_EXPORT CopyJob *move(const QList<QUrl> &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Create a link.
* If the protocols and hosts are the same, a Unix symlink will be created.
* Otherwise, a .desktop file of Type Link and pointing to the src URL will be created.
*
* @param src The existing file or directory, 'target' of the link.
* @param destDir Destination directory where the link will be created.
* @param flags link() supports HideProgressInfo only
* @return the job handling the operation
*/
KIOCORE_EXPORT CopyJob *link(const QUrl &src, const QUrl &destDir, JobFlags flags = DefaultFlags);
/**
* Create several links
* If the protocols and hosts are the same, a Unix symlink will be created.
* Otherwise, a .desktop file of Type Link and pointing to the src URL will be created.
*
* @param src The existing files or directories, 'targets' of the link.
* @param destDir Destination directory where the links will be created.
* @param flags link() supports HideProgressInfo only
* @return the job handling the operation
* @see link()
*/
KIOCORE_EXPORT CopyJob *link(const QList<QUrl> &src, const QUrl &destDir, JobFlags flags = DefaultFlags);
/**
* Create a link. Unlike link() this operation will fail when @p dest is an existing
* directory rather than the final name for the link.
* If the protocols and hosts are the same, a Unix symlink will be created.
* Otherwise, a .desktop file of Type Link and pointing to the src URL will be created.
*
* @param src The existing file or directory, 'target' of the link.
* @param dest Destination (i.e. the final symlink)
* @param flags linkAs() supports HideProgressInfo only
* @return the job handling the operation
* @see link ()
* @see copyAs()
*/
KIOCORE_EXPORT CopyJob *linkAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags);
/**
* Trash a file or directory.
* This is currently only supported for local files and directories.
* Use QUrl::fromLocalFile to create a URL from a local file path.
*
* @param src file to delete
* @param flags trash() supports HideProgressInfo only
* @return the job handling the operation
*/
KIOCORE_EXPORT CopyJob *trash(const QUrl &src, JobFlags flags = DefaultFlags);
/**
* Trash a list of files or directories.
* This is currently only supported for local files and directories.
*
* @param src the files to delete
* @param flags trash() supports HideProgressInfo only
* @return the job handling the operation
*/
KIOCORE_EXPORT CopyJob *trash(const QList<QUrl> &src, JobFlags flags = DefaultFlags);
}
#endif
@@ -0,0 +1,270 @@
/*
Implementation of the data protocol (rfc 2397)
SPDX-FileCopyrightText: 2002, 2003 Leo Savernik <l.savernik@aon.at>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "dataprotocol_p.h"
#include "global.h"
#include "metadata.h"
#include <QByteArray>
#include <QStringDecoder>
using namespace KIO;
/** structure containing header information */
struct DataHeader {
QString mime_type; // MIME type of content (lowercase)
MetaData attributes; // attribute/value pairs (attribute lowercase,
// value unchanged)
bool is_base64; // true if data is base64 encoded
QByteArray url; // reference to decoded url
int data_offset; // zero-indexed position within url
// where the real data begins. May point beyond
// the end to indicate that there is no data
};
/** returns the position of the first occurrence of any of the given
* characters @p c1 or comma (',') or semicolon (';') or buf.length()
* if none is contained.
*
* @param buf buffer where to look for c
* @param begin zero-indexed starting position
* @param c1 character to find or '\0' to ignore
*/
static int find(const QByteArray &buf, int begin, const char c1)
{
static const char comma = ',';
static const char semicolon = ';';
int pos = begin;
int size = buf.length();
while (pos < size) {
const char ch = buf[pos];
if (ch == comma || ch == semicolon || (c1 != '\0' && ch == c1)) {
break;
}
pos++;
} /*wend*/
return pos;
}
/** extracts the string between the current position @p pos and the first
* occurrence of either @p c1 or comma (',') or semicolon (';') exclusively
* and updates @p pos to point at the found delimiter or at the end of the
* buffer if neither character occurred.
* @param buf buffer where to look for
* @param pos zero-indexed position within buffer
* @param c1 character to find or '\0' to ignore
*/
static inline QString extract(const QByteArray &buf, int &pos, const char c1 = '\0')
{
int oldpos = pos;
pos = find(buf, oldpos, c1);
return QString::fromLatin1(buf.mid(oldpos, pos - oldpos));
}
/** ignores all whitespaces
* @param buf buffer to operate on
* @param pos position to shift to first non-whitespace character
* Upon return @p pos will either point to the first non-whitespace
* character or to the end of the buffer.
*/
static inline void ignoreWS(const QByteArray &buf, int &pos)
{
int size = buf.length();
while (pos < size && (buf[pos] == ' ' || buf[pos] == '\t')) {
++pos;
}
}
/** parses a quoted string as per rfc 822.
*
* If trailing quote is missing, the whole rest of the buffer is returned.
* @param buf buffer to operate on
* @param pos position pointing to the leading quote
* @return the extracted string. @p pos will be updated to point to the
* character following the trailing quote.
*/
static QString parseQuotedString(const QByteArray &buf, int &pos)
{
int size = buf.length();
QString res;
res.reserve(size); // can't be larger than buf
pos++; // jump over leading quote
bool escaped = false; // if true means next character is literal
bool parsing = true; // true as long as end quote not found
while (parsing && pos < size) {
const QChar ch = QLatin1Char(buf[pos++]);
if (escaped) {
res += ch;
escaped = false;
} else {
switch (ch.unicode()) {
case '"':
parsing = false;
break;
case '\\':
escaped = true;
break;
default:
res += ch;
break;
} /*end switch*/
} /*end if*/
} /*wend*/
res.squeeze();
return res;
}
/** parses the header of a data url
* @param url the data url
* @param mimeOnly if the only interesting information is the MIME type
* @return DataHeader structure with the header information
*/
static DataHeader parseDataHeader(const QUrl &url, const bool mimeOnly)
{
DataHeader header_info;
// initialize header info members
header_info.mime_type = QStringLiteral("text/plain");
header_info.attributes.insert(QStringLiteral("charset"), QStringLiteral("us-ascii"));
header_info.is_base64 = false;
// decode url and save it
const QByteArray &raw_url = header_info.url = QByteArray::fromPercentEncoding(url.path(QUrl::FullyEncoded).toLatin1());
const int raw_url_len = raw_url.length();
header_info.data_offset = 0;
// read MIME type
if (raw_url_len == 0) {
return header_info;
}
const QString mime_type = extract(raw_url, header_info.data_offset).trimmed();
if (!mime_type.isEmpty()) {
header_info.mime_type = mime_type;
}
if (mimeOnly) {
return header_info;
}
if (header_info.data_offset >= raw_url_len) {
return header_info;
}
// jump over delimiter token and return if data reached
if (raw_url[header_info.data_offset++] == ',') {
return header_info;
}
// read all attributes and store them
bool data_begin_reached = false;
while (!data_begin_reached && header_info.data_offset < raw_url_len) {
// read attribute
const QString attribute = extract(raw_url, header_info.data_offset, '=').trimmed();
if (header_info.data_offset >= raw_url_len || raw_url[header_info.data_offset] != '=') {
// no assignment, must be base64 option
if (attribute == QLatin1String("base64")) {
header_info.is_base64 = true;
}
} else {
header_info.data_offset++; // jump over '=' token
// read value
ignoreWS(raw_url, header_info.data_offset);
if (header_info.data_offset >= raw_url_len) {
return header_info;
}
QString value;
if (raw_url[header_info.data_offset] == '"') {
value = parseQuotedString(raw_url, header_info.data_offset);
ignoreWS(raw_url, header_info.data_offset);
} else {
value = extract(raw_url, header_info.data_offset).trimmed();
}
// add attribute to map
header_info.attributes[attribute.toLower()] = value;
} /*end if*/
if (header_info.data_offset < raw_url_len && raw_url[header_info.data_offset] == ',') {
data_begin_reached = true;
}
header_info.data_offset++; // jump over separator token
} /*wend*/
return header_info;
}
DataProtocol::DataProtocol()
{
}
DataProtocol::~DataProtocol() = default;
void DataProtocol::get(const QUrl &url)
{
ref();
// qDebug() << this;
const DataHeader hdr = parseDataHeader(url, false);
const int size = hdr.url.length();
const int data_ofs = qMin(hdr.data_offset, size);
// FIXME: string is copied, would be nice if we could have a reference only
const QByteArray url_data = hdr.url.mid(data_ofs);
QByteArray outData;
if (hdr.is_base64) {
// base64 stuff is expected to contain the correct charset, so we just
// decode it and pass it to the receiver
outData = QByteArray::fromBase64(url_data);
} else {
QStringDecoder codec(hdr.attributes[QStringLiteral("charset")].toLatin1().constData());
if (codec.isValid()) {
outData = QString(codec.decode(url_data)).toUtf8();
} else {
outData = url_data;
} /*end if*/
} /*end if*/
// qDebug() << "emit mimeType@"<<this;
Q_EMIT mimeType(hdr.mime_type);
// qDebug() << "emit totalSize@"<<this;
Q_EMIT totalSize(outData.size());
// qDebug() << "emit setMetaData@"<<this;
setAllMetaData(hdr.attributes);
// qDebug() << "emit sendMetaData@"<<this;
sendMetaData();
// qDebug() << "(1) queue size " << dispatchQueue.size();
// empiric studies have shown that this shouldn't be queued & dispatched
Q_EMIT data(outData);
// qDebug() << "(2) queue size " << dispatchQueue.size();
dispatch_data(QByteArray{});
// qDebug() << "(3) queue size " << dispatchQueue.size();
dispatch_finished();
// qDebug() << "(4) queue size " << dispatchQueue.size();
deref();
}
/* --------------------------------------------------------------------- */
void DataProtocol::mimetype(const QUrl &url)
{
ref();
Q_EMIT mimeType(parseDataHeader(url, true).mime_type);
Q_EMIT finished();
deref();
}
/* --------------------------------------------------------------------- */
#if !defined(TESTKIO)
#include "moc_dataprotocol_p.cpp"
#endif
@@ -0,0 +1,65 @@
/*
Interface of the KDE data protocol core operations
SPDX-FileCopyrightText: 2002 Leo Savernik <l.savernik@aon.at>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef DATAPROTOCOL_H
#define DATAPROTOCOL_H
// dataprotocol.* interprets the following defines
// TESTKIO: define for test-driving
// Both defines are mutually exclusive. Defining none of them compiles
// DataProtocol for internal usage within libkiocore.
/* Wondering what this is all about? Leo explained it to me:
*
* That's simple, you can compile it into a standalone executable that is
* registered like any other KIO worker.
*
* However, given that data-urls don't depend on any external data it seemed
* overkill, therefore I added a special hack that the kio-dataworker is invoked
* in-process on the client side.
*/
class QByteArray;
class QUrl;
#if !defined(TESTKIO)
#include "dataworker_p.h"
#endif
namespace KIO
{
/** This KIO worker provides support of data urls as specified by rfc 2397
* @see https://www.ietf.org/rfc/rfc2397.txt
* @author Leo Savernik
*/
#if defined(TESTKIO)
class DataProtocol : public TestWorker
{
#else
class DataProtocol : public DataWorker
{
Q_OBJECT
#endif
public:
DataProtocol();
#if defined(TESTKIO)
void mimetype(const QUrl &url);
void get(const QUrl &url);
#else
void mimetype(const QUrl &url) override;
void get(const QUrl &url) override;
#endif
~DataProtocol() override;
};
} /*end namespace*/
#endif
@@ -0,0 +1,192 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Derived from worker.cpp
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "commands_p.h"
#include "dataprotocol_p.h"
#include "dataworker_p.h"
#include "workerbase.h"
#include <KLocalizedString>
#include <QDataStream>
#include <QTimer>
using namespace KIO;
static constexpr int s_kioDataPollInterval = 0;
// don't forget to sync DISPATCH_DECL in dataworker_p.h
/* clang-format off */
#define DISPATCH_IMPL(type) \
void DataWorker::dispatch_##type() \
{ \
if (_suspended) { \
QueueStruct q(Queue_##type); \
q.size = -1; \
dispatchQueue.push_back(q); \
if (!timer->isActive()) { \
timer->start(s_kioDataPollInterval); \
} \
} else \
Q_EMIT type(); \
}
// don't forget to sync DISPATCH_DECL1 in dataworker_p.h
#define DISPATCH_IMPL1(type, paramtype, paramname) \
void DataWorker::dispatch_##type(paramtype paramname) \
{ \
if (_suspended) { \
QueueStruct q(Queue_##type); \
q.paramname = paramname; \
dispatchQueue.push_back(q); \
if (!timer->isActive()) { \
timer->start(s_kioDataPollInterval); \
} \
} else \
Q_EMIT type(paramname); \
}
DataWorker::DataWorker()
: Worker(QStringLiteral("data"))
{
// qDebug() << this;
_suspended = false;
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &DataWorker::dispatchNext);
}
DataWorker::~DataWorker()
{
// qDebug() << this;
}
void DataWorker::suspend()
{
_suspended = true;
// qDebug() << this;
timer->stop();
}
void DataWorker::resume()
{
_suspended = false;
// qDebug() << this;
// aarrrgh! This makes the once hyper fast and efficient data protocol
// implementation slow as molasses. But it wouldn't work otherwise,
// and I don't want to start messing around with threads
timer->start(s_kioDataPollInterval);
}
// finished is a special case. If we emit it right away, then
// TransferJob::start can delete the job even before the end of the method
void DataWorker::dispatch_finished()
{
QueueStruct q(Queue_finished);
q.size = -1;
dispatchQueue.push_back(q);
if (!timer->isActive()) {
timer->start(s_kioDataPollInterval);
}
}
void DataWorker::dispatchNext()
{
if (dispatchQueue.empty()) {
timer->stop();
return;
}
const QueueStruct &q = dispatchQueue.front();
// qDebug() << this << "dispatching" << q.type << dispatchQueue.size() << "left";
switch (q.type) {
case Queue_mimeType:
Q_EMIT mimeType(q.s);
break;
case Queue_totalSize:
Q_EMIT totalSize(q.size);
break;
case Queue_sendMetaData:
sendMetaData();
break;
case Queue_data:
Q_EMIT data(q.ba);
break;
case Queue_finished:
Q_EMIT finished();
break;
} /*end switch*/
dispatchQueue.pop_front();
}
void DataWorker::send(int cmd, const QByteArray &arr)
{
QDataStream stream(arr);
QUrl url;
switch (cmd) {
case CMD_GET: {
stream >> url;
get(url);
break;
}
case CMD_MIMETYPE: {
stream >> url;
mimetype(url);
break;
}
// ignore these (must not emit error, otherwise SIGSEGV occurs)
case CMD_REPARSECONFIGURATION:
case CMD_META_DATA:
break;
default:
Q_EMIT error(ERR_UNSUPPORTED_ACTION, unsupportedActionErrorString(QStringLiteral("data"), cmd));
} /*end switch*/
}
bool DataWorker::suspended()
{
return _suspended;
}
void DataWorker::setHost(const QString & /*host*/, quint16 /*port*/, const QString & /*user*/, const QString & /*passwd*/)
{
// irrelevant -> will be ignored
}
void DataWorker::setConfig(const MetaData & /*config*/)
{
// FIXME: decide to handle this directly or not at all
#if 0
QByteArray data;
QDataStream stream(data, QIODevice::WriteOnly);
stream << config;
slaveconn.send(CMD_CONFIG, data);
#endif
}
void DataWorker::setAllMetaData(const MetaData &md)
{
meta_data = md;
}
void DataWorker::sendMetaData()
{
Q_EMIT metaData(meta_data);
}
DISPATCH_IMPL1(mimeType, const QString &, s)
DISPATCH_IMPL1(totalSize, KIO::filesize_t, size)
DISPATCH_IMPL(sendMetaData)
DISPATCH_IMPL1(data, const QByteArray &, ba)
#undef DISPATCH_IMPL
#undef DISPATCH_IMPL1
#include "moc_dataworker_p.cpp"
@@ -0,0 +1,119 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Derived from worker_p.h
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_DATAWORKER_P_H
#define KIO_DATAWORKER_P_H
#include "global.h"
#include "worker_p.h"
class QTimer;
// don't forget to sync DISPATCH_IMPL in dataworker.cpp
#define DISPATCH_DECL(type) void dispatch_##type();
// don't forget to sync DISPATCH_IMPL1 in dataworker.cpp
#define DISPATCH_DECL1(type, paramtype, param) void dispatch_##type(paramtype param);
namespace KIO
{
/**
* This class provides a high performance implementation for the data
* url scheme (rfc2397).
*
* @internal
* Do not use this class in external applications. It is an implementation
* detail of KIO and subject to change without notice.
* @author Leo Savernik
*/
class DataWorker : public KIO::Worker
{
Q_OBJECT
public:
DataWorker();
~DataWorker() override;
void setHost(const QString &host, quint16 port, const QString &user, const QString &passwd) override;
void setConfig(const MetaData &config) override;
void suspend() override;
void resume() override;
bool suspended() override;
void send(int cmd, const QByteArray &arr = QByteArray()) override;
// pure virtual methods that are defined by the actual protocol
virtual void get(const QUrl &url) = 0;
virtual void mimetype(const QUrl &url) = 0;
protected:
/**
* Sets metadata
* @internal
*/
void setAllMetaData(const MetaData &);
/**
* Sends metadata set with setAllMetaData
* @internal
*/
void sendMetaData();
// queuing methods
/** identifiers of functions to be queued */
enum QueueType {
Queue_mimeType = 1,
Queue_totalSize,
Queue_sendMetaData,
Queue_data,
Queue_finished,
};
/** structure for queuing. It is very primitive, it doesn't
* even try to conserve memory.
*/
struct QueueStruct {
QueueType type;
QString s;
KIO::filesize_t size;
QByteArray ba;
QueueStruct()
{
}
QueueStruct(QueueType type)
: type(type)
{
}
};
typedef QList<QueueStruct> DispatchQueue;
DispatchQueue dispatchQueue;
DISPATCH_DECL1(mimeType, const QString &, s)
DISPATCH_DECL1(totalSize, KIO::filesize_t, size)
DISPATCH_DECL(sendMetaData)
DISPATCH_DECL1(data, const QByteArray &, ba)
DISPATCH_DECL(finished)
protected Q_SLOTS:
/** dispatches next queued method. Does nothing if there are no
* queued methods.
*/
void dispatchNext();
private:
MetaData meta_data;
bool _suspended;
QTimer *timer;
};
}
#undef DISPATCH_DECL
#undef DISPATCH_DECL1
#endif
@@ -0,0 +1,134 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Jan-Pascal van Best <janpascal@vanbest.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "davjob.h"
#include <QDataStream>
#include <QDomDocument>
#include "httpmethod_p.h"
#include "job_p.h"
using namespace KIO;
/** @internal */
class KIO::DavJobPrivate : public KIO::TransferJobPrivate
{
public:
explicit DavJobPrivate(const QUrl &url)
: TransferJobPrivate(url, KIO::CMD_SPECIAL, QByteArray(), QByteArray())
{
}
QByteArray savedStaticData;
QByteArray str_response;
Q_DECLARE_PUBLIC(DavJob)
static inline DavJob *newJob(const QUrl &url, int method, const QString &request, JobFlags flags)
{
DavJob *job = new DavJob(*new DavJobPrivate(url), method, request);
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
return job;
}
};
DavJob::DavJob(DavJobPrivate &dd, int method, const QString &request)
: TransferJob(dd)
{
// We couldn't set the args when calling the parent constructor,
// so do it now.
Q_D(DavJob);
QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
stream << (int)7 << d->m_url << method;
// Same for static data
if (!request.isEmpty()) {
d->staticData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + request.toUtf8();
d->staticData.chop(1);
d->savedStaticData = d->staticData;
stream << static_cast<qint64>(d->staticData.size());
} else {
stream << static_cast<qint64>(-1);
}
}
QByteArray DavJob::responseData() const
{
return d_func()->str_response;
}
void DavJob::slotData(const QByteArray &data)
{
Q_D(DavJob);
if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) {
unsigned int oldSize = d->str_response.size();
d->str_response.resize(oldSize + data.size());
memcpy(d->str_response.data() + oldSize, data.data(), data.size());
}
}
void DavJob::slotFinished()
{
Q_D(DavJob);
// qDebug() << d->str_response;
if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid() && (d->m_command == CMD_SPECIAL)) {
QDataStream istream(d->m_packedArgs);
int s_cmd;
int s_method;
qint64 s_size;
QUrl s_url;
istream >> s_cmd;
istream >> s_url;
istream >> s_method;
istream >> s_size;
// PROPFIND
if ((s_cmd == 7) && (s_method == (int)KIO::DAV_PROPFIND)) {
d->m_packedArgs.truncate(0);
QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
stream << (int)7 << d->m_redirectionURL << (int)KIO::DAV_PROPFIND << s_size;
}
}
TransferJob::slotFinished();
d->staticData = d->savedStaticData; // Need to send DAV request to this host too
}
/* Convenience methods */
DavJob *KIO::davPropFind(const QUrl &url, const QString &properties, const QString &depth, JobFlags flags)
{
DavJob *job = DavJobPrivate::newJob(url, (int)KIO::DAV_PROPFIND, properties, flags);
job->addMetaData(QStringLiteral("davDepth"), depth);
return job;
}
DavJob *KIO::davPropPatch(const QUrl &url, const QString &properties, JobFlags flags)
{
return DavJobPrivate::newJob(url, (int)KIO::DAV_PROPPATCH, properties, flags);
}
DavJob *KIO::davSearch(const QUrl &url, const QString &nsURI, const QString &qName, const QString &query, JobFlags flags)
{
QDomDocument doc;
QDomElement searchrequest = doc.createElementNS(QStringLiteral("DAV:"), QStringLiteral("searchrequest"));
QDomElement searchelement = doc.createElementNS(nsURI, qName);
QDomText text = doc.createTextNode(query);
searchelement.appendChild(text);
searchrequest.appendChild(searchelement);
doc.appendChild(searchrequest);
return DavJobPrivate::newJob(url, KIO::DAV_SEARCH, doc.toString(), flags);
}
DavJob *KIO::davReport(const QUrl &url, const QString &report, const QString &depth, JobFlags flags)
{
DavJob *job = DavJobPrivate::newJob(url, (int)KIO::DAV_REPORT, report, flags);
job->addMetaData(QStringLiteral("davDepth"), depth);
return job;
}
#include "moc_davjob.cpp"
@@ -0,0 +1,112 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Jan-Pascal van Best <janpascal@vanbest.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_DAVJOB_H
#define KIO_DAVJOB_H
#include "global.h"
#include "kiocore_export.h"
#include "transferjob.h"
#include <QObject>
#include <QPointer>
#include <QString>
#include <QStringList>
#include <sys/stat.h>
#include <sys/types.h>
namespace KIO
{
class DavJobPrivate;
/**
* @class KIO::DavJob davjob.h <KIO/DavJob>
*
* The transfer job pumps data into and/or out of a KIO worker.
* Data is sent to the worker on request of the worker ( dataReq).
* If data coming from the worker can not be handled, the
* reading of data from the worker should be suspended.
* @see KIO::davPropFind()
* @see KIO::davPropPatch()
* @see KIO::davSearch()
*/
class KIOCORE_EXPORT DavJob : public TransferJob
{
Q_OBJECT
public:
/**
* Returns the reponse data.
* @since 5.86
*/
QByteArray responseData() const;
protected Q_SLOTS:
void slotFinished() override;
void slotData(const QByteArray &data) override;
protected:
KIOCORE_NO_EXPORT DavJob(DavJobPrivate &dd, int, const QString &);
private:
Q_DECLARE_PRIVATE(DavJob)
};
/**
* Creates a new DavJob that issues a PROPFIND command. PROPFIND retrieves
* the properties of the resource identified by the given @p url.
*
* @param url the URL of the resource
* @param properties a propfind document that describes the properties that
* should be retrieved
* @param depth the depth of the request. Can be "0", "1" or "infinity"
* @param flags We support HideProgressInfo here
* @return the new DavJob
* @since 5.84
*/
KIOCORE_EXPORT DavJob *davPropFind(const QUrl &url, const QString &properties, const QString &depth, JobFlags flags = DefaultFlags);
/**
* Creates a new DavJob that issues a PROPPATCH command. PROPPATCH sets
* the properties of the resource identified by the given @p url.
*
* @param url the URL of the resource
* @param properties a PROPPACTCH document that describes the properties that
* should be modified and its new values
* @param flags We support HideProgressInfo here
* @return the new DavJob
* @since 5.84
*/
KIOCORE_EXPORT DavJob *davPropPatch(const QUrl &url, const QString &properties, JobFlags flags = DefaultFlags);
/**
* Creates a new DavJob that issues a SEARCH command.
*
* @param url the URL of the resource
* @param nsURI the URI of the search method's qualified name
* @param qName the local part of the search method's qualified name
* @param query the search string
* @param flags We support HideProgressInfo here
* @return the new DavJob
*/
KIOCORE_EXPORT DavJob *davSearch(const QUrl &url, const QString &nsURI, const QString &qName, const QString &query, JobFlags flags = DefaultFlags);
/**
* Creates a new DavJob that issues a REPORT command.
*
* @param url the URL of the resource
* @param report a REPORT document that describes the request to make
* @param depth the depth of the request. Can be "0", "1" or "infinity"
* @param flags We support HideProgressInfo here
* @return the new DavJob
*/
KIOCORE_EXPORT DavJob *davReport(const QUrl &url, const QString &report, const QString &depth, JobFlags flags = DefaultFlags);
} // namespace KIO
#endif
@@ -0,0 +1,618 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "deletejob.h"
#include "../utils_p.h"
#include "job.h" // buildErrorString
#include "kcoredirlister.h"
#include "kprotocolmanager.h"
#include "listjob.h"
#include "statjob.h"
#include <KDirWatch>
#include <kdirnotify.h>
#include <KLocalizedString>
#include <kio/jobuidelegatefactory.h>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMetaObject>
#include <QPointer>
#include <QThread>
#include <QTimer>
#include "job_p.h"
extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol.
static bool isHttpProtocol(const QString &protocol)
{
return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) || protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive));
}
namespace KIO
{
enum DeleteJobState {
DELETEJOB_STATE_STATING,
DELETEJOB_STATE_DELETING_FILES,
DELETEJOB_STATE_DELETING_DIRS,
};
class DeleteJobIOWorker : public QObject
{
Q_OBJECT
Q_SIGNALS:
void rmfileResult(bool succeeded, bool isLink);
void rmddirResult(bool succeeded);
public Q_SLOTS:
/**
* Deletes the file @p url points to
* The file must be a LocalFile
*/
void rmfile(const QUrl &url, bool isLink)
{
Q_EMIT rmfileResult(QFile::remove(url.toLocalFile()), isLink);
}
/**
* Deletes the directory @p url points to
* The directory must be a LocalFile
*/
void rmdir(const QUrl &url)
{
Q_EMIT rmddirResult(QDir().rmdir(url.toLocalFile()));
}
};
class DeleteJobPrivate : public KIO::JobPrivate
{
public:
explicit DeleteJobPrivate(const QList<QUrl> &src)
: state(DELETEJOB_STATE_STATING)
, m_processedFiles(0)
, m_processedDirs(0)
, m_totalFilesDirs(0)
, m_srcList(src)
, m_currentStat(m_srcList.begin())
, m_reportTimer(nullptr)
{
}
DeleteJobState state;
int m_processedFiles;
int m_processedDirs;
int m_totalFilesDirs;
QUrl m_currentURL;
QList<QUrl> files;
QList<QUrl> symlinks;
QList<QUrl> dirs;
QList<QUrl> m_srcList;
QList<QUrl>::iterator m_currentStat;
QSet<QString> m_parentDirs;
QTimer *m_reportTimer;
DeleteJobIOWorker *m_ioworker = nullptr;
QThread *m_thread = nullptr;
void statNextSrc();
DeleteJobIOWorker *worker();
void currentSourceStated(bool isDir, bool isLink);
void finishedStatPhase();
void deleteNextFile();
void deleteNextDir();
void restoreDirWatch() const;
void slotReport();
void slotStart();
void slotEntries(KIO::Job *, const KIO::UDSEntryList &list);
/// Callback of worker rmfile
void rmFileResult(bool result, bool isLink);
/// Callback of worker rmdir
void rmdirResult(bool result);
void deleteFileUsingJob(const QUrl &url, bool isLink);
void deleteDirUsingJob(const QUrl &url);
~DeleteJobPrivate() override;
Q_DECLARE_PUBLIC(DeleteJob)
static inline DeleteJob *newJob(const QList<QUrl> &src, JobFlags flags)
{
DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = Delete;
}
return job;
}
};
} // namespace KIO
using namespace KIO;
DeleteJob::DeleteJob(DeleteJobPrivate &dd)
: Job(dd)
{
Q_D(DeleteJob);
d->m_reportTimer = new QTimer(this);
connect(d->m_reportTimer, &QTimer::timeout, this, [d]() {
d->slotReport();
});
// this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
d->m_reportTimer->start(200);
QTimer::singleShot(0, this, [d]() {
d->slotStart();
});
}
DeleteJob::~DeleteJob()
{
}
DeleteJobPrivate::~DeleteJobPrivate()
{
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
}
}
QList<QUrl> DeleteJob::urls() const
{
return d_func()->m_srcList;
}
void DeleteJobPrivate::slotStart()
{
statNextSrc();
}
DeleteJobIOWorker *DeleteJobPrivate::worker()
{
Q_Q(DeleteJob);
if (!m_ioworker) {
m_thread = new QThread();
m_ioworker = new DeleteJobIOWorker;
m_ioworker->moveToThread(m_thread);
QObject::connect(m_thread, &QThread::finished, m_ioworker, &QObject::deleteLater);
QObject::connect(m_ioworker, &DeleteJobIOWorker::rmfileResult, q, [=, this](bool result, bool isLink) {
this->rmFileResult(result, isLink);
});
QObject::connect(m_ioworker, &DeleteJobIOWorker::rmddirResult, q, [=, this](bool result) {
this->rmdirResult(result);
});
m_thread->start();
}
return m_ioworker;
}
void DeleteJobPrivate::slotReport()
{
Q_Q(DeleteJob);
Q_EMIT q->deleting(q, m_currentURL);
// TODO: maybe we could skip everything else when (flags & HideProgressInfo) ?
JobPrivate::emitDeleting(q, m_currentURL);
switch (state) {
case DELETEJOB_STATE_STATING:
q->setTotalAmount(KJob::Files, files.count());
q->setTotalAmount(KJob::Directories, dirs.count());
break;
case DELETEJOB_STATE_DELETING_DIRS:
q->setProcessedAmount(KJob::Directories, m_processedDirs);
q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs);
break;
case DELETEJOB_STATE_DELETING_FILES:
q->setProcessedAmount(KJob::Files, m_processedFiles);
q->emitPercent(m_processedFiles, m_totalFilesDirs);
break;
}
}
void DeleteJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list)
{
UDSEntryList::ConstIterator it = list.begin();
const UDSEntryList::ConstIterator end = list.end();
for (; it != end; ++it) {
const UDSEntry &entry = *it;
const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
Q_ASSERT(!displayName.isEmpty());
if (displayName != QLatin1String("..") && displayName != QLatin1String(".")) {
QUrl url;
const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
if (!urlStr.isEmpty()) {
url = QUrl(urlStr);
} else {
url = static_cast<SimpleJob *>(job)->url(); // assumed to be a dir
url.setPath(Utils::concatPaths(url.path(), displayName));
}
// qDebug() << displayName << "(" << url << ")";
if (entry.isLink()) {
symlinks.append(url);
} else if (entry.isDir()) {
dirs.append(url);
} else {
files.append(url);
}
}
}
}
void DeleteJobPrivate::statNextSrc()
{
Q_Q(DeleteJob);
// qDebug();
if (m_currentStat != m_srcList.end()) {
m_currentURL = (*m_currentStat);
// if the file system doesn't support deleting, we do not even stat
if (!KProtocolManager::supportsDeleting(m_currentURL)) {
QPointer<DeleteJob> that = q;
++m_currentStat;
Q_EMIT q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.toDisplayString()));
if (that) {
statNextSrc();
}
return;
}
// Stat it
state = DELETEJOB_STATE_STATING;
// Fast path for KFileItems in directory views
while (m_currentStat != m_srcList.end()) {
m_currentURL = (*m_currentStat);
const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentURL);
if (cachedItem.isNull()) {
break;
}
// qDebug() << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink();
currentSourceStated(cachedItem.isDir(), cachedItem.isLink());
++m_currentStat;
}
// Hook for unit test to disable the fast path.
if (!kio_resolve_local_urls) {
// Fast path for local files
// (using a loop, instead of a huge recursion)
while (m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) {
m_currentURL = (*m_currentStat);
QFileInfo fileInfo(m_currentURL.toLocalFile());
currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink());
++m_currentStat;
}
}
if (m_currentStat == m_srcList.end()) {
// Done, jump to the last else of this method
statNextSrc();
} else {
KIO::SimpleJob *job = KIO::stat(m_currentURL, StatJob::SourceSide, KIO::StatBasic, KIO::HideProgressInfo);
// qDebug() << "stat'ing" << m_currentURL;
q->addSubjob(job);
}
} else {
if (!q->hasSubjobs()) { // don't go there yet if we're still listing some subdirs
finishedStatPhase();
}
}
}
void DeleteJobPrivate::finishedStatPhase()
{
m_totalFilesDirs = files.count() + symlinks.count() + dirs.count();
slotReport();
// Now we know which dirs hold the files we're going to delete.
// To speed things up and prevent double-notification, we disable KDirWatch
// on those dirs temporarily (using KDirWatch::self, that's the instance
// used by e.g. kdirlister).
for (const QString &dir : std::as_const(m_parentDirs)) {
KDirWatch::self()->stopDirScan(dir);
}
state = DELETEJOB_STATE_DELETING_FILES;
deleteNextFile();
}
void DeleteJobPrivate::rmFileResult(bool result, bool isLink)
{
if (result) {
m_processedFiles++;
if (isLink) {
symlinks.removeFirst();
} else {
files.removeFirst();
}
deleteNextFile();
} else {
// fallback if QFile::remove() failed (we'll use the job's error handling in that case)
deleteFileUsingJob(m_currentURL, isLink);
}
}
void DeleteJobPrivate::deleteFileUsingJob(const QUrl &url, bool isLink)
{
Q_Q(DeleteJob);
SimpleJob *job;
if (isHttpProtocol(url.scheme())) {
job = KIO::http_delete(url, KIO::HideProgressInfo);
} else {
job = KIO::file_delete(url, KIO::HideProgressInfo);
job->setParentJob(q);
}
if (isLink) {
symlinks.removeFirst();
} else {
files.removeFirst();
}
q->addSubjob(job);
}
void DeleteJobPrivate::deleteNextFile()
{
// qDebug();
// if there is something else to delete
// the loop is run using callbacks slotResult and rmFileResult
if (!files.isEmpty() || !symlinks.isEmpty()) {
// Take first file to delete out of list
QList<QUrl>::iterator it = files.begin();
const bool isLink = (it == files.end()); // No more files
if (isLink) {
it = symlinks.begin(); // Pick up a symlink to delete
}
m_currentURL = (*it);
// If local file, try do it directly
if (m_currentURL.isLocalFile()) {
// separate thread will do the work
DeleteJobIOWorker *w = worker();
auto rmfileFunc = [this, w, isLink]() {
w->rmfile(m_currentURL, isLink);
};
QMetaObject::invokeMethod(w, rmfileFunc, Qt::QueuedConnection);
} else {
// if remote, use a job
deleteFileUsingJob(m_currentURL, isLink);
}
return;
}
state = DELETEJOB_STATE_DELETING_DIRS;
deleteNextDir();
}
void DeleteJobPrivate::rmdirResult(bool result)
{
if (result) {
m_processedDirs++;
dirs.removeLast();
deleteNextDir();
} else {
// fallback
deleteDirUsingJob(m_currentURL);
}
}
void DeleteJobPrivate::deleteDirUsingJob(const QUrl &url)
{
Q_Q(DeleteJob);
// Call rmdir - works for KIO workers with canDeleteRecursive too,
// CMD_DEL will trigger the recursive deletion in the worker.
SimpleJob *job = KIO::rmdir(url);
job->setParentJob(q);
job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true"));
dirs.removeLast();
q->addSubjob(job);
}
void DeleteJobPrivate::deleteNextDir()
{
Q_Q(DeleteJob);
if (!dirs.isEmpty()) { // some dirs to delete ?
// the loop is run using callbacks slotResult and rmdirResult
// Take first dir to delete out of list - last ones first !
QList<QUrl>::iterator it = --dirs.end();
m_currentURL = (*it);
// If local dir, try to rmdir it directly
if (m_currentURL.isLocalFile()) {
// delete it on separate worker thread
DeleteJobIOWorker *w = worker();
auto rmdirFunc = [this, w]() {
w->rmdir(m_currentURL);
};
QMetaObject::invokeMethod(w, rmdirFunc, Qt::QueuedConnection);
} else {
deleteDirUsingJob(m_currentURL);
}
return;
}
// Re-enable watching on the dirs that held the deleted files
restoreDirWatch();
// Finished - tell the world
if (!m_srcList.isEmpty()) {
// qDebug() << "KDirNotify'ing FilesRemoved" << m_srcList;
#ifdef WITH_QTDBUS
org::kde::KDirNotify::emitFilesRemoved(m_srcList);
#endif
}
if (m_reportTimer != nullptr) {
m_reportTimer->stop();
}
// display final numbers
q->setProcessedAmount(KJob::Directories, m_processedDirs);
q->setProcessedAmount(KJob::Files, m_processedFiles);
q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs);
q->emitResult();
}
void DeleteJobPrivate::restoreDirWatch() const
{
const auto itEnd = m_parentDirs.constEnd();
for (auto it = m_parentDirs.constBegin(); it != itEnd; ++it) {
KDirWatch::self()->restartDirScan(*it);
}
}
void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink)
{
Q_Q(DeleteJob);
const QUrl url = (*m_currentStat);
if (isDir && !isLink) {
// Add toplevel dir in list of dirs
dirs.append(url);
if (url.isLocalFile()) {
// We are about to delete this dir, no need to watch it
// Maybe we should ask kdirwatch to remove all watches recursively?
// But then there would be no feedback (things disappearing progressively) during huge deletions
KDirWatch::self()->stopDirScan(url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
}
if (!KProtocolManager::canDeleteRecursive(url)) {
// qDebug() << url << "is a directory, let's list it";
ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo);
newjob->addMetaData(QStringLiteral("details"), QString::number(KIO::StatBasic));
newjob->setUnrestricted(true); // No KIOSK restrictions
QObject::connect(newjob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
slotEntries(job, list);
});
q->addSubjob(newjob);
// Note that this listing job will happen in parallel with other stat jobs.
}
} else {
if (isLink) {
// qDebug() << "Target is a symlink";
symlinks.append(url);
} else {
// qDebug() << "Target is a file";
files.append(url);
}
}
if (url.isLocalFile()) {
const QString parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
m_parentDirs.insert(parentDir);
}
}
void DeleteJob::slotResult(KJob *job)
{
Q_D(DeleteJob);
switch (d->state) {
case DELETEJOB_STATE_STATING:
removeSubjob(job);
// Was this a stat job or a list job? We do both in parallel.
if (StatJob *statJob = qobject_cast<StatJob *>(job)) {
// Was there an error while stating ?
if (job->error()) {
// Probably : doesn't exist
Job::slotResult(job); // will set the error and emit result(this)
d->restoreDirWatch();
return;
}
const UDSEntry &entry = statJob->statResult();
// Is it a file or a dir ?
const bool isLink = entry.isLink();
const bool isDir = entry.isDir();
d->currentSourceStated(isDir, isLink);
++d->m_currentStat;
d->statNextSrc();
} else {
if (job->error()) {
// Try deleting nonetheless, it may be empty (and non-listable)
}
if (!hasSubjobs()) {
d->finishedStatPhase();
}
}
break;
case DELETEJOB_STATE_DELETING_FILES:
// Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob
// FIXME: setMetaData() in the KIO API only allows access to outgoing metadata,
// but we need to alter the incoming one
d->m_incomingMetaData = dynamic_cast<KIO::Job *>(job)->metaData();
if (job->error()) {
Job::slotResult(job); // will set the error and emit result(this)
d->restoreDirWatch();
return;
}
removeSubjob(job);
Q_ASSERT(!hasSubjobs());
d->m_processedFiles++;
d->deleteNextFile();
break;
case DELETEJOB_STATE_DELETING_DIRS:
if (job->error()) {
Job::slotResult(job); // will set the error and emit result(this)
d->restoreDirWatch();
return;
}
removeSubjob(job);
Q_ASSERT(!hasSubjobs());
d->m_processedDirs++;
// emit processedAmount( this, KJob::Directories, d->m_processedDirs );
// emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs );
d->deleteNextDir();
break;
default:
Q_ASSERT(0);
}
}
DeleteJob *KIO::del(const QUrl &src, JobFlags flags)
{
QList<QUrl> srcList;
srcList.append(src);
DeleteJob *job = DeleteJobPrivate::newJob(srcList, flags);
if (job->uiDelegateExtension()) {
job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
}
return job;
}
DeleteJob *KIO::del(const QList<QUrl> &src, JobFlags flags)
{
DeleteJob *job = DeleteJobPrivate::newJob(src, flags);
if (job->uiDelegateExtension()) {
job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
}
return job;
}
#include "deletejob.moc"
#include "moc_deletejob.cpp"
@@ -0,0 +1,111 @@
// -*- c++ -*-
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_DELETEJOB_H
#define KIO_DELETEJOB_H
#include <QStringList>
#include "global.h"
#include "kiocore_export.h"
#include "job_base.h"
class QTimer;
namespace KIO
{
class DeleteJobPrivate;
/**
* @class KIO::DeleteJob deletejob.h <KIO/DeleteJob>
*
* A more complex Job to delete files and directories.
* Don't create the job directly, but use KIO::del() instead.
*
* @see KIO::del()
*/
class KIOCORE_EXPORT DeleteJob : public Job
{
Q_OBJECT
public:
~DeleteJob() override;
/**
* Returns the list of URLs.
* @return the list of URLs.
*/
QList<QUrl> urls() const;
Q_SIGNALS:
/**
* Emitted when the total number of files is known.
* @param job the job that emitted this signal
* @param files the total number of files
*/
void totalFiles(KJob *job, unsigned long files);
/**
* Emitted when the total number of directories is known.
* @param job the job that emitted this signal
* @param dirs the total number of directories
*/
void totalDirs(KJob *job, unsigned long dirs);
/**
* Sends the number of processed files.
* @param job the job that emitted this signal
* @param files the number of processed files
*/
void processedFiles(KIO::Job *job, unsigned long files);
/**
* Sends the number of processed directories.
* @param job the job that emitted this signal
* @param dirs the number of processed dirs
*/
void processedDirs(KIO::Job *job, unsigned long dirs);
/**
* Sends the URL of the file that is currently being deleted.
* @param job the job that emitted this signal
* @param file the URL of the file or directory that is being
* deleted
*/
void deleting(KIO::Job *job, const QUrl &file);
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOCORE_NO_EXPORT explicit DeleteJob(DeleteJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(DeleteJob)
};
/**
* Delete a file or directory.
*
* @param src file to delete
* @param flags We support HideProgressInfo here
* @return the job handling the operation
*/
KIOCORE_EXPORT DeleteJob *del(const QUrl &src, JobFlags flags = DefaultFlags);
/**
* Deletes a list of files or directories.
*
* @param src the files to delete
* @param flags We support HideProgressInfo here
* @return the job handling the operation
*/
KIOCORE_EXPORT DeleteJob *del(const QList<QUrl> &src, JobFlags flags = DefaultFlags);
}
#endif
@@ -0,0 +1,597 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006-2013 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <michael.pyne@kdemail.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "desktopexecparser.h"
#ifdef WITH_QTDBUS
#include "kiofuse_interface.h"
#endif
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KLocalizedString>
#include <KMacroExpander>
#include <KService>
#include <KSharedConfig>
#include <KShell>
#include <kprotocolinfo.h> // KF6 TODO remove after moving hasSchemeHandler to OpenUrlJob
#ifdef WITH_QTDBUS
#include <QDBusConnection>
#include <QDBusReply>
#endif
#include <QDir>
#include <QFile>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QUrl>
#include <config-kiocore.h> // KDE_INSTALL_FULL_LIBEXECDIR_KF
#include "kiocoredebug.h"
class KRunMX1 : public KMacroExpanderBase
{
public:
explicit KRunMX1(const KService &_service)
: KMacroExpanderBase(QLatin1Char('%'))
, service(_service)
{
}
bool hasUrls = false;
bool hasSpec = false;
bool hasError = false;
protected:
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
private:
const KService &service;
};
int KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
if (str.length() == pos + 1) {
// Internally, the stack of KMacroExpanderBase is empty and thus it thinks everything is successfully parsed
// This could be the case if the escape char "%" in this case, is at the end of the string
// See BUG: 495606
hasError = true;
return 0;
}
uint option = str[pos + 1].unicode();
switch (option) {
case 'c':
ret << service.name().replace(QLatin1Char('%'), QLatin1String("%%"));
break;
case 'k':
ret << service.entryPath().replace(QLatin1Char('%'), QLatin1String("%%"));
break;
case 'i':
ret << QStringLiteral("--icon") << service.icon().replace(QLatin1Char('%'), QLatin1String("%%"));
break;
case 'm':
// ret << "-miniicon" << service.icon().replace( '%', "%%" );
qCWarning(KIO_CORE) << "-miniicon isn't supported anymore (service" << service.name() << ')';
break;
case 'u':
case 'U':
hasUrls = true;
Q_FALLTHROUGH();
/* fallthrough */
case 'f':
case 'F':
case 'n':
case 'N':
case 'd':
case 'D':
case 'v':
hasSpec = true;
Q_FALLTHROUGH();
/* fallthrough */
default:
return -2; // subst with same and skip
}
return 2;
}
class KRunMX2 : public KMacroExpanderBase
{
public:
explicit KRunMX2(const QList<QUrl> &_urls)
: KMacroExpanderBase(QLatin1Char('%'))
, ignFile(false)
, urls(_urls)
{
}
bool ignFile;
protected:
int expandEscapedMacro(const QString &str, int pos, QStringList &ret) override;
private:
void subst(int option, const QUrl &url, QStringList &ret);
const QList<QUrl> &urls;
};
void KRunMX2::subst(int option, const QUrl &url, QStringList &ret)
{
switch (option) {
case 'u':
ret << ((url.isLocalFile() && url.fragment().isNull() && url.query().isNull()) ? QDir::toNativeSeparators(url.toLocalFile()) : url.toString());
break;
case 'd':
ret << url.adjusted(QUrl::RemoveFilename).path();
break;
case 'f':
ret << QDir::toNativeSeparators(url.toLocalFile());
break;
case 'n':
ret << url.fileName();
break;
case 'v':
if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry("Dev");
}
break;
}
return;
}
int KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
{
uint option = str[pos + 1].unicode();
switch (option) {
case 'f':
case 'u':
case 'n':
case 'd':
case 'v':
if (urls.isEmpty()) {
if (!ignFile) {
// qCDebug(KIO_CORE) << "No URLs supplied to single-URL service" << str;
}
} else if (urls.count() > 1) {
qCWarning(KIO_CORE) << urls.count() << "URLs supplied to single-URL service" << str;
} else {
subst(option, urls.first(), ret);
}
break;
case 'F':
case 'U':
case 'N':
case 'D':
option += 'a' - 'A';
for (const QUrl &url : urls) {
subst(option, url, ret);
}
break;
case '%':
ret = QStringList(QStringLiteral("%"));
break;
default:
return -2; // subst with same and skip
}
return 2;
}
QStringList KIO::DesktopExecParser::supportedProtocols(const KService &service)
{
QStringList supportedProtocols = service.supportedProtocols();
KRunMX1 mx1(service);
QString exec = service.exec();
if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
if (!supportedProtocols.isEmpty()) {
qCWarning(KIO_CORE) << service.entryPath() << "contains supported protocols but doesn't use %u or %U in its Exec line! This is inconsistent.";
}
return QStringList();
} else {
if (supportedProtocols.isEmpty()) {
// compat mode: assume KIO if not set and it's a KDE app (or a KDE service)
const QStringList categories = service.property<QStringList>(QStringLiteral("Categories"));
if (categories.contains(QLatin1String("KDE")) || !service.isApplication() || service.entryPath().isEmpty() /*temp service*/) {
supportedProtocols.append(QStringLiteral("KIO"));
} else { // if no KDE app, be a bit over-generic
supportedProtocols.append(QStringLiteral("http"));
supportedProtocols.append(QStringLiteral("https")); // #253294
supportedProtocols.append(QStringLiteral("ftp"));
}
}
}
// qCDebug(KIO_CORE) << "supportedProtocols:" << supportedProtocols;
return supportedProtocols;
}
bool KIO::DesktopExecParser::isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols)
{
return url.isLocalFile() //
|| supportedProtocols.contains(QLatin1String("KIO")) //
|| supportedProtocols.contains(url.scheme(), Qt::CaseInsensitive);
}
// We have up to two sources of data, for protocols not handled by KIO workers (so called "helper") :
// 1) the exec line of the .protocol file, if there's one
// 2) the application associated with x-scheme-handler/<protocol> if there's one
bool KIO::DesktopExecParser::hasSchemeHandler(const QUrl &url) // KF6 TODO move to OpenUrlJob
{
if (KProtocolInfo::isHelperProtocol(url)) {
return true;
}
const KService::Ptr service = KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + url.scheme());
if (service) {
qCDebug(KIO_CORE) << QLatin1String("preferred service for x-scheme-handler/") + url.scheme() << service->desktopEntryName();
}
return service;
}
class KIO::DesktopExecParserPrivate
{
public:
DesktopExecParserPrivate(const KService &_service, const QList<QUrl> &_urls)
: service(_service)
, urls(_urls)
, tempFiles(false)
{
}
bool isUrlSupported(const QUrl &url, const QStringList &supportedProtocols);
const KService &service;
QList<QUrl> urls;
bool tempFiles;
QString suggestedFileName;
QString m_errorString;
};
KIO::DesktopExecParser::DesktopExecParser(const KService &service, const QList<QUrl> &urls)
: d(new DesktopExecParserPrivate(service, urls))
{
}
KIO::DesktopExecParser::~DesktopExecParser()
{
}
void KIO::DesktopExecParser::setUrlsAreTempFiles(bool tempFiles)
{
d->tempFiles = tempFiles;
}
void KIO::DesktopExecParser::setSuggestedFileName(const QString &suggestedFileName)
{
d->suggestedFileName = suggestedFileName;
}
static const QString kioexecPath()
{
QString kioexec = QCoreApplication::applicationDirPath() + QLatin1String("/kioexec");
if (!QFileInfo::exists(kioexec)) {
kioexec = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kioexec");
}
Q_ASSERT(QFileInfo::exists(kioexec));
return kioexec;
}
static QString findNonExecutableProgram(const QString &executable)
{
// Relative to current dir, or absolute path
const QFileInfo fi(executable);
if (fi.exists() && !fi.isExecutable()) {
return executable;
}
#ifdef Q_OS_UNIX
// This is a *very* simplified version of QStandardPaths::findExecutable
const QStringList searchPaths = QString::fromLocal8Bit(qgetenv("PATH")).split(QDir::listSeparator(), Qt::SkipEmptyParts);
for (const QString &searchPath : searchPaths) {
const QString candidate = searchPath + QLatin1Char('/') + executable;
const QFileInfo fileInfo(candidate);
if (fileInfo.exists()) {
if (fileInfo.isExecutable()) {
qWarning() << "Internal program error. QStandardPaths::findExecutable couldn't find" << executable << "but our own logic found it at"
<< candidate << ". Please report a bug at https://bugs.kde.org";
} else {
return candidate;
}
}
}
#endif
return QString();
}
bool KIO::DesktopExecParserPrivate::isUrlSupported(const QUrl &url, const QStringList &protocols)
{
if (KIO::DesktopExecParser::isProtocolInSupportedList(url, protocols)) {
return true;
}
// supportedProtocols() only checks whether the .desktop file has MimeType=x-scheme-handler/xxx
// We also want to check whether the app has been set as default/associated in mimeapps.list
const auto handlers = KApplicationTrader::queryByMimeType(QLatin1String("x-scheme-handler/") + url.scheme());
for (const KService::Ptr &handler : handlers) {
if (handler->desktopEntryName() == service.desktopEntryName()) {
return true;
}
}
return false;
}
QStringList KIO::DesktopExecParser::resultingArguments() const
{
QString exec = d->service.exec();
if (exec.isEmpty()) {
d->m_errorString = i18n("No Exec field in %1", d->service.entryPath());
qCWarning(KIO_CORE) << "No Exec field in" << d->service.entryPath();
return QStringList();
}
// Extract the name of the binary to execute from the full Exec line, to see if it exists
const QString binary = executablePath(exec);
QString executableFullPath;
if (!binary.isEmpty()) { // skip all this if the Exec line is a complex shell command
if (QDir::isRelativePath(binary)) {
// Resolve the executable to ensure that helpers in libexec are found.
// Too bad for commands that need a shell - they must reside in $PATH.
executableFullPath = QStandardPaths::findExecutable(binary);
if (executableFullPath.isEmpty()) {
executableFullPath = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/") + binary;
}
} else {
executableFullPath = binary;
}
// Now check that the binary exists and has the executable flag
if (!QFileInfo(executableFullPath).isExecutable()) {
// Does it really not exist, or is it non-executable (on Unix)? (bug #415567)
const QString nonExecutable = findNonExecutableProgram(binary);
if (nonExecutable.isEmpty()) {
d->m_errorString = i18n("Could not find the program '%1'", binary);
} else {
if (QDir::isRelativePath(binary)) {
d->m_errorString = i18n("The program '%1' was found at '%2' but it is missing executable permissions.", binary, nonExecutable);
} else {
d->m_errorString = i18n("The program '%1' is missing executable permissions.", nonExecutable);
}
}
return QStringList();
}
}
QStringList result;
bool appHasTempFileOption;
KRunMX1 mx1(d->service);
KRunMX2 mx2(d->urls);
if (!mx1.expandMacrosShellQuote(exec) || mx1.hasError) { // Error in shell syntax
d->m_errorString = i18n("Syntax error in command %1 coming from %2", exec, d->service.entryPath());
qCWarning(KIO_CORE) << "Syntax error in command" << d->service.exec() << ", service" << d->service.name();
return QStringList();
}
// FIXME: the current way of invoking kioexec disables term and su use
// Check if we need "tempexec" (kioexec in fact)
appHasTempFileOption = d->tempFiles && d->service.property<bool>(QStringLiteral("X-KDE-HasTempFileOption"));
if (d->tempFiles && !appHasTempFileOption && d->urls.size()) {
result << kioexecPath() << QStringLiteral("--tempfiles") << exec;
if (!d->suggestedFileName.isEmpty()) {
result << QStringLiteral("--suggestedfilename");
result << d->suggestedFileName;
}
result += QUrl::toStringList(d->urls);
return result;
}
#ifdef WITH_QTDBUS
// Return true for non-KIO desktop files with explicit X-KDE-Protocols list, like vlc, for the special case below
auto isNonKIO = [this]() {
const QStringList protocols = d->service.property<QStringList>(QStringLiteral("X-KDE-Protocols"));
return !protocols.isEmpty() && !protocols.contains(QLatin1String("KIO"));
};
// Check if we need kioexec, or KIOFuse
bool useKioexec = false;
org::kde::KIOFuse::VFS kiofuse_iface(QStringLiteral("org.kde.KIOFuse"), QStringLiteral("/org/kde/KIOFuse"), QDBusConnection::sessionBus());
struct MountRequest {
QDBusPendingReply<QString> reply;
int urlIndex;
};
QList<MountRequest> requests;
requests.reserve(d->urls.count());
const QStringList appSupportedProtocols = supportedProtocols(d->service);
for (int i = 0; i < d->urls.count(); ++i) {
const QUrl url = d->urls.at(i);
const bool supported = mx1.hasUrls ? d->isUrlSupported(url, appSupportedProtocols) : url.isLocalFile();
if (!supported) {
// If FUSE fails, and there is no scheme handler, we'll have to fallback to kioexec
useKioexec = true;
}
// NOTE: Some non-KIO apps may support the URLs (e.g. VLC supports smb://)
// but will not have the password if they are not in the URL itself.
// Hence convert URL to KIOFuse equivalent in case there is a password.
// @see https://pointieststick.com/2018/01/17/videos-on-samba-shares/
// @see https://bugs.kde.org/show_bug.cgi?id=330192
if (!supported || (!url.userName().isEmpty() && url.password().isEmpty() && isNonKIO())) {
requests.push_back({kiofuse_iface.mountUrl(url.toString()), i});
}
}
for (auto &request : requests) {
request.reply.waitForFinished();
}
const bool fuseError = std::any_of(requests.cbegin(), requests.cend(), [](const MountRequest &request) {
return request.reply.isError();
});
if (fuseError && useKioexec) {
// We need to run the app through kioexec
result << kioexecPath();
if (d->tempFiles) {
result << QStringLiteral("--tempfiles");
}
if (!d->suggestedFileName.isEmpty()) {
result << QStringLiteral("--suggestedfilename");
result << d->suggestedFileName;
}
result << exec;
result += QUrl::toStringList(d->urls);
return result;
}
// At this point we know we're not using kioexec, so feel free to replace
// KIO URLs with their KIOFuse local path.
for (const auto &request : std::as_const(requests)) {
if (!request.reply.isError()) {
d->urls[request.urlIndex] = QUrl::fromLocalFile(request.reply.value());
}
}
#endif
if (appHasTempFileOption) {
exec += QLatin1String(" --tempfile");
}
// Did the user forget to append something like '%f'?
// If so, then assume that '%f' is the right choice => the application
// accepts only local files.
if (!mx1.hasSpec) {
exec += QLatin1String(" %f");
mx2.ignFile = true;
}
mx2.expandMacrosShellQuote(exec); // syntax was already checked, so don't check return value
/*
1 = need_shell, 2 = terminal, 4 = su
0 << split(cmd)
1 << "sh" << "-c" << cmd
2 << split(term) << "-e" << split(cmd)
3 << split(term) << "-e" << "sh" << "-c" << cmd
4 << "kdesu" << "-u" << user << "-c" << cmd
5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
6 << split(term) << "-e" << "su" << user << "-c" << cmd
7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd))
"sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
this could be optimized with the -s switch of some su versions (e.g., debian linux).
*/
if (d->service.terminal()) {
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
QString terminal = cg.readPathEntry("TerminalApplication", QStringLiteral("konsole"));
const bool isKonsole = (terminal == QLatin1String("konsole"));
QStringList terminalParts = KShell::splitArgs(terminal);
QString terminalPath;
if (!terminalParts.isEmpty()) {
terminalPath = QStandardPaths::findExecutable(terminalParts.at(0));
}
if (terminalPath.isEmpty()) {
d->m_errorString = i18n("Terminal %1 not found while trying to run %2", terminal, d->service.entryPath());
qCWarning(KIO_CORE) << "Terminal" << terminal << "not found, service" << d->service.name();
return QStringList();
}
terminalParts[0] = terminalPath;
terminal = KShell::joinArgs(terminalParts);
if (isKonsole) {
if (!d->service.workingDirectory().isEmpty()) {
terminal += QLatin1String(" --workdir ") + KShell::quoteArg(d->service.workingDirectory());
}
terminal += QLatin1String(" -qwindowtitle '%c'");
if (!d->service.icon().isEmpty()) {
terminal += QLatin1String(" -qwindowicon ") + KShell::quoteArg(d->service.icon().replace(QLatin1Char('%'), QLatin1String("%%")));
}
}
terminal += QLatin1Char(' ') + d->service.terminalOptions();
if (!mx1.expandMacrosShellQuote(terminal) || mx1.hasError) {
d->m_errorString = i18n("Syntax error in command %1 while trying to run %2", terminal, d->service.entryPath());
qCWarning(KIO_CORE) << "Syntax error in command" << terminal << ", service" << d->service.name();
return QStringList();
}
mx2.expandMacrosShellQuote(terminal);
result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell!
result << QStringLiteral("-e");
}
KShell::Errors err;
QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
if (!executableFullPath.isEmpty()) {
execlist[0] = executableFullPath;
}
if (d->service.substituteUid()) {
if (d->service.terminal()) {
result << QStringLiteral("su");
} else {
QString kdesu = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kdesu");
if (!QFile::exists(kdesu)) {
kdesu = QStandardPaths::findExecutable(QStringLiteral("kdesu"));
}
if (!QFile::exists(kdesu)) {
// Insert kdesu as string so we show a nice warning: 'Could not launch kdesu'
result << QStringLiteral("kdesu");
return result;
} else {
result << kdesu << QStringLiteral("-u");
}
}
result << d->service.username() << QStringLiteral("-c");
if (err == KShell::FoundMeta) {
exec = QLatin1String("/bin/sh -c ") + KShell::quoteArg(exec);
} else {
exec = KShell::joinArgs(execlist);
}
result << exec;
} else {
if (err == KShell::FoundMeta) {
result << QStringLiteral("/bin/sh") << QStringLiteral("-c") << exec;
} else {
result += execlist;
}
}
return result;
}
QString KIO::DesktopExecParser::errorMessage() const
{
return d->m_errorString;
}
// static
QString KIO::DesktopExecParser::executableName(const QString &execLine)
{
const QString bin = executablePath(execLine);
return bin.mid(bin.lastIndexOf(QLatin1Char('/')) + 1);
}
// static
QString KIO::DesktopExecParser::executablePath(const QString &execLine)
{
// Remove parameters and/or trailing spaces.
const QStringList args = KShell::splitArgs(execLine, KShell::AbortOnMeta | KShell::TildeExpand);
auto it = std::find_if(args.cbegin(), args.cend(), [](const QString &arg) {
return !arg.contains(QLatin1Char('='));
});
return it != args.cend() ? *it : QString{};
}
@@ -0,0 +1,118 @@
/*
SPDX-FileCopyrightText: 2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KIO_DESKTOPEXECPARSER_H
#define KIO_DESKTOPEXECPARSER_H
#include "kiocore_export.h"
#include <QList>
#include <QScopedPointer>
#include <QStringList>
class QUrl;
class KService;
namespace KIO
{
class DesktopExecParserPrivate;
/**
* @class KIO::DesktopExecParser desktopexecparser.h <KIO/DesktopExecParser>
*
* Parses the Exec= line from a .desktop file,
* and process all the '\%' placeholders, e.g.\ handling URLs vs local files.
*
* The processing actually happens when calling resultingArguments(), after
* setting everything up.
*
* @since 5.0
*/
class KIOCORE_EXPORT DesktopExecParser
{
public:
/**
* Creates a parser for a desktop file Exec line.
*
* @param service the service to extract information from.
* The KService instance must remain alive as long as the parser is alive.
* @param urls The urls the service should open.
*/
DesktopExecParser(const KService &service, const QList<QUrl> &urls);
/**
* Destructor
*/
~DesktopExecParser();
/**
* If @p tempFiles is set to true and the urls given to the constructor are local files,
* they will be deleted when the application exits.
*/
void setUrlsAreTempFiles(bool tempFiles);
/**
* Sets the file name to use in the case of downloading the file to a tempfile
* in order to give to a non-url-aware application. Some apps rely on the extension
* to determine the MIME type of the file. Usually the file name comes from the URL,
* but in the case of the HTTP Content-Disposition header, we need to override the
* file name.
*/
void setSuggestedFileName(const QString &suggestedFileName);
/**
* @return a list of arguments suitable for QProcess.
* Returns an empty list on error, check errorMessage() for details.
*/
QStringList resultingArguments() const;
/**
* @return an error message for when resultingArguments() returns an empty list
* @since 5.71
*/
QString errorMessage() const;
/**
* Returns the list of protocols which the application supports.
* This can be a list of actual protocol names, or just "KIO" for KIO-based apps.
*/
static QStringList supportedProtocols(const KService &service);
/**
* Returns true if @p protocol is in the list of protocols returned by supportedProtocols().
* The only reason for this method is the special handling of "KIO".
*/
static bool isProtocolInSupportedList(const QUrl &url, const QStringList &supportedProtocols);
/**
* Returns true if @p protocol should be opened by a "handler" application, i.e.\ an application
* associated to _all_ URLs using this protocol (a.k.a. scheme).
*/
static bool hasSchemeHandler(const QUrl &url); // KF6 TODO move to OpenUrlJob
/**
* Given a full command line (e.g.\ the Exec= line from a .desktop file),
* extract the name of the executable being run (removing the path, if specified).
* @param execLine the full command line
* @return the name of the executable to run, example: "ls"
*/
static QString executableName(const QString &execLine);
/**
* Given a full command line (e.g.\ the Exec= line from a .desktop file),
* extract the name of the executable being run, including its full path, if specified.
* @param execLine the full command line
* @return the name of the executable to run, example: "/bin/ls"
*/
static QString executablePath(const QString &execLine);
private:
QScopedPointer<DesktopExecParserPrivate> d;
};
} // namespace KIO
#endif
@@ -0,0 +1,206 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "directorysizejob.h"
#include "global.h"
#include "listjob.h"
#include <QDebug>
#include <QTimer>
#include <kio/jobuidelegatefactory.h>
#include "job_p.h"
#include <set>
namespace KIO
{
class DirectorySizeJobPrivate : public KIO::JobPrivate
{
public:
DirectorySizeJobPrivate()
: m_totalSize(0L)
, m_totalFiles(0L)
, m_totalSubdirs(0L)
, m_currentItem(0)
{
}
explicit DirectorySizeJobPrivate(const KFileItemList &lstItems)
: m_totalSize(0L)
, m_totalFiles(0L)
, m_totalSubdirs(0L)
, m_lstItems(lstItems)
, m_currentItem(0)
{
}
KIO::filesize_t m_totalSize;
KIO::filesize_t m_totalFiles;
KIO::filesize_t m_totalSubdirs;
KFileItemList m_lstItems;
int m_currentItem;
QHash<long, std::set<long>> m_visitedInodes; // device -> set of inodes
void startNextJob(const QUrl &url);
void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
void processNextItem();
Q_DECLARE_PUBLIC(DirectorySizeJob)
static inline DirectorySizeJob *newJob(const QUrl &directory)
{
DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate;
DirectorySizeJob *job = new DirectorySizeJob(*d);
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
d->startNextJob(directory);
return job;
}
static inline DirectorySizeJob *newJob(const KFileItemList &lstItems)
{
DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate(lstItems);
DirectorySizeJob *job = new DirectorySizeJob(*d);
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
QTimer::singleShot(0, job, [d]() {
d->processNextItem();
});
return job;
}
};
} // namespace KIO
using namespace KIO;
DirectorySizeJob::DirectorySizeJob(DirectorySizeJobPrivate &dd)
: KIO::Job(dd)
{
}
DirectorySizeJob::~DirectorySizeJob()
{
}
KIO::filesize_t DirectorySizeJob::totalSize() const
{
return d_func()->m_totalSize;
}
KIO::filesize_t DirectorySizeJob::totalFiles() const
{
return d_func()->m_totalFiles;
}
KIO::filesize_t DirectorySizeJob::totalSubdirs() const
{
return d_func()->m_totalSubdirs;
}
void DirectorySizeJobPrivate::processNextItem()
{
Q_Q(DirectorySizeJob);
while (m_currentItem < m_lstItems.count()) {
const KFileItem item = m_lstItems[m_currentItem++];
// qDebug() << item;
if (!item.isLink()) {
if (item.isDir()) {
// qDebug() << "dir -> listing";
const auto localPath = item.localPath();
if (!localPath.isNull()) {
startNextJob(QUrl::fromLocalFile(localPath));
} else {
startNextJob(item.targetUrl());
}
return; // we'll come back later, when this one's finished
} else {
m_totalSize += item.size();
m_totalFiles++;
// qDebug() << "file -> " << m_totalSize;
}
} else {
m_totalFiles++;
}
}
// qDebug() << "finished";
q->emitResult();
}
void DirectorySizeJobPrivate::startNextJob(const QUrl &url)
{
Q_Q(DirectorySizeJob);
// qDebug() << url;
KIO::ListJob *listJob = KIO::listRecursive(url, KIO::HideProgressInfo);
listJob->addMetaData(QStringLiteral("details"), QString::number(KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatInode));
q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
slotEntries(job, list);
});
q->addSubjob(listJob);
}
void DirectorySizeJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
{
KIO::UDSEntryList::ConstIterator it = list.begin();
const KIO::UDSEntryList::ConstIterator end = list.end();
for (; it != end; ++it) {
const KIO::UDSEntry &entry = *it;
const long device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0);
if (device && !entry.isLink()) {
// Hard-link detection (#67939)
const long inode = entry.numberValue(KIO::UDSEntry::UDS_INODE, 0);
std::set<long> &visitedInodes = m_visitedInodes[device]; // find or insert
const auto [it, isNewInode] = visitedInodes.insert(inode);
if (!isNewInode) {
continue;
}
}
const KIO::filesize_t size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0);
const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
if (name == QLatin1Char('.')) {
m_totalSize += size;
// qDebug() << "'.': added" << size << "->" << m_totalSize;
} else if (name != QLatin1String("..")) {
if (!entry.isLink()) {
m_totalSize += size;
}
if (!entry.isDir()) {
m_totalFiles++;
} else {
m_totalSubdirs++;
}
// qDebug() << name << ":" << size << "->" << m_totalSize;
}
}
}
void DirectorySizeJob::slotResult(KJob *job)
{
Q_D(DirectorySizeJob);
// qDebug() << d->m_totalSize;
removeSubjob(job);
if (d->m_currentItem < d->m_lstItems.count()) {
d->processNextItem();
} else {
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
}
emitResult();
}
}
// static
DirectorySizeJob *KIO::directorySize(const QUrl &directory)
{
return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs
}
// static
DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems)
{
return DirectorySizeJobPrivate::newJob(lstItems);
}
#include "moc_directorysizejob.cpp"
@@ -0,0 +1,85 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DIRECTORYSIZEJOB_H
#define DIRECTORYSIZEJOB_H
#include "job_base.h"
#include "kiocore_export.h"
#include <kfileitem.h>
namespace KIO
{
class DirectorySizeJobPrivate;
/**
* @class KIO::DirectorySizeJob directorysizejob.h <KIO/DirectorySizeJob>
*
* Computes a directory size (similar to "du", but doesn't give the same results
* since we simply sum up the dir and file sizes, whereas du speaks disk blocks)
*
* Usage: see KIO::directorySize.
*/
class KIOCORE_EXPORT DirectorySizeJob : public KIO::Job
{
Q_OBJECT
public:
~DirectorySizeJob() override;
public:
/**
* @return the size we found
*/
KIO::filesize_t totalSize() const;
/**
* @return the total number of files (counting symlinks to files, sockets
* and character devices as files) in this directory and all sub-directories
*/
KIO::filesize_t totalFiles() const;
/**
* @return the total number of sub-directories found (not including the
* directory the search started from and treating symlinks to directories
* as directories)
*/
KIO::filesize_t totalSubdirs() const;
protected Q_SLOTS:
void slotResult(KJob *job) override;
protected:
KIOCORE_NO_EXPORT explicit DirectorySizeJob(DirectorySizeJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(DirectorySizeJob)
};
/**
* Computes a directory size (by doing a recursive listing).
* Connect to the result signal (this is the preferred solution to avoid blocking the GUI),
* or use exec() for a synchronous (blocking) calculation.
*
* This one lists a single directory.
*/
KIOCORE_EXPORT DirectorySizeJob *directorySize(const QUrl &directory);
/**
* Computes a directory size (by doing a recursive listing).
* Connect to the result signal (this is the preferred solution to avoid blocking the GUI),
* or use exec() for a synchronous (blocking) calculation.
*
* This one lists the items from @p lstItems.
* The reason we asks for items instead of just urls, is so that
* we directly know if the item is a file or a directory,
* and in case of a file, we already have its size.
*/
KIOCORE_EXPORT DirectorySizeJob *directorySize(const KFileItemList &lstItems);
}
#endif
@@ -0,0 +1,54 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "emptytrashjob.h"
#include "job.h"
#include "job_p.h"
// #include <KNotification>
using namespace KIO;
class KIO::EmptyTrashJobPrivate : public SimpleJobPrivate
{
public:
EmptyTrashJobPrivate(int command, const QByteArray &packedArgs)
: SimpleJobPrivate(QUrl(QStringLiteral("trash:/")), command, packedArgs)
{
}
Q_DECLARE_PUBLIC(EmptyTrashJob)
static inline EmptyTrashJob *newJob(int command, const QByteArray &packedArgs)
{
EmptyTrashJob *job = new EmptyTrashJob(*new EmptyTrashJobPrivate(command, packedArgs));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
return job;
}
};
EmptyTrashJob::EmptyTrashJob(EmptyTrashJobPrivate &dd)
: SimpleJob(dd)
{
}
EmptyTrashJob::~EmptyTrashJob()
{
}
// TODO KF6: remove this
void EmptyTrashJob::slotFinished()
{
SimpleJob::slotFinished();
}
KIO::EmptyTrashJob *KIO::emptyTrash()
{
KIO_ARGS << int(1);
return EmptyTrashJobPrivate::newJob(CMD_SPECIAL, packedArgs);
}
#include "moc_emptytrashjob.cpp"
@@ -0,0 +1,52 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef EMPTYTRASHJOB_H
#define EMPTYTRASHJOB_H
#include "kiocore_export.h"
#include "simplejob.h"
namespace KIO
{
class EmptyTrashJobPrivate;
/**
* @class KIO::EmptyTrashJob emptytrashjob.h <KIO/EmptyTrashJob>
*
* A KIO job for emptying the trash
* @see KIO::trash()
* @see KIO::restoreFromTrash()
* @since 5.2
*/
class KIOCORE_EXPORT EmptyTrashJob : public SimpleJob
{
Q_OBJECT
public:
~EmptyTrashJob() override;
protected:
void slotFinished() override;
private:
KIOCORE_NO_EXPORT explicit EmptyTrashJob(EmptyTrashJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(EmptyTrashJob)
};
/**
* Empties the trash.
*
* @return A pointer to the job handling the operation.
* @since 5.2
*/
KIOCORE_EXPORT EmptyTrashJob *emptyTrash();
}
#endif
@@ -0,0 +1,181 @@
/*
This file is part of KIO.
SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org>
SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "faviconscache_p.h"
#include <KConfig>
#include <KConfigGroup>
#include <QMutex>
#include <QCache>
#include <QDir>
#include <QFile>
#include <QSet>
#include <QStandardPaths>
#include <QUrl>
using namespace KIO;
static QString portForUrl(const QUrl &url)
{
if (url.port() > 0) {
return QLatin1Char('_') + QString::number(url.port());
}
return QString();
}
static QString simplifyUrl(const QUrl &url)
{
// splat any = in the URL so it can be safely used as a config key
QString result = url.host() + portForUrl(url) + url.path();
result.replace(QLatin1Char('='), QLatin1Char('_'));
while (result.endsWith(QLatin1Char('/'))) {
result.chop(1);
}
return result;
}
static QString iconNameFromUrl(const QUrl &iconUrl)
{
if (iconUrl.path() == QLatin1String("/favicon.ico")) {
return iconUrl.host() + portForUrl(iconUrl);
}
QString result = simplifyUrl(iconUrl);
// splat / so it can be safely used as a file name
result.replace(QLatin1Char('/'), QLatin1Char('_'));
const int dotExtLen = 4;
const QStringView ext = QStringView(result).right(dotExtLen);
if (ext == QLatin1String(".ico") || ext == QLatin1String(".png") || ext == QLatin1String(".xpm")) {
result.chop(dotExtLen);
}
return result;
}
////
class KIO::FavIconsCachePrivate
{
public:
FavIconsCachePrivate()
: cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons/"))
, config(cacheDir + QStringLiteral("index"))
{
}
QString cachedIconUrlForUrl(const QUrl &url);
const QString cacheDir;
QMutex mutex; // protects all the member variables below
KConfig config;
QCache<QString, QString> faviconsCache;
QSet<QUrl> failedDownloads;
};
QString FavIconsCachePrivate::cachedIconUrlForUrl(const QUrl &url)
{
Q_ASSERT(!mutex.tryLock());
const QString simplifiedUrl = simplifyUrl(url);
QString *cachedIconUrl = faviconsCache[simplifiedUrl];
return (cachedIconUrl ? *cachedIconUrl : config.group(QString()).readEntry(simplifiedUrl, QString()));
}
FavIconsCache *FavIconsCache::instance()
{
static FavIconsCache s_cache; // remind me why we need Q_GLOBAL_STATIC, again, now that C++11 guarantees thread safety?
return &s_cache;
}
FavIconsCache::FavIconsCache()
: d(new FavIconsCachePrivate)
{
}
FavIconsCache::~FavIconsCache() = default;
QString FavIconsCache::iconForUrl(const QUrl &url)
{
if (url.host().isEmpty()) {
return QString();
}
QMutexLocker locker(&d->mutex);
const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
QString icon = d->cacheDir;
if (!cachedIconUrl.isEmpty()) {
icon += iconNameFromUrl(QUrl(cachedIconUrl));
} else {
icon += url.host();
}
icon += QStringLiteral(".png");
if (QFile::exists(icon)) {
return icon;
}
return QString();
}
QUrl FavIconsCache::iconUrlForUrl(const QUrl &url)
{
QMutexLocker locker(&d->mutex);
const QString cachedIconUrl = d->cachedIconUrlForUrl(url);
if (!cachedIconUrl.isEmpty()) {
return QUrl(cachedIconUrl);
} else {
QUrl iconUrl;
iconUrl.setScheme(url.scheme());
iconUrl.setHost(url.host());
iconUrl.setPort(url.port());
iconUrl.setPath(QStringLiteral("/favicon.ico"));
iconUrl.setUserInfo(url.userInfo());
return iconUrl;
}
}
void FavIconsCache::setIconForUrl(const QUrl &url, const QUrl &iconUrl)
{
QMutexLocker locker(&d->mutex);
const QString simplifiedUrl = simplifyUrl(url);
const QString iconUrlStr = iconUrl.url();
d->faviconsCache.insert(simplifiedUrl, new QString(iconUrlStr));
d->config.group(QString()).writeEntry(simplifiedUrl, iconUrlStr);
d->config.sync();
}
QString FavIconsCache::cachePathForIconUrl(const QUrl &iconUrl) const
{
QMutexLocker locker(&d->mutex);
const QString iconName = iconNameFromUrl(iconUrl);
return d->cacheDir + iconName + QLatin1String(".png");
}
void FavIconsCache::ensureCacheExists()
{
QMutexLocker locker(&d->mutex);
QDir().mkpath(d->cacheDir);
}
void FavIconsCache::addFailedDownload(const QUrl &url)
{
QMutexLocker locker(&d->mutex);
d->failedDownloads.insert(url);
}
void FavIconsCache::removeFailedDownload(const QUrl &url)
{
QMutexLocker locker(&d->mutex);
d->failedDownloads.remove(url);
}
bool FavIconsCache::isFailedDownload(const QUrl &url) const
{
QMutexLocker locker(&d->mutex);
return d->failedDownloads.contains(url);
}
#include "moc_faviconscache_p.cpp"
@@ -0,0 +1,60 @@
/*
This file is part of KIO.
SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef FAVICONSCACHE_P_H
#define FAVICONSCACHE_P_H
#include <QObject>
#include <memory>
#include <kiocore_export.h>
namespace KIO
{
class FavIconsCachePrivate;
/**
* @internal
* Singleton handling the cache (memory + disk) for favicons.
* Exported for KIOGui's FavIconsManager
*/
class KIOCORE_EXPORT FavIconsCache : public QObject
{
Q_OBJECT
public:
static FavIconsCache *instance();
// Fast cache lookup, used by KIO::favIconForUrl
QString iconForUrl(const QUrl &url);
// Look for a custom icon URL in the cache, otherwise assemble default host icon URL
QUrl iconUrlForUrl(const QUrl &url);
// Remember association to a custom icon URL
void setIconForUrl(const QUrl &url, const QUrl &iconUrl);
QString cachePathForIconUrl(const QUrl &iconUrl) const;
void ensureCacheExists();
void addFailedDownload(const QUrl &url);
void removeFailedDownload(const QUrl &url);
bool isFailedDownload(const QUrl &url) const;
Q_SIGNALS:
private:
KIOCORE_NO_EXPORT FavIconsCache();
KIOCORE_NO_EXPORT ~FavIconsCache() override;
std::unique_ptr<FavIconsCachePrivate> const d;
};
}
#endif // FAVICONSCACHE_H
@@ -0,0 +1,649 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "filecopyjob.h"
#include "askuseractioninterface.h"
#include "job_p.h"
#include "kprotocolmanager.h"
#include "scheduler.h"
#include "worker_p.h"
#include <kio/jobuidelegatefactory.h>
#include <KLocalizedString>
#include <QFile>
#include <QTimer>
using namespace KIO;
static inline Worker *jobWorker(SimpleJob *job)
{
return SimpleJobPrivate::get(job)->m_worker;
}
/** @internal */
class KIO::FileCopyJobPrivate : public KIO::JobPrivate
{
public:
FileCopyJobPrivate(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags)
: m_sourceSize(filesize_t(-1))
, m_src(src)
, m_dest(dest)
, m_moveJob(nullptr)
, m_copyJob(nullptr)
, m_delJob(nullptr)
, m_chmodJob(nullptr)
, m_getJob(nullptr)
, m_putJob(nullptr)
, m_permissions(permissions)
, m_move(move)
, m_mustChmod(0)
, m_bFileCopyInProgress(false)
, m_flags(flags)
{
}
KIO::filesize_t m_sourceSize;
QDateTime m_modificationTime;
QUrl m_src;
QUrl m_dest;
QByteArray m_buffer;
SimpleJob *m_moveJob;
SimpleJob *m_copyJob;
SimpleJob *m_delJob;
SimpleJob *m_chmodJob;
TransferJob *m_getJob;
TransferJob *m_putJob;
int m_permissions;
bool m_move : 1;
bool m_canResume : 1;
bool m_resumeAnswerSent : 1;
bool m_mustChmod : 1;
bool m_bFileCopyInProgress : 1;
JobFlags m_flags;
void startBestCopyMethod();
void startCopyJob();
void startCopyJob(const QUrl &workerUrl);
void startRenameJob(const QUrl &workerUrl);
void startDataPump();
void connectSubjob(SimpleJob *job);
void slotStart();
void slotData(KIO::Job *, const QByteArray &data);
void slotDataReq(KIO::Job *, QByteArray &data);
void slotMimetype(KIO::Job *, const QString &type);
/**
* Forward signal from subjob
* @param job the job that emitted this signal
* @param offset the offset to resume from
*/
void slotCanResume(KIO::Job *job, KIO::filesize_t offset);
void processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset);
Q_DECLARE_PUBLIC(FileCopyJob)
static inline FileCopyJob *newJob(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags)
{
// qDebug() << src << "->" << dest;
FileCopyJob *job = new FileCopyJob(*new FileCopyJobPrivate(src, dest, permissions, move, flags));
job->setProperty("destUrl", dest.toString());
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = move ? Move : Copy;
}
return job;
}
};
static bool isSrcDestSameWorkerProcess(const QUrl &src, const QUrl &dest)
{
/* clang-format off */
return src.scheme() == dest.scheme()
&& src.host() == dest.host()
&& src.port() == dest.port()
&& src.userName() == dest.userName()
&& src.password() == dest.password();
/* clang-format on */
}
/*
* The FileCopyJob works according to the famous Bavarian
* 'Alternating Bitburger Protocol': we either drink a beer or we
* we order a beer, but never both at the same time.
* Translated to KIO workers: We alternate between receiving a block of data
* and sending it away.
*/
FileCopyJob::FileCopyJob(FileCopyJobPrivate &dd)
: Job(dd)
{
Q_D(FileCopyJob);
QTimer::singleShot(0, this, [d]() {
d->slotStart();
});
}
void FileCopyJobPrivate::slotStart()
{
Q_Q(FileCopyJob);
if (!m_move) {
JobPrivate::emitCopying(q, m_src, m_dest);
} else {
JobPrivate::emitMoving(q, m_src, m_dest);
}
if (m_move) {
// The if() below must be the same as the one in startBestCopyMethod
if (isSrcDestSameWorkerProcess(m_src, m_dest)) {
startRenameJob(m_src);
return;
} else if (m_src.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) {
startRenameJob(m_dest);
return;
} else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_src)) {
startRenameJob(m_src);
return;
}
// No fast-move available, use copy + del.
}
startBestCopyMethod();
}
void FileCopyJobPrivate::startBestCopyMethod()
{
if (isSrcDestSameWorkerProcess(m_src, m_dest)) {
startCopyJob();
} else if (m_src.isLocalFile() && KProtocolManager::canCopyFromFile(m_dest)) {
startCopyJob(m_dest);
} else if (m_dest.isLocalFile() && KProtocolManager::canCopyToFile(m_src) && !KIO::Scheduler::isWorkerOnHoldFor(m_src)) {
startCopyJob(m_src);
} else {
startDataPump();
}
}
FileCopyJob::~FileCopyJob()
{
}
void FileCopyJob::setSourceSize(KIO::filesize_t size)
{
Q_D(FileCopyJob);
d->m_sourceSize = size;
if (size != (KIO::filesize_t)-1) {
setTotalAmount(KJob::Bytes, size);
}
}
void FileCopyJob::setModificationTime(const QDateTime &mtime)
{
Q_D(FileCopyJob);
d->m_modificationTime = mtime;
}
QUrl FileCopyJob::srcUrl() const
{
return d_func()->m_src;
}
QUrl FileCopyJob::destUrl() const
{
return d_func()->m_dest;
}
void FileCopyJobPrivate::startCopyJob()
{
startCopyJob(m_src);
}
void FileCopyJobPrivate::startCopyJob(const QUrl &workerUrl)
{
Q_Q(FileCopyJob);
// qDebug();
KIO_ARGS << m_src << m_dest << m_permissions << (qint8)(m_flags & Overwrite);
auto job = new DirectCopyJob(workerUrl, packedArgs);
m_copyJob = job;
m_copyJob->setParentJob(q);
if (m_modificationTime.isValid()) {
m_copyJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804
}
q->addSubjob(m_copyJob);
connectSubjob(m_copyJob);
q->connect(job, &DirectCopyJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) {
slotCanResume(job, offset);
});
}
void FileCopyJobPrivate::startRenameJob(const QUrl &workerUrl)
{
Q_Q(FileCopyJob);
m_mustChmod = true; // CMD_RENAME by itself doesn't change permissions
KIO_ARGS << m_src << m_dest << (qint8)(m_flags & Overwrite);
m_moveJob = SimpleJobPrivate::newJobNoUi(workerUrl, CMD_RENAME, packedArgs);
m_moveJob->setParentJob(q);
if (m_modificationTime.isValid()) {
m_moveJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804
}
q->addSubjob(m_moveJob);
connectSubjob(m_moveJob);
}
void FileCopyJobPrivate::connectSubjob(SimpleJob *job)
{
Q_Q(FileCopyJob);
q->connect(job, &KJob::totalSize, q, [q](KJob *job, qulonglong totalSize) {
Q_UNUSED(job);
if (totalSize != q->totalAmount(KJob::Bytes)) {
q->setTotalAmount(KJob::Bytes, totalSize);
}
});
q->connect(job, &KJob::processedSize, q, [q, this](const KJob *job, qulonglong processedSize) {
if (job == m_copyJob) {
m_bFileCopyInProgress = processedSize > 0;
}
q->setProcessedAmount(KJob::Bytes, processedSize);
});
q->connect(job, &KJob::percentChanged, q, [q](KJob *, ulong percent) {
if (percent > q->percent()) {
q->setPercent(percent);
}
});
if (q->isSuspended()) {
job->suspend();
}
}
bool FileCopyJob::doSuspend()
{
Q_D(FileCopyJob);
if (d->m_moveJob) {
d->m_moveJob->suspend();
}
if (d->m_copyJob) {
d->m_copyJob->suspend();
}
if (d->m_getJob) {
d->m_getJob->suspend();
}
if (d->m_putJob) {
d->m_putJob->suspend();
}
Job::doSuspend();
return true;
}
bool FileCopyJob::doResume()
{
Q_D(FileCopyJob);
if (d->m_moveJob) {
d->m_moveJob->resume();
}
if (d->m_copyJob) {
d->m_copyJob->resume();
}
if (d->m_getJob) {
d->m_getJob->resume();
}
if (d->m_putJob) {
d->m_putJob->resume();
}
Job::doResume();
return true;
}
void FileCopyJobPrivate::startDataPump()
{
Q_Q(FileCopyJob);
// qDebug();
m_canResume = false;
m_resumeAnswerSent = false;
m_getJob = nullptr; // for now
m_putJob = put(m_dest, m_permissions, (m_flags | HideProgressInfo) /* no GUI */);
m_putJob->setParentJob(q);
// qDebug() << "m_putJob=" << m_putJob << "m_dest=" << m_dest;
if (m_modificationTime.isValid()) {
m_putJob->setModificationTime(m_modificationTime);
}
// The first thing the put job will tell us is whether we can
// resume or not (this is always emitted)
q->connect(m_putJob, &KIO::TransferJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) {
slotCanResume(job, offset);
});
q->connect(m_putJob, &KIO::TransferJob::dataReq, q, [this](KIO::Job *job, QByteArray &data) {
slotDataReq(job, data);
});
q->addSubjob(m_putJob);
}
void FileCopyJobPrivate::slotCanResume(KIO::Job *job, KIO::filesize_t offset)
{
Q_Q(FileCopyJob);
if (job == m_getJob) {
// Cool, the get job said ok, we can resume
m_canResume = true;
// qDebug() << "'can resume' from the GET job -> we can resume";
jobWorker(m_getJob)->setOffset(jobWorker(m_putJob)->offset());
return;
}
if (job == m_putJob || job == m_copyJob) {
// qDebug() << "'can resume' from PUT job. offset=" << KIO::number(offset);
if (offset == 0) {
m_resumeAnswerSent = true; // No need for an answer
} else {
KIO::Job *kioJob = q->parentJob() ? q->parentJob() : q;
auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(kioJob);
if (!KProtocolManager::autoResume() && !(m_flags & Overwrite) && askUserActionInterface) {
auto renameSignal = &AskUserActionInterface::askUserRenameResult;
q->connect(askUserActionInterface, renameSignal, q, [=, this](KIO::RenameDialog_Result result, const QUrl &, const KJob *askJob) {
Q_ASSERT(kioJob == askJob);
// Only receive askUserRenameResult once per rename dialog
QObject::disconnect(askUserActionInterface, renameSignal, q, nullptr);
processCanResumeResult(job, result, offset);
});
// Ask confirmation about resuming previous transfer
askUserActionInterface->askUserRename(kioJob,
i18n("File Already Exists"),
m_src,
m_dest,
RenameDialog_Options(RenameDialog_Overwrite | RenameDialog_Resume | RenameDialog_NoRename),
m_sourceSize,
offset);
return;
}
}
processCanResumeResult(job, //
Result_Resume, // The default is to resume
offset);
return;
}
qCWarning(KIO_CORE) << "unknown job=" << job << "m_getJob=" << m_getJob << "m_putJob=" << m_putJob;
}
void FileCopyJobPrivate::processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset)
{
Q_Q(FileCopyJob);
if (result == Result_Overwrite || (m_flags & Overwrite)) {
offset = 0;
} else if (result == Result_Cancel) {
if (job == m_putJob) {
m_putJob->kill(FileCopyJob::Quietly);
q->removeSubjob(m_putJob);
m_putJob = nullptr;
} else {
m_copyJob->kill(FileCopyJob::Quietly);
q->removeSubjob(m_copyJob);
m_copyJob = nullptr;
}
q->setError(ERR_USER_CANCELED);
q->emitResult();
return;
}
if (job == m_copyJob) {
jobWorker(m_copyJob)->sendResumeAnswer(offset != 0);
return;
}
if (job == m_putJob) {
m_getJob = KIO::get(m_src, NoReload, HideProgressInfo /* no GUI */);
m_getJob->setParentJob(q);
// qDebug() << "m_getJob=" << m_getJob << m_src;
m_getJob->addMetaData(QStringLiteral("AllowCompressedPage"), QStringLiteral("false"));
// Set size in subjob. This helps if the worker doesn't emit totalSize.
if (m_sourceSize != (KIO::filesize_t)-1) {
m_getJob->setTotalAmount(KJob::Bytes, m_sourceSize);
}
if (offset) {
// qDebug() << "Setting metadata for resume to" << (unsigned long) offset;
m_getJob->addMetaData(QStringLiteral("range-start"), KIO::number(offset));
// Might or might not get emitted
q->connect(m_getJob, &KIO::TransferJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) {
slotCanResume(job, offset);
});
}
jobWorker(m_putJob)->setOffset(offset);
m_putJob->d_func()->internalSuspend();
q->addSubjob(m_getJob);
connectSubjob(m_getJob); // Progress info depends on get
m_getJob->d_func()->internalResume(); // Order a beer
q->connect(m_getJob, &KIO::TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) {
slotData(job, data);
});
q->connect(m_getJob, &KIO::TransferJob::mimeTypeFound, q, [this](KIO::Job *job, const QString &type) {
slotMimetype(job, type);
});
}
}
void FileCopyJobPrivate::slotData(KIO::Job *, const QByteArray &data)
{
// qDebug() << "data size:" << data.size();
Q_ASSERT(m_putJob);
if (!m_putJob) {
return; // Don't crash
}
m_getJob->d_func()->internalSuspend();
m_putJob->d_func()->internalResume(); // Drink the beer
m_buffer += data;
// On the first set of data incoming, we tell the "put" worker about our
// decision about resuming
if (!m_resumeAnswerSent) {
m_resumeAnswerSent = true;
// qDebug() << "(first time) -> send resume answer " << m_canResume;
jobWorker(m_putJob)->sendResumeAnswer(m_canResume);
}
}
void FileCopyJobPrivate::slotDataReq(KIO::Job *, QByteArray &data)
{
Q_Q(FileCopyJob);
// qDebug();
if (!m_resumeAnswerSent && !m_getJob) {
// This can't happen
q->setError(ERR_INTERNAL);
q->setErrorText(QStringLiteral("'Put' job did not send canResume or 'Get' job did not send data!"));
m_putJob->kill(FileCopyJob::Quietly);
q->removeSubjob(m_putJob);
m_putJob = nullptr;
q->emitResult();
return;
}
if (m_getJob) {
m_getJob->d_func()->internalResume(); // Order more beer
m_putJob->d_func()->internalSuspend();
}
data = m_buffer;
m_buffer = QByteArray();
}
void FileCopyJobPrivate::slotMimetype(KIO::Job *, const QString &type)
{
Q_Q(FileCopyJob);
Q_EMIT q->mimeTypeFound(q, type);
}
void FileCopyJob::slotResult(KJob *job)
{
Q_D(FileCopyJob);
// qDebug() << "this=" << this << "job=" << job;
removeSubjob(job);
// If result comes from copyjob then we are not writing anymore.
if (job == d->m_copyJob) {
d->m_bFileCopyInProgress = false;
}
// Did job have an error ?
if (job->error()) {
if ((job == d->m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) {
d->m_moveJob = nullptr;
d->startBestCopyMethod();
return;
} else if ((job == d->m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) {
d->m_copyJob = nullptr;
d->startDataPump();
return;
} else if (job == d->m_getJob) {
d->m_getJob = nullptr;
if (d->m_putJob) {
d->m_putJob->kill(Quietly);
removeSubjob(d->m_putJob);
}
} else if (job == d->m_putJob) {
d->m_putJob = nullptr;
if (d->m_getJob) {
d->m_getJob->kill(Quietly);
removeSubjob(d->m_getJob);
}
} else if (job == d->m_chmodJob) {
d->m_chmodJob = nullptr;
if (d->m_delJob) {
d->m_delJob->kill(Quietly);
removeSubjob(d->m_delJob);
}
} else if (job == d->m_delJob) {
d->m_delJob = nullptr;
if (d->m_chmodJob) {
d->m_chmodJob->kill(Quietly);
removeSubjob(d->m_chmodJob);
}
}
setError(job->error());
setErrorText(job->errorText());
emitResult();
return;
}
if (d->m_mustChmod) {
// If d->m_permissions == -1, keep the default permissions
if (d->m_permissions != -1) {
d->m_chmodJob = chmod(d->m_dest, d->m_permissions);
addSubjob(d->m_chmodJob);
}
d->m_mustChmod = false;
}
if (job == d->m_moveJob) {
d->m_moveJob = nullptr; // Finished
}
if (job == d->m_copyJob) {
d->m_copyJob = nullptr;
if (d->m_move) {
d->m_delJob = file_delete(d->m_src, HideProgressInfo /*no GUI*/); // Delete source
addSubjob(d->m_delJob);
}
}
if (job == d->m_getJob) {
// qDebug() << "m_getJob finished";
d->m_getJob = nullptr; // No action required
if (d->m_putJob) {
d->m_putJob->d_func()->internalResume();
}
}
if (job == d->m_putJob) {
// qDebug() << "m_putJob finished";
d->m_putJob = nullptr;
if (d->m_getJob) {
// The get job is still running, probably after emitting data(QByteArray())
// and before we receive its finished().
d->m_getJob->d_func()->internalResume();
}
if (d->m_move) {
d->m_delJob = file_delete(d->m_src, HideProgressInfo /*no GUI*/); // Delete source
addSubjob(d->m_delJob);
}
}
if (job == d->m_delJob) {
d->m_delJob = nullptr; // Finished
}
if (job == d->m_chmodJob) {
d->m_chmodJob = nullptr; // Finished
}
if (!hasSubjobs()) {
emitResult();
}
}
bool FileCopyJob::doKill()
{
#ifdef Q_OS_WIN
// TODO Use SetConsoleCtrlHandler on Windows or similar behaviour.
// https://stackoverflow.com/questions/2007516/is-there-a-posix-sigterm-alternative-on-windows-a-gentle-kill-for-console-ap
// https://danielkaes.wordpress.com/2009/06/04/how-to-catch-kill-events-with-python/
// https://phabricator.kde.org/D25117#566107
Q_D(FileCopyJob);
// If we are interrupted in the middle of file copying,
// we may end up with corrupted file at the destination.
// It is better to clean up this file. If a copy is being
// made as part of move operation then delete the dest only if
// source file is intact (m_delJob == NULL).
if (d->m_bFileCopyInProgress && d->m_copyJob && d->m_dest.isLocalFile()) {
if (d->m_flags & Overwrite) {
QFile::remove(d->m_dest.toLocalFile() + QStringLiteral(".part"));
} else {
QFile::remove(d->m_dest.toLocalFile());
}
}
#endif
return Job::doKill();
}
FileCopyJob *KIO::file_copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags)
{
return FileCopyJobPrivate::newJob(src, dest, permissions, false, flags);
}
FileCopyJob *KIO::file_move(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags)
{
FileCopyJob *job = FileCopyJobPrivate::newJob(src, dest, permissions, true, flags);
if (job->uiDelegateExtension()) {
job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent);
}
return job;
}
#include "moc_filecopyjob.cpp"
@@ -0,0 +1,143 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_FILECOPYJOB_H
#define KIO_FILECOPYJOB_H
#include "job_base.h"
#include <kio/global.h> // filesize_t
namespace KIO
{
class FileCopyJobPrivate;
/**
* @class KIO::FileCopyJob filecopyjob.h <KIO/FileCopyJob>
*
* The FileCopyJob copies data from one place to another.
* @see KIO::file_copy()
* @see KIO::file_move()
*/
class KIOCORE_EXPORT FileCopyJob : public Job
{
Q_OBJECT
public:
~FileCopyJob() override;
/**
* If you know the size of the source file, call this method
* to inform this job. It will be displayed in the "resume" dialog.
* @param size the size of the source file
*/
void setSourceSize(KIO::filesize_t size);
/**
* Sets the modification time of the file
*
* Note that this is ignored if a direct copy (WorkerBase::copy) can be done,
* in which case the mtime of the source is applied to the destination (if the protocol
* supports the concept).
*/
void setModificationTime(const QDateTime &mtime);
/**
* Returns the source URL.
* @return the source URL
*/
QUrl srcUrl() const;
/**
* Returns the destination URL.
* @return the destination URL
*/
QUrl destUrl() const;
bool doSuspend() override;
bool doResume() override;
bool doKill() override;
Q_SIGNALS:
/**
* MIME type determined during a file copy.
* This is never emitted during a move, and might not be emitted during
* a file copy, depending on the worker. But when a get and a put are
* being used (which is the common case), this signal forwards the
* MIME type information from the get job.
*
* @param job the job that emitted this signal
* @param mimeType the MIME type
* @since 5.78
*/
void mimeTypeFound(KIO::Job *job, const QString &mimeType);
protected Q_SLOTS:
/**
* Called whenever a subjob finishes.
* @param job the job that emitted this signal
*/
void slotResult(KJob *job) override;
protected:
KIOCORE_NO_EXPORT explicit FileCopyJob(FileCopyJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(FileCopyJob)
};
/**
* Copy a single file.
*
* Uses either WorkerBase::copy() if the worker supports that
* or get() and put() otherwise.
*
* @param src Where to get the file
* @param dest Where to put the file
* @param permissions the file mode permissions to set on @p dest; if this is -1
* (the default) no special permissions will be set on @p dest, i.e. it'll have
* the default system permissions for newly created files, and the owner and group
* permissions are not preserved.
* @param flags Can be @ref JobFlag::HideProgressInfo, Overwrite and Resume here
* WARNING: Setting @ref JobFlag::Resume means that the data will be appended to
* @p dest if @p dest exists
* @return the job handling the operation
*/
KIOCORE_EXPORT FileCopyJob *file_copy(const QUrl &src, const QUrl &dest, int permissions = -1, JobFlags flags = DefaultFlags);
/**
* Overload for catching code mistakes. Do NOT call this method (it is not implemented),
* insert a value for permissions (-1 by default) before the JobFlags.
*/
FileCopyJob *file_copy(const QUrl &src, const QUrl &dest, JobFlags flags) Q_DECL_EQ_DELETE; // not implemented - on purpose.
/**
* Move a single file.
*
* Use either WorkerBase::rename() if the worker supports that,
* or copy() and del() otherwise, or eventually get() & put() & del()
*
* @param src Where to get the file
* @param dest Where to put the file
* @param permissions the file mode permissions to set on @p dest; if this is -1
* (the default), no special permissions are set on @p dest, i.e. it'll have
* the default system permissions for newly created files, and the owner and group
* permissions are not preserved.
* @param flags Can be HideProgressInfo, Overwrite and Resume here
* WARNING: Setting @ref JobFlag::Resume means that the data will be appended to
* @p dest if @p dest exists
* @return the job handling the operation
*/
KIOCORE_EXPORT FileCopyJob *file_move(const QUrl &src, const QUrl &dest, int permissions = -1, JobFlags flags = DefaultFlags);
/**
* Overload for catching code mistakes. Do NOT call this method (it is not implemented),
* insert a value for permissions (-1 by default) before the JobFlags.
*/
FileCopyJob *file_move(const QUrl &src, const QUrl &dest, JobFlags flags) Q_DECL_EQ_DELETE; // not implemented - on purpose.
}
#endif
@@ -0,0 +1,246 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Allan Sandfeld Jensen <kde@carewolf.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "filejob.h"
#include "job_p.h"
#include "worker_p.h"
class KIO::FileJobPrivate : public KIO::SimpleJobPrivate
{
public:
FileJobPrivate(const QUrl &url, const QByteArray &packedArgs)
: SimpleJobPrivate(url, CMD_OPEN, packedArgs)
, m_open(false)
, m_size(0)
{
}
bool m_open;
QString m_mimetype;
KIO::filesize_t m_size;
void slotRedirection(const QUrl &url);
void slotData(const QByteArray &data);
void slotMimetype(const QString &mimetype);
void slotOpen();
void slotWritten(KIO::filesize_t);
void slotFinished();
void slotPosition(KIO::filesize_t);
void slotTruncated(KIO::filesize_t);
void slotTotalSize(KIO::filesize_t);
/**
* @internal
* Called by the scheduler when a @p worker gets to
* work on this job.
* @param worker the worker that starts working on this job
*/
void start(Worker *worker) override;
Q_DECLARE_PUBLIC(FileJob)
static inline FileJob *newJob(const QUrl &url, const QByteArray &packedArgs)
{
FileJob *job = new FileJob(*new FileJobPrivate(url, packedArgs));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
return job;
}
};
using namespace KIO;
FileJob::FileJob(FileJobPrivate &dd)
: SimpleJob(dd)
{
}
FileJob::~FileJob()
{
}
void FileJob::read(KIO::filesize_t size)
{
Q_D(FileJob);
if (!d->m_open) {
return;
}
KIO_ARGS << size;
d->m_worker->send(CMD_READ, packedArgs);
}
void FileJob::write(const QByteArray &_data)
{
Q_D(FileJob);
if (!d->m_open) {
return;
}
d->m_worker->send(CMD_WRITE, _data);
}
void FileJob::seek(KIO::filesize_t offset)
{
Q_D(FileJob);
if (!d->m_open) {
return;
}
KIO_ARGS << KIO::filesize_t(offset);
d->m_worker->send(CMD_SEEK, packedArgs);
}
void FileJob::truncate(KIO::filesize_t length)
{
Q_D(FileJob);
if (!d->m_open) {
return;
}
KIO_ARGS << KIO::filesize_t(length);
d->m_worker->send(CMD_TRUNCATE, packedArgs);
}
void FileJob::close()
{
Q_D(FileJob);
if (!d->m_open) {
return;
}
d->m_worker->send(CMD_CLOSE);
// ### close?
}
KIO::filesize_t FileJob::size()
{
Q_D(FileJob);
if (!d->m_open) {
return 0;
}
return d->m_size;
}
// Worker sends data
void FileJobPrivate::slotData(const QByteArray &_data)
{
Q_Q(FileJob);
Q_EMIT q_func()->data(q, _data);
}
void FileJobPrivate::slotRedirection(const QUrl &url)
{
Q_Q(FileJob);
// qDebug() << url;
Q_EMIT q->redirection(q, url);
}
void FileJobPrivate::slotMimetype(const QString &type)
{
Q_Q(FileJob);
m_mimetype = type;
Q_EMIT q->mimeTypeFound(q, m_mimetype);
}
void FileJobPrivate::slotPosition(KIO::filesize_t pos)
{
Q_Q(FileJob);
Q_EMIT q->position(q, pos);
}
void FileJobPrivate::slotTruncated(KIO::filesize_t length)
{
Q_Q(FileJob);
Q_EMIT q->truncated(q, length);
}
void FileJobPrivate::slotTotalSize(KIO::filesize_t t_size)
{
m_size = t_size;
Q_Q(FileJob);
q->setTotalAmount(KJob::Bytes, m_size);
}
void FileJobPrivate::slotOpen()
{
Q_Q(FileJob);
m_open = true;
Q_EMIT q->open(q);
}
void FileJobPrivate::slotWritten(KIO::filesize_t t_written)
{
Q_Q(FileJob);
Q_EMIT q->written(q, t_written);
}
void FileJobPrivate::slotFinished()
{
Q_Q(FileJob);
// qDebug() << this << m_url;
m_open = false;
Q_EMIT q->fileClosed(q);
// Return worker to the scheduler
workerDone();
// Scheduler::doJob(this);
q->emitResult();
}
void FileJobPrivate::start(Worker *worker)
{
Q_Q(FileJob);
q->connect(worker, &KIO::WorkerInterface::data, q, [this](const QByteArray &ba) {
slotData(ba);
});
q->connect(worker, &KIO::WorkerInterface::redirection, q, [this](const QUrl &url) {
slotRedirection(url);
});
q->connect(worker, &KIO::WorkerInterface::mimeType, q, [this](const QString &mimeType) {
slotMimetype(mimeType);
});
q->connect(worker, &KIO::WorkerInterface::open, q, [this]() {
slotOpen();
});
q->connect(worker, &KIO::WorkerInterface::finished, q, [this]() {
slotFinished();
});
q->connect(worker, &KIO::WorkerInterface::position, q, [this](KIO::filesize_t pos) {
slotPosition(pos);
});
q->connect(worker, &KIO::WorkerInterface::truncated, q, [this](KIO::filesize_t length) {
slotTruncated(length);
});
q->connect(worker, &KIO::WorkerInterface::written, q, [this](KIO::filesize_t dataWritten) {
slotWritten(dataWritten);
});
q->connect(worker, &KIO::WorkerInterface::totalSize, q, [this](KIO::filesize_t size) {
slotTotalSize(size);
});
SimpleJobPrivate::start(worker);
}
FileJob *KIO::open(const QUrl &url, QIODevice::OpenMode mode)
{
// Send decoded path and encoded query
KIO_ARGS << url << mode;
return FileJobPrivate::newJob(url, packedArgs);
}
#include "moc_filejob.cpp"
@@ -0,0 +1,199 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Allan Sandfeld Jensen <kde@carewolf.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_FILEJOB_H
#define KIO_FILEJOB_H
#include "kiocore_export.h"
#include "simplejob.h"
namespace KIO
{
class FileJobPrivate;
/**
* @class KIO::FileJob filejob.h <KIO/FileJob>
*
* The file-job is an asynchronous version of normal file handling.
* It allows block-wise reading and writing, and allows seeking and truncation. Results are returned through signals.
*
* Should always be created using KIO::open(const QUrl&, QIODevice::OpenMode).
*/
class KIOCORE_EXPORT FileJob : public SimpleJob
{
Q_OBJECT
public:
~FileJob() override;
/**
* This function attempts to read up to \p size bytes from the URL passed to
* KIO::open() and returns the bytes received via the data() signal.
*
* The read operation commences at the current file offset, and the file
* offset is incremented by the number of bytes read, but this change in the
* offset does not result in the position() signal being emitted.
*
* If the current file offset is at or past the end of file (i.e. EOD), no
* bytes are read, and the data() signal returns an empty QByteArray.
*
* On error the data() signal is not emitted. To catch errors please connect
* to the result() signal.
*
* @param size the requested amount of data to read
*
*/
void read(KIO::filesize_t size);
/**
* This function attempts to write all the bytes in \p data to the URL
* passed to KIO::open() and returns the bytes written received via the
* written() signal.
*
* The write operation commences at the current file offset, and the file
* offset is incremented by the number of bytes read, but this change in the
* offset does not result in the position() being emitted.
*
* On error the written() signal is not emitted. To catch errors please
* connect to the result() signal.
*
* @param data the data to write
*/
void write(const QByteArray &data);
/**
* Closes the file KIO worker.
*
* The worker emits close() and result().
*/
void close();
/**
* Seek
*
* The worker emits position() on successful seek to the specified \p offset.
*
* On error the position() signal is not emitted. To catch errors please
* connect to the result() signal.
*
* @param offset the position from start to go to
*/
void seek(KIO::filesize_t offset);
/**
* Truncate
*
* The worker emits truncated() on successful truncation to the specified \p length.
*
* On error the truncated() signal is not emitted. To catch errors please
* connect to the result() signal.
*
* @param length the desired length to truncate to
* @since 5.66
*/
void truncate(KIO::filesize_t length);
/**
* Size
*
* @return the file size
*/
KIO::filesize_t size();
Q_SIGNALS:
/**
* Data from the worker has arrived. Emitted after read().
*
* Unless a read() request was sent for 0 bytes, End of data (EOD) has been
* reached if data.size() == 0
*
* @param job the job that emitted this signal
* @param data data received from the worker.
*
*/
void data(KIO::Job *job, const QByteArray &data);
/**
* Signals the file is a redirection.
* Follow this url manually to reach data
* @param job the job that emitted this signal
* @param url the new URL
*/
void redirection(KIO::Job *job, const QUrl &url);
/**
* MIME type determined.
* @param job the job that emitted this signal
* @param mimeType the MIME type
* @since 5.78
*/
void mimeTypeFound(KIO::Job *job, const QString &mimeType);
/**
* File is open, metadata has been determined and the
* file KIO worker is ready to receive commands.
* @param job the job that emitted this signal
*/
void open(KIO::Job *job);
/**
* \p written bytes were written to the file. Emitted after write().
* @param job the job that emitted this signal
* @param written bytes written.
*/
void written(KIO::Job *job, KIO::filesize_t written);
/**
* Signals that the file is closed and will accept no more commands.
*
* @param job the job that emitted this signal
*
* @since 5.79
*/
void fileClosed(KIO::Job *job);
/**
* The file has reached this position. Emitted after seek().
* @param job the job that emitted this signal
* @param offset the new position
*/
void position(KIO::Job *job, KIO::filesize_t offset);
/**
* The file has been truncated to this point. Emitted after truncate().
* @param job the job that emitted this signal
* @param length the new length of the file
* @since 5.66
*/
void truncated(KIO::Job *job, KIO::filesize_t length);
protected:
KIOCORE_NO_EXPORT explicit FileJob(FileJobPrivate &dd);
private:
Q_DECLARE_PRIVATE(FileJob)
};
/**
* Open ( random access I/O )
*
* The file-job emits open() when opened
*
* On error the open() signal is not emitted. To catch errors please
* connect to the result() signal.
*
* @param url the URL of the file
* @param mode the access privileges: see \ref OpenMode
*
* @return The file-handling job. It will never return 0. Errors are handled asynchronously
* (emitted as signals).
*/
KIOCORE_EXPORT FileJob *open(const QUrl &url, QIODevice::OpenMode mode);
} // namespace
#endif
@@ -0,0 +1,91 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "filesystemfreespacejob.h"
#include "job.h"
#include "job_p.h"
#include <worker_p.h>
using namespace KIO;
class KIO::FileSystemFreeSpaceJobPrivate : public SimpleJobPrivate
{
public:
FileSystemFreeSpaceJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs)
: SimpleJobPrivate(url, command, packedArgs)
{
}
/**
* @internal
* Called by the scheduler when a @p worker gets to
* work on this job.
* @param worker the worker that starts working on this job
*/
void start(Worker *worker) override;
Q_DECLARE_PUBLIC(FileSystemFreeSpaceJob)
static inline FileSystemFreeSpaceJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs)
{
FileSystemFreeSpaceJob *job = new FileSystemFreeSpaceJob(*new FileSystemFreeSpaceJobPrivate(url, command, packedArgs));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
return job;
}
KIO::filesize_t size = -1;
KIO::filesize_t availableSize = -1;
};
FileSystemFreeSpaceJob::FileSystemFreeSpaceJob(FileSystemFreeSpaceJobPrivate &dd)
: SimpleJob(dd)
{
}
FileSystemFreeSpaceJob::~FileSystemFreeSpaceJob()
{
}
KIO::filesize_t FileSystemFreeSpaceJob::size() const
{
Q_D(const FileSystemFreeSpaceJob);
return d->size;
}
KIO::filesize_t FileSystemFreeSpaceJob::availableSize() const
{
Q_D(const FileSystemFreeSpaceJob);
return d->availableSize;
}
void FileSystemFreeSpaceJobPrivate::start(Worker *worker)
{
SimpleJobPrivate::start(worker);
}
void FileSystemFreeSpaceJob::slotFinished()
{
Q_D(FileSystemFreeSpaceJob);
const QString totalStr = queryMetaData(QStringLiteral("total"));
const QString availableStr = queryMetaData(QStringLiteral("available"));
if (availableStr.isEmpty()) { // CopyJob only cares for available. "total" is optional
setError(KIO::ERR_UNSUPPORTED_ACTION);
}
d->size = totalStr.toULongLong();
d->availableSize = availableStr.toULongLong();
// Return worker to the scheduler
SimpleJob::slotFinished();
}
KIO::FileSystemFreeSpaceJob *KIO::fileSystemFreeSpace(const QUrl &url)
{
KIO_ARGS << url;
return FileSystemFreeSpaceJobPrivate::newJob(url, CMD_FILESYSTEMFREESPACE, packedArgs);
}
#include "moc_filesystemfreespacejob.cpp"
@@ -0,0 +1,62 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef FILESYSTEMFREESPACEJOB_H
#define FILESYSTEMFREESPACEJOB_H
#include "kiocore_export.h"
#include "simplejob.h"
namespace KIO
{
class FileSystemFreeSpaceJobPrivate;
/**
* @class KIO::FileSystemFreeSpaceJob filesystemfreespacejob.h <KIO/FileSystemFreeSpaceJob>
*
* A KIO job that retrieves the total and available size of a filesystem.
* @since 5.3
*/
class KIOCORE_EXPORT FileSystemFreeSpaceJob : public SimpleJob
{
Q_OBJECT
public:
~FileSystemFreeSpaceJob() override;
/**
* Get total amount of space
* @since 6.0
*/
KIO::filesize_t size() const;
/**
* Get available amount of space
* @since 6.0
*/
KIO::filesize_t availableSize() const;
public:
KIOCORE_NO_EXPORT explicit FileSystemFreeSpaceJob(FileSystemFreeSpaceJobPrivate &dd);
private:
void slotFinished() override;
Q_DECLARE_PRIVATE(FileSystemFreeSpaceJob)
};
/**
* Get a filesystem's total and available space.
*
* @param url Url to the filesystem.
* @return the job handling the operation.
*/
KIOCORE_EXPORT FileSystemFreeSpaceJob *fileSystemFreeSpace(const QUrl &url);
}
#endif /* FILESYSTEMFREESPACEJOB_H */
@@ -0,0 +1,475 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2004 Kevin Ottens <ervin@ipsquad.net>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "forwardingworkerbase.h"
#include "../utils_p.h"
#include "deletejob.h"
#include "filecopyjob.h"
#include "kiocoredebug.h"
#include "listjob.h"
#include "mimetypejob.h"
#include "mkdirjob.h"
#include "statjob.h"
#include "transferjob.h"
#include <QEventLoop>
#include <QMimeDatabase>
namespace KIO
{
class ForwardingWorkerBasePrivate
{
public:
ForwardingWorkerBasePrivate(const QByteArray &protocol, QObject *eventLoopParent, ForwardingWorkerBase *qq)
: q(qq)
, m_protocol(QString::fromUtf8(protocol))
, eventLoop(eventLoopParent)
{
}
ForwardingWorkerBase *const q;
const QString m_protocol;
QUrl m_processedURL;
QUrl m_requestedURL;
bool internalRewriteUrl(const QUrl &url, QUrl &newURL);
void connectJob(Job *job);
void connectSimpleJob(SimpleJob *job);
void connectListJob(ListJob *job);
void connectTransferJob(TransferJob *job);
void _k_slotResult(KJob *job);
void _k_slotWarning(KJob *job, const QString &msg) const;
void _k_slotInfoMessage(KJob *job, const QString &msg) const;
void _k_slotTotalSize(KJob *job, qulonglong size) const;
void _k_slotProcessedSize(KJob *job, qulonglong size) const;
void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond) const;
// KIO::SimpleJob subclasses
void _k_slotRedirection(KIO::Job *job, const QUrl &url);
// KIO::ListJob
void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) const;
// KIO::TransferJob
void _k_slotData(KIO::Job *job, const QByteArray &data) const;
void _k_slotDataReq(KIO::Job *job, QByteArray &data) const;
void _k_slotMimetype(KIO::Job *job, const QString &type) const;
void _k_slotCanResume(KIO::Job *job, KIO::filesize_t offset) const;
[[nodiscard]] WorkerResult loopResult()
{
eventLoop.exec();
return m_pendingResult;
}
private:
// These are intentionally private to force us to go through [[nodiscard]] helper functions, lest we forget to retrieve the result.
QEventLoop eventLoop;
WorkerResult m_pendingResult = WorkerResult::pass();
};
ForwardingWorkerBase::ForwardingWorkerBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket)
: WorkerBase(protocol, poolSocket, appSocket)
, d(new ForwardingWorkerBasePrivate(protocol, this, this))
{
}
ForwardingWorkerBase::~ForwardingWorkerBase() = default;
bool ForwardingWorkerBasePrivate::internalRewriteUrl(const QUrl &url, QUrl &newURL)
{
bool result = true;
if (url.scheme() == m_protocol) {
result = q->rewriteUrl(url, newURL);
} else {
newURL = url;
}
m_processedURL = newURL;
m_requestedURL = url;
return result;
}
void ForwardingWorkerBase::adjustUDSEntry(KIO::UDSEntry &entry, UDSEntryCreationMode creationMode) const
{
const bool listing = (creationMode == UDSEntryCreationInListDir);
// qDebug() << "listing==" << listing;
const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
QString mimetype = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
QUrl url;
const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
const bool url_found = !urlStr.isEmpty();
if (url_found) {
url = QUrl(urlStr);
QUrl new_url(d->m_requestedURL);
if (listing) {
new_url.setPath(Utils::concatPaths(new_url.path(), url.fileName()));
}
// ## Didn't find a way to use an iterator instead of re-doing a key lookup
entry.replace(KIO::UDSEntry::UDS_URL, new_url.toString());
// qDebug() << "URL =" << url;
// qDebug() << "New URL =" << new_url;
}
if (mimetype.isEmpty()) {
QUrl new_url(d->m_processedURL);
if (url_found && listing) {
new_url.setPath(Utils::concatPaths(new_url.path(), url.fileName()));
} else if (listing) {
new_url.setPath(Utils::concatPaths(new_url.path(), name));
}
QMimeDatabase db;
mimetype = db.mimeTypeForUrl(new_url).name();
entry.replace(KIO::UDSEntry::UDS_MIME_TYPE, mimetype);
// qDebug() << "New MIME type = " << mimetype;
}
if (d->m_processedURL.isLocalFile()) {
QUrl new_url(d->m_processedURL);
if (listing) {
new_url.setPath(Utils::concatPaths(new_url.path(), name));
}
entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, new_url.toLocalFile());
}
}
QUrl ForwardingWorkerBase::processedUrl() const
{
return d->m_processedURL;
}
QUrl ForwardingWorkerBase::requestedUrl() const
{
return d->m_requestedURL;
}
WorkerResult ForwardingWorkerBase::get(const QUrl &url)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::TransferJob *job = KIO::get(new_url, NoReload, HideProgressInfo);
d->connectTransferJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::put(const QUrl &url, int permissions, JobFlags flags)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::TransferJob *job = KIO::put(new_url, permissions, flags | HideProgressInfo);
d->connectTransferJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::stat(const QUrl &url)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::SimpleJob *job = KIO::stat(new_url, KIO::HideProgressInfo);
d->connectSimpleJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::mimetype(const QUrl &url)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::TransferJob *job = KIO::mimetype(new_url, KIO::HideProgressInfo);
d->connectTransferJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::listDir(const QUrl &url)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::ListJob *job = KIO::listDir(new_url, KIO::HideProgressInfo);
d->connectListJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::mkdir(const QUrl &url, int permissions)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::SimpleJob *job = KIO::mkdir(new_url, permissions);
d->connectSimpleJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::rename(const QUrl &src, const QUrl &dest, JobFlags flags)
{
qCDebug(KIO_CORE) << "rename" << src << dest;
QUrl new_src;
QUrl new_dest;
if (!d->internalRewriteUrl(src, new_src)) {
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString());
}
if (d->internalRewriteUrl(dest, new_dest)) {
KIO::Job *job = KIO::rename(new_src, new_dest, flags);
d->connectJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_MALFORMED_URL, dest.toDisplayString());
}
WorkerResult ForwardingWorkerBase::symlink(const QString &target, const QUrl &dest, JobFlags flags)
{
qCDebug(KIO_CORE) << "symlink" << target << dest;
QUrl new_dest;
if (d->internalRewriteUrl(dest, new_dest)) {
KIO::SimpleJob *job = KIO::symlink(target, new_dest, flags | HideProgressInfo);
d->connectSimpleJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_MALFORMED_URL, dest.toDisplayString());
}
WorkerResult ForwardingWorkerBase::chmod(const QUrl &url, int permissions)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::SimpleJob *job = KIO::chmod(new_url, permissions);
d->connectSimpleJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::setModificationTime(const QUrl &url, const QDateTime &mtime)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
KIO::SimpleJob *job = KIO::setModificationTime(new_url, mtime);
d->connectSimpleJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
WorkerResult ForwardingWorkerBase::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags)
{
qCDebug(KIO_CORE) << "copy" << src << dest;
QUrl new_src;
QUrl new_dest;
if (!d->internalRewriteUrl(src, new_src)) {
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString());
}
if (d->internalRewriteUrl(dest, new_dest)) {
KIO::Job *job = KIO::file_copy(new_src, new_dest, permissions, flags | HideProgressInfo);
d->connectJob(job);
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_MALFORMED_URL, dest.toDisplayString());
}
WorkerResult ForwardingWorkerBase::del(const QUrl &url, bool isfile)
{
QUrl new_url;
if (d->internalRewriteUrl(url, new_url)) {
if (isfile) {
KIO::DeleteJob *job = KIO::del(new_url, HideProgressInfo);
d->connectJob(job);
} else {
KIO::SimpleJob *job = KIO::rmdir(new_url);
d->connectSimpleJob(job);
}
return d->loopResult();
}
return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString());
}
//////////////////////////////////////////////////////////////////////////////
void ForwardingWorkerBasePrivate::connectJob(KIO::Job *job)
{
// We will forward the warning message, no need to let the job
// display it itself
job->setUiDelegate(nullptr);
// Forward metadata (e.g. modification time for put())
job->setMetaData(q->allMetaData());
q->connect(job, &KJob::result, q, [this](KJob *job) {
_k_slotResult(job);
});
q->connect(job, &KJob::warning, q, [this](KJob *job, const QString &text) {
_k_slotWarning(job, text);
});
q->connect(job, &KJob::infoMessage, q, [this](KJob *job, const QString &info) {
_k_slotInfoMessage(job, info);
});
q->connect(job, &KJob::totalSize, q, [this](KJob *job, qulonglong size) {
_k_slotTotalSize(job, size);
});
q->connect(job, &KJob::processedSize, q, [this](KJob *job, qulonglong size) {
_k_slotProcessedSize(job, size);
});
q->connect(job, &KJob::speed, q, [this](KJob *job, ulong speed) {
_k_slotSpeed(job, speed);
});
}
void ForwardingWorkerBasePrivate::connectSimpleJob(KIO::SimpleJob *job)
{
connectJob(job);
if (job->metaObject()->indexOfSignal("redirection(KIO::Job*,QUrl)") > -1) {
q->connect(job, SIGNAL(redirection(KIO::Job *, QUrl)), SLOT(_k_slotRedirection(KIO::Job *, QUrl)));
}
}
void ForwardingWorkerBasePrivate::connectListJob(KIO::ListJob *job)
{
connectSimpleJob(job);
q->connect(job, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &entries) {
_k_slotEntries(job, entries);
});
}
void ForwardingWorkerBasePrivate::connectTransferJob(KIO::TransferJob *job)
{
connectSimpleJob(job);
q->connect(job, &KIO::TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) {
_k_slotData(job, data);
});
q->connect(job, &KIO::TransferJob::dataReq, q, [this](KIO::Job *job, QByteArray &data) {
_k_slotDataReq(job, data);
});
q->connect(job, &KIO::TransferJob::mimeTypeFound, q, [this](KIO::Job *job, const QString &mimeType) {
_k_slotMimetype(job, mimeType);
});
q->connect(job, &KIO::TransferJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) {
_k_slotCanResume(job, offset);
});
}
//////////////////////////////////////////////////////////////////////////////
void ForwardingWorkerBasePrivate::_k_slotResult(KJob *job)
{
if (job->error() != 0) {
m_pendingResult = WorkerResult::fail(job->error(), job->errorText());
} else {
if (auto stat_job = qobject_cast<KIO::StatJob *>(job)) {
KIO::UDSEntry entry = stat_job->statResult();
q->adjustUDSEntry(entry, ForwardingWorkerBase::UDSEntryCreationInStat);
q->statEntry(entry);
}
m_pendingResult = WorkerResult::pass();
}
eventLoop.exit();
}
void ForwardingWorkerBasePrivate::_k_slotWarning(KJob * /*job*/, const QString &msg) const
{
q->warning(msg);
}
void ForwardingWorkerBasePrivate::_k_slotInfoMessage(KJob * /*job*/, const QString &msg) const
{
q->infoMessage(msg);
}
void ForwardingWorkerBasePrivate::_k_slotTotalSize(KJob * /*job*/, qulonglong size) const
{
q->totalSize(size);
}
void ForwardingWorkerBasePrivate::_k_slotProcessedSize(KJob * /*job*/, qulonglong size) const
{
q->processedSize(size);
}
void ForwardingWorkerBasePrivate::_k_slotSpeed(KJob * /*job*/, unsigned long bytesPerSecond) const
{
q->speed(bytesPerSecond);
}
void ForwardingWorkerBasePrivate::_k_slotRedirection(KIO::Job *job, const QUrl &url)
{
q->redirection(url);
// We've been redirected stop everything.
job->kill(KJob::Quietly);
m_pendingResult = WorkerResult::pass();
eventLoop.exit();
}
void ForwardingWorkerBasePrivate::_k_slotEntries(KIO::Job * /*job*/, const KIO::UDSEntryList &entries) const
{
KIO::UDSEntryList final_entries = entries;
for (auto &entry : final_entries) {
q->adjustUDSEntry(entry, ForwardingWorkerBase::UDSEntryCreationInListDir);
}
q->listEntries(final_entries);
}
void ForwardingWorkerBasePrivate::_k_slotData(KIO::Job * /*job*/, const QByteArray &_data) const
{
q->data(_data);
}
void ForwardingWorkerBasePrivate::_k_slotDataReq(KIO::Job * /*job*/, QByteArray &data) const
{
q->dataReq();
q->readData(data);
}
void ForwardingWorkerBasePrivate::_k_slotMimetype(KIO::Job * /*job*/, const QString &type) const
{
q->mimeType(type);
}
void ForwardingWorkerBasePrivate::_k_slotCanResume(KIO::Job * /*job*/, KIO::filesize_t offset) const
{
q->canResume(offset);
}
} // namespace KIO
#include "moc_forwardingworkerbase.cpp"
@@ -0,0 +1,144 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2004 Kevin Ottens <ervin@ipsquad.net>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef _FORWARDING_WORKER_BASE_H_
#define _FORWARDING_WORKER_BASE_H_
#include "kiocore_export.h"
#include <kio/workerbase.h>
#include <QObject>
#include <memory>
namespace KIO
{
class ForwardingWorkerBasePrivate;
/**
* @class KIO::ForwardingWorkerBase forwardingworkerbase.h <KIO/ForwardingWorkerBase>
*
* This class should be used as a base for KIO workers acting as a
* forwarder to other KIO workers. It has been designed to support only
* local filesystem like KIO workers.
*
* If the resulting KIO worker should be a simple proxy, you only need
* to implement the ForwardingWorkerBase::rewriteUrl() method.
*
* For more advanced behavior, the classic KIO worker methods should
* be reimplemented, because their default behavior in this class
* is to forward using the ForwardingWorkerBase::rewriteUrl() method.
*
* A possible code snippet for an advanced stat() behavior would look
* like this in the child class:
*
* \code
* WorkerResult ChildProtocol::stat(const QUrl &url)
* {
* bool is_special = false;
*
* // Process the URL to see if it should have
* // a special treatment
*
* if (is_special) {
* // Handle the URL ourselves
* KIO::UDSEntry entry;
* // Fill entry with values
* statEntry(entry);
* return WorkerResult::pass();
* }
* // Setup the KIO worker internal state if
* // required by ChildProtocol::rewriteUrl()
* return ForwardingWorkerBase::stat(url);
* }
* \endcode
*
* Of course in this case, you surely need to reimplement listDir()
* and get() accordingly.
*
* If you want view on directories to be correctly refreshed when
* something changes on a forwarded URL, you'll need a companion kded
* module to emit the KDirNotify Files*() D-Bus signals.
*
* @see ForwardingWorkerBase::rewriteUrl()
* @author Kevin Ottens <ervin@ipsquad.net>
* @since 5.101
*/
class KIOCORE_EXPORT ForwardingWorkerBase : public QObject, public WorkerBase
{
Q_OBJECT
public:
ForwardingWorkerBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket);
~ForwardingWorkerBase() override;
Q_DISABLE_COPY_MOVE(ForwardingWorkerBase)
WorkerResult get(const QUrl &url) override;
WorkerResult put(const QUrl &url, int permissions, JobFlags flags) override;
WorkerResult stat(const QUrl &url) override;
WorkerResult mimetype(const QUrl &url) override;
WorkerResult listDir(const QUrl &url) override;
WorkerResult mkdir(const QUrl &url, int permissions) override;
WorkerResult rename(const QUrl &src, const QUrl &dest, JobFlags flags) override;
WorkerResult symlink(const QString &target, const QUrl &dest, JobFlags flags) override;
WorkerResult chmod(const QUrl &url, int permissions) override;
WorkerResult setModificationTime(const QUrl &url, const QDateTime &mtime) override;
WorkerResult copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) override;
WorkerResult del(const QUrl &url, bool isfile) override;
protected:
/**
* Rewrite an url to its forwarded counterpart. It should return
* true if everything was ok, and false otherwise.
*
* If a problem is detected it's up to this method to trigger error()
* before returning. Returning false silently cancels the current
* KIO worker operation.
*
* @param url The URL as given during the KIO worker call
* @param newURL The new URL to forward the KIO worker call to
* @return true if the given url could be correctly rewritten
*/
virtual bool rewriteUrl(const QUrl &url, QUrl &newURL) = 0;
enum UDSEntryCreationMode {
UDSEntryCreationInStat, ///< The entry is created during a stat operation.
UDSEntryCreationInListDir, ///< The entry is created during a listDir operation.
};
/**
* Adjusts a UDSEntry before it's sent in the reply to the KIO worker endpoint.
* This is the default implementation working in most cases, but sometimes
* you could make use of more forwarding black magic (for example
* dynamically transform any desktop file into a fake directory...)
*
* @param entry the UDSEntry to adjust
* @param creationMode the operation for which this entry is created
*/
virtual void adjustUDSEntry(KIO::UDSEntry &entry, UDSEntryCreationMode creationMode) const;
/**
* Return the URL being processed by the KIO worker
* Only access it inside adjustUDSEntry()
*/
QUrl processedUrl() const;
/**
* Return the URL asked to the KIO worker
* Only access it inside adjustUDSEntry()
*/
QUrl requestedUrl() const;
private:
Q_PRIVATE_SLOT(d, void _k_slotRedirection(KIO::Job *, QUrl))
friend class ForwardingWorkerBasePrivate;
std::unique_ptr<ForwardingWorkerBasePrivate> const d;
};
} // namespace KIO
#endif
@@ -0,0 +1,254 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "global.h"
#include "faviconscache_p.h"
#include "kioglobal_p.h"
#include <KConfig>
#include <KConfigGroup>
#include <KFileUtils>
#include <KFormat>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QMimeDatabase>
#include <QUrl>
#include <kfileitem.h>
#include <kprotocolinfo.h>
#include "kiocoredebug.h"
KFormat::BinaryUnitDialect _k_loadBinaryDialect();
Q_GLOBAL_STATIC_WITH_ARGS(KFormat::BinaryUnitDialect, _k_defaultBinaryDialect, (_k_loadBinaryDialect()))
KFormat::BinaryUnitDialect _k_loadBinaryDialect()
{
KConfigGroup mainGroup(KSharedConfig::openConfig(), QStringLiteral("Locale"));
KFormat::BinaryUnitDialect dialect(KFormat::BinaryUnitDialect(mainGroup.readEntry("BinaryUnitDialect", int(KFormat::DefaultBinaryDialect))));
dialect = static_cast<KFormat::BinaryUnitDialect>(mainGroup.readEntry("BinaryUnitDialect", int(dialect)));
// Error checking
if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
dialect = KFormat::IECBinaryDialect;
}
return dialect;
}
KIOCORE_EXPORT QString KIO::convertSize(KIO::filesize_t fileSize)
{
const KFormat::BinaryUnitDialect dialect = *_k_defaultBinaryDialect();
return KFormat().formatByteSize(fileSize, 1, dialect);
}
KIOCORE_EXPORT QString KIO::convertSizeFromKiB(KIO::filesize_t kibSize)
{
return convertSize(kibSize * 1024);
}
KIOCORE_EXPORT QString KIO::number(KIO::filesize_t size)
{
return QString::number(size);
}
KIOCORE_EXPORT unsigned int KIO::calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed)
{
if ((speed != 0) && (totalSize != 0)) {
return (totalSize - processedSize) / speed;
} else {
return 0;
}
}
KIOCORE_EXPORT QString KIO::convertSeconds(unsigned int seconds)
{
unsigned int days = seconds / 86400;
unsigned int hours = (seconds - (days * 86400)) / 3600;
unsigned int mins = (seconds - (days * 86400) - (hours * 3600)) / 60;
seconds = (seconds - (days * 86400) - (hours * 3600) - (mins * 60));
const QTime time(hours, mins, seconds);
const QString timeStr(time.toString(QStringLiteral("hh:mm:ss")));
if (days > 0) {
return i18np("1 day %2", "%1 days %2", days, timeStr);
} else {
return timeStr;
}
}
KIOCORE_EXPORT QString KIO::itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize)
{
if (files == 0 && dirs == 0 && items == 0) {
return i18np("%1 Item", "%1 Items", 0);
}
QString summary;
const QString foldersText = i18np("1 Folder", "%1 Folders", dirs);
const QString filesText = i18np("1 File", "%1 Files", files);
if (files > 0 && dirs > 0) {
summary = showSize ? i18nc("folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KIO::convertSize(size))
: i18nc("folders, files", "%1, %2", foldersText, filesText);
} else if (files > 0) {
summary = showSize ? i18nc("files (size)", "%1 (%2)", filesText, KIO::convertSize(size)) : filesText;
} else if (dirs > 0) {
summary = foldersText;
}
if (items > dirs + files) {
const QString itemsText = i18np("%1 Item", "%1 Items", items);
summary = summary.isEmpty() ? itemsText : i18nc("items: folders, files (size)", "%1: %2", itemsText, summary);
}
return summary;
}
KIOCORE_EXPORT QString KIO::encodeFileName(const QString &_str)
{
QString str(_str);
str.replace(QLatin1Char('/'), QChar(0x2044)); // "Fraction slash"
return str;
}
KIOCORE_EXPORT QString KIO::decodeFileName(const QString &_str)
{
// Nothing to decode. "Fraction slash" is fine in filenames.
return _str;
}
/***************************************************************
*
* Utility functions
*
***************************************************************/
#if KIOCORE_BUILD_DEPRECATED_SINCE(6, 6)
KIO::CacheControl KIO::parseCacheControl(const QString &cacheControl)
{
QString tmp = cacheControl.toLower();
if (tmp == QLatin1String("cacheonly")) {
return KIO::CC_CacheOnly;
}
if (tmp == QLatin1String("cache")) {
return KIO::CC_Cache;
}
if (tmp == QLatin1String("verify")) {
return KIO::CC_Verify;
}
if (tmp == QLatin1String("refresh")) {
return KIO::CC_Refresh;
}
if (tmp == QLatin1String("reload")) {
return KIO::CC_Reload;
}
qCDebug(KIO_CORE) << "unrecognized Cache control option:" << cacheControl;
return KIO::CC_Verify;
}
#endif
#if KIOCORE_BUILD_DEPRECATED_SINCE(6, 6)
QString KIO::getCacheControlString(KIO::CacheControl cacheControl)
{
if (cacheControl == KIO::CC_CacheOnly) {
return QStringLiteral("CacheOnly");
}
if (cacheControl == KIO::CC_Cache) {
return QStringLiteral("Cache");
}
if (cacheControl == KIO::CC_Verify) {
return QStringLiteral("Verify");
}
if (cacheControl == KIO::CC_Refresh) {
return QStringLiteral("Refresh");
}
if (cacheControl == KIO::CC_Reload) {
return QStringLiteral("Reload");
}
qCDebug(KIO_CORE) << "unrecognized Cache control enum value:" << cacheControl;
return QString();
}
#endif
QString KIO::favIconForUrl(const QUrl &url)
{
if (url.isLocalFile() || !url.scheme().startsWith(QLatin1String("http"))) {
return QString();
}
return FavIconsCache::instance()->iconForUrl(url);
}
QString KIO::iconNameForUrl(const QUrl &url)
{
if (url.scheme().isEmpty()) { // empty URL or relative URL (e.g. '~')
return QStringLiteral("unknown");
}
QMimeDatabase db;
const QMimeType mt = db.mimeTypeForUrl(url);
QString iconName;
if (url.isLocalFile()) {
// Check to see whether it's an xdg location (e.g. Pictures folder)
if (mt.inherits(QStringLiteral("inode/directory"))) {
iconName = KIOPrivate::iconForStandardPath(url.toLocalFile());
}
// Let KFileItem::iconName handle things for us
if (iconName.isEmpty()) {
const KFileItem item(url, mt.name());
iconName = item.iconName();
}
} else {
// It's non-local and maybe on a slow filesystem
// Look for a favicon
if (url.scheme().startsWith(QLatin1String("http"))) {
iconName = favIconForUrl(url);
}
// Then handle the trash
else if (url.scheme() == QLatin1String("trash")) {
if (url.path().length() <= 1) { // trash:/ itself
KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
iconName =
trashConfig.group(QStringLiteral("Status")).readEntry("Empty", true) ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full");
} else { // url.path().length() > 1, it's a file/folder under trash:/
iconName = mt.iconName();
}
}
// and other protocols
if (iconName.isEmpty() && (mt.isDefault() || url.path().size() <= 1)) {
iconName = KProtocolInfo::icon(url.scheme());
}
}
// if we found nothing, return QMimeType.iconName()
// (which fallbacks to "application-octet-stream" when no MIME type could be determined)
return !iconName.isEmpty() ? iconName : mt.iconName();
}
QUrl KIO::upUrl(const QUrl &url)
{
if (!url.isValid() || url.isRelative()) {
return QUrl();
}
QUrl u(url);
if (url.hasQuery()) {
u.setQuery(QString());
return u;
}
if (url.hasFragment()) {
u.setFragment(QString());
}
u = u.adjusted(QUrl::StripTrailingSlash); /// don't combine with the line below
return u.adjusted(QUrl::RemoveFilename);
}
@@ -0,0 +1,359 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_GLOBAL_H
#define KIO_GLOBAL_H
#include "kiocore_export.h"
#include <QFile> // for QFile::Permissions
#include <QString>
#include <KJob>
class QUrl;
class QTime;
#if defined(Q_OS_WIN) && defined(Q_CC_MSVC)
// on windows ssize_t is not defined, only SSIZE_T exists
#include <basetsd.h>
typedef SSIZE_T ssize_t;
#endif
/**
* @short A namespace for KIO globals
*
*/
namespace KIO
{
/// 64-bit file offset
typedef qlonglong fileoffset_t;
/// 64-bit file size
typedef qulonglong filesize_t;
/**
* Converts @p size from bytes to the string representation.
*
* @param size size in bytes
* @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB
*/
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size);
/**
* Converts a size to a string representation
* Not unlike QString::number(...)
*
* @param size size in bytes
* @return converted size as a string - e.g. 123456789
*/
KIOCORE_EXPORT QString number(KIO::filesize_t size);
/**
* Converts size from kibi-bytes (2^10) to the string representation.
*
* @param kibSize size in kibi-bytes (2^10)
* @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB
*/
KIOCORE_EXPORT QString convertSizeFromKiB(KIO::filesize_t kibSize);
/**
* Calculates remaining time in seconds from total size, processed size and speed.
*
* @param totalSize total size in bytes
* @param processedSize processed size in bytes
* @param speed speed in bytes per second
* @return calculated remaining time in seconds
*/
KIOCORE_EXPORT unsigned int calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed);
/**
* Convert @p seconds to a string representing number of days, hours, minutes and seconds
*
* @param seconds number of seconds to convert
* @return string representation in a locale depending format
*/
KIOCORE_EXPORT QString convertSeconds(unsigned int seconds);
/**
* Helper for showing information about a set of files and directories
* @param items the number of items (= @p files + @p dirs + number of symlinks :)
* @param files the number of files
* @param dirs the number of dirs
* @param size the sum of the size of the @p files
* @param showSize whether to show the size in the result
* @return the summary string
*/
KIOCORE_EXPORT QString itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize);
/**
* Encodes (from the text displayed to the real filename)
* This translates '/' into a "unicode fraction slash", QChar(0x2044).
* Used by KIO::link, for instance.
* @param str the file name to encode
* @return the encoded file name
*/
KIOCORE_EXPORT QString encodeFileName(const QString &str);
/**
* Decodes (from the filename to the text displayed)
* This doesn't do anything anymore, it used to do the opposite of encodeFileName
* when encodeFileName was using %2F for '/'.
* @param str the file name to decode
* @return the decoded file name
*/
KIOCORE_EXPORT QString decodeFileName(const QString &str);
/**
* Error codes that can be emitted by KIO.
*/
enum Error {
ERR_CANNOT_OPEN_FOR_READING = KJob::UserDefinedError + 1,
ERR_CANNOT_OPEN_FOR_WRITING = KJob::UserDefinedError + 2,
ERR_CANNOT_LAUNCH_PROCESS = KJob::UserDefinedError + 3,
ERR_INTERNAL = KJob::UserDefinedError + 4,
ERR_MALFORMED_URL = KJob::UserDefinedError + 5,
ERR_UNSUPPORTED_PROTOCOL = KJob::UserDefinedError + 6,
ERR_NO_SOURCE_PROTOCOL = KJob::UserDefinedError + 7,
ERR_UNSUPPORTED_ACTION = KJob::UserDefinedError + 8,
ERR_IS_DIRECTORY = KJob::UserDefinedError + 9, ///< ... where a file was expected
ERR_IS_FILE = KJob::UserDefinedError + 10, ///< ... where a directory was expected (e.g.\ listing)
ERR_DOES_NOT_EXIST = KJob::UserDefinedError + 11,
ERR_FILE_ALREADY_EXIST = KJob::UserDefinedError + 12,
ERR_DIR_ALREADY_EXIST = KJob::UserDefinedError + 13,
ERR_UNKNOWN_HOST = KJob::UserDefinedError + 14,
ERR_ACCESS_DENIED = KJob::UserDefinedError + 15,
ERR_WRITE_ACCESS_DENIED = KJob::UserDefinedError + 16,
ERR_CANNOT_ENTER_DIRECTORY = KJob::UserDefinedError + 17,
ERR_PROTOCOL_IS_NOT_A_FILESYSTEM = KJob::UserDefinedError + 18,
ERR_CYCLIC_LINK = KJob::UserDefinedError + 19,
ERR_USER_CANCELED = KJob::KilledJobError,
ERR_CYCLIC_COPY = KJob::UserDefinedError + 21,
ERR_CANNOT_CREATE_SOCKET = KJob::UserDefinedError + 22,
ERR_CANNOT_CONNECT = KJob::UserDefinedError + 23,
ERR_CONNECTION_BROKEN = KJob::UserDefinedError + 24,
ERR_NOT_FILTER_PROTOCOL = KJob::UserDefinedError + 25,
ERR_CANNOT_MOUNT = KJob::UserDefinedError + 26,
ERR_CANNOT_UNMOUNT = KJob::UserDefinedError + 27,
ERR_CANNOT_READ = KJob::UserDefinedError + 28,
ERR_CANNOT_WRITE = KJob::UserDefinedError + 29,
ERR_CANNOT_BIND = KJob::UserDefinedError + 30,
ERR_CANNOT_LISTEN = KJob::UserDefinedError + 31,
ERR_CANNOT_ACCEPT = KJob::UserDefinedError + 32,
ERR_CANNOT_LOGIN = KJob::UserDefinedError + 33,
ERR_CANNOT_STAT = KJob::UserDefinedError + 34,
ERR_CANNOT_CLOSEDIR = KJob::UserDefinedError + 35,
ERR_CANNOT_MKDIR = KJob::UserDefinedError + 37,
ERR_CANNOT_RMDIR = KJob::UserDefinedError + 38,
ERR_CANNOT_RESUME = KJob::UserDefinedError + 39,
ERR_CANNOT_RENAME = KJob::UserDefinedError + 40,
ERR_CANNOT_CHMOD = KJob::UserDefinedError + 41,
ERR_CANNOT_DELETE = KJob::UserDefinedError + 42,
// The text argument is the protocol that the dead worker supported.
// This means for example: file, ftp, http, ...
ERR_WORKER_DIED = KJob::UserDefinedError + 43, ///< @since 5.96
ERR_OUT_OF_MEMORY = KJob::UserDefinedError + 44,
ERR_UNKNOWN_PROXY_HOST = KJob::UserDefinedError + 45,
ERR_CANNOT_AUTHENTICATE = KJob::UserDefinedError + 46,
ERR_ABORTED = KJob::UserDefinedError + 47, ///< Action got aborted from application side
ERR_INTERNAL_SERVER = KJob::UserDefinedError + 48,
ERR_SERVER_TIMEOUT = KJob::UserDefinedError + 49,
ERR_SERVICE_NOT_AVAILABLE = KJob::UserDefinedError + 50,
ERR_UNKNOWN = KJob::UserDefinedError + 51,
// (was a warning) ERR_CHECKSUM_MISMATCH = 52,
ERR_UNKNOWN_INTERRUPT = KJob::UserDefinedError + 53,
ERR_CANNOT_DELETE_ORIGINAL = KJob::UserDefinedError + 54,
ERR_CANNOT_DELETE_PARTIAL = KJob::UserDefinedError + 55,
ERR_CANNOT_RENAME_ORIGINAL = KJob::UserDefinedError + 56,
ERR_CANNOT_RENAME_PARTIAL = KJob::UserDefinedError + 57,
ERR_NEED_PASSWD = KJob::UserDefinedError + 58,
ERR_CANNOT_SYMLINK = KJob::UserDefinedError + 59,
ERR_NO_CONTENT = KJob::UserDefinedError + 60, ///< Action succeeded but no content will follow.
ERR_DISK_FULL = KJob::UserDefinedError + 61,
ERR_IDENTICAL_FILES = KJob::UserDefinedError + 62, ///< src==dest when moving/copying
/**
* For worker specified errors that can be
* rich text. Email links will be handled
* by the standard email app and all hrefs
* will be handled by the standard browser.
* <a href="exec:/khelpcenter ?" will be
* forked.
* @since 5.96
*/
ERR_WORKER_DEFINED = KJob::UserDefinedError + 63,
ERR_UPGRADE_REQUIRED = KJob::UserDefinedError + 64, ///< A transport upgrade is required to access this
///< object. For instance, TLS is demanded by
///< the server in order to continue.
ERR_POST_DENIED = KJob::UserDefinedError + 65, ///< Issued when trying to POST data to a certain Ports
// see job.cpp
ERR_CANNOT_SEEK = KJob::UserDefinedError + 66,
ERR_CANNOT_SETTIME = KJob::UserDefinedError + 67, ///< Emitted by setModificationTime
ERR_CANNOT_CHOWN = KJob::UserDefinedError + 68,
ERR_POST_NO_SIZE = KJob::UserDefinedError + 69,
ERR_DROP_ON_ITSELF = KJob::UserDefinedError + 70, ///< from KIO::DropJob, @since 5.6
ERR_CANNOT_MOVE_INTO_ITSELF = KJob::UserDefinedError + 71, ///< emitted by KIO::move, @since 5.18
ERR_PASSWD_SERVER = KJob::UserDefinedError + 72, ///< returned by WorkerBase::openPasswordDialog, @since 5.24
ERR_CANNOT_CREATE_WORKER = KJob::UserDefinedError + 73, ///< used by Worker::createWorker, @since 5.96
ERR_FILE_TOO_LARGE_FOR_FAT32 = KJob::UserDefinedError + 74, ///< @since 5.54
ERR_OWNER_DIED ///< Value used between kuiserver and views when the job owner disappears unexpectedly. It should not be emitted by workers. @since 5.54
= KJob::UserDefinedError + 75,
ERR_PRIVILEGE_NOT_REQUIRED = KJob::UserDefinedError + 76, ///< used by file ioworker, @since 5.60
ERR_CANNOT_TRUNCATE = KJob::UserDefinedError + 77, // used by FileJob::truncate, @since 5.66
/**
* Indicates failure to create a symlink due to the underlying filesystem (FAT/ExFAT)
* not supporting them. Used by e.g. CopyJob.
* @since 5.88
*/
ERR_SYMLINKS_NOT_SUPPORTED = KJob::UserDefinedError + 78,
/**
* Moving files/dirs to the Trash failed due to size constraints.
*
* @since 5.100
*/
ERR_TRASH_FILE_TOO_LARGE = KJob::UserDefinedError + 79,
};
#if KIOCORE_ENABLE_DEPRECATED_SINCE(6, 6)
/**
* Specifies how to use the cache.
* @see parseCacheControl()
* @see getCacheControlString()
* @deprecated since 6.6, not used
*/
KIOCORE_DEPRECATED_VERSION(6, 6, "Not used")
enum CacheControl {
CC_CacheOnly, ///< Fail request if not in cache
CC_Cache, ///< Use cached entry if available
CC_Verify, ///< Validate cached entry with remote site if expired
CC_Refresh, ///< Always validate cached entry with remote site
CC_Reload, ///< Always fetch from remote site.
};
#endif
/**
* Specifies privilege file operation status.
* @since 5.43
*/
enum PrivilegeOperationStatus {
OperationAllowed = 1,
OperationCanceled,
OperationNotAllowed,
};
/**
* Describes the fields that a stat command will retrieve
* @see UDSEntry
* @see StatDetails
* @since 5.69
*/
enum StatDetail {
/// No field returned, useful to check if a file exists
StatNoDetails = 0x0,
/// Filename, access, type, size, linkdest
StatBasic = 0x1,
/// uid, gid
StatUser = 0x2,
/// atime, mtime, btime
StatTime = 0x4,
/// Resolve symlinks
StatResolveSymlink = 0x8,
/// ACL data
StatAcl = 0x10,
/// dev, inode
StatInode = 0x20,
/// Recursive size
/// @since 5.70
StatRecursiveSize = 0x40,
/// MIME type
/// @since 5.82
StatMimeType = 0x80,
/// Default StatDetail flag when creating a @c StatJob.
/// Equivalent to setting <tt>StatBasic | StatUser | StatTime | StatAcl | StatResolveSymlink</tt>
StatDefaultDetails = StatBasic | StatUser | StatTime | StatAcl | StatResolveSymlink,
};
/**
* Stores a combination of #StatDetail values.
*/
Q_DECLARE_FLAGS(StatDetails, StatDetail)
Q_DECLARE_OPERATORS_FOR_FLAGS(KIO::StatDetails)
#if KIOCORE_ENABLE_DEPRECATED_SINCE(6, 6)
/**
* Parses the string representation of the cache control option.
*
* @param cacheControl the string representation
* @return the cache control value
* @see getCacheControlString()
* @deprecated since 6.6, not used
*/
KIOCORE_EXPORT KIO::CacheControl parseCacheControl(const QString &cacheControl);
#endif
#if KIOCORE_ENABLE_DEPRECATED_SINCE(6, 6)
/**
* Returns a string representation of the given cache control method.
*
* @param cacheControl the cache control method
* @return the string representation
* @see parseCacheControl()
* @deprecated since 6.6, not used
*/
KIOCORE_DEPRECATED_VERSION(6, 6, "Not used")
KIOCORE_EXPORT QString getCacheControlString(KIO::CacheControl cacheControl);
#endif
/**
* Return the "favicon" (see http://www.favicon.com) for the given @p url,
* if available. Does NOT attempt to download the favicon, it only returns
* one that is already available.
*
* If unavailable, returns QString().
* Use KIO::FavIconRequestJob instead of this method if you can wait
* for the favicon to be downloaded.
*
* @param url the URL of the favicon
* @return the path to the icon (to be passed to QIcon()), or QString()
*
* @since 5.0
*/
KIOCORE_EXPORT QString favIconForUrl(const QUrl &url);
/**
* Converts KIO file permissions from mode_t to QFile::Permissions format.
*
* This is a convenience function for converting KIO permissions parameter from
* mode_t to QFile::Permissions.
*
* @param permissions KIO file permissions.
*
* @return -1 if @p permissions is -1, otherwise its OR'ed QFile::Permission equivalent.
*/
KIOCORE_EXPORT QFile::Permissions convertPermissions(int permissions);
/**
* Return the icon name for a URL.
* Most of the time this returns the MIME type icon,
* but also has fallback to favicon and protocol-specific icon.
*
* Pass this to QIcon::fromTheme().
*
* @since 5.0
*/
KIOCORE_EXPORT QString iconNameForUrl(const QUrl &url);
/**
* This function is useful to implement the "Up" button in a file manager for example.
*
* @return a URL that is a level higher
*
* @since 5.0
*/
KIOCORE_EXPORT QUrl upUrl(const QUrl &url);
}
#endif
@@ -0,0 +1,381 @@
/*
SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "hostinfo.h"
#include <QCache>
#include <QFutureWatcher>
#include <QHash>
#include <QHostInfo>
#include <QList>
#include <QMetaType>
#include <QPair>
#include <QSemaphore>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <QtConcurrentRun>
#ifdef Q_OS_UNIX
#include <QFileInfo>
#include <arpa/nameser.h>
#include <netinet/in.h>
#include <resolv.h> // for _PATH_RESCONF
#ifndef _PATH_RESCONF
#define _PATH_RESCONF "/etc/resolv.conf"
#endif
#endif
static constexpr int TTL = 300;
namespace KIO
{
class HostInfoAgentPrivate : public QObject
{
Q_OBJECT
class Query;
public:
explicit HostInfoAgentPrivate(int cacheSize = 100);
~HostInfoAgentPrivate() override
{
}
void lookupHost(const QString &hostName, QObject *receiver, const char *member);
QHostInfo lookupCachedHostInfoFor(const QString &hostName);
void cacheLookup(const QHostInfo &);
private Q_SLOTS:
void queryFinished(const QHostInfo &info, Query *sender);
private:
class Result;
QHash<QString, Query *> openQueries;
struct HostCacheInfo {
QHostInfo hostInfo;
QTime time;
};
QCache<QString, HostCacheInfo> dnsCache;
QDateTime resolvConfMTime;
};
class HostInfoAgentPrivate::Result : public QObject
{
Q_OBJECT
Q_SIGNALS:
void result(const QHostInfo &);
private:
friend class HostInfoAgentPrivate;
};
class HostInfoAgentPrivate::Query : public QObject
{
Q_OBJECT
public:
Query()
: m_watcher()
, m_hostName()
{
connect(&m_watcher, &QFutureWatcher<QHostInfo>::finished, this, &Query::relayFinished);
}
void start(const QString &hostName)
{
m_hostName = hostName;
QFuture<QHostInfo> future = QtConcurrent::run(&QHostInfo::fromName, hostName);
m_watcher.setFuture(future);
}
QString hostName() const
{
return m_hostName;
}
Q_SIGNALS:
void result(const QHostInfo &);
private Q_SLOTS:
void relayFinished()
{
Q_EMIT result(m_watcher.result());
}
private:
QFutureWatcher<QHostInfo> m_watcher;
QString m_hostName;
};
class NameLookupThreadRequest
{
public:
NameLookupThreadRequest(const QString &hostName)
: m_hostName(hostName)
{
}
QSemaphore *semaphore()
{
return &m_semaphore;
}
QHostInfo result() const
{
return m_hostInfo;
}
void setResult(const QHostInfo &hostInfo)
{
m_hostInfo = hostInfo;
}
QString hostName() const
{
return m_hostName;
}
int lookupId() const
{
return m_lookupId;
}
void setLookupId(int id)
{
m_lookupId = id;
}
private:
Q_DISABLE_COPY(NameLookupThreadRequest)
QString m_hostName;
QSemaphore m_semaphore;
QHostInfo m_hostInfo;
int m_lookupId;
};
}
Q_DECLARE_METATYPE(std::shared_ptr<KIO::NameLookupThreadRequest>)
namespace KIO
{
class NameLookUpThreadWorker : public QObject
{
Q_OBJECT
public Q_SLOTS:
void lookupHost(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
{
const QString hostName = request->hostName();
const int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo)));
request->setLookupId(lookupId);
m_lookups.insert(lookupId, request);
}
void abortLookup(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
{
QHostInfo::abortHostLookup(request->lookupId());
m_lookups.remove(request->lookupId());
}
void lookupFinished(const QHostInfo &hostInfo)
{
auto it = m_lookups.find(hostInfo.lookupId());
if (it != m_lookups.end()) {
(*it)->setResult(hostInfo);
(*it)->semaphore()->release();
m_lookups.erase(it);
}
}
private:
QMap<int, std::shared_ptr<NameLookupThreadRequest>> m_lookups;
};
class NameLookUpThread : public QThread
{
Q_OBJECT
public:
NameLookUpThread()
: m_worker(nullptr)
{
qRegisterMetaType<std::shared_ptr<NameLookupThreadRequest>>();
start();
}
~NameLookUpThread() override
{
quit();
wait();
}
NameLookUpThreadWorker *worker()
{
return m_worker;
}
QSemaphore *semaphore()
{
return &m_semaphore;
}
void run() override
{
NameLookUpThreadWorker worker;
m_worker = &worker;
m_semaphore.release();
exec();
}
private:
NameLookUpThreadWorker *m_worker;
QSemaphore m_semaphore;
};
}
using namespace KIO;
Q_GLOBAL_STATIC(HostInfoAgentPrivate, hostInfoAgentPrivate)
Q_GLOBAL_STATIC(NameLookUpThread, nameLookUpThread)
void HostInfo::lookupHost(const QString &hostName, QObject *receiver, const char *member)
{
hostInfoAgentPrivate()->lookupHost(hostName, receiver, member);
}
QHostInfo HostInfo::lookupHost(const QString &hostName, unsigned long timeout)
{
// Do not perform a reverse lookup here...
QHostAddress address(hostName);
QHostInfo hostInfo;
if (!address.isNull()) {
QList<QHostAddress> addressList;
addressList << address;
hostInfo.setAddresses(addressList);
return hostInfo;
}
// Look up the name in the KIO DNS cache...
hostInfo = HostInfo::lookupCachedHostInfoFor(hostName);
if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
return hostInfo;
}
// Failing all of the above, do the lookup...
std::shared_ptr<NameLookupThreadRequest> request = std::make_shared<NameLookupThreadRequest>(hostName);
nameLookUpThread()->semaphore()->acquire();
nameLookUpThread()->semaphore()->release();
NameLookUpThreadWorker *worker = nameLookUpThread()->worker();
auto lookupFunc = [worker, request]() {
worker->lookupHost(request);
};
QMetaObject::invokeMethod(worker, lookupFunc, Qt::QueuedConnection);
if (request->semaphore()->tryAcquire(1, timeout)) {
hostInfo = request->result();
if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
HostInfo::cacheLookup(hostInfo); // cache the look up...
}
} else {
auto abortFunc = [worker, request]() {
worker->abortLookup(request);
};
QMetaObject::invokeMethod(worker, abortFunc, Qt::QueuedConnection);
}
// qDebug() << "Name look up succeeded for" << hostName;
return hostInfo;
}
QHostInfo HostInfo::lookupCachedHostInfoFor(const QString &hostName)
{
return hostInfoAgentPrivate()->lookupCachedHostInfoFor(hostName);
}
void HostInfo::cacheLookup(const QHostInfo &info)
{
hostInfoAgentPrivate()->cacheLookup(info);
}
HostInfoAgentPrivate::HostInfoAgentPrivate(int cacheSize)
: openQueries()
, dnsCache(cacheSize)
{
qRegisterMetaType<QHostInfo>();
}
void HostInfoAgentPrivate::lookupHost(const QString &hostName, QObject *receiver, const char *member)
{
#ifdef _PATH_RESCONF
QFileInfo resolvConf(QFile::decodeName(_PATH_RESCONF));
QDateTime currentMTime = resolvConf.lastModified();
if (resolvConf.exists() && currentMTime != resolvConfMTime) {
// /etc/resolv.conf has been modified
// clear our cache
resolvConfMTime = currentMTime;
dnsCache.clear();
}
#endif
if (HostCacheInfo *info = dnsCache.object(hostName)) {
if (QTime::currentTime() <= info->time.addSecs(TTL)) {
Result result;
if (receiver) {
QObject::connect(&result, SIGNAL(result(QHostInfo)), receiver, member);
Q_EMIT result.result(info->hostInfo);
}
return;
}
dnsCache.remove(hostName);
}
if (Query *query = openQueries.value(hostName)) {
if (receiver) {
connect(query, SIGNAL(result(QHostInfo)), receiver, member);
}
return;
}
Query *query = new Query();
openQueries.insert(hostName, query);
connect(query, &Query::result, this, [this, query](const QHostInfo &info) {
queryFinished(info, query);
});
if (receiver) {
connect(query, SIGNAL(result(QHostInfo)), receiver, member);
}
query->start(hostName);
}
QHostInfo HostInfoAgentPrivate::lookupCachedHostInfoFor(const QString &hostName)
{
HostCacheInfo *info = dnsCache.object(hostName);
if (info && info->time.addSecs(TTL) >= QTime::currentTime()) {
return info->hostInfo;
}
// not found in dnsCache
QHostInfo hostInfo;
hostInfo.setError(QHostInfo::HostNotFound);
return hostInfo;
}
void HostInfoAgentPrivate::cacheLookup(const QHostInfo &info)
{
if (info.hostName().isEmpty()) {
return;
}
if (info.error() != QHostInfo::NoError) {
return;
}
dnsCache.insert(info.hostName(), new HostCacheInfo{info, QTime::currentTime()});
}
void HostInfoAgentPrivate::queryFinished(const QHostInfo &info, Query *sender)
{
const auto host = sender->hostName();
openQueries.remove(host);
if (info.error() == QHostInfo::NoError) {
dnsCache.insert(host, new HostCacheInfo{info, QTime::currentTime()});
}
sender->deleteLater();
}
#include "hostinfo.moc"
@@ -0,0 +1,41 @@
/*
SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef HOSTINFO_H_
#define HOSTINFO_H_
#include "kiocore_export.h"
#include <QObject>
#include <QString>
class QHostInfo;
namespace KIO
{
/**
* @internal
* WARNING: this could disappear at some point in time.
* DO NOT USE outside KDE Frameworks
*/
/*
* TODO KF6: This header is intenionally not installed in KF6, and we should look into unexporting
* and removing most of these functions; they are only used internally in very few places where
* they might even be inlined to.
*/
namespace HostInfo
{
/// @internal
void lookupHost(const QString &hostName, QObject *receiver, const char *member);
/// @internal
KIOCORE_EXPORT QHostInfo lookupHost(const QString &hostName, unsigned long timeout);
/// @internal
KIOCORE_EXPORT QHostInfo lookupCachedHostInfoFor(const QString &hostName);
/// @internal
KIOCORE_EXPORT void cacheLookup(const QHostInfo &info);
}
}
#endif
@@ -0,0 +1,39 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Jan-Pascal van Best <janpascal@vanbest.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIOWORKER_HTTPMETHOD_P_H_
#define KIOWORKER_HTTPMETHOD_P_H_
namespace KIO
{
/** HTTP / DAV method **/
enum HTTP_METHOD {
HTTP_GET,
HTTP_PUT,
HTTP_POST,
HTTP_HEAD,
HTTP_DELETE,
HTTP_OPTIONS,
DAV_PROPFIND,
DAV_PROPPATCH,
DAV_MKCOL,
DAV_COPY,
DAV_MOVE,
DAV_LOCK,
DAV_UNLOCK,
DAV_SEARCH,
DAV_SUBSCRIBE,
DAV_UNSUBSCRIBE,
DAV_POLL,
DAV_NOTIFY,
DAV_REPORT,
HTTP_UNKNOWN = -1,
};
}
#endif
@@ -0,0 +1,43 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef _KIO_IOWORKER_DEFAULTS_H
#define _KIO_IOWORKER_DEFAULTS_H
// TIMEOUT VALUES
static constexpr int DEFAULT_RESPONSE_TIMEOUT = 600; // 10 min.
static constexpr int DEFAULT_CONNECT_TIMEOUT = 20; // 20 secs.
static constexpr int DEFAULT_READ_TIMEOUT = 15; // 15 secs.
static constexpr int DEFAULT_PROXY_CONNECT_TIMEOUT = 10; // 10 secs.
static constexpr int MIN_TIMEOUT_VALUE = 2; // 2 secs.
// MINIMUM SIZE FOR ABORTED DOWNLOAD TO BE KEPT
static constexpr int DEFAULT_MINIMUM_KEEP_SIZE = 5120; // 5 Kbs
// NORMAL PORT DEFAULTS
static constexpr int DEFAULT_FTP_PORT = 21;
static constexpr int DEFAULT_SFTP_PORT = 22;
static constexpr int DEFAULT_SMTP_PORT = 25;
static constexpr int DEFAULT_HTTP_PORT = 80;
static constexpr int DEFAULT_POP3_PORT = 110;
static constexpr int DEFAULT_NNTP_PORT = 119;
static constexpr int DEFAULT_IMAP_PORT = 143;
static constexpr int DEFAULT_IMAP3_PORT = 220;
static constexpr int DEFAULT_LDAP_PORT = 389;
// SECURE PORT DEFAULTS
static constexpr int DEFAULT_HTTPS_PORT = 443;
static constexpr int DEFAULT_NNTPS_PORT = 563;
static constexpr int DEFAULT_LDAPS_PORT = 389;
static constexpr int DEFAULT_IMAPS_PORT = 993;
static constexpr int DEFAULT_POP3S_PORT = 995;
// OTHER GENERIC PORT DEFAULTS
static constexpr int DEFAULT_PROXY_PORT = 8080;
static constexpr int MAX_PORT_VALUE = 65535;
#endif
@@ -0,0 +1,387 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2000-2009 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "job.h"
#include "job_p.h"
#include <time.h>
#include <KLocalizedString>
#include <KStringHandler>
#include "worker_p.h"
#include <kio/jobuidelegateextension.h>
using namespace KIO;
Job::Job()
: KCompositeJob(nullptr)
, d_ptr(new JobPrivate)
{
d_ptr->q_ptr = this;
setCapabilities(KJob::Killable | KJob::Suspendable);
}
Job::Job(JobPrivate &dd)
: KCompositeJob(nullptr)
, d_ptr(&dd)
{
d_ptr->q_ptr = this;
setCapabilities(KJob::Killable | KJob::Suspendable);
}
Job::~Job()
{
delete d_ptr;
}
JobUiDelegateExtension *Job::uiDelegateExtension() const
{
Q_D(const Job);
return d->m_uiDelegateExtension;
}
void Job::setUiDelegateExtension(JobUiDelegateExtension *extension)
{
Q_D(Job);
d->m_uiDelegateExtension = extension;
}
bool Job::addSubjob(KJob *jobBase)
{
// qDebug() << "addSubjob(" << jobBase << ") this=" << this;
bool ok = KCompositeJob::addSubjob(jobBase);
KIO::Job *job = qobject_cast<KIO::Job *>(jobBase);
if (ok && job) {
// Copy metadata into subjob (e.g. window-id, user-timestamp etc.)
Q_D(Job);
job->mergeMetaData(d->m_outgoingMetaData);
// Forward information from that subjob.
connect(job, &KJob::speed, this, [this](KJob *job, ulong speed) {
Q_UNUSED(job);
emitSpeed(speed);
});
job->setProperty("widget", property("widget")); // see KJobWidgets
job->setProperty("window", property("window")); // see KJobWidgets
job->setProperty("userTimestamp", property("userTimestamp")); // see KJobWidgets
job->setUiDelegateExtension(d->m_uiDelegateExtension);
}
return ok;
}
bool Job::removeSubjob(KJob *jobBase)
{
// qDebug() << "removeSubjob(" << jobBase << ") this=" << this << "subjobs=" << subjobs().count();
return KCompositeJob::removeSubjob(jobBase);
}
static QString url_description_string(const QUrl &url)
{
return url.scheme() == QLatin1String("data") ? QStringLiteral("data:[...]") : KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 100);
}
KIO::JobPrivate::~JobPrivate()
{
}
void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
static const QString s_title = i18nc("@title job", "Moving");
static const QString s_source = i18nc("The source of a file operation", "Source");
static const QString s_destination = i18nc("The destination of a file operation", "Destination");
Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(src)), qMakePair(s_destination, url_description_string(dest)));
}
void JobPrivate::emitRenaming(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
static const QString s_title = i18nc("@title job", "Renaming");
static const QString s_source = i18nc("The source of a file operation", "Source");
static const QString s_destination = i18nc("The destination of a file operation", "Destination");
Q_EMIT job->description(job, s_title, {s_source, url_description_string(src)}, {s_destination, url_description_string(dest)});
}
void JobPrivate::emitCopying(KIO::Job *job, const QUrl &src, const QUrl &dest)
{
static const QString s_title = i18nc("@title job", "Copying");
static const QString s_source = i18nc("The source of a file operation", "Source");
static const QString s_destination = i18nc("The destination of a file operation", "Destination");
Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(src)), qMakePair(s_destination, url_description_string(dest)));
}
void JobPrivate::emitCreatingDir(KIO::Job *job, const QUrl &dir)
{
static const QString s_title = i18nc("@title job", "Creating directory");
static const QString s_directory = i18n("Directory");
Q_EMIT job->description(job, s_title, qMakePair(s_directory, url_description_string(dir)));
}
void JobPrivate::emitDeleting(KIO::Job *job, const QUrl &url)
{
static const QString s_title = i18nc("@title job", "Deleting");
static const QString s_file = i18n("File");
Q_EMIT job->description(job, s_title, qMakePair(s_file, url_description_string(url)));
}
void JobPrivate::emitStating(KIO::Job *job, const QUrl &url)
{
static const QString s_title = i18nc("@title job", "Examining");
static const QString s_file = i18n("File");
Q_EMIT job->description(job, s_title, qMakePair(s_file, url_description_string(url)));
}
void JobPrivate::emitTransferring(KIO::Job *job, const QUrl &url)
{
static const QString s_title = i18nc("@title job", "Transferring");
static const QString s_source = i18nc("The source of a file operation", "Source");
Q_EMIT job->description(job, s_title, qMakePair(s_source, url_description_string(url)));
}
void JobPrivate::emitMounting(KIO::Job *job, const QString &dev, const QString &point)
{
Q_EMIT job->description(job, i18nc("@title job", "Mounting"), qMakePair(i18n("Device"), dev), qMakePair(i18n("Mountpoint"), point));
}
void JobPrivate::emitUnmounting(KIO::Job *job, const QString &point)
{
Q_EMIT job->description(job, i18nc("@title job", "Unmounting"), qMakePair(i18n("Mountpoint"), point));
}
bool Job::doKill()
{
// kill all subjobs, without triggering their result slot
for (KJob *job : subjobs()) {
job->kill(KJob::Quietly);
}
clearSubjobs();
return true;
}
bool Job::doSuspend()
{
for (KJob *job : subjobs()) {
if (!job->suspend()) {
return false;
}
}
return true;
}
bool Job::doResume()
{
for (KJob *job : subjobs()) {
if (!job->resume()) {
return false;
}
}
return true;
}
// Job::errorString is implemented in job_error.cpp
void Job::setParentJob(Job *job)
{
Q_D(Job);
Q_ASSERT(d->m_parentJob == nullptr);
Q_ASSERT(job);
d->m_parentJob = job;
}
Job *Job::parentJob() const
{
return d_func()->m_parentJob;
}
MetaData Job::metaData() const
{
return d_func()->m_incomingMetaData;
}
QString Job::queryMetaData(const QString &key)
{
return d_func()->m_incomingMetaData.value(key, QString());
}
void Job::setMetaData(const KIO::MetaData &_metaData)
{
Q_D(Job);
d->m_outgoingMetaData = _metaData;
}
void Job::addMetaData(const QString &key, const QString &value)
{
d_func()->m_outgoingMetaData.insert(key, value);
}
void Job::addMetaData(const QMap<QString, QString> &values)
{
Q_D(Job);
for (auto it = values.cbegin(); it != values.cend(); ++it) {
d->m_outgoingMetaData.insert(it.key(), it.value());
}
}
void Job::mergeMetaData(const QMap<QString, QString> &values)
{
Q_D(Job);
for (auto it = values.cbegin(); it != values.cend(); ++it) {
// there's probably a faster way
if (!d->m_outgoingMetaData.contains(it.key())) {
d->m_outgoingMetaData.insert(it.key(), it.value());
}
}
}
MetaData Job::outgoingMetaData() const
{
return d_func()->m_outgoingMetaData;
}
QByteArray JobPrivate::privilegeOperationData()
{
PrivilegeOperationStatus status = OperationNotAllowed;
if (m_parentJob) {
QByteArray jobData = m_parentJob->d_func()->privilegeOperationData();
// Copy meta-data from parent job
m_incomingMetaData.insert(QStringLiteral("TestData"), m_parentJob->queryMetaData(QStringLiteral("TestData")));
return jobData;
} else {
if (m_privilegeExecutionEnabled) {
status = OperationAllowed;
switch (m_operationType) {
case ChangeAttr:
m_title = i18n("Change Attribute");
m_message = i18n(
"Root privileges are required to change file attributes. "
"Do you want to continue?");
break;
case Copy:
m_title = i18n("Copy Files");
m_message = i18n(
"Root privileges are required to complete the copy operation. "
"Do you want to continue?");
break;
case Delete:
m_title = i18n("Delete Files");
m_message = i18n(
"Root privileges are required to complete the delete operation. "
"However, doing so may damage your system. Do you want to continue?");
break;
case MkDir:
m_title = i18n("Create Folder");
m_message = i18n(
"Root privileges are required to create this folder. "
"Do you want to continue?");
break;
case Move:
m_title = i18n("Move Items");
m_message = i18n(
"Root privileges are required to complete the move operation. "
"Do you want to continue?");
break;
case Rename:
m_title = i18n("Rename");
m_message = i18n(
"Root privileges are required to complete renaming. "
"Do you want to continue?");
break;
case Symlink:
m_title = i18n("Create Symlink");
m_message = i18n(
"Root privileges are required to create a symlink. "
"Do you want to continue?");
break;
case Transfer:
m_title = i18n("Transfer data");
m_message = i18n(
"Root privileges are required to complete transferring data. "
"Do you want to continue?");
Q_FALLTHROUGH();
default:
break;
}
if (m_outgoingMetaData.value(QStringLiteral("UnitTesting")) == QLatin1String("true")) {
// Set meta-data for the top-level job
m_incomingMetaData.insert(QStringLiteral("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
}
}
QByteArray parentJobData;
QDataStream ds(&parentJobData, QIODevice::WriteOnly);
ds << status << m_title << m_message;
return parentJobData;
}
//////////////////////////
class KIO::DirectCopyJobPrivate : public KIO::SimpleJobPrivate
{
public:
DirectCopyJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs)
: SimpleJobPrivate(url, command, packedArgs)
{
}
/**
* @internal
* Called by the scheduler when a @p worker gets to
* work on this job.
* @param worker the worker that starts working on this job
*/
void start(Worker *worker) override;
Q_DECLARE_PUBLIC(DirectCopyJob)
};
DirectCopyJob::DirectCopyJob(const QUrl &url, const QByteArray &packedArgs)
: SimpleJob(*new DirectCopyJobPrivate(url, CMD_COPY, packedArgs))
{
setUiDelegate(KIO::createDefaultJobUiDelegate());
}
DirectCopyJob::~DirectCopyJob()
{
}
void DirectCopyJobPrivate::start(Worker *worker)
{
Q_Q(DirectCopyJob);
q->connect(worker, &WorkerInterface::canResume, q, &DirectCopyJob::slotCanResume);
SimpleJobPrivate::start(worker);
}
void DirectCopyJob::slotCanResume(KIO::filesize_t offset)
{
Q_EMIT canResume(this, offset);
}
//////////////////////////
SimpleJob *KIO::file_delete(const QUrl &src, JobFlags flags)
{
KIO_ARGS << src << qint8(true); // isFile
SimpleJob *job = SimpleJobPrivate::newJob(src, CMD_DEL, packedArgs, flags);
if (job->uiDelegateExtension()) {
job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
}
return job;
}
//////////
////
#include "moc_job_base.cpp"
#include "moc_job_p.cpp"
@@ -0,0 +1,46 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOB_H
#define KIO_JOB_H
#include "job_base.h" // IWYU pragma: export
#include "kiocore_export.h"
#include <QUrl>
namespace KIO
{
/**
* Returns a translated error message for @p errorCode using the
* additional error information provided by @p errorText.
* @param errorCode the error code
* @param errorText the additional error text
* @return the created error string
*/
KIOCORE_EXPORT QString buildErrorString(int errorCode, const QString &errorText);
/**
* Returns translated error details for @p errorCode using the
* additional error information provided by @p errorText , @p reqUrl
* (the request URL), and the KIO worker @p method .
*
* @param errorCode the error code
* @param errorText the additional error text
* @param reqUrl the request URL
* @param method the KIO worker method
* @return the following data:
* @li QString errorName - the name of the error
* @li QString techName - if not null, the more technical name of the error
* @li QString description - a description of the error
* @li QStringList causes - a list of possible causes of the error
* @li QStringList solutions - a liso of solutions for the error
*/
KIOCORE_EXPORT QByteArray rawErrorDetail(int errorCode, const QString &errorText, const QUrl *reqUrl = nullptr, int method = -1);
}
#endif
@@ -0,0 +1,290 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOB_BASE_H
#define KIO_JOB_BASE_H
#include <KCompositeJob>
#include <kio/metadata.h>
namespace KIO
{
class JobUiDelegateExtension;
class JobPrivate;
/**
* @class KIO::Job job_base.h <KIO/Job>
*
* The base class for all jobs.
* For all jobs created in an application, the code looks like
*
* \code
* KIO::Job* job = KIO::someoperation(some parameters);
* connect(job, &KJob::result, this, &MyClass::slotResult);
* \endcode
* (other connects, specific to the job)
*
* And slotResult is usually at least:
*
* \code
* void MyClass::slotResult(KJob *job)
* {
* if (job->error()) {
* job->uiDelegate()->showErrorMessage();
* }
* }
* \endcode
* @see KIO::Scheduler
*/
class KIOCORE_EXPORT Job : public KCompositeJob
{
Q_OBJECT
protected:
Job();
// used also from KIOGui's PreviewJob, so needs to be exported
explicit Job(JobPrivate &dd);
public:
~Job() override;
void start() override
{
} // Since KIO autostarts its jobs
/**
* Retrieves the UI delegate extension used by this job.
* @since 5.0
*/
JobUiDelegateExtension *uiDelegateExtension() const;
/**
* Sets the UI delegate extension to be used by this job.
* The default UI delegate extension is KIO::defaultJobUiDelegateExtension()
*/
void setUiDelegateExtension(JobUiDelegateExtension *extension);
protected:
/**
* Abort this job.
* This kills all subjobs and deletes the job.
*
*/
bool doKill() override;
/**
* Suspend this job
* @see resume
*/
bool doSuspend() override;
/**
* Resume this job
* @see suspend
*/
bool doResume() override;
public:
/**
* Converts an error code and a non-i18n error message into an
* error message in the current language. The low level (non-i18n)
* error message (usually a url) is put into the translated error
* message using %1.
*
* Example for errid == ERR_CANNOT_OPEN_FOR_READING:
* \code
* i18n("Could not read\n%1", errortext);
* \endcode
* Use this to display the error yourself, but for a dialog box
* use uiDelegate()->showErrorMessage(). Do not call it if error()
* is not 0.
* @return the error message and if there is no error, a message
* telling the user that the app is broken, so check with
* error() whether there is an error
*/
QString errorString() const override;
/**
* Converts an error code and a non-i18n error message into i18n
* strings suitable for presentation in a detailed error message box.
*
* @param reqUrl the request URL that generated this error message
* @param method the method that generated this error message
* (unimplemented)
* @return the following strings: title, error + description,
* causes+solutions
*/
QStringList detailedErrorStrings(const QUrl *reqUrl = nullptr, int method = -1) const;
/**
* Set the parent Job.
* One example use of this is when FileCopyJob calls RenameDialog::open,
* it must pass the correct progress ID of the parent CopyJob
* (to hide the progress dialog).
* You can set the parent job only once. By default a job does not
* have a parent job.
* @param parentJob the new parent job
*/
void setParentJob(Job *parentJob);
/**
* Returns the parent job, if there is one.
* @return the parent job, or @c nullptr if there is none
* @see setParentJob
*/
Job *parentJob() const;
/**
* Set meta data to be sent to the worker, replacing existing
* meta data.
* @param metaData the meta data to set
* @see addMetaData()
* @see mergeMetaData()
*/
void setMetaData(const KIO::MetaData &metaData);
/**
* Add key/value pair to the meta data that is sent to the worker.
* @param key the key of the meta data
* @param value the value of the meta data
* @see setMetaData()
* @see mergeMetaData()
*/
void addMetaData(const QString &key, const QString &value);
/**
* Add key/value pairs to the meta data that is sent to the worker.
* If a certain key already existed, it will be overridden.
* @param values the meta data to add
* @see setMetaData()
* @see mergeMetaData()
*/
void addMetaData(const QMap<QString, QString> &values);
/**
* Add key/value pairs to the meta data that is sent to the worker.
* If a certain key already existed, it will remain unchanged.
* @param values the meta data to merge
* @see setMetaData()
* @see addMetaData()
*/
void mergeMetaData(const QMap<QString, QString> &values);
/**
* @internal. For the scheduler. Do not use.
*/
MetaData outgoingMetaData() const;
/**
* Get meta data received from the worker.
* (Valid when first data is received and/or worker is finished)
* @return the job's meta data
*/
MetaData metaData() const;
/**
* Query meta data received from the worker.
* (Valid when first data is received and/or worker is finished)
* @param key the key of the meta data to retrieve
* @return the value of the meta data, or QString() if the
* @p key does not exist
*/
QString queryMetaData(const QString &key);
protected:
Q_SIGNALS:
/**
* Emitted when the worker successfully connected to the host.
* There is no guarantee the worker will send this, and this is
* currently unused (in the applications).
* @param job the job that emitted this signal
*/
void connected(KIO::Job *job);
protected:
/**
* Add a job that has to be finished before a result
* is emitted. This has obviously to be called before
* the finish signal is emitted by the worker.
*
* @param job the subjob to add
*/
bool addSubjob(KJob *job) override;
/**
* Mark a sub job as being done.
*
* Note that this does not terminate the parent job, even if @p job
* is the last subjob. emitResult must be called to indicate that
* the job is complete.
*
* @param job the subjob to remove
*/
bool removeSubjob(KJob *job) override;
protected:
JobPrivate *const d_ptr;
private:
Q_DECLARE_PRIVATE(Job)
};
/**
* Flags for the job properties.
* Not all flags are supported in all cases. Please see documentation of
* the calling function!
* @see JobFlags
*/
enum JobFlag {
/**
* Show the progress info GUI, no Resume and no Overwrite
*/
DefaultFlags = 0,
/**
* Hide progress information dialog, i.e.\ don't show a GUI.
*/
HideProgressInfo = 1,
/**
* When set, automatically append to the destination file if it exists already.
* WARNING: this is NOT the builtin support for offering the user to resume a previous
* partial download. The Resume option is much less used, it allows to append
* to an existing file.
* This is used by KIO::put(), KIO::file_copy(), KIO::file_move().
*/
Resume = 2,
/**
* When set, automatically overwrite the destination if it exists already.
* This is used by KIO::rename(), KIO::put(), KIO::file_copy(), KIO::file_move(), KIO::symlink().
* Otherwise the operation will fail with ERR_FILE_ALREADY_EXIST or ERR_DIR_ALREADY_EXIST.
*/
Overwrite = 4,
/**
* When set, notifies the worker that application/job does not want privilege execution.
* So in case of failure due to insufficient privileges show an error without attempting
* to run the operation as root first.
*
* @since 5.43
*/
NoPrivilegeExecution = 8,
};
/**
* Stores a combination of #JobFlag values.
*/
Q_DECLARE_FLAGS(JobFlags, JobFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(JobFlags)
enum LoadType {
Reload,
NoReload
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,368 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2000-2009 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOB_P_H
#define KIO_JOB_P_H
#include "commands_p.h"
#include "global.h"
#include "jobtracker.h"
#include "kiocoredebug.h"
#include "simplejob.h"
#include "transferjob.h"
#include "worker_p.h"
#include <KJobTrackerInterface>
#include <QDataStream>
#include <QPointer>
#include <QUrl>
#include <kio/jobuidelegateextension.h>
#include <kio/jobuidelegatefactory.h>
/* clang-format off */
#define KIO_ARGS \
QByteArray packedArgs; \
QDataStream stream(&packedArgs, QIODevice::WriteOnly); \
stream
/* clang-format on */
namespace KIO
{
static constexpr filesize_t invalidFilesize = static_cast<KIO::filesize_t>(-1);
// Exported for KIOWidgets jobs
class KIOCORE_EXPORT JobPrivate
{
public:
JobPrivate()
: m_parentJob(nullptr)
, m_extraFlags(0)
, m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension())
, m_privilegeExecutionEnabled(false)
{
}
virtual ~JobPrivate();
/**
* Some extra storage space for jobs that don't have their own
* private d pointer.
*/
enum {
EF_TransferJobAsync = (1 << 0),
EF_TransferJobNeedData = (1 << 1),
EF_TransferJobDataSent = (1 << 2),
EF_ListJobUnrestricted = (1 << 3),
EF_KillCalled = (1 << 4),
};
enum FileOperationType {
ChangeAttr, // chmod(), chown(), setModificationTime()
Copy,
Delete,
MkDir,
Move,
Rename,
Symlink,
Transfer, // put() and get()
Other, // if other file operation set message, title inside the job.
};
// Maybe we could use the QObject parent/child mechanism instead
// (requires a new ctor, and moving the ctor code to some init()).
Job *m_parentJob;
int m_extraFlags;
MetaData m_incomingMetaData;
MetaData m_internalMetaData;
MetaData m_outgoingMetaData;
JobUiDelegateExtension *m_uiDelegateExtension;
Job *q_ptr;
// For privilege operation
bool m_privilegeExecutionEnabled;
QString m_title, m_message;
FileOperationType m_operationType;
QByteArray privilegeOperationData();
void slotSpeed(KJob *job, unsigned long speed);
static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest);
static void emitRenaming(KIO::Job *, const QUrl &src, const QUrl &dest);
static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest);
static void emitCreatingDir(KIO::Job *, const QUrl &dir);
static void emitDeleting(KIO::Job *, const QUrl &url);
static void emitStating(KIO::Job *, const QUrl &url);
static void emitTransferring(KIO::Job *, const QUrl &url);
static void emitMounting(KIO::Job *, const QString &dev, const QString &point);
static void emitUnmounting(KIO::Job *, const QString &point);
Q_DECLARE_PUBLIC(Job)
};
class SimpleJobPrivate : public JobPrivate
{
public:
/**
* Creates a new simple job.
* @param url the url of the job
* @param command the command of the job
* @param packedArgs the arguments
*/
SimpleJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs)
: m_worker(nullptr)
, m_packedArgs(packedArgs)
, m_url(url)
, m_command(command)
, m_schedSerial(0)
, m_redirectionHandlingEnabled(true)
{
}
QPointer<Worker> m_worker;
QByteArray m_packedArgs;
QUrl m_url;
int m_command;
// for use in KIO::Scheduler
//
// There are two kinds of protocol:
// (1) The protocol of the url
// (2) The actual protocol that the KIO worker uses.
//
// These two often match, but not necessarily. Most notably, they don't
// match when doing ftp via a proxy.
// In that case (1) is ftp, but (2) is http.
//
// JobData::protocol stores (2) while Job::url().protocol() returns (1).
// The ProtocolInfoDict is indexed with (2).
//
// We schedule workers based on (2) but tell the worker about (1) via
// Worker::setProtocol().
QString m_protocol;
int m_schedSerial;
bool m_redirectionHandlingEnabled;
void simpleJobInit();
/**
* Called on a worker's connected signal.
* @see connected()
*/
void slotConnected();
/**
* Forward signal from the worker.
* @param data_size the processed size in bytes
* @see processedSize()
*/
void slotProcessedSize(KIO::filesize_t data_size);
/**
* Forward signal from the worker.
* @param speed the speed in bytes/s
* @see speed()
*/
void slotSpeed(unsigned long speed);
/**
* Forward signal from the worker.
* Can also be called by the parent job, when it knows the size.
* @param data_size the total size
*/
void slotTotalSize(KIO::filesize_t data_size);
/**
* Called on a worker's info message.
* @param s the info message
* @see infoMessage()
*/
void _k_slotWorkerInfoMessage(const QString &s);
/**
* Called when privilegeOperationRequested() is emitted by worker.
*/
void slotPrivilegeOperationRequested();
/**
* @internal
* Called by the scheduler when a worker gets to
* work on this job.
**/
virtual void start(KIO::Worker *worker);
/**
* @internal
* Called to detach a worker from a job.
**/
void workerDone();
/**
* Called by subclasses to restart the job after a redirection was signalled.
* The m_redirectionURL data member can appear in several subclasses, so we have it
* passed in. The regular URL will be set to the redirection URL which is then cleared.
*/
void restartAfterRedirection(QUrl *redirectionUrl);
Q_DECLARE_PUBLIC(SimpleJob)
static inline SimpleJobPrivate *get(KIO::SimpleJob *job)
{
return job->d_func();
}
static inline SimpleJob *newJobNoUi(const QUrl &url, int command, const QByteArray &packedArgs)
{
SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs));
return job;
}
static inline SimpleJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, JobFlags flags = HideProgressInfo)
{
SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
// Only delete, rename and symlink operation accept JobFlags.
FileOperationType opType;
switch (command) {
case CMD_DEL:
opType = Delete;
break;
case CMD_RENAME:
opType = Rename;
break;
case CMD_SYMLINK:
opType = Symlink;
break;
default:
return job;
}
job->d_func()->m_operationType = opType;
}
return job;
}
};
class TransferJobPrivate : public SimpleJobPrivate
{
public:
inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData)
: SimpleJobPrivate(url, command, packedArgs)
, m_internalSuspended(false)
, staticData(_staticData)
, m_isMimetypeEmitted(false)
, m_closedBeforeStart(false)
{
}
inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice)
: SimpleJobPrivate(url, command, packedArgs)
, m_internalSuspended(false)
, m_isMimetypeEmitted(false)
, m_closedBeforeStart(false)
, m_outgoingDataSource(QPointer<QIODevice>(ioDevice))
{
}
bool m_internalSuspended;
QByteArray staticData;
QUrl m_redirectionURL;
QList<QUrl> m_redirectionList;
QString m_mimetype;
bool m_isMimetypeEmitted;
bool m_closedBeforeStart;
QPointer<QIODevice> m_outgoingDataSource;
QMetaObject::Connection m_readChannelFinishedConnection;
/**
* Flow control. Suspend data processing from the worker.
*/
void internalSuspend();
/**
* Flow control. Resume data processing from the worker.
*/
void internalResume();
/**
* @internal
* Called by the scheduler when a worker gets to
* work on this job.
* @param worker the worker that works on the job
*/
void start(KIO::Worker *worker) override;
/**
* @internal
* Called when the KIO worker needs the data to send the server. This slot
* is invoked when the data is to be sent is read from a QIODevice rather
* instead of a QByteArray buffer.
*/
virtual void slotDataReqFromDevice();
void slotIODeviceClosed();
void slotIODeviceClosedBeforeStart();
void slotPostRedirection();
Q_DECLARE_PUBLIC(TransferJob)
static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData, JobFlags flags)
{
TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, _staticData));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
job->setFinishedNotificationHidden();
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = Transfer;
}
return job;
}
static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags)
{
TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, ioDevice));
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
if (!(flags & HideProgressInfo)) {
job->setFinishedNotificationHidden();
KIO::getJobTracker()->registerJob(job);
}
if (!(flags & NoPrivilegeExecution)) {
job->d_func()->m_privilegeExecutionEnabled = true;
job->d_func()->m_operationType = Transfer;
}
return job;
}
};
class DirectCopyJobPrivate;
/**
* @internal
* Used for direct copy from or to the local filesystem (i.e.\ WorkerBase::copy())
*/
class DirectCopyJob : public SimpleJob
{
Q_OBJECT
public:
DirectCopyJob(const QUrl &url, const QByteArray &packedArgs);
~DirectCopyJob() override;
public Q_SLOTS:
void slotCanResume(KIO::filesize_t offset);
Q_SIGNALS:
/**
* @internal
* Emitted if the job found an existing partial file
* and supports resuming. Used by FileCopyJob.
*/
void canResume(KIO::Job *job, KIO::filesize_t offset);
private:
Q_DECLARE_PRIVATE(DirectCopyJob)
};
}
#endif
@@ -0,0 +1,25 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "jobtracker.h"
#include <KJobTrackerInterface>
static KJobTrackerInterface *s_tracker = nullptr;
Q_GLOBAL_STATIC(KJobTrackerInterface, globalDummyTracker)
KJobTrackerInterface *KIO::getJobTracker()
{
if (!s_tracker) {
s_tracker = globalDummyTracker(); // don't return nullptr, caller doesn't expect that
}
return s_tracker;
}
void KIO::setJobTracker(KJobTrackerInterface *tracker)
{
s_tracker = tracker;
}
@@ -0,0 +1,30 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_JOBTRACKER_H
#define KIO_JOBTRACKER_H
#include "kiocore_export.h"
class KJobTrackerInterface;
namespace KIO
{
/**
* Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
*/
KIOCORE_EXPORT KJobTrackerInterface *getJobTracker();
/**
* @internal
* Allows the KIO widgets library to register its widget-based job tracker automatically.
* @since 5.0
*/
KIOCORE_EXPORT void setJobTracker(KJobTrackerInterface *tracker);
}
#endif /* KIO_JOBTRACKER_H */
@@ -0,0 +1,40 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <jobuidelegateextension.h>
using namespace KIO;
JobUiDelegateExtension::JobUiDelegateExtension()
: d(nullptr)
{
}
JobUiDelegateExtension::~JobUiDelegateExtension()
{
}
ClipboardUpdater *KIO::JobUiDelegateExtension::createClipboardUpdater(Job *, ClipboardUpdaterMode)
{
return nullptr;
}
void KIO::JobUiDelegateExtension::updateUrlInClipboard(const QUrl &, const QUrl &)
{
}
static JobUiDelegateExtension *s_extension = nullptr;
JobUiDelegateExtension *KIO::defaultJobUiDelegateExtension()
{
return s_extension;
}
void KIO::setDefaultJobUiDelegateExtension(JobUiDelegateExtension *extension)
{
s_extension = extension;
}
@@ -0,0 +1,217 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
SPDX-FileCopyrightText: 2000-2013 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOBUIDELEGATEEXTENSION_H
#define KIO_JOBUIDELEGATEEXTENSION_H
#include "kiocore_export.h"
#include <QDateTime>
#include <kio/global.h>
class KJob;
namespace KIO
{
class Job;
class ClipboardUpdater;
/**
* @see RenameDialog_Options
* @since 5.0
*/
enum RenameDialog_Option {
RenameDialog_Overwrite = 1, ///< We have an existing destination, show details about it and offer to overwrite it.
RenameDialog_OverwriteItself = 2, ///< Warn that the current operation would overwrite a file with itself, which is not allowed.
RenameDialog_Skip = 4, ///< Offer a "Skip" button, to skip other files too. Requires RenameDialog_MultipleItems.
RenameDialog_MultipleItems =
8, ///< Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply the user's choice to all files/folders.
RenameDialog_Resume = 16, ///< Offer a "Resume" button (plus "Resume All" if RenameDialog_MultipleItems).
RenameDialog_NoRename = 64, ///< Don't offer a "Rename" button.
RenameDialog_DestIsDirectory = 128, ///< The destination is a directory, the dialog updates labels and tooltips accordingly. @since 5.78
RenameDialog_SourceIsDirectory = 256, ///< The source is a directory, the dialog updates labels and tooltips accordingly. @since 5.78
};
/**
* Stores a combination of #RenameDialog_Option values.
*/
Q_DECLARE_FLAGS(RenameDialog_Options, RenameDialog_Option)
Q_DECLARE_OPERATORS_FOR_FLAGS(RenameDialog_Options)
/**
* @see SkipDialog_Options
* @since 5.0
*/
enum SkipDialog_Option {
/**
* Set if the current operation concerns multiple files, so it makes sense
* to offer buttons that apply the user's choice to all files/folders.
*/
SkipDialog_MultipleItems = 8,
/**
* Set if the current operation involves copying files/folders with certain
* characters in their names that are not supported by the destination
* filesystem (e.g.\ VFAT and NTFS disallow "*" in file/folder names).
*
* This will make the SkipDialog show a "Replace" button that can be used
* to instruct the underlying job to replace any problematic character with
* an underscore "_".
*
* @since 5.86
*/
SkipDialog_Replace_Invalid_Chars = 16,
/**
* Set if the current operation @e cannot be retried.
*
* For example if there is an issue that involves the destination filesystem
* support, e.g. VFAT and ExFat don't support symlinks, then retrying doesn't
* make sense.
*
* @since 5.88
*/
SkipDialog_Hide_Retry = 32,
};
/**
* Stores a combination of #SkipDialog_Option values.
*/
Q_DECLARE_FLAGS(SkipDialog_Options, SkipDialog_Option)
Q_DECLARE_OPERATORS_FOR_FLAGS(SkipDialog_Options)
/**
* The result of a rename or skip dialog
*/
enum RenameDialog_Result {
Result_Cancel = 0,
Result_Rename = 1,
Result_Skip = 2,
Result_AutoSkip = 3,
Result_Overwrite = 4,
Result_OverwriteAll = 5,
Result_Resume = 6,
Result_ResumeAll = 7,
Result_AutoRename = 8,
Result_Retry = 9,
/**
* Can be returned only when multiple files are passed, Option overwrite is passed
* And files modification times are valid
* @since 5.77
*/
Result_OverwriteWhenOlder = 10,
/**
* Can be returned if the user selects to replace any character disallowed
* by the destination filesystem with an underscore "_".
*
* See @ref SkipDialog_Option::SkipDialog_Replace_Invalid_Chars
*
* @since 5.86
*/
Result_ReplaceInvalidChars = 11,
/**
* The same as @c Result_ReplaceInvalidChars, but the user selected to
* automatically replace any invalid character, without being asked about
* every file/folder.
*
* @since 5.86
*/
Result_ReplaceAllInvalidChars = 12,
};
typedef RenameDialog_Result SkipDialog_Result;
/**
* @class KIO::JobUiDelegateExtension jobuidelegateextension.h <KIO/JobUiDelegateExtension>
*
* An abstract class defining interaction with users from KIO jobs:
* \li asking for confirmation before deleting files or directories
* @since 5.0
*/
class KIOCORE_EXPORT JobUiDelegateExtension
{
protected:
/**
* Constructor
*/
JobUiDelegateExtension();
/**
* Destructor
*/
virtual ~JobUiDelegateExtension();
public:
/**
* The type of deletion: real deletion, moving the files to the trash
* or emptying the trash
* Used by askDeleteConfirmation.
*/
enum DeletionType {
Delete,
Trash,
EmptyTrash
};
/**
* ForceConfirmation: always ask the user for confirmation
* DefaultConfirmation: don't ask the user if he/she said "don't ask again".
*
* Used by askDeleteConfirmation.
*/
enum ConfirmationType {
DefaultConfirmation,
ForceConfirmation
};
/**
* Ask for confirmation before deleting/trashing @p urls.
*
* Note that this method is not called automatically by KIO jobs. It's the application's
* responsibility to ask the user for confirmation before calling KIO::del() or KIO::trash().
*
* @param urls the urls about to be deleted/trashed
* @param deletionType the type of deletion (Delete for real deletion, Trash otherwise)
* @param confirmationType see ConfirmationType. Normally set to DefaultConfirmation.
* Note: the window passed to setWindow is used as the parent for the message box.
* @return true if confirmed
*/
virtual bool askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType) = 0;
enum ClipboardUpdaterMode {
UpdateContent,
OverwriteContent,
RemoveContent,
};
/**
* Creates a clipboard updater as a child of the given job.
*/
virtual ClipboardUpdater *createClipboardUpdater(Job *job, ClipboardUpdaterMode mode);
/**
* Update URL in clipboard, if present
*/
virtual void updateUrlInClipboard(const QUrl &src, const QUrl &dest);
// TODO KF6: add virtual_hook
private:
class Private;
Private *const d;
};
/**
* Returns the default job UI delegate extension to be used by all KIO jobs (in which HideProgressInfo is not set)
* Can return nullptr, if no kio GUI library is loaded.
* @since 5.0
*/
KIOCORE_EXPORT JobUiDelegateExtension *defaultJobUiDelegateExtension();
/**
* Internal. Allows the KIO widgets library to register its widget-based job UI delegate extension
* automatically.
* @since 5.0
*/
KIOCORE_EXPORT void setDefaultJobUiDelegateExtension(JobUiDelegateExtension *extension);
} // namespace KIO
#endif
@@ -0,0 +1,40 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <jobuidelegatefactory.h>
using namespace KIO;
JobUiDelegateFactory::JobUiDelegateFactory()
: d(nullptr)
{
}
JobUiDelegateFactory::~JobUiDelegateFactory() = default;
static JobUiDelegateFactory *s_factory = nullptr;
KJobUiDelegate *KIO::createDefaultJobUiDelegate()
{
return s_factory ? s_factory->createDelegate() : nullptr;
}
KJobUiDelegate *KIO::createDefaultJobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window)
{
return s_factory ? s_factory->createDelegate(flags, window) : nullptr;
}
JobUiDelegateFactory *KIO::defaultJobUiDelegateFactory()
{
return s_factory;
}
void KIO::setDefaultJobUiDelegateFactory(JobUiDelegateFactory *factory)
{
s_factory = factory;
}
@@ -0,0 +1,118 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 David Faure <faure+bluesystems@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_JOBUIDELEGATEFACTORY_H
#define KIO_JOBUIDELEGATEFACTORY_H
#include "job_base.h"
#include "kiocore_export.h"
#include <KJobUiDelegate>
#include <QDateTime>
#include <kio/global.h>
#include <KCompositeJob>
class QWidget;
namespace KIO
{
/**
* @class KIO::JobUiDelegateFactory jobuidelegatefactory.h <KIO/JobUiDelegateFactory>
*
* A factory for creating job ui delegates.
* Every KIO job will get a delegate from this factory.
* \since 5.0
*/
class KIOCORE_EXPORT JobUiDelegateFactory
{
protected:
/**
* Constructor
*/
JobUiDelegateFactory();
/**
* Destructor
*/
virtual ~JobUiDelegateFactory();
public:
virtual KJobUiDelegate *createDelegate() const = 0;
/**
* @since 6.0
*/
virtual KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const = 0;
private:
class Private;
Private *const d;
};
/**
* Convenience method: use default factory, if there's one, to create a delegate and return it.
*/
KIOCORE_EXPORT KJobUiDelegate *createDefaultJobUiDelegate();
/**
* Convenience method: use default factory, if there's one, to create a delegate and return it.
*
* @since 5.98
*/
KIOCORE_EXPORT KJobUiDelegate *createDefaultJobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window);
/**
* Returns the default job UI delegate factory to be used by all KIO jobs (in which HideProgressInfo is not set)
* Can return nullptr, if no kio GUI library is loaded.
* @since 6.0
*/
KIOCORE_EXPORT JobUiDelegateFactory *defaultJobUiDelegateFactory();
/**
* Internal. Allows the KIO widgets library to register its widget-based job UI delegate factory
* automatically.
* @since 6.0
*/
KIOCORE_EXPORT void setDefaultJobUiDelegateFactory(JobUiDelegateFactory *factory);
/**
* Returns the child of the job's uiDelegate() that implements the given extension,
* or nullptr if none was found (or if the job had no uiDelegate).
* @since 5.78
*/
template<typename T>
inline T delegateExtension(KJob *job)
{
KJobUiDelegate *ui = job->uiDelegate();
// If setParentJob() was used, try the uiDelegate of parentJob first
if (!ui) {
if (KIO::Job *kiojob = qobject_cast<KIO::Job *>(job)) {
if (KJob *parentJob = kiojob->parentJob()) {
ui = parentJob->uiDelegate();
}
}
}
// Still nothing? if compositeJob->addSubjob(job) was used, try the ui delegate
// of compositeJob
while (!ui) {
job = qobject_cast<KCompositeJob *>(job->parent());
if (job) {
ui = job->uiDelegate();
} else {
break;
}
}
return ui ? ui->findChild<T>(QString(), Qt::FindDirectChildrenOnly) : nullptr;
}
} // namespace KIO
#endif
@@ -0,0 +1,691 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// $Id: kacl.cpp 424977 2005-06-13 15:13:22Z tilladam $
#include <config-kiocore.h>
#include "kacl.h"
#if HAVE_POSIX_ACL
#include "../aclhelpers_p.h"
#endif
#include <QDataStream>
#include <QHash>
#include <QString>
#if HAVE_POSIX_ACL
using namespace KIO;
#endif
class Q_DECL_HIDDEN KACL::KACLPrivate
{
public:
KACLPrivate()
#if HAVE_POSIX_ACL
: m_acl(nullptr)
#endif
{
}
#if HAVE_POSIX_ACL
explicit KACLPrivate(acl_t acl)
: m_acl(acl)
{
}
#endif
#if HAVE_POSIX_ACL
~KACLPrivate()
{
if (m_acl) {
acl_free(m_acl);
}
}
#endif
// helpers
#if HAVE_POSIX_ACL
bool setMaskPermissions(unsigned short v);
QString getUserName(uid_t uid) const;
QString getGroupName(gid_t gid) const;
bool setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type);
bool setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type);
acl_t m_acl;
mutable QHash<uid_t, QString> m_usercache;
mutable QHash<gid_t, QString> m_groupcache;
#endif
};
KACL::KACL(const QString &aclString)
: d(new KACLPrivate)
{
setACL(aclString);
}
KACL::KACL(mode_t basePermissions)
#if HAVE_POSIX_ACL
: d(new KACLPrivate(ACLPortability::acl_from_mode(basePermissions)))
#else
: d(new KACLPrivate)
#endif
{
#if !HAVE_POSIX_ACL
Q_UNUSED(basePermissions);
#endif
}
KACL::KACL()
: d(new KACLPrivate)
{
}
KACL::KACL(const KACL &rhs)
: d(new KACLPrivate)
{
setACL(rhs.asString());
}
KACL::~KACL() = default;
KACL &KACL::operator=(const KACL &rhs)
{
if (this != &rhs) {
setACL(rhs.asString());
}
return *this;
}
bool KACL::operator==(const KACL &rhs) const
{
#if HAVE_POSIX_ACL
return (ACLPortability::acl_cmp(d->m_acl, rhs.d->m_acl) == 0);
#else
Q_UNUSED(rhs);
return true;
#endif
}
bool KACL::operator!=(const KACL &rhs) const
{
return !operator==(rhs);
}
bool KACL::isValid() const
{
bool valid = false;
#if HAVE_POSIX_ACL
if (d->m_acl) {
valid = (acl_valid(d->m_acl) == 0);
}
#endif
return valid;
}
bool KACL::isExtended() const
{
#if HAVE_POSIX_ACL
return (ACLPortability::acl_equiv_mode(d->m_acl, nullptr) != 0);
#else
return false;
#endif
}
#if HAVE_POSIX_ACL
static acl_entry_t entryForTag(acl_t acl, acl_tag_t tag)
{
acl_entry_t entry;
int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == tag) {
return entry;
}
ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
}
return nullptr;
}
static unsigned short entryToPermissions(acl_entry_t entry)
{
if (entry == nullptr) {
return 0;
}
acl_permset_t permset;
if (acl_get_permset(entry, &permset) != 0) {
return 0;
}
return (ACLPortability::acl_get_perm(permset, ACL_READ) << 2 | ACLPortability::acl_get_perm(permset, ACL_WRITE) << 1
| ACLPortability::acl_get_perm(permset, ACL_EXECUTE));
}
static void permissionsToEntry(acl_entry_t entry, unsigned short v)
{
if (entry == nullptr) {
return;
}
acl_permset_t permset;
if (acl_get_permset(entry, &permset) != 0) {
return;
}
acl_clear_perms(permset);
if (v & 4) {
acl_add_perm(permset, ACL_READ);
}
if (v & 2) {
acl_add_perm(permset, ACL_WRITE);
}
if (v & 1) {
acl_add_perm(permset, ACL_EXECUTE);
}
}
static int getUidForName(const QString &name)
{
struct passwd *user = getpwnam(name.toLocal8Bit().constData());
if (user) {
return user->pw_uid;
} else {
return -1;
}
}
static int getGidForName(const QString &name)
{
struct group *group = getgrnam(name.toLocal8Bit().constData());
if (group) {
return group->gr_gid;
} else {
return -1;
}
}
#endif
// ------------------ begin API implementation ------------
unsigned short KACL::ownerPermissions() const
{
#if HAVE_POSIX_ACL
return entryToPermissions(entryForTag(d->m_acl, ACL_USER_OBJ));
#else
return 0;
#endif
}
bool KACL::setOwnerPermissions(unsigned short v)
{
#if HAVE_POSIX_ACL
permissionsToEntry(entryForTag(d->m_acl, ACL_USER_OBJ), v);
#else
Q_UNUSED(v);
#endif
return true;
}
unsigned short KACL::owningGroupPermissions() const
{
#if HAVE_POSIX_ACL
return entryToPermissions(entryForTag(d->m_acl, ACL_GROUP_OBJ));
#else
return 0;
#endif
}
bool KACL::setOwningGroupPermissions(unsigned short v)
{
#if HAVE_POSIX_ACL
permissionsToEntry(entryForTag(d->m_acl, ACL_GROUP_OBJ), v);
#else
Q_UNUSED(v);
#endif
return true;
}
unsigned short KACL::othersPermissions() const
{
#if HAVE_POSIX_ACL
return entryToPermissions(entryForTag(d->m_acl, ACL_OTHER));
#else
return 0;
#endif
}
bool KACL::setOthersPermissions(unsigned short v)
{
#if HAVE_POSIX_ACL
permissionsToEntry(entryForTag(d->m_acl, ACL_OTHER), v);
#else
Q_UNUSED(v);
#endif
return true;
}
mode_t KACL::basePermissions() const
{
mode_t perms(0);
#if HAVE_POSIX_ACL
if (ownerPermissions() & ACL_READ) {
perms |= S_IRUSR;
}
if (ownerPermissions() & ACL_WRITE) {
perms |= S_IWUSR;
}
if (ownerPermissions() & ACL_EXECUTE) {
perms |= S_IXUSR;
}
if (owningGroupPermissions() & ACL_READ) {
perms |= S_IRGRP;
}
if (owningGroupPermissions() & ACL_WRITE) {
perms |= S_IWGRP;
}
if (owningGroupPermissions() & ACL_EXECUTE) {
perms |= S_IXGRP;
}
if (othersPermissions() & ACL_READ) {
perms |= S_IROTH;
}
if (othersPermissions() & ACL_WRITE) {
perms |= S_IWOTH;
}
if (othersPermissions() & ACL_EXECUTE) {
perms |= S_IXOTH;
}
#endif
return perms;
}
unsigned short KACL::maskPermissions(bool &exists) const
{
exists = true;
#if HAVE_POSIX_ACL
acl_entry_t entry = entryForTag(d->m_acl, ACL_MASK);
if (entry == nullptr) {
exists = false;
return 0;
}
return entryToPermissions(entry);
#else
return 0;
#endif
}
#if HAVE_POSIX_ACL
bool KACL::KACLPrivate::setMaskPermissions(unsigned short v)
{
acl_entry_t entry = entryForTag(m_acl, ACL_MASK);
if (entry == nullptr) {
acl_create_entry(&m_acl, &entry);
acl_set_tag_type(entry, ACL_MASK);
}
permissionsToEntry(entry, v);
return true;
}
#endif
bool KACL::setMaskPermissions(unsigned short v)
{
#if HAVE_POSIX_ACL
return d->setMaskPermissions(v);
#else
Q_UNUSED(v);
return true;
#endif
}
#if HAVE_POSIX_ACL
using unique_ptr_acl_free = std::unique_ptr<void, int (*)(void *)>;
#endif
/**************************
* Deal with named users *
**************************/
unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const
{
#if HAVE_POSIX_ACL
acl_entry_t entry;
*exists = false;
int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == ACL_USER) {
const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
const uid_t id = *(static_cast<uid_t *>(idptr.get()));
if (d->getUserName(id) == name) {
*exists = true;
return entryToPermissions(entry);
}
}
ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
}
#else
Q_UNUSED(name);
Q_UNUSED(exists);
#endif
return 0;
}
#if HAVE_POSIX_ACL
bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type)
{
bool allIsWell = true;
acl_t newACL = acl_dup(m_acl);
acl_entry_t entry;
bool createdNewEntry = false;
bool found = false;
int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == type) {
const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
const int id = *(static_cast<int *>(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t)
const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id);
if (entryName == name) {
// found him, update
permissionsToEntry(entry, permissions);
found = true;
break;
}
}
ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
}
if (!found) {
acl_create_entry(&newACL, &entry);
acl_set_tag_type(entry, type);
int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
acl_delete_entry(newACL, entry);
allIsWell = false;
} else {
permissionsToEntry(entry, permissions);
createdNewEntry = true;
}
}
if (allIsWell && createdNewEntry) {
// 23.1.1 of 1003.1e states that as soon as there is a named user or
// named group entry, there needs to be a mask entry as well, so add
// one, if the user hasn't explicitly set one.
if (entryForTag(newACL, ACL_MASK) == nullptr) {
acl_calc_mask(&newACL);
}
}
if (!allIsWell || acl_valid(newACL) != 0) {
acl_free(newACL);
allIsWell = false;
} else {
acl_free(m_acl);
m_acl = newACL;
}
return allIsWell;
}
#endif
bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions)
{
#if HAVE_POSIX_ACL
return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER);
#else
Q_UNUSED(name);
Q_UNUSED(permissions);
return true;
#endif
}
ACLUserPermissionsList KACL::allUserPermissions() const
{
ACLUserPermissionsList list;
#if HAVE_POSIX_ACL
acl_entry_t entry;
int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == ACL_USER) {
const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
const uid_t id = *(static_cast<uid_t *>(idptr.get()));
QString name = d->getUserName(id);
unsigned short permissions = entryToPermissions(entry);
ACLUserPermissions pair = qMakePair(name, permissions);
list.append(pair);
}
ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
}
#endif
return list;
}
#if HAVE_POSIX_ACL
bool KACL::KACLPrivate::setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type)
{
bool allIsWell = true;
bool atLeastOneUserOrGroup = false;
// make working copy, in case something goes wrong
acl_t newACL = acl_dup(m_acl);
acl_entry_t entry;
// clear user entries
int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == type) {
acl_delete_entry(newACL, entry);
// we have to start from the beginning, the iterator is
// invalidated, on deletion
ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
} else {
ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
}
}
// now add the entries from the list
for (const auto &[name, userId] : list) {
acl_create_entry(&newACL, &entry);
acl_set_tag_type(entry, type);
int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
// user or group doesn't exist => error
acl_delete_entry(newACL, entry);
allIsWell = false;
break;
} else {
permissionsToEntry(entry, userId);
atLeastOneUserOrGroup = true;
}
}
if (allIsWell && atLeastOneUserOrGroup) {
// 23.1.1 of 1003.1e states that as soon as there is a named user or
// named group entry, there needs to be a mask entry as well, so add
// one, if the user hasn't explicitly set one.
if (entryForTag(newACL, ACL_MASK) == nullptr) {
acl_calc_mask(&newACL);
}
}
if (allIsWell && (acl_valid(newACL) == 0)) {
acl_free(m_acl);
m_acl = newACL;
} else {
acl_free(newACL);
}
return allIsWell;
}
#endif
bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users)
{
#if HAVE_POSIX_ACL
return d->setAllUsersOrGroups(users, ACL_USER);
#else
Q_UNUSED(users);
return true;
#endif
}
/**************************
* Deal with named groups *
**************************/
unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const
{
*exists = false;
#if HAVE_POSIX_ACL
acl_entry_t entry;
int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == ACL_GROUP) {
const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
const gid_t id = *(static_cast<gid_t *>(idptr.get()));
if (d->getGroupName(id) == name) {
*exists = true;
return entryToPermissions(entry);
}
}
ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
}
#else
Q_UNUSED(name);
#endif
return 0;
}
bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions)
{
#if HAVE_POSIX_ACL
return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP);
#else
Q_UNUSED(name);
Q_UNUSED(permissions);
return true;
#endif
}
ACLGroupPermissionsList KACL::allGroupPermissions() const
{
ACLGroupPermissionsList list;
#if HAVE_POSIX_ACL
acl_entry_t entry;
int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
while (ret == 1) {
acl_tag_t currentTag;
acl_get_tag_type(entry, &currentTag);
if (currentTag == ACL_GROUP) {
const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
const gid_t id = *(static_cast<gid_t *>(idptr.get()));
QString name = d->getGroupName(id);
unsigned short permissions = entryToPermissions(entry);
ACLGroupPermissions pair = qMakePair(name, permissions);
list.append(pair);
}
ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
}
#endif
return list;
}
bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups)
{
#if HAVE_POSIX_ACL
return d->setAllUsersOrGroups(groups, ACL_GROUP);
#else
Q_UNUSED(groups);
return true;
#endif
}
/**************************
* from and to string *
**************************/
bool KACL::setACL(const QString &aclStr)
{
bool ret = false;
#if HAVE_POSIX_ACL
acl_t temp = acl_from_text(aclStr.toLatin1().constData());
if (acl_valid(temp) != 0) {
// TODO errno is set, what to do with it here?
acl_free(temp);
} else {
if (d->m_acl) {
acl_free(d->m_acl);
}
d->m_acl = temp;
ret = true;
}
#else
Q_UNUSED(aclStr);
#endif
return ret;
}
QString KACL::asString() const
{
#if HAVE_POSIX_ACL
ssize_t size = 0;
char *txt = acl_to_text(d->m_acl, &size);
const QString ret = QString::fromLatin1(txt, size);
acl_free(txt);
return ret;
#else
return QString();
#endif
}
// helpers
#if HAVE_POSIX_ACL
QString KACL::KACLPrivate::getUserName(uid_t uid) const
{
auto it = m_usercache.find(uid);
if (it == m_usercache.end()) {
struct passwd *user = getpwuid(uid);
if (user) {
it = m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
} else {
return QString::number(uid);
}
}
return *it;
}
QString KACL::KACLPrivate::getGroupName(gid_t gid) const
{
auto it = m_groupcache.find(gid);
if (it == m_groupcache.end()) {
struct group *grp = getgrgid(gid);
if (grp) {
it = m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
} else {
return QString::number(gid);
}
}
return *it;
}
#endif
void KACL::virtual_hook(int, void *)
{
/*BASE::virtual_hook( id, data );*/
}
QDataStream &operator<<(QDataStream &s, const KACL &a)
{
s << a.asString();
return s;
}
QDataStream &operator>>(QDataStream &s, KACL &a)
{
QString str;
s >> str;
a.setACL(str);
return s;
}
@@ -0,0 +1,196 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KACL_H
#define KACL_H
#include "kiocore_export.h"
#include <qplatformdefs.h>
#include <QList>
#include <QPair>
#include <memory>
typedef QPair<QString, unsigned short> ACLUserPermissions;
typedef QList<ACLUserPermissions> ACLUserPermissionsList;
typedef QList<ACLUserPermissions>::iterator ACLUserPermissionsIterator;
typedef QList<ACLUserPermissions>::const_iterator ACLUserPermissionsConstIterator;
typedef QPair<QString, unsigned short> ACLGroupPermissions;
typedef QList<ACLGroupPermissions> ACLGroupPermissionsList;
typedef QList<ACLGroupPermissions>::iterator ACLGroupPermissionsIterator;
typedef QList<ACLGroupPermissions>::const_iterator ACLGroupPermissionsConstIterator;
/**
* @class KACL kacl.h <KACL>
*
* The KACL class encapsulates a POSIX Access Control List. It follows the
* little standard that couldn't, 1003.1e/1003.2c, which died in draft status.
* @short a POSIX ACL encapsulation
* @author Till Adam <adam@kde.org>
*/
class KIOCORE_EXPORT KACL
{
public:
/**
* Creates a new KACL from @p aclString. If the string is a valid acl
* string, isValid() will afterwards return true.
*/
KACL(const QString &aclString);
/** Copy ctor */
KACL(const KACL &rhs);
/**
* Creates a new KACL from the basic permissions passed in @p basicPermissions.
* isValid() will return true, afterwards.
*/
KACL(mode_t basicPermissions);
/**
* Creates an empty KACL. Until a valid acl string is set via setACL,
* isValid() will return false.
*/
KACL();
virtual ~KACL();
KACL &operator=(const KACL &rhs);
bool operator==(const KACL &rhs) const;
bool operator!=(const KACL &rhs) const;
/**
* Returns whether the KACL object represents a valid acl.
* @return whether the KACL object represents a valid acl.
*/
bool isValid() const;
/** The standard (non-extended) part of an ACL. These map directly to
* standard unix file permissions. Setting them will never make a valid
* ACL invalid. */
/** @return the owner's permissions entry */
unsigned short ownerPermissions() const;
/** Set the owner's permissions entry.
* @return success or failure */
bool setOwnerPermissions(unsigned short);
/** @return the owning group's permissions entry */
unsigned short owningGroupPermissions() const;
/** Set the owning group's permissions entry.
* @return success or failure */
bool setOwningGroupPermissions(unsigned short);
/** @return the permissions entry for others */
unsigned short othersPermissions() const;
/** Set the permissions entry for others.
* @return success or failure */
bool setOthersPermissions(unsigned short);
/** @return the basic (owner/group/others) part of the ACL as a mode_t */
mode_t basePermissions() const;
/** The interface to the extended ACL. This is a mask, permissions for
* n named users and permissions for m named groups. */
/**
* Return whether the ACL contains extended entries or can be expressed
* using only basic file permissions.
* @return whether the ACL contains extended entries */
bool isExtended() const;
/**
* Return the entry for the permissions mask if there is one and sets
* @p exists to true. If there is no such entry, @p exists is set to false.
* @return the permissions mask entry */
unsigned short maskPermissions(bool &exists) const;
/** Set the permissions mask for the ACL. Permissions set for individual
* entries will be masked with this, such that their effective permissions
* are the result of the logical and of their entry and the mask.
* @return success or failure */
bool setMaskPermissions(unsigned short);
/**
* Access to the permissions entry for a named user, if such an entry
* exists. If @p exists is non-null, the boolean variable it points to
* is set to true if a matching entry exists and to false otherwise.
* @return the permissions for a user entry with the name in @p name */
unsigned short namedUserPermissions(const QString &name, bool *exists) const;
/** Set the permissions for a user with the name @p name. Will fail
* if the user doesn't exist, in which case the ACL will be unchanged.
* @return success or failure. */
bool setNamedUserPermissions(const QString &name, unsigned short);
/** Returns the list of all group permission entries. Each entry consists
* of a name/permissions pair. This is a QPair, therefore access is provided
* via the .first and .next members.
* @return the list of all group permission entries. */
ACLUserPermissionsList allUserPermissions() const;
/** Replace the list of all user permissions with @p list. If one
* of the entries in the list does not exists, or setting of the ACL
* entry fails for any reason, the ACL will be left unchanged.
* @return success or failure */
bool setAllUserPermissions(const ACLUserPermissionsList &list);
/**
* Access to the permissions entry for a named group, if such an entry
* exists. If @p exists is non-null, the boolean variable it points to is
* set to true if a matching entry exists and to false otherwise.
* @return the permissions for a group with the name in @p name */
unsigned short namedGroupPermissions(const QString &name, bool *exists) const;
/** Set the permissions for a group with the name @p name. Will fail
* if the group doesn't exist, in which case the ACL be unchanged.
* @return success or failure. */
bool setNamedGroupPermissions(const QString &name, unsigned short);
/** Returns the list of all group permission entries. Each entry consists
* of a name/permissions pair. This is a QPair, therefore access is provided
* via the .first and .next members.
* @return the list of all group permission entries. */
ACLGroupPermissionsList allGroupPermissions() const;
/** Replace the list of all user permissions with @p list. If one
* of the entries in the list does not exists, or setting of the ACL
* entry fails for any reason, the ACL will be left unchanged.
* @return success or failure */
bool setAllGroupPermissions(const ACLGroupPermissionsList &);
/** Sets the whole list from a string. If the string in @p aclStr represents
* a valid ACL, it will be set, otherwise the ACL remains unchanged.
* @return whether setting the ACL was successful. */
bool setACL(const QString &aclStr);
/** Return a string representation of the ACL.
* @return a string version of the ACL in the format compatible with libacl and
* POSIX 1003.1e. Implementations conforming to that standard should be able
* to take such strings as input. */
QString asString() const;
protected:
virtual void virtual_hook(int id, void *data);
private:
class KACLPrivate;
std::unique_ptr<KACLPrivate> const d;
KIOCORE_EXPORT friend QDataStream &operator<<(QDataStream &s, const KACL &a);
KIOCORE_EXPORT friend QDataStream &operator>>(QDataStream &s, KACL &a);
};
KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const KACL &a);
KIOCORE_EXPORT QDataStream &operator>>(QDataStream &s, KACL &a);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,635 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2001, 2002, 2004-2006 Michael Brade <brade@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOREDIRLISTER_H
#define KCOREDIRLISTER_H
#include "kfileitem.h"
#include <QString>
#include <QStringList>
#include <QUrl>
#include <memory>
class KJob;
namespace KIO
{
class Job;
class ListJob;
}
class KCoreDirListerPrivate;
/**
* @class KCoreDirLister kcoredirlister.h <KCoreDirLister>
*
* @short Helper class for the kiojob used to list and update a directory.
*
* The dir lister deals with the kiojob used to list and update a directory
* and has signals for the user of this class (e.g. konqueror view or
* kdesktop) to create/destroy its items when asked.
*
* This class is independent from the graphical representation of the dir
* (icon container, tree view, ...) and it stores the items (as KFileItems).
*
* Typical usage :
* @li Create an instance.
* @li Connect to at least update, clear, itemsAdded, and itemsDeleted.
* @li Call openUrl - the signals will be called.
* @li Reuse the instance when opening a new url (openUrl).
* @li Destroy the instance when not needed anymore (usually destructor).
*
* Advanced usage : call openUrl with OpenUrlFlag::Keep to list directories
* without forgetting the ones previously read (e.g. for a tree view)
*
* @author Michael Brade <brade@kde.org>
*/
class KIOCORE_EXPORT KCoreDirLister : public QObject
{
friend class KCoreDirListerCache;
friend struct KCoreDirListerCacheDirectoryData;
Q_OBJECT
Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate)
Q_PROPERTY(bool showHiddenFiles READ showHiddenFiles WRITE setShowHiddenFiles)
Q_PROPERTY(bool dirOnlyMode READ dirOnlyMode WRITE setDirOnlyMode)
Q_PROPERTY(bool delayedMimeTypes READ delayedMimeTypes WRITE setDelayedMimeTypes)
Q_PROPERTY(bool requestMimeTypeWhileListing READ requestMimeTypeWhileListing WRITE setRequestMimeTypeWhileListing)
Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter)
Q_PROPERTY(QStringList mimeFilter READ mimeFilters WRITE setMimeFilter RESET clearMimeFilter)
Q_PROPERTY(bool autoErrorHandlingEnabled READ autoErrorHandlingEnabled WRITE setAutoErrorHandlingEnabled)
public:
/**
* @see OpenUrlFlags
*/
enum OpenUrlFlag {
NoFlags = 0x0, ///< No additional flags specified.
Keep = 0x1, ///< Previous directories aren't forgotten
///< (they are still watched by kdirwatch and their items
///< are kept for this KCoreDirLister). This is useful for e.g.
///< a treeview.
Reload = 0x2, ///< Indicates whether to use the cache or to reread
///< the directory from the disk.
///< Use only when opening a dir not yet listed by this lister
///< without using the cache. Otherwise use updateDirectory.
};
/**
* Stores a combination of #OpenUrlFlag values.
*/
Q_DECLARE_FLAGS(OpenUrlFlags, OpenUrlFlag)
/**
* Create a directory lister.
*/
KCoreDirLister(QObject *parent = nullptr);
/**
* Destroy the directory lister.
*/
~KCoreDirLister() override;
/**
* Run the directory lister on the given url.
*
* This method causes KCoreDirLister to emit @em all the items of @p dirUrl, in any case.
* Depending on @p flags, either clear() or clearDir(const QUrl &) will be emitted first.
*
* The newItems() signal may be emitted more than once to supply you with KFileItems, up
* until the signal completed() is emitted (and isFinished() returns @c true).
*
* @param dirUrl the directory URL.
* @param flags whether to keep previous directories, and whether to reload, see OpenUrlFlags
* @return @c true if successful, @c false otherwise (e.g. if @p dirUrl is invalid)
*/
bool openUrl(const QUrl &dirUrl, OpenUrlFlags flags = NoFlags); // TODO KF6: change bool to void
/**
* Stop listing all directories currently being listed.
*
* Emits canceled() if there was at least one job running.
* Emits listingDirCanceled(const QUrl &) for each stopped job if there is more than one
* directory being watched by this KCoreDirLister.
*/
void stop();
/**
* Stop listing the given directory.
*
* Emits canceled() if the killed job was the last running one.
* Emits listingDirCanceled(const QUrl &) for the killed job if there is more than one
* directory being watched by this KCoreDirLister.
*
* No signal is emitted if there was no job running for @p dirUrl.
*
* @param dirUrl the directory URL
*/
void stop(const QUrl &dirUrl);
/**
* Stop listening for further changes in the given directory.
* When a new directory is opened with OpenUrlFlag::Keep the caller will keep being notified of file changes for all directories that were kept open.
* This call selectively removes a directory from sending future notifications to this KCoreDirLister.
*
* @param dirUrl the directory URL.
* @since 5.91
*/
void forgetDirs(const QUrl &dirUrl);
/**
* @return @c true if the "delayed MIME types" feature was enabled
* @see setDelayedMimeTypes
*/
bool delayedMimeTypes() const;
/**
* Delayed MIME types feature:
* If enabled, MIME types will be fetched on demand, which leads to a
* faster initial directory listing, where icons get progressively replaced
* with the correct one while KMimeTypeResolver is going through the items
* with unknown or imprecise MIME type (e.g. files with no extension or an
* unknown extension).
*/
void setDelayedMimeTypes(bool delayedMimeTypes);
/**
* Checks whether KDirWatch will automatically update directories. This is
* enabled by default.
*
* @return @c true if KDirWatch is used to automatically update directories
*/
bool autoUpdate() const;
/**
* Toggle automatic directory updating, when a directory changes (using KDirWatch).
*
* @param enable set to @c true to enable or @c false to disable
*/
void setAutoUpdate(bool enable);
/**
* Checks whether hidden files (e.g. files whose name start with '.' on Unix) will be shown.
* By default this option is disabled (hidden files are not shown).
*
* @return @c true if hidden files are shown, @c false otherwise
*
* @see setShowHiddenFiles()
* @since 5.100
*/
bool showHiddenFiles() const;
/**
* Toggles whether hidden files (e.g. files whose name start with '.' on Unix) are shown/
* By default hidden files are not shown.
*
* You need to call emitChanges() afterwards.
*
* @param showHiddenFiles set to @c true/false to show/hide hidden files respectively
*
* @see showHiddenFiles()
* @since 5.100
*/
void setShowHiddenFiles(bool showHiddenFiles);
/**
* Checks whether this KCoreDirLister only lists directories or all items (directories and
* files), by default all items are listed.
*
* @return @c true if only directories are listed, @c false otherwise
*
* @see setDirOnlyMode(bool)
*/
bool dirOnlyMode() const;
/**
* Call this to list only directories (by default all items (directories and files)
* are listed).
*
* You need to call emitChanges() afterwards.
*
* @param dirsOnly set to @c true to list only directories
*/
void setDirOnlyMode(bool dirsOnly);
/**
* Checks whether this KCoreDirLister requests the MIME type of files from the worker.
*
* Enabling this will tell the worker used for listing that it should try to
* determine the mime type of entries while listing them. This potentially
* reduces the speed at which entries are listed but ensures mime types are
* immediately available when an entry is added, greatly speeding up things
* like mime type filtering.
*
* By default this is disabled.
*
* @return @c true if the worker is asked for MIME types, @c false otherwise.
*
* @see setRequestMimeTypeWhileListing(bool)
*
* @since 5.82
*/
bool requestMimeTypeWhileListing() const;
/**
* Toggles whether to request MIME types from the worker or in-process.
*
* @param request set to @c true to request MIME types from the worker.
*
* @note If this is changed while the lister is already listing a directory,
* it will only have an effect the next time openUrl() is called.
*
* @see requestMimeTypeWhileListing()
*
* @since 5.82
*/
void setRequestMimeTypeWhileListing(bool request);
/**
* Returns the top level URL that is listed by this KCoreDirLister.
*
* It might be different from the one given with openUrl() if there was a
* redirection. If you called openUrl() with OpenUrlFlag::Keep this is the
* first url opened (e.g. in a treeview this is the root).
*
* @return the url used by this instance to list the files
*/
QUrl url() const;
/**
* Returns all URLs that are listed by this KCoreDirLister. This is only
* useful if you called openUrl() with OpenUrlFlag::Keep, as it happens in a
* treeview, for example. (Note that the base url is included in the list
* as well, of course.)
*
* @return a list of all listed URLs
*/
QList<QUrl> directories() const;
/**
* Actually emit the changes made with setShowHiddenFiles, setDirOnlyMode,
* setNameFilter and setMimeFilter.
*/
void emitChanges();
/**
* Update the directory @p dirUrl. This method causes KCoreDirLister to @em only emit
* the items of @p dirUrl that actually changed compared to the current state in the
* cache, and updates the cache.
*
* The current implementation calls updateDirectory automatically for local files, using
* KDirWatch (if autoUpdate() is @c true), but it might be useful to force an update manually.
*
* @param dirUrl the directory URL
*/
void updateDirectory(const QUrl &dirUrl);
/**
* Returns @c true if no I/O operation is currently in progress.
*
* @return @c true if finished, @c false otherwise
*/
bool isFinished() const;
/**
* Returns the file item of the URL.
*
* Can return an empty KFileItem.
* @return the file item for url() itself (".")
*/
KFileItem rootItem() const;
/**
* Find an item by its URL.
* @param url the item URL
* @return the KFileItem
*/
KFileItem findByUrl(const QUrl &url) const;
/**
* Find an item by its name.
* @param name the item name
* @return the KFileItem
*/
KFileItem findByName(const QString &name) const;
/**
* Set a name filter to only list items matching this name, e.g.\ "*.cpp".
*
* You can set more than one filter by separating them with whitespace, e.g
* "*.cpp *.h".
* Note: the directory is not automatically reloaded.
* You need to call emitChanges() afterwards.
*
* @param filter the new filter, QString() to disable filtering
* @see matchesFilter
*/
void setNameFilter(const QString &filter);
/**
* Returns the current name filter, as set via setNameFilter()
* @return the current name filter, can be QString() if filtering
* is turned off
*/
QString nameFilter() const;
/**
* Set MIME type based filter to only list items matching the given MIME types.
*
* NOTE: setting the filter does not automatically reload directory.
* Also calling this function will not affect any named filter already set.
*
* You need to call emitChanges() afterwards.
*
* @param mimeList a list of MIME types
*
* @see clearMimeFilter
* @see matchesMimeFilter
*/
void setMimeFilter(const QStringList &mimeList);
/**
* Filtering should be done with KFileFilter. This will be implemented in a later
* revision of KCoreDirLister. This method may be removed then.
*
* Set MIME type based exclude filter to only list items not matching the given MIME types
*
* NOTE: setting the filter does not automatically reload directory.
* Also calling this function will not affect any named filter already set.
*
* @param mimeList a list of MIME types
* @see clearMimeFilter
* @see matchesMimeFilter
*/
void setMimeExcludeFilter(const QStringList &mimeList);
/**
* Clears the MIME type based filter.
*
* You need to call emitChanges() afterwards.
*
* @see setMimeFilter
*/
void clearMimeFilter();
/**
* Returns the list of MIME type based filters, as set via setMimeFilter().
* @return the list of MIME type based filters. Empty, when no MIME type filter is set.
*/
QStringList mimeFilters() const;
/**
* Used by items() and itemsForDir() to specify whether you want
* all items for a directory or just the filtered ones.
*/
enum WhichItems {
AllItems = 0,
FilteredItems = 1,
};
/**
* Returns the items listed for the current url().
*
* This method will @em not start listing a directory, you should only call
* this in a slot connected to the finished() signal.
*
* The items in the KFileItemList are copies of the items used by KCoreDirLister.
*
* @param which specifies whether the returned list will contain all entries
* or only the ones that passed the nameFilter(), mimeFilter(),
* etc. Note that the latter causes iteration over all the
* items, filtering them. If this is too slow for you, use the
* newItems() signal, sending out filtered items in chunks
* @return the items listed for the current url()
*/
KFileItemList items(WhichItems which = FilteredItems) const;
/**
* Returns the items listed for the given @p dirUrl.
* This method will @em not start listing @p dirUrl, you should only call
* this in a slot connected to the finished() signal.
*
* The items in the KFileItemList are copies of the items used by KCoreDirLister.
*
* @param dirUrl specifies the url for which the items should be returned. This
* is only useful if you use KCoreDirLister with multiple URLs
* i.e. using bool OpenUrlFlag::Keep in openUrl()
* @param which specifies whether the returned list will contain all entries
* or only the ones that passed the nameFilter, mimeFilter, etc.
* Note that the latter causes iteration over all the items,
* filtering them. If this is too slow for you, use the
* newItems() signal, sending out filtered items in chunks
*
* @return the items listed for @p dirUrl
*/
KFileItemList itemsForDir(const QUrl &dirUrl, WhichItems which = FilteredItems) const;
/**
* Return the KFileItem for the given URL, if it was listed recently and it's
* still in the cache, which is always the case if a directory view is currently
* showing this item. If not, then it might be in the cache; if not in the cache a
* a null KFileItem will be returned.
*
* If you really need a KFileItem for this URL in all cases, then use KIO::stat() instead.
*
*/
static KFileItem cachedItemForUrl(const QUrl &url);
/**
* Checks whether auto error handling is enabled.
* If enabled, it will show an error dialog to the user when an
* error occurs (assuming the application links to KIOWidgets).
* It is turned on by default.
* @return @c true if auto error handling is enabled, @c false otherwise
* @see setAutoErrorHandlingEnabled()
* @since 5.82
*/
bool autoErrorHandlingEnabled() const;
/**
* Enable or disable auto error handling.
* If enabled, it will show an error dialog to the user when an
* error occurs. It is turned on by default.
* @param enable true to enable auto error handling, false to disable
* @param parent the parent widget for the error dialogs, can be @c nullptr for
* top-level
* @see autoErrorHandlingEnabled()
* @since 5.82
*/
void setAutoErrorHandlingEnabled(bool enable);
Q_SIGNALS:
/**
* Tell the view that this KCoreDirLister has started to list @p dirUrl. Note that this
* does @em not imply that there is really a job running! I.e. KCoreDirLister::jobs()
* may return an empty list, in which case the items are taken from the cache.
*
* The view knows that openUrl should start it, so this might seem useless, but the view
* also needs to know when an automatic update happens.
* @param dirUrl the URL to list
*/
void started(const QUrl &dirUrl);
/**
* Tell the view that listing is finished. There are no jobs running anymore.
*/
void completed();
/**
* Tell the view that the listing of the directory @p dirUrl is finished.
* There might be other running jobs left.
*
* @param dirUrl the directory URL
*
* @since 5.79
*/
void listingDirCompleted(const QUrl &dirUrl);
/**
* Tell the view that the user canceled the listing. No running jobs are left.
*/
void canceled();
/**
* Tell the view that the listing of the directory @p dirUrl was canceled.
* There might be other running jobs left.
*
* @param dirUrl the directory URL
*
* @since 5.79
*/
void listingDirCanceled(const QUrl &dirUrl);
/**
* Signals a redirection.
*
* @param oldUrl the original URL
* @param newUrl the new URL
*/
void redirection(const QUrl &oldUrl, const QUrl &newUrl);
/**
* Signals to the view to remove all items (when e.g.\ going from dirA to dirB).
* Make sure to connect to this signal to avoid having duplicate items in the view.
*/
void clear();
/**
* Signals to the view to clear all items from directory @p dirUrl.
*
* This is only emitted if the lister is holding more than one directory.
*
* @param dirUrl the directory that the view should clear all items from
*
* @since 5.79
*/
void clearDir(const QUrl &dirUrl);
/**
* Signal new items.
*
* @param items a list of new items
*/
void newItems(const KFileItemList &items);
/**
* Signal that new items were found during directory listing.
* Alternative signal emitted at the same time as newItems(),
* but itemsAdded also passes the url of the parent directory.
*
* @param items a list of new items
*/
void itemsAdded(const QUrl &directoryUrl, const KFileItemList &items);
/**
* Send a list of items filtered-out by MIME type.
* @param items the list of filtered items
*
*/
void itemsFilteredByMime(const KFileItemList &items);
/**
* Signal that items have been deleted
*
* @since 4.1.2
* @param items the list of deleted items
*/
void itemsDeleted(const KFileItemList &items);
/**
* Signal an item to refresh (its MIME-type/icon/name has changed).
* Note: KFileItem::refresh has already been called on those items.
* @param items the items to refresh. This is a list of pairs, where
* the first item in the pair is the OLD item, and the second item is the
* NEW item. This allows to track which item has changed, especially after
* a renaming.
*/
void refreshItems(const QList<QPair<KFileItem, KFileItem>> &items);
/**
* Emitted to display information about running jobs.
* Examples of message are "Resolving host", "Connecting to host...", etc.
* @param msg the info message
*/
void infoMessage(const QString &msg);
/**
* Progress signal showing the overall progress of the KCoreDirLister.
* This allows using a progress bar very easily. (see QProgressBar)
* @param percent the progress in percent
*/
void percent(int percent);
/**
* Emitted when we know the size of the jobs.
* @param size the total size in bytes
*/
void totalSize(KIO::filesize_t size);
/**
* Regularly emitted to show the progress of this KCoreDirLister.
* @param size the processed size in bytes
*/
void processedSize(KIO::filesize_t size);
/**
* Emitted to display information about the speed of the jobs.
* @param bytes_per_second the speed in bytes/s
*/
void speed(int bytes_per_second);
/**
* Emitted if listing a directory fails with an error.
* A typical implementation in a widgets-based application
* would show a message box by calling this in a slot connected to this signal:
* <tt>job->uiDelegate()->showErrorMessage()</tt>
* Many applications might prefer to embed the error message though
* (e.g. by using the KMessageWidget class, from the KWidgetsAddons Framework).
* @param the job with an error
* @since 5.82
*/
void jobError(KIO::Job *job);
protected:
/**
* Reimplemented by KDirLister to associate windows with jobs
* @since 5.0
*/
virtual void jobStarted(KIO::ListJob *);
private:
friend class KCoreDirListerPrivate;
std::unique_ptr<KCoreDirListerPrivate> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KCoreDirLister::OpenUrlFlags)
#endif
@@ -0,0 +1,605 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2002-2006 Michael Brade <brade@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOREDIRLISTER_P_H
#define KCOREDIRLISTER_P_H
#include "kfileitem.h"
#include "kmountpoint.h"
#ifdef WITH_QTDBUS
#include "kdirnotify.h"
#endif
#include <QCache>
#include <QCoreApplication>
#include <QFileInfo>
#include <QHash>
#include <QList>
#include <QMap>
#include <QTimer>
#include <QUrl>
#include <KDirWatch>
#include <kio/global.h>
#include <set>
class QRegularExpression;
class KCoreDirLister;
namespace KIO
{
class Job;
class ListJob;
}
class OrgKdeKDirNotifyInterface;
struct KCoreDirListerCacheDirectoryData;
class KCoreDirListerPrivate
{
public:
explicit KCoreDirListerPrivate(KCoreDirLister *qq);
void emitCachedItems(const QUrl &, bool, bool);
void slotInfoMessage(KJob *, const QString &);
void slotPercent(KJob *, unsigned long);
void slotTotalSize(KJob *, qulonglong);
void slotProcessedSize(KJob *, qulonglong);
void slotSpeed(KJob *, unsigned long);
/*
* Called by the public matchesMimeFilter() to do the
* actual filtering. Those methods may be reimplemented to customize
* filtering.
* @param mimeType the MIME type to filter
* @param filters the list of MIME types to filter
*/
bool doMimeFilter(const QString &mimeType, const QStringList &filters) const;
bool doMimeExcludeFilter(const QString &mimeExclude, const QStringList &filters) const;
void connectJob(KIO::ListJob *);
void jobDone(KIO::ListJob *);
uint numJobs();
void addNewItem(const QUrl &directoryUrl, const KFileItem &item);
void addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items);
void addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item);
void emitItems();
void emitItemsDeleted(const KFileItemList &items);
/*
* Called for every new item before emitting newItems().
* You may reimplement this method in a subclass to implement your own
* filtering.
* The default implementation filters out ".." and everything not matching
* the name filter(s)
* @return @c true if the item is "ok".
* @c false if the item shall not be shown in a view, e.g.
* files not matching a pattern *.cpp ( KFileItem::isHidden())
* @see matchesFilter
* @see setNameFilter
*/
bool matchesFilter(const KFileItem &) const;
/*
* Called for every new item before emitting newItems().
* You may reimplement this method in a subclass to implement your own
* filtering.
* The default implementation filters out everything not matching
* the mime filter(s)
* @return @c true if the item is "ok".
* @c false if the item shall not be shown in a view, e.g.
* files not matching the mime filter
* @see matchesMimeFilter
* @see setMimeFilter
*/
bool matchesMimeFilter(const KFileItem &) const;
/**
* Redirect this dirlister from oldUrl to newUrl.
* @param keepItems if true, keep the fileitems (e.g. when renaming an existing dir);
* if false, clear out everything (e.g. when redirecting during listing).
*/
void redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems);
/**
* Should this item be visible according to the current filter settings?
*/
bool isItemVisible(const KFileItem &item) const;
void prepareForSettingsChange()
{
if (!hasPendingChanges) {
hasPendingChanges = true;
oldSettings = settings;
}
}
void emitChanges();
class CachedItemsJob;
CachedItemsJob *cachedItemsJobForUrl(const QUrl &url) const;
KCoreDirLister *const q;
/**
* List of dirs handled by this dirlister. The first entry is the base URL.
* For a tree view, it contains all the dirs shown.
*/
QList<QUrl> lstDirs;
// toplevel URL
QUrl url;
bool complete = false;
bool autoUpdate = false;
bool delayedMimeTypes = false;
bool hasPendingChanges = false; // i.e. settings != oldSettings
bool m_autoErrorHandling = true;
bool requestMimeTypeWhileListing = false;
struct JobData {
long unsigned int percent, speed;
KIO::filesize_t processedSize, totalSize;
};
QMap<KIO::ListJob *, JobData> jobData;
// file item for the root itself (".")
KFileItem rootFileItem;
typedef QHash<QUrl, KFileItemList> NewItemsHash;
NewItemsHash lstNewItems;
QList<QPair<KFileItem, KFileItem>> lstRefreshItems;
KFileItemList lstMimeFilteredItems, lstRemoveItems;
QList<CachedItemsJob *> m_cachedItemsJobs;
QString nameFilter; // parsed into lstFilters
struct FilterSettings {
FilterSettings()
: isShowingDotFiles(false)
, dirOnlyMode(false)
{
}
bool isShowingDotFiles;
bool dirOnlyMode;
QList<QRegularExpression> lstFilters;
QStringList mimeFilter;
QStringList mimeExcludeFilter;
};
FilterSettings settings;
FilterSettings oldSettings;
friend class KCoreDirListerCache;
};
/**
* Design of the cache:
* There is a single KCoreDirListerCache for the whole process.
* It holds all the items used by the dir listers (itemsInUse)
* as well as a cache of the recently used items (itemsCached).
* Those items are grouped by directory (a DirItem represents a whole directory).
*
* KCoreDirListerCache also runs all the jobs for listing directories, whether they are for
* normal listing or for updates.
* For faster lookups, it also stores a hash table, which gives for a directory URL:
* - the dirlisters holding that URL (listersCurrentlyHolding)
* - the dirlisters currently listing that URL (listersCurrentlyListing)
*/
class KCoreDirListerCache : public QObject
{
Q_OBJECT
public:
KCoreDirListerCache(); // only called by QThreadStorage internally
~KCoreDirListerCache() override;
void updateDirectory(const QUrl &dir);
KFileItem itemForUrl(const QUrl &url) const;
QList<KFileItem> *itemsForDir(const QUrl &dir) const;
bool listDir(KCoreDirLister *lister, const QUrl &_url, bool _keep, bool _reload);
// stop all running jobs for lister
void stop(KCoreDirLister *lister, bool silent = false);
// stop just the job listing url for lister
void stopListingUrl(KCoreDirLister *lister, const QUrl &_url, bool silent = false);
void setAutoUpdate(KCoreDirLister *lister, bool enable);
void forgetDirs(KCoreDirLister *lister);
void forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify, const KMountPoint::List &possibleMountPoints);
KFileItem findByName(const KCoreDirLister *lister, const QString &_name) const;
// findByUrl returns a pointer so that it's possible to modify the item.
// See itemForUrl for the version that returns a readonly kfileitem.
// @param lister can be 0. If set, it is checked that the url is held by the lister
KFileItem findByUrl(const KCoreDirLister *lister, const QUrl &url) const;
// Called by CachedItemsJob:
// Emits the cached items, for this lister and this url
void emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted);
// Called by CachedItemsJob:
void forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &url);
public Q_SLOTS:
/**
* Notify that files have been added in @p directory
* The receiver will list that directory again to find
* the new items (since it needs more than just the names anyway).
* Connected to the DBus signal from the KDirNotify interface.
*/
void slotFilesAdded(const QString &urlDirectory);
/**
* Notify that files have been deleted.
* This call passes the exact urls of the deleted files
* so that any view showing them can simply remove them
* or be closed (if its current dir was deleted)
* Connected to the DBus signal from the KDirNotify interface.
*/
void slotFilesRemoved(const QStringList &fileList);
/**
* Notify that files have been changed.
* At the moment, this is only used for new icon, but it could be
* used for size etc. as well.
* Connected to the DBus signal from the KDirNotify interface.
*/
void slotFilesChanged(const QStringList &fileList);
void slotFileRenamed(const QString &srcUrl, const QString &dstUrl, const QString &dstPath);
private Q_SLOTS:
void slotFileDirty(const QString &_file);
void slotFileCreated(const QString &_file);
void slotFileDeleted(const QString &_file);
void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
void slotResult(KJob *j);
void slotRedirection(KIO::Job *job, const QUrl &url);
void slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
void slotUpdateResult(KJob *job);
void processPendingUpdates();
private:
void itemsAddedInDirectory(const QUrl &url);
class DirItem;
DirItem *dirItemForUrl(const QUrl &dir) const;
void stopListJob(const QUrl &url, bool silent);
KIO::ListJob *jobForUrl(const QUrl &url, KIO::ListJob *not_job = nullptr);
const QUrl &joburl(KIO::ListJob *job);
void killJob(KIO::ListJob *job);
// Called when something tells us that the directory @p url has changed.
// Returns true if @p url is held by some lister (meaning: do the update now)
// otherwise mark the cached item as not-up-to-date for later and return false
bool checkUpdate(const QUrl &url);
// Helper method for slotFileDirty
void handleFileDirty(const QUrl &url);
void handleDirDirty(const QUrl &url);
// when there were items deleted from the filesystem all the listers holding
// the parent directory need to be notified, the items have to be deleted
// and removed from the cache including all the children.
void deleteUnmarkedItems(const QList<KCoreDirLister *> &, QList<KFileItem> &lstItems, const QHash<QString, KFileItem> &itemsToDelete);
// Helper method called when we know that a list of items was deleted
void itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems);
void slotFilesRemoved(const QList<QUrl> &urls);
// common for slotRedirection and slotFileRenamed
void renameDir(const QUrl &oldUrl, const QUrl &url);
// common for deleteUnmarkedItems and slotFilesRemoved
void deleteDir(const QUrl &dirUrl);
// remove directory from cache (itemsCached), including all child dirs
void removeDirFromCache(const QUrl &dir);
// helper for renameDir
void emitRedirections(const QUrl &oldUrl, const QUrl &url);
/**
* Emits refreshItem() in the directories that cared for oldItem.
* The caller has to remember to call emitItems in the set of dirlisters returned
* (but this allows to buffer change notifications)
*/
std::set<KCoreDirLister *> emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem);
/**
* Remove the item from the sorted by url list matching @p oldUrl,
* that is in the wrong place (because its url has changed) and insert @p item in the right place.
* @param oldUrl the previous url of the @p item
* @param item the modified item to be inserted
*/
void reinsert(const KFileItem &item, const QUrl &oldUrl)
{
const QUrl parentDir = oldUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
DirItem *dirItem = dirItemForUrl(parentDir);
if (dirItem) {
auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), oldUrl);
if (it != dirItem->lstItems.end()) {
dirItem->lstItems.erase(it);
dirItem->insert(item);
}
}
}
void remove(const QUrl &oldUrl)
{
const QUrl parentDir = oldUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
DirItem *dirItem = dirItemForUrl(parentDir);
if (dirItem) {
auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), oldUrl);
if (it != dirItem->lstItems.end()) {
dirItem->lstItems.erase(it);
}
}
}
/**
* When KDirWatch tells us that something changed in "dir", we need to
* also notify the dirlisters that are listing a symlink to "dir" (#213799)
*/
QList<QUrl> directoriesForCanonicalPath(const QUrl &dir) const;
// Definition of the cache of ".hidden" files
struct CacheHiddenFile {
CacheHiddenFile(const QDateTime &mtime, std::set<QString> &&listedFilesParam)
: mtime(mtime)
, listedFiles(std::move(listedFilesParam))
{
}
QDateTime mtime;
std::set<QString> listedFiles;
};
/**
* Returns the names listed in dir's ".hidden" file, if it exists.
* If a file named ".hidden" exists in the @p dir directory, this method
* returns all the file names listed in that file. If it doesn't exist, an
* empty set is returned.
* @param dir path to the target directory.
* @return names listed in the directory's ".hidden" file (empty if it doesn't exist).
*/
CacheHiddenFile *cachedDotHiddenForDir(const QString &dir);
#ifndef NDEBUG
void printDebug();
#endif
class DirItem
{
public:
DirItem(const QUrl &dir, const QString &canonicalPath)
: url(dir)
, m_canonicalPath(canonicalPath)
{
autoUpdates = 0;
complete = false;
watchedWhileInCache = false;
}
~DirItem()
{
if (autoUpdates) {
if (KDirWatch::exists() && url.isLocalFile()) {
KDirWatch::self()->removeDir(m_canonicalPath);
}
// Since sendSignal goes through D-Bus, QCoreApplication has to be available
// which might not be the case anymore from a global static dtor like the
// lister cache
if (QCoreApplication::instance()) {
sendSignal(false, url);
}
}
lstItems.clear();
}
DirItem(const DirItem &) = delete;
DirItem &operator=(const DirItem &) = delete;
void sendSignal(bool entering, const QUrl &url)
{
// Note that "entering" means "start watching", and "leaving" means "stop watching"
// (i.e. it's not when the user leaves the directory, it's when the directory is removed from the cache)
#ifdef WITH_QTDBUS
if (entering) {
org::kde::KDirNotify::emitEnteredDirectory(url);
} else {
org::kde::KDirNotify::emitLeftDirectory(url);
}
#endif
}
void redirect(const QUrl &newUrl)
{
if (autoUpdates) {
if (url.isLocalFile()) {
KDirWatch::self()->removeDir(m_canonicalPath);
}
sendSignal(false, url);
if (newUrl.isLocalFile()) {
m_canonicalPath = QFileInfo(newUrl.toLocalFile()).canonicalFilePath();
KDirWatch::self()->addDir(m_canonicalPath);
}
sendSignal(true, newUrl);
}
url = newUrl;
if (!rootItem.isNull()) {
rootItem.setUrl(newUrl);
}
}
void incAutoUpdate()
{
if (autoUpdates++ == 0) {
if (url.isLocalFile()) {
KDirWatch::self()->addDir(m_canonicalPath);
}
sendSignal(true, url);
}
}
void decAutoUpdate()
{
if (--autoUpdates == 0) {
if (url.isLocalFile()) {
KDirWatch::self()->removeDir(m_canonicalPath);
}
sendSignal(false, url);
}
else if (autoUpdates < 0) {
autoUpdates = 0;
}
}
// Insert the item in the sorted list
void insert(const KFileItem &item)
{
auto it = std::lower_bound(lstItems.begin(), lstItems.end(), item.url());
lstItems.insert(it, item);
}
// Insert the already sorted items in the sorted list
void insertSortedItems(const KFileItemList &items)
{
if (items.isEmpty()) {
return;
}
lstItems.reserve(lstItems.size() + items.size());
auto it = lstItems.begin();
for (const auto &item : items) {
it = std::lower_bound(it, lstItems.end(), item.url());
it = lstItems.insert(it, item);
}
}
// number of KCoreDirListers using autoUpdate for this dir
short autoUpdates;
// this directory is up-to-date
bool complete;
// the directory is watched while being in the cache (useful for proper incAutoUpdate/decAutoUpdate count)
bool watchedWhileInCache;
// the complete url of this directory
QUrl url;
// the local path, with symlinks resolved, so that KDirWatch works
QString m_canonicalPath;
// KFileItem representing the root of this directory.
// Remember that this is optional. FTP sites don't return '.' in
// the list, so they give no root item
KFileItem rootItem;
// The fileitems contained in the directory. Empty when directory is not readable.
QList<KFileItem> lstItems;
};
QMap<KIO::ListJob *, KIO::UDSEntryList> runningListJobs;
// an item is a complete directory
QHash<QUrl, DirItem *> itemsInUse;
QCache<QUrl, DirItem> itemsCached;
// cache of ".hidden" files
QCache<QString /*dot hidden file*/, CacheHiddenFile> m_cacheHiddenFiles;
typedef QHash<QUrl, KCoreDirListerCacheDirectoryData> DirectoryDataHash;
DirectoryDataHash directoryData;
// Symlink-to-directories are registered here so that we can
// find the url that changed, when kdirwatch tells us about
// changes in the canonical url. (#213799)
QHash<QUrl /*canonical path*/, QList<QUrl> /*dirlister urls*/> canonicalUrls;
// Set of local files that we have changed recently (according to KDirWatch)
// We temporize the notifications by keeping them 500ms in this list.
std::set<QString /*path*/> pendingUpdates;
std::set<QString /*path*/> pendingDirectoryUpdates;
// The timer for doing the delayed updates
QTimer pendingUpdateTimer;
// Set of remote files that have changed recently -- but we can't emit those
// changes yet, we need to wait for the "update" directory listing.
// The cmp() call can't differ MIME types since they are determined on demand,
// this is why we need to remember those files here.
std::set<KFileItem> pendingRemoteUpdates;
#ifdef WITH_QTDBUS
// the KDirNotify signals
OrgKdeKDirNotifyInterface *kdirnotify;
#endif
struct ItemInUseChange;
};
// Data associated with a directory url
// This could be in DirItem but only in the itemsInUse dict...
struct KCoreDirListerCacheDirectoryData {
// A lister can be EITHER in listersCurrentlyListing OR listersCurrentlyHolding
// but NOT in both at the same time.
// But both lists can have different listers at the same time; this
// happens if more listers are requesting url at the same time and
// one lister was stopped during the listing of files.
// Listers that are currently listing this url
QList<KCoreDirLister *> listersCurrentlyListing;
// Listers that are currently holding this url
QList<KCoreDirLister *> listersCurrentlyHolding;
void moveListersWithoutCachedItemsJob(const QUrl &url);
};
// This job tells KCoreDirListerCache to emit cached items asynchronously from listDir()
// to give the KCoreDirLister user enough time for connecting to its signals, and so
// that KCoreDirListerCache behaves just like when a real KIO::Job is used: nothing
// is emitted during the openUrl call itself.
class KCoreDirListerPrivate::CachedItemsJob : public KJob
{
Q_OBJECT
public:
CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload);
/*reimp*/ void start() override
{
QMetaObject::invokeMethod(this, &KCoreDirListerPrivate::CachedItemsJob::done, Qt::QueuedConnection);
}
// For updateDirectory() to cancel m_emitCompleted;
void setEmitCompleted(bool b)
{
m_emitCompleted = b;
}
QUrl url() const
{
return m_url;
}
protected:
bool doKill() override;
public Q_SLOTS:
void done();
private:
KCoreDirLister *m_lister;
QUrl m_url;
bool m_reload;
bool m_emitCompleted;
};
#endif
@@ -0,0 +1,74 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Thiago Macieira <thiago@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kdirnotify.h"
#include <QUrl>
/*
* Implementation of interface class OrgKdeKDirNotifyInterface
*/
OrgKdeKDirNotifyInterface::OrgKdeKDirNotifyInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
: QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
{
}
OrgKdeKDirNotifyInterface::~OrgKdeKDirNotifyInterface()
{
}
static void emitSignal(const QString &signalName, const QVariantList &args)
{
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/"), QLatin1String(org::kde::KDirNotify::staticInterfaceName()), signalName);
message.setArguments(args);
QDBusConnection::sessionBus().send(message);
}
void OrgKdeKDirNotifyInterface::emitFileRenamed(const QUrl &src, const QUrl &dst)
{
emitSignal(QStringLiteral("FileRenamed"), QVariantList{src.toString(), dst.toString()});
emitSignal(QStringLiteral("FileRenamedWithLocalPath"), QVariantList{src.toString(), dst.toString(), QString()});
}
void OrgKdeKDirNotifyInterface::emitFileRenamedWithLocalPath(const QUrl &src, const QUrl &dst, const QString &dstPath)
{
emitSignal(QStringLiteral("FileRenamed"), QVariantList{src.toString(), dst.toString()});
emitSignal(QStringLiteral("FileRenamedWithLocalPath"), QVariantList{src.toString(), dst.toString(), dstPath});
}
void OrgKdeKDirNotifyInterface::emitFileMoved(const QUrl &src, const QUrl &dst)
{
emitSignal(QStringLiteral("FileMoved"), QVariantList{src.toString(), dst.toString()});
}
void OrgKdeKDirNotifyInterface::emitFilesAdded(const QUrl &directory)
{
emitSignal(QStringLiteral("FilesAdded"), QVariantList{QVariant(directory.toString())});
}
void OrgKdeKDirNotifyInterface::emitFilesChanged(const QList<QUrl> &fileList)
{
emitSignal(QStringLiteral("FilesChanged"), QVariantList{QVariant(QUrl::toStringList(fileList))});
}
void OrgKdeKDirNotifyInterface::emitFilesRemoved(const QList<QUrl> &fileList)
{
emitSignal(QStringLiteral("FilesRemoved"), QVariantList{QVariant(QUrl::toStringList(fileList))});
}
void OrgKdeKDirNotifyInterface::emitEnteredDirectory(const QUrl &url)
{
emitSignal(QStringLiteral("enteredDirectory"), QVariantList{QVariant(url.toString())});
}
void OrgKdeKDirNotifyInterface::emitLeftDirectory(const QUrl &url)
{
emitSignal(QStringLiteral("leftDirectory"), QVariantList{QVariant(url.toString())});
}
#include "moc_kdirnotify.cpp"
@@ -0,0 +1,125 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2006 Thiago Macieira <thiago@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KDIRNOTIFY_H
#define KDIRNOTIFY_H
#include "kiocore_export.h"
#include <QByteArray>
#ifdef QT_DBUS_LIB
#include <QDBusAbstractInterface>
#include <QList>
#include <QMap>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QVariant>
class QDBusConnection;
/**
* \class OrgKdeKDirNotifyInterface kdirnotify.h KDirNotify
*
* \brief Proxy class for interface org.kde.KDirNotify.
*
* KDirNotify can be used to inform KIO about changes in real or virtual file systems.
* Classes like KDirModel connect to the signals as in the following example to
* be able to keep caches up-to-date.
*
* \code
* kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
* connect(kdirnotify, &KDirNotify::FileRenamedWithLocalPath,
* this, [this](const QString &src, const QString &dst, const QString &dstPath) {
* slotFileRenamed(src, dst, dstPath);
* });
*
* connect(kdirnotify, &KDirNotify::FilesAdded,
* this, [this](const QString &directory) { slotFilesAdded(directory); });
*
* connect(kdirnotify, &KDirNotify::FilesChanged,
* this, [this](const QStringList &fileList) { slotFilesChanged(fileList); });
*
* connect(kdirnotify, &KDirNotify::FilesRemoved,
* this, [this](const QStringList &fileList) { slotFilesRemoved(fileList); });
* \endcode
*
* Especially noteworthy are the empty strings for both \p service and \p path. That
* way the client will connect to signals emitted by any application.
*
* The second usage is to actually emit the signals. For that emitFileRenamed() and friends are
* to be used.
*/
class KIOCORE_EXPORT OrgKdeKDirNotifyInterface : public QDBusAbstractInterface
{
Q_OBJECT
public:
static inline const char *staticInterfaceName()
{
return "org.kde.KDirNotify";
}
public:
/**
* Create a new KDirNotify interface.
*
* \param service The service whose signals one wants to listed to. Use an empty
* string to connect to all services/applications.
* \param path The path to the D-Bus object whose signals one wants to listed to.
* Use an empty string to connect to signals from all objects.
* \param connection Typically QDBusConnection::sessionBus().
* \param parent The parent QObject.
*/
OrgKdeKDirNotifyInterface(const QString &service,
const QString &path,
const QDBusConnection &connection = QDBusConnection::sessionBus(),
QObject *parent = nullptr);
/**
* Destructor.
*/
~OrgKdeKDirNotifyInterface() override;
public Q_SLOTS: // METHODS
Q_SIGNALS: // SIGNALS
void FileRenamed(const QString &src, const QString &dst);
void FileRenamedWithLocalPath(const QString &src, const QString &dst, const QString &dstPath);
void FileMoved(const QString &src, const QString &dst);
void FilesAdded(const QString &directory);
void FilesChanged(const QStringList &fileList);
void FilesRemoved(const QStringList &fileList);
void enteredDirectory(const QString &url);
void leftDirectory(const QString &url);
public:
static void emitFileRenamed(const QUrl &src, const QUrl &dst);
/**
* \param src The old URL of the file that has been renamed.
* \param dst The new URL of the file after it was renamed.
* \param dstPath The local path of the file after it was renamed. This may be empty
* and should otherwise be used to update UDS_LOCAL_PATH.
* @since 5.20
*/
static void emitFileRenamedWithLocalPath(const QUrl &src, const QUrl &dst, const QString &dstPath);
static void emitFileMoved(const QUrl &src, const QUrl &dst);
static void emitFilesAdded(const QUrl &directory);
static void emitFilesChanged(const QList<QUrl> &fileList);
static void emitFilesRemoved(const QList<QUrl> &fileList);
static void emitEnteredDirectory(const QUrl &url);
static void emitLeftDirectory(const QUrl &url);
};
namespace org
{
namespace kde
{
typedef ::OrgKdeKDirNotifyInterface KDirNotify;
}
}
#endif
#endif
@@ -0,0 +1,224 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfilefilter.h"
#include <QDebug>
#include <QMetaType>
#include <QMimeDatabase>
#include <algorithm>
#include <qchar.h>
#include "kiocoredebug.h"
class KFileFilterPrivate : public QSharedData
{
public:
KFileFilterPrivate()
{
}
KFileFilterPrivate(const KFileFilterPrivate &other)
: QSharedData(other)
, m_label(other.m_label)
, m_filePatterns(other.m_filePatterns)
, m_mimePatterns(other.m_mimePatterns)
{
}
QString m_label;
QStringList m_filePatterns;
QStringList m_mimePatterns;
bool m_isValid = true;
};
QList<KFileFilter> KFileFilter::fromFilterString(const QString &filterString)
{
int pos = filterString.indexOf(QLatin1Char('/'));
// Check for an un-escaped '/', if found
// interpret as a MIME filter.
if (pos > 0 && filterString[pos - 1] != QLatin1Char('\\')) {
const QStringList filters = filterString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
QList<KFileFilter> result;
result.reserve(filters.size());
std::transform(filters.begin(), filters.end(), std::back_inserter(result), [](const QString &mimeType) {
return KFileFilter::fromMimeType(mimeType);
});
return result;
}
// Strip the escape characters from
// escaped '/' characters.
QString escapeRemoved(filterString);
for (pos = 0; (pos = escapeRemoved.indexOf(QLatin1String("\\/"), pos)) != -1; ++pos) {
escapeRemoved.remove(pos, 1);
}
const QStringList filters = escapeRemoved.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
QList<KFileFilter> result;
for (const QString &filter : filters) {
int separatorPos = filter.indexOf(QLatin1Char('|'));
QString label;
QStringList patterns;
if (separatorPos != -1) {
label = filter.mid(separatorPos + 1);
patterns = filter.left(separatorPos).split(QLatin1Char(' '));
} else {
patterns = filter.split(QLatin1Char(' '));
label = patterns.join(QLatin1Char(' '));
}
result << KFileFilter(label, patterns, {});
}
return result;
}
KFileFilter::KFileFilter()
: d(new KFileFilterPrivate)
{
}
KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
: d(new KFileFilterPrivate)
{
d->m_filePatterns = filePatterns;
d->m_mimePatterns = mimePatterns;
d->m_label = label;
}
KFileFilter::~KFileFilter() = default;
KFileFilter::KFileFilter(const KFileFilter &other)
: d(other.d)
{
}
KFileFilter &KFileFilter::operator=(const KFileFilter &other)
{
if (this != &other) {
d = other.d;
}
return *this;
}
QString KFileFilter::label() const
{
return d->m_label;
}
QStringList KFileFilter::filePatterns() const
{
return d->m_filePatterns;
}
QStringList KFileFilter::mimePatterns() const
{
return d->m_mimePatterns;
}
bool KFileFilter::operator==(const KFileFilter &other) const
{
return d->m_filePatterns == other.d->m_filePatterns && d->m_mimePatterns == other.d->m_mimePatterns;
}
bool KFileFilter::isEmpty() const
{
return d->m_filePatterns.isEmpty() && d->m_mimePatterns.isEmpty();
}
bool KFileFilter::isValid() const
{
return d->m_isValid;
}
QString KFileFilter::toFilterString() const
{
if (!d->m_filePatterns.isEmpty() && !d->m_mimePatterns.isEmpty()) {
qCWarning(KIO_CORE) << "KFileFilters with both mime and file patterns cannot be converted to filter strings";
return QString();
}
if (!d->m_mimePatterns.isEmpty()) {
return d->m_mimePatterns.join(QLatin1Char(' '));
}
if (!d->m_label.isEmpty()) {
const QString patterns = d->m_filePatterns.join(QLatin1Char(' '));
const QString escapedLabel = QString(d->m_label).replace(QLatin1String("/"), QLatin1String("\\/"));
if (patterns != d->m_label) {
return patterns + QLatin1Char('|') + escapedLabel;
} else {
return patterns;
}
} else {
return d->m_filePatterns.join(QLatin1Char(' '));
}
}
KFileFilter KFileFilter::fromMimeType(const QString &mimeType)
{
if (mimeType.isEmpty()) {
qCWarning(KIO_CORE) << "KFileFilter::fromMimeType() called with empty input";
KFileFilter filter;
filter.d->m_isValid = false;
return filter;
}
static QMimeDatabase db;
const QMimeType type = db.mimeTypeForName(mimeType);
if (type.isValid() || mimeType.endsWith(QLatin1String("/*"))) {
KFileFilter filter(type.comment(), {}, {mimeType});
return filter;
} else {
qCWarning(KIO_CORE) << "KFileFilter::fromMimeType() called with unknown MIME type" << mimeType;
KFileFilter filter;
filter.d->m_isValid = false;
return filter;
}
}
QList<KFileFilter> KFileFilter::fromMimeTypes(const QStringList &mimeTypes)
{
QList<KFileFilter> ret;
ret.reserve(mimeTypes.size());
for (const QString &type : mimeTypes) {
ret << KFileFilter::fromMimeType(type);
}
return ret;
}
QDebug operator<<(QDebug dbg, const KFileFilter &filter)
{
dbg.nospace() << "KFileFilter(";
dbg.nospace() << "MIME patterns: " << filter.mimePatterns();
dbg.nospace() << " ";
dbg.nospace() << "File patterns: " << filter.filePatterns();
dbg.nospace() << " ";
dbg.nospace() << "label: " << filter.label();
dbg.nospace() << ")";
return dbg;
}
Q_DECLARE_METATYPE(KFileFilter);
@@ -0,0 +1,127 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEFILTER_H
#define KFILEFILTER_H
#include "kiocore_export.h"
#include <QSharedDataPointer>
#include <QString>
#include <QStringList>
class KFileFilterPrivate;
/**
* @class KFileFilter kfilefilter.h <KFileFilter>
*
* Encapsulates rules to filter a list of files.
* Files can be filtered based on name patterns (e.g. *.cpp), MIME types, or both.
* Filters also optionally have a user-facing label.
*
* @since 5.101
*/
class KIOCORE_EXPORT KFileFilter
{
public:
/**
* Creates an empty filter.
*/
explicit KFileFilter();
/**
* Creates a filter with a given label, name patterns, and MIME types.
*
* @param label The user-facing label for this filter.
* @param filePatterns A list of file name patterns that should be included, e.g. ("*.cpp", "*.cxx").
* @param mimePatterns A list of MIME types that should be included, e.g. ("text/plain", "image/png").
*
*/
explicit KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns);
KFileFilter(const KFileFilter &other);
KFileFilter &operator=(const KFileFilter &other);
~KFileFilter();
/**
* Checks whether two filters are equal.
*
* Filters are considered equal if their file and name patters match.
* The label is ignored here.
*/
bool operator==(const KFileFilter &other) const;
/**
* The user-facing label for this filter.
*
* If no label is passed on creation one is created based on the patterns.
*/
QString label() const;
/**
* List of file name patterns that are included by this filter.
*/
QStringList filePatterns() const;
/**
* List of MIME types that are included by this filter;
*/
QStringList mimePatterns() const;
/**
* Converts this filter to a string representation
*/
QString toFilterString() const;
/**
* Whether the filer is empty, i.e.\ matches all files.
*/
bool isEmpty() const;
/**
* Whether the filter is valid.
*
* Creating a filter from an invalid/unkown MIME type will result in an invalid filter.
*
* @since 6.0
*/
bool isValid() const;
/*
* Creates a filter for one MIME type.
* The user-facing label is automatically determined from the MIME type.
*/
static KFileFilter fromMimeType(const QString &mimeType);
/**
* Creates filters from a list of MIME types.
* The user-facing label is automatically determined from the MIME type.
*
* @since 6.0
*/
static QList<KFileFilter> fromMimeTypes(const QStringList &mimeTypes);
private:
/**
* Convert a filter string understood by KFileWidget to a list of KFileFilters.
*/
static QList<KFileFilter> fromFilterString(const QString &filterString);
friend class KFileFilterCombo;
friend class KFileFilterTest;
friend class KFileFilterComboPrivate;
friend class KFileWidgetTest;
friend class KFileFilterComboTest;
friend class KDEPlatformFileDialog;
friend class KDEPlatformFileDialogHelper;
friend class KEncodingFileDialog;
QSharedDataPointer<KFileFilterPrivate> d;
};
KIOCORE_EXPORT QDebug operator<<(QDebug dbg, const KFileFilter &filter);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,677 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEITEM_H
#define KFILEITEM_H
#include "kiocore_export.h"
#include <QDateTime>
#include <QFile>
#include <QUrl>
#include <kacl.h>
#include <kio/global.h>
#include <kio/udsentry.h>
#include <QList>
#include <QMimeType>
#include <qplatformdefs.h>
class KFileItemPrivate;
/**
* @class KFileItem kfileitem.h <KFileItem>
*
* A KFileItem is a generic class to handle a file, local or remote.
* In particular, it makes it easier to handle the result of KIO::listDir
* (UDSEntry isn't very friendly to use).
* It includes many file attributes such as MIME type, icon, text, mode, link...
*
* KFileItem is implicitly shared, i.e. it can be used as a value and copied around at almost no cost.
*/
class KIOCORE_EXPORT KFileItem
{
Q_GADGET
Q_PROPERTY(QUrl url READ url WRITE setUrl)
Q_PROPERTY(QString user READ user)
Q_PROPERTY(QString group READ group)
Q_PROPERTY(bool isLink READ isLink)
Q_PROPERTY(bool isDir READ isDir)
Q_PROPERTY(bool isFile READ isFile)
Q_PROPERTY(bool isReadable READ isReadable)
Q_PROPERTY(bool isWritable READ isWritable)
Q_PROPERTY(bool isHidden READ isHidden)
Q_PROPERTY(bool isSlow READ isSlow)
Q_PROPERTY(bool isDesktopFile READ isDesktopFile)
Q_PROPERTY(QString linkDest READ linkDest)
Q_PROPERTY(QUrl targetUrl READ targetUrl)
Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath)
Q_PROPERTY(bool isLocalFile READ isLocalFile)
Q_PROPERTY(QString text READ text)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString mimetype READ mimetype)
Q_PROPERTY(QMimeType determineMimeType READ determineMimeType)
Q_PROPERTY(QMimeType currentMimeType READ currentMimeType)
Q_PROPERTY(bool isFinalIconKnown READ isFinalIconKnown)
Q_PROPERTY(bool isMimeTypeKnown READ isMimeTypeKnown)
Q_PROPERTY(QString mimeComment READ mimeComment)
Q_PROPERTY(QString iconName READ iconName)
Q_PROPERTY(QStringList overlays READ overlays)
Q_PROPERTY(QString comment READ comment)
Q_PROPERTY(QString getStatusBarInfo READ getStatusBarInfo)
Q_PROPERTY(bool isRegularFile READ isRegularFile)
public:
enum {
Unknown = static_cast<mode_t>(-1)
};
/**
* The timestamps associated with a file.
* - ModificationTime: the time the file's contents were last modified
* - AccessTime: the time the file was last accessed (last read or written to)
* - CreationTime: the time the file was created
*/
enum FileTimes {
// warning: don't change without looking at the Private class
ModificationTime = 0,
AccessTime = 1,
CreationTime = 2,
// ChangeTime
};
Q_ENUM(FileTimes)
enum MimeTypeDetermination {
NormalMimeTypeDetermination = 0,
SkipMimeTypeFromContent,
};
Q_ENUM(MimeTypeDetermination)
/**
* Null KFileItem. Doesn't represent any file, only exists for convenience.
*/
KFileItem();
/**
* Creates an item representing a file, from a UDSEntry.
* This is the preferred constructor when using KIO::listDir().
*
* @param entry the KIO entry used to get the file, contains info about it
* @param itemOrDirUrl the URL of the item or of the directory containing this item (see urlIsDirectory).
* @param delayedMimeTypes specifies if the MIME type of the given
* URL should be determined immediately or on demand.
* See the bool delayedMimeTypes in the KDirLister constructor.
* @param urlIsDirectory specifies if the url is just the directory of the
* fileitem and the filename from the UDSEntry should be used.
*
* When creating KFileItems out of the UDSEntry emitted by a KIO list job,
* use KFileItem(entry, listjob->url(), delayedMimeTypes, true);
*/
KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes = false, bool urlIsDirectory = false);
/**
* Creates an item representing a file, for which the MIME type is already known.
* @param url the file url
* @param mimeType the name of the file's MIME type
* @param mode the mode (S_IFDIR...)
*/
explicit KFileItem(const QUrl &url, const QString &mimeType = QString(), mode_t mode = KFileItem::Unknown);
/**
* Creates an item representing a file, with the option of skipping MIME type determination.
* @param url the file url
* @param mimeTypeDetermination the mode of determining the MIME type:
* NormalMimeTypeDetermination by content if local file, i.e. access the file,
* open and read part of it;
* by QMimeDatabase::MatchMode::MatchExtension if not local.
* SkipMimeTypeFromContent always by QMimeDatabase::MatchMode::MatchExtension,
* i.e. won't access the file by stat() or opening it;
* only suitable for files, directories won't be recognized.
* @since 5.57
*/
KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination);
/**
* Copy constructor
*/
KFileItem(const KFileItem &);
/**
* Destructor
*/
~KFileItem();
/**
* Move constructor
* @since 5.43
*/
KFileItem(KFileItem &&);
/**
* Copy assignment
*/
KFileItem &operator=(const KFileItem &);
/**
* Move assignment
* @since 5.43
*/
KFileItem &operator=(KFileItem &&);
/**
* Throw away and re-read (for local files) all information about the file.
* This is called when the _file_ changes.
*/
void refresh();
/**
* Re-reads MIME type information.
* This is called when the MIME type database changes.
*/
void refreshMimeType();
/**
* Sets MIME type determination to be immediate or on demand.
* Call this after the constructor, and before using any MIME-type-related method.
* @since 5.0
*/
void setDelayedMimeTypes(bool b);
/**
* Returns the url of the file.
* @return the url of the file
*/
QUrl url() const;
/**
* Sets the item's URL. Do not call unless you know what you are doing!
* (used for example when an item got renamed).
* @param url the item's URL
*/
void setUrl(const QUrl &url);
/**
* Sets the item's local path (UDS_LOCAL_PATH). Do not call unless you know what you are doing!
* This won't change the item's name or URL.
* (used for example when an item got renamed).
* @param path the item's local path
* @since 5.20
*/
void setLocalPath(const QString &path);
/**
* Sets the item's name (i.e.\ the filename).
* This is automatically done by setUrl, to set the name from the URL's fileName().
* This method is provided for some special cases like relative paths as names (KFindPart)
* @param name the item's name
*/
void setName(const QString &name);
/**
* Returns the permissions of the file (stat.st_mode containing only permissions).
* @return the permissions of the file
*/
mode_t permissions() const;
/**
* Returns the access permissions for the file as a string.
* @return the access permission as string
*/
QString permissionsString() const;
/**
* Tells if the file has extended access level information ( Posix ACL )
* @return true if the file has extend ACL information or false if it hasn't
*/
bool hasExtendedACL() const;
/**
* Returns the access control list for the file.
* @return the access control list as a KACL
*/
KACL ACL() const;
/**
* Returns the default access control list for the directory.
* @return the default access control list as a KACL
*/
KACL defaultACL() const;
/**
* Returns the file type (stat.st_mode containing only S_IFDIR, S_IFLNK, ...).
* @return the file type
*/
mode_t mode() const;
/**
* Returns the file's owner's user id.
* Available only on supported protocols.
* @since 6.0
*/
int userId() const;
/**
* Returns the file's owner's group id.
* Available only on supported protocols.
* @since 6.0
*/
int groupId() const;
/**
* Returns the owner of the file.
* @return the file's owner
*/
QString user() const;
/**
* Returns the group of the file.
* @return the file's group
*/
QString group() const;
/**
* Returns true if this item represents a link in the UNIX sense of
* a link.
* @return true if the file is a link
*/
bool isLink() const;
/**
* Returns true if this item represents a directory.
* @return true if the item is a directory
*/
bool isDir() const;
/**
* Returns true if this item represents a file (and not a directory)
* @return true if the item is a file
*/
bool isFile() const;
/**
* Checks whether the file or directory is readable. In some cases
* (remote files), we may return true even though it can't be read.
* @return true if the file can be read - more precisely,
* false if we know for sure it can't
*/
bool isReadable() const;
/**
* Checks whether the file or directory is writable. In some cases
* (remote files), we may return true even though it can't be written to.
* @return true if the file or directory can be written to - more precisely,
* false if we know for sure it can't
*/
bool isWritable() const;
/**
* Checks whether the file is hidden.
* @return true if the file is hidden.
*/
bool isHidden() const;
/**
* @return true if the file is a remote URL, or a local file on a network mount.
* It will return false only for really-local file systems.
* @since 4.7.4
*/
bool isSlow() const;
/**
* Checks whether the file is a readable local .desktop file,
* i.e.\ a file whose path can be given to KDesktopFile
* @return true if the file is a desktop file.
*/
bool isDesktopFile() const;
/**
* Returns the link destination if isLink() == true.
* @return the link destination. QString() if the item is not a link
*/
QString linkDest() const;
/**
* Returns the target url of the file, which is the same as url()
* in cases where the worker doesn't specify UDS_TARGET_URL
* @return the target url.
*/
QUrl targetUrl() const;
/**
* Returns the local path if isLocalFile() == true or the KIO item has
* a UDS_LOCAL_PATH atom.
*
* Treat it as a readonly path to open/list contents, use original url to move/delete files.
*
* @return the item local path, or QString() if not known
*/
QString localPath() const;
/**
* Returns the size of the file, if known.
* @return the file size, or 0 if not known
*/
KIO::filesize_t size() const;
/**
* @brief For folders, its recursive size:
* the size of its files plus the recursiveSize of its folder
*
* Initially only implemented for trash:/
*
* @since 5.70
* @return The recursive size
*/
KIO::filesize_t recursiveSize() const;
/**
* Requests the modification, access or creation time, depending on @p which.
* @param which the timestamp
* @return the time asked for, QDateTime() if not available
* @see timeString()
*/
Q_INVOKABLE QDateTime time(KFileItem::FileTimes which) const;
/**
* Requests the modification, access or creation time as a string, depending
* on @p which.
* @param which the timestamp
* @returns a formatted string of the requested time.
* @see time
*/
Q_INVOKABLE QString timeString(KFileItem::FileTimes which = ModificationTime) const;
/**
* Returns true if the file is a local file.
* @return true if the file is local, false otherwise
*/
bool isLocalFile() const;
/**
* Returns the text of the file item.
* It's not exactly the filename since some decoding happens ('%2F'->'/').
* @return the text of the file item
*/
QString text() const;
/**
* Return the name of the file item (without a path).
* Similar to text(), but unencoded, i.e. the original name.
* @param lowerCase if true, the name will be returned in lower case,
* which is useful to speed up sorting by name, case insensitively.
* @return the file's name
*/
QString name(bool lowerCase = false) const;
/**
* Returns the MIME type of the file item.
* If @p delayedMimeTypes was used in the constructor, this will determine
* the MIME type first. Equivalent to determineMimeType()->name()
* @return the MIME type of the file
*/
QString mimetype() const;
/**
* Returns the MIME type of the file item.
* If delayedMimeTypes was used in the constructor, this will determine
* the MIME type first.
* @return the MIME type
*/
QMimeType determineMimeType() const;
/**
* Returns the currently known MIME type of the file item.
* This will not try to determine the MIME type if unknown.
* @return the known MIME type
*/
QMimeType currentMimeType() const;
/**
* @return true if we have determined the final icon of this file already.
* @since 4.10.2
*/
bool isFinalIconKnown() const;
/**
* @return true if we have determined the MIME type of this file already,
* i.e. if determineMimeType() will be fast. Otherwise it will have to
* find what the MIME type is, which is a possibly slow operation; usually
* this is delayed until necessary.
*/
bool isMimeTypeKnown() const;
/**
* Returns the user-readable string representing the type of this file,
* like "OpenDocument Text File".
* @return the type of this KFileItem
*/
QString mimeComment() const;
/**
* Returns the full path name to the icon that represents
* this MIME type.
* @return iconName the name of the file's icon
*/
QString iconName() const;
/**
* Returns the overlays (bitfield of KIconLoader::*Overlay flags) that are used
* for this item's pixmap. Overlays are used to show for example, whether
* a file can be modified.
* @return the overlays of the pixmap
*/
QStringList overlays() const;
/**
* A comment which can contain anything - even rich text. It will
* simply be displayed to the user as is.
*
*/
QString comment() const;
/**
* Returns the string to be displayed in the statusbar,
* e.g.\ when the mouse is over this item.
* @return the status bar information
*/
QString getStatusBarInfo() const;
/**
* Returns the UDS entry. Used by the tree view to access all details
* by position.
* @return the UDS entry
*/
KIO::UDSEntry entry() const;
/**
* Return true if this item is a regular file,
* false otherwise (directory, link, character/block device, fifo, socket)
*/
bool isRegularFile() const;
/**
* Returns the file extension
* Similar to QFileInfo::suffix except it takes into account UDS_DISPLAY_NAME and saves a stat call
* @since 6.0
*/
QString suffix() const;
/**
* Somewhat like a comparison operator, but more explicit,
* and it can detect that two fileitems differ if any property of the file item
* has changed (file size, modification date, etc.). Two items are equal if
* all properties are equal. In contrast, operator== only compares URLs.
* @param item the item to compare
* @return true if all values are equal
*/
bool cmp(const KFileItem &item) const;
/**
* Returns true if both items share the same URL.
*/
bool operator==(const KFileItem &other) const;
/**
* Returns true if both items do not share the same URL.
*/
bool operator!=(const KFileItem &other) const;
/**
* Returns true if this item's URL is lexically less than other's URL; otherwise returns false
* @since 5.48
*/
bool operator<(const KFileItem &other) const;
/**
* Returns true if this item's URL is lexically less than url other; otherwise returns false
* @since 5.48
*/
bool operator<(const QUrl &other) const;
/**
* Converts this KFileItem to a QVariant, this allows to use KFileItem
* in QVariant() constructor
*/
operator QVariant() const;
/**
* Tries to return a local URL for this file item if possible.
* If @p local is not null, it will be set to @c true if the returned url is local,
* @c false otherwise.
*
* Example:
* @code
* bool isLocal = false;
* KFileItem item;
* const QUrl url = item.mostLocalUrl(&isLocal);
* if (isLocal) {
* // Use url
* }
* @endcode
*
*/
QUrl mostLocalUrl(bool *local = nullptr) const;
struct MostLocalUrlResult {
QUrl url;
bool local;
};
/**
* Returns a MostLocalUrlResult, with the local Url for this item if possible
* (otherwise an empty Url), and a bool that is set to @c true if this Url
* does represent a local file otherwise @c false.
*
* Basically this is an alternative to mostLocalUrl(bool*), that does not use an
* output parameter.
*
* Example:
* @code
* KFileItem item;
* const MostLocalUrlResult result = item.isMostLocalUrl();
* if (result.local) { // A local file
* // Use result.url
* }
* @endcode
* @since 5.84
*/
MostLocalUrlResult isMostLocalUrl() const;
/**
* Return true if default-constructed
*/
bool isNull() const;
/**
* returns whether the KFileItem exists on-disk
* Call only after initialization (i.e `KIO::stat` or `refresh()` for local files)
* @since 6.0
*/
bool exists() const;
/**
* Return true if the file has executable permission
* @since 6.0
*/
bool isExecutable() const;
private:
QSharedDataPointer<KFileItemPrivate> d;
/**
* Hides the file.
*/
KIOCORE_NO_EXPORT void setHidden();
private:
KIOCORE_EXPORT friend QDataStream &operator<<(QDataStream &s, const KFileItem &a);
KIOCORE_EXPORT friend QDataStream &operator>>(QDataStream &s, KFileItem &a);
friend class KFileItemTest;
friend class KCoreDirListerCache;
};
Q_DECLARE_METATYPE(KFileItem)
Q_DECLARE_TYPEINFO(KFileItem, Q_RELOCATABLE_TYPE);
inline size_t qHash(const KFileItem &item, size_t seed = 0)
{
return qHash(item.url(), seed);
}
/**
* @class KFileItemList kfileitem.h <KFileItem>
*
* List of KFileItems, which adds a few helper
* methods to QList<KFileItem>.
*/
class KIOCORE_EXPORT KFileItemList : public QList<KFileItem>
{
public:
/// Creates an empty list of file items.
KFileItemList();
/// Creates a new KFileItemList from a QList of file @p items.
KFileItemList(const QList<KFileItem> &items);
/// Creates a new KFileItemList from an initializer_list of file @p items.
/// @since 5.76
KFileItemList(std::initializer_list<KFileItem> items);
/**
* Find a KFileItem by name and return it.
* @return the item with the given name, or a null-item if none was found
* (see KFileItem::isNull())
*/
KFileItem findByName(const QString &fileName) const;
/**
* Find a KFileItem by URL and return it.
* @return the item with the given URL, or a null-item if none was found
* (see KFileItem::isNull())
*/
KFileItem findByUrl(const QUrl &url) const;
/// @return the list of URLs that those items represent
QList<QUrl> urlList() const;
/// @return the list of target URLs that those items represent
/// @since 4.2
QList<QUrl> targetUrlList() const;
// TODO KDE-5 add d pointer here so that we can merge KFileItemListProperties into KFileItemList
};
KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const KFileItem &a);
KIOCORE_EXPORT QDataStream &operator>>(QDataStream &s, KFileItem &a);
/**
* Support for qDebug() << aFileItem
* \since 4.4
*/
KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KFileItem &item);
#endif
@@ -0,0 +1,208 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2008 George Goldberg <grundleborg@googlemail.com>
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kfileitemlistproperties.h"
#include <kfileitem.h>
#include <kprotocolmanager.h>
#include <QFileInfo>
class KFileItemListPropertiesPrivate : public QSharedData
{
public:
KFileItemListPropertiesPrivate()
: m_isDirectory(false)
, m_isFile(false)
, m_supportsReading(false)
, m_supportsDeleting(false)
, m_supportsWriting(false)
, m_supportsMoving(false)
, m_isLocal(true)
{
}
void setItems(const KFileItemList &items);
void determineMimeTypeAndGroup() const;
KFileItemList m_items;
mutable QString m_mimeType;
mutable QString m_mimeGroup;
bool m_isDirectory : 1;
bool m_isFile : 1;
bool m_supportsReading : 1;
bool m_supportsDeleting : 1;
bool m_supportsWriting : 1;
bool m_supportsMoving : 1;
bool m_isLocal : 1;
};
KFileItemListProperties::KFileItemListProperties()
: d(new KFileItemListPropertiesPrivate)
{
}
KFileItemListProperties::KFileItemListProperties(const KFileItemList &items)
: d(new KFileItemListPropertiesPrivate)
{
setItems(items);
}
void KFileItemListProperties::setItems(const KFileItemList &items)
{
d->setItems(items);
}
void KFileItemListPropertiesPrivate::setItems(const KFileItemList &items)
{
const bool initialValue = !items.isEmpty();
m_items = items;
m_supportsReading = initialValue;
m_supportsDeleting = initialValue;
m_supportsWriting = initialValue;
m_supportsMoving = initialValue;
m_isDirectory = initialValue;
m_isFile = initialValue;
m_isLocal = true;
m_mimeType.clear();
m_mimeGroup.clear();
QFileInfo parentDirInfo;
for (const KFileItem &item : items) {
const QUrl url = item.url();
const auto [localUrl, isLocal] = item.isMostLocalUrl();
m_isLocal = m_isLocal && isLocal;
m_supportsReading = m_supportsReading && KProtocolManager::supportsReading(url);
m_supportsDeleting = m_supportsDeleting && KProtocolManager::supportsDeleting(url);
m_supportsWriting = m_supportsWriting && KProtocolManager::supportsWriting(url) && item.isWritable();
m_supportsMoving = m_supportsMoving && KProtocolManager::supportsMoving(url);
// For local files we can do better: check if we have write permission in parent directory
// TODO: if we knew about the parent KFileItem, we could even do that for remote protocols too
#ifndef Q_OS_WIN
if (m_isLocal && (m_supportsDeleting || m_supportsMoving)) {
const QString directory = localUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile();
if (parentDirInfo.filePath() != directory) {
parentDirInfo.setFile(directory);
}
if (!parentDirInfo.isWritable()) {
m_supportsDeleting = false;
m_supportsMoving = false;
}
}
#else
if (m_isLocal && m_supportsDeleting) {
if (!QFileInfo(url.toLocalFile()).isWritable())
m_supportsDeleting = false;
}
#endif
if (m_isDirectory && !item.isDir()) {
m_isDirectory = false;
}
if (m_isFile && !item.isFile()) {
m_isFile = false;
}
}
}
KFileItemListProperties::KFileItemListProperties(const KFileItemListProperties &other)
: d(other.d)
{
}
KFileItemListProperties &KFileItemListProperties::operator=(const KFileItemListProperties &other)
{
d = other.d;
return *this;
}
KFileItemListProperties::~KFileItemListProperties()
{
}
bool KFileItemListProperties::supportsReading() const
{
return d->m_supportsReading;
}
bool KFileItemListProperties::supportsDeleting() const
{
return d->m_supportsDeleting;
}
bool KFileItemListProperties::supportsWriting() const
{
return d->m_supportsWriting;
}
bool KFileItemListProperties::supportsMoving() const
{
return d->m_supportsMoving && d->m_supportsDeleting;
}
bool KFileItemListProperties::isLocal() const
{
return d->m_isLocal;
}
KFileItemList KFileItemListProperties::items() const
{
return d->m_items;
}
QList<QUrl> KFileItemListProperties::urlList() const
{
return d->m_items.targetUrlList();
}
bool KFileItemListProperties::isDirectory() const
{
return d->m_isDirectory;
}
bool KFileItemListProperties::isFile() const
{
return d->m_isFile;
}
QString KFileItemListProperties::mimeType() const
{
if (d->m_mimeType.isEmpty()) {
d->determineMimeTypeAndGroup();
}
return d->m_mimeType;
}
QString KFileItemListProperties::mimeGroup() const
{
if (d->m_mimeType.isEmpty()) {
d->determineMimeTypeAndGroup();
}
return d->m_mimeGroup;
}
void KFileItemListPropertiesPrivate::determineMimeTypeAndGroup() const
{
if (!m_items.isEmpty()) {
m_mimeType = m_items.first().mimetype();
m_mimeGroup = m_mimeType.left(m_mimeType.indexOf(QLatin1Char('/')));
}
for (const KFileItem &item : std::as_const(m_items)) {
const QString itemMimeType = item.mimetype();
// Determine if common MIME type among all items
if (m_mimeType != itemMimeType) {
m_mimeType.clear();
const auto type = QStringView(itemMimeType).left(itemMimeType.indexOf(QLatin1Char('/')));
if (m_mimeGroup != type) {
m_mimeGroup.clear(); // MIME type groups are different as well!
}
}
}
}
@@ -0,0 +1,133 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Peter Penz <peter.penz@gmx.at>
SPDX-FileCopyrightText: 2008 George Goldberg <grundleborg@googlemail.com>
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFILEITEMLISTPROPERTIES_H
#define KFILEITEMLISTPROPERTIES_H
#include "kiocore_export.h"
#include <QList>
#include <QSharedDataPointer>
#include <QUrl>
class KFileItemListPropertiesPrivate;
class KFileItemList;
/**
* @class KFileItemListProperties kfileitemlistproperties.h <KFileItemListProperties>
*
* @brief Provides information about the common properties of a group of
* KFileItem objects.
*
* Given a list of KFileItems, this class can determine (and cache) the common
* MIME type for all items, whether all items are directories, whether all items
* are readable, writable, etc.
* As soon as one file item does not support a specific capability (read, write etc.),
* it is marked as unsupported for all items.
*
* This class is implicitly shared, which means it can be used as a value and
* copied around at almost no cost.
*
*/
class KIOCORE_EXPORT KFileItemListProperties
{
public:
/**
* @brief Default constructor. Use setItems to specify the items.
*/
KFileItemListProperties();
/**
* @brief Constructor that takes a KFileItemList and sets the capabilities
* supported by all the FileItems as true.
* @param items The list of items that are to have their supported
* capabilities checked.
*/
KFileItemListProperties(const KFileItemList &items);
/**
* @brief Copy constructor
*/
KFileItemListProperties(const KFileItemListProperties &);
/**
* @brief Destructor
*/
virtual ~KFileItemListProperties();
/**
* @brief Assignment operator
*/
KFileItemListProperties &operator=(const KFileItemListProperties &other);
/**
* Sets the items that are to have their supported capabilities checked.
*/
void setItems(const KFileItemList &items);
/**
* @brief Check if reading capability is supported
* @return true if all the FileItems can be read, otherwise false.
*/
bool supportsReading() const;
/**
* @brief Check if deleting capability is supported
* @return true if all the FileItems can be deleted, otherwise false.
*/
bool supportsDeleting() const;
/**
* @brief Check if writing capability is supported
* (file managers use this mostly for directories)
* @return true if all the FileItems can be written to, otherwise false.
*/
bool supportsWriting() const;
/**
* @brief Check if moving capability is supported
* @return true if all the FileItems can be moved, otherwise false.
*/
bool supportsMoving() const;
/**
* @brief Check if files are local
* @return true if all the FileItems are local, otherwise there is one or more
* remote file, so false.
*/
bool isLocal() const;
/**
* List of fileitems passed to the constructor or to setItems().
*/
KFileItemList items() const;
/**
* List of urls, gathered from the fileitems
*/
QList<QUrl> urlList() const;
/**
* @return true if all items are directories
*/
bool isDirectory() const;
/**
* @return Whether all items are files, as reported by KFileItem::isFile().
* @since 5.47
*/
bool isFile() const;
/**
* @return the MIME type of all items, if they all have the same, otherwise an empty string
*/
QString mimeType() const;
/**
* @return the MIME type group (e.g. "text") of all items, if they all have the same, otherwise an empty string
*/
QString mimeGroup() const;
private:
/** @brief d-pointer */
QSharedDataPointer<KFileItemListPropertiesPrivate> d;
};
#endif /* KFILEITEMLISTPROPERTIES_H */
@@ -0,0 +1,56 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2015 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kioglobal_p.h"
#include <QStandardPaths>
using LocationMap = QMap<QString, QString>;
static QMap<QString, QString> standardLocationsMap()
{
struct LocationInfo {
QStandardPaths::StandardLocation location;
const char *iconName;
};
static const LocationInfo mapping[] = {
{QStandardPaths::TemplatesLocation, "folder-templates"},
{QStandardPaths::PublicShareLocation, "folder-public"},
{QStandardPaths::MusicLocation, "folder-music"},
{QStandardPaths::MoviesLocation, "folder-videos"},
{QStandardPaths::PicturesLocation, "folder-pictures"},
{QStandardPaths::TempLocation, "folder-temp"},
{QStandardPaths::DownloadLocation, "folder-download"},
// Order matters here as paths can be reused for multiple purposes
// We essentially want more generic choices to trump more specific
// ones.
// home > desktop > documents > *.
{QStandardPaths::DocumentsLocation, "folder-documents"},
{QStandardPaths::DesktopLocation, "user-desktop"},
{QStandardPaths::HomeLocation, "user-home"},
};
LocationMap map;
for (const auto &row : mapping) {
const QStringList locations = QStandardPaths::standardLocations(row.location);
for (const QString &location : locations) {
map.insert(location, QLatin1String(row.iconName));
}
}
return map;
}
QString KIOPrivate::iconForStandardPath(const QString &localDirectory)
{
static auto map = standardLocationsMap();
QString path = localDirectory;
if (path.endsWith(QLatin1Char('/'))) {
path.chop(1);
}
return map.value(path, QString());
}
@@ -0,0 +1,122 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KIO_KIOGLOBAL_P_H
#define KIO_KIOGLOBAL_P_H
#include "kiocore_export.h"
#include <qplatformdefs.h>
#include <KUser>
#ifdef Q_OS_WIN
// windows just sets the mode_t access rights bits to the same value for user+group+other.
// This means using the Linux values here is fine.
#ifndef S_IRUSR
#define S_IRUSR 0400
#endif
#ifndef S_IRGRP
#define S_IRGRP 0040
#endif
#ifndef S_IROTH
#define S_IROTH 0004
#endif
#ifndef S_IWUSR
#define S_IWUSR 0200
#endif
#ifndef S_IWGRP
#define S_IWGRP 0020
#endif
#ifndef S_IWOTH
#define S_IWOTH 0002
#endif
#ifndef S_IXUSR
#define S_IXUSR 0100
#endif
#ifndef S_IXGRP
#define S_IXGRP 0010
#endif
#ifndef S_IXOTH
#define S_IXOTH 0001
#endif
#ifndef S_IRWXU
#define S_IRWXU S_IRUSR | S_IWUSR | S_IXUSR
#endif
#ifndef S_IRWXG
#define S_IRWXG S_IRGRP | S_IWGRP | S_IXGRP
#endif
#ifndef S_IRWXO
#define S_IRWXO S_IROTH | S_IWOTH | S_IXOTH
#endif
Q_STATIC_ASSERT(S_IRUSR == _S_IREAD && S_IWUSR == _S_IWRITE && S_IXUSR == _S_IEXEC);
// these three will never be set in st_mode
#ifndef S_ISUID
#define S_ISUID 04000 // SUID bit does not exist on windows
#endif
#ifndef S_ISGID
#define S_ISGID 02000 // SGID bit does not exist on windows
#endif
#ifndef S_ISVTX
#define S_ISVTX 01000 // sticky bit does not exist on windows
#endif
// Windows does not have S_IFBLK and S_IFSOCK, just use the Linux values, they won't conflict
#ifndef S_IFBLK
#define S_IFBLK 0060000
#endif
#ifndef S_IFSOCK
#define S_IFSOCK 0140000
#endif
/** performs a QT_STAT and add QT_STAT_LNK to st_mode if the path is a symlink */
KIOCORE_EXPORT int kio_windows_lstat(const char *path, QT_STATBUF *buffer);
#ifndef QT_LSTAT
#define QT_LSTAT kio_windows_lstat
#endif
#ifndef QT_STAT_LNK
#define QT_STAT_LNK 0120000
#endif // QT_STAT_LNK
#endif // Q_OS_WIN
namespace KIOPrivate
{
/** @return true if the process with given PID is currently running */
KIOCORE_EXPORT bool isProcessAlive(qint64 pid);
/** Send a terminate signal (SIGTERM on UNIX) to the process with given PID. */
KIOCORE_EXPORT void sendTerminateSignal(qint64 pid);
enum SymlinkType {
GuessSymlinkType,
FileSymlink,
DirectorySymlink,
};
/** Creates a symbolic link at @p destination pointing to @p source
* Unlike UNIX, Windows needs to know whether the symlink points to a file or a directory
* when creating the link. This information can be passed in @p type. If @p type is not given
* the windows code will guess the type based on the source file.
* @note On Windows this requires the current user to have the SeCreateSymbolicLink privilege which
* is usually only given to administrators.
* @return true on success, false on error
*/
KIOCORE_EXPORT bool createSymlink(const QString &source, const QString &destination, SymlinkType type = GuessSymlinkType);
/** Changes the ownership of @p file (like chown()) */
KIOCORE_EXPORT bool changeOwnership(const QString &file, KUserId newOwner, KGroupId newGroup);
/** Returns an icon name for a standard path,
* e.g. folder-pictures for any path in QStandardPaths::PicturesLocation */
QString iconForStandardPath(const QString &localDirectory);
}
#endif // KIO_KIOGLOBAL_P_H
@@ -0,0 +1,32 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kioglobal_p.h"
#include <QFile>
#include <signal.h>
#include <unistd.h>
KIOCORE_EXPORT bool KIOPrivate::isProcessAlive(qint64 pid)
{
return ::kill(pid, 0) == 0;
}
KIOCORE_EXPORT void KIOPrivate::sendTerminateSignal(qint64 pid)
{
::kill(pid, SIGTERM);
}
KIOCORE_EXPORT bool KIOPrivate::createSymlink(const QString &source, const QString &destination, SymlinkType type)
{
Q_UNUSED(type)
return ::symlink(QFile::encodeName(source).constData(), QFile::encodeName(destination).constData()) == 0;
}
KIOCORE_EXPORT bool KIOPrivate::changeOwnership(const QString &file, KUserId newOwner, KGroupId newGroup)
{
return ::chown(QFile::encodeName(file).constData(), newOwner.nativeId(), newGroup.nativeId()) == 0;
}
@@ -0,0 +1,101 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kioglobal_p.h"
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <qt_windows.h>
KIOCORE_EXPORT bool KIOPrivate::isProcessAlive(qint64 pid)
{
HANDLE procHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
bool alive = false;
if (procHandle != INVALID_HANDLE_VALUE) {
DWORD exitCode;
if (GetExitCodeProcess(procHandle, &exitCode)) {
alive = exitCode == STILL_ACTIVE;
}
CloseHandle(procHandle);
}
return alive;
}
// A callback to shutdown cleanly (no forced kill)
BOOL CALLBACK closeProcessCallback(HWND hwnd, LPARAM lParam)
{
DWORD id;
GetWindowThreadProcessId(hwnd, &id);
if (id == (DWORD)lParam) {
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
return TRUE;
}
KIOCORE_EXPORT void KIOPrivate::sendTerminateSignal(qint64 pid)
{
// no error checking whether kill succeeded, Linux code also just sends a SIGTERM without checking
HANDLE procHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pid);
if (procHandle != INVALID_HANDLE_VALUE) {
EnumWindows(&closeProcessCallback, (LPARAM)pid);
CloseHandle(procHandle);
}
}
KIOCORE_EXPORT bool KIOPrivate::createSymlink(const QString &source, const QString &destination, KIOPrivate::SymlinkType type)
{
#if _WIN32_WINNT >= 0x600
DWORD flag;
if (type == KIOPrivate::DirectorySymlink) {
flag = SYMBOLIC_LINK_FLAG_DIRECTORY;
} else if (type == KIOPrivate::FileSymlink) {
flag = 0;
} else {
// Guess the type of the symlink based on the source path
// If the source is a directory we set the flag SYMBOLIC_LINK_FLAG_DIRECTORY, for files
// and non-existent paths we create a symlink to a file
DWORD sourceAttrs = GetFileAttributesW((LPCWSTR)source.utf16());
if (sourceAttrs != INVALID_FILE_ATTRIBUTES && (sourceAttrs & FILE_ATTRIBUTE_DIRECTORY)) {
flag = SYMBOLIC_LINK_FLAG_DIRECTORY;
} else {
flag = 0;
}
}
bool ok = CreateSymbolicLinkW((LPCWSTR)destination.utf16(), (LPCWSTR)source.utf16(), flag);
if (!ok) {
// create a .lnk file
ok = QFile::link(source, destination);
}
return ok;
#else
qWarning("KIOPrivate::createSymlink: not implemented");
return false;
#endif
}
KIOCORE_EXPORT int kio_windows_lstat(const char *path, QT_STATBUF *buffer)
{
int result = QT_STAT(path, buffer);
if (result != 0) {
return result;
}
const QString pathStr = QFile::decodeName(path);
// QFileInfo currently only checks for .lnk file in isSymlink() -> also check native win32 symlinks
const DWORD fileAttrs = GetFileAttributesW((LPCWSTR)pathStr.utf16());
if (fileAttrs != INVALID_FILE_ATTRIBUTES && (fileAttrs & FILE_ATTRIBUTE_REPARSE_POINT) || QFileInfo(pathStr).isSymLink()) {
buffer->st_mode |= QT_STAT_LNK;
}
return result;
}
KIOCORE_EXPORT bool KIOPrivate::changeOwnership(const QString &file, KUserId newOwner, KGroupId newGroup)
{
#pragma message("TODO")
qWarning("KIOPrivate::changeOwnership: not implemented yet");
return false;
}
@@ -0,0 +1,534 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kmountpoint.h"
#include <stdlib.h>
#include "../utils_p.h"
#include <config-kmountpoint.h>
#include <kioglobal_p.h> // Defines QT_LSTAT on windows to kio_windows_lstat
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <qplatformdefs.h>
#ifdef Q_OS_WIN
#include <qt_windows.h>
static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
#else
static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
#endif
// This is the *BSD branch
#if HAVE_SYS_MOUNT_H
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
// FreeBSD has a table of names of mount-options in mount.h, which is only
// defined (as MNTOPT_NAMES) if _WANT_MNTOPTNAMES is defined.
#define _WANT_MNTOPTNAMES
#include <sys/mount.h>
#undef _WANT_MNTOPTNAMES
#endif
#if HAVE_FSTAB_H
#include <fstab.h>
#endif
// Linux
#if HAVE_LIB_MOUNT
#include <libmount/libmount.h>
#endif
static bool isNetfs(const QString &mountType)
{
// List copied from util-linux/libmount/src/utils.c
static const std::vector<QLatin1String> netfsList{
QLatin1String("cifs"),
QLatin1String("smb3"),
QLatin1String("smbfs"),
QLatin1String("nfs"),
QLatin1String("nfs3"),
QLatin1String("nfs4"),
QLatin1String("afs"),
QLatin1String("ncpfs"),
QLatin1String("fuse.curlftpfs"),
QLatin1String("fuse.sshfs"),
QLatin1String("9p"),
};
return std::any_of(netfsList.cbegin(), netfsList.cend(), [mountType](const QLatin1String netfs) {
return mountType == netfs;
});
}
class KMountPointPrivate
{
public:
void resolveGvfsMountPoints(KMountPoint::List &result);
void finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
void finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
QString m_mountedFrom;
QString m_device; // Only available when the NeedRealDeviceName flag was set.
QString m_mountPoint;
QString m_mountType;
QStringList m_mountOptions;
dev_t m_deviceId = 0;
bool m_isNetFs = false;
};
KMountPoint::KMountPoint()
: d(new KMountPointPrivate)
{
}
KMountPoint::~KMountPoint() = default;
#if HAVE_GETMNTINFO
#ifdef MNTOPT_NAMES
static struct mntoptnames bsdOptionNames[] = {MNTOPT_NAMES};
/** @brief Get mount options from @p flags and puts human-readable version in @p list
*
* Appends all positive options found in @p flags to the @p list
* This is roughly paraphrased from FreeBSD's mount.c, prmount().
*/
static void translateMountOptions(QStringList &list, uint64_t flags)
{
const struct mntoptnames *optionInfo = bsdOptionNames;
// Not all 64 bits are useful option names
flags = flags & MNT_VISFLAGMASK;
// Chew up options as long as we're in the table and there
// are any flags left.
for (; flags != 0 && optionInfo->o_opt != 0; ++optionInfo) {
if (flags & optionInfo->o_opt) {
list.append(QString::fromLatin1(optionInfo->o_name));
flags &= ~optionInfo->o_opt;
}
}
}
#else
/** @brief Get mount options from @p flags and puts human-readable version in @p list
*
* This default version just puts the hex representation of @p flags
* in the list, because there is no human-readable version.
*/
static void translateMountOptions(QStringList &list, uint64_t flags)
{
list.append(QStringLiteral("0x%1").arg(QString::number(flags, 16)));
}
#endif
#endif // HAVE_GETMNTINFO
void KMountPointPrivate::finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
{
QString potentialDevice;
if (const auto tag = QLatin1String("UUID="); m_mountedFrom.startsWith(tag)) {
potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-uuid/") + QStringView(m_mountedFrom).mid(tag.size()));
} else if (const auto tag = QLatin1String("LABEL="); m_mountedFrom.startsWith(tag)) {
potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-label/") + QStringView(m_mountedFrom).mid(tag.size()));
}
if (QFile::exists(potentialDevice)) {
m_mountedFrom = potentialDevice;
}
if (infoNeeded & KMountPoint::NeedRealDeviceName) {
if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
}
}
// Chop trailing slash
Utils::removeTrailingSlash(m_mountedFrom);
}
void KMountPointPrivate::finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
{
if (infoNeeded & KMountPoint::NeedRealDeviceName) {
if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
}
}
}
KMountPoint::List KMountPoint::possibleMountPoints(DetailsNeededFlags infoNeeded)
{
KMountPoint::List result;
#ifdef Q_OS_WIN
result = KMountPoint::currentMountPoints(infoNeeded);
#elif HAVE_LIB_MOUNT
if (struct libmnt_table *table = mnt_new_table()) {
// By default parses "/etc/fstab"
if (mnt_table_parse_fstab(table, nullptr) == 0) {
struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
struct libmnt_fs *fs;
while (mnt_table_next_fs(table, itr, &fs) == 0) {
const char *fsType = mnt_fs_get_fstype(fs);
if (qstrcmp(fsType, "swap") == 0) {
continue;
}
Ptr mp(new KMountPoint);
mp->d->m_mountType = QFile::decodeName(fsType);
const char *target = mnt_fs_get_target(fs);
mp->d->m_mountPoint = QFile::decodeName(target);
if (QT_STATBUF buff; QT_LSTAT(target, &buff) == 0) {
mp->d->m_deviceId = buff.st_dev;
}
// First field in /etc/fstab, e.g. /dev/sdXY, LABEL=, UUID=, /some/bind/mount/dir
// or some network mount
if (const char *source = mnt_fs_get_source(fs)) {
mp->d->m_mountedFrom = QFile::decodeName(source);
}
if (infoNeeded & NeedMountOptions) {
mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
}
mp->d->finalizePossibleMountPoint(infoNeeded);
result.append(mp);
}
mnt_free_iter(itr);
}
mnt_free_table(table);
}
#elif HAVE_FSTAB_H
QFile f{QLatin1String(FSTAB)};
if (!f.open(QIODevice::ReadOnly)) {
return result;
}
QTextStream t(&f);
QString s;
while (!t.atEnd()) {
s = t.readLine().simplified();
if (s.isEmpty() || (s[0] == QLatin1Char('#'))) {
continue;
}
// not empty or commented out by '#'
const QStringList item = s.split(QLatin1Char(' '));
if (item.count() < 4) {
continue;
}
Ptr mp(new KMountPoint);
int i = 0;
mp->d->m_mountedFrom = item[i++];
mp->d->m_mountPoint = item[i++];
mp->d->m_mountType = item[i++];
if (mp->d->m_mountType == QLatin1String("swap")) {
continue;
}
QString options = item[i++];
if (infoNeeded & NeedMountOptions) {
mp->d->m_mountOptions = options.split(QLatin1Char(','));
}
mp->d->finalizePossibleMountPoint(infoNeeded);
result.append(mp);
} // while
f.close();
#endif
return result;
}
void KMountPointPrivate::resolveGvfsMountPoints(KMountPoint::List &result)
{
if (m_mountedFrom == QLatin1String("gvfsd-fuse")) {
const QDir gvfsDir(m_mountPoint);
const QStringList mountDirs = gvfsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString &mountDir : mountDirs) {
const QString type = mountDir.section(QLatin1Char(':'), 0, 0);
if (type.isEmpty()) {
continue;
}
KMountPoint::Ptr gvfsmp(new KMountPoint);
gvfsmp->d->m_mountedFrom = m_mountedFrom;
gvfsmp->d->m_mountPoint = m_mountPoint + QLatin1Char('/') + mountDir;
gvfsmp->d->m_mountType = type;
result.append(gvfsmp);
}
}
}
KMountPoint::List KMountPoint::currentMountPoints(DetailsNeededFlags infoNeeded)
{
KMountPoint::List result;
#if HAVE_GETMNTINFO
#if GETMNTINFO_USES_STATVFS
struct statvfs *mounted;
#else
struct statfs *mounted;
#endif
int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
result.reserve(num_fs);
for (int i = 0; i < num_fs; i++) {
Ptr mp(new KMountPoint);
mp->d->m_mountedFrom = QFile::decodeName(mounted[i].f_mntfromname);
mp->d->m_mountPoint = QFile::decodeName(mounted[i].f_mntonname);
mp->d->m_mountType = QFile::decodeName(mounted[i].f_fstypename);
if (QT_STATBUF buff; QT_LSTAT(mounted[i].f_mntonname, &buff) == 0) {
mp->d->m_deviceId = buff.st_dev;
}
if (infoNeeded & NeedMountOptions) {
struct fstab *ft = getfsfile(mounted[i].f_mntonname);
if (ft != nullptr) {
QString options = QFile::decodeName(ft->fs_mntops);
mp->d->m_mountOptions = options.split(QLatin1Char(','));
} else {
translateMountOptions(mp->d->m_mountOptions, mounted[i].f_flags);
}
}
mp->d->finalizeCurrentMountPoint(infoNeeded);
// TODO: Strip trailing '/' ?
result.append(mp);
}
#elif defined(Q_OS_WIN)
// nothing fancy with infoNeeded but it gets the job done
DWORD bits = GetLogicalDrives();
if (!bits) {
return result;
}
for (int i = 0; i < 26; i++) {
if (bits & (1 << i)) {
Ptr mp(new KMountPoint);
mp->d->m_mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/"));
result.append(mp);
}
}
#elif HAVE_LIB_MOUNT
if (struct libmnt_table *table = mnt_new_table()) {
// if "/etc/mtab" is a regular file,
// "/etc/mtab" is used by default instead of "/proc/self/mountinfo" file.
// This leads to NTFS mountpoints being hidden.
if (
#if QT_VERSION_CHECK(LIBMOUNT_MAJOR_VERSION, LIBMOUNT_MINOR_VERSION, LIBMOUNT_PATCH_VERSION) >= QT_VERSION_CHECK(2, 39, 0)
mnt_table_parse_mtab(table, nullptr)
#else // backwards compat, the documentation advises to use nullptr so lets do that whenever possible
mnt_table_parse_mtab(table, "/proc/self/mountinfo")
#endif
== 0) {
struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
struct libmnt_fs *fs;
while (mnt_table_next_fs(table, itr, &fs) == 0) {
Ptr mp(new KMountPoint);
mp->d->m_mountedFrom = QFile::decodeName(mnt_fs_get_source(fs));
mp->d->m_mountPoint = QFile::decodeName(mnt_fs_get_target(fs));
mp->d->m_mountType = QFile::decodeName(mnt_fs_get_fstype(fs));
mp->d->m_isNetFs = mnt_fs_is_netfs(fs) == 1;
mp->d->m_deviceId = mnt_fs_get_devno(fs);
if (infoNeeded & NeedMountOptions) {
mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
}
if (infoNeeded & NeedRealDeviceName) {
if (mp->d->m_mountedFrom.startsWith(QLatin1Char('/'))) {
mp->d->m_device = mp->d->m_mountedFrom;
}
}
mp->d->resolveGvfsMountPoints(result);
mp->d->finalizeCurrentMountPoint(infoNeeded);
result.push_back(mp);
}
mnt_free_iter(itr);
}
mnt_free_table(table);
}
#endif
return result;
}
QString KMountPoint::mountedFrom() const
{
return d->m_mountedFrom;
}
dev_t KMountPoint::deviceId() const
{
return d->m_deviceId;
}
bool KMountPoint::isOnNetwork() const
{
return d->m_isNetFs || isNetfs(d->m_mountType);
}
QString KMountPoint::realDeviceName() const
{
return d->m_device;
}
QString KMountPoint::mountPoint() const
{
return d->m_mountPoint;
}
QString KMountPoint::mountType() const
{
return d->m_mountType;
}
QStringList KMountPoint::mountOptions() const
{
return d->m_mountOptions;
}
KMountPoint::List::List()
: QList<Ptr>()
{
}
KMountPoint::Ptr KMountPoint::List::findByPath(const QString &path) const
{
#ifdef Q_OS_WIN
const QString realPath = QDir::fromNativeSeparators(QDir(path).absolutePath());
#else
/* If the path contains symlinks, get the real name */
QFileInfo fileinfo(path);
// canonicalFilePath won't work unless file exists
const QString realPath = fileinfo.exists() ? fileinfo.canonicalFilePath() : fileinfo.absolutePath();
#endif
KMountPoint::Ptr result;
if (QT_STATBUF buff; QT_LSTAT(QFile::encodeName(realPath).constData(), &buff) == 0) {
auto it = std::find_if(this->cbegin(), this->cend(), [&buff, &realPath](const KMountPoint::Ptr &mountPtr) {
// For a bind mount, the deviceId() is that of the base mount point, e.g. /mnt/foo,
// however the path we're looking for, e.g. /home/user/bar, doesn't start with the
// mount point of the base device, so we go on searching
return mountPtr->deviceId() == buff.st_dev && realPath.startsWith(mountPtr->mountPoint());
});
if (it != this->cend()) {
result = *it;
}
}
return result;
}
KMountPoint::Ptr KMountPoint::List::findByDevice(const QString &device) const
{
const QString realDevice = QFileInfo(device).canonicalFilePath();
if (realDevice.isEmpty()) { // d->m_device can be empty in the loop below, don't match empty with it
return Ptr();
}
for (const KMountPoint::Ptr &mountPoint : *this) {
if (realDevice.compare(mountPoint->d->m_device, cs) == 0 || realDevice.compare(mountPoint->d->m_mountedFrom, cs) == 0) {
return mountPoint;
}
}
return Ptr();
}
bool KMountPoint::probablySlow() const
{
/* clang-format off */
return isOnNetwork()
|| d->m_mountType == QLatin1String("autofs")
|| d->m_mountType == QLatin1String("subfs")
// Technically KIOFUSe mounts local workers as well,
// such as recents:/, but better safe than sorry...
|| d->m_mountType == QLatin1String("fuse.kio-fuse");
/* clang-format on */
}
bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
{
/* clang-format off */
const bool isMsDos = d->m_mountType == QLatin1String("msdos")
|| d->m_mountType == QLatin1String("fat")
|| d->m_mountType == QLatin1String("vfat");
const bool isNtfs = d->m_mountType.contains(QLatin1String("fuse.ntfs"))
|| d->m_mountType.contains(QLatin1String("fuseblk.ntfs"))
// fuseblk could really be anything. But its most common use is for NTFS mounts, these days.
|| d->m_mountType == QLatin1String("fuseblk");
const bool isSmb = d->m_mountType == QLatin1String("cifs")
|| d->m_mountType == QLatin1String("smb3")
|| d->m_mountType == QLatin1String("smbfs")
// gvfs-fuse mounted SMB share
|| d->m_mountType == QLatin1String("smb-share");
/* clang-format on */
switch (flag) {
case SupportsChmod:
case SupportsChown:
case SupportsUTime:
case SupportsSymlinks:
return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :)
case CaseInsensitive:
return isMsDos;
}
return false;
}
KIOCORE_EXPORT QDebug operator<<(QDebug debug, const KMountPoint::Ptr &mp)
{
QDebugStateSaver saver(debug);
if (!mp) {
debug << "QDebug operator<< called on a null KMountPoint::Ptr";
return debug;
}
// clang-format off
debug.nospace() << "KMountPoint ["
<< "Mounted from: " << mp->d->m_mountedFrom
<< ", device name: " << mp->d->m_device
<< ", mount point: " << mp->d->m_mountPoint
<< ", mount type: " << mp->d->m_mountType
<<']';
// clang-format on
return debug;
}
@@ -0,0 +1,202 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KMOUNTPOINT_H
#define KMOUNTPOINT_H
#include "kiocore_export.h"
#include <QExplicitlySharedDataPointer>
#include <QStringList>
#include <memory>
#include <sys/types.h> // dev_t among other definitions
class KMountPointPrivate;
/**
* @class KMountPoint kmountpoint.h <KMountPoint>
*
* The KMountPoint class provides information about mounted and unmounted disks.
* It provides a system independent interface to fstab.
*
* @author Waldo Bastian <bastian@kde.org>
*/
class KIOCORE_EXPORT KMountPoint : public QSharedData
{
public:
using Ptr = QExplicitlySharedDataPointer<KMountPoint>;
/**
* List of mount points.
*/
class KIOCORE_EXPORT List : public QList<Ptr>
{
public:
List();
/**
* Find the mountpoint on which resides @p path
* For instance if /home is a separate partition, findByPath("/home/user/blah")
* will return /home
* @param path the path to check
* @return the mount point of the given file
*/
Ptr findByPath(const QString &path) const;
/**
* Returns the mount point associated with @p device,
* i.e. the one where mountedFrom() == @p device
* (after symlink resolution).
* @return the mountpoint, or @c nullptr if this device doesn't exist or isn't mounted
*/
Ptr findByDevice(const QString &device) const;
};
public:
/**
* Flags that specify which additional details should be fetched for each mountpoint.
* @see DetailsNeededFlags
*/
enum DetailsNeededFlag {
/**
* Only the basic details: mountedFrom, mountPoint, mountType.
*/
BasicInfoNeeded = 0,
/**
* Also fetch the options used when mounting, see KMountPoint::mountOptions().
*/
NeedMountOptions = 1,
/**
* Also fetch the device name (with symlinks resolved), see KMountPoint::realDeviceName().
*/
NeedRealDeviceName = 2,
};
/**
* Stores a combination of #DetailsNeededFlag values.
*/
Q_DECLARE_FLAGS(DetailsNeededFlags, DetailsNeededFlag)
/**
* This function gives a list of all possible mountpoints. (fstab)
* @param infoNeeded Flags that specify which additional information
* should be fetched.
*/
static List possibleMountPoints(DetailsNeededFlags infoNeeded = BasicInfoNeeded);
/**
* Returns a list of all current mountpoints.
*
* @param infoNeeded Flags that specify which additional information
* should be fetched.
*
* @note This method will return an empty list on @c Android
*/
static List currentMountPoints(DetailsNeededFlags infoNeeded = BasicInfoNeeded);
/**
* Where this filesystem gets mounted from.
* This can refer to a device, a remote server or something else.
*/
QString mountedFrom() const;
/**
* Returns @c true if this mount point represents a network filesystem (e.g.\ NFS,
* CIFS, etc.), otherwise returns @c false.
*
* @since 5.86
*/
bool isOnNetwork() const;
/**
* Returns the device ID (dev_t, major, minor) of this mount point. This
* ID is unique per device (including network mounts).
*
* @since 5.86
*/
dev_t deviceId() const;
/**
* Canonical name of the device where the filesystem got mounted from.
* (Or empty, if not a device)
* Only available when the NeedRealDeviceName flag was set.
*/
QString realDeviceName() const;
/**
* Path where the filesystem is mounted (if you used @ref currentMountPoints()),
* or can be mounted (if you used @ref possibleMountPoints()).
*/
QString mountPoint() const;
/**
* Type of filesystem
*/
QString mountType() const;
/**
* Options used to mount the filesystem.
* Only available if the @ref NeedMountOptions flag was set.
*/
QStringList mountOptions() const;
/**
* Returns @c true if the filesystem is "probably" slow, e.g.\ a network mount,
* @c false otherwise.
*/
bool probablySlow() const;
enum FileSystemFlag {
SupportsChmod,
SupportsChown,
SupportsUTime,
SupportsSymlinks,
CaseInsensitive,
};
/**
* Checks the capabilities of the filesystem.
* @param flag the flag to check
* @return true if the filesystem has that flag, false if not
*
* The available flags are:
* @li SupportsChmod: returns true if the filesystem supports chmod
* (e.g. msdos filesystems return false)
* @li SupportsChown: returns true if the filesystem supports chown
* (e.g. msdos filesystems return false)
* @li SupportsUtime: returns true if the filesystems supports utime
* (e.g. msdos filesystems return false)
* @li SupportsSymlinks: returns true if the filesystems supports symlinks
* (e.g. msdos filesystems return false)
* @li CaseInsensitive: returns true if the filesystem treats
* "foo" and "FOO" as being the same file (true for msdos filesystems)
*
*/
bool testFileSystemFlag(FileSystemFlag flag) const;
/**
* Destructor
*/
~KMountPoint();
private:
/**
* Constructor
*/
KIOCORE_NO_EXPORT KMountPoint();
friend KIOCORE_EXPORT QDebug operator<<(QDebug debug, const Ptr &mp);
friend KMountPointPrivate;
std::unique_ptr<KMountPointPrivate> d;
};
KIOCORE_EXPORT QDebug operator<<(QDebug debug, const KMountPoint::Ptr &mp);
Q_DECLARE_OPERATORS_FOR_FLAGS(KMountPoint::DetailsNeededFlags)
#endif // KMOUNTPOINT_H
@@ -0,0 +1,208 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "knfsshare.h"
#include "../utils_p.h"
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QSet>
#include <QTextStream>
#include "kiocoredebug.h"
#include <KConfig>
#include <KConfigGroup>
#include <KDirWatch>
class Q_DECL_HIDDEN KNFSShare::KNFSSharePrivate
{
public:
explicit KNFSSharePrivate(KNFSShare *parent);
void slotFileChange(const QString &);
bool readExportsFile();
bool findExportsFile();
KNFSShare *const q;
QSet<QString> sharedPaths;
QString exportsFile;
};
KNFSShare::KNFSSharePrivate::KNFSSharePrivate(KNFSShare *parent)
: q(parent)
{
if (findExportsFile()) {
readExportsFile();
}
}
/**
* Try to find the nfs config file path
* First tries the kconfig, then checks
* several well-known paths
* @return whether an 'exports' file was found.
**/
bool KNFSShare::KNFSSharePrivate::findExportsFile()
{
KConfig knfsshare(QStringLiteral("knfsshare"));
KConfigGroup config(&knfsshare, QStringLiteral("General"));
exportsFile = config.readPathEntry("exportsFile", QString());
if (!exportsFile.isEmpty() && QFileInfo::exists(exportsFile)) {
return true;
}
if (QFile::exists(QStringLiteral("/etc/exports"))) {
exportsFile = QStringLiteral("/etc/exports");
} else {
// qDebug() << "Could not find exports file! /etc/exports doesn't exist. Configure it in share/config/knfsshare, [General], exportsFile=....";
return false;
}
config.writeEntry("exportsFile", exportsFile);
return true;
}
/**
* Reads all paths from the exports file
* and fills the sharedPaths dict with the values
*/
bool KNFSShare::KNFSSharePrivate::readExportsFile()
{
QFile f(exportsFile);
// qDebug() << exportsFile;
if (!f.open(QIODevice::ReadOnly)) {
qCWarning(KIO_CORE) << "KNFSShare: Could not open" << exportsFile;
return false;
}
sharedPaths.clear();
QTextStream s(&f);
bool continuedLine = false; // is true if the line before ended with a backslash
QString completeLine;
while (!s.atEnd()) {
QString currentLine = s.readLine().trimmed();
if (continuedLine) {
completeLine += currentLine;
continuedLine = false;
} else {
completeLine = currentLine;
}
// is the line continued in the next line ?
if (completeLine.endsWith(QLatin1Char('\\'))) {
continuedLine = true;
// remove the ending backslash
completeLine.chop(1);
continue;
}
// comments or empty lines
if (completeLine.startsWith(QLatin1Char('#')) || completeLine.isEmpty()) {
continue;
}
QString path;
// Handle quotation marks
if (completeLine[0] == QLatin1Char('\"')) {
int i = completeLine.indexOf(QLatin1Char('"'), 1);
if (i == -1) {
qCWarning(KIO_CORE) << "KNFSShare: Parse error: Missing quotation mark:" << completeLine;
continue;
}
path = completeLine.mid(1, i - 1);
} else { // no quotation marks
int i = completeLine.indexOf(QLatin1Char(' '));
if (i == -1) {
i = completeLine.indexOf(QLatin1Char('\t'));
}
if (i == -1) {
path = completeLine;
} else {
path = completeLine.left(i);
}
}
// qDebug() << "KNFSShare: Found path: " << path;
if (!path.isEmpty()) {
// Append a '/' to normalize path
Utils::appendSlash(path);
sharedPaths.insert(path);
}
}
return true;
}
KNFSShare::KNFSShare()
: d(new KNFSSharePrivate(this))
{
if (!d->exportsFile.isEmpty() && QFileInfo::exists(d->exportsFile)) {
KDirWatch::self()->addFile(d->exportsFile);
connect(KDirWatch::self(), &KDirWatch::dirty, this, [this](const QString &path) {
d->slotFileChange(path);
});
}
}
KNFSShare::~KNFSShare() = default;
bool KNFSShare::isDirectoryShared(const QString &path) const
{
if (path.isEmpty()) {
return false;
}
return d->sharedPaths.contains(Utils::slashAppended(path));
}
QStringList KNFSShare::sharedDirectories() const
{
return d->sharedPaths.values();
}
QString KNFSShare::exportsPath() const
{
return d->exportsFile;
}
void KNFSShare::KNFSSharePrivate::slotFileChange(const QString &path)
{
if (path == exportsFile) {
readExportsFile();
}
Q_EMIT q->changed();
}
class KNFSShareSingleton
{
public:
KNFSShare instance;
};
Q_GLOBAL_STATIC(KNFSShareSingleton, _instance)
KNFSShare *KNFSShare::instance()
{
return &_instance()->instance;
}
#include "moc_knfsshare.cpp"
@@ -0,0 +1,76 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef knfsshare_h
#define knfsshare_h
#include <QObject>
#include "kiocore_export.h"
#include <memory>
/**
* @class KNFSShare knfsshare.h <KNFSShare>
*
* Similar functionality like KFileShare,
* but works only for NFS and do not need
* any suid script.
* It parses the /etc/exports file to get its information.
* Singleton class, call instance() to get an instance.
*/
class KNFSShare : public QObject
{
Q_OBJECT
public:
/**
* Returns the one and only instance of KNFSShare
*/
static KNFSShare *instance();
/**
* Whether or not the given path is shared by NFS.
* @param path the path to check if it is shared by NFS.
* @return whether the given path is shared by NFS.
*/
bool isDirectoryShared(const QString &path) const;
/**
* Returns a list of all directories shared by NFS.
* The resulting list is not sorted.
* @return a list of all directories shared by NFS.
*/
QStringList sharedDirectories() const;
/**
* KNFSShare destructor.
* Do not call!
* The instance is destroyed automatically!
*/
~KNFSShare() override;
/**
* Returns the path to the used exports file,
* or null if no exports file was found
*/
QString exportsPath() const;
Q_SIGNALS:
/**
* Emitted when the /etc/exports file has changed
*/
void changed();
private:
KIOCORE_NO_EXPORT KNFSShare();
class KNFSSharePrivate;
std::unique_ptr<KNFSSharePrivate> const d;
friend class KNFSShareSingleton;
};
#endif
@@ -0,0 +1,19 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2015 Olivier Goffart <ogoffart@woboq.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "koverlayiconplugin.h"
KOverlayIconPlugin::KOverlayIconPlugin(QObject *parent)
: QObject(parent)
{
}
KOverlayIconPlugin::~KOverlayIconPlugin()
{
}
#include "moc_koverlayiconplugin.cpp"
@@ -0,0 +1,73 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2015 Olivier Goffart <ogoffart@woboq.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KOVERLAYICONPLUGIN_H
#define KOVERLAYICONPLUGIN_H
#include "kiocore_export.h"
#include <QObject>
class QUrl;
/**
* @class KOverlayIconPlugin koverlayiconplugin.h <KOverlayIconPlugin>
*
* @brief Base class for overlay icon plugins.
* Enables file managers to show custom overlay icons on files.
*
* This plugin can be created and installed through kcoreaddons_add_plugin
* @code
* kcoreaddons_add_plugin(myoverlayplugin SOURCES myoverlayplugin.cpp INSTALL_NAMESPACE "kf6/overlayicon")
* target_link_libraries(myoverlayplugin KF6::KIOCore)
* @endcode
* The C++ file should look like this:
* @code
#include <KOverlayIconPlugin>
class MyOverlayPlugin : public KOverlayIconPlugin
{
Q_PLUGIN_METADATA(IID "org.kde.overlayicon.myplugin")
Q_OBJECT
public:
MyOverlayPlugin() {
}
QStringList getOverlays(const QUrl &url) override {
// Implement your logic
}
};
#include "myoverlayplugin.moc"
* @endcode
* @since 5.16
*/
class KIOCORE_EXPORT KOverlayIconPlugin : public QObject
{
Q_OBJECT
public:
explicit KOverlayIconPlugin(QObject *parent = nullptr);
~KOverlayIconPlugin() override;
/**
* Returns a list of overlay icons to add to a file
* This can be a path to an icon, or the icon name
*
* This function is called from the main thread and must not block.
* It is recommended to have a cache. And if the item is not in cache
* just return an empty list and call the overlaysChanged when the
* information is available.
*/
virtual QStringList getOverlays(const QUrl &item) = 0;
Q_SIGNALS:
/**
* Emit this signal when the list of overlay icons changed for a given URL
*/
void overlaysChanged(const QUrl &url, const QStringList &overlays);
};
#endif
@@ -0,0 +1,128 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kpasswdserverclient.h"
#include "kiocoredebug.h"
#include <kio/authinfo.h>
#include <kio/global.h>
#include "kpasswdserver_interface.h"
#include "kpasswdserverloop_p.h"
class KPasswdServerClientPrivate
{
public:
KPasswdServerClientPrivate()
: seqNr(0)
{
}
qlonglong seqNr;
QString lastHost;
};
KPasswdServerClient::KPasswdServerClient()
: m_interface(
new OrgKdeKPasswdServerInterface(QStringLiteral("org.kde.kpasswdserver6"), QStringLiteral("/modules/kpasswdserver"), QDBusConnection::sessionBus()))
, d(new KPasswdServerClientPrivate)
{
}
KPasswdServerClient::~KPasswdServerClient()
{
delete m_interface;
}
bool KPasswdServerClient::checkAuthInfo(KIO::AuthInfo *info, qlonglong windowId, qlonglong usertime)
{
// qDebug() << "window-id=" << windowId << "url=" << info.url;
if (!QCoreApplication::instance()) {
qCWarning(KIO_CORE) << "KIO worker is not a QCoreApplication! This is required for checkAuthInfo.";
return false;
}
// create the loop for waiting for a result before sending the request
KPasswdServerLoop loop;
QObject::connect(m_interface, &OrgKdeKPasswdServerInterface::checkAuthInfoAsyncResult, &loop, &KPasswdServerLoop::slotQueryResult);
QDBusReply<qlonglong> reply = m_interface->checkAuthInfoAsync(*info, windowId, usertime);
if (!reply.isValid()) {
qCWarning(KIO_CORE) << "Can't communicate with kiod_kpasswdserver (for checkAuthInfo)!" << reply.error().message();
return false;
}
if (!loop.waitForResult(reply.value())) {
qCWarning(KIO_CORE) << "kiod_kpasswdserver died while waiting for reply!";
return false;
}
if (loop.authInfo().isModified()) {
// qDebug() << "username=" << info.username << "password=[hidden]";
*info = loop.authInfo();
return true;
}
return false;
}
int KPasswdServerClient::queryAuthInfo(KIO::AuthInfo *info, const QString &errorMsg, qlonglong windowId, qlonglong usertime)
{
if (info->url.host() != d->lastHost) { // see kpasswdserver/DESIGN
d->lastHost = info->url.host();
d->seqNr = 0;
}
// qDebug() << "window-id=" << windowId;
if (!QCoreApplication::instance()) {
qCWarning(KIO_CORE) << "KIO worker is not a QCoreApplication! This is required for queryAuthInfo.";
return KIO::ERR_PASSWD_SERVER;
}
// create the loop for waiting for a result before sending the request
KPasswdServerLoop loop;
QObject::connect(m_interface, &OrgKdeKPasswdServerInterface::queryAuthInfoAsyncResult, &loop, &KPasswdServerLoop::slotQueryResult);
QDBusReply<qlonglong> reply = m_interface->queryAuthInfoAsync(*info, errorMsg, windowId, d->seqNr, usertime);
if (!reply.isValid()) {
qCWarning(KIO_CORE) << "Can't communicate with kiod_kpasswdserver (for queryAuthInfo)!";
// qDebug() << reply.error().name() << reply.error().message();
return KIO::ERR_PASSWD_SERVER;
}
if (!loop.waitForResult(reply.value())) {
qCWarning(KIO_CORE) << "kiod_kpasswdserver died while waiting for reply!";
return KIO::ERR_PASSWD_SERVER;
}
*info = loop.authInfo();
// qDebug() << "username=" << info->username << "password=[hidden]";
const qlonglong newSeqNr = loop.seqNr();
if (newSeqNr > 0) {
d->seqNr = newSeqNr;
if (info->isModified()) {
return KJob::NoError;
}
}
return KIO::ERR_USER_CANCELED;
}
void KPasswdServerClient::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId)
{
m_interface->addAuthInfo(info, windowId);
}
void KPasswdServerClient::removeAuthInfo(const QString &host, const QString &protocol, const QString &user)
{
m_interface->removeAuthInfo(host, protocol, user);
}
@@ -0,0 +1,97 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KPASSWDSERVERCLIENT_H
#define KPASSWDSERVERCLIENT_H
#include <kiocore_export.h>
#include <qglobal.h>
#include <memory>
class QString;
class OrgKdeKPasswdServerInterface;
namespace KIO
{
class AuthInfo;
}
class KPasswdServerClientPrivate;
/**
* @class KPasswdServerClient kpasswdserverclient.h <KPasswdServerClient>
*
* Interface class for kpasswdserver.
* KIO workers should not use this directly but via the WorkerBase API.
* @since 5.30
*/
class KIOCORE_EXPORT KPasswdServerClient
{
public:
/**
* Creates a client instance for kpasswdserver.
* The instance should be kept for the lifetime of the process, not created for each request.
*/
KPasswdServerClient();
/**
* Destructor.
*/
~KPasswdServerClient();
KPasswdServerClient(const KPasswdServerClient &) = delete;
KPasswdServerClient &operator=(const KPasswdServerClient &) = delete;
/**
* Check if kpasswdserver has cached authentication information regarding
* an AuthInfo object.
* @param info information to check cache for
* @param windowId used as parent for dialogs, comes from QWidget::winId() on the toplevel widget
* @param usertime the X11 user time from the calling application, so that any dialog
* (e.g. wallet password) respects focus-prevention rules.
* Use KUserTimestamp::userTimestamp in the GUI application from which the request originates.
* @return true if kpasswdserver provided cached information, false if not
* @remarks info will contain the results of the check. To see if
* information was retrieved, check info.isModified().
*/
bool checkAuthInfo(KIO::AuthInfo *info, qlonglong windowId, qlonglong usertime);
/**
* Let kpasswdserver ask the user for authentication information.
* @param info information to query the user for
* @param errorMsg error message that will be displayed to the user
* @param windowId used as parent for dialogs, comes from QWidget::winId() on the toplevel widget
* @param usertime the X11 user time from the calling application, so that the dialog
* (e.g. wallet password) respects focus-prevention rules.
* Use KUserTimestamp::userTimestamp in the GUI application from which the request originates.
* @return a KIO error code: KJob::NoError (0) on success, otherwise ERR_USER_CANCELED if the user canceled,
* or ERR_PASSWD_SERVER if we couldn't communicate with kpasswdserver.
* @remarks If NoError is returned, then @p info will contain the authentication information that was retrieved.
*/
int queryAuthInfo(KIO::AuthInfo *info, const QString &errorMsg, qlonglong windowId, qlonglong usertime);
/**
* Manually add authentication information to kpasswdserver's cache.
* @param info information to add
* @param windowId used as parent window for dialogs, comes from QWidget::winId() on the toplevel widget
*/
void addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId);
/**
* Manually remove authentication information from kpasswdserver's cache.
* @param host hostname of the information to remove
* @param protocol protocol to remove information for
* @param user username to remove information for
*/
void removeAuthInfo(const QString &host, const QString &protocol, const QString &user);
private:
OrgKdeKPasswdServerInterface *m_interface;
std::unique_ptr<KPasswdServerClientPrivate> d;
};
#endif
@@ -0,0 +1,57 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kpasswdserverloop_p.h"
#include <QDBusConnection>
#include <QDBusServiceWatcher>
KPasswdServerLoop::KPasswdServerLoop()
: m_seqNr(-1)
{
QDBusServiceWatcher *watcher =
new QDBusServiceWatcher(QStringLiteral("org.kde.kpasswdserver6"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration, this);
connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &KPasswdServerLoop::kdedServiceUnregistered);
}
KPasswdServerLoop::~KPasswdServerLoop()
{
}
bool KPasswdServerLoop::waitForResult(qlonglong requestId)
{
m_requestId = requestId;
m_seqNr = -1;
m_authInfo = KIO::AuthInfo();
return (exec() == 0);
}
qlonglong KPasswdServerLoop::seqNr() const
{
return m_seqNr;
}
const KIO::AuthInfo &KPasswdServerLoop::authInfo() const
{
return m_authInfo;
}
void KPasswdServerLoop::slotQueryResult(qlonglong requestId, qlonglong seqNr, const KIO::AuthInfo &authInfo)
{
if (m_requestId == requestId) {
m_seqNr = seqNr;
m_authInfo = authInfo;
exit(0);
}
}
void KPasswdServerLoop::kdedServiceUnregistered()
{
exit(-1);
}
#include "moc_kpasswdserverloop_p.cpp"
@@ -0,0 +1,41 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KPASSWDSERVERLOOP_P_H
#define KPASSWDSERVERLOOP_P_H
#include <QEventLoop>
#include <kio/authinfo.h>
// Wait for the result of an asynchronous D-Bus request to KPasswdServer.
// Objects of this class are one-way ie. as soon as they have received
// a result you can't call waitForResult() again.
class KPasswdServerLoop : public QEventLoop
{
Q_OBJECT
public:
KPasswdServerLoop();
~KPasswdServerLoop() override;
bool waitForResult(qlonglong requestId);
qlonglong seqNr() const;
const KIO::AuthInfo &authInfo() const;
public Q_SLOTS:
void slotQueryResult(qlonglong requestId, qlonglong seqNr, const KIO::AuthInfo &authInfo);
private Q_SLOTS:
void kdedServiceUnregistered();
private:
qlonglong m_requestId;
qlonglong m_seqNr;
KIO::AuthInfo m_authInfo;
};
#endif
@@ -0,0 +1,331 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kprotocolinfo.h"
#include "kprotocolinfo_p.h"
#include "kprotocolinfofactory_p.h"
#include "kiocoredebug.h"
#include <KApplicationTrader>
#include <KConfig>
#include <KConfigGroup>
#include <KJsonUtils>
#include <KPluginMetaData>
#include <KSharedConfig>
#include <QUrl>
KProtocolInfoPrivate::KProtocolInfoPrivate(const QString &name, const QString &exec, const QJsonObject &json)
: m_name(name)
, m_exec(exec)
{
// source has fallback true if not set
m_isSourceProtocol = json.value(QStringLiteral("source")).toBool(true);
// true if not set for backwards compatibility
m_supportsPermissions = json.value(QStringLiteral("permissions")).toBool(true);
// other bools are fine with default false by toBool
m_isHelperProtocol = json.value(QStringLiteral("helper")).toBool();
m_supportsReading = json.value(QStringLiteral("reading")).toBool();
m_supportsWriting = json.value(QStringLiteral("writing")).toBool();
m_supportsMakeDir = json.value(QStringLiteral("makedir")).toBool();
m_supportsDeleting = json.value(QStringLiteral("deleting")).toBool();
m_supportsLinking = json.value(QStringLiteral("linking")).toBool();
m_supportsMoving = json.value(QStringLiteral("moving")).toBool();
m_supportsOpening = json.value(QStringLiteral("opening")).toBool();
m_supportsTruncating = json.value(QStringLiteral("truncating")).toBool();
m_canCopyFromFile = json.value(QStringLiteral("copyFromFile")).toBool();
m_canCopyToFile = json.value(QStringLiteral("copyToFile")).toBool();
m_canRenameFromFile = json.value(QStringLiteral("renameFromFile")).toBool();
m_canRenameToFile = json.value(QStringLiteral("renameToFile")).toBool();
m_canDeleteRecursive = json.value(QStringLiteral("deleteRecursive")).toBool();
// default is "FromURL"
const QString fnu = json.value(QStringLiteral("fileNameUsedForCopying")).toString();
m_fileNameUsedForCopying = KProtocolInfo::FromUrl;
if (fnu == QLatin1String("Name")) {
m_fileNameUsedForCopying = KProtocolInfo::Name;
} else if (fnu == QLatin1String("DisplayName")) {
m_fileNameUsedForCopying = KProtocolInfo::DisplayName;
}
m_listing = json.value(QStringLiteral("listing")).toVariant().toStringList();
// Many .protocol files say "Listing=false" when they really mean "Listing=" (i.e. unsupported)
if (m_listing.count() == 1 && m_listing.first() == QLatin1String("false")) {
m_listing.clear();
}
m_supportsListing = (m_listing.count() > 0);
m_defaultMimetype = json.value(QStringLiteral("defaultMimetype")).toString();
// determineMimetypeFromExtension has fallback true if not set
m_determineMimetypeFromExtension = json.value(QStringLiteral("determineMimetypeFromExtension")).toBool(true);
m_archiveMimeTypes = json.value(QStringLiteral("archiveMimetype")).toVariant().toStringList();
m_icon = json.value(QStringLiteral("Icon")).toString();
// config has fallback to name if not set
m_config = json.value(QStringLiteral("config")).toString(m_name);
// max workers has fallback to 1 if not set
m_maxWorkers = json.value(QStringLiteral("maxInstances")).toInt(1);
m_maxWorkersPerHost = json.value(QStringLiteral("maxInstancesPerHost")).toInt();
QString tmp = json.value(QStringLiteral("input")).toString();
if (tmp == QLatin1String("filesystem")) {
m_inputType = KProtocolInfo::T_FILESYSTEM;
} else if (tmp == QLatin1String("stream")) {
m_inputType = KProtocolInfo::T_STREAM;
} else {
m_inputType = KProtocolInfo::T_NONE;
}
tmp = json.value(QStringLiteral("output")).toString();
if (tmp == QLatin1String("filesystem")) {
m_outputType = KProtocolInfo::T_FILESYSTEM;
} else if (tmp == QLatin1String("stream")) {
m_outputType = KProtocolInfo::T_STREAM;
} else {
m_outputType = KProtocolInfo::T_NONE;
}
m_docPath = json.value(QStringLiteral("X-DocPath")).toString();
if (m_docPath.isEmpty()) {
m_docPath = json.value(QStringLiteral("DocPath")).toString();
}
m_protClass = json.value(QStringLiteral("Class")).toString().toLower();
if (!m_protClass.startsWith(QLatin1Char(':'))) {
m_protClass.prepend(QLatin1Char(':'));
}
// ExtraNames is a translated value, use the KCoreAddons helper to read it
const QStringList extraNames = KJsonUtils::readTranslatedValue(json, QStringLiteral("ExtraNames")).toVariant().toStringList();
const QStringList extraTypes = json.value(QStringLiteral("ExtraTypes")).toVariant().toStringList();
if (extraNames.size() == extraTypes.size()) {
auto func = [](const QString &name, const QString &type) {
const int metaType = QMetaType::fromName(type.toLatin1().constData()).id();
// currently QMetaType::Type and ExtraField::Type use the same subset of values, so we can just cast.
return KProtocolInfo::ExtraField(name, static_cast<KProtocolInfo::ExtraField::Type>(metaType));
};
std::transform(extraNames.cbegin(), extraNames.cend(), extraTypes.cbegin(), std::back_inserter(m_extraFields), func);
} else {
qCWarning(KIO_CORE) << "Malformed JSON protocol file for protocol:" << name
<< ", number of the ExtraNames fields should match the number of ExtraTypes fields";
}
// fallback based on class
m_showPreviews = json.value(QStringLiteral("ShowPreviews")).toBool(m_protClass == QLatin1String(":local"));
m_capabilities = json.value(QStringLiteral("Capabilities")).toVariant().toStringList();
m_proxyProtocol = json.value(QStringLiteral("ProxiedBy")).toString();
}
//
// Static functions:
//
QStringList KProtocolInfo::protocols()
{
return KProtocolInfoFactory::self()->protocols();
}
bool KProtocolInfo::isFilterProtocol(const QString &_protocol)
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return false;
}
return !prot->m_isSourceProtocol;
}
QString KProtocolInfo::icon(const QString &_protocol)
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
if (auto service = KApplicationTrader::preferredService(QLatin1String("x-scheme-handler/") + _protocol)) {
return service->icon();
} else {
return QString();
}
}
return prot->m_icon;
}
QString KProtocolInfo::config(const QString &_protocol)
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QString();
}
return QStringLiteral("kio_%1rc").arg(prot->m_config);
}
int KProtocolInfo::maxWorkers(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return 1;
}
return prot->m_maxWorkers;
}
int KProtocolInfo::maxWorkersPerHost(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return 0;
}
return prot->m_maxWorkersPerHost;
}
bool KProtocolInfo::determineMimetypeFromExtension(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return true;
}
return prot->m_determineMimetypeFromExtension;
}
QString KProtocolInfo::exec(const QString &protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol);
if (!prot) {
return QString();
}
return prot->m_exec;
}
KProtocolInfo::ExtraFieldList KProtocolInfo::extraFields(const QUrl &url)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(url.scheme());
if (!prot) {
return ExtraFieldList();
}
return prot->m_extraFields;
}
QString KProtocolInfo::defaultMimetype(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QString();
}
return prot->m_defaultMimetype;
}
QString KProtocolInfo::docPath(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QString();
}
return prot->m_docPath;
}
QString KProtocolInfo::protocolClass(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QString();
}
return prot->m_protClass;
}
bool KProtocolInfo::showFilePreview(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
const bool defaultSetting = prot ? prot->m_showPreviews : false;
KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings"));
return group.readEntry(_protocol, defaultSetting);
}
QStringList KProtocolInfo::capabilities(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QStringList();
}
return prot->m_capabilities;
}
QStringList KProtocolInfo::archiveMimetypes(const QString &protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol);
if (!prot) {
return QStringList();
}
return prot->m_archiveMimeTypes;
}
#if KIOCORE_BUILD_DEPRECATED_SINCE(6, 4)
QString KProtocolInfo::proxiedBy(const QString &_protocol)
{
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(_protocol);
if (!prot) {
return QString();
}
return prot->m_proxyProtocol;
}
#endif
bool KProtocolInfo::isFilterProtocol(const QUrl &url)
{
return isFilterProtocol(url.scheme());
}
bool KProtocolInfo::isHelperProtocol(const QUrl &url)
{
return isHelperProtocol(url.scheme());
}
bool KProtocolInfo::isHelperProtocol(const QString &protocol)
{
// We call the findProtocol directly (not via KProtocolManager) to bypass any proxy settings.
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol);
if (prot) {
return prot->m_isHelperProtocol;
}
return false;
}
bool KProtocolInfo::isKnownProtocol(const QUrl &url)
{
return isKnownProtocol(url.scheme());
}
bool KProtocolInfo::isKnownProtocol(const QString &protocol, bool updateCacheIfNotfound)
{
// We call the findProtocol (const QString&) to bypass any proxy settings.
KProtocolInfoPrivate *prot = KProtocolInfoFactory::self()->findProtocol(protocol, updateCacheIfNotfound);
return prot;
}
@@ -0,0 +1,340 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000-2001 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPROTOCOLINFO_H
#define KPROTOCOLINFO_H
#include "kiocore_export.h"
#include <QMetaType>
#include <QStringList>
/**
* \class KProtocolInfo kprotocolinfo.h <KProtocolInfo>
*
* Information about I/O (Internet, etc.) protocols supported by KDE.
* KProtocolInfo is useful if you want to know which protocols
* KDE supports. In addition you can find out lots of information
* about a certain protocol. All of the functionality is provided by the static
* methods.
* The implementation scans the *.protocol files of all installed KIO workers to get
* this information and stores the result into an internal cache.
*
* *.protocol files are installed in the "services" resource.
*
* The KProtocolInfo methods are reentrant (i.e. can be called from multiple threads simultaneously).
*/
class KIOCORE_EXPORT KProtocolInfo
{
public:
//
// Static functions:
//
/**
* Returns list of all known protocols.
* @return a list of all known protocols
*/
static QStringList protocols();
/**
* Returns whether a protocol is installed that is able to handle @p url.
*
* @param url the url to check
* @return true if the protocol is known
* @see name()
*/
static bool isKnownProtocol(const QUrl &url);
/**
* Same as above except you can supply just the protocol instead of
* the whole URL.
*/
static bool isKnownProtocol(const QString &protocol, bool updateCacheIfNotfound = true);
/**
* Returns the library / executable to open for the protocol @p protocol
* Example : "kio_ftp", meaning either the executable "kio_ftp" or
* the library "kio_ftp.la" (recommended), whichever is available.
*
* This corresponds to the "exec=" field in the protocol description file.
* @param protocol the protocol to check
* @return the executable of library to open, or QString() for
* unsupported protocols
* @see KUrl::protocol()
*/
static QString exec(const QString &protocol);
/**
* Describes the type of a protocol.
* For instance ftp:// appears as a filesystem with folders and files,
* while bzip2:// appears as a single file (a stream of data),
* and telnet:// doesn't output anything.
* @see outputType
*/
enum Type {
T_STREAM, ///< stream of data (e.g.\ single file)
T_FILESYSTEM, ///< structured directory
T_NONE, ///< no information about the type available
T_ERROR, ///< used to signal an error
};
/**
* Definition of an extra field in the UDS entries, returned by a listDir operation.
*
* The name is the name of the column, translated.
*
* The type name comes from QMetaType::name()
* Currently supported types: "QString", "QDateTime" (ISO-8601 format)
*/
struct ExtraField {
enum Type {
String = QMetaType::QString,
DateTime = QMetaType::QDateTime,
Invalid = QMetaType::UnknownType
};
ExtraField()
: type(Invalid)
{
}
ExtraField(const QString &_name, Type _type)
: name(_name)
, type(_type)
{
}
QString name;
Type type;
};
typedef QList<ExtraField> ExtraFieldList;
/**
* Definition of extra fields in the UDS entries, returned by a listDir operation.
*
* This corresponds to the "ExtraNames=" and "ExtraTypes=" fields in the protocol description file.
* Those two lists should be separated with ',' in the protocol description file.
* See ExtraField for details about names and types
*/
static ExtraFieldList extraFields(const QUrl &url);
/**
* Returns whether the protocol can act as a helper protocol.
* A helper protocol invokes an external application and does not return
* a file or stream.
*
* This corresponds to the "helper=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol is a helper protocol (e.g. vnc), false
* if not (e.g. http)
*/
static bool isHelperProtocol(const QUrl &url);
/**
* Same as above except you can supply just the protocol instead of
* the whole URL.
*/
static bool isHelperProtocol(const QString &protocol);
/**
* Returns whether the protocol can act as a filter protocol.
*
* A filter protocol can operate on data that is passed to it
* but does not retrieve/store data itself, like gzip.
* A filter protocol is the opposite of a source protocol.
*
* The "source=" field in the protocol description file determines
* whether a protocol is a source protocol or a filter protocol.
* Valid values for this field are "true" (default) for source protocol or
* "false" for filter protocol.
*
* @param url the url to check
* @return true if the protocol is a filter (e.g. gzip), false if the
* protocol is a helper or source
*/
static bool isFilterProtocol(const QUrl &url);
/**
* Same as above except you can supply just the protocol instead of
* the whole URL.
*/
static bool isFilterProtocol(const QString &protocol);
/**
* Returns the name of the icon, associated with the specified protocol.
*
* This corresponds to the "Icon=" field in the protocol description file.
*
* @param protocol the protocol to check
* @return the icon of the protocol, or an empty string if unknown
*/
static QString icon(const QString &protocol);
/**
* Returns the name of the config file associated with the
* specified protocol. This is useful if two similar protocols
* need to share a single config file, e.g. http and https.
*
* This corresponds to the "config=" field in the protocol description file.
* The default is the protocol name, see name()
*
* @param protocol the protocol to check
* @return the config file, or an empty string if unknown
*/
static QString config(const QString &protocol);
/**
* Returns the soft limit on the number of KIO workers for this protocol.
* This limits the number of workers used for a single operation, note
* that multiple operations may result in a number of instances that
* exceeds this soft limit.
*
* This corresponds to the "maxInstances=" field in the protocol's worker metadata.
* The default is 1.
*
* @param protocol the protocol to check
* @return the maximum number of workers, or 1 if unknown
*
* @since 5.101
*/
static int maxWorkers(const QString &protocol);
/**
* Returns the limit on the number of KIO workers for this protocol per host.
*
* This corresponds to the "maxInstancesPerHost=" field in the protocol's worker metadata.
* The default is 0 which means there is no per host limit.
*
* @param protocol the protocol to check
* @return the maximum number of workers, or 1 if unknown
*
* @since 5.101
*/
static int maxWorkersPerHost(const QString &protocol);
/**
* Returns whether MIME types can be determined based on extension for this
* protocol. For some protocols, e.g. http, the filename extension in the URL
* can not be trusted to truly reflect the file type.
*
* This corresponds to the "determineMimetypeFromExtension=" field in the protocol description file.
* Valid values for this field are "true" (default) or "false".
*
* @param protocol the protocol to check
* @return true if the MIME types can be determined by extension
*/
static bool determineMimetypeFromExtension(const QString &protocol);
/**
* Returns the default MIME type for the specified protocol, if one exists.
*
* This corresponds to the "defaultMimetype=" field in the protocol description file.
*
* @param protocol the protocol to check
* @return the default MIME type of the protocol, or an empty string if none set or protocol unknown
* @since 5.60
*/
static QString defaultMimetype(const QString &protocol);
/**
* Returns the documentation path for the specified protocol.
*
* This corresponds to the "X-DocPath=" or "DocPath=" field in the protocol description file.
*
* @param protocol the protocol to check
* @return the docpath of the protocol, or an empty string if unknown
*/
static QString docPath(const QString &protocol);
/**
* Returns the protocol class for the specified protocol.
*
* This corresponds to the "Class=" field in the protocol description file.
*
* The following classes are defined:
* @li ":internet" for common internet protocols
* @li ":local" for protocols that access local resources
*
* Protocol classes always start with a ':' so that they can not be confused with
* the protocols themselves.
*
* @param protocol the protocol to check
* @return the class of the protocol, or an empty string if unknown
*/
static QString protocolClass(const QString &protocol);
/**
* Returns whether file previews should be shown for the specified protocol.
*
* This corresponds to the "ShowPreviews=" field in the protocol description file.
*
* By default previews are shown if protocolClass is :local.
*
* @param protocol the protocol to check
* @return true if previews should be shown by default, false otherwise
*/
static bool showFilePreview(const QString &protocol);
/**
* Returns the list of capabilities provided by the KIO worker implementing
* this protocol.
*
* This corresponds to the "Capabilities=" field in the protocol description file.
*
* The capability names are not defined globally, they are up to each
* worker implementation. For example when adding support for a new
* special command for mounting, one would add the string "Mount" to the
* capabilities list, and applications could check for that string
* before sending a special() command that would otherwise do nothing
* on older KIO worker implementations.
*
* @param protocol the protocol to check
* @return the list of capabilities.
*/
static QStringList capabilities(const QString &protocol);
/**
* Returns the list of archive MIME types handled by the KIO worker implementing
* this protocol.
*
* This corresponds to the "archiveMimetype=" field in the protocol description file.
*
* @param protocol the protocol to check
* @return the list of archive MIME types (e.g. application/x-zip) handled.
* @since 5.23
*/
static QStringList archiveMimetypes(const QString &protocol);
#if KIOCORE_ENABLE_DEPRECATED_SINCE(6, 4)
/**
* Returns the name of the protocol through which the request
* will be routed if proxy support is enabled.
*
* A good example of this is the ftp protocol for which proxy
* support is commonly handled by the http protocol.
*
* This corresponds to the "ProxiedBy=" in the protocol description file.
*
* @deprecated since 6.4, not used.
*/
KIOCORE_DEPRECATED_VERSION(6, 4, "Not used")
static QString proxiedBy(const QString &protocol);
#endif
typedef enum {
Name,
FromUrl,
DisplayName
} FileNameUsedForCopying;
private:
Q_DISABLE_COPY(KProtocolInfo)
};
#endif
@@ -0,0 +1,65 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000-2001 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPROTOCOLINFOPRIVATE_H
#define KPROTOCOLINFOPRIVATE_H
#include "kprotocolinfo.h"
#include <QJsonObject>
/**
* @internal
*/
class KProtocolInfoPrivate
{
public:
KProtocolInfoPrivate(const QString &name, const QString &exec, const QJsonObject &json);
QString m_name;
QString m_exec;
KProtocolInfo::Type m_inputType;
KProtocolInfo::Type m_outputType;
QStringList m_listing;
bool m_isSourceProtocol : 1;
bool m_isHelperProtocol : 1;
bool m_supportsListing : 1;
bool m_supportsReading : 1;
bool m_supportsWriting : 1;
bool m_supportsMakeDir : 1;
bool m_supportsDeleting : 1;
bool m_supportsLinking : 1;
bool m_supportsMoving : 1;
bool m_supportsOpening : 1;
bool m_supportsTruncating : 1;
bool m_determineMimetypeFromExtension : 1;
bool m_canCopyFromFile : 1;
bool m_canCopyToFile : 1;
bool m_showPreviews : 1;
bool m_canRenameFromFile : 1;
bool m_canRenameToFile : 1;
bool m_canDeleteRecursive : 1;
bool m_supportsPermissions : 1;
QString m_defaultMimetype;
QString m_icon;
QString m_config;
int m_maxWorkers;
QString m_docPath;
QString m_protClass;
QStringList m_archiveMimeTypes;
KProtocolInfo::ExtraFieldList m_extraFields;
KProtocolInfo::FileNameUsedForCopying m_fileNameUsedForCopying;
QStringList m_capabilities;
QString m_proxyProtocol;
int m_maxWorkersPerHost;
};
#endif
@@ -0,0 +1,118 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kprotocolinfo_p.h"
#include "kprotocolinfofactory_p.h"
#include <KPluginMetaData>
#include <QCoreApplication>
#include <QDirIterator>
#include <QStandardPaths>
#include "kiocoredebug.h"
Q_GLOBAL_STATIC(KProtocolInfoFactory, kProtocolInfoFactoryInstance)
KProtocolInfoFactory *KProtocolInfoFactory::self()
{
return kProtocolInfoFactoryInstance();
}
KProtocolInfoFactory::KProtocolInfoFactory()
: m_cacheDirty(true)
{
}
KProtocolInfoFactory::~KProtocolInfoFactory()
{
QMutexLocker locker(&m_mutex);
qDeleteAll(m_cache);
m_cache.clear();
}
QStringList KProtocolInfoFactory::protocols()
{
QMutexLocker locker(&m_mutex);
// fill cache, if not already done and use it
fillCache();
return m_cache.keys();
}
QList<KProtocolInfoPrivate *> KProtocolInfoFactory::allProtocols()
{
QMutexLocker locker(&m_mutex);
// fill cache, if not already done and use it
fillCache();
return m_cache.values();
}
KProtocolInfoPrivate *KProtocolInfoFactory::findProtocol(const QString &protocol, bool updateCacheIfNotfound)
{
Q_ASSERT(!protocol.isEmpty());
Q_ASSERT(!protocol.contains(QLatin1Char(':')));
QMutexLocker locker(&m_mutex);
const bool filled = fillCache();
KProtocolInfoPrivate *info = m_cache.value(protocol);
if (!info && !filled && updateCacheIfNotfound) {
// Unknown protocol! Maybe it just got installed and our cache is out of date?
qCDebug(KIO_CORE) << "Refilling KProtocolInfoFactory cache in the hope to find" << protocol;
m_cacheDirty = true;
fillCache();
info = m_cache.value(protocol);
}
return info;
}
bool KProtocolInfoFactory::fillCache()
{
// mutex MUST be locked from the outside!
Q_ASSERT(!m_mutex.tryLock());
// no work if filled
if (!m_cacheDirty) {
return false;
}
qDeleteAll(m_cache);
m_cache.clear();
// first: search for meta data protocol info, that might be bundled with applications
// we search in all library paths inside kf5/kio
const QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio"));
for (const KPluginMetaData &md : plugins) {
// get worker name & protocols it supports, if any
const QString workerPath = md.fileName();
const QJsonObject protocols(md.rawData().value(QStringLiteral("KDE-KIO-Protocols")).toObject());
qCDebug(KIO_CORE) << workerPath << "supports protocols" << protocols.keys();
// add all protocols, does nothing if object invalid
for (auto it = protocols.begin(); it != protocols.end(); ++it) {
// skip empty objects
const QJsonObject protocol(it.value().toObject());
if (protocol.isEmpty()) {
continue;
}
// add to cache, skip double entries
if (!m_cache.contains(it.key())) {
m_cache.insert(it.key(), new KProtocolInfoPrivate(it.key(), workerPath, protocol));
}
}
}
// all done, don't do it again
m_cacheDirty = false;
return true;
}
@@ -0,0 +1,70 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000, 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef kprotocolinfofactory_h
#define kprotocolinfofactory_h
#include <QHash>
#include <QMutex>
#include <QString>
#include <QStringList>
class KProtocolInfoPrivate;
/**
* @internal
*
* KProtocolInfoFactory is a factory for getting
* KProtocolInfo. The factory is a singleton
* (only one instance can exist).
*/
class KProtocolInfoFactory
{
public:
/**
* @return the instance of KProtocolInfoFactory (singleton).
*/
static KProtocolInfoFactory *self();
KProtocolInfoFactory();
~KProtocolInfoFactory();
/**
* Returns protocol info for @p protocol.
*
* Does not take proxy settings into account.
* @param protocol the protocol to search for
* @param updateCacheIfNotfound Flag for revalidating the cache. This will cause all plugins to be reloaded
* @return the pointer to the KProtocolInfo, or @c nullptr if not found
*/
KProtocolInfoPrivate *findProtocol(const QString &protocol, bool updateCacheIfNotfound = true);
/**
* Loads all protocols. Slow, obviously, but fills the cache once and for all.
*/
QList<KProtocolInfoPrivate *> allProtocols();
/**
* Returns list of all known protocols.
* @return a list of all protocols
*/
QStringList protocols();
private:
/**
* Fill the internal cache.
*/
bool fillCache();
typedef QHash<QString, KProtocolInfoPrivate *> ProtocolCache;
ProtocolCache m_cache;
bool m_cacheDirty;
mutable QMutex m_mutex; // protects m_cache and m_allProtocolsLoaded
};
#endif
@@ -0,0 +1,392 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000 Waldo Bastain <bastain@kde.org>
SPDX-FileCopyrightText: 2000 Dawit Alemayehu <adawit@kde.org>
SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kprotocolmanager.h"
#include "kprotocolinfo_p.h"
#include "kprotocolmanager_p.h"
#include <config-kiocore.h>
#include <QCoreApplication>
#include <QUrl>
#include <KConfigGroup>
#include <KSharedConfig>
#include <kprotocolinfofactory_p.h>
#include "ioworker_defaults.h"
#include "workerconfig.h"
Q_GLOBAL_STATIC(KProtocolManagerPrivate, kProtocolManagerPrivate)
static void syncOnExit()
{
if (kProtocolManagerPrivate.exists()) {
kProtocolManagerPrivate()->sync();
}
}
KProtocolManagerPrivate::KProtocolManagerPrivate()
{
// post routine since KConfig::sync() breaks if called too late
qAddPostRoutine(syncOnExit);
}
KProtocolManagerPrivate::~KProtocolManagerPrivate()
{
}
void KProtocolManagerPrivate::sync()
{
QMutexLocker lock(&mutex);
if (configPtr) {
configPtr->sync();
}
}
void KProtocolManager::reparseConfiguration()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
if (d->configPtr) {
d->configPtr->reparseConfiguration();
}
lock.unlock();
// Force the slave config to re-read its config...
KIO::WorkerConfig::self()->reset();
}
static KSharedConfig::Ptr config()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
Q_ASSERT(!d->mutex.tryLock()); // the caller must have locked the mutex
if (!d->configPtr) {
d->configPtr = KSharedConfig::openConfig(QStringLiteral("kioslaverc"), KConfig::NoGlobals);
}
return d->configPtr;
}
QMap<QString, QString> KProtocolManager::entryMap(const QString &group)
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
return config()->entryMap(group);
}
/*=============================== TIMEOUT SETTINGS ==========================*/
int KProtocolManager::readTimeout()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
KConfigGroup cg(config(), QString());
int val = cg.readEntry("ReadTimeout", DEFAULT_READ_TIMEOUT);
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::connectTimeout()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
KConfigGroup cg(config(), QString());
int val = cg.readEntry("ConnectTimeout", DEFAULT_CONNECT_TIMEOUT);
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::proxyConnectTimeout()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
KConfigGroup cg(config(), QString());
int val = cg.readEntry("ProxyConnectTimeout", DEFAULT_PROXY_CONNECT_TIMEOUT);
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::responseTimeout()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
KConfigGroup cg(config(), QString());
int val = cg.readEntry("ResponseTimeout", DEFAULT_RESPONSE_TIMEOUT);
return qMax(MIN_TIMEOUT_VALUE, val);
}
/*==================================== OTHERS ===============================*/
bool KProtocolManager::markPartial()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
return config()->group(QString()).readEntry("MarkPartial", true);
}
int KProtocolManager::minimumKeepSize()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
return config()->group(QString()).readEntry("MinimumKeepSize",
DEFAULT_MINIMUM_KEEP_SIZE); // 5000 byte
}
bool KProtocolManager::autoResume()
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
return config()->group(QString()).readEntry("AutoResume", false);
}
/* =========================== PROTOCOL CAPABILITIES ============== */
static KProtocolInfoPrivate *findProtocol(const QUrl &url)
{
if (!url.isValid()) {
return nullptr;
}
QString protocol = url.scheme();
return KProtocolInfoFactory::self()->findProtocol(protocol);
}
KProtocolInfo::Type KProtocolManager::inputType(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return KProtocolInfo::T_NONE;
}
return prot->m_inputType;
}
KProtocolInfo::Type KProtocolManager::outputType(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return KProtocolInfo::T_NONE;
}
return prot->m_outputType;
}
bool KProtocolManager::isSourceProtocol(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_isSourceProtocol;
}
bool KProtocolManager::supportsListing(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsListing;
}
QStringList KProtocolManager::listing(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return QStringList();
}
return prot->m_listing;
}
bool KProtocolManager::supportsReading(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsReading;
}
bool KProtocolManager::supportsWriting(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsWriting;
}
bool KProtocolManager::supportsMakeDir(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsMakeDir;
}
bool KProtocolManager::supportsDeleting(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsDeleting;
}
bool KProtocolManager::supportsLinking(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsLinking;
}
bool KProtocolManager::supportsMoving(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsMoving;
}
bool KProtocolManager::supportsOpening(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsOpening;
}
bool KProtocolManager::supportsTruncating(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_supportsTruncating;
}
bool KProtocolManager::canCopyFromFile(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_canCopyFromFile;
}
bool KProtocolManager::canCopyToFile(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_canCopyToFile;
}
bool KProtocolManager::canRenameFromFile(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_canRenameFromFile;
}
bool KProtocolManager::canRenameToFile(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_canRenameToFile;
}
bool KProtocolManager::canDeleteRecursive(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return false;
}
return prot->m_canDeleteRecursive;
}
KProtocolInfo::FileNameUsedForCopying KProtocolManager::fileNameUsedForCopying(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return KProtocolInfo::FromUrl;
}
return prot->m_fileNameUsedForCopying;
}
QString KProtocolManager::defaultMimetype(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return QString();
}
return prot->m_defaultMimetype;
}
QString KProtocolManager::protocolForArchiveMimetype(const QString &mimeType)
{
KProtocolManagerPrivate *d = kProtocolManagerPrivate();
QMutexLocker lock(&d->mutex);
if (d->protocolForArchiveMimetypes.isEmpty()) {
const QList<KProtocolInfoPrivate *> allProtocols = KProtocolInfoFactory::self()->allProtocols();
for (KProtocolInfoPrivate *allProtocol : allProtocols) {
const QStringList archiveMimetypes = allProtocol->m_archiveMimeTypes;
for (const QString &mime : archiveMimetypes) {
d->protocolForArchiveMimetypes.insert(mime, allProtocol->m_name);
}
}
}
return d->protocolForArchiveMimetypes.value(mimeType);
}
QString KProtocolManager::charsetFor(const QUrl &url)
{
return KIO::WorkerConfig::self()->configData(url.scheme(), url.host(), QStringLiteral("Charset"));
}
bool KProtocolManager::supportsPermissions(const QUrl &url)
{
KProtocolInfoPrivate *prot = findProtocol(url);
if (!prot) {
return true;
}
return prot->m_supportsPermissions;
}
#undef PRIVATE_DATA
#include "moc_kprotocolmanager_p.cpp"
@@ -0,0 +1,423 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000 Waldo Bastain <bastain@kde.org>
SPDX-FileCopyrightText: 2000 Dawit Alemayehu <adawit@kde.org>
SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPROTOCOLMANAGER_H
#define KPROTOCOLMANAGER_H
#include <QStringList>
#include "kio/global.h" // KIO::CacheControl
#include "kiocore_export.h"
#include "kprotocolinfo.h"
class KSharedConfig;
template<class T>
class QExplicitlySharedDataPointer;
typedef QExplicitlySharedDataPointer<KSharedConfig> KSharedConfigPtr;
namespace KIO
{
class WorkerConfigPrivate;
} // namespace KIO
/**
* @class KProtocolManager kprotocolmanager.h <KProtocolManager>
*
* Provides information about I/O (Internet, etc.) settings chosen/set
* by the end user.
*
* KProtocolManager has a heap of static functions that allows only read
* access to KDE's IO related settings. These include proxy, cache, file
* transfer resumption, timeout and user-agent related settings.
*
* The information provided by this class is generic enough to be applicable
* to any application that makes use of KDE's IO sub-system. Note that this
* mean the proxy, timeout etc. settings are saved in a separate user-specific
* config file and not in the config file of the application.
*
* Original author:
* @author Torben Weis <weis@kde.org>
*
* Revised by:
* @author Waldo Bastain <bastain@kde.org>
* @author Dawit Alemayehu <adawit@kde.org>
* @see KPAC
*/
class KIOCORE_EXPORT KProtocolManager
{
public:
/*=========================== TIMEOUT CONFIG ================================*/
/**
* Returns the preferred timeout value for reading from
* remote connections in seconds.
*
* @return timeout value for remote connection in secs.
*/
static int readTimeout();
/**
* Returns the preferred timeout value for remote connections
* in seconds.
*
* @return timeout value for remote connection in secs.
*/
static int connectTimeout();
/**
* Returns the preferred timeout value for proxy connections
* in seconds.
*
* @return timeout value for proxy connection in secs.
*/
static int proxyConnectTimeout();
/**
* Returns the preferred response timeout value for
* remote connecting in seconds.
*
* @return timeout value for remote connection in seconds.
*/
static int responseTimeout();
/*============================ DOWNLOAD CONFIG ==============================*/
/**
* Returns true if partial downloads should be
* automatically resumed.
* @return true to resume partial downloads
*/
static bool autoResume();
/**
* Returns true if partial downloads should be marked
* with a ".part" extension.
* @return true if partial downloads should get an ".part" extension
*/
static bool markPartial();
/**
* Returns the minimum file size for keeping aborted
* downloads.
*
* Any data downloaded that does not meet this minimum
* requirement will simply be discarded. The default size
* is 5 KB.
*
* @return the minimum keep size for aborted downloads in bytes
*/
static int minimumKeepSize();
/*===================== PROTOCOL CAPABILITIES ===============================*/
/**
* Returns whether the protocol can list files/objects.
* If a protocol supports listing it can be browsed in e.g. file-dialogs
* and konqueror.
*
* Whether a protocol supports listing is determined by the "listing="
* field in the protocol description file.
* If the protocol support listing it should list the fields it provides in
* this field. If the protocol does not support listing this field should
* remain empty (default.)
*
* @param url the url to check
* @return true if the protocol support listing
* @see listing()
*/
static bool supportsListing(const QUrl &url);
/**
* Returns whether the protocol can retrieve data from URLs.
*
* This corresponds to the "reading=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if it is possible to read from the URL
*/
static bool supportsReading(const QUrl &url);
/**
* Returns whether the protocol can store data to URLs.
*
* This corresponds to the "writing=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports writing
*/
static bool supportsWriting(const QUrl &url);
/**
* Returns whether the protocol can create directories/folders.
*
* This corresponds to the "makedir=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can create directories
*/
static bool supportsMakeDir(const QUrl &url);
/**
* Returns whether the protocol can delete files/objects.
*
* This corresponds to the "deleting=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports deleting
*/
static bool supportsDeleting(const QUrl &url);
/**
* Returns whether the protocol can create links between files/objects.
*
* This corresponds to the "linking=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports linking
*/
static bool supportsLinking(const QUrl &url);
/**
* Returns whether the protocol can move files/objects between different
* locations.
*
* This corresponds to the "moving=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports moving
*/
static bool supportsMoving(const QUrl &url);
/**
* Returns whether the protocol can be opened using KIO::open(const QUrl&).
*
* This corresponds to the "opening=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports opening
*/
static bool supportsOpening(const QUrl &url);
/**
* Returns whether the protocol can be truncated with FileJob::truncate(KIO::filesize_t length).
*
* This corresponds to the "truncating=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol supports truncating
* @since 5.66
*/
static bool supportsTruncating(const QUrl &url);
/**
* Returns whether the protocol can copy files/objects directly from the
* filesystem itself. If not, the application will read files from the
* filesystem using the file-protocol and pass the data on to the destination
* protocol.
*
* This corresponds to the "copyFromFile=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can copy files from the local file system
*/
static bool canCopyFromFile(const QUrl &url);
/**
* Returns whether the protocol can copy files/objects directly to the
* filesystem itself. If not, the application will receive the data from
* the source protocol and store it in the filesystem using the
* file-protocol.
*
* This corresponds to the "copyToFile=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can copy files to the local file system
*/
static bool canCopyToFile(const QUrl &url);
/**
* Returns whether the protocol can rename (i.e. move fast) files/objects
* directly from the filesystem itself. If not, the application will read
* files from the filesystem using the file-protocol and pass the data on
* to the destination protocol.
*
* This corresponds to the "renameFromFile=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can rename/move files from the local file system
*/
static bool canRenameFromFile(const QUrl &url);
/**
* Returns whether the protocol can rename (i.e. move fast) files/objects
* directly to the filesystem itself. If not, the application will receive
* the data from the source protocol and store it in the filesystem using the
* file-protocol.
*
* This corresponds to the "renameToFile=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can rename files to the local file system
*/
static bool canRenameToFile(const QUrl &url);
/**
* Returns whether the protocol can recursively delete directories by itself.
* If not (the usual case) then KIO will list the directory and delete files
* and empty directories one by one.
*
* This corresponds to the "deleteRecursive=" field in the protocol description file.
* Valid values for this field are "true" or "false" (default).
*
* @param url the url to check
* @return true if the protocol can delete non-empty directories by itself.
*/
static bool canDeleteRecursive(const QUrl &url);
/**
* This setting defines the strategy to use for generating a filename, when
* copying a file or directory to another directory. By default the destination
* filename is made out of the filename in the source URL. However if the
* KIO worker displays names that are different from the filename of the URL
* (e.g. kio_fonts shows Arial for arial.ttf, or kio_trash shows foo.txt and
* uses some internal URL), using Name means that the display name (UDS_NAME)
* will be used to as the filename in the destination directory.
*
* This corresponds to the "fileNameUsedForCopying=" field in the protocol description file.
* Valid values for this field are "Name" or "FromURL" (default).
*
* @param url the url to check
* @return how to generate the filename in the destination directory when copying/moving
*/
static KProtocolInfo::FileNameUsedForCopying fileNameUsedForCopying(const QUrl &url);
/**
* Returns default MIME type for this URL based on the protocol.
*
* This corresponds to the "defaultMimetype=" field in the protocol description file.
*
* @param url the url to check
* @return the default MIME type of the protocol, or an empty string if unknown
*/
static QString defaultMimetype(const QUrl &url);
/**
* Returns whether the protocol should be treated as a filesystem
* or as a stream when reading from it.
*
* This corresponds to the "input=" field in the protocol description file.
* Valid values for this field are "filesystem", "stream" or "none" (default).
*
* @param url the url to check
* @return the input type of the given @p url
*/
static KProtocolInfo::Type inputType(const QUrl &url);
/**
* Returns whether the protocol should be treated as a filesystem
* or as a stream when writing to it.
*
* This corresponds to the "output=" field in the protocol description file.
* Valid values for this field are "filesystem", "stream" or "none" (default).
*
* @param url the url to check
* @return the output type of the given @p url
*/
static KProtocolInfo::Type outputType(const QUrl &url);
/**
* Returns the list of fields this protocol returns when listing
* The current possibilities are
* Name, Type, Size, Date, AccessDate, Access, Owner, Group, Link, URL, MimeType
* as well as Extra1, Extra2 etc. for extra fields (see extraFields).
*
* This corresponds to the "listing=" field in the protocol description file.
* The supported fields should be separated with ',' in the protocol description file.
*
* @param url the url to check
* @return a list of field names
*/
static QStringList listing(const QUrl &url);
/**
* Returns whether the protocol can act as a source protocol.
*
* A source protocol retrieves data from or stores data to the
* location specified by a URL.
* A source protocol is the opposite of a filter protocol.
*
* The "source=" field in the protocol description file determines
* whether a protocol is a source protocol or a filter protocol.
* @param url the url to check
* @return true if the protocol is a source of data (e.g. http), false if the
* protocol is a filter (e.g. gzip)
*/
static bool isSourceProtocol(const QUrl &url);
/**
* Returns which protocol handles this MIME type, if it's an archive MIME type.
* For instance zip:/ handles application/x-zip.
*
* This is defined in the protocol description file using an entry like
* "archiveMimetype=application/x-zip"
*
* @param mimeType the MIME type to check
* @return the protocol that can handle this archive MIME type, for instance "zip".
*/
static QString protocolForArchiveMimetype(const QString &mimeType);
/*=============================== OTHERS ====================================*/
/**
* Force a reload of the general config file of
* KIO workers ( kioslaverc).
*/
static void reparseConfiguration();
/**
* Returns the charset to use for the specified @ref url.
*
*/
static QString charsetFor(const QUrl &url);
/**
* @brief Returns whether the protocol suppports KIO/POSIX permissions handling.
*
* When this is false the Permissions properties tab may be hidden, for example. The protocol may still support
* permission control through other means, specific to the individual KIO worker.
*
* @param url the url to check
* @return whether the protocol supports permissions
* @since 5.98
*/
static bool supportsPermissions(const QUrl &url);
private:
friend class KIO::WorkerConfigPrivate;
/**
* @internal
* (Shared with WorkerConfig)
*/
KIOCORE_NO_EXPORT static QMap<QString, QString> entryMap(const QString &group);
};
#endif
@@ -0,0 +1,38 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2000 Waldo Bastain <bastain@kde.org>
SPDX-FileCopyrightText: 2000 Dawit Alemayehu <adawit@kde.org>
SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPROTOCOLMANAGER_P_H
#define KPROTOCOLMANAGER_P_H
#include <kiocore_export.h>
#include <QMutex>
#include <QString>
#include <QUrl>
#include <KSharedConfig>
#include "kprotocolmanager.h"
class KIOCORE_EXPORT KProtocolManagerPrivate
{
public:
KProtocolManagerPrivate();
~KProtocolManagerPrivate();
void sync();
QMutex mutex; // protects all member vars
KSharedConfig::Ptr configPtr;
QMap<QString /*mimetype*/, QString /*protocol*/> protocolForArchiveMimetypes;
};
#endif

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