Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 64ca8a8153..13c61c8c6e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,367 +1,367 @@
project(kdelibs)
# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# Make CPack available to easy generate binary packages
include(CPack)
################# set KDE specific information #################
set (KDE_VERSION_MAJOR 4)
set (KDE_VERSION_MINOR 6)
set (KDE_VERSION_RELEASE 80)
set (KDE_VERSION "${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}.${KDE_VERSION_RELEASE}" )
set (KDE_VERSION_STRING "${KDE_VERSION} (4.7 Beta1)")
set (KDE_DISTRIBUTION_TEXT "compiled sources" CACHE STRING "Indicate the distribution in bug reports" )
# win32: give kde home in debug mode a different name as the release home dir because the settings and caches are different
if (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set (_KDE_DEFAULT_HOME_POSTFIX "-debug" CACHE STRING "default KDE home directory postfix" )
endif (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set (KDE_DEFAULT_HOME ".kde${_KDE_DEFAULT_HOME_POSTFIX}" CACHE STRING "The default KDE home directory" )
# this must be before FindKDE4Internal in order to preset the result of the visibility test, so that it will be skipped
option(KHTML_BUILD_TESTREGRESSION "Build KHTML's testregression. Note: this disables hidden visibility")
# Disable visibility if testregression is built, because the symbols are needed then
if (KHTML_BUILD_TESTREGRESSION)
set (__KDE_HAVE_GCC_VISIBILITY 0)
endif (KHTML_BUILD_TESTREGRESSION)
option(STATIC_LIBRARY "Build kdelibs as static libraries." FALSE)
################# write platform profile file which will be installed #################
include(CreateKDEPlatformProfile.cmake)
if(KDE_PLATFORM_FEATURE_DISABLE_DEPRECATED)
set(KDE_NO_DEPRECATED TRUE)
endif(KDE_PLATFORM_FEATURE_DISABLE_DEPRECATED)
############### Load the CTest options ###############
# CTestCustom.cmake has to be in the CTEST_BINARY_DIR.
# in the KDE build system, this is the same as CMAKE_BINARY_DIR.
configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake COPYONLY)
################# now find all used packages #################
set (QT_MIN_VERSION "4.7.0")
find_package(KDE4Internal REQUIRED)
include(KDE4Defaults)
include (MacroLibrary)
if (APPLE)
find_package(Carbon REQUIRED)
endif (APPLE)
if(UNIX AND Q_WS_X11)
#X11 Session Management (SM) is required
#X11_SM_FOUND is set in FindX11, which is required by KDE4Internal
if(NOT X11_SM_FOUND)
message(FATAL_ERROR "\nThe X11 Session Management (SM) development package could not be found.\nPlease install libSM.\n")
endif(NOT X11_SM_FOUND)
endif(UNIX AND Q_WS_X11)
#required features:
# Perl is used e.g. in khtml, kjs, kjsembed and others
find_package(Perl)
macro_log_feature(PERL_FOUND "Perl" "Needed for building kdelibs" "http://www.perl.org" TRUE "" "")
find_package(ZLIB)
macro_log_feature(ZLIB_FOUND "ZLib" "Support for gzip compressed files and data streams" "http://www.zlib.net" TRUE "" "Required by the core KDE libraries and some critical kioslaves")
set(STRIGI_MIN_VERSION 0.6.3)
find_package(Strigi)
if (WIN32)
set (STRIGI_REQUIRED FALSE)
set (STRIGI_EXTRA_TEXT "")
else (WIN32)
set (STRIGI_REQUIRED TRUE)
set (STRIGI_EXTRA_TEXT "Required by some critical kioslaves")
endif (WIN32)
macro_log_feature(STRIGI_FOUND "Strigi" "Desktop indexing and search support" "http://strigi.sourceforge.net" ${STRIGI_REQUIRED} "${STRIGI_MIN_VERSION}" ${STRIGI_EXTRA_TEXT})
set(LIBATTICA_MIN_VERSION "0.1.90")
find_package(LibAttica)
macro_log_feature(LIBATTICA_FOUND "libattica" "Support for Get Hot New Stuff" "git clone git://anongit.kde.org/attica" TRUE "${LIBATTICA_MIN_VERSION}" "")
#optional features
if(X11_FOUND)
#X11_Xrender discovery is done by FindX11
macro_log_feature(X11_Xrender_FOUND "X Rendering Extension (libXrender)" "Support for compositing, rendering operations, and alpha-blending" "http://www.x.org" FALSE "" "STRONGLY RECOMMENDED")
macro_bool_to_01(X11_Xscreensaver_FOUND HAVE_XSCREENSAVER)
macro_bool_to_01(X11_XSync_FOUND HAVE_XSYNC)
macro_log_feature(HAVE_XSCREENSAVER "X Screensaver Extension (libXss)" "Support for KIdleTime (fallback mode)" "http://www.x.org/" FALSE "" "")
macro_log_feature(HAVE_XSYNC "X Sync Extension (libXext)" "Efficient operation of KIdleTime" "http://www.x.org/" FALSE "" "STRONGLY RECOMMENDED")
if(NOT HAVE_XSYNC AND NOT HAVE_XSCREENSAVER)
message(FATAL_ERROR "\nNeither the XSync (libXext) nor XScreensaver (libXss) development package was found.\nPlease install one of them (XSync is recommended)\n")
endif(NOT HAVE_XSYNC AND NOT HAVE_XSCREENSAVER)
endif(X11_FOUND)
macro_optional_find_package(BZip2)
macro_log_feature(BZIP2_FOUND "BZip2" "Support for BZip2 compressed files and data streams" "http://www.bzip.org" FALSE "" "STRONGLY RECOMMENDED")
macro_optional_find_package(LibLZMA)
macro_log_feature(LIBLZMA_FOUND "LZMA/XZ" "Support for xz compressed files and data streams" "http://tukaani.org/xz/" FALSE "" "")
macro_optional_find_package(OpenSSL)
macro_log_feature(OPENSSL_FOUND "OpenSSL" "Support for secure network communications (SSL and TLS)" "http://openssl.org" FALSE "" "STRONGLY RECOMMENDED: KDE uses OpenSSL for the bulk of secure communications, including secure web browsing via HTTPS")
macro_optional_find_package(Libintl)
macro_log_feature(LIBINTL_FOUND "Libintl" "Support for multiple languages" "http://www.gnu.org/software/gettext" FALSE "" "STRONGLY RECOMMENDED: Enables KDE to be available in many different languages")
set(SOPRANO_MIN_VERSION "2.5.60")
macro_optional_find_package(Soprano ${SOPRANO_MIN_VERSION} COMPONENTS PLUGIN_RAPTORPARSER PLUGIN_REDLANDBACKEND)
macro_log_feature(SOPRANO_FOUND "Soprano" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "${SOPRANO_MIN_VERSION}" "")
macro_log_feature(SOPRANO_PLUGIN_RAPTORPARSER_FOUND "Soprano Raptor Parser" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "" "")
macro_log_feature(SOPRANO_PLUGIN_REDLANDBACKEND_FOUND "Soprano Redland Backend" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "" "")
set(SHAREDDESKTOPONTOLOGIES_MIN_VERSION 0.6.50)
macro_optional_find_package(SharedDesktopOntologies ${SHAREDDESKTOPONTOLOGIES_MIN_VERSION})
macro_log_feature(SHAREDDESKTOPONTOLOGIES_FOUND "Shared desktop ontologies" "Support for the Nepomuk semantic desktop system" "http://oscaf.sourceforge.net" FALSE "${SHAREDDESKTOPONTOLOGIES_MIN_VERSION}" "")
macro_optional_find_package(QCA2)
macro_log_feature(QCA2_FOUND "QCA2" "Support for remote plasma widgets" "http://delta.affinix.com/qca" FALSE "2.0.0" "")
find_package(DBusMenuQt)
macro_log_feature(DBUSMENUQT_FOUND "DBusMenuQt" "Support for notification area menus via the DBusMenu protocol" "git clone git://gitorious.org/dbusmenu/dbusmenu-qt.git" TRUE "" "")
################# Disallow in-source build #################
macro_ensure_out_of_source_build("kdelibs requires an out of source build. Please create a separate build directory and run 'cmake path_to_kdelibs [options]' there.")
# ... and warn in case of an earlier in-source build
set(generatedFileInSourceDir EXISTS ${kdelibs_SOURCE_DIR}/kdemacros.h OR EXISTS ${kdelibs_SOURCE_DIR}/config.h)
if(${generatedFileInSourceDir})
message(STATUS "kdemacros.h or config.h exists in your source directory.")
message(FATAL_ERROR "Please run svn-clean, it would seem that your source directory has generated files in it.")
endif(${generatedFileInSourceDir})
#########################################################################
add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
remove_definitions(-DQT3_SUPPORT_WARNINGS -DQT3_SUPPORT)
add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS)
################# setup the include directories #################
# for including config.h and for includes like <kparts/foo.h>
include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces)
if(QCA2_FOUND)
include_directories(
${QCA2_INCLUDE_DIR}
)
endif(QCA2_FOUND)
# Those variables for are only valid inside of kdelibs, of course.
# Use the one variable for the lib you depend upon.
# E.g. kdeui uses ${KDE4_KDECORE_INCLUDES}. Something that depends on kparts uses ${KDE4_KPARTS_INCLUDES}.
set(KDE4_KJS_INCLUDES ${CMAKE_SOURCE_DIR}/kjs
${CMAKE_BINARY_DIR}/kjs)
if(NOT WINCE)
set(KDE4_KDECORE_INCLUDES ${KDE4_KJS_INCLUDES} )
endif(NOT WINCE)
# kdecore depends on Qt (need only headers from kjs)
set(KDE4_KDECORE_INCLUDES ${KDE4_KDECORE_INCLUDES}
${CMAKE_SOURCE_DIR}/kdecore
${CMAKE_BINARY_DIR}/kdecore
${CMAKE_SOURCE_DIR}/kdecore/compression
${CMAKE_SOURCE_DIR}/kdecore/config
${CMAKE_SOURCE_DIR}/kdecore/date
${CMAKE_SOURCE_DIR}/kdecore/io
${CMAKE_SOURCE_DIR}/kdecore/jobs
${CMAKE_SOURCE_DIR}/kdecore/kernel
${CMAKE_SOURCE_DIR}/kdecore/auth
${CMAKE_SOURCE_DIR}/kdecore/network
${CMAKE_SOURCE_DIR}/kdecore/services
${CMAKE_SOURCE_DIR}/kdecore/localization
${CMAKE_SOURCE_DIR}/kdecore/sycoca
${CMAKE_SOURCE_DIR}/kdecore/text
${CMAKE_SOURCE_DIR}/kdecore/util
${CMAKE_SOURCE_DIR}/kdecore/sonnet
${QT_INCLUDES}
${_KDE4_PLATFORM_INCLUDE_DIRS})
# kdeui depends on kdecore
set(KDE4_KDEUI_INCLUDES ${CMAKE_SOURCE_DIR}/kdeui
${CMAKE_SOURCE_DIR}/kdeui/actions
${CMAKE_SOURCE_DIR}/kdeui/colors
${CMAKE_SOURCE_DIR}/kdeui/config
${CMAKE_SOURCE_DIR}/kdeui/dialogs
${CMAKE_SOURCE_DIR}/kdeui/findreplace
${CMAKE_SOURCE_DIR}/kdeui/fonts
${CMAKE_SOURCE_DIR}/kdeui/icons
${CMAKE_SOURCE_DIR}/kdeui/itemviews
${CMAKE_SOURCE_DIR}/kdeui/jobs
${CMAKE_SOURCE_DIR}/kdeui/kernel
${CMAKE_SOURCE_DIR}/kdeui/notifications
${CMAKE_SOURCE_DIR}/kdeui/paged
${CMAKE_SOURCE_DIR}/kdeui/plotting
${CMAKE_SOURCE_DIR}/kdeui/shortcuts
${CMAKE_SOURCE_DIR}/kdeui/sonnet
${CMAKE_SOURCE_DIR}/kdeui/util
${CMAKE_SOURCE_DIR}/kdeui/widgets
${CMAKE_SOURCE_DIR}/kdeui/windowmanagement
${CMAKE_SOURCE_DIR}/kdeui/xmlgui
${KDE4_KDECORE_INCLUDES})
# kio depends on kdeui
set(KDE4_KIO_INCLUDES ${CMAKE_SOURCE_DIR}/kio
${CMAKE_SOURCE_DIR}/kio/bookmarks
${CMAKE_SOURCE_DIR}/kio/kio
${CMAKE_SOURCE_DIR}/kio/kfile
${KDE4_KDEUI_INCLUDES})
# kpty
set(KDE4_KPTY_INCLUDES ${CMAKE_SOURCE_DIR}/kpty ${KDE4_KIO_INCLUDES} )
# kparts depends on kio
set(KDE4_KPARTS_INCLUDES ${CMAKE_SOURCE_DIR}/kparts
${KDE4_KIO_INCLUDES})
# kde3support depends on kparts
set(KDE4_KDE3SUPPORT_INCLUDES ${CMAKE_SOURCE_DIR}/kde3support
${CMAKE_SOURCE_DIR}/kde3support/kdecore
${CMAKE_SOURCE_DIR}/kde3support/kdeui
${CMAKE_SOURCE_DIR}/kde3support/kio
${KDE4_KPARTS_INCLUDES})
if(NOT WINCE)
set(KDE4_KHTML_INCLUDES ${CMAKE_SOURCE_DIR}/khtml)
endif(NOT WINCE)
################# configure checks and create the configured files #################
if(WINCE)
set(STATIC_LIBRARY ON)
add_definitions(-DSTATIC_INSTALL_PATH=L\\\"/programme/kde\\\")
endif(WINCE)
if(STATIC_LIBRARY)
set(LIBRARY_TYPE STATIC)
add_definitions(-DKDELIBS_STATIC_LIBS)
message(STATUS "Building kdelibs as static libraries")
else(STATIC_LIBRARY)
set(LIBRARY_TYPE SHARED)
endif(STATIC_LIBRARY)
# ACL stuff (used in kio/ and kioslaves/)
macro_optional_find_package(ACL)
macro_bool_to_01(ACL_FOUND HAVE_LIBACL HAVE_POSIX_ACL)
macro_log_feature(ACL_FOUND "LibACL" "Support for manipulating access control lists" "ftp://oss.sgi.com/projects/xfs/cmd_tars" FALSE "" "STRONGLY RECOMMENDED")
configure_file(config-acl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-acl.h )
include(ConfigureChecks.cmake)
# Actually nepomuk is not optional, without it other KDE modules don't build,
# so this must be fixed. Alex
if(Soprano_FOUND AND SHAREDDESKTOPONTOLOGIES_FOUND)
set(HAVE_NEPOMUK true)
include(SopranoAddOntology)
add_subdirectory(nepomuk)
endif(Soprano_FOUND AND SHAREDDESKTOPONTOLOGIES_FOUND)
# now create config headers
configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h )
configure_file(config-prefix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-prefix.h )
configure_file(config-compiler.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-compiler.h )
configure_file(config-pty.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-pty.h )
configure_file(config-nepomuk.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-nepomuk.h )
configure_file(kdemacros.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h )
# these two calls here should go somewhere else, Alex
check_library_exists(nsl gethostbyname "" HAVE_NSL_LIBRARY)
check_library_exists(socket connect "" HAVE_SOCKET_LIBRARY)
################# list the subdirectories #################
add_subdirectory( cmake )
add_subdirectory( kdecore )
add_subdirectory( kdeui )
if (UNIX)
add_subdirectory( kpty )
add_subdirectory( kdesu )
endif (UNIX)
if(NOT WINCE)
add_subdirectory( kjs )
add_subdirectory( kjsembed )
endif(NOT WINCE)
add_subdirectory( kio )
add_subdirectory( solid )
add_subdirectory( kded )
if (QT_QT3SUPPORT_FOUND)
add_subdirectory( kde3support )
endif (QT_QT3SUPPORT_FOUND)
add_subdirectory( kfile )
add_subdirectory( kconf_update )
if(NOT WINCE)
add_subdirectory( kdoctools )
endif(NOT WINCE)
add_subdirectory( kioslave )
add_subdirectory( knewstuff )
add_subdirectory( kparts )
add_subdirectory( kutils )
add_subdirectory( licenses )
add_subdirectory( mimetypes )
add_subdirectory( kinit )
add_subdirectory( threadweaver )
add_subdirectory( sonnet )
if(NOT WINCE)
add_subdirectory( khtml )
endif(NOT WINCE)
add_subdirectory( interfaces )
#if ( NOT CMAKE_CROSSCOMPILING AND QT_QTDESIGNER_FOUND )
add_subdirectory( kdewidgets )
#endif ( NOT CMAKE_CROSSCOMPILING AND QT_QTDESIGNER_FOUND )
add_subdirectory( knotify )
if(NOT WINCE)
add_subdirectory( kimgio )
endif(NOT WINCE)
add_subdirectory( dnssd )
add_subdirectory( kross )
add_subdirectory( security )
if(NOT WINCE)
add_subdirectory( plasma )
endif(NOT WINCE)
add_subdirectory( kunitconversion )
add_subdirectory( kdewebkit )
add_subdirectory( includes )
-macro_optional_add_subdirectory( experimental )
+add_subdirectory( experimental )
macro_optional_add_subdirectory( doc )
################# write dependency file which will be installed #################
# Used in configure_file() and install(EXPORT)
set(KDE4_TARGET_PREFIX KDE4__)
include(CreateKDELibsDependenciesFile.cmake)
################# install files #################
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h DESTINATION ${INCLUDE_INSTALL_DIR} )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/config-nepomuk.h DESTINATION ${INCLUDE_INSTALL_DIR} )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDELibsDependencies.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules)
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDEPlatformProfile.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules)
# run a script before installing the exports files which deletes previously installed
# configuration specific export files KDELibs4(Library|Tools)Targets-<config>.cmake
# if the main exports file KDELibs4(Library|Tools)Targets.cmake has changed. This makes sure
# that this main file doesn't include older and different configuration specific exports files,
# which might have a different set of targets or targets with different names.
# The code for installing the exports files will soon go into a macro. Alex
install(CODE "set(EXPORT_FILES KDELibs4LibraryTargets.cmake KDELibs4ToolsTargets.cmake)"
CODE "set(EXPORT_INSTALL_DIR \"${DATA_INSTALL_DIR}/cmake/modules\")"
SCRIPT "${CMAKE_SOURCE_DIR}/cmake/modules/check_installed_exports_file.cmake" )
install( EXPORT kdelibsLibraryTargets DESTINATION ${DATA_INSTALL_DIR}/cmake/modules NAMESPACE ${KDE4_TARGET_PREFIX} FILE KDELibs4LibraryTargets.cmake )
install( EXPORT kdelibsToolsTargets DESTINATION ${DATA_INSTALL_DIR}/cmake/modules NAMESPACE ${KDE4_TARGET_PREFIX} FILE KDELibs4ToolsTargets.cmake )
# the following will be the correct locations once cmake has the improved FIND_PACKAGE()
# install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDELibsDependencies.cmake DESTINATION ${PLUGIN_INSTALL_DIR}/cmake RENAME KDE4Config.cmake)
macro_display_feature_log()
diff --git a/doc/kdeinit4/man-kdeinit4.8.docbook b/doc/kdeinit4/man-kdeinit4.8.docbook
index 98f848e161..c2f68e3c6d 100644
--- a/doc/kdeinit4/man-kdeinit4.8.docbook
+++ b/doc/kdeinit4/man-kdeinit4.8.docbook
@@ -1,247 +1,247 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.2-Based Variant V1.1//EN" "dtd/kdex.dtd" [
<!ENTITY % English "INCLUDE"><!-- change language only here -->
]>
<refentry>
<refentryinfo>
<title>&kde; User's Manual</title>
<author>
<firstname>Waldo</firstname>
<surname>Bastian</surname>
<affiliation>
<address><email>bastian@kde.org</email></address>
</affiliation>
</author>
<author>
<firstname>Mario</firstname>
<surname>Weilguni</surname>
<affiliation>
<address><email>mweilguni@sime.com</email></address>
</affiliation>
</author>
<author>
<firstname>Lubos</firstname>
<surname>Lunak</surname>
<affiliation>
<address><email>l.lunak@kde.org</email></address>
</affiliation>
</author>
<date>2008-10-03</date>
<releaseinfo>0.01.01</releaseinfo>
</refentryinfo>
<refmeta>
<refentrytitle><command>kdeinit4</command></refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname><command>kdeinit4</command></refname>
-<refpurpose>Start all other &kde; programs and kdeinit loadable modules (KLMs).</refpurpose>
+<refpurpose>&kde; process launcher.</refpurpose>
</refnamediv>
<refsynopsisdiv>
<title>Synopsis</title>
<cmdsynopsis>
<command>kdeinit4</command>
<arg choice="opt">--help</arg>
<group>
<arg>--no-fork</arg>
<arg>--no-kded</arg>
<arg>--suicide</arg>
</group>
<group>
<arg>+programs</arg>
<arg>programs</arg>
</group>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>kdeinit4 is a process launcher somewhat similar to the
famous <command>init</command> used for booting UNIX. It executes &kde;
programs and kdeinit loadable modules (KLMs) starting them more efficiently.
</para>
<para>Using kdeinit4 to launch &kde; applications makes starting a typical
&kde; application a couple times faster and reduces memory consumption by
a substantial amount.</para>
<para>kdeinit4 is linked against all libraries a standard &kde; application
needs. With this technique, starting an application becomes much faster
because now only the application itself needs to be linked whereas otherwise
both the application as well as all the libaries it uses need to be linked.
</para>
</refsect1>
<refsect1>
<title>Disadvantages</title>
<para>
The process name of applications started via kdeinit4
is "kdeinit4". This problem can be corrected to a degree by changing the
application name as shown by <command>ps</command>. However, applications
like <command>killall</command> will only see kdeinit4
as the process name. To workaround this, use <command>kdekillall</command>
(from kdesdk/scripts) for applications started via kdeinit4.
</para>
</refsect1>
<refsect1>
<title>Options</title>
<variablelist>
<varlistentry>
<term><option>--help</option></term>
<listitem>
<para>
Show help about options
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--no-fork</option></term>
<listitem>
<para>Do not fork, i.e. do not exit until all the executed programs ends</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--no-kded</option></term>
<listitem>
<para>Do not start kded</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--suicide</option></term>
<listitem>
<para>
Terminate when no KDE applications are left running
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>+programs</option></term>
<listitem>
<para>
runs the programs handling requests
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>programs</option></term>
<listitem>
<para>
runs the programs without handling requests
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Usage</title>
<para>
A standard way to run this program is by simply specifying the following
command at the prompt
<userinput><command>kdeinit4</command> program</userinput> or
<userinput><command>kdeinit4</command> +program
</userinput>
</para>
</refsect1>
<!--
<refsect1>
<title>Examples</title>
<para>
<userinput><command>kdeinit4</command> +kwrite</userinput>
</para>
</refsect1>
-->
<refsect1>
<title>Files</title>
<variablelist>
<varlistentry>
<term><filename>/tmp/kde-$USER/kdeinit4_$INSTANCE</filename></term>
<listitem>
<para>...</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Environment Variables</title>
<variablelist>
<varlistentry>
<term>$<envar>HOME</envar></term>
<listitem>
<para>Specifies the home directory of the current user</para>
</listitem>
</varlistentry>
<varlistentry>
<term>$<envar>KDE_HOME_READONLY</envar></term>
<listitem>
<para>Specifies if the home directory of the current user is read only</para>
</listitem>
</varlistentry>
<varlistentry>
<term>$<envar>KDE_IS_PRELINKED</envar></term>
<listitem>
<para>If set, tells kdeinit4 that the &kde; programs are pre-linked.</para>
<para>(Prelinking is a process that allows you to speed up the process of dynamic linking.)</para>
</listitem>
</varlistentry>
<varlistentry>
<term>$<envar>KDE_DISPLAY</envar></term>
<listitem>
<para>If set, tells kdeinit4 that it is running on a &kde; desktop.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para>kded, kdekillall</para>
</refsect1>
<refsect1>
<title>Bugs</title>
<para>There are probably tons of bugs. Use <ulink url="http://bugs.kde.org">bugs.kde.org</ulink> to report them.</para>
</refsect1>
</refentry>
diff --git a/kdecore/MAINTAINERS b/kdecore/MAINTAINERS
index 4ea948e521..b46b2885f9 100644
--- a/kdecore/MAINTAINERS
+++ b/kdecore/MAINTAINERS
@@ -1,214 +1,213 @@
Here are the code maintainers for each part of this library. Any problems or
suggested patches for a class should be directed to the responsible person for
that class.
compression/
kbzip2filter.cpp David Faure <faure@kde.org> (copyright)
kfilterbase.cpp David Faure <faure@kde.org> (copyright)
kfilterdev.cpp David Faure <faure@kde.org> (copyright)
kgzipfilter.cpp David Faure <faure@kde.org> (copyright)
kxzfilter.cpp Per Øyvind Karlsen <peroyvind@mandriva.org> (copyright)
config/
kconfigbackend.cpp
kconfigbase.cpp
kconfig.cpp
kconfiggroup.cpp
kconfigini.cpp
kcoreconfigskeleton.cpp
kdesktopfile.cpp
ksharedconfig.cpp
date/
kcalendarera.cpp John Layt <john@layt.net>
kcalendarsystem.cpp John Layt <john@layt.net>
kdatetimeformatter.cpp John Layt <john@layt.net>
kdatetimeparser.cpp John Layt <john@layt.net>
kdayperiod.cpp John Layt <john@layt.net>
klocalizeddate.cpp John Layt <john@layt.net>
kdatetime.cpp David Jarvie <djarvie@kde.org>
ksystemtimezone.cpp
ktimezone.cpp
ktzfiletimezone.cpp David Jarvie <djarvie@kde.org>
io/
kautosavefile.cpp
kcmdwrapper.cpp
kdebug.cpp Stephan Kulow <coolo@kde.org>
kdebugdbusiface.cpp
klockfile_unix.cpp
klockfile_win.cpp
kmessage.cpp
kprocess.cpp
ksavefile.cpp
ktempdir.cpp Joseph Wenninger <jowenn@kde.org> (copyright)
ktemporaryfile.cpp Jaison Lee <lee.jaison@gmail.com>
kurl.cpp David Faure <faure@kde.org>
jobs/
kcompositejob.cpp
kjob.cpp
kjobtrackerinterface.cpp
kjobuidelegate.cpp
kconfig_compiler/
kconfig_compiler.cpp
kernel/
kaboutdata.cpp David Faure <faure@kde.org>
kauthorized.cpp
kautostart.cpp
kcmdlineargs.cpp
kcomponentdata.cpp
kglobal.cpp Stephan Kulow <coolo@kde.org>
kkernel_mac.cpp
kkernel_win.cpp
kstandarddirs.cpp David Faure <faure@kde.org>
kstandarddirs_unix.cpp David Faure <faure@kde.org>
kstandarddirs_win.cpp
ktoolinvocation.cpp David Faure <faure@kde.org>
ktoolinvocation_win.cpp
ktoolinvocation_x11.cpp
localization/
common_helpers.cpp
guess_ja.cpp
kcatalog.cpp Hans Petter Bieker <bieker@kde.org>
kcharsets.cpp
kencodingdetector.cpp
kencodingprober.cpp
kentities.c
klocale.cpp Hans Petter Bieker <bieker@kde.org>
klocalizedstring.cpp Chusslove Illich <caslav.ilic@gmx.net>
ktranscript.cpp Chusslove Illich <caslav.ilic@gmx.net>
kuitformats.cpp Chusslove Illich <caslav.ilic@gmx.net>
kuitsemantics.cpp Chusslove Illich <caslav.ilic@gmx.net>
network/ Thiago Macieira <thiago@kde.org>
k3bufferedsocket.cpp
k3clientsocketbase.cpp
k3datagramsocket.cpp
k3httpproxysocketdevice.cpp
k3resolver.cpp
k3resolvermanager.cpp
k3resolverstandardworkers.cpp
k3resolverworkerbase.cpp
k3reverseresolver.cpp
k3serversocket.cpp
k3socketaddress.cpp
k3socketbase.cpp
k3socketbuffer.cpp
k3socketdevice.cpp
k3socks.cpp
k3sockssocketdevice.cpp
k3streamsocket.cpp
- kidna.cpp
klocalsocket.cpp
klocalsocket_unix.cpp
klocalsocket_win.cpp
ksocketfactory.cpp
ksslcertificatemanager.cpp
ktcpsocket.cpp
netsupp.cpp
network/kssld/
kssld.cpp
services/ David Faure <faure@kde.org>
kfoldermimetype.cpp
kmimemagicrule.cpp
kmimetype.cpp
kmimetypefactory.cpp
kmimetypetrader.cpp
kplugininfo.cpp
kserviceaction.cpp
kservice.cpp
kservicefactory.cpp
kservicegroup.cpp
kservicegroupfactory.cpp
kserviceoffer.cpp
kservicetype.cpp
kservicetypefactory.cpp
kservicetypeprofile.cpp
kservicetypetrader.cpp
ktraderparse.cpp
ktraderparsetree.cpp
sonnet/
backgroundchecker.cpp
backgroundengine.cpp
client.cpp
filter.cpp
globals.cpp
loader.cpp
settings.cpp
speller.cpp
spellerplugin.cpp
sycoca/
kmemfile.cpp
kprotocolinfo.cpp David Faure <faure@kde.org>
kprotocolinfofactory.cpp David Faure <faure@kde.org>
ksycoca.cpp David Faure <faure@kde.org>
ksycocadict.cpp David Faure <faure@kde.org>
ksycocaentry.cpp David Faure <faure@kde.org>
ksycocafactory.cpp David Faure <faure@kde.org>
text/
kascii.cpp
kcodecs.cpp
kstringhandler.cpp
util/
kallocator.cpp
kdedmodule.cpp
kde_file_win.cpp
kdeversion.cpp
klauncher_iface.cpp
klibloader.cpp
klibrary.cpp
kmacroexpander.cpp
kmacroexpander_unix.cpp
kmacroexpander_win.cpp
kpluginfactory.cpp
kpluginloader.cpp
krandom.cpp
krandomsequence.cpp
kshell.cpp Oswald Buddenhagen <ossi@kde.org> (copyright)
kshell_unix.cpp
kshell_win.cpp
kuser_unix.cpp
kuser_win.cpp
qtest_kde.cpp
Orphan Files
============
These are files listed with maintainers in the old version of this file that
no longer exist in kdecore. It is likely these were moved or deleted for KDE4.
If you know the fate of these files, please move or delete as appropriate.
akasyncio.cpp Thiago Macieira <thiago@kde.org>
kapplication_win.cpp Jaroslaw Staniek <staniek@kde.org> (copyright)
kbufferedio.cpp Thiago Macieira <thiago@kde.org>
kcharsets.cpp Lars Knoll <knoll@kde.org>
kcheckaccelerators.cpp Matthias Kalle Dalheimer (kalle@kde.org) (copyright)
kclipboard.cpp Carsten Pfeiffer <pfeiffer@kde.org> (copyright)
kcompletion.cpp Carsten Pfeiffer <pfeiffer@kde.org>
kdebugdcopiface.cpp Andreas Beckermann (b_mann@gmx.de) (copyright)
kextsock.cpp Thiago Macieira <thiago@kde.org>
kglobalaccel_win.cpp Ellis Whitehead <ellis@kde.org> (copyright)
kglobalaccel_x11.cpp Ellis Whitehead <ellis@kde.org>
kglobalaccel.cpp Ellis Whitehead <ellis@kde.org>
kinstance.cpp Stephan Kulow <coolo@kde.org>
kmountpoint.cpp David Faure <faure@kde.org>
kmultipledrag.cpp David Faure <faure@kde.org>
kpixmapprovider.cpp Carsten Pfeiffer <pfeiffer@kde.org>
kprotocolinfo_kdecore.cpp David Faure <faure@kde.org>
kshortcut.cpp Ellis Whitehead <ellis@kde.org>
kshortcutmenu.cpp Ellis Whitehead <ellis@kde.org> (copyright)
ksock.cpp Thiago Macieira <thiago@kde.org>
ksockaddr.cpp Thiago Macieira <thiago@kde.org>
kstaticdeleter.cpp Stephan Kulow <coolo@kde.org>
kstdaccel.cpp Ellis Whitehead <ellis@kde.org>
kuser.cpp Tim Jansen <tim@tjansen.de> (copyright)
libintl.cpp Hans Petter Bieker <bieker@kde.org>
diff --git a/kdecore/io/klockfile_unix.cpp b/kdecore/io/klockfile_unix.cpp
index cd909cf8f3..107a11ba8a 100644
--- a/kdecore/io/klockfile_unix.cpp
+++ b/kdecore/io/klockfile_unix.cpp
@@ -1,383 +1,541 @@
/*
This file is part of the KDE libraries
Copyright (c) 2004 Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "klockfile.h"
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <QtCore/QDate>
#include <QtCore/QFile>
-#include <QtCore/QTextIStream>
+#include <QTextStream>
+#include <QMutex>
+#include <QDebug>
#include "krandom.h"
#include "kglobal.h"
#include "kcomponentdata.h"
#include "ktemporaryfile.h"
#include "kde_file.h"
+#include "kmountpoint.h"
-// TODO: http://www.spinnaker.de/linux/nfs-locking.html
+#include <unistd.h>
+#include <fcntl.h>
+
+// Related reading:
+// http://www.spinnaker.de/linux/nfs-locking.html
+// http://en.wikipedia.org/wiki/File_locking
+// http://apenwarr.ca/log/?m=201012
+
+// Related source code:
+// * lockfile-create, from the lockfile-progs package, uses the link() trick from lockFileWithLink below,
+// so it fails on FAT32 too.
+// * the flock program, which uses flock(LOCK_EX), works on local filesystems (including FAT32), but not NFS.
+// Note about flock: don't unlink, it creates a race. http://world.std.com/~swmcd/steven/tech/flock.html
+
+// My attempts with fcntl(F_SETLK) require K_GLOBAL_STATIC(mutex + qset of filenames) in order
+// to lock out other threads, not just other processes, and unlocks when just
+// reading the file in the same process (!). See the apenwarr.ca article above.
+
+// Simpler: O_EXCL. http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=144
class KLockFile::Private
{
public:
Private(const KComponentData &c)
- : componentData(c)
+ : staleTime(30), // 30 seconds
+ isLocked(false),
+ recoverLock(false),
+ linkCountSupport(true),
+ m_pid(-1),
+ m_componentData(c)
{
}
- QString file;
+ // The main method
+ KLockFile::LockResult lockFile(KDE_struct_stat &st_buf);
+
+ // Three different implementations
+ KLockFile::LockResult lockFileOExcl(KDE_struct_stat &st_buf);
+ KLockFile::LockResult lockFileFcntl(KDE_struct_stat &st_buf);
+ KLockFile::LockResult lockFileWithLink(KDE_struct_stat &st_buf);
+
+ KLockFile::LockResult deleteStaleLock();
+ KLockFile::LockResult deleteStaleLockWithLink();
+
+ void writeIntoLockFile(QFile& file, const KComponentData& componentData);
+ void readLockFile();
+ bool isNfs() const;
+
+ QFile m_file;
+ QString m_fileName;
int staleTime;
bool isLocked;
bool recoverLock;
bool linkCountSupport;
QTime staleTimer;
KDE_struct_stat statBuf;
- int pid;
- QString hostname;
- QString instance;
+ int m_pid;
+ QString m_hostname;
+ QString m_componentName;
QString lockRecoverFile;
- KComponentData componentData;
+ KComponentData m_componentData;
};
-// 30 seconds
KLockFile::KLockFile(const QString &file, const KComponentData &componentData)
: d(new Private(componentData))
{
- d->file = file;
- d->staleTime = 30;
- d->isLocked = false;
- d->recoverLock = false;
- d->linkCountSupport = true;
+ d->m_fileName = file;
}
KLockFile::~KLockFile()
{
unlock();
delete d;
}
int
KLockFile::staleTime() const
{
return d->staleTime;
}
void
KLockFile::setStaleTime(int _staleTime)
{
d->staleTime = _staleTime;
}
static bool operator==( const KDE_struct_stat &st_buf1,
const KDE_struct_stat &st_buf2)
{
#define FIELD_EQ(what) (st_buf1.what == st_buf2.what)
return FIELD_EQ(st_dev) && FIELD_EQ(st_ino) &&
FIELD_EQ(st_uid) && FIELD_EQ(st_gid) && FIELD_EQ(st_nlink);
#undef FIELD_EQ
}
static bool operator!=( const KDE_struct_stat& st_buf1,
const KDE_struct_stat& st_buf2 )
{
return !(st_buf1 == st_buf2);
}
static bool testLinkCountSupport(const QByteArray &fileName)
{
KDE_struct_stat st_buf;
int result = -1;
// Check if hardlinks raise the link count at all?
if(!::link( fileName, QByteArray(fileName+".test") )) {
result = KDE_lstat( fileName, &st_buf );
::unlink( QByteArray(fileName+".test") );
}
return (result < 0 || ((result == 0) && (st_buf.st_nlink == 2)));
}
-static KLockFile::LockResult lockFile(const QString &lockFile, KDE_struct_stat &st_buf,
- bool &linkCountSupport, const KComponentData &componentData)
+void KLockFile::Private::writeIntoLockFile(QFile& file, const KComponentData& componentData)
{
- QByteArray lockFileName = QFile::encodeName( lockFile );
- int result = KDE_lstat( lockFileName, &st_buf );
- if (result == 0)
- return KLockFile::LockFail;
-
- KTemporaryFile uniqueFile(componentData);
- uniqueFile.setFileTemplate(lockFile);
- if (!uniqueFile.open())
- return KLockFile::LockError;
- uniqueFile.setPermissions(QFile::ReadUser|QFile::WriteUser|QFile::ReadGroup|QFile::ReadOther);
+ file.setPermissions(QFile::ReadUser|QFile::WriteUser|QFile::ReadGroup|QFile::ReadOther);
char hostname[256];
hostname[0] = 0;
gethostname(hostname, 255);
hostname[255] = 0;
- QString componentName = componentData.componentName();
+ m_hostname = QString::fromLocal8Bit(hostname);
+ m_componentName = componentData.componentName();
+
+ QTextStream stream(&file);
+ m_pid = getpid();
- QTextStream stream(&uniqueFile);
- stream << QString::number(getpid()) << endl
- << componentName << endl
- << hostname << endl;
+ stream << QString::number(m_pid) << endl
+ << m_componentName << endl
+ << m_hostname << endl;
stream.flush();
+}
+
+void KLockFile::Private::readLockFile()
+{
+ m_pid = -1;
+ m_hostname.clear();
+ m_componentName.clear();
+
+ QFile file(m_fileName);
+ if (file.open(QIODevice::ReadOnly))
+ {
+ QTextStream ts(&file);
+ if (!ts.atEnd())
+ m_pid = ts.readLine().toInt();
+ if (!ts.atEnd())
+ m_componentName = ts.readLine();
+ if (!ts.atEnd())
+ m_hostname = ts.readLine();
+ }
+}
+
+KLockFile::LockResult KLockFile::Private::lockFileWithLink(KDE_struct_stat &st_buf)
+{
+ const QByteArray lockFileName = QFile::encodeName( m_fileName );
+ int result = KDE_lstat( lockFileName, &st_buf );
+ if (result == 0)
+ return KLockFile::LockFail;
+
+ KTemporaryFile uniqueFile(m_componentData);
+ uniqueFile.setFileTemplate(m_fileName);
+ if (!uniqueFile.open())
+ return KLockFile::LockError;
+
+ writeIntoLockFile(uniqueFile, m_componentData);
QByteArray uniqueName = QFile::encodeName( uniqueFile.fileName() );
// Create lock file
result = ::link( uniqueName, lockFileName );
if (result != 0)
return KLockFile::LockError;
if (!linkCountSupport)
return KLockFile::LockOK;
KDE_struct_stat st_buf2;
result = KDE_lstat( uniqueName, &st_buf2 );
if (result != 0)
return KLockFile::LockError;
result = KDE_lstat( lockFileName, &st_buf );
if (result != 0)
return KLockFile::LockError;
if (st_buf != st_buf2 || S_ISLNK(st_buf.st_mode) || S_ISLNK(st_buf2.st_mode))
{
// SMBFS supports hardlinks by copying the file, as a result the above test will always fail
// cifs increases link count artifically but the inodes are still different
if ((st_buf2.st_nlink > 1 ||
((st_buf.st_nlink == 1) && (st_buf2.st_nlink == 1))) && (st_buf.st_ino != st_buf2.st_ino))
{
linkCountSupport = testLinkCountSupport(uniqueName);
if (!linkCountSupport)
return KLockFile::LockOK; // Link count support is missing... assume everything is OK.
}
return KLockFile::LockFail;
}
return KLockFile::LockOK;
}
-static KLockFile::LockResult deleteStaleLock(const QString &lockFile, KDE_struct_stat &st_buf, bool &linkCountSupport, const KComponentData &componentData)
+bool KLockFile::Private::isNfs() const
+{
+ // Note: Qt's isLikelyToBeNfs() in corelib/io/qsettings.cpp seems faster
+ KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(m_fileName);
+ const bool slow = mp ? mp->probablySlow() : false;
+ return slow;
+}
+
+KLockFile::LockResult KLockFile::Private::lockFile(KDE_struct_stat &st_buf)
{
- // This is dangerous, we could be deleting a new lock instead of
- // the old stale one, let's be very careful
+ if (isNfs()) {
+ return lockFileWithLink(st_buf);
+ }
+
+ //return lockFileFcntl(st_buf);
+ return lockFileOExcl(st_buf);
+}
- // Create temp file
- KTemporaryFile *ktmpFile = new KTemporaryFile(componentData);
- ktmpFile->setFileTemplate(lockFile);
- if (!ktmpFile->open())
- return KLockFile::LockError;
+class LocksInThisProcess
+{
+public:
+ QSet<QString> m_fileNames;
+ QMutex m_mutex;
+};
+K_GLOBAL_STATIC(LocksInThisProcess, s_locksInThisProcess)
- QByteArray lckFile = QFile::encodeName(lockFile);
- QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName());
- delete ktmpFile;
+KLockFile::LockResult KLockFile::Private::lockFileOExcl(KDE_struct_stat &st_buf)
+{
+ const QByteArray lockFileName = QFile::encodeName( m_fileName );
+
+ int fd = KDE_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL);
+ if (fd < 0) {
+ if (errno == EEXIST) {
+ // File already exists
+ return LockFail;
+ } else {
+ return LockError;
+ }
+ }
+ // We hold the lock, continue.
+ if (!m_file.open(fd, QIODevice::WriteOnly)) {
+ return LockError;
+ }
+ writeIntoLockFile(m_file, m_componentData);
+ const int result = KDE_lstat(QFile::encodeName(m_fileName), &st_buf);
+ if (result != 0)
+ return KLockFile::LockError;
+ return KLockFile::LockOK;
+}
+
+KLockFile::LockResult KLockFile::Private::lockFileFcntl(KDE_struct_stat &st_buf)
+{
+ m_file.setFileName(m_fileName);
+ // fcntl locking requires the file to remain open as long as the lock is held
+ if (m_file.open(QIODevice::WriteOnly)) {
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 1;
+ fl.l_type = F_WRLCK; // exclusive lock
+ //const int cmd = (options & NoBlockFlag) ? F_SETLK : F_SETLKW; // waiting is implemented in the caller
+ const int cmd = F_SETLK;
+ if (fcntl(m_file.handle(), cmd, &fl) == 0) {
+ writeIntoLockFile(m_file, m_componentData);
+ s_locksInThisProcess->m_fileNames.insert(m_fileName);
+
+ const int result = KDE_lstat(QFile::encodeName(m_fileName), &st_buf);
+ if (result != 0)
+ return KLockFile::LockError;
+
+ return KLockFile::LockOK;
+ }
+ return KLockFile::LockFail;
+ }
+ return KLockFile::LockError;
+}
+
+KLockFile::LockResult KLockFile::Private::deleteStaleLock()
+{
+ if (isNfs())
+ return deleteStaleLockWithLink();
+
+ // I see no way to prevent the race condition here, where we could
+ // delete a new lock file that another process just got after we
+ // decided the old one was too stale for us too.
+ qWarning("WARNING: deleting stale lockfile %s", qPrintable(m_fileName));
+ QFile::remove(m_fileName);
+ return LockOK;
+}
+
+KLockFile::LockResult KLockFile::Private::deleteStaleLockWithLink()
+{
+ // This is dangerous, we could be deleting a new lock instead of
+ // the old stale one, let's be very careful
+
+ // Create temp file
+ KTemporaryFile *ktmpFile = new KTemporaryFile(m_componentData);
+ ktmpFile->setFileTemplate(m_fileName);
+ if (!ktmpFile->open()) {
+ delete ktmpFile;
+ return KLockFile::LockError;
+ }
+
+ const QByteArray lckFile = QFile::encodeName(m_fileName);
+ const QByteArray tmpFile = QFile::encodeName(ktmpFile->fileName());
+ delete ktmpFile;
// link to lock file
if (::link(lckFile, tmpFile) != 0)
return KLockFile::LockFail; // Try again later
// check if link count increased with exactly one
// and if the lock file still matches
KDE_struct_stat st_buf1;
KDE_struct_stat st_buf2;
- memcpy(&st_buf1, &st_buf, sizeof(KDE_struct_stat));
+ memcpy(&st_buf1, &statBuf, sizeof(KDE_struct_stat));
st_buf1.st_nlink++;
if ((KDE_lstat(tmpFile, &st_buf2) == 0) && st_buf1 == st_buf2)
{
if ((KDE_lstat(lckFile, &st_buf2) == 0) && st_buf1 == st_buf2)
{
// - - if yes, delete lock file, delete temp file, retry lock
qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
::unlink(lckFile);
::unlink(tmpFile);
return KLockFile::LockOK;
}
}
// SMBFS supports hardlinks by copying the file, as a result the above test will always fail
if (linkCountSupport)
{
linkCountSupport = testLinkCountSupport(tmpFile);
}
if (!linkCountSupport)
{
// Without support for link counts we will have a little race condition
qWarning("WARNING: deleting stale lockfile %s", lckFile.data());
::unlink(tmpFile);
if (::unlink(lckFile) < 0) {
qWarning("WARNING: Problem deleting stale lockfile %s: %s", lckFile.data(),
strerror(errno));
return KLockFile::LockFail;
}
return KLockFile::LockOK;
}
// Failed to delete stale lock file
qWarning("WARNING: Problem deleting stale lockfile %s", lckFile.data());
::unlink(tmpFile);
return KLockFile::LockFail;
}
KLockFile::LockResult KLockFile::lock(LockFlags options)
{
if (d->isLocked)
return KLockFile::LockOK;
KLockFile::LockResult result;
int hardErrors = 5;
int n = 5;
while(true)
{
- KDE_struct_stat st_buf;
- result = lockFile(d->file, st_buf, d->linkCountSupport, d->componentData);
+ KDE_struct_stat st_buf;
+ bool lockedByThisProcess = false;
+ QMutexLocker locker(&s_locksInThisProcess->m_mutex);
+ // TODO turn relative paths into absolute?
+ if (s_locksInThisProcess->m_fileNames.contains(d->m_fileName)) {
+ lockedByThisProcess = true;
+ result = KLockFile::LockFail;
+ } else {
+ // Try to create the lock file
+ result = d->lockFile(st_buf);
+ }
+
if (result == KLockFile::LockOK)
{
d->staleTimer = QTime();
break;
}
else if (result == KLockFile::LockError)
{
d->staleTimer = QTime();
if (--hardErrors == 0)
{
break;
}
}
else // KLockFile::Fail -- there is already such a file present (e.g. left by a crashed app)
{
if (!d->staleTimer.isNull() && d->statBuf != st_buf)
d->staleTimer = QTime();
if (d->staleTimer.isNull())
{
memcpy(&(d->statBuf), &st_buf, sizeof(KDE_struct_stat));
d->staleTimer.start();
- d->pid = -1;
- d->hostname.clear();
- d->instance.clear();
-
- QFile file(d->file);
- if (file.open(QIODevice::ReadOnly))
- {
- QTextStream ts(&file);
- if (!ts.atEnd())
- d->pid = ts.readLine().toInt();
- if (!ts.atEnd())
- d->instance = ts.readLine();
- if (!ts.atEnd())
- d->hostname = ts.readLine();
+ if (!lockedByThisProcess) {
+ d->readLockFile();
}
}
bool isStale = false;
- if ((d->pid > 0) && !d->hostname.isEmpty())
+ if (!lockedByThisProcess && (d->m_pid > 0) && !d->m_hostname.isEmpty())
{
// Check if hostname is us
char hostname[256];
hostname[0] = 0;
gethostname(hostname, 255);
hostname[255] = 0;
- if (d->hostname == QLatin1String(hostname))
+ if (d->m_hostname == QLatin1String(hostname))
{
// Check if pid still exists
- int res = ::kill(d->pid, 0);
+ int res = ::kill(d->m_pid, 0);
if ((res == -1) && (errno == ESRCH))
- isStale = true;
+ isStale = true; // pid does not exist
}
}
if (d->staleTimer.elapsed() > (d->staleTime*1000))
isStale = true;
if (isStale)
{
if ((options & ForceFlag) == 0)
return KLockFile::LockStale;
- result = deleteStaleLock(d->file, d->statBuf, d->linkCountSupport, d->componentData);
+ result = d->deleteStaleLock();
if (result == KLockFile::LockOK)
{
// Lock deletion successful
d->staleTimer = QTime();
continue; // Now try to get the new lock
}
else if (result != KLockFile::LockFail)
{
return result;
}
}
}
if (options & NoBlockFlag)
break;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = n*((KRandom::random() % 200)+100);
if (n < 2000)
n = n * 2;
select(0, 0, 0, 0, &tv);
}
if (result == LockOK)
d->isLocked = true;
return result;
}
bool KLockFile::isLocked() const
{
return d->isLocked;
}
void KLockFile::unlock()
{
if (d->isLocked)
{
- ::unlink(QFile::encodeName(d->file));
+ ::unlink(QFile::encodeName(d->m_fileName));
+ s_locksInThisProcess->m_fileNames.remove(d->m_fileName);
+ d->m_file.close();
+ d->m_pid = -1;
d->isLocked = false;
}
}
bool KLockFile::getLockInfo(int &pid, QString &hostname, QString &appname)
{
- if (d->pid == -1)
+ if (d->m_pid == -1)
return false;
- pid = d->pid;
- hostname = d->hostname;
- appname = d->instance;
+ pid = d->m_pid;
+ hostname = d->m_hostname;
+ appname = d->m_componentName;
return true;
}
diff --git a/kdecore/io/kmountpoint.cpp b/kdecore/io/kmountpoint.cpp
index 8e53dd106e..52e3240a88 100644
--- a/kdecore/io/kmountpoint.cpp
+++ b/kdecore/io/kmountpoint.cpp
@@ -1,543 +1,545 @@
/*
* This file is part of the KDE libraries
* Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
* 2007 David Faure <faure@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kmountpoint.h"
#include <config.h>
#include <stdlib.h>
#include <QtCore/QFile>
#include <QtCore/QTextIStream>
#include "kstandarddirs.h"
#ifdef Q_WS_WIN
#include <windows.h>
#include <QDir>
#endif
#ifdef HAVE_VOLMGT
#include <volmgt.h>
#endif
#ifdef HAVE_SYS_MNTTAB_H
#include <sys/mnttab.h>
#endif
#ifdef HAVE_MNTENT_H
#include <mntent.h>
#elif defined(HAVE_SYS_MNTENT_H)
#include <sys/mntent.h>
#endif
// This is the *BSD branch
#ifdef HAVE_SYS_MOUNT_H
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/mount.h>
#endif
#ifdef HAVE_FSTAB_H
#include <fstab.h>
#endif
#if defined(_AIX)
#include <sys/mntctl.h>
#include <sys/vmount.h>
#include <sys/vfs.h>
/* AIX does not prototype mntctl anywhere that I can find */
#ifndef mntctl
extern "C" int mntctl(int command, int size, void* buffer);
#endif
extern "C" struct vfs_ent *getvfsbytype(int vfsType);
extern "C" void endvfsent( );
#endif
#ifndef HAVE_GETMNTINFO
# ifdef _PATH_MOUNTED
// On some Linux, MNTTAB points to /etc/fstab !
# undef MNTTAB
# define MNTTAB _PATH_MOUNTED
# else
# ifndef MNTTAB
# ifdef MTAB_FILE
# define MNTTAB MTAB_FILE
# else
# define MNTTAB "/etc/mnttab"
# endif
# endif
# endif
#endif
#include "kdebug.h"
#ifdef _OS_SOLARIS_
#define FSTAB "/etc/vfstab"
#else
#define FSTAB "/etc/fstab"
#endif
class KMountPoint::Private {
public:
void finalizePossibleMountPoint(DetailsNeededFlags infoNeeded);
void finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded);
QString mountedFrom;
QString device; // Only available when the NeedRealDeviceName flag was set.
QString mountPoint;
QString mountType;
QStringList mountOptions;
};
KMountPoint::KMountPoint()
:d( new Private )
{
}
KMountPoint::~KMountPoint()
{
delete d;
}
// There are (at least) four kind of APIs:
// setmntent + getmntent + struct mntent (linux...)
// getmntent + struct mnttab
// mntctl + struct vmount (AIX)
// getmntinfo + struct statfs&flags (BSD 4.4 and friends)
// getfsent + char* (BSD 4.3 and friends)
#ifdef HAVE_SETMNTENT
#define SETMNTENT setmntent
#define ENDMNTENT endmntent
#define STRUCT_MNTENT struct mntent *
#define STRUCT_SETMNTENT FILE *
#define GETMNTENT(file, var) ((var = getmntent(file)) != 0)
#define MOUNTPOINT(var) var->mnt_dir
#define MOUNTTYPE(var) var->mnt_type
#define MOUNTOPTIONS(var) var->mnt_opts
#define FSNAME(var) var->mnt_fsname
#else
#define SETMNTENT fopen
#define ENDMNTENT fclose
#define STRUCT_MNTENT struct mnttab
#define STRUCT_SETMNTENT FILE *
#define GETMNTENT(file, var) (getmntent(file, &var) == 0)
#define MOUNTPOINT(var) var.mnt_mountp
#define MOUNTTYPE(var) var.mnt_fstype
#define MOUNTOPTIONS(var) var.mnt_mntopts
#define FSNAME(var) var.mnt_special
#endif
/**
* When using supermount, the device name is in the options field
* as dev=/my/device
*/
static QString devNameFromOptions(const QStringList &options)
{
// Search options to find the device name
for ( QStringList::ConstIterator it = options.begin(); it != options.end(); ++it)
{
if( (*it).startsWith(QLatin1String("dev=")))
return (*it).mid(4);
}
return QString::fromLatin1("none");
}
void KMountPoint::Private::finalizePossibleMountPoint(DetailsNeededFlags infoNeeded)
{
if (mountType == QLatin1String("supermount")) {
mountedFrom = devNameFromOptions(mountOptions);
}
if (mountedFrom.startsWith(QLatin1String("UUID="))) {
const QString uuid = mountedFrom.mid(5);
const QString potentialDevice = QFile::symLinkTarget(QString::fromLatin1("/dev/disk/by-uuid/") + uuid);
if (QFile::exists(potentialDevice)) {
mountedFrom = potentialDevice;
}
}
if (mountedFrom.startsWith(QLatin1String("LABEL="))) {
const QString label = mountedFrom.mid(6);
const QString potentialDevice = QFile::symLinkTarget(QString::fromLatin1("/dev/disk/by-label/") + label);
if (QFile::exists(potentialDevice)) {
mountedFrom = potentialDevice;
}
}
if (infoNeeded & NeedRealDeviceName) {
if (mountedFrom.startsWith(QLatin1Char('/')))
device = KStandardDirs::realFilePath(mountedFrom);
}
// TODO: Strip trailing '/' ?
}
void KMountPoint::Private::finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded)
{
if (infoNeeded & NeedRealDeviceName) {
if (mountedFrom.startsWith(QLatin1Char('/')))
device = KStandardDirs::realFilePath(mountedFrom);
}
}
KMountPoint::List KMountPoint::possibleMountPoints(DetailsNeededFlags infoNeeded)
{
#ifdef Q_WS_WIN
return KMountPoint::currentMountPoints(infoNeeded);
#endif
KMountPoint::List result;
#ifdef HAVE_SETMNTENT
STRUCT_SETMNTENT fstab;
if ((fstab = SETMNTENT(FSTAB, "r")) == 0)
return result;
STRUCT_MNTENT fe;
while (GETMNTENT(fstab, fe))
{
Ptr mp(new KMountPoint);
mp->d->mountedFrom = QFile::decodeName(FSNAME(fe));
mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe));
mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe));
//Devices using supermount have their device names in the mount options
//instead of the device field. That's why we need to read the mount options
if (infoNeeded & NeedMountOptions || (mp->d->mountType == QLatin1String("supermount")))
{
QString options = QFile::decodeName(MOUNTOPTIONS(fe));
mp->d->mountOptions = options.split( QLatin1Char(',') );
}
mp->d->finalizePossibleMountPoint(infoNeeded);
result.append(mp);
}
ENDMNTENT(fstab);
#else
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(' '));
#ifdef _OS_SOLARIS_
if (item.count() < 5)
continue;
#else
if (item.count() < 4)
continue;
#endif
Ptr mp(new KMountPoint);
int i = 0;
mp->d->mountedFrom = item[i++];
#ifdef _OS_SOLARIS_
//device to fsck
i++;
#endif
mp->d->mountPoint = item[i++];
mp->d->mountType = item[i++];
QString options = item[i++];
if (infoNeeded & NeedMountOptions)
{
mp->d->mountOptions = options.split(QLatin1Char(','));
}
mp->d->finalizePossibleMountPoint(infoNeeded);
result.append(mp);
} //while
f.close();
#endif
return result;
}
KMountPoint::List KMountPoint::currentMountPoints(DetailsNeededFlags infoNeeded)
{
KMountPoint::List result;
#ifdef HAVE_GETMNTINFO
#ifdef GETMNTINFO_USES_STATVFS
struct statvfs *mounted;
#else
struct statfs *mounted;
#endif
int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
for (int i=0;i< num_fs;i++)
{
Ptr mp(new KMountPoint);
mp->d->mountedFrom = QFile::decodeName(mounted[i].f_mntfromname);
mp->d->mountPoint = QFile::decodeName(mounted[i].f_mntonname);
#ifdef __osf__
mp->d->mountType = QFile::decodeName(mnt_names[mounted[i].f_type]);
#else
mp->d->mountType = QFile::decodeName(mounted[i].f_fstypename);
#endif
if (infoNeeded & NeedMountOptions)
{
struct fstab *ft = getfsfile(mounted[i].f_mntonname);
if (ft != 0) {
QString options = QFile::decodeName(ft->fs_mntops);
mp->d->mountOptions = options.split(QLatin1Char(','));
} else {
// TODO: get mount options if not mounted via fstab, see mounted[i].f_flags
}
}
mp->d->finalizeCurrentMountPoint(infoNeeded);
// TODO: Strip trailing '/' ?
result.append(mp);
}
#elif defined(_AIX)
struct vmount *mntctl_buffer;
struct vmount *vm;
char *mountedfrom;
char *mountedto;
int fsname_len, num;
int buf_sz = 4096;
mntctl_buffer = (struct vmount*)malloc(buf_sz);
num = mntctl(MCTL_QUERY, buf_sz, mntctl_buffer);
if (num == 0)
{
buf_sz = *(int*)mntctl_buffer;
free(mntctl_buffer);
mntctl_buffer = (struct vmount*)malloc(buf_sz);
num = mntctl(MCTL_QUERY, buf_sz, mntctl_buffer);
}
if (num > 0)
{
/* iterate through items in the vmount structure: */
vm = (struct vmount *)mntctl_buffer;
for ( ; num > 0; --num )
{
/* get the name of the mounted file systems: */
fsname_len = vmt2datasize(vm, VMT_STUB);
mountedto = (char*)malloc(fsname_len + 1);
mountedto[fsname_len] = '\0';
strncpy(mountedto, (char *)vmt2dataptr(vm, VMT_STUB), fsname_len);
fsname_len = vmt2datasize(vm, VMT_OBJECT);
mountedfrom = (char*)malloc(fsname_len + 1);
mountedfrom[fsname_len] = '\0';
strncpy(mountedfrom, (char *)vmt2dataptr(vm, VMT_OBJECT), fsname_len);
/* Look up the string for the file system type,
* as listed in /etc/vfs.
* ex.: nfs,jfs,afs,cdrfs,sfs,cachefs,nfs3,autofs
*/
struct vfs_ent* ent = getvfsbytype(vm->vmt_gfstype);
KMountPoint *mp = new KMountPoint;
mp->d->mountedFrom = QFile::decodeName(mountedfrom);
mp->d->mountPoint = QFile::decodeName(mountedto);
mp->d->mountType = QFile::decodeName(ent->vfsent_name);
free(mountedfrom);
free(mountedto);
if (infoNeeded & NeedMountOptions)
{
// TODO
}
mp->d->finalizeCurrentMountPoint(infoNeeded);
result.append(mp);
/* goto the next vmount structure: */
vm = (struct vmount *)((char *)vm + vm->vmt_length);
}
endvfsent( );
}
free( mntctl_buffer );
#elif defined(Q_WS_WIN) && !defined(_WIN32_WCE)
//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->mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/"));
result.append(mp);
}
}
#elif defined(_WIN32_WCE)
Ptr mp(new KMountPoint);
mp->d->mountPoint = QString("/");
result.append(mp);
#else
STRUCT_SETMNTENT mnttab;
if ((mnttab = SETMNTENT(MNTTAB, "r")) == 0)
return result;
STRUCT_MNTENT fe;
while (GETMNTENT(mnttab, fe))
{
Ptr mp(new KMountPoint);
mp->d->mountedFrom = QFile::decodeName(FSNAME(fe));
mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe));
mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe));
//Devices using supermount have their device names in the mount options
//instead of the device field. That's why we need to read the mount options
if (infoNeeded & NeedMountOptions || (mp->d->mountType == QLatin1String("supermount")))
{
QString options = QFile::decodeName(MOUNTOPTIONS(fe));
mp->d->mountOptions = options.split( QLatin1Char(',') );
}
mp->d->finalizeCurrentMountPoint(infoNeeded);
result.append(mp);
}
ENDMNTENT(mnttab);
#endif
return result;
}
QString KMountPoint::mountedFrom() const
{
return d->mountedFrom;
}
QString KMountPoint::realDeviceName() const
{
return d->device;
}
QString KMountPoint::mountPoint() const
{
return d->mountPoint;
}
QString KMountPoint::mountType() const
{
return d->mountType;
}
QStringList KMountPoint::mountOptions() const
{
return d->mountOptions;
}
KMountPoint::List::List()
: QList<Ptr>()
{
}
KMountPoint::Ptr KMountPoint::List::findByPath(const QString& path) const
{
#ifndef Q_WS_WIN
/* If the path contains symlinks, get the real name */
const QString realname = KStandardDirs::realFilePath(path);
#else
const QString realname = QDir::fromNativeSeparators(QDir(path).absolutePath());
#endif
int max = 0;
KMountPoint::Ptr result;
for (const_iterator it = begin(); it != end(); ++it) {
const QString mountpoint = (*it)->d->mountPoint;
const int length = mountpoint.length();
if (realname.startsWith(mountpoint) && length > max) {
max = length;
result = *it;
// keep iterating to check for a better match (bigger max)
}
}
return result;
}
KMountPoint::Ptr KMountPoint::List::findByDevice(const QString& device) const
{
const QString realDevice = KStandardDirs::realFilePath(device);
if (realDevice.isEmpty()) // d->device can be empty in the loop below, don't match empty with it
return Ptr();
for (const_iterator it = begin(); it != end(); ++it) {
if ((*it)->d->device == realDevice ||
(*it)->d->mountedFrom == realDevice)
return *it;
}
return Ptr();
}
bool KMountPoint::probablySlow() const
{
bool nfs = d->mountType == QLatin1String("nfs");
bool autofs = d->mountType == QLatin1String("autofs") || d->mountType == QLatin1String("subfs");
+ // TODO add smbfs here?
+ //
//bool pid = d->mountPoint.contains(":(pid");
// The "pid" thing was in kde3's KIO::probably_slow_mounted, with obscure logic
// (looks like it used state from the previous line or something...)
// This needs to be revised once we have a testcase or explanation about it.
// But autofs works already, it shows nfs as mountType in mtab.
if (nfs || autofs) {
return true;
}
return false;
}
bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
{
const bool isMsDos = ( d->mountType == QLatin1String("msdos") || d->mountType == QLatin1String("fat") || d->mountType == QLatin1String("vfat") );
switch (flag) {
case SupportsChmod:
case SupportsChown:
case SupportsUTime:
case SupportsSymlinks:
return !isMsDos; // it's amazing the number of things FAT doesn't support :)
case CaseInsensitive:
return isMsDos;
}
return false;
}
diff --git a/kdecore/network/kidna.cpp b/kdecore/network/kidna.cpp
deleted file mode 100644
index 3a8d99559b..0000000000
--- a/kdecore/network/kidna.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- This file is part of the KDE libraries
-
- Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA.
-*/
-
-#include "kidna.h"
-
-#ifndef Q_WS_WIN //TODO kresolver not ported
-#include "k3resolver.h"
-#endif
-
-#ifndef Q_WS_WIN //TODO knetwork not ported
-using namespace KNetwork;
-#endif
-
-QByteArray KIDNA::toAsciiCString(const QString &idna)
-{
-#ifndef Q_WS_WIN //TODO kresolver not ported
- return KResolver::domainToAscii(idna);
-#else
- return QByteArray();
-#endif
-}
-
-QString KIDNA::toAscii(const QString &idna)
-{
- if (idna.length() && (idna[0] == QLatin1Char('.')))
- {
- QString host = QLatin1String(toAsciiCString(idna.mid(1)));
- if (host.isEmpty())
- return QString(); // Error
- return idna[0] + host;
- }
- return QLatin1String(toAsciiCString(idna));
-}
-
-QString KIDNA::toUnicode(const QString &idna)
-{
-#ifndef Q_WS_WIN //TODO kresolver not ported
- if (idna.length() && (idna[0] == QLatin1Char('.')))
- return idna[0] + KResolver::domainToUnicode(idna.mid(1));
- return KResolver::domainToUnicode(idna);
-#else
- return QString();
-#endif
-}
diff --git a/kdecore/network/kidna.h b/kdecore/network/kidna.h
deleted file mode 100644
index 7f35f98913..0000000000
--- a/kdecore/network/kidna.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- This file is part of the KDE libraries
-
- Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA.
-*/
-
-#error CHOKE
-#ifndef _KIDNA_H
-#define _KIDNA_H
-
-#include <kdecore_export.h>
-#include <QtCore/QString>
-
-namespace KIDNA {
- /**
- * Converts an International Domain Name @p idna to
- * its ASCII representation
- *
- * If conversion is not possible, an empty string is returned.
- */
- KDECORE_EXPORT QByteArray toAsciiCString(const QString &idna);
-
- /**
- * Converts an International Domain Name @p idna to
- * its ASCII representation
- *
- * If conversion is not possible, an empty string is returned.
- */
- KDECORE_EXPORT QString toAscii(const QString &idna);
-
- /**
- * Converts an International Domain Name @p idna to
- * its UNICODE representation
- */
- KDECORE_EXPORT QString toUnicode(const QString &idna);
-}
-
-#endif /* _KIDNA_H */
diff --git a/kdecore/tests/CMakeLists.txt b/kdecore/tests/CMakeLists.txt
index 0c1f1ba530..3433998490 100644
--- a/kdecore/tests/CMakeLists.txt
+++ b/kdecore/tests/CMakeLists.txt
@@ -1,174 +1,175 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${KDE4_KDECORE_INCLUDES} )
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
MACRO(KDECORE_UNIT_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_unit_test(${_testname} TESTNAME "kdecore-${_testname}" NOGUI ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTNETWORK_LIBRARY})
if(WINCE)
target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES})
endif(WINCE)
ENDFOREACH(_testname)
ENDMACRO(KDECORE_UNIT_TESTS)
MACRO(KDECORE_EXECUTABLE_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_executable(${_testname} NOGUI TEST ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
if(WINCE)
target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES})
endif(WINCE)
ENDFOREACH(_testname)
ENDMACRO(KDECORE_EXECUTABLE_TESTS)
########### next target ###############
KDECORE_UNIT_TESTS(
karchivetest
kdirwatch_unittest
klocaletimeformattest
klocalizedstringtest
kmountpointtest
kstandarddirstest
kaboutdatatest
kurltest
kstringhandlertest
cplusplustest
ksortablelisttest
kcharsetstest
kcalendartest
kmacroexpandertest
kshelltest
kasciitest
ktimezonestest
kentrymaptest
kconfigtest
kurlmimetest
klockfiletest
ktempdirtest
ksharedptrtest
kdatetimetest
ksavefiletest
kautosavefiletest
kdesktopfiletest
ktemporaryfiletest
kautostarttest
kjobtest
ksycocadicttest
kservicetest
kglobalstatictest
kglobaltest
globalcleanuptest
kprocesstest
kconfigafterkglobaltest1
kconfigafterkglobaltest2
ktcpsockettest
ksycocathreadtest
kdebug_unittest
kencodingdetectortest
qcoreapptest
kdebug_qcoreapptest
kmimetype_nomimetypes
)
if(NOT KDE_NO_DEPRECATED)
KDECORE_UNIT_TESTS(
klibloadertest
)
endif(NOT KDE_NO_DEPRECATED)
if(UNIX)
KDECORE_UNIT_TESTS(
klocalsockettest
klocalsocketservertest
)
endif(UNIX)
KDECORE_EXECUTABLE_TESTS(
kdirwatchtest
krandomsequencetest
ktartest
kziptest
kdebugtest
kcmdlineargstest
kmemtest
dbuscalltest
kmdcodectest
startserviceby
+ klockfile_testlock # helper for klockfiletest
)
########### klocaletest ###############
# compile into the test since it's not exported
set(klocaletest_SRCS klocaletest.cpp ../date/kdayperiod.cpp)
kde4_add_unit_test(klocaletest ${klocaletest_SRCS})
target_link_libraries(klocaletest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kdatetimeformattertest ###############
# compile KDateTimeFormatter and KDayPeriod into the test since it's not exported
set(kdatetimeformattertest_SRCS kdatetimeformattertest.cpp ../date/kdatetimeformatter.cpp ../date/kdayperiod.cpp)
kde4_add_unit_test(kdatetimeformattertest ${kdatetimeformattertest_SRCS})
target_link_libraries(kdatetimeformattertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kdirwatchtest_gui ###############
kde4_add_executable(kdirwatchtest_gui TEST kdirwatchtest_gui.cpp)
target_link_libraries(kdirwatchtest_gui ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY})
########### klimitediodevicetest ###############
kde4_add_unit_test(klimitediodevicetest TESTNAME kdecore-klimitediodevicetest klimitediodevicetest.cpp ../io/klimitediodevice.cpp)
target_link_libraries(klimitediodevicetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
########### kmimetypetest ###############
# compile kmimemagicrule.cpp into the test since it's not exported and we call match().
set(kmimetypetest_SRCS kmimetypetest.cpp ../services/kmimemagicrule.cpp)
kde4_add_unit_test(kmimetypetest TESTNAME kdecore-kmimetypetest ${kmimetypetest_SRCS})
target_link_libraries(kmimetypetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kmimeglobsfileparsertest ###############
# compile kmimeglobsfileparser.cpp into the test since it's not exported
set(kmimeglobsfileparsertest_SRCS kmimeglobsfileparsertest.cpp ../services/kmimeglobsfileparser.cpp)
kde4_add_unit_test(kmimeglobsfileparsertest TESTNAME kdecore-kmimeglobsfileparsertest ${kmimeglobsfileparsertest_SRCS})
target_link_libraries(kmimeglobsfileparsertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kfiltertest ###############
# compile httpfilter.cpp into the test since it's not part of kdelibs
# (only par of kio_http and kmultipart)
set(kfiltertest_SRCS kfiltertest.cpp ../../kio/httpfilter/httpfilter.cc)
include_directories( ${CMAKE_SOURCE_DIR}/kio/httpfilter )
kde4_add_unit_test(kfiltertest TESTNAME kdecore-kfiltertest ${kfiltertest_SRCS})
target_link_libraries(kfiltertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
target_link_libraries(kfiltertest ${ZLIB_LIBRARIES})
########### module for klibloadertest ###############
if(NOT KDE_NO_DEPRECATED)
set(klibloadertestmodule_PART_SRCS klibloadertest_module.cpp )
kde4_add_plugin(klibloadertestmodule ${klibloadertestmodule_PART_SRCS})
target_link_libraries(klibloadertestmodule ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
set_target_properties(klibloadertestmodule PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE)
endif(NOT KDE_NO_DEPRECATED)
########### module for klibloadertest4 ###############
if (NOT WIN32) # TODO: reenable for win32
set(klibloadertestmodule4_PART_SRCS klibloadertest4_module.cpp )
kde4_add_plugin(klibloadertestmodule4 ${klibloadertestmodule4_PART_SRCS})
target_link_libraries(klibloadertestmodule4 ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
set_target_properties(klibloadertestmodule4 PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE)
endif (NOT WIN32)
diff --git a/kdecore/tests/klockfile_testlock.cpp b/kdecore/tests/klockfile_testlock.cpp
new file mode 100644
index 0000000000..2ad1d879dd
--- /dev/null
+++ b/kdecore/tests/klockfile_testlock.cpp
@@ -0,0 +1,44 @@
+/* This file is part of the KDE libraries
+ Copyright (c) 2011 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <kcomponentdata.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klockfile.h>
+#include <kdebug.h>
+
+int
+main(int argc, char *argv[])
+{
+ KAboutData about("klockfile_testlock", 0, ki18n("klockfile_testlock"), "version");
+ KComponentData cData(&about);
+ //KCmdLineArgs::init(argc, argv, &about);
+ //KApplication a;
+
+ if (argc <= 1) {
+ return KLockFile::LockError;
+ }
+
+ const QString lockName = QString::fromLocal8Bit(argv[1]);
+
+ KLockFile lockFile(lockName);
+ if (lockFile.isLocked())
+ return KLockFile::LockError;
+ return lockFile.lock(KLockFile::NoBlockFlag);
+}
diff --git a/kdecore/tests/klockfiletest.cpp b/kdecore/tests/klockfiletest.cpp
index 32cdec907e..5a6eaf6a44 100644
--- a/kdecore/tests/klockfiletest.cpp
+++ b/kdecore/tests/klockfiletest.cpp
@@ -1,124 +1,145 @@
/* This file is part of the KDE libraries
Copyright (c) 2005 Thomas Braxton <brax108@cox.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "klockfiletest.h"
#include "klockfiletest.moc"
+#include <kdebug.h>
#include <unistd.h>
+#include <qtconcurrentrun.h>
namespace QTest {
template<>
char* toString(const KLockFile::LockResult& result)
{
static const char *const strings[] = {
"LockOK", "LockFail", "LockError", "LockStale"
};
return qstrdup(strings[result]);
}
}
+// Here's how to test file locking on a FAT filesystem, on linux:
+// cd /tmp
+// dd if=/dev/zero of=fatfile count=180000
+// mkfs.vfat -F32 fatfile
+// mkdir -p fatsystem
+// sudo mount -o loop -o uid=$UID fatfile fatsystem
+//
+//static const char *const lockName = "/tmp/fatsystem/klockfiletest.lock";
+
static const char *const lockName = "klockfiletest.lock";
void
Test_KLockFile::initTestCase()
{
QFile::remove( lockName );
lockFile = new KLockFile(QLatin1String(lockName));
}
+static KLockFile::LockResult testLockFromProcess(const QString& lockName)
+{
+ const int ret = QProcess::execute("./klockfile_testlock", QStringList() << lockName);
+ return KLockFile::LockResult(ret);
+}
+
void
Test_KLockFile::testLock()
{
QVERIFY(!lockFile->isLocked());
QCOMPARE(lockFile->lock(), KLockFile::LockOK);
QVERIFY(lockFile->isLocked());
-#ifdef Q_WS_WIN
+
+ // Try to lock it again, should fail
KLockFile *lockFile2 = new KLockFile(QLatin1String(lockName));
QVERIFY(!lockFile2->isLocked());
- QCOMPARE(lockFile2->lock(), KLockFile::LockFail);
+ QCOMPARE(lockFile2->lock(KLockFile::NoBlockFlag), KLockFile::LockFail);
QVERIFY(!lockFile2->isLocked());
delete lockFile2;
-#endif
+
+ // Also try from a different process.
+ QCOMPARE(testLockFromProcess(QLatin1String(lockName)), KLockFile::LockFail);
}
void
Test_KLockFile::testStale()
{
#ifdef Q_WS_WIN
qDebug("unix stale lock support not implemented yet");
#else
QVERIFY(lockFile->isLocked());
const int secs = 2;
KLockFile lf = KLockFile(QLatin1String(lockName));
lf.setStaleTime(secs);
QVERIFY(lf.staleTime() == secs);
QTest::qWait(secs*1000);
QCOMPARE(lf.lock(), KLockFile::LockStale);
QVERIFY(!lf.isLocked());
int pid;
QString host, app;
if (lf.getLockInfo(pid, host, app)) {
QCOMPARE(pid, ::getpid());
char hostname[256];
if (::gethostname(hostname, sizeof(hostname)) == 0)
QCOMPARE(host, QLatin1String(hostname));
QCOMPARE(app, QLatin1String("qttest")); // this is our KComponentData name
}
#endif
}
void
Test_KLockFile::testUnlock()
{
QVERIFY(lockFile->isLocked());
lockFile->unlock();
QVERIFY(!lockFile->isLocked());
}
void
Test_KLockFile::testStaleNoBlockFlag()
{
#ifdef Q_WS_WIN
QSKIP("lockfile on windows has different format",SkipSingle);
#else
char hostname[256];
::gethostname(hostname, sizeof(hostname));
-
+
QFile f(lockName);
f.open(QIODevice::WriteOnly);
QTextStream stream(&f);
stream << QString::number(111222) << endl << QLatin1String("qttest") << endl << hostname << endl;
stream.flush();
f.close();
lockFile = new KLockFile(QLatin1String(lockName));
QVERIFY(!lockFile->isLocked());
QCOMPARE(lockFile->lock(KLockFile::NoBlockFlag), KLockFile::LockStale);
- QTest::ignoreMessage(QtWarningMsg, "WARNING: deleting stale lockfile klockfiletest.lock");
+ QByteArray expectedMsg = QByteArray("WARNING: deleting stale lockfile ") + lockName;
+ QTest::ignoreMessage(QtWarningMsg, expectedMsg);
QCOMPARE(lockFile->lock(KLockFile::NoBlockFlag|KLockFile::ForceFlag), KLockFile::LockOK);
QVERIFY(lockFile->isLocked());
#endif
}
QTEST_KDEMAIN_CORE(Test_KLockFile)
diff --git a/kdeui/actions/kactioncollection.h b/kdeui/actions/kactioncollection.h
index 076088b977..bfc937fc92 100644
--- a/kdeui/actions/kactioncollection.h
+++ b/kdeui/actions/kactioncollection.h
@@ -1,412 +1,431 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Reginald Stadlbauer <reggie@kde.org>
(C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 2000 Nicolas Hadacek <haadcek@kde.org>
(C) 2000 Kurt Granroth <granroth@kde.org>
(C) 2000 Michael Koch <koch@kde.org>
(C) 2001 Holger Freyther <freyther@kde.org>
(C) 2002 Ellis Whitehead <ellis@kde.org>
(C) 2005-2006 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KACTIONCOLLECTION_H
#define KACTIONCOLLECTION_H
#include <kdeui_export.h>
#include <kstandardaction.h>
#include <kcomponentdata.h>
#include <QtCore/QObject>
class QAction;
class KXMLGUIClient;
class QActionGroup;
class QString;
/**
* \short A container for a set of QAction objects.
*
* KActionCollection manages a set of QAction objects. It
* allows them to be grouped for organized presentation of configuration to the user,
* saving + loading of configuration, and optionally for automatic plugging into
* specified widget(s).
*
* Additionally, KActionCollection provides several convenience functions for locating
* named actions, and actions grouped by QActionGroup.
*
* \note If you create your own action collection and need to assign shortcuts
* to the actions within, you have to call associateWidget() or
* addAssociatedWidget() to have them working.
*/
class KDEUI_EXPORT KActionCollection : public QObject
{
friend class KXMLGUIClient;
Q_OBJECT
Q_PROPERTY( QString configGroup READ configGroup WRITE setConfigGroup )
Q_PROPERTY( bool configIsGlobal READ configIsGlobal WRITE setConfigGlobal )
public:
/**
* Constructor. Allows specification of a KComponentData other than the default
* global KComponentData, where needed.
*/
explicit KActionCollection(QObject *parent, const KComponentData &cData = KComponentData());
/**
* Destructor.
*/
virtual ~KActionCollection();
/**
* Access the list of all action collections in existence for this app
*/
static const QList<KActionCollection*>& allCollections();
/**
* Clears the entire action collection, deleting all actions.
*/
void clear();
/**
* Associate all actions in this collection to the given \a widget.
* Unlike addAssociatedWidget, this method only adds all current actions
* in the collection to the given widget. Any action added after this call
* will not be added to the given widget automatically.
* So this is just a shortcut for a foreach loop and a widget->addAction call.
*/
void associateWidget(QWidget* widget) const;
/**
* Associate all actions in this collection to the given \a widget, including any actions
* added after this association is made.
*
* This does not change the action's shortcut context, so if you need to have the actions only
* trigger when the widget has focus, you'll need to set the shortcut context on each action
* to Qt::WidgetShortcut (or better still, Qt::WidgetWithChildrenShortcut with Qt 4.4+)
*/
void addAssociatedWidget(QWidget* widget);
/**
* Remove an association between all actions in this collection and the given \a widget, i.e.
* remove those actions from the widget, and stop associating newly added actions as well.
*/
void removeAssociatedWidget(QWidget* widget);
/**
* Return a list of all associated widgets.
*/
QList<QWidget*> associatedWidgets() const;
/**
* Clear all associated widgets and remove the actions from those widgets.
*/
void clearAssociatedWidgets();
/**
* Returns the KConfig group with which settings will be loaded and saved.
*/
QString configGroup() const;
/**
* Returns whether this action collection's configuration should be global to KDE ( \e true ),
* or specific to the application ( \e false ).
*/
bool configIsGlobal() const;
/**
* Sets \a group as the KConfig group with which settings will be loaded and saved.
*/
void setConfigGroup( const QString& group );
/**
* Set whether this action collection's configuration should be global to KDE ( \e true ),
* or specific to the application ( \e false ).
*/
void setConfigGlobal( bool global );
/**
* Read all key associations from @p config.
*
* If @p config is zero, read all key associations from the
* application's configuration file KGlobal::config(),
* in the group set by setConfigGroup().
*/
void readSettings( KConfigGroup* config = 0 );
/**
* Import from @p config all configurable global key associations.
*
* \since 4.1
*
* \param config Config object to read from
*/
void importGlobalShortcuts( KConfigGroup* config );
/**
* Export the current configurable global key associations to @p config.
*
* \since 4.1
*
* \param config Config object to save to
* \param writeDefaults set to true to write settings which are already at defaults.
*/
void exportGlobalShortcuts( KConfigGroup* config, bool writeDefaults = false ) const;
/**
* Write the current configurable key associations to @a config. What the
* function does if @a config is zero depends. If this action collection
* belongs to a KXMLGuiClient the setting are saved to the kxmlgui
* definition file. If not the settings are written to the applications
* config file.
*
* \note oneAction() and writeDefaults() have no meaning for the kxmlgui
* configuration file.
*
* \param config Config object to save to, or null (see above)
* \param writeDefaults set to true to write settings which are already at defaults.
* \param oneAction pass an action here if you just want to save the values for one action, eg.
* if you know that action is the only one which has changed.
*/
void writeSettings( KConfigGroup* config = 0, bool writeDefaults = false, QAction* oneAction = 0 ) const;
/**
* Returns the number of actions in the collection.
*
* This is equivalent to actions().count().
*/
int count() const;
/**
* Returns whether the action collection is empty or not.
*/
bool isEmpty() const;
/**
* Return the QAction* at position "index" in the action collection.
*
* This is equivalent to actions().value(index);
*/
QAction *action(int index) const;
/**
* Get the action with the given \a name from the action collection.
*
* @param name Name of the KAction
* @return A pointer to the KAction in the collection which matches the parameters or
* null if nothing matches.
*/
QAction* action( const QString& name ) const;
/**
* Returns the list of KActions which belong to this action collection.
*
* The list is guaranteed to be in the same order the action were put into
* the collection.
*/
QList<QAction*> actions() const;
/**
* Returns the list of KActions without an QAction::actionGroup() which belong to this action collection.
*/
const QList<QAction*> actionsWithoutGroup() const;
/**
* Returns the list of all QActionGroups associated with actions in this action collection.
*/
const QList<QActionGroup*> actionGroups() const;
/**
* Set the \a componentData associated with this action collection.
*
* \warning Don't call this method on a KActionCollection that contains
* actions. This is not supported.
*
* \param componentData the KComponentData which is to be associated with this action collection,
* or an invalid KComponentData instance to indicate the default KComponentData.
*/
void setComponentData(const KComponentData &componentData);
/** The KComponentData with which this class is associated. */
KComponentData componentData() const;
/**
* The parent KXMLGUIClient, or null if not available.
*/
const KXMLGUIClient *parentGUIClient() const;
Q_SIGNALS:
/**
* Indicates that \a action was inserted into this action collection.
*/
void inserted( QAction* action );
/**
* Indicates that \a action was removed from this action collection.
* @deprecated
*/
QT_MOC_COMPAT void removed( QAction* action );
/**
* Indicates that \a action was highlighted (hovered over).
* @deprecated Replaced by actionHovered(QAction* action);
*/
QT_MOC_COMPAT void actionHighlighted(QAction* action);
/**
* Indicates that \a action was hovered.
*/
void actionHovered(QAction* action);
/**
* Indicates that \a action was triggered
*/
void actionTriggered(QAction* action);
protected Q_SLOTS:
/// Overridden to perform connections when someone wants to know whether an action was highlighted or triggered
virtual void connectNotify ( const char * signal );
virtual void slotActionTriggered();
/**
* @internal
* @deprecated Replaced by slotActionHovered();
*/
QT_MOC_COMPAT virtual void slotActionHighlighted();
private Q_SLOTS:
void slotActionHovered();
public:
/**
* Add an action under the given name to the collection.
*
* Inserting an action that was previously inserted under a different name will replace the
* old entry, i.e. the action will not be available under the old name anymore but only under
* the new one.
*
* Inserting an action under a name that is already used for another action will replace
* the other action in the collection (but will not delete it).
*
* @param name The name by which the action be retrieved again from the collection.
* @param action The action to add.
* @return the same as the action given as parameter. This is just for convenience
* (chaining calls) and consistency with the other addAction methods, you can also
* simply ignore the return value.
*/
QAction *addAction(const QString &name, QAction *action);
KAction *addAction(const QString &name, KAction *action);
/**
* Removes an action from the collection and deletes it.
* @param action The action to remove.
*/
void removeAction(QAction *action);
/**
* Removes an action from the collection.
* @param action the action to remove.
*/
QAction* takeAction(QAction *action);
/**
* Creates a new standard action, adds it to the collection and connects the
* action's triggered(bool) signal to the specified receiver/member. The
* newly created action is also returned.
*
* Note: Using KStandardAction::OpenRecent will cause a different signal than
* triggered(bool) to be used, see KStandardAction for more information.
*
* The action can be retrieved later from the collection by its standard name as per
* KStandardAction::stdName.
+ *
+ * @param actionType The standard action type of the action to create.
+ * @param receiver The QObject to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @param member The SLOT to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @return new action of the given type ActionType.
*/
KAction *addAction(KStandardAction::StandardAction actionType, const QObject *receiver = 0, const char *member = 0);
/**
* Creates a new standard action, adds to the collection under the given name
* and connects the action's triggered(bool) signal to the specified
* receiver/member. The newly created action is also returned.
*
* Note: Using KStandardAction::OpenRecent will cause a different signal than
* triggered(bool) to be used, see KStandardAction for more information.
*
* The action can be retrieved later from the collection by the specified name.
+ *
+ * @param actionType The standard action type of the action to create.
+ * @param name The name by which the action be retrieved again from the collection.
+ * @param receiver The QObject to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @param member The SLOT to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @return new action of the given type ActionType.
*/
KAction *addAction(KStandardAction::StandardAction actionType, const QString &name,
const QObject *receiver = 0, const char *member = 0);
/**
* Creates a new action under the given name to the collection and connects
* the action's triggered(bool) signal to the specified receiver/member. The
* newly created action is returned.
*
* NOTE: KDE prior to 4.2 used the triggered() signal instead of the triggered(bool)
* signal.
*
* Inserting an action that was previously inserted under a different name will replace the
* old entry, i.e. the action will not be available under the old name anymore but only under
* the new one.
*
* Inserting an action under a name that is already used for another action will replace
* the other action in the collection.
*
* @param name The name by which the action be retrieved again from the collection.
- * @param action The action to add.
+ * @param receiver The QObject to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @param member The SLOT to connect the triggered(bool) signal to. Leave 0 if no
+ * connection is desired.
+ * @return new action of the given type ActionType.
*/
KAction *addAction(const QString &name, const QObject *receiver = 0, const char *member = 0);
/**
* Creates a new action under the given name, adds it to the collection and connects the action's triggered(bool)
* signal to the specified receiver/member. The receiver slot may accept either a bool or no
* parameters at all (i.e. slotTriggered(bool) or slotTriggered() ).
* The type of the action is specified by the template parameter ActionType.
*
* NOTE: KDE prior to 4.2 connected the triggered() signal instead of the triggered(bool)
* signal.
*
* @param name The internal name of the action (e.g. "file-open").
* @param receiver The QObject to connect the triggered(bool) signal to. Leave 0 if no
* connection is desired.
* @param member The SLOT to connect the triggered(bool) signal to. Leave 0 if no
* connection is desired.
* @return new action of the given type ActionType.
*/
template<class ActionType>
ActionType *add(const QString &name, const QObject *receiver = 0, const char *member = 0)
{
ActionType *a = new ActionType(this);
if (receiver && member)
connect(a, SIGNAL(triggered(bool)), receiver, member);
addAction(name, a);
return a;
}
private:
Q_PRIVATE_SLOT(d, void _k_actionDestroyed(QObject *))
Q_PRIVATE_SLOT(d, void _k_associatedWidgetDestroyed(QObject*))
KActionCollection( const KXMLGUIClient* parent ); // used by KXMLGUIClient
friend class KActionCollectionPrivate;
class KActionCollectionPrivate* const d;
};
#endif
diff --git a/kdeui/dialogs/kconfigdialogmanager.cpp b/kdeui/dialogs/kconfigdialogmanager.cpp
index 0209f49032..a0f6af2398 100644
--- a/kdeui/dialogs/kconfigdialogmanager.cpp
+++ b/kdeui/dialogs/kconfigdialogmanager.cpp
@@ -1,531 +1,533 @@
/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net)
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kconfigdialogmanager.h"
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QMetaObject>
#include <QMetaProperty>
#include <QTimer>
#include <QRadioButton>
//#include <QButtonGroup>
#include <kconfigskeleton.h>
#include <kdebug.h>
#include <kglobal.h>
#include <assert.h>
typedef QHash<QString, QByteArray> MyHash;
K_GLOBAL_STATIC(MyHash, s_propertyMap)
K_GLOBAL_STATIC(MyHash, s_changedMap)
class KConfigDialogManager::Private {
public:
Private(KConfigDialogManager *q) : q(q), insideGroupBox(false) { }
public:
KConfigDialogManager *q;
static int debugArea() { static int s_area = KDebug::registerArea("kdeui (KConfigDialogManager)"); return s_area; }
/**
* KConfigSkeleton object used to store settings
*/
KCoreConfigSkeleton *m_conf;
/**
* Dialog being managed
*/
QWidget *m_dialog;
QHash<QString, QWidget *> knownWidget;
QHash<QString, QWidget *> buddyWidget;
bool insideGroupBox : 1;
bool trackChanges : 1;
};
KConfigDialogManager::KConfigDialogManager(QWidget *parent, KCoreConfigSkeleton *conf)
: QObject(parent), d(new Private(this))
{
d->m_conf = conf;
d->m_dialog = parent;
init(true);
}
KConfigDialogManager::KConfigDialogManager(QWidget *parent, KConfigSkeleton *conf)
: QObject(parent), d(new Private(this))
{
d->m_conf = conf;
d->m_dialog = parent;
init(true);
}
KConfigDialogManager::~KConfigDialogManager()
{
delete d;
}
void KConfigDialogManager::initMaps()
{
if ( s_propertyMap->isEmpty() ) {
s_propertyMap->insert( "KButtonGroup", "current" );
s_propertyMap->insert( "KColorButton", "color" );
s_propertyMap->insert( "KColorCombo", "color" );
//s_propertyMap->insert( "KUrlRequester", "url" );
//s_propertyMap->insert( "KUrlComboRequester", "url" );
}
if( s_changedMap->isEmpty() )
{
// QT
s_changedMap->insert("QCheckBox", SIGNAL(stateChanged(int)));
s_changedMap->insert("QPushButton", SIGNAL(clicked(bool)));
s_changedMap->insert("QRadioButton", SIGNAL(toggled(bool)));
// We can only store one thing, so you can't have
// a ButtonGroup that is checkable.
// s_changedMap->insert("QButtonGroup", SIGNAL(buttonClicked(int)));
s_changedMap->insert("QGroupBox", SIGNAL(toggled(bool)));
s_changedMap->insert("QComboBox", SIGNAL(activated (int)));
//qsqlproperty map doesn't store the text, but the value!
//s_changedMap->insert("QComboBox", SIGNAL(textChanged(const QString &)));
s_changedMap->insert("QDateEdit", SIGNAL(dateChanged(const QDate &)));
s_changedMap->insert("QTimeEdit", SIGNAL(timeChanged(const QTime &)));
s_changedMap->insert("QDateTimeEdit", SIGNAL(dateTimeChanged(const QDateTime &)));
s_changedMap->insert("QDial", SIGNAL(valueChanged (int)));
s_changedMap->insert("QDoubleSpinBox", SIGNAL(valueChanged(double)));
s_changedMap->insert("QLineEdit", SIGNAL(textChanged(const QString &)));
s_changedMap->insert("QSlider", SIGNAL(valueChanged(int)));
s_changedMap->insert("QSpinBox", SIGNAL(valueChanged(int)));
s_changedMap->insert("QTextEdit", SIGNAL(textChanged()));
s_changedMap->insert("QTextBrowser", SIGNAL(sourceChanged(const QString &)));
s_changedMap->insert("QPlainTextEdit", SIGNAL(textChanged()));
s_changedMap->insert("QTabWidget", SIGNAL(currentChanged(int)));
// KDE
s_changedMap->insert( "KComboBox", SIGNAL(activated (int)));
s_changedMap->insert( "KFontComboBox", SIGNAL(activated (int)));
s_changedMap->insert( "KFontRequester", SIGNAL(fontSelected(const QFont &)));
s_changedMap->insert( "KFontChooser", SIGNAL(fontSelected(const QFont &)));
s_changedMap->insert( "KHistoryCombo", SIGNAL(activated (int)));
s_changedMap->insert( "KColorCombo", SIGNAL(activated (const QColor &)));
s_changedMap->insert( "KColorButton", SIGNAL(changed(const QColor &)));
s_changedMap->insert( "KDatePicker", SIGNAL(dateSelected (QDate)));
s_changedMap->insert( "KDateWidget", SIGNAL(changed (QDate)));
s_changedMap->insert( "KDateTimeWidget", SIGNAL(valueChanged (const QDateTime &)));
s_changedMap->insert( "KEditListBox", SIGNAL(changed()));
s_changedMap->insert( "KEditListWidget", SIGNAL(changed()));
s_changedMap->insert( "KListWidget", SIGNAL(itemSelectionChanged()));
s_changedMap->insert( "KLineEdit", SIGNAL(textChanged(const QString &)));
s_changedMap->insert( "KPasswordEdit", SIGNAL(textChanged(const QString &)));
s_changedMap->insert( "KRestrictedLine", SIGNAL(textChanged(const QString &)));
s_changedMap->insert( "KTextBrowser", SIGNAL(sourceChanged(const QString &)));
s_changedMap->insert( "KTextEdit", SIGNAL(textChanged()));
s_changedMap->insert( "KUrlRequester", SIGNAL(textChanged (const QString& )));
s_changedMap->insert( "KUrlComboRequester", SIGNAL(textChanged (const QString& )));
s_changedMap->insert( "KUrlComboBox", SIGNAL(urlActivated (const KUrl& )));
s_changedMap->insert( "KIntNumInput", SIGNAL(valueChanged (int)));
s_changedMap->insert( "KIntSpinBox", SIGNAL(valueChanged (int)));
s_changedMap->insert( "KDoubleNumInput", SIGNAL(valueChanged (double)));
s_changedMap->insert( "KButtonGroup", SIGNAL(changed(int)));
}
}
QHash<QString, QByteArray> *KConfigDialogManager::propertyMap()
{
initMaps();
return s_propertyMap;
}
QHash<QString, QByteArray> *KConfigDialogManager::changedMap()
{
initMaps();
return s_changedMap;
}
void KConfigDialogManager::init(bool trackChanges)
{
initMaps();
d->trackChanges = trackChanges;
// Go through all of the children of the widgets and find all known widgets
(void) parseChildren(d->m_dialog, trackChanges);
}
void KConfigDialogManager::addWidget(QWidget *widget)
{
(void) parseChildren(widget, true);
}
void KConfigDialogManager::setupWidget(QWidget *widget, KConfigSkeletonItem *item)
{
QVariant minValue = item->minValue();
if (minValue.isValid())
{
// Only q3datetimeedit is using this property we can remove it if we stop supporting Qt3Support
if (widget->metaObject()->indexOfProperty("minValue") != -1)
widget->setProperty("minValue", minValue);
if (widget->metaObject()->indexOfProperty("minimum") != -1)
widget->setProperty("minimum", minValue);
}
QVariant maxValue = item->maxValue();
if (maxValue.isValid())
{
// Only q3datetimeedit is using that property we can remove it if we stop supporting Qt3Support
if (widget->metaObject()->indexOfProperty("maxValue") != -1)
widget->setProperty("maxValue", maxValue);
if (widget->metaObject()->indexOfProperty("maximum") != -1)
widget->setProperty("maximum", maxValue);
}
if (widget->whatsThis().isEmpty())
{
QString whatsThis = item->whatsThis();
if ( !whatsThis.isEmpty() )
{
widget->setWhatsThis(whatsThis );
}
}
if (widget->toolTip().isEmpty())
{
QString toolTip = item->toolTip();
if ( !toolTip.isEmpty() )
{
widget->setToolTip(toolTip);
}
}
if(!item->isEqual( property(widget) ))
setProperty( widget, item->property() );
}
bool KConfigDialogManager::parseChildren(const QWidget *widget, bool trackChanges)
{
bool valueChanged = false;
const QList<QObject*> listOfChildren = widget->children();
if(listOfChildren.count()==0) //?? XXX
return valueChanged;
foreach ( QObject *object, listOfChildren )
{
if(!object->isWidgetType())
continue; // Skip non-widgets
QWidget *childWidget = static_cast<QWidget *>(object);
QString widgetName = childWidget->objectName();
bool bParseChildren = true;
bool bSaveInsideGroupBox = d->insideGroupBox;
if (widgetName.startsWith(QLatin1String("kcfg_")))
{
// This is one of our widgets!
QString configId = widgetName.mid(5);
KConfigSkeletonItem *item = d->m_conf->findItem(configId);
if (item)
{
d->knownWidget.insert(configId, childWidget);
setupWidget(childWidget, item);
if ( d->trackChanges ) {
QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap->constFind(childWidget->metaObject()->className());
if (changedIt == s_changedMap->constEnd())
{
// If the class name of the widget wasn't in the monitored widgets map, then look for
// it again using the super class name. This fixes a problem with using QtRuby/Korundum
// widgets with KConfigXT where 'Qt::Widget' wasn't being seen a the real deal, even
// though it was a 'QWidget'.
if ( childWidget->metaObject()->superClass() )
changedIt = s_changedMap->constFind(childWidget->metaObject()->superClass()->className());
else
changedIt = s_changedMap->constFind(0);
}
if (changedIt == s_changedMap->constEnd())
{
kWarning(d->debugArea()) << "Don't know how to monitor widget '" << childWidget->metaObject()->className() << "' for changes!";
}
else
{
connect(childWidget, *changedIt,
this, SIGNAL(widgetModified()));
QComboBox *cb = qobject_cast<QComboBox *>(childWidget);
if (cb && cb->isEditable())
connect(cb, SIGNAL(editTextChanged(const QString &)),
this, SIGNAL(widgetModified()));
}
}
QGroupBox *gb = qobject_cast<QGroupBox *>(childWidget);
if (!gb)
bParseChildren = false;
else
d->insideGroupBox = true;
}
else
{
kFatal(d->debugArea()) << "A widget named '" << widgetName << "' was found but there is no setting named '" << configId << "'";
}
}
else if (QLabel *label = qobject_cast<QLabel*>(childWidget))
{
QWidget *buddy = label->buddy();
if (!buddy)
continue;
QString buddyName = buddy->objectName();
if (buddyName.startsWith(QLatin1String("kcfg_")))
{
// This is one of our widgets!
QString configId = buddyName.mid(5);
d->buddyWidget.insert(configId, childWidget);
}
}
#ifndef NDEBUG
else if (!widgetName.isEmpty() && d->trackChanges)
{
QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap->constFind(childWidget->metaObject()->className());
if (changedIt != s_changedMap->constEnd())
{
if ((!d->insideGroupBox || !qobject_cast<QRadioButton*>(childWidget)) &&
!qobject_cast<QGroupBox*>(childWidget) &&!qobject_cast<QTabWidget*>(childWidget) )
kDebug(d->debugArea()) << "Widget '" << widgetName << "' (" << childWidget->metaObject()->className() << ") remains unmanaged.";
}
}
#endif
if(bParseChildren)
{
// this widget is not known as something we can store.
// Maybe we can store one of its children.
valueChanged |= parseChildren(childWidget, trackChanges);
}
d->insideGroupBox = bSaveInsideGroupBox;
}
return valueChanged;
}
void KConfigDialogManager::updateWidgets()
{
bool changed = false;
bool bSignalsBlocked = signalsBlocked();
blockSignals(true);
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget );
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item)
{
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
if(!item->isEqual( property(widget) ))
{
setProperty( widget, item->property() );
// kDebug(d->debugArea()) << "The setting '" << it.key() << "' [" << widget->className() << "] has changed";
changed = true;
}
if (item->isImmutable())
{
widget->setEnabled(false);
QWidget *buddy = d->buddyWidget.value(it.key(), 0);
if (buddy)
buddy->setEnabled(false);
}
}
blockSignals(bSignalsBlocked);
if (changed)
QTimer::singleShot(0, this, SIGNAL(widgetModified()));
}
void KConfigDialogManager::updateWidgetsDefault()
{
bool bUseDefaults = d->m_conf->useDefaults(true);
updateWidgets();
d->m_conf->useDefaults(bUseDefaults);
}
void KConfigDialogManager::updateSettings()
{
bool changed = false;
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget );
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item) {
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
QVariant fromWidget = property(widget);
if(!item->isEqual( fromWidget )) {
item->setProperty( fromWidget );
changed = true;
}
}
if (changed)
{
d->m_conf->writeConfig();
emit settingsChanged();
}
}
QByteArray KConfigDialogManager::getUserProperty(const QWidget *widget) const
{
if (!s_propertyMap->contains(widget->metaObject()->className())) {
const QMetaObject *metaObject = widget->metaObject();
const QMetaProperty user = metaObject->userProperty();
if ( user.isValid() ) {
s_propertyMap->insert( widget->metaObject()->className(), user.name() );
//kDebug(d->debugArea()) << "class name: '" << widget->metaObject()->className()
//<< " 's USER property: " << metaProperty.name() << endl;
}
else {
return QByteArray(); //no USER property
}
}
return s_propertyMap->value( widget->metaObject()->className() );
}
QByteArray KConfigDialogManager::getCustomProperty(const QWidget *widget) const
{
QVariant prop(widget->property("kcfg_property"));
if (prop.isValid()) {
if (!prop.canConvert(QVariant::ByteArray)) {
kWarning(d->debugArea()) << "kcfg_property on" << widget->metaObject()->className()
<< "is not of type ByteArray";
} else {
return prop.toByteArray();
}
}
return QByteArray();
}
void KConfigDialogManager::setProperty(QWidget *w, const QVariant &v)
{
/* QButtonGroup *bg = qobject_cast<QButtonGroup *>(w);
if (bg)
{
QAbstractButton *b = bg->button(v.toInt());
if (b)
b->setDown(true);
return;
}*/
QByteArray userproperty = getCustomProperty(w);
if (userproperty.isEmpty()) {
userproperty = getUserProperty(w);
}
if (userproperty.isEmpty()) {
QComboBox *cb = qobject_cast<QComboBox *>(w);
if (cb) {
if (cb->isEditable()) {
int i = cb->findText(v.toString());
if (i != -1) {
cb->setCurrentIndex(i);
} else {
cb->setEditText(v.toString());
}
} else {
cb->setCurrentIndex(v.toInt());
}
return;
}
kWarning(d->debugArea()) << w->metaObject()->className() << " widget not handled!";
return;
}
w->setProperty(userproperty, v);
}
QVariant KConfigDialogManager::property(QWidget *w) const
{
/* QButtonGroup *bg = qobject_cast<QButtonGroup *>(w);
if (bg && bg->checkedButton())
return QVariant(bg->id(bg->checkedButton()));*/
QByteArray userproperty = getCustomProperty(w);
- if (userproperty.isEmpty()) {
- userproperty = getUserProperty(w);
- }
if (userproperty.isEmpty()) {
QComboBox *cb = qobject_cast<QComboBox *>(w);
if (cb) {
if (cb->isEditable()) {
return QVariant(cb->currentText());
} else {
return QVariant(cb->currentIndex());
}
}
+ }
+ if (userproperty.isEmpty()) {
+ userproperty = getUserProperty(w);
+ }
+ if (userproperty.isEmpty()) {
kWarning(d->debugArea()) << w->metaObject()->className() << " widget not handled!";
return QVariant();
}
return w->property(userproperty);
}
bool KConfigDialogManager::hasChanged() const
{
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget) ;
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item) {
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
if(!item->isEqual( property(widget) )) {
// kDebug(d->debugArea()) << "Widget for '" << it.key() << "' has changed.";
return true;
}
}
return false;
}
bool KConfigDialogManager::isDefault() const
{
bool bUseDefaults = d->m_conf->useDefaults(true);
bool result = !hasChanged();
d->m_conf->useDefaults(bUseDefaults);
return result;
}
#include "kconfigdialogmanager.moc"
diff --git a/kfile/kdiroperator.cpp b/kfile/kdiroperator.cpp
index 8eb1819037..4c93ac9b2c 100644
--- a/kfile/kdiroperator.cpp
+++ b/kfile/kdiroperator.cpp
@@ -1,2662 +1,2662 @@
/* This file is part of the KDE libraries
Copyright (C) 1999,2000 Stephan Kulow <coolo@kde.org>
1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdiroperator.h"
#include <kprotocolmanager.h>
#include "kdirmodel.h"
#include "kdiroperatordetailview_p.h"
#include "kdirsortfilterproxymodel.h"
#include "kfileitem.h"
#include "kfilemetapreview.h"
#include "kpreviewwidgetbase.h"
#include "knewfilemenu.h"
#include <config-kfile.h>
#include <unistd.h>
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QtCore/QTimer>
#include <QtCore/QAbstractItemModel>
#include <QtGui/QApplication>
#include <QtGui/QDialog>
#include <QtGui/QHeaderView>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#include <QtGui/QListView>
#include <QtGui/QMouseEvent>
#include <QtGui/QTreeView>
#include <QtGui/QPushButton>
#include <QtGui/QProgressBar>
#include <QtGui/QScrollBar>
#include <QtGui/QSplitter>
#include <QtGui/QWheelEvent>
#include <kaction.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kdialog.h>
#include <kdirlister.h>
#include <kfileitemdelegate.h>
#include <kicon.h>
#include <kinputdialog.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmenu.h>
#include <kstandardaction.h>
#include <kio/job.h>
#include <kio/deletejob.h>
#include <kio/copyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/jobclasses.h>
#include <kio/netaccess.h>
#include <kio/previewjob.h>
#include <kio/renamedialog.h>
#include <kfilepreviewgenerator.h>
#include <krun.h>
#include <kpropertiesdialog.h>
#include <kstandardshortcut.h>
#include <kde_file.h>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <kconfiggroup.h>
#include <kdeversion.h>
template class QHash<QString, KFileItem>;
// QDir::SortByMask is not only undocumented, it also omits QDir::Type which is another
// sorting mode.
static const int QDirSortMask = QDir::SortByMask | QDir::Type;
/**
* Default icon view for KDirOperator using
* custom view options.
*/
class KDirOperatorIconView : public QListView
{
public:
KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent = 0);
virtual ~KDirOperatorIconView();
protected:
virtual QStyleOptionViewItem viewOptions() const;
virtual void dragEnterEvent(QDragEnterEvent* event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void wheelEvent(QWheelEvent *event);
private:
KDirOperator *ops;
};
KDirOperatorIconView::KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent) :
QListView(parent),
ops(dirOperator)
{
setViewMode(QListView::IconMode);
setFlow(QListView::TopToBottom);
setResizeMode(QListView::Adjust);
setSpacing(0);
setMovement(QListView::Static);
setDragDropMode(QListView::DragOnly);
setVerticalScrollMode(QListView::ScrollPerPixel);
setHorizontalScrollMode(QListView::ScrollPerPixel);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setWordWrap(true);
setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
}
KDirOperatorIconView::~KDirOperatorIconView()
{
}
QStyleOptionViewItem KDirOperatorIconView::viewOptions() const
{
QStyleOptionViewItem viewOptions = QListView::viewOptions();
viewOptions.showDecorationSelected = true;
viewOptions.decorationPosition = ops->decorationPosition();
if (viewOptions.decorationPosition == QStyleOptionViewItem::Left) {
viewOptions.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter;
} else {
viewOptions.displayAlignment = Qt::AlignCenter;
}
return viewOptions;
}
void KDirOperatorIconView::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void KDirOperatorIconView::mousePressEvent(QMouseEvent *event)
{
if (!indexAt(event->pos()).isValid()) {
const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) {
clearSelection();
}
}
QListView::mousePressEvent(event);
}
void KDirOperatorIconView::wheelEvent(QWheelEvent *event)
{
QListView::wheelEvent(event);
// apply the vertical wheel event to the horizontal scrollbar, as
// the items are aligned from left to right
if (event->orientation() == Qt::Vertical) {
QWheelEvent horizEvent(event->pos(),
event->delta(),
event->buttons(),
event->modifiers(),
Qt::Horizontal);
QApplication::sendEvent(horizontalScrollBar(), &horizEvent);
}
}
void KDirOperator::keyPressEvent(QKeyEvent *e)
{
if (!(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )) {
QWidget::keyPressEvent(e);
}
}
class KDirOperator::Private
{
public:
Private( KDirOperator *parent );
~Private();
enum InlinePreviewState {
ForcedToFalse = 0,
ForcedToTrue,
NotForced
};
// private methods
bool checkPreviewInternal() const;
void checkPath(const QString &txt, bool takeFiles = false);
bool openUrl(const KUrl &url, KDirLister::OpenUrlFlags flags = KDirLister::NoFlags);
int sortColumn() const;
Qt::SortOrder sortOrder() const;
void updateSorting(QDir::SortFlags sort);
static bool isReadable(const KUrl &url);
KFile::FileView allViews();
// private slots
void _k_slotDetailedView();
void _k_slotSimpleView();
void _k_slotTreeView();
void _k_slotDetailedTreeView();
void _k_slotToggleHidden(bool);
void _k_togglePreview(bool);
void _k_toggleInlinePreviews(bool);
void _k_slotOpenFileManager();
void _k_slotSortByName();
void _k_slotSortBySize();
void _k_slotSortByDate();
void _k_slotSortByType();
void _k_slotSortReversed(bool doReverse);
void _k_slotToggleDirsFirst();
void _k_slotToggleIgnoreCase();
void _k_slotStarted();
void _k_slotProgress(int);
void _k_slotShowProgress();
void _k_slotIOFinished();
void _k_slotCanceled();
void _k_slotRedirected(const KUrl&);
void _k_slotProperties();
void _k_slotActivated(const QModelIndex&);
void _k_slotSelectionChanged();
void _k_openContextMenu(const QPoint&);
void _k_triggerPreview(const QModelIndex&);
void _k_showPreview();
void _k_slotSplitterMoved(int, int);
void _k_assureVisibleSelection();
void _k_synchronizeSortingState(int, Qt::SortOrder);
void _k_slotChangeDecorationPosition();
void _k_slotExpandToUrl(const QModelIndex&);
void _k_slotItemsChanged();
void _k_slotDirectoryCreated(const KUrl&);
void updateListViewGrid();
int iconSizeForViewType(QAbstractItemView *itemView) const;
// private members
KDirOperator *parent;
QStack<KUrl*> backStack; ///< Contains all URLs you can reach with the back button.
QStack<KUrl*> forwardStack; ///< Contains all URLs you can reach with the forward button.
QModelIndex lastHoveredIndex;
KDirLister *dirLister;
KUrl currUrl;
KCompletion completion;
KCompletion dirCompletion;
bool completeListDirty;
QDir::SortFlags sorting;
QStyleOptionViewItem::Position decorationPosition;
QSplitter *splitter;
QAbstractItemView *itemView;
KDirModel *dirModel;
KDirSortFilterProxyModel *proxyModel;
KFileItemList pendingMimeTypes;
// the enum KFile::FileView as an int
int viewKind;
int defaultView;
KFile::Modes mode;
QProgressBar *progressBar;
KPreviewWidgetBase *preview;
KUrl previewUrl;
int previewWidth;
bool dirHighlighting;
bool onlyDoubleClickSelectsFiles;
QString lastURL; // used for highlighting a directory on cdUp
QTimer *progressDelayTimer;
int dropOptions;
KActionMenu *actionMenu;
KActionCollection *actionCollection;
KNewFileMenu *newFileMenu;
KConfigGroup *configGroup;
KFilePreviewGenerator *previewGenerator;
bool showPreviews;
int iconsZoom;
bool isSaving;
KActionMenu *decorationMenu;
KToggleAction *leftAction;
KUrl::List itemsToBeSetAsCurrent;
bool shouldFetchForItems;
InlinePreviewState inlinePreviewState;
};
KDirOperator::Private::Private(KDirOperator *_parent) :
parent(_parent),
dirLister(0),
decorationPosition(QStyleOptionViewItem::Left),
splitter(0),
itemView(0),
dirModel(0),
proxyModel(0),
progressBar(0),
preview(0),
previewUrl(),
previewWidth(0),
dirHighlighting(false),
onlyDoubleClickSelectsFiles(!KGlobalSettings::singleClick()),
progressDelayTimer(0),
dropOptions(0),
actionMenu(0),
actionCollection(0),
newFileMenu(0),
configGroup(0),
previewGenerator(0),
showPreviews(false),
iconsZoom(0),
isSaving(false),
decorationMenu(0),
leftAction(0),
shouldFetchForItems(false),
inlinePreviewState(NotForced)
{
}
KDirOperator::Private::~Private()
{
delete itemView;
itemView = 0;
// TODO:
// if (configGroup) {
// itemView->writeConfig(configGroup);
// }
qDeleteAll(backStack);
qDeleteAll(forwardStack);
delete preview;
preview = 0;
delete proxyModel;
proxyModel = 0;
delete dirModel;
dirModel = 0;
dirLister = 0; // deleted by KDirModel
delete configGroup;
configGroup = 0;
delete progressDelayTimer;
progressDelayTimer = 0;
}
KDirOperator::KDirOperator(const KUrl& _url, QWidget *parent) :
QWidget(parent),
d(new Private(this))
{
d->splitter = new QSplitter(this);
d->splitter->setChildrenCollapsible(false);
connect(d->splitter, SIGNAL(splitterMoved(int, int)),
this, SLOT(_k_slotSplitterMoved(int, int)));
d->preview = 0;
d->mode = KFile::File;
d->viewKind = KFile::Simple;
if (_url.isEmpty()) { // no dir specified -> current dir
QString strPath = QDir::currentPath();
strPath.append(QChar('/'));
d->currUrl = KUrl();
d->currUrl.setProtocol(QLatin1String("file"));
d->currUrl.setPath(strPath);
} else {
d->currUrl = _url;
if (d->currUrl.protocol().isEmpty())
d->currUrl.setProtocol(QLatin1String("file"));
d->currUrl.addPath("/"); // make sure we have a trailing slash!
}
// We set the direction of this widget to LTR, since even on RTL desktops
// viewing directory listings in RTL mode makes people's head explode.
// Is this the correct place? Maybe it should be in some lower level widgets...?
setLayoutDirection(Qt::LeftToRight);
setDirLister(new KDirLister());
connect(&d->completion, SIGNAL(match(const QString&)),
SLOT(slotCompletionMatch(const QString&)));
d->progressBar = new QProgressBar(this);
d->progressBar->setObjectName("d->progressBar");
d->progressBar->adjustSize();
d->progressBar->move(2, height() - d->progressBar->height() - 2);
d->progressDelayTimer = new QTimer(this);
d->progressDelayTimer->setObjectName(QLatin1String("d->progressBar delay timer"));
connect(d->progressDelayTimer, SIGNAL(timeout()),
SLOT(_k_slotShowProgress()));
d->completeListDirty = false;
// action stuff
setupActions();
setupMenu();
d->sorting = QDir::NoSort; //so updateSorting() doesn't think nothing has changed
d->updateSorting(QDir::Name | QDir::DirsFirst);
setFocusPolicy(Qt::WheelFocus);
}
KDirOperator::~KDirOperator()
{
resetCursor();
disconnect(d->dirLister, 0, this, 0);
delete d;
}
void KDirOperator::setSorting(QDir::SortFlags spec)
{
d->updateSorting(spec);
}
QDir::SortFlags KDirOperator::sorting() const
{
return d->sorting;
}
bool KDirOperator::isRoot() const
{
#ifdef Q_WS_WIN
if (url().isLocalFile()) {
const QString path = url().toLocalFile();
if (path.length() == 3)
return (path[0].isLetter() && path[1] == ':' && path[2] == '/');
return false;
} else
#endif
return url().path() == QString(QLatin1Char('/'));
}
KDirLister *KDirOperator::dirLister() const
{
return d->dirLister;
}
void KDirOperator::resetCursor()
{
if (qApp)
QApplication::restoreOverrideCursor();
d->progressBar->hide();
}
void KDirOperator::sortByName()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Name);
}
void KDirOperator::sortBySize()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Size);
}
void KDirOperator::sortByDate()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Time);
}
void KDirOperator::sortByType()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Type);
}
void KDirOperator::sortReversed()
{
// toggle it, hence the inversion of current state
d->_k_slotSortReversed(!(d->sorting & QDir::Reversed));
}
void KDirOperator::toggleDirsFirst()
{
d->_k_slotToggleDirsFirst();
}
void KDirOperator::toggleIgnoreCase()
{
if (d->proxyModel != 0) {
Qt::CaseSensitivity cs = d->proxyModel->sortCaseSensitivity();
cs = (cs == Qt::CaseSensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive;
d->proxyModel->setSortCaseSensitivity(cs);
}
}
void KDirOperator::updateSelectionDependentActions()
{
const bool hasSelection = (d->itemView != 0) &&
d->itemView->selectionModel()->hasSelection();
d->actionCollection->action("trash")->setEnabled(hasSelection);
d->actionCollection->action("delete")->setEnabled(hasSelection);
d->actionCollection->action("properties")->setEnabled(hasSelection);
}
void KDirOperator::setPreviewWidget(KPreviewWidgetBase *w)
{
const bool showPreview = (w != 0);
if (showPreview) {
d->viewKind = (d->viewKind | KFile::PreviewContents);
} else {
d->viewKind = (d->viewKind & ~KFile::PreviewContents);
}
delete d->preview;
d->preview = w;
if (w) {
d->splitter->addWidget(w);
}
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
previewAction->setEnabled(showPreview);
previewAction->setChecked(showPreview);
setView(static_cast<KFile::FileView>(d->viewKind));
}
KFileItemList KDirOperator::selectedItems() const
{
KFileItemList itemList;
if (d->itemView == 0) {
return itemList;
}
const QItemSelection selection = d->proxyModel->mapSelectionToSource(d->itemView->selectionModel()->selection());
const QModelIndexList indexList = selection.indexes();
foreach(const QModelIndex &index, indexList) {
KFileItem item = d->dirModel->itemForIndex(index);
if (!item.isNull()) {
itemList.append(item);
}
}
return itemList;
}
bool KDirOperator::isSelected(const KFileItem &item) const
{
if ((item.isNull()) || (d->itemView == 0)) {
return false;
}
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex);
return d->itemView->selectionModel()->isSelected(proxyIndex);
}
int KDirOperator::numDirs() const
{
return (d->dirLister == 0) ? 0 : d->dirLister->directories().count();
}
int KDirOperator::numFiles() const
{
return (d->dirLister == 0) ? 0 : d->dirLister->items().count() - numDirs();
}
KCompletion * KDirOperator::completionObject() const
{
return const_cast<KCompletion *>(&d->completion);
}
KCompletion *KDirOperator::dirCompletionObject() const
{
return const_cast<KCompletion *>(&d->dirCompletion);
}
KActionCollection * KDirOperator::actionCollection() const
{
return d->actionCollection;
}
KFile::FileView KDirOperator::Private::allViews() {
return static_cast<KFile::FileView>(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree);
}
void KDirOperator::Private::_k_slotDetailedView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Detail);
parent->setView(view);
}
void KDirOperator::Private::_k_slotSimpleView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Simple);
parent->setView(view);
}
void KDirOperator::Private::_k_slotTreeView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Tree);
parent->setView(view);
}
void KDirOperator::Private::_k_slotDetailedTreeView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::DetailTree);
parent->setView(view);
}
void KDirOperator::Private::_k_slotToggleHidden(bool show)
{
dirLister->setShowingDotFiles(show);
parent->updateDir();
_k_assureVisibleSelection();
}
void KDirOperator::Private::_k_togglePreview(bool on)
{
if (on) {
viewKind = viewKind | KFile::PreviewContents;
if (preview == 0) {
preview = new KFileMetaPreview(parent);
actionCollection->action("preview")->setChecked(true);
splitter->addWidget(preview);
}
preview->show();
QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection);
if (itemView != 0) {
const QModelIndex index = itemView->selectionModel()->currentIndex();
if (index.isValid()) {
_k_triggerPreview(index);
}
}
} else if (preview != 0) {
viewKind = viewKind & ~KFile::PreviewContents;
preview->hide();
}
}
void KDirOperator::Private::_k_toggleInlinePreviews(bool show)
{
if (showPreviews == show) {
return;
}
showPreviews = show;
if (!previewGenerator) {
return;
}
previewGenerator->setPreviewShown(show);
if (!show) {
// remove all generated previews
QAbstractItemModel *model = dirModel;
for (int i = 0; i < model->rowCount(); ++i) {
QModelIndex index = model->index(i, 0);
const KFileItem item = dirModel->itemForIndex(index);
const_cast<QAbstractItemModel*>(index.model())->setData(index, KIcon(item.iconName()), Qt::DecorationRole);
}
}
}
void KDirOperator::Private::_k_slotOpenFileManager()
{
new KRun(currUrl, parent);
}
void KDirOperator::Private::_k_slotSortByName()
{
parent->sortByName();
}
void KDirOperator::Private::_k_slotSortBySize()
{
parent->sortBySize();
}
void KDirOperator::Private::_k_slotSortByDate()
{
parent->sortByDate();
}
void KDirOperator::Private::_k_slotSortByType()
{
parent->sortByType();
}
void KDirOperator::Private::_k_slotSortReversed(bool doReverse)
{
QDir::SortFlags s = sorting & ~QDir::Reversed;
if (doReverse) {
s |= QDir::Reversed;
}
updateSorting(s);
}
void KDirOperator::Private::_k_slotToggleDirsFirst()
{
QDir::SortFlags s = (sorting ^ QDir::DirsFirst);
updateSorting(s);
}
void KDirOperator::Private::_k_slotToggleIgnoreCase()
{
// TODO: port to Qt4's QAbstractItemView
/*if ( !d->fileView )
return;
QDir::SortFlags sorting = d->fileView->sorting();
if ( !KFile::isSortCaseInsensitive( sorting ) )
d->fileView->setSorting( sorting | QDir::IgnoreCase );
else
d->fileView->setSorting( sorting & ~QDir::IgnoreCase );
d->sorting = d->fileView->sorting();*/
}
void KDirOperator::mkdir()
{
d->newFileMenu->setPopupFiles(url());
d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles());
d->newFileMenu->createDirectory();
}
bool KDirOperator::mkdir(const QString& directory, bool enterDirectory)
{
// Creates "directory", relative to the current directory (d->currUrl).
// The given path may contain any number directories, existent or not.
// They will all be created, if possible.
bool writeOk = false;
bool exists = false;
KUrl url(d->currUrl);
const QStringList dirs = directory.split('/', QString::SkipEmptyParts);
QStringList::ConstIterator it = dirs.begin();
for (; it != dirs.end(); ++it) {
url.addPath(*it);
exists = KIO::NetAccess::exists(url, KIO::NetAccess::DestinationSide, 0);
writeOk = !exists && KIO::NetAccess::mkdir(url, topLevelWidget());
}
if (exists) { // url was already existent
KMessageBox::sorry(d->itemView, i18n("A file or folder named %1 already exists.", url.pathOrUrl()));
} else if (!writeOk) {
KMessageBox::sorry(d->itemView, i18n("You do not have permission to "
"create that folder."));
} else if (enterDirectory) {
setUrl(url, true);
}
return writeOk;
}
KIO::DeleteJob * KDirOperator::del(const KFileItemList& items,
QWidget *parent,
bool ask, bool showProgress)
{
if (items.isEmpty()) {
KMessageBox::information(parent,
i18n("You did not select a file to delete."),
i18n("Nothing to Delete"));
return 0L;
}
if (parent == 0) {
parent = this;
}
KUrl::List urls;
QStringList files;
foreach (const KFileItem &item, items) {
const KUrl url = item.url();
urls.append(url);
files.append(url.pathOrUrl());
}
bool doIt = !ask;
if (ask) {
int ret;
if (items.count() == 1) {
ret = KMessageBox::warningContinueCancel(parent,
i18n("<qt>Do you really want to delete\n <b>'%1'</b>?</qt>" ,
files.first()),
i18n("Delete File"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(), "AskForDelete");
} else
ret = KMessageBox::warningContinueCancelList(parent,
i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", items.count()),
files,
i18n("Delete Files"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(), "AskForDelete");
doIt = (ret == KMessageBox::Continue);
}
if (doIt) {
KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::DeleteJob *job = KIO::del(urls, flags);
job->ui()->setWindow(topLevelWidget());
job->ui()->setAutoErrorHandlingEnabled(true);
return job;
}
return 0L;
}
void KDirOperator::deleteSelected()
{
const KFileItemList list = selectedItems();
if (!list.isEmpty()) {
del(list, this);
}
}
KIO::CopyJob * KDirOperator::trash(const KFileItemList& items,
QWidget *parent,
bool ask, bool showProgress)
{
if (items.isEmpty()) {
KMessageBox::information(parent,
i18n("You did not select a file to trash."),
i18n("Nothing to Trash"));
return 0L;
}
KUrl::List urls;
QStringList files;
foreach (const KFileItem &item, items) {
const KUrl url = item.url();
urls.append(url);
files.append(url.pathOrUrl());
}
bool doIt = !ask;
if (ask) {
int ret;
if (items.count() == 1) {
ret = KMessageBox::warningContinueCancel(parent,
i18n("<qt>Do you really want to trash\n <b>'%1'</b>?</qt>" ,
files.first()),
i18n("Trash File"),
KGuiItem(i18nc("to trash", "&Trash"), "user-trash"),
KStandardGuiItem::cancel(), "AskForTrash");
} else
ret = KMessageBox::warningContinueCancelList(parent,
i18np("translators: not called for n == 1", "Do you really want to trash these %1 items?", items.count()),
files,
i18n("Trash Files"),
KGuiItem(i18nc("to trash", "&Trash"), "user-trash"),
KStandardGuiItem::cancel(), "AskForTrash");
doIt = (ret == KMessageBox::Continue);
}
if (doIt) {
KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::CopyJob *job = KIO::trash(urls, flags);
job->ui()->setWindow(topLevelWidget());
job->ui()->setAutoErrorHandlingEnabled(true);
return job;
}
return 0L;
}
KFilePreviewGenerator *KDirOperator::previewGenerator() const
{
return d->previewGenerator;
}
void KDirOperator::setInlinePreviewShown(bool show)
{
d->inlinePreviewState = show ? Private::ForcedToTrue : Private::ForcedToFalse;
}
bool KDirOperator::isInlinePreviewShown() const
{
return d->showPreviews;
}
int KDirOperator::iconsZoom() const
{
return d->iconsZoom;
}
void KDirOperator::setIsSaving(bool isSaving)
{
d->isSaving = isSaving;
}
bool KDirOperator::isSaving() const
{
return d->isSaving;
}
void KDirOperator::trashSelected()
{
if (d->itemView == 0) {
return;
}
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
deleteSelected();
return;
}
const KFileItemList list = selectedItems();
if (!list.isEmpty()) {
trash(list, this);
}
}
void KDirOperator::setIconsZoom(int _value)
{
if (d->iconsZoom == _value) {
return;
}
int value = _value;
value = qMin(100, value);
value = qMax(0, value);
d->iconsZoom = value;
if (d->configGroup && d->inlinePreviewState == Private::NotForced) {
if (qobject_cast<QListView*>(d->itemView)) {
d->configGroup->writeEntry("listViewIconSize", d->iconsZoom);
} else {
d->configGroup->writeEntry("detailedViewIconSize", d->iconsZoom);
}
}
if (!d->previewGenerator) {
return;
}
const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
const int val = (maxSize * value / 100) + KIconLoader::SizeSmall;
d->itemView->setIconSize(QSize(val, val));
d->updateListViewGrid();
d->previewGenerator->updatePreviews();
emit currentIconSizeChanged(value);
}
void KDirOperator::close()
{
resetCursor();
d->pendingMimeTypes.clear();
d->completion.clear();
d->dirCompletion.clear();
d->completeListDirty = true;
d->dirLister->stop();
}
void KDirOperator::Private::checkPath(const QString &, bool /*takeFiles*/) // SLOT
{
#if 0
// copy the argument in a temporary string
QString text = _txt;
// it's unlikely to happen, that at the beginning are spaces, but
// for the end, it happens quite often, I guess.
text = text.trimmed();
// if the argument is no URL (the check is quite fragil) and it's
// no absolute path, we add the current directory to get a correct url
if (text.find(':') < 0 && text[0] != '/')
text.insert(0, d->currUrl);
// in case we have a selection defined and someone patched the file-
// name, we check, if the end of the new name is changed.
if (!selection.isNull()) {
int position = text.lastIndexOf('/');
ASSERT(position >= 0); // we already inserted the current d->dirLister in case
QString filename = text.mid(position + 1, text.length());
if (filename != selection)
selection.clear();
}
KUrl u(text); // I have to take care of entered URLs
bool filenameEntered = false;
if (u.isLocalFile()) {
// the empty path is kind of a hack
KFileItem i("", u.toLocalFile());
if (i.isDir())
setUrl(text, true);
else {
if (takeFiles)
if (acceptOnlyExisting && !i.isFile())
warning("you entered an invalid URL");
else
filenameEntered = true;
}
} else
setUrl(text, true);
if (filenameEntered) {
filename_ = u.url();
emit fileSelected(filename_);
QApplication::restoreOverrideCursor();
accept();
}
#endif
kDebug(kfile_area) << "TODO KDirOperator::checkPath()";
}
void KDirOperator::setUrl(const KUrl& _newurl, bool clearforward)
{
KUrl newurl;
if (!_newurl.isValid())
newurl.setPath(QDir::homePath());
else
newurl = _newurl;
newurl.adjustPath( KUrl::AddTrailingSlash );
#ifdef Q_WS_WIN
QString pathstr = QDir::fromNativeSeparators(newurl.toLocalFile());
#else
QString pathstr = newurl.path();
#endif
newurl.setPath(pathstr);
// already set
if (newurl.equals(d->currUrl, KUrl::CompareWithoutTrailingSlash))
return;
if (!Private::isReadable(newurl)) {
// maybe newurl is a file? check its parent directory
newurl.setPath(newurl.directory(KUrl::ObeyTrailingSlash));
if (newurl.equals(d->currUrl, KUrl::CompareWithoutTrailingSlash))
return; // parent is current dir, nothing to do (fixes #173454, too)
KIO::UDSEntry entry;
bool res = KIO::NetAccess::stat(newurl, entry, this);
KFileItem i(entry, newurl);
if ((!res || !Private::isReadable(newurl)) && i.isDir()) {
resetCursor();
KMessageBox::error(d->itemView,
i18n("The specified folder does not exist "
"or was not readable."));
return;
} else if (!i.isDir()) {
return;
}
}
if (clearforward) {
// autodelete should remove this one
d->backStack.push(new KUrl(d->currUrl));
qDeleteAll(d->forwardStack);
d->forwardStack.clear();
}
d->lastURL = d->currUrl.url(KUrl::RemoveTrailingSlash);
d->currUrl = newurl;
pathChanged();
emit urlEntered(newurl);
// enable/disable actions
QAction* forwardAction = d->actionCollection->action("forward");
forwardAction->setEnabled(!d->forwardStack.isEmpty());
QAction* backAction = d->actionCollection->action("back");
backAction->setEnabled(!d->backStack.isEmpty());
QAction* upAction = d->actionCollection->action("up");
upAction->setEnabled(!isRoot());
d->openUrl(newurl);
}
void KDirOperator::updateDir()
{
QApplication::setOverrideCursor(Qt::WaitCursor);
d->dirLister->emitChanges();
QApplication::restoreOverrideCursor();
}
void KDirOperator::rereadDir()
{
pathChanged();
d->openUrl(d->currUrl, KDirLister::Reload);
}
bool KDirOperator::Private::openUrl(const KUrl& url, KDirLister::OpenUrlFlags flags)
{
const bool result = KProtocolManager::supportsListing(url) && dirLister->openUrl(url, flags);
if (!result) // in that case, neither completed() nor canceled() will be emitted by KDL
_k_slotCanceled();
return result;
}
int KDirOperator::Private::sortColumn() const
{
int column = KDirModel::Name;
if (KFile::isSortByDate(sorting)) {
column = KDirModel::ModifiedTime;
} else if (KFile::isSortBySize(sorting)) {
column = KDirModel::Size;
} else if (KFile::isSortByType(sorting)) {
column = KDirModel::Type;
} else {
Q_ASSERT(KFile::isSortByName(sorting));
}
return column;
}
Qt::SortOrder KDirOperator::Private::sortOrder() const
{
return (sorting & QDir::Reversed) ? Qt::DescendingOrder :
Qt::AscendingOrder;
}
void KDirOperator::Private::updateSorting(QDir::SortFlags sort)
{
kDebug(kfile_area) << "changing sort flags from" << sorting << "to" << sort;
if (sort == sorting) {
return;
}
if ((sorting ^ sort) & QDir::DirsFirst) {
// The "Folders First" setting has been changed.
// We need to make sure that the files and folders are really re-sorted.
// Without the following intermediate "fake resorting",
// QSortFilterProxyModel::sort(int column, Qt::SortOrder order)
// would do nothing because neither the column nor the sort order have been changed.
Qt::SortOrder tmpSortOrder = (sortOrder() == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder);
proxyModel->sort(sortOrder(), tmpSortOrder);
proxyModel->setSortFoldersFirst(sort & QDir::DirsFirst);
}
sorting = sort;
parent->updateSortActions();
proxyModel->sort(sortColumn(), sortOrder());
// TODO: The headers from QTreeView don't take care about a sorting
// change of the proxy model hence they must be updated the manually.
// This is done here by a qobject_cast, but it would be nicer to:
// - provide a signal 'sortingChanged()'
// - connect KDirOperatorDetailView() with this signal and update the
// header internally
QTreeView* treeView = qobject_cast<QTreeView*>(itemView);
if (treeView != 0) {
QHeaderView* headerView = treeView->header();
headerView->blockSignals(true);
headerView->setSortIndicator(sortColumn(), sortOrder());
headerView->blockSignals(false);
}
_k_assureVisibleSelection();
}
// Protected
void KDirOperator::pathChanged()
{
if (d->itemView == 0)
return;
d->pendingMimeTypes.clear();
//d->fileView->clear(); TODO
d->completion.clear();
d->dirCompletion.clear();
// it may be, that we weren't ready at this time
QApplication::restoreOverrideCursor();
// when KIO::Job emits finished, the slot will restore the cursor
QApplication::setOverrideCursor(Qt::WaitCursor);
if (!Private::isReadable(d->currUrl)) {
KMessageBox::error(d->itemView,
i18n("The specified folder does not exist "
"or was not readable."));
if (d->backStack.isEmpty())
home();
else
back();
}
}
void KDirOperator::Private::_k_slotRedirected(const KUrl& newURL)
{
currUrl = newURL;
pendingMimeTypes.clear();
completion.clear();
dirCompletion.clear();
completeListDirty = true;
emit parent->urlEntered(newURL);
}
// Code pinched from kfm then hacked
void KDirOperator::back()
{
if (d->backStack.isEmpty())
return;
d->forwardStack.push(new KUrl(d->currUrl));
KUrl *s = d->backStack.pop();
setUrl(*s, false);
delete s;
}
// Code pinched from kfm then hacked
void KDirOperator::forward()
{
if (d->forwardStack.isEmpty())
return;
d->backStack.push(new KUrl(d->currUrl));
KUrl *s = d->forwardStack.pop();
setUrl(*s, false);
delete s;
}
KUrl KDirOperator::url() const
{
return d->currUrl;
}
void KDirOperator::cdUp()
{
KUrl tmp(d->currUrl);
tmp.cd(QLatin1String(".."));
setUrl(tmp, true);
}
void KDirOperator::home()
{
KUrl u;
u.setPath(QDir::homePath());
setUrl(u, true);
}
void KDirOperator::clearFilter()
{
d->dirLister->setNameFilter(QString());
d->dirLister->clearMimeFilter();
checkPreviewSupport();
}
void KDirOperator::setNameFilter(const QString& filter)
{
d->dirLister->setNameFilter(filter);
checkPreviewSupport();
}
QString KDirOperator::nameFilter() const
{
return d->dirLister->nameFilter();
}
void KDirOperator::setMimeFilter(const QStringList& mimetypes)
{
d->dirLister->setMimeFilter(mimetypes);
checkPreviewSupport();
}
QStringList KDirOperator::mimeFilter() const
{
return d->dirLister->mimeFilters();
}
void KDirOperator::setNewFileMenuSupportedMimeTypes(const QStringList& mimeTypes)
{
d->newFileMenu->setSupportedMimeTypes(mimeTypes);
}
QStringList KDirOperator::newFileMenuSupportedMimeTypes() const
{
return d->newFileMenu->supportedMimeTypes();
}
bool KDirOperator::checkPreviewSupport()
{
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
bool hasPreviewSupport = false;
KConfigGroup cg(KGlobal::config(), ConfigGroup);
if (cg.readEntry("Show Default Preview", true))
hasPreviewSupport = d->checkPreviewInternal();
previewAction->setEnabled(hasPreviewSupport);
return hasPreviewSupport;
}
void KDirOperator::activatedMenu(const KFileItem &item, const QPoint &pos)
{
Q_UNUSED(item);
updateSelectionDependentActions();
d->newFileMenu->setPopupFiles(url());
d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles());
d->newFileMenu->checkUpToDate();
emit contextMenuAboutToShow( item, d->actionMenu->menu() );
d->actionMenu->menu()->exec(pos);
}
void KDirOperator::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
}
bool KDirOperator::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched);
// If we are not hovering any items, check if there is a current index
// set. In that case, we show the preview of that item.
switch(event->type()) {
case QEvent::MouseMove: {
if (d->preview && !d->preview->isHidden()) {
const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos()));
if (d->lastHoveredIndex == hoveredIndex)
return QWidget::eventFilter(watched, event);
d->lastHoveredIndex = hoveredIndex;
const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex()
: QModelIndex();
if (!hoveredIndex.isValid() && focusedIndex.isValid() &&
d->itemView->selectionModel()->isSelected(focusedIndex) &&
(d->lastHoveredIndex != focusedIndex)) {
const QModelIndex sourceFocusedIndex = d->proxyModel->mapToSource(focusedIndex);
const KFileItem item = d->dirModel->itemForIndex(sourceFocusedIndex);
if (!item.isNull()) {
d->preview->showPreview(item.url());
}
}
}
}
break;
case QEvent::MouseButtonRelease: {
if (d->preview != 0 && !d->preview->isHidden()) {
const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos()));
const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex()
: QModelIndex();
if (((!focusedIndex.isValid()) ||
!d->itemView->selectionModel()->isSelected(focusedIndex)) &&
(!hoveredIndex.isValid())) {
d->preview->clearPreview();
}
}
}
break;
case QEvent::Wheel: {
QWheelEvent *evt = static_cast<QWheelEvent*>(event);
if (evt->modifiers() & Qt::ControlModifier) {
if (evt->delta() > 0) {
setIconsZoom(d->iconsZoom + 10);
} else {
setIconsZoom(d->iconsZoom - 10);
}
return true;
}
}
break;
default:
break;
}
return QWidget::eventFilter(watched, event);
}
bool KDirOperator::Private::checkPreviewInternal() const
{
const QStringList supported = KIO::PreviewJob::supportedMimeTypes();
// no preview support for directories?
if (parent->dirOnlyMode() && supported.indexOf("inode/directory") == -1)
return false;
QStringList mimeTypes = dirLister->mimeFilters();
const QStringList nameFilter = dirLister->nameFilter().split(' ', QString::SkipEmptyParts);
if (mimeTypes.isEmpty() && nameFilter.isEmpty() && !supported.isEmpty())
return true;
else {
QRegExp r;
r.setPatternSyntax(QRegExp::Wildcard); // the "mimetype" can be "image/*"
if (!mimeTypes.isEmpty()) {
QStringList::ConstIterator it = supported.begin();
for (; it != supported.end(); ++it) {
r.setPattern(*it);
QStringList result = mimeTypes.filter(r);
if (!result.isEmpty()) { // matches! -> we want previews
return true;
}
}
}
if (!nameFilter.isEmpty()) {
// find the mimetypes of all the filter-patterns
QStringList::const_iterator it1 = nameFilter.begin();
for (; it1 != nameFilter.end(); ++it1) {
if ((*it1) == "*") {
return true;
}
KMimeType::Ptr mt = KMimeType::findByPath(*it1, 0, true /*fast mode, no file contents exist*/);
if (!mt)
continue;
QString mime = mt->name();
// the "mimetypes" we get from the PreviewJob can be "image/*"
// so we need to check in wildcard mode
QStringList::ConstIterator it2 = supported.begin();
for (; it2 != supported.end(); ++it2) {
r.setPattern(*it2);
if (r.indexIn(mime) != -1) {
return true;
}
}
}
}
}
return false;
}
QAbstractItemView* KDirOperator::createView(QWidget* parent, KFile::FileView viewKind)
{
QAbstractItemView *itemView = 0;
if (KFile::isDetailView(viewKind) || KFile::isTreeView(viewKind) || KFile::isDetailTreeView(viewKind)) {
KDirOperatorDetailView *detailView = new KDirOperatorDetailView(parent);
detailView->setViewMode(viewKind);
itemView = detailView;
} else {
itemView = new KDirOperatorIconView(this, parent);
}
return itemView;
}
void KDirOperator::setAcceptDrops(bool b)
{
// TODO:
//if (d->fileView)
// d->fileView->widget()->setAcceptDrops(b);
QWidget::setAcceptDrops(b);
}
void KDirOperator::setDropOptions(int options)
{
d->dropOptions = options;
// TODO:
//if (d->fileView)
// d->fileView->setDropOptions(options);
}
void KDirOperator::setView(KFile::FileView viewKind)
{
bool preview = (KFile::isPreviewInfo(viewKind) || KFile::isPreviewContents(viewKind));
if (viewKind == KFile::Default) {
if (KFile::isDetailView((KFile::FileView)d->defaultView)) {
viewKind = KFile::Detail;
} else if (KFile::isTreeView((KFile::FileView)d->defaultView)) {
viewKind = KFile::Tree;
} else if (KFile::isDetailTreeView((KFile::FileView)d->defaultView)) {
viewKind = KFile::DetailTree;
} else {
viewKind = KFile::Simple;
}
const KFile::FileView defaultViewKind = static_cast<KFile::FileView>(d->defaultView);
preview = (KFile::isPreviewInfo(defaultViewKind) || KFile::isPreviewContents(defaultViewKind))
&& d->actionCollection->action("preview")->isEnabled();
}
d->viewKind = static_cast<int>(viewKind);
viewKind = static_cast<KFile::FileView>(d->viewKind);
QAbstractItemView *newView = createView(this, viewKind);
setView(newView);
d->_k_togglePreview(preview);
}
QAbstractItemView * KDirOperator::view() const
{
return d->itemView;
}
KFile::Modes KDirOperator::mode() const
{
return d->mode;
}
void KDirOperator::setMode(KFile::Modes mode)
{
if (d->mode == mode)
return;
d->mode = mode;
d->dirLister->setDirOnlyMode(dirOnlyMode());
// reset the view with the different mode
if (d->itemView != 0)
setView(static_cast<KFile::FileView>(d->viewKind));
}
void KDirOperator::setView(QAbstractItemView *view)
{
if (view == d->itemView) {
return;
}
// TODO: do a real timer and restart it after that
d->pendingMimeTypes.clear();
const bool listDir = (d->itemView == 0);
if (d->mode & KFile::Files) {
view->setSelectionMode(QAbstractItemView::ExtendedSelection);
} else {
view->setSelectionMode(QAbstractItemView::SingleSelection);
}
QItemSelectionModel *selectionModel = 0;
if ((d->itemView != 0) && d->itemView->selectionModel()->hasSelection()) {
// remember the selection of the current item view and apply this selection
// to the new view later
const QItemSelection selection = d->itemView->selectionModel()->selection();
selectionModel = new QItemSelectionModel(d->proxyModel, this);
selectionModel->select(selection, QItemSelectionModel::Select);
}
setFocusProxy(0);
delete d->itemView;
d->itemView = view;
d->itemView->setModel(d->proxyModel);
setFocusProxy(d->itemView);
view->viewport()->installEventFilter(this);
KFileItemDelegate *delegate = new KFileItemDelegate(d->itemView);
d->itemView->setItemDelegate(delegate);
d->itemView->viewport()->setAttribute(Qt::WA_Hover);
d->itemView->setContextMenuPolicy(Qt::CustomContextMenu);
d->itemView->setMouseTracking(true);
//d->itemView->setDropOptions(d->dropOptions);
// first push our settings to the view, then listen for changes from the view
QTreeView* treeView = qobject_cast<QTreeView*>(d->itemView);
if (treeView) {
QHeaderView* headerView = treeView->header();
headerView->setSortIndicator(d->sortColumn(), d->sortOrder());
connect(headerView, SIGNAL(sortIndicatorChanged (int, Qt::SortOrder)),
this, SLOT(_k_synchronizeSortingState(int, Qt::SortOrder)));
}
connect(d->itemView, SIGNAL(activated(const QModelIndex&)),
this, SLOT(_k_slotActivated(const QModelIndex&)));
connect(d->itemView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(_k_openContextMenu(const QPoint&)));
connect(d->itemView, SIGNAL(entered(const QModelIndex&)),
this, SLOT(_k_triggerPreview(const QModelIndex&)));
updateViewActions();
d->splitter->insertWidget(0, d->itemView);
d->splitter->resize(size());
d->itemView->show();
if (listDir) {
QApplication::setOverrideCursor(Qt::WaitCursor);
d->openUrl(d->currUrl);
}
if (selectionModel != 0) {
d->itemView->setSelectionModel(selectionModel);
QMetaObject::invokeMethod(this, "_k_assureVisibleSelection", Qt::QueuedConnection);
}
connect(d->itemView->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)),
this, SLOT(_k_triggerPreview(const QModelIndex&)));
connect(d->itemView->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(_k_slotSelectionChanged()));
// if we cannot cast it to a QListView, disable the "Icon Position" menu. Note that this check
// needs to be done here, and not in createView, since we can be set an external view
d->decorationMenu->setEnabled(qobject_cast<QListView*>(d->itemView));
d->shouldFetchForItems = qobject_cast<QTreeView*>(view);
if (d->shouldFetchForItems) {
connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex)));
} else {
d->itemsToBeSetAsCurrent.clear();
}
const bool previewForcedToTrue = d->inlinePreviewState == Private::ForcedToTrue;
const bool previewShown = d->inlinePreviewState == Private::NotForced ? d->showPreviews : previewForcedToTrue;
d->previewGenerator = new KFilePreviewGenerator(d->itemView);
const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
const int val = (maxSize * d->iconsZoom / 100) + KIconLoader::SizeSmall;
d->itemView->setIconSize(previewForcedToTrue ? QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge) : QSize(val, val));
d->previewGenerator->setPreviewShown(previewShown);
d->actionCollection->action("inline preview")->setChecked(previewShown);
// ensure we change everything needed
d->_k_slotChangeDecorationPosition();
emit viewChanged(view);
const int zoom = previewForcedToTrue ? (KIconLoader::SizeHuge - KIconLoader::SizeSmall + 1) * 100 / maxSize : d->iconSizeForViewType(view);
// this will make d->iconsZoom be updated, since setIconsZoom slot will be called
emit currentIconSizeChanged(zoom);
}
void KDirOperator::setDirLister(KDirLister *lister)
{
if (lister == d->dirLister) // sanity check
return;
delete d->dirModel;
d->dirModel = 0;
delete d->proxyModel;
d->proxyModel = 0;
//delete d->dirLister; // deleted by KDirModel already, which took ownership
d->dirLister = lister;
d->dirModel = new KDirModel();
d->dirModel->setDirLister(d->dirLister);
d->dirModel->setDropsAllowed(KDirModel::DropOnDirectory);
d->shouldFetchForItems = qobject_cast<QTreeView*>(d->itemView);
if (d->shouldFetchForItems) {
connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex)));
} else {
d->itemsToBeSetAsCurrent.clear();
}
d->proxyModel = new KDirSortFilterProxyModel(this);
d->proxyModel->setSourceModel(d->dirModel);
d->dirLister->setAutoUpdate(true);
d->dirLister->setDelayedMimeTypes(true);
QWidget* mainWidget = topLevelWidget();
d->dirLister->setMainWindow(mainWidget);
kDebug(kfile_area) << "mainWidget=" << mainWidget;
connect(d->dirLister, SIGNAL(percent(int)),
SLOT(_k_slotProgress(int)));
connect(d->dirLister, SIGNAL(started(const KUrl&)), SLOT(_k_slotStarted()));
connect(d->dirLister, SIGNAL(completed()), SLOT(_k_slotIOFinished()));
connect(d->dirLister, SIGNAL(canceled()), SLOT(_k_slotCanceled()));
connect(d->dirLister, SIGNAL(redirection(const KUrl&)),
SLOT(_k_slotRedirected(const KUrl&)));
connect(d->dirLister, SIGNAL(newItems(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(itemsDeleted(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(itemsFilteredByMime(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(clear()), SLOT(_k_slotItemsChanged()));
}
void KDirOperator::selectDir(const KFileItem &item)
{
setUrl(item.targetUrl(), true);
}
void KDirOperator::selectFile(const KFileItem &item)
{
QApplication::restoreOverrideCursor();
emit fileSelected(item);
}
void KDirOperator::highlightFile(const KFileItem &item)
{
if ((d->preview != 0 && !d->preview->isHidden()) && !item.isNull()) {
d->preview->showPreview(item.url());
}
emit fileHighlighted(item);
}
void KDirOperator::setCurrentItem(const QString& url)
{
kDebug(kfile_area);
KFileItem item = d->dirLister->findByUrl(url);
if (d->shouldFetchForItems && item.isNull()) {
d->itemsToBeSetAsCurrent << url;
d->dirModel->expandToUrl(url);
return;
}
setCurrentItem(item);
}
void KDirOperator::setCurrentItem(const KFileItem& item)
{
kDebug(kfile_area);
if (!d->itemView) {
return;
}
QItemSelectionModel *selModel = d->itemView->selectionModel();
if (selModel) {
selModel->clear();
if (!item.isNull()) {
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex);
selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select);
}
}
}
void KDirOperator::setCurrentItems(const QStringList& urls)
{
kDebug(kfile_area);
if (!d->itemView) {
return;
}
KFileItemList itemList;
foreach (const QString &url, urls) {
KFileItem item = d->dirLister->findByUrl(url);
if (d->shouldFetchForItems && item.isNull()) {
d->itemsToBeSetAsCurrent << url;
d->dirModel->expandToUrl(url);
continue;
}
itemList << item;
}
setCurrentItems(itemList);
}
void KDirOperator::setCurrentItems(const KFileItemList& items)
{
kDebug(kfile_area);
if (d->itemView == 0) {
return;
}
QItemSelectionModel *selModel = d->itemView->selectionModel();
if (selModel) {
selModel->clear();
QModelIndex proxyIndex;
foreach (const KFileItem &item, items) {
if (!item.isNull()) {
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
proxyIndex = d->proxyModel->mapFromSource(dirIndex);
selModel->select(proxyIndex, QItemSelectionModel::Select);
}
}
if (proxyIndex.isValid()) {
selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::NoUpdate);
}
}
}
QString KDirOperator::makeCompletion(const QString& string)
{
if (string.isEmpty()) {
d->itemView->selectionModel()->clear();
return QString();
}
prepareCompletionObjects();
return d->completion.makeCompletion(string);
}
QString KDirOperator::makeDirCompletion(const QString& string)
{
if (string.isEmpty()) {
d->itemView->selectionModel()->clear();
return QString();
}
prepareCompletionObjects();
return d->dirCompletion.makeCompletion(string);
}
void KDirOperator::prepareCompletionObjects()
{
if (d->itemView == 0) {
return;
}
if (d->completeListDirty) { // create the list of all possible completions
const KFileItemList itemList = d->dirLister->items();
foreach (const KFileItem &item, itemList) {
d->completion.addItem(item.name());
if (item.isDir()) {
d->dirCompletion.addItem(item.name());
}
}
d->completeListDirty = false;
}
}
void KDirOperator::slotCompletionMatch(const QString& match)
{
setCurrentItem(match);
emit completion(match);
}
void KDirOperator::setupActions()
{
d->actionCollection = new KActionCollection(this);
d->actionCollection->setObjectName("KDirOperator::actionCollection");
d->actionMenu = new KActionMenu(i18n("Menu"), this);
d->actionCollection->addAction("popupMenu", d->actionMenu);
QAction* upAction = d->actionCollection->addAction(KStandardAction::Up, "up", this, SLOT(cdUp()));
upAction->setText(i18n("Parent Folder"));
d->actionCollection->addAction(KStandardAction::Back, "back", this, SLOT(back()));
d->actionCollection->addAction(KStandardAction::Forward, "forward", this, SLOT(forward()));
QAction* homeAction = d->actionCollection->addAction(KStandardAction::Home, "home", this, SLOT(home()));
homeAction->setText(i18n("Home Folder"));
KAction* reloadAction = d->actionCollection->addAction(KStandardAction::Redisplay, "reload", this, SLOT(rereadDir()));
reloadAction->setText(i18n("Reload"));
reloadAction->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Reload));
KAction* mkdirAction = new KAction(i18n("New Folder..."), this);
d->actionCollection->addAction("mkdir", mkdirAction);
mkdirAction->setIcon(KIcon(QLatin1String("folder-new")));
connect(mkdirAction, SIGNAL(triggered(bool)), this, SLOT(mkdir()));
KAction* trash = new KAction(i18n("Move to Trash"), this);
d->actionCollection->addAction("trash", trash);
trash->setIcon(KIcon("user-trash"));
trash->setShortcuts(KShortcut(Qt::Key_Delete));
connect(trash, SIGNAL(triggered(bool)), SLOT(trashSelected()));
KAction* action = new KAction(i18n("Delete"), this);
d->actionCollection->addAction("delete", action);
action->setIcon(KIcon("edit-delete"));
action->setShortcuts(KShortcut(Qt::SHIFT + Qt::Key_Delete));
connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteSelected()));
// the sort menu actions
KActionMenu *sortMenu = new KActionMenu(i18n("Sorting"), this);
d->actionCollection->addAction("sorting menu", sortMenu);
KToggleAction *byNameAction = new KToggleAction(i18n("By Name"), this);
d->actionCollection->addAction("by name", byNameAction);
connect(byNameAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByName()));
KToggleAction *bySizeAction = new KToggleAction(i18n("By Size"), this);
d->actionCollection->addAction("by size", bySizeAction);
connect(bySizeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortBySize()));
KToggleAction *byDateAction = new KToggleAction(i18n("By Date"), this);
d->actionCollection->addAction("by date", byDateAction);
connect(byDateAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByDate()));
KToggleAction *byTypeAction = new KToggleAction(i18n("By Type"), this);
d->actionCollection->addAction("by type", byTypeAction);
connect(byTypeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByType()));
KToggleAction *descendingAction = new KToggleAction(i18n("Descending"), this);
d->actionCollection->addAction("descending", descendingAction);
connect(descendingAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortReversed(bool)));
KToggleAction *dirsFirstAction = new KToggleAction(i18n("Folders First"), this);
d->actionCollection->addAction("dirs first", dirsFirstAction);
connect(dirsFirstAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotToggleDirsFirst()));
QActionGroup* sortGroup = new QActionGroup(this);
byNameAction->setActionGroup(sortGroup);
bySizeAction->setActionGroup(sortGroup);
byDateAction->setActionGroup(sortGroup);
byTypeAction->setActionGroup(sortGroup);
d->decorationMenu = new KActionMenu(i18n("Icon Position"), this);
d->actionCollection->addAction("decoration menu", d->decorationMenu);
d->leftAction = new KToggleAction(i18n("Next to File Name"), this);
d->actionCollection->addAction("decorationAtLeft", d->leftAction);
connect(d->leftAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition()));
KToggleAction *topAction = new KToggleAction(i18n("Above File Name"), this);
d->actionCollection->addAction("decorationAtTop", topAction);
connect(topAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition()));
d->decorationMenu->addAction(d->leftAction);
d->decorationMenu->addAction(topAction);
QActionGroup* decorationGroup = new QActionGroup(this);
d->leftAction->setActionGroup(decorationGroup);
topAction->setActionGroup(decorationGroup);
KToggleAction *shortAction = new KToggleAction(i18n("Short View"), this);
d->actionCollection->addAction("short view", shortAction);
shortAction->setIcon(KIcon(QLatin1String("view-list-icons")));
connect(shortAction, SIGNAL(triggered()), SLOT(_k_slotSimpleView()));
KToggleAction *detailedAction = new KToggleAction(i18n("Detailed View"), this);
d->actionCollection->addAction("detailed view", detailedAction);
detailedAction->setIcon(KIcon(QLatin1String("view-list-details")));
connect(detailedAction, SIGNAL(triggered ()), SLOT(_k_slotDetailedView()));
KToggleAction *treeAction = new KToggleAction(i18n("Tree View"), this);
d->actionCollection->addAction("tree view", treeAction);
treeAction->setIcon(KIcon(QLatin1String("view-list-tree")));
connect(treeAction, SIGNAL(triggered ()), SLOT(_k_slotTreeView()));
KToggleAction *detailedTreeAction = new KToggleAction(i18n("Detailed Tree View"), this);
d->actionCollection->addAction("detailed tree view", detailedTreeAction);
detailedTreeAction->setIcon(KIcon(QLatin1String("view-list-tree")));
connect(detailedTreeAction, SIGNAL(triggered ()), SLOT(_k_slotDetailedTreeView()));
QActionGroup* viewGroup = new QActionGroup(this);
shortAction->setActionGroup(viewGroup);
detailedAction->setActionGroup(viewGroup);
treeAction->setActionGroup(viewGroup);
detailedTreeAction->setActionGroup(viewGroup);
KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Files"), this);
d->actionCollection->addAction("show hidden", showHiddenAction);
connect(showHiddenAction, SIGNAL(toggled(bool)), SLOT(_k_slotToggleHidden(bool)));
KToggleAction *previewAction = new KToggleAction(i18n("Show Aside Preview"), this);
d->actionCollection->addAction("preview", previewAction);
connect(previewAction, SIGNAL(toggled(bool)),
SLOT(_k_togglePreview(bool)));
KToggleAction *inlinePreview = new KToggleAction(KIcon("view-preview"),
i18n("Show Preview"), this);
d->actionCollection->addAction("inline preview", inlinePreview);
connect(inlinePreview, SIGNAL(toggled(bool)), SLOT(_k_toggleInlinePreviews(bool)));
KAction *fileManager = new KAction(i18n("Open File Manager"), this);
d->actionCollection->addAction("file manager", fileManager);
fileManager->setIcon(KIcon(QLatin1String("system-file-manager")));
connect(fileManager, SIGNAL(triggered()), SLOT(_k_slotOpenFileManager()));
action = new KAction(i18n("Properties"), this);
d->actionCollection->addAction("properties", action);
action->setIcon(KIcon("document-properties"));
action->setShortcut(KShortcut(Qt::ALT + Qt::Key_Return));
connect(action, SIGNAL(triggered(bool)), this, SLOT(_k_slotProperties()));
// the view menu actions
KActionMenu* viewMenu = new KActionMenu(i18n("&View"), this);
d->actionCollection->addAction("view menu", viewMenu);
viewMenu->addAction(shortAction);
viewMenu->addAction(detailedAction);
// Comment following lines to hide the extra two modes
viewMenu->addAction(treeAction);
viewMenu->addAction(detailedTreeAction);
// TODO: QAbstractItemView does not offer an action collection. Provide
// an interface to add a custom action collection.
d->newFileMenu = new KNewFileMenu(d->actionCollection, "new", this);
connect(d->newFileMenu, SIGNAL(directoryCreated(KUrl)), this, SLOT(_k_slotDirectoryCreated(KUrl)));
d->actionCollection->addAssociatedWidget(this);
foreach (QAction* action, d->actionCollection->actions())
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
void KDirOperator::setupMenu()
{
setupMenu(SortActions | ViewActions | FileActions);
}
void KDirOperator::setupMenu(int whichActions)
{
// first fill the submenus (sort and view)
KActionMenu *sortMenu = static_cast<KActionMenu*>(d->actionCollection->action("sorting menu"));
sortMenu->menu()->clear();
sortMenu->addAction(d->actionCollection->action("by name"));
sortMenu->addAction(d->actionCollection->action("by size"));
sortMenu->addAction(d->actionCollection->action("by date"));
sortMenu->addAction(d->actionCollection->action("by type"));
sortMenu->addSeparator();
sortMenu->addAction(d->actionCollection->action("descending"));
sortMenu->addAction(d->actionCollection->action("dirs first"));
// now plug everything into the popupmenu
d->actionMenu->menu()->clear();
if (whichActions & NavActions) {
d->actionMenu->addAction(d->actionCollection->action("up"));
d->actionMenu->addAction(d->actionCollection->action("back"));
d->actionMenu->addAction(d->actionCollection->action("forward"));
d->actionMenu->addAction(d->actionCollection->action("home"));
d->actionMenu->addSeparator();
}
if (whichActions & FileActions) {
d->actionMenu->addAction(d->actionCollection->action("new"));
if (d->currUrl.isLocalFile() && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
d->actionMenu->addAction(d->actionCollection->action("trash"));
}
KConfigGroup cg(KGlobal::config(), QLatin1String("KDE"));
const bool del = !d->currUrl.isLocalFile() ||
(QApplication::keyboardModifiers() & Qt::ShiftModifier) ||
cg.readEntry("ShowDeleteCommand", false);
if (del) {
d->actionMenu->addAction(d->actionCollection->action("delete"));
}
d->actionMenu->addSeparator();
}
if (whichActions & SortActions) {
d->actionMenu->addAction(sortMenu);
if (!(whichActions & ViewActions)) {
d->actionMenu->addSeparator();
}
}
if (whichActions & ViewActions) {
d->actionMenu->addAction(d->actionCollection->action("view menu"));
d->actionMenu->addSeparator();
}
if (whichActions & FileActions) {
d->actionMenu->addAction(d->actionCollection->action("file manager"));
d->actionMenu->addAction(d->actionCollection->action("properties"));
}
}
void KDirOperator::updateSortActions()
{
if (KFile::isSortByName(d->sorting)) {
d->actionCollection->action("by name")->setChecked(true);
} else if (KFile::isSortByDate(d->sorting)) {
d->actionCollection->action("by date")->setChecked(true);
} else if (KFile::isSortBySize(d->sorting)) {
d->actionCollection->action("by size")->setChecked(true);
} else if (KFile::isSortByType(d->sorting)) {
d->actionCollection->action("by type")->setChecked(true);
}
d->actionCollection->action("descending")->setChecked(d->sorting & QDir::Reversed);
d->actionCollection->action("dirs first")->setChecked(d->sorting & QDir::DirsFirst);
}
void KDirOperator::updateViewActions()
{
KFile::FileView fv = static_cast<KFile::FileView>(d->viewKind);
//QAction *separateDirs = d->actionCollection->action("separate dirs");
//separateDirs->setChecked(KFile::isSeparateDirs(fv) &&
// separateDirs->isEnabled());
d->actionCollection->action("short view")->setChecked(KFile::isSimpleView(fv));
d->actionCollection->action("detailed view")->setChecked(KFile::isDetailView(fv));
d->actionCollection->action("tree view")->setChecked(KFile::isTreeView(fv));
d->actionCollection->action("detailed tree view")->setChecked(KFile::isDetailTreeView(fv));
}
void KDirOperator::readConfig(const KConfigGroup& configGroup)
{
d->defaultView = 0;
QString viewStyle = configGroup.readEntry("View Style", "Simple");
if (viewStyle == QLatin1String("Detail")) {
d->defaultView |= KFile::Detail;
} else if (viewStyle == QLatin1String("Tree")) {
d->defaultView |= KFile::Tree;
} else if (viewStyle == QLatin1String("DetailTree")) {
d->defaultView |= KFile::DetailTree;
} else {
d->defaultView |= KFile::Simple;
}
//if (configGroup.readEntry(QLatin1String("Separate Directories"),
// DefaultMixDirsAndFiles)) {
// d->defaultView |= KFile::SeparateDirs;
//}
if (configGroup.readEntry(QLatin1String("Show Preview"), false)) {
d->defaultView |= KFile::PreviewContents;
}
d->previewWidth = configGroup.readEntry(QLatin1String("Preview Width"), 100);
if (configGroup.readEntry(QLatin1String("Show hidden files"),
DefaultShowHidden)) {
d->actionCollection->action("show hidden")->setChecked(true);
d->dirLister->setShowingDotFiles(true);
}
QDir::SortFlags sorting = QDir::Name;
if (configGroup.readEntry(QLatin1String("Sort directories first"),
DefaultDirsFirst)) {
sorting |= QDir::DirsFirst;
}
QString name = QLatin1String("Name");
QString sortBy = configGroup.readEntry(QLatin1String("Sort by"), name);
if (sortBy == name) {
sorting |= QDir::Name;
} else if (sortBy == QLatin1String("Size")) {
sorting |= QDir::Size;
} else if (sortBy == QLatin1String("Date")) {
sorting |= QDir::Time;
} else if (sortBy == QLatin1String("Type")) {
sorting |= QDir::Type;
}
if (configGroup.readEntry(QLatin1String("Sort reversed"), DefaultSortReversed)) {
sorting |= QDir::Reversed;
}
d->updateSorting(sorting);
if (d->inlinePreviewState == Private::NotForced) {
d->showPreviews = configGroup.readEntry(QLatin1String("Previews"), false);
}
QStyleOptionViewItem::Position pos = (QStyleOptionViewItem::Position) configGroup.readEntry(QLatin1String("Decoration position"), (int) QStyleOptionViewItem::Left);
setDecorationPosition(pos);
}
void KDirOperator::writeConfig(KConfigGroup& configGroup)
{
QString sortBy = QLatin1String("Name");
if (KFile::isSortBySize(d->sorting)) {
sortBy = QLatin1String("Size");
} else if (KFile::isSortByDate(d->sorting)) {
sortBy = QLatin1String("Date");
} else if (KFile::isSortByType(d->sorting)) {
sortBy = QLatin1String("Type");
}
configGroup.writeEntry(QLatin1String("Sort by"), sortBy);
configGroup.writeEntry(QLatin1String("Sort reversed"),
d->actionCollection->action("descending")->isChecked());
configGroup.writeEntry(QLatin1String("Sort directories first"),
d->actionCollection->action("dirs first")->isChecked());
// don't save the preview when an application specific preview is in use.
bool appSpecificPreview = false;
if (d->preview) {
KFileMetaPreview *tmp = dynamic_cast<KFileMetaPreview*>(d->preview);
appSpecificPreview = (tmp == 0);
}
if (!appSpecificPreview) {
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
if (previewAction->isEnabled()) {
bool hasPreview = previewAction->isChecked();
configGroup.writeEntry(QLatin1String("Show Preview"), hasPreview);
if (hasPreview) {
// remember the width of the preview widget
QList<int> sizes = d->splitter->sizes();
Q_ASSERT(sizes.count() == 2);
configGroup.writeEntry(QLatin1String("Preview Width"), sizes[1]);
}
}
}
configGroup.writeEntry(QLatin1String("Show hidden files"),
d->actionCollection->action("show hidden")->isChecked());
KFile::FileView fv = static_cast<KFile::FileView>(d->viewKind);
QString style;
if (KFile::isDetailView(fv))
style = QLatin1String("Detail");
else if (KFile::isSimpleView(fv))
style = QLatin1String("Simple");
else if (KFile::isTreeView(fv))
style = QLatin1String("Tree");
else if (KFile::isDetailTreeView(fv))
style = QLatin1String("DetailTree");
configGroup.writeEntry(QLatin1String("View Style"), style);
if (d->inlinePreviewState == Private::NotForced) {
configGroup.writeEntry(QLatin1String("Previews"), d->showPreviews);
}
configGroup.writeEntry(QLatin1String("Decoration position"), (int) d->decorationPosition);
}
void KDirOperator::resizeEvent(QResizeEvent *)
{
// resize the splitter and assure that the width of
// the preview widget is restored
QList<int> sizes = d->splitter->sizes();
const bool hasPreview = (sizes.count() == 2);
d->splitter->resize(size());
sizes = d->splitter->sizes();
const bool restorePreviewWidth = hasPreview && (d->previewWidth != sizes[1]);
if (restorePreviewWidth) {
const int availableWidth = sizes[0] + sizes[1];
sizes[0] = availableWidth - d->previewWidth;
sizes[1] = d->previewWidth;
d->splitter->setSizes(sizes);
}
if (hasPreview) {
d->previewWidth = sizes[1];
}
if (d->progressBar->parent() == this) {
// might be reparented into a statusbar
d->progressBar->move(2, height() - d->progressBar->height() - 2);
}
}
void KDirOperator::setOnlyDoubleClickSelectsFiles(bool enable)
{
d->onlyDoubleClickSelectsFiles = enable;
// TODO: port to Qt4's QAbstractItemModel
//if (d->itemView != 0) {
// d->itemView->setOnlyDoubleClickSelectsFiles(enable);
//}
}
bool KDirOperator::onlyDoubleClickSelectsFiles() const
{
return d->onlyDoubleClickSelectsFiles;
}
void KDirOperator::Private::_k_slotStarted()
{
progressBar->setValue(0);
// delay showing the progressbar for one second
progressDelayTimer->setSingleShot(true);
progressDelayTimer->start(1000);
}
void KDirOperator::Private::_k_slotShowProgress()
{
progressBar->raise();
progressBar->show();
QApplication::flush();
}
void KDirOperator::Private::_k_slotProgress(int percent)
{
progressBar->setValue(percent);
// we have to redraw this as fast as possible
if (progressBar->isVisible())
QApplication::flush();
}
void KDirOperator::Private::_k_slotIOFinished()
{
progressDelayTimer->stop();
_k_slotProgress(100);
progressBar->hide();
emit parent->finishedLoading();
parent->resetCursor();
if (preview) {
preview->clearPreview();
}
}
void KDirOperator::Private::_k_slotCanceled()
{
emit parent->finishedLoading();
parent->resetCursor();
}
QProgressBar * KDirOperator::progressBar() const
{
return d->progressBar;
}
void KDirOperator::clearHistory()
{
qDeleteAll(d->backStack);
d->backStack.clear();
d->actionCollection->action("back")->setEnabled(false);
qDeleteAll(d->forwardStack);
d->forwardStack.clear();
d->actionCollection->action("forward")->setEnabled(false);
}
void KDirOperator::setEnableDirHighlighting(bool enable)
{
d->dirHighlighting = enable;
}
bool KDirOperator::dirHighlighting() const
{
return d->dirHighlighting;
}
bool KDirOperator::dirOnlyMode() const
{
return dirOnlyMode(d->mode);
}
bool KDirOperator::dirOnlyMode(uint mode)
{
return ((mode & KFile::Directory) &&
(mode & (KFile::File | KFile::Files)) == 0);
}
void KDirOperator::Private::_k_slotProperties()
{
if (itemView == 0) {
return;
}
const KFileItemList list = parent->selectedItems();
if (!list.isEmpty()) {
KPropertiesDialog dialog(list, parent);
dialog.exec();
}
}
void KDirOperator::Private::_k_slotActivated(const QModelIndex& index)
{
const QModelIndex dirIndex = proxyModel->mapToSource(index);
KFileItem item = dirModel->itemForIndex(dirIndex);
const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (item.isNull() || (modifiers & Qt::ShiftModifier) || (modifiers & Qt::ControlModifier))
return;
if (item.isDir()) {
parent->selectDir(item);
} else {
parent->selectFile(item);
}
}
void KDirOperator::Private::_k_slotSelectionChanged()
{
if (itemView == 0) {
return;
}
// In the multiselection mode each selection change is indicated by
// emitting a null item. Also when the selection has been cleared, a
// null item must be emitted.
const bool multiSelectionMode = (itemView->selectionMode() == QAbstractItemView::ExtendedSelection);
const bool hasSelection = itemView->selectionModel()->hasSelection();
if (multiSelectionMode || !hasSelection) {
KFileItem nullItem;
parent->highlightFile(nullItem);
}
else {
KFileItem selectedItem = parent->selectedItems().first();
parent->highlightFile(selectedItem);
}
}
void KDirOperator::Private::_k_openContextMenu(const QPoint& pos)
{
const QModelIndex proxyIndex = itemView->indexAt(pos);
const QModelIndex dirIndex = proxyModel->mapToSource(proxyIndex);
KFileItem item = dirModel->itemForIndex(dirIndex);
if (item.isNull())
return;
parent->activatedMenu(item, QCursor::pos());
}
void KDirOperator::Private::_k_triggerPreview(const QModelIndex& index)
{
if ((preview != 0 && !preview->isHidden()) && index.isValid() && (index.column() == KDirModel::Name)) {
const QModelIndex dirIndex = proxyModel->mapToSource(index);
const KFileItem item = dirModel->itemForIndex(dirIndex);
if (item.isNull())
return;
if (!item.isDir()) {
previewUrl = item.url();
_k_showPreview();
} else {
preview->clearPreview();
}
}
}
void KDirOperator::Private::_k_showPreview()
{
if (preview != 0) {
preview->showPreview(previewUrl);
}
}
void KDirOperator::Private::_k_slotSplitterMoved(int, int)
{
const QList<int> sizes = splitter->sizes();
if (sizes.count() == 2) {
// remember the width of the preview widget (see KDirOperator::resizeEvent())
previewWidth = sizes[1];
}
}
void KDirOperator::Private::_k_assureVisibleSelection()
{
if (itemView == 0) {
return;
}
QItemSelectionModel* selModel = itemView->selectionModel();
if (selModel->hasSelection()) {
const QModelIndex index = selModel->currentIndex();
itemView->scrollTo(index, QAbstractItemView::EnsureVisible);
_k_triggerPreview(index);
}
}
void KDirOperator::Private::_k_synchronizeSortingState(int logicalIndex, Qt::SortOrder order)
{
QDir::SortFlags newSort = sorting & ~(QDirSortMask | QDir::Reversed);
switch (logicalIndex) {
case KDirModel::Name:
newSort |= QDir::Name;
break;
case KDirModel::Size:
newSort |= QDir::Size;
break;
case KDirModel::ModifiedTime:
newSort |= QDir::Time;
break;
case KDirModel::Type:
newSort |= QDir::Type;
break;
default:
Q_ASSERT(false);
}
if (order == Qt::DescendingOrder) {
newSort |= QDir::Reversed;
}
updateSorting(newSort);
QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection);
}
void KDirOperator::Private::_k_slotChangeDecorationPosition()
{
if (!itemView) {
return;
}
QListView *view = qobject_cast<QListView*>(itemView);
if (!view) {
return;
}
const bool leftChecked = actionCollection->action("decorationAtLeft")->isChecked();
if (leftChecked) {
decorationPosition = QStyleOptionViewItem::Left;
view->setFlow(QListView::TopToBottom);
} else {
decorationPosition = QStyleOptionViewItem::Top;
view->setFlow(QListView::LeftToRight);
}
updateListViewGrid();
itemView->update();
}
void KDirOperator::Private::_k_slotExpandToUrl(const QModelIndex &index)
{
QTreeView *treeView = qobject_cast<QTreeView*>(itemView);
if (!treeView) {
return;
}
const KFileItem item = dirModel->itemForIndex(index);
if (item.isNull()) {
return;
}
if (!item.isDir()) {
const QModelIndex proxyIndex = proxyModel->mapFromSource(index);
KUrl::List::Iterator it = itemsToBeSetAsCurrent.begin();
while (it != itemsToBeSetAsCurrent.end()) {
const KUrl url = *it;
if (url.isParentOf(item.url())) {
const KFileItem _item = dirLister->findByUrl(url);
- if (_item.isDir()) {
+ if (!_item.isNull() && _item.isDir()) {
const QModelIndex _index = dirModel->indexForItem(_item);
const QModelIndex _proxyIndex = proxyModel->mapFromSource(_index);
treeView->expand(_proxyIndex);
// if we have expanded the last parent of this item, select it
if (item.url().directory() == url.path(KUrl::RemoveTrailingSlash)) {
treeView->selectionModel()->select(proxyIndex, QItemSelectionModel::Select);
}
}
it = itemsToBeSetAsCurrent.erase(it);
} else {
++it;
}
}
} else if (!itemsToBeSetAsCurrent.contains(item.url())) {
itemsToBeSetAsCurrent << item.url();
}
}
void KDirOperator::Private::_k_slotItemsChanged()
{
completeListDirty = true;
}
void KDirOperator::Private::updateListViewGrid()
{
if (!itemView) {
return;
}
QListView *view = qobject_cast<QListView*>(itemView);
if (!view) {
return;
}
const bool leftChecked = actionCollection->action("decorationAtLeft")->isChecked();
if (leftChecked) {
view->setGridSize(QSize());
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate*>(view->itemDelegate());
if (delegate) {
delegate->setMaximumSize(QSize());
}
} else {
const QFontMetrics metrics(itemView->viewport()->font());
int size = itemView->iconSize().height() + metrics.height() * 2;
// some heuristics for good looking. let's guess width = height * (3 / 2) is nice
view->setGridSize(QSize(size * (3.0 / 2.0), size + metrics.height()));
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate*>(view->itemDelegate());
if (delegate) {
delegate->setMaximumSize(QSize(size * (3.0 / 2.0), size + metrics.height()));
}
}
}
int KDirOperator::Private::iconSizeForViewType(QAbstractItemView *itemView) const
{
if (!itemView || !configGroup) {
return 0;
}
if (qobject_cast<QListView*>(itemView)) {
return configGroup->readEntry("listViewIconSize", 0);
} else {
return configGroup->readEntry("detailedViewIconSize", 0);
}
}
void KDirOperator::setViewConfig(KConfigGroup& configGroup)
{
delete d->configGroup;
d->configGroup = new KConfigGroup(configGroup);
}
KConfigGroup* KDirOperator::viewConfigGroup() const
{
return d->configGroup;
}
void KDirOperator::setShowHiddenFiles(bool s)
{
d->actionCollection->action("show hidden")->setChecked(s);
}
bool KDirOperator::showHiddenFiles() const
{
return d->actionCollection->action("show hidden")->isChecked();
}
QStyleOptionViewItem::Position KDirOperator::decorationPosition() const
{
return d->decorationPosition;
}
void KDirOperator::setDecorationPosition(QStyleOptionViewItem::Position position)
{
d->decorationPosition = position;
const bool decorationAtLeft = d->decorationPosition == QStyleOptionViewItem::Left;
d->actionCollection->action("decorationAtLeft")->setChecked(decorationAtLeft);
d->actionCollection->action("decorationAtTop")->setChecked(!decorationAtLeft);
}
// ### temporary code
#include <dirent.h>
bool KDirOperator::Private::isReadable(const KUrl& url)
{
if (!url.isLocalFile())
return true; // what else can we say?
KDE_struct_stat buf;
#ifdef Q_WS_WIN
QString ts = url.toLocalFile();
#else
QString ts = url.path(KUrl::AddTrailingSlash);
#endif
bool readable = (KDE::stat(ts, &buf) == 0);
if (readable) { // further checks
DIR *test;
test = opendir(QFile::encodeName(ts)); // we do it just to test here
readable = (test != 0);
if (test)
closedir(test);
}
return readable;
}
void KDirOperator::Private::_k_slotDirectoryCreated(const KUrl& url)
{
parent->setUrl(url, true);
}
#include "kdiroperator.moc"
diff --git a/kfile/tests/kdiroperatortest.cpp b/kfile/tests/kdiroperatortest.cpp
index 147aaaa0ae..429fc30430 100644
--- a/kfile/tests/kdiroperatortest.cpp
+++ b/kfile/tests/kdiroperatortest.cpp
@@ -1,74 +1,96 @@
/* This file is part of the KDE libraries
Copyright (c) 2009 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License or ( at
your option ) version 3 or, at the discretion of KDE e.V. ( which shall
act as a proxy as in section 14 of the GPLv3 ), any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <kdebug.h>
#include <qtest_kde.h>
#include <kdiroperator.h>
#include <kconfiggroup.h>
#include <qtreeview.h>
/**
* Unit test for KDirOperator
*/
class KDirOperatorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
}
void cleanupTestCase()
{
}
void testNoViewConfig()
{
KDirOperator dirOp;
// setIconsZoom tries to write config.
// Make sure it won't crash if setViewConfig() isn't called
dirOp.setIconsZoom(5);
QCOMPARE(dirOp.iconsZoom(), 5);
}
void testReadConfig()
{
// Test: Make sure readConfig() and then setView() restores
// the correct kind of view.
KDirOperator *dirOp = new KDirOperator;
dirOp->setView(KFile::DetailTree);
dirOp->setShowHiddenFiles(true);
KConfigGroup cg(KGlobal::config(), "diroperator");
dirOp->writeConfig(cg);
delete dirOp;
dirOp = new KDirOperator;
dirOp->readConfig(cg);
dirOp->setView(KFile::Default);
QVERIFY(dirOp->showHiddenFiles());
// KDirOperatorDetail inherits QTreeView, so this test should work
QVERIFY(qobject_cast<QTreeView*>(dirOp->view()));
delete dirOp;
}
+
+ /**
+ * testBug187066 does the following:
+ *
+ * 1. Open a KDirOperator in kdelibs/kfile
+ * 2. Set the current item to "file:///"
+ * 3. Set the current item to "file:///.../kdelibs/kfile/tests/kdiroperatortest.cpp"
+ *
+ * This may result in a crash, see https://bugs.kde.org/show_bug.cgi?id=187066
+ */
+
+ void testBug187066()
+ {
+ const KUrl kFileDirUrl(KUrl(KDESRCDIR).upUrl());
+
+ KDirOperator dirOp(kFileDirUrl);
+ dirOp.setView(KFile::DetailTree);
+ QTest::kWaitForSignal(dirOp.dirLister(), SIGNAL(completed()));
+ dirOp.setCurrentItem("file:///");
+ dirOp.setCurrentItem(KDESRCDIR "kdiroperatortest.cpp");
+ QTest::kWaitForSignal(dirOp.dirLister(), SIGNAL(completed()));
+ }
};
QTEST_KDEMAIN( KDirOperatorTest, GUI )
#include "kdiroperatortest.moc"
diff --git a/kio/kio/kdirmodel.cpp b/kio/kio/kdirmodel.cpp
index 6bf57be519..96126ade50 100644
--- a/kio/kio/kdirmodel.cpp
+++ b/kio/kio/kdirmodel.cpp
@@ -1,1164 +1,1169 @@
/* This file is part of the KDE project
Copyright (C) 2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdirmodel.h"
#include "kdirlister.h"
#include "kfileitem.h"
#include <kdatetime.h>
#include <kicon.h>
#include <klocale.h>
#include <kglobal.h>
#include <kio/copyjob.h>
#include <kio/fileundomanager.h>
#include <kio/jobuidelegate.h>
#include <kio/joburlcache_p.h>
#include <kurl.h>
#include <kdebug.h>
#include <QMimeData>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <sys/types.h>
#include <dirent.h>
#ifdef Q_WS_WIN
#include <windows.h>
#endif
class KDirModelNode;
class KDirModelDirNode;
static KUrl cleanupUrl(const KUrl& url) {
KUrl u = url;
u.cleanPath(); // remove double slashes in the path, simplify "foo/." to "foo/", etc.
u.adjustPath(KUrl::RemoveTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
u.setQuery(QString());
u.setRef(QString());
return u;
}
// We create our own tree behind the scenes to have fast lookup from an item to its parent,
// and also to get the children of an item fast.
class KDirModelNode
{
public:
KDirModelNode( KDirModelDirNode* parent, const KFileItem& item ) :
m_item(item),
m_parent(parent),
m_preview()
{
}
// m_item is KFileItem() for the root item
const KFileItem& item() const { return m_item; }
void setItem(const KFileItem& item) { m_item = item; }
KDirModelDirNode* parent() const { return m_parent; }
// linear search
int rowNumber() const; // O(n)
QIcon preview() const { return m_preview; }
void setPreview( const QPixmap& pix ) { m_preview = QIcon(); m_preview.addPixmap(pix); }
void setPreview( const QIcon& icn ) { m_preview = icn; }
private:
KFileItem m_item;
KDirModelDirNode* const m_parent;
QIcon m_preview;
};
// Specialization for directory nodes
class KDirModelDirNode : public KDirModelNode
{
public:
KDirModelDirNode( KDirModelDirNode* parent, const KFileItem& item)
: KDirModelNode( parent, item),
m_childNodes(),
m_childCount(KDirModel::ChildCountUnknown),
m_populated(false)
{}
~KDirModelDirNode() {
qDeleteAll(m_childNodes);
}
QList<KDirModelNode *> m_childNodes; // owns the nodes
// If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); }
void setChildCount(int count) { m_childCount = count; }
bool isPopulated() const { return m_populated; }
void setPopulated( bool populated ) { m_populated = populated; }
// For removing all child urls from the global hash.
void collectAllChildUrls(KUrl::List &urls) const {
Q_FOREACH(KDirModelNode* node, m_childNodes) {
const KFileItem& item = node->item();
urls.append(cleanupUrl(item.url()));
if (item.isDir())
static_cast<KDirModelDirNode*>(node)->collectAllChildUrls(urls);
}
}
private:
int m_childCount:31;
bool m_populated:1;
};
int KDirModelNode::rowNumber() const
{
if (!m_parent) return 0;
return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode*>(this));
}
////
class KDirModelPrivate
{
public:
KDirModelPrivate( KDirModel* model )
: q(model), m_dirLister(0),
m_rootNode(new KDirModelDirNode(0, KFileItem())),
m_dropsAllowed(KDirModel::NoDrops), m_jobTransfersVisible(false)
{
}
~KDirModelPrivate() {
delete m_rootNode;
}
void _k_slotNewItems(const KUrl& directoryUrl, const KFileItemList&);
void _k_slotDeleteItems(const KFileItemList&);
void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&);
void _k_slotClear();
void _k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl);
void _k_slotJobUrlsChanged(const QStringList& urlList);
void clear() {
delete m_rootNode;
m_rootNode = new KDirModelDirNode(0, KFileItem());
}
// Emit expand for each parent and then return the
// last known parent if there is no node for this url
KDirModelNode* expandAllParentsUntil(const KUrl& url) const;
// Return the node for a given url, using the hash.
KDirModelNode* nodeForUrl(const KUrl& url) const;
KDirModelNode* nodeForIndex(const QModelIndex& index) const;
QModelIndex indexForNode(KDirModelNode* node, int rowNumber = -1 /*unknown*/) const;
bool isDir(KDirModelNode* node) const {
return (node == m_rootNode) || node->item().isDir();
}
KUrl urlForNode(KDirModelNode* node) const {
/**
* Queries and fragments are removed from the URL, so that the URL of
* child items really starts with the URL of the parent.
*
* For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100
* so we have to remove the query in both to be able to compare the URLs
*/
KUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url());
if (url.hasQuery() || url.hasRef()) { // avoid detach if not necessary.
url.setQuery(QString());
url.setRef(QString()); // kill ref (#171117)
}
return url;
}
void removeFromNodeHash(KDirModelNode* node, const KUrl& url);
#ifndef NDEBUG
void dump();
#endif
KDirModel* q;
KDirLister* m_dirLister;
KDirModelDirNode* m_rootNode;
KDirModel::DropsAllowed m_dropsAllowed;
bool m_jobTransfersVisible;
// key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
// value = final url[s] being fetched
QMap<KDirModelNode*, KUrl::List> m_urlsBeingFetched;
QHash<KUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
QStringList m_allCurrentDestUrls; //list of all dest urls that have jobs on them (e.g. copy, download)
};
KDirModelNode* KDirModelPrivate::nodeForUrl(const KUrl& _url) const // O(1), well, O(length of url as a string)
{
KUrl url = cleanupUrl(_url);
if (url == urlForNode(m_rootNode))
return m_rootNode;
return m_nodeHash.value(url);
}
void KDirModelPrivate::removeFromNodeHash(KDirModelNode* node, const KUrl& url)
{
if (node->item().isDir()) {
KUrl::List urls;
static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(urls);
Q_FOREACH(const KUrl& u, urls) {
m_nodeHash.remove(u);
}
}
m_nodeHash.remove(cleanupUrl(url));
}
KDirModelNode* KDirModelPrivate::expandAllParentsUntil(const KUrl& _url) const // O(depth)
{
KUrl url = cleanupUrl(_url);
//kDebug(7008) << url;
KUrl nodeUrl = urlForNode(m_rootNode);
if (url == nodeUrl)
return m_rootNode;
// Protocol mismatch? Don't even start comparing paths then. #171721
if (url.protocol() != nodeUrl.protocol())
return 0;
const QString pathStr = url.path(); // no trailing slash
KDirModelDirNode* dirNode = m_rootNode;
if (!pathStr.startsWith(nodeUrl.path())) {
return 0;
}
for (;;) {
const QString nodePath = nodeUrl.path(KUrl::AddTrailingSlash);
if(!pathStr.startsWith(nodePath)) {
kError(7008) << "The kioslave for" << url.protocol() << "violates the hierarchy structure:"
<< "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
return 0;
}
// E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b
const int nextSlash = pathStr.indexOf('/', nodePath.length());
const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1
nodeUrl.setPath(newPath);
nodeUrl.adjustPath(KUrl::RemoveTrailingSlash); // #172508
KDirModelNode* node = nodeForUrl(nodeUrl);
if (!node) {
//kDebug(7008) << "child equal or starting with" << url << "not found";
// return last parent found:
return dirNode;
}
emit q->expand(indexForNode(node));
//kDebug(7008) << " nodeUrl=" << nodeUrl;
if (nodeUrl == url) {
//kDebug(7008) << "Found node" << node << "for" << url;
return node;
}
//kDebug(7008) << "going into" << node->item().url();
Q_ASSERT(isDir(node));
dirNode = static_cast<KDirModelDirNode *>(node);
}
// NOTREACHED
//return 0;
}
#ifndef NDEBUG
void KDirModelPrivate::dump()
{
kDebug() << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
QHashIterator<KUrl, KDirModelNode *> it(m_nodeHash);
while (it.hasNext()) {
it.next();
kDebug() << it.key() << it.value();
}
}
#endif
// node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
QModelIndex KDirModelPrivate::indexForNode(KDirModelNode* node, int rowNumber) const
{
if (node == m_rootNode)
return QModelIndex();
Q_ASSERT(node->parent());
return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
}
// index -> node. O(1)
KDirModelNode* KDirModelPrivate::nodeForIndex(const QModelIndex& index) const
{
return index.isValid()
? static_cast<KDirModelNode*>(index.internalPointer())
: m_rootNode;
}
/*
* This model wraps the data held by KDirLister.
*
* The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
* E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
*
* Invalid parent index means root of the tree, m_rootNode
*/
#ifndef NDEBUG
static QString debugIndex(const QModelIndex& index)
{
QString str;
if (!index.isValid())
str = "[invalid index, i.e. root]";
else {
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
str = "[index for " + node->item().url().pathOrUrl();
if (index.column() > 0)
str += ", column " + QString::number(index.column());
str += ']';
}
return str;
}
#endif
KDirModel::KDirModel(QObject* parent)
: QAbstractItemModel(parent),
d(new KDirModelPrivate(this))
{
setDirLister(new KDirLister(this));
}
KDirModel::~KDirModel()
{
delete d;
}
void KDirModel::setDirLister(KDirLister* dirLister)
{
if (d->m_dirLister) {
d->clear();
delete d->m_dirLister;
}
d->m_dirLister = dirLister;
d->m_dirLister->setParent(this);
connect( d->m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)),
this, SLOT(_k_slotNewItems(KUrl,KFileItemList)) );
connect( d->m_dirLister, SIGNAL(itemsDeleted(KFileItemList)),
this, SLOT(_k_slotDeleteItems(KFileItemList)) );
connect( d->m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem, KFileItem> >)),
this, SLOT(_k_slotRefreshItems(QList<QPair<KFileItem, KFileItem> >)) );
connect( d->m_dirLister, SIGNAL(clear()),
this, SLOT(_k_slotClear()) );
connect(d->m_dirLister, SIGNAL(redirection(KUrl, KUrl)),
this, SLOT(_k_slotRedirection(KUrl, KUrl)));
}
KDirLister* KDirModel::dirLister() const
{
return d->m_dirLister;
}
void KDirModelPrivate::_k_slotNewItems(const KUrl& directoryUrl, const KFileItemList& items)
{
//kDebug(7008) << "directoryUrl=" << directoryUrl;
KDirModelNode* result = nodeForUrl(directoryUrl); // O(depth)
// If the directory containing the items wasn't found, then we have a big problem.
// Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead.
if (!result) {
kError(7008) << "Items emitted in directory" << directoryUrl
<< "but that directory isn't in KDirModel!"
<< "Root directory:" << urlForNode(m_rootNode);
Q_FOREACH(const KFileItem& item, items) {
kDebug() << "Item:" << item.url();
}
#ifndef NDEBUG
dump();
#endif
Q_ASSERT(result);
}
Q_ASSERT(isDir(result));
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(result);
const QModelIndex index = indexForNode(dirNode); // O(n)
const int newItemsCount = items.count();
const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
#if 0
#ifndef NDEBUG // debugIndex only defined in debug mode
kDebug(7008) << items.count() << "in" << directoryUrl
<< "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
#endif
#endif
q->beginInsertRows( index, newRowCount - newItemsCount, newRowCount - 1 ); // parent, first, last
const KUrl::List urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
//kDebug(7008) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
QList<QModelIndex> emitExpandFor;
KFileItemList::const_iterator it = items.begin();
KFileItemList::const_iterator end = items.end();
for ( ; it != end ; ++it ) {
const bool isDir = it->isDir();
KDirModelNode* node = isDir
? new KDirModelDirNode( dirNode, *it )
: new KDirModelNode( dirNode, *it );
#ifndef NDEBUG
// Test code for possible duplication of items in the childnodes list,
// not sure if/how it ever happened.
//if (dirNode->m_childNodes.count() &&
// dirNode->m_childNodes.last()->item().name() == (*it).name())
// kFatal() << "Already having" << (*it).name() << "in" << directoryUrl
// << "url=" << dirNode->m_childNodes.last()->item().url();
#endif
dirNode->m_childNodes.append(node);
const KUrl url = it->url();
m_nodeHash.insert(cleanupUrl(url), node);
//kDebug(7008) << url;
if (!urlsBeingFetched.isEmpty()) {
const KUrl dirUrl = url;
foreach(const KUrl& urlFetched, urlsBeingFetched) {
if (dirUrl.isParentOf(urlFetched)) {
kDebug(7008) << "Listing found" << dirUrl << "which is a parent of fetched url" << urlFetched;
const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count()-1);
Q_ASSERT(parentIndex.isValid());
emitExpandFor.append(parentIndex);
if (isDir && dirUrl != urlFetched) {
q->fetchMore(parentIndex);
m_urlsBeingFetched[node].append(urlFetched);
}
}
}
}
}
m_urlsBeingFetched.remove(dirNode);
q->endInsertRows();
// Emit expand signal after rowsInserted signal has been emitted,
// so that any proxy model will have updated its mapping already
Q_FOREACH(const QModelIndex& idx, emitExpandFor) {
emit q->expand(idx);
}
}
void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList& items)
{
//kDebug(7008) << items.count();
// I assume all items are from the same directory.
// From KDirLister's code, this should be the case, except maybe emitChanges?
const KFileItem item = items.first();
Q_ASSERT(!item.isNull());
KUrl url = item.url();
KDirModelNode* node = nodeForUrl(url); // O(depth)
if (!node) {
kWarning(7008) << "No node found for item that was just removed:" << url;
return;
}
KDirModelDirNode* dirNode = node->parent();
if (!dirNode)
return;
QModelIndex parentIndex = indexForNode(dirNode); // O(n)
// Short path for deleting a single item
if (items.count() == 1) {
const int r = node->rowNumber();
q->beginRemoveRows(parentIndex, r, r);
removeFromNodeHash(node, url);
delete dirNode->m_childNodes.takeAt(r);
q->endRemoveRows();
return;
}
// We need to make lists of consecutive row numbers, for the beginRemoveRows call.
// Let's use a bit array where each bit represents a given child node.
const int childCount = dirNode->m_childNodes.count();
QBitArray rowNumbers(childCount, false);
Q_FOREACH(const KFileItem& item, items) {
if (!node) { // don't lookup the first item twice
url = item.url();
node = nodeForUrl(url);
if (!node) {
kWarning(7008) << "No node found for item that was just removed:" << url;
continue;
}
+ if (!node->parent()) {
+ // The root node has been deleted, but it was not first in the list 'items'.
+ // see https://bugs.kde.org/show_bug.cgi?id=196695
+ return;
+ }
}
rowNumbers.setBit(node->rowNumber(), 1); // O(n)
removeFromNodeHash(node, url);
node = 0;
}
int start = -1;
int end = -1;
bool lastVal = false;
// Start from the end, otherwise all the row numbers are offset while we go
for (int i = childCount - 1; i >= 0; --i) {
const bool val = rowNumbers.testBit(i);
if (!lastVal && val) {
end = i;
//kDebug(7008) << "end=" << end;
}
if ((lastVal && !val) || (i == 0 && val)) {
start = val ? i : i + 1;
//kDebug(7008) << "beginRemoveRows" << start << end;
q->beginRemoveRows(parentIndex, start, end);
for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
//kDebug(7008) << "Removing from m_childNodes at" << r;
delete dirNode->m_childNodes.takeAt(r);
}
q->endRemoveRows();
}
lastVal = val;
}
}
void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
{
QModelIndex topLeft, bottomRight;
// Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
// Solution 2: more fine-grained, actually figure out the beginning and end rows.
for ( QList<QPair<KFileItem, KFileItem> >::const_iterator fit = items.begin(), fend = items.end() ; fit != fend ; ++fit ) {
Q_ASSERT(!fit->first.isNull());
Q_ASSERT(!fit->second.isNull());
const KUrl oldUrl = fit->first.url();
const KUrl newUrl = fit->second.url();
KDirModelNode* node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
//kDebug(7008) << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
if (!node) // not found [can happen when renaming a dir, redirection was emitted already]
continue;
if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
bool hasNewNode = false;
// A file became directory (well, it was overwritten)
if (fit->first.isDir() != fit->second.isDir()) {
//kDebug(7008) << "DIR/FILE STATUS CHANGE";
const int r = node->rowNumber();
removeFromNodeHash(node, oldUrl);
KDirModelDirNode* dirNode = node->parent();
delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second)
: new KDirModelNode(dirNode, fit->second);
dirNode->m_childNodes.insert(r, node); // same position!
hasNewNode = true;
} else {
node->setItem(fit->second);
}
if (oldUrl != newUrl || hasNewNode) {
// What if a renamed dir had children? -> kdirlister takes care of emitting for each item
//kDebug(7008) << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
m_nodeHash.remove(cleanupUrl(oldUrl));
m_nodeHash.insert(cleanupUrl(newUrl), node);
}
// Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
if (fit->first.mimeTypePtr()->name() != fit->second.mimeTypePtr()->name()) {
node->setPreview(QIcon());
}
const QModelIndex index = indexForNode(node);
if (!topLeft.isValid() || index.row() < topLeft.row()) {
topLeft = index;
}
if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
bottomRight = index;
}
}
}
#ifndef NDEBUG // debugIndex only defined in debug mode
kDebug(7008) << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
#endif
bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex())-1);
emit q->dataChanged(topLeft, bottomRight);
}
// Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup)
// and when renaming a directory.
void KDirModelPrivate::_k_slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
{
KDirModelNode* node = nodeForUrl(oldUrl);
if (!node)
return;
m_nodeHash.remove(cleanupUrl(oldUrl));
m_nodeHash.insert(cleanupUrl(newUrl), node);
// Ensure the node's URL is updated. In case of a listjob redirection
// we won't get a refreshItem, and in case of renaming a directory
// we'll get it too late (so the hash won't find the old url anymore).
KFileItem item = node->item();
if (!item.isNull()) { // null if root item, #180156
item.setUrl(newUrl);
node->setItem(item);
}
// The items inside the renamed directory have been handled before,
// KDirLister took care of emitting refreshItem for each of them.
}
void KDirModelPrivate::_k_slotClear()
{
const int numRows = m_rootNode->m_childNodes.count();
if (numRows > 0) {
q->beginRemoveRows( QModelIndex(), 0, numRows - 1 );
q->endRemoveRows();
}
m_nodeHash.clear();
//emit layoutAboutToBeChanged();
clear();
//emit layoutChanged();
}
void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList& urlList)
{
m_allCurrentDestUrls = urlList;
}
void KDirModel::itemChanged( const QModelIndex& index )
{
// This method is really a itemMimeTypeChanged(), it's mostly called by KMimeTypeResolver.
// When the mimetype is determined, clear the old "preview" (could be
// mimetype dependent like when cutting files, #164185)
KDirModelNode* node = d->nodeForIndex(index);
if (node)
node->setPreview(QIcon());
#ifndef NDEBUG // debugIndex only defined in debug mode
//kDebug(7008) << "dataChanged(" << debugIndex(index);
#endif
emit dataChanged(index, index);
}
int KDirModel::columnCount( const QModelIndex & ) const
{
return ColumnCount;
}
QVariant KDirModel::data( const QModelIndex & index, int role ) const
{
if (index.isValid()) {
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
const KFileItem& item( node->item() );
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case Name:
return item.text();
case Size:
//
//return KIO::convertSize(item->size());
// Default to "file size in bytes" like in kde3's filedialog
return KGlobal::locale()->formatNumber(item.size(), 0);
case ModifiedTime: {
KDateTime dt = item.time(KFileItem::ModificationTime);
return KGlobal::locale()->formatDateTime(dt);
}
case Permissions:
return item.permissionsString();
case Owner:
return item.user();
case Group:
return item.group();
case Type:
return item.mimeComment();
}
break;
case Qt::EditRole:
switch (index.column()) {
case Name:
return item.text();
}
break;
case Qt::DecorationRole:
if (index.column() == Name) {
if (!node->preview().isNull()) {
//kDebug(7008) << item->url() << " preview found";
return node->preview();
}
Q_ASSERT(!item.isNull());
//kDebug(7008) << item->url() << " overlays=" << item->overlays();
return KIcon(item.iconName(), 0, item.overlays());
}
break;
case Qt::TextAlignmentRole:
if (index.column() == Size) {
// use a right alignment for L2R and R2L languages
const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter;
return int(alignment);
}
break;
case Qt::ToolTipRole:
return item.text();
case FileItemRole:
return QVariant::fromValue(item);
case ChildCountRole:
if (!item.isDir())
return ChildCountUnknown;
else {
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(node);
int count = dirNode->childCount();
if (count == ChildCountUnknown && item.isReadable()) {
const QString path = item.localPath();
if (!path.isEmpty()) {
// slow
// QDir dir(path);
// count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
#ifdef Q_WS_WIN
QString s = path + QLatin1String( "\\*.*" );
s.replace('/', '\\');
count = 0;
WIN32_FIND_DATA findData;
HANDLE hFile = FindFirstFile( (LPWSTR)s.utf16(), &findData );
if( hFile != INVALID_HANDLE_VALUE ) {
do {
if (!( findData.cFileName[0] == '.' &&
findData.cFileName[1] == '\0' ) &&
!( findData.cFileName[0] == '.' &&
findData.cFileName[1] == '.' &&
findData.cFileName[2] == '\0' ) )
++count;
} while( FindNextFile( hFile, &findData ) != 0 );
FindClose( hFile );
}
#else
DIR* dir = ::opendir(QFile::encodeName(path));
if (dir) {
count = 0;
struct dirent *dirEntry = 0;
while ((dirEntry = ::readdir(dir))) {
if (dirEntry->d_name[0] == '.') {
if (dirEntry->d_name[1] == '\0') // skip "."
continue;
if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') // skip ".."
continue;
}
++count;
}
::closedir(dir);
}
#endif
//kDebug(7008) << "child count for " << path << ":" << count;
dirNode->setChildCount(count);
}
}
return count;
}
case HasJobRole:
if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
KDirModelNode* node = d->nodeForIndex(index);
const QString url = node->item().url().url();
//return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
return QVariant(d->m_allCurrentDestUrls.contains(url));
}
}
}
return QVariant();
}
void KDirModel::sort( int column, Qt::SortOrder order )
{
// Not implemented - we should probably use QSortFilterProxyModel instead.
return QAbstractItemModel::sort(column, order);
}
bool KDirModel::setData( const QModelIndex & index, const QVariant & value, int role )
{
switch (role) {
case Qt::EditRole:
if (index.column() == Name && value.type() == QVariant::String) {
Q_ASSERT(index.isValid());
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
const KFileItem& item = node->item();
const QString newName = value.toString();
- if (newName.isEmpty() || newName == item.text())
+ if (newName.isEmpty() || newName == item.text() || (newName == QLatin1String(".")) || (newName == QLatin1String("..")))
return true;
KUrl newurl(item.url());
- newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + newName);
+ newurl.setPath(newurl.directory(KUrl::AppendTrailingSlash) + KIO::encodeFileName(newName));
KIO::Job * job = KIO::moveAs(item.url(), newurl, newurl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
job->ui()->setAutoErrorHandlingEnabled(true);
// undo handling
KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Rename, item.url(), newurl, job );
return true;
}
break;
case Qt::DecorationRole:
if (index.column() == Name) {
Q_ASSERT(index.isValid());
// Set new pixmap - e.g. preview
KDirModelNode* node = static_cast<KDirModelNode*>(index.internalPointer());
//kDebug(7008) << "setting icon for " << node->item()->url();
Q_ASSERT(node);
if (value.type() == QVariant::Icon) {
const QIcon icon(qvariant_cast<QIcon>(value));
node->setPreview(icon);
} else if (value.type() == QVariant::Pixmap) {
node->setPreview(qvariant_cast<QPixmap>(value));
}
emit dataChanged(index, index);
return true;
}
break;
default:
break;
}
return false;
}
int KDirModel::rowCount( const QModelIndex & parent ) const
{
KDirModelNode* node = d->nodeForIndex(parent);
if (!node || !d->isDir(node)) // #176555
return 0;
KDirModelDirNode* parentNode = static_cast<KDirModelDirNode *>(node);
Q_ASSERT(parentNode);
const int count = parentNode->m_childNodes.count();
#if 0
QStringList filenames;
for (int i = 0; i < count; ++i) {
filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
}
kDebug(7008) << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
#endif
return count;
}
// sibling() calls parent() and isn't virtual! So parent() should be fast...
QModelIndex KDirModel::parent( const QModelIndex & index ) const
{
if (!index.isValid())
return QModelIndex();
KDirModelNode* childNode = static_cast<KDirModelNode*>(index.internalPointer());
Q_ASSERT(childNode);
KDirModelNode* parentNode = childNode->parent();
Q_ASSERT(parentNode);
return d->indexForNode(parentNode); // O(n)
}
static bool lessThan(const KUrl &left, const KUrl &right)
{
return left.url().compare(right.url()) < 0;
}
void KDirModel::requestSequenceIcon(const QModelIndex& index, int sequenceIndex)
{
emit needSequenceIcon(index, sequenceIndex);
}
void KDirModel::setJobTransfersVisible(bool value)
{
if(value) {
d->m_jobTransfersVisible = true;
connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection);
JobUrlCache::instance().requestJobUrlsChanged();
} else {
disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList)));
}
}
bool KDirModel::jobTransfersVisible() const
{
return d->m_jobTransfersVisible;
}
KUrl::List KDirModel::simplifiedUrlList(const KUrl::List &urls)
{
if (!urls.count()) {
return urls;
}
KUrl::List ret(urls);
qSort(ret.begin(), ret.end(), lessThan);
KUrl::List::iterator it = ret.begin();
KUrl url = *it;
++it;
while (it != ret.end()) {
if (url.isParentOf(*it)) {
it = ret.erase(it);
} else {
url = *it;
++it;
}
}
return ret;
}
QStringList KDirModel::mimeTypes( ) const
{
return KUrl::List::mimeDataTypes();
}
QMimeData * KDirModel::mimeData( const QModelIndexList & indexes ) const
{
KUrl::List urls, mostLocalUrls;
bool canUseMostLocalUrls = true;
foreach (const QModelIndex &index, indexes) {
const KFileItem& item = d->nodeForIndex(index)->item();
urls << item.url();
bool isLocal;
mostLocalUrls << item.mostLocalUrl(isLocal);
if (!isLocal)
canUseMostLocalUrls = false;
}
QMimeData *data = new QMimeData();
const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
urls = simplifiedUrlList(urls);
if (different) {
mostLocalUrls = simplifiedUrlList(mostLocalUrls);
urls.populateMimeData(mostLocalUrls, data);
} else {
urls.populateMimeData(data);
}
// for compatibility reasons (when dropping or pasting into kde3 applications)
QString application_x_qiconlist;
const int items = urls.count();
for (int i = 0; i < items; i++) {
const int offset = i*16;
QString tmp("%1$@@$%2$@@$32$@@$32$@@$%3$@@$%4$@@$32$@@$16$@@$no data$@@$");
application_x_qiconlist += tmp.arg(offset).arg(offset).arg(offset).arg(offset+40);
}
data->setData("application/x-qiconlist", application_x_qiconlist.toLatin1());
return data;
}
// Public API; not much point in calling it internally
KFileItem KDirModel::itemForIndex( const QModelIndex& index ) const
{
if (!index.isValid()) {
return d->m_dirLister->rootItem();
} else {
return static_cast<KDirModelNode*>(index.internalPointer())->item();
}
}
#ifndef KDE_NO_DEPRECATED
QModelIndex KDirModel::indexForItem( const KFileItem* item ) const
{
// Note that we can only use the URL here, not the pointer.
// KFileItems can be copied.
return indexForUrl(item->url()); // O(n)
}
#endif
QModelIndex KDirModel::indexForItem( const KFileItem& item ) const
{
// Note that we can only use the URL here, not the pointer.
// KFileItems can be copied.
return indexForUrl(item.url()); // O(n)
}
// url -> index. O(n)
QModelIndex KDirModel::indexForUrl(const KUrl& url) const
{
KDirModelNode* node = d->nodeForUrl(url); // O(depth)
if (!node) {
kDebug(7007) << url << "not found";
return QModelIndex();
}
return d->indexForNode(node); // O(n)
}
QModelIndex KDirModel::index( int row, int column, const QModelIndex & parent ) const
{
KDirModelNode* parentNode = d->nodeForIndex(parent); // O(1)
Q_ASSERT(parentNode);
Q_ASSERT(d->isDir(parentNode));
KDirModelNode* childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
if (childNode)
return createIndex(row, column, childNode);
else
return QModelIndex();
}
QVariant KDirModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if (orientation == Qt::Horizontal) {
switch (role) {
case Qt::DisplayRole:
switch (section) {
case Name:
return i18nc("@title:column","Name");
case Size:
return i18nc("@title:column","Size");
case ModifiedTime:
return i18nc("@title:column","Date");
case Permissions:
return i18nc("@title:column","Permissions");
case Owner:
return i18nc("@title:column","Owner");
case Group:
return i18nc("@title:column","Group");
case Type:
return i18nc("@title:column","Type");
}
}
}
return QVariant();
}
bool KDirModel::hasChildren( const QModelIndex & parent ) const
{
if (!parent.isValid())
return true;
const KFileItem& parentItem = static_cast<KDirModelNode*>(parent.internalPointer())->item();
Q_ASSERT(!parentItem.isNull());
return parentItem.isDir();
}
Qt::ItemFlags KDirModel::flags( const QModelIndex & index ) const
{
Qt::ItemFlags f = Qt::ItemIsEnabled;
if (index.column() == Name) {
f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
}
// Allow dropping onto this item?
if (d->m_dropsAllowed != NoDrops) {
if(!index.isValid()) {
if (d->m_dropsAllowed & DropOnDirectory) {
f |= Qt::ItemIsDropEnabled;
}
} else {
KFileItem item = itemForIndex(index);
if (item.isNull()) {
kWarning(7007) << "Invalid item returned for index";
} else if (item.isDir()) {
if (d->m_dropsAllowed & DropOnDirectory) {
f |= Qt::ItemIsDropEnabled;
}
} else { // regular file item
if (d->m_dropsAllowed & DropOnAnyFile)
f |= Qt::ItemIsDropEnabled;
else if (d->m_dropsAllowed & DropOnLocalExecutable) {
if (!item.localPath().isEmpty()) {
// Desktop file?
if (item.mimeTypePtr()->is("application/x-desktop"))
f |= Qt::ItemIsDropEnabled;
// Executable, shell script ... ?
else if ( QFileInfo( item.localPath() ).isExecutable() )
f |= Qt::ItemIsDropEnabled;
}
}
}
}
}
return f;
}
bool KDirModel::canFetchMore( const QModelIndex & parent ) const
{
if (!parent.isValid())
return false;
// We now have a bool KDirModelNode::m_populated,
// to avoid calling fetchMore more than once on empty dirs.
// But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
// Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
KDirModelNode* node = static_cast<KDirModelNode*>(parent.internalPointer());
const KFileItem& item = node->item();
return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated()
&& static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
}
void KDirModel::fetchMore( const QModelIndex & parent )
{
if (!parent.isValid())
return;
KDirModelNode* parentNode = static_cast<KDirModelNode*>(parent.internalPointer());
KFileItem parentItem = parentNode->item();
Q_ASSERT(!parentItem.isNull());
Q_ASSERT(parentItem.isDir());
KDirModelDirNode* dirNode = static_cast<KDirModelDirNode *>(parentNode);
if( dirNode->isPopulated() )
return;
dirNode->setPopulated( true );
const KUrl parentUrl = parentItem.url();
d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
}
bool KDirModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
{
// Not sure we want to implement any drop handling at this level,
// but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
Q_UNUSED(data);
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(column);
Q_UNUSED(parent);
return false;
}
void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed)
{
d->m_dropsAllowed = dropsAllowed;
}
void KDirModel::expandToUrl(const KUrl& url)
{
// emit expand for each parent and return last parent
KDirModelNode* result = d->expandAllParentsUntil(url); // O(depth)
//kDebug(7008) << url << result;
if (!result) // doesn't seem related to our base url?
return;
if (!(result->item().isNull()) && result->item().url() == url) {
// We have it already, nothing to do
kDebug(7008) << "have it already item=" <<url /*result->item()*/;
return;
}
d->m_urlsBeingFetched[result].append(url);
if (result == d->m_rootNode) {
kDebug(7008) << "Remembering to emit expand after listing the root url";
// the root is fetched by default, so it must be currently being fetched
return;
}
kDebug(7008) << "Remembering to emit expand after listing" << result->item().url();
// start a new fetch to look for the next level down the URL
const QModelIndex parentIndex = d->indexForNode(result); // O(n)
Q_ASSERT(parentIndex.isValid());
fetchMore(parentIndex);
}
bool KDirModel::insertRows(int , int, const QModelIndex&)
{
return false;
}
bool KDirModel::insertColumns(int, int, const QModelIndex&)
{
return false;
}
bool KDirModel::removeRows(int, int, const QModelIndex&)
{
return false;
}
bool KDirModel::removeColumns(int, int, const QModelIndex&)
{
return false;
}
#include "kdirmodel.moc"
diff --git a/kio/kio/kfileitemdelegate.cpp b/kio/kio/kfileitemdelegate.cpp
index cb3939d772..27c8fd3496 100644
--- a/kio/kio/kfileitemdelegate.cpp
+++ b/kio/kio/kfileitemdelegate.cpp
@@ -1,1690 +1,1692 @@
/*
This file is part of the KDE project
Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net>
Copyright © 2006-2007, 2008 Fredrik Höglund <fredrik@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfileitemdelegate.h"
#include "imagefilter_p.h"
#include <config.h> // for HAVE_XRENDER
#include <QApplication>
#include <QStyle>
#include <QModelIndex>
#include <QPainter>
#include <QCache>
#include <QImage>
#include <QPainterPath>
#include <QTextLayout>
#include <QListView>
#include <QPaintEngine>
#include <qmath.h>
#include <kglobal.h>
#include <klocale.h>
#include <kicon.h>
#include <kiconloader.h>
#include <kiconeffect.h>
#include <kdirmodel.h>
#include <kfileitem.h>
#include <kcolorscheme.h>
#include <kglobalsettings.h>
#include <ktextedit.h>
#include <kstringhandler.h>
#include "delegateanimationhandler_p.h"
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
# include <X11/Xlib.h>
# include <X11/extensions/Xrender.h>
# include <QX11Info>
# undef KeyPress
# undef FocusOut
#endif
struct Margin
{
int left, right, top, bottom;
};
class KFileItemDelegate::Private
{
public:
enum MarginType { ItemMargin = 0, TextMargin, IconMargin, NMargins };
Private(KFileItemDelegate *parent);
~Private() {}
QSize decorationSizeHint(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const;
QSize displaySizeHint(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const;
QString replaceNewlines(const QString &string) const;
inline KFileItem fileItem(const QModelIndex &index) const;
QString elidedText(QTextLayout &layout, const QStyleOptionViewItemV4 &option, const QSize &maxSize) const;
QSize layoutText(QTextLayout &layout, const QStyleOptionViewItemV4 &option,
const QString &text, const QSize &constraints) const;
QSize layoutText(QTextLayout &layout, const QString &text, int maxWidth) const;
inline void setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItemV4 &options) const;
inline bool verticalLayout(const QStyleOptionViewItemV4 &option) const;
inline QBrush brush(const QVariant &value, const QStyleOptionViewItemV4 &option) const;
QBrush foregroundBrush(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const;
inline void setActiveMargins(Qt::Orientation layout);
void setVerticalMargin(MarginType type, int left, int right, int top, int bottom);
void setHorizontalMargin(MarginType type, int left, int right, int top, int bottom);
inline void setVerticalMargin(MarginType type, int hor, int ver);
inline void setHorizontalMargin(MarginType type, int hor, int ver);
inline QRect addMargin(const QRect &rect, MarginType type) const;
inline QRect subtractMargin(const QRect &rect, MarginType type) const;
inline QSize addMargin(const QSize &size, MarginType type) const;
inline QSize subtractMargin(const QSize &size, MarginType type) const;
QString itemSize(const QModelIndex &index, const KFileItem &item) const;
QString information(const QStyleOptionViewItemV4 &option, const QModelIndex &index, const KFileItem &item) const;
bool isListView(const QStyleOptionViewItemV4 &option) const;
QString display(const QModelIndex &index) const;
QIcon decoration(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const;
QPoint iconPosition(const QStyleOptionViewItemV4 &option) const;
QRect labelRectangle(const QStyleOptionViewItemV4 &option) const;
void layoutTextItems(const QStyleOptionViewItemV4 &option, const QModelIndex &index,
QTextLayout *labelLayout, QTextLayout *infoLayout, QRect *textBoundingRect) const;
void drawTextItems(QPainter *painter, const QTextLayout &labelLayout, const QTextLayout &infoLayout,
const QRect &textBoundingRect) const;
KIO::AnimationState *animationState(const QStyleOptionViewItemV4 &option, const QModelIndex &index,
const QAbstractItemView *view) const;
void restartAnimation(KIO::AnimationState* state);
QPixmap applyHoverEffect(const QPixmap &icon) const;
QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) const;
void initStyleOption(QStyleOptionViewItemV4 *option, const QModelIndex &index) const;
void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) const;
void gotNewIcon(const QModelIndex& index);
void paintJobTransfers(QPainter* painter, const qreal& jobAnimationAngle, const QPoint& iconPos, QStyleOptionViewItemV4 opt);
public:
KFileItemDelegate::InformationList informationList;
QColor shadowColor;
QPointF shadowOffset;
qreal shadowBlur;
QSize maximumSize;
bool showToolTipWhenElided;
QTextOption::WrapMode wrapMode;
bool jobTransfersVisible;
KIcon downArrowIcon;
private:
KFileItemDelegate * const q;
KIO::DelegateAnimationHandler *animationHandler;
Margin verticalMargin[NMargins];
Margin horizontalMargin[NMargins];
Margin *activeMargins;
};
KFileItemDelegate::Private::Private(KFileItemDelegate *parent)
: shadowColor(Qt::transparent), shadowOffset(1, 1), shadowBlur(2), maximumSize(0, 0),
showToolTipWhenElided(true), wrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ), jobTransfersVisible(false),
q(parent), animationHandler(new KIO::DelegateAnimationHandler(parent)), activeMargins(0)
{
}
void KFileItemDelegate::Private::setActiveMargins(Qt::Orientation layout)
{
activeMargins = (layout == Qt::Horizontal ?
horizontalMargin : verticalMargin);
}
void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int left, int top, int right, int bottom)
{
verticalMargin[type].left = left;
verticalMargin[type].right = right;
verticalMargin[type].top = top;
verticalMargin[type].bottom = bottom;
}
void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int left, int top, int right, int bottom)
{
horizontalMargin[type].left = left;
horizontalMargin[type].right = right;
horizontalMargin[type].top = top;
horizontalMargin[type].bottom = bottom;
}
void KFileItemDelegate::Private::setVerticalMargin(MarginType type, int horizontal, int vertical)
{
setVerticalMargin(type, horizontal, vertical, horizontal, vertical);
}
void KFileItemDelegate::Private::setHorizontalMargin(MarginType type, int horizontal, int vertical)
{
setHorizontalMargin(type, horizontal, vertical, horizontal, vertical);
}
QRect KFileItemDelegate::Private::addMargin(const QRect &rect, MarginType type) const
{
Q_ASSERT(activeMargins != 0);
const Margin &m = activeMargins[type];
return rect.adjusted(-m.left, -m.top, m.right, m.bottom);
}
QRect KFileItemDelegate::Private::subtractMargin(const QRect &rect, MarginType type) const
{
Q_ASSERT(activeMargins != 0);
const Margin &m = activeMargins[type];
return rect.adjusted(m.left, m.top, -m.right, -m.bottom);
}
QSize KFileItemDelegate::Private::addMargin(const QSize &size, MarginType type) const
{
Q_ASSERT(activeMargins != 0);
const Margin &m = activeMargins[type];
return QSize(size.width() + m.left + m.right, size.height() + m.top + m.bottom);
}
QSize KFileItemDelegate::Private::subtractMargin(const QSize &size, MarginType type) const
{
Q_ASSERT(activeMargins != 0);
const Margin &m = activeMargins[type];
return QSize(size.width() - m.left - m.right, size.height() - m.top - m.bottom);
}
// Returns the size of a file, or the number of items in a directory, as a QString
QString KFileItemDelegate::Private::itemSize(const QModelIndex &index, const KFileItem &item) const
{
// Return a formatted string containing the file size, if the item is a file
if (item.isFile())
return KGlobal::locale()->formatByteSize(item.size());
// Return the number of items in the directory
const QVariant value = index.data(KDirModel::ChildCountRole);
const int count = value.type() == QVariant::Int ? value.toInt() : KDirModel::ChildCountUnknown;
if (count == KDirModel::ChildCountUnknown) {
// was: i18nc("Items in a folder", "? items");
// but this just looks useless in a remote directory listing,
// better not show anything.
return QString();
}
return i18ncp("Items in a folder", "1 item", "%1 items", count);
}
// Returns the additional information string, if one should be shown, or an empty string otherwise
QString KFileItemDelegate::Private::information(const QStyleOptionViewItemV4 &option, const QModelIndex &index,
const KFileItem &item) const
{
QString string;
if (informationList.isEmpty() || item.isNull() || !isListView(option))
return string;
foreach (KFileItemDelegate::Information info, informationList)
{
if (info == KFileItemDelegate::NoInformation)
continue;
if (!string.isEmpty())
string += QChar::LineSeparator;
switch (info)
{
case KFileItemDelegate::Size:
string += itemSize(index, item);
break;
case KFileItemDelegate::Permissions:
string += item.permissionsString();
break;
case KFileItemDelegate::OctalPermissions:
string += QString('0') + QString::number(item.permissions(), 8);
break;
case KFileItemDelegate::Owner:
string += item.user();
break;
case KFileItemDelegate::OwnerAndGroup:
string += item.user() + ':' + item.group();
break;
case KFileItemDelegate::CreationTime:
string += item.timeString(KFileItem::CreationTime);
break;
case KFileItemDelegate::ModificationTime:
string += item.timeString(KFileItem::ModificationTime);
break;
case KFileItemDelegate::AccessTime:
string += item.timeString(KFileItem::AccessTime);
break;
case KFileItemDelegate::MimeType:
string += item.isMimeTypeKnown() ? item.mimetype() : i18nc("@info mimetype","Unknown");
break;
case KFileItemDelegate::FriendlyMimeType:
string += item.isMimeTypeKnown() ? item.mimeComment() : i18nc("@info mimetype","Unknown");
break;
case KFileItemDelegate::LinkDest:
string += item.linkDest();
break;
case KFileItemDelegate::LocalPathOrUrl:
if(!item.localPath().isEmpty())
string += item.localPath();
else
string += item.url().pathOrUrl();
break;
case KFileItemDelegate::Comment:
string += item.comment();
break;
default:
break;
} // switch (info)
} // foreach (info, list)
return string;
}
// Returns the KFileItem for the index
KFileItem KFileItemDelegate::Private::fileItem(const QModelIndex &index) const
{
const QVariant value = index.data(KDirModel::FileItemRole);
return qvariant_cast<KFileItem>(value);
}
// Replaces any newline characters in the provided string, with QChar::LineSeparator
QString KFileItemDelegate::Private::replaceNewlines(const QString &text) const
{
QString string = text;
const QChar newline = QLatin1Char('\n');
for (int i = 0; i < string.length(); i++)
if (string[i] == newline)
string[i] = QChar::LineSeparator;
return string;
}
// Lays the text out in a rectangle no larger than constraints, eliding it as necessary
QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QStyleOptionViewItemV4 &option,
const QString &text, const QSize &constraints) const
{
const QSize size = layoutText(layout, text, constraints.width());
if (size.width() > constraints.width() || size.height() > constraints.height())
{
const QString elided = elidedText(layout, option, constraints);
return layoutText(layout, elided, constraints.width());
}
return size;
}
// Lays the text out in a rectangle no wider than maxWidth
QSize KFileItemDelegate::Private::layoutText(QTextLayout &layout, const QString &text, int maxWidth) const
{
QFontMetrics metrics(layout.font());
int leading = metrics.leading();
int height = 0;
qreal widthUsed = 0;
QTextLine line;
layout.setText(text);
layout.beginLayout();
while ((line = layout.createLine()).isValid())
{
line.setLineWidth(maxWidth);
height += leading;
line.setPosition(QPoint(0, height));
height += int(line.height());
widthUsed = qMax(widthUsed, line.naturalTextWidth());
}
layout.endLayout();
return QSize(qCeil(widthUsed), height);
}
// Elides the text in the layout, by iterating over each line in the layout, eliding
// or word breaking the line if it's wider than the max width, and finally adding an
// ellipses at the end of the last line, if there are more lines than will fit within
// the vertical size constraints.
QString KFileItemDelegate::Private::elidedText(QTextLayout &layout, const QStyleOptionViewItemV4 &option,
const QSize &size) const
{
const QString text = layout.text();
int maxWidth = size.width();
int maxHeight = size.height();
qreal height = 0;
bool wrapText = (option.features & QStyleOptionViewItemV2::WrapText);
// If the string contains a single line of text that shouldn't be word wrapped
if (!wrapText && text.indexOf(QChar::LineSeparator) == -1)
return option.fontMetrics.elidedText(text, option.textElideMode, maxWidth);
// Elide each line that has already been laid out in the layout.
QString elided;
elided.reserve(text.length());
for (int i = 0; i < layout.lineCount(); i++)
{
QTextLine line = layout.lineAt(i);
int start = line.textStart();
int length = line.textLength();
height += option.fontMetrics.leading();
if (height + line.height() + option.fontMetrics.lineSpacing() > maxHeight)
{
// Unfortunately, if the line ends because of a line separator, elidedText() will be too
// clever and keep adding lines until it finds one that's too wide.
if (line.naturalTextWidth() < maxWidth && text[start + length - 1] == QChar::LineSeparator)
elided += text.mid(start, length - 1);
else
elided += option.fontMetrics.elidedText(text.mid(start), option.textElideMode, maxWidth);
break;
}
else if (line.naturalTextWidth() > maxWidth)
{
elided += option.fontMetrics.elidedText(text.mid(start, length), option.textElideMode, maxWidth);
if (!elided.endsWith(QChar::LineSeparator))
elided += QChar::LineSeparator;
}
else
elided += text.mid(start, length);
height += line.height();
}
return elided;
}
void KFileItemDelegate::Private::setLayoutOptions(QTextLayout &layout, const QStyleOptionViewItemV4 &option) const
{
QTextOption textoption;
textoption.setTextDirection(option.direction);
textoption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
textoption.setWrapMode((option.features & QStyleOptionViewItemV2::WrapText) ? wrapMode : QTextOption::NoWrap);
layout.setFont(option.font);
layout.setTextOption(textoption);
}
QSize KFileItemDelegate::Private::displaySizeHint(const QStyleOptionViewItemV4 &option,
const QModelIndex &index) const
{
QString label = option.text;
int maxWidth = 0;
if (maximumSize.isEmpty()) {
maxWidth = verticalLayout(option) && (option.features & QStyleOptionViewItemV2::WrapText)
? option.decorationSize.width() + 10 : 32757;
}
else {
const Margin &itemMargin = activeMargins[ItemMargin];
const Margin &textMargin = activeMargins[TextMargin];
maxWidth = maximumSize.width() -
(itemMargin.left + itemMargin.right) -
(textMargin.left + textMargin.right);
}
KFileItem item = fileItem(index);
// To compute the nominal size for the label + info, we'll just append
// the information string to the label
const QString info = information(option, index, item);
if (!info.isEmpty())
label += QString(QChar::LineSeparator) + info;
QTextLayout layout;
setLayoutOptions(layout, option);
QSize size = layoutText(layout, label, maxWidth);
if (!info.isEmpty())
{
// As soon as additional information is shown, it might be necessary that
// the label and/or the additional information must get elided. To prevent
// an expensive eliding in the scope of displaySizeHint, the maximum
// width is reserved instead.
size.setWidth(maxWidth);
}
return addMargin(size, TextMargin);
}
QSize KFileItemDelegate::Private::decorationSizeHint(const QStyleOptionViewItemV4 &option,
const QModelIndex &index) const
{
Q_UNUSED(index)
QSize iconSize = option.icon.actualSize(option.decorationSize);
if (!verticalLayout(option))
iconSize.rwidth() = option.decorationSize.width();
else if (iconSize.width() < option.decorationSize.width())
iconSize.rwidth() = qMin(iconSize.width() + 10, option.decorationSize.width());
if (iconSize.height() < option.decorationSize.height())
iconSize.rheight() = option.decorationSize.height();
return addMargin(iconSize, IconMargin);
}
bool KFileItemDelegate::Private::verticalLayout(const QStyleOptionViewItemV4 &option) const
{
return (option.decorationPosition == QStyleOptionViewItem::Top ||
option.decorationPosition == QStyleOptionViewItem::Bottom);
}
// Converts a QVariant of type Brush or Color to a QBrush
QBrush KFileItemDelegate::Private::brush(const QVariant &value, const QStyleOptionViewItemV4 &option) const
{
if (value.userType() == qMetaTypeId<KStatefulBrush>())
return qvariant_cast<KStatefulBrush>(value).brush(option.palette);
switch (value.type())
{
case QVariant::Color:
return QBrush(qvariant_cast<QColor>(value));
case QVariant::Brush:
return qvariant_cast<QBrush>(value);
default:
return QBrush(Qt::NoBrush);
}
}
QBrush KFileItemDelegate::Private::foregroundBrush(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const
{
QPalette::ColorGroup cg = QPalette::Active;
if (!(option.state & QStyle::State_Enabled)) {
cg = QPalette::Disabled;
} else if (!(option.state & QStyle::State_Active)) {
cg = QPalette::Inactive;
}
// Always use the highlight color for selected items
if (option.state & QStyle::State_Selected)
return option.palette.brush(cg, QPalette::HighlightedText);
// If the model provides its own foreground color/brush for this item
const QVariant value = index.data(Qt::ForegroundRole);
if (value.isValid())
return brush(value, option);
return option.palette.brush(cg, QPalette::Text);
}
bool KFileItemDelegate::Private::isListView(const QStyleOptionViewItemV4 &option) const
{
if (qobject_cast<const QListView*>(option.widget) || verticalLayout(option))
return true;
return false;
}
QPixmap KFileItemDelegate::Private::applyHoverEffect(const QPixmap &icon) const
{
KIconEffect *effect = KIconLoader::global()->iconEffect();
// Note that in KIconLoader terminology, active = hover.
// ### We're assuming that the icon group is desktop/filemanager, since this
// is KFileItemDelegate.
if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState))
return effect->apply(icon, KIconLoader::Desktop, KIconLoader::ActiveState);
return icon;
}
void KFileItemDelegate::Private::gotNewIcon(const QModelIndex& index)
{
animationHandler->gotNewIcon(index);
}
void KFileItemDelegate::Private::restartAnimation(KIO::AnimationState* state)
{
animationHandler->restartAnimation(state);
}
KIO::AnimationState *KFileItemDelegate::Private::animationState(const QStyleOptionViewItemV4 &option,
const QModelIndex &index,
const QAbstractItemView *view) const
{
if (!(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects)) {
return NULL;
}
if (index.column() == KDirModel::Name)
return animationHandler->animationState(option, index, view);
return NULL;
}
QPixmap KFileItemDelegate::Private::transition(const QPixmap &from, const QPixmap &to, qreal amount) const
{
int value = int(0xff * amount);
if (value == 0 || to.isNull())
return from;
if (value == 0xff || from.isNull())
return to;
QColor color;
color.setAlphaF(amount);
// FIXME: Somehow this doesn't work on Mac OS..
#if defined(Q_OS_MAC)
const bool usePixmap = false;
#else
const bool usePixmap = from.paintEngine()->hasFeature(QPaintEngine::PorterDuff) &&
from.paintEngine()->hasFeature(QPaintEngine::BlendModes);
#endif
// If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
if (usePixmap)
{
QPixmap under = from;
QPixmap over = to;
QPainter p;
p.begin(&over);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(over.rect(), color);
p.end();
p.begin(&under);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(under.rect(), color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawPixmap(0, 0, over);
p.end();
return under;
}
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
else if (from.paintEngine()->hasFeature(QPaintEngine::PorterDuff)) // We have Xrender support
{
// QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3,
// which we need to be able to do a transition from one pixmap to
// another.
//
// In order to avoid the overhead of converting the pixmaps to images
// and doing the operation entirely in software, this function has a
// specialized path for X11 that uses Xrender directly to do the
// transition. This operation can be fully accelerated in HW.
//
// This specialization can be removed when QX11PaintEngine supports
// CompositionMode_Plus.
QPixmap source(to), destination(from);
source.detach();
destination.detach();
Display *dpy = QX11Info::display();
XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8);
XRenderPictureAttributes pa;
pa.repeat = 1; // RepeatNormal
// Create a 1x1 8 bit repeating alpha picture
Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8);
Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa);
XFreePixmap(dpy, pixmap);
// Fill the alpha picture with the opacity value
XRenderColor xcolor;
xcolor.alpha = quint16(0xffff * amount);
XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1);
// Reduce the alpha of the destination with 1 - opacity
XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(),
0, 0, 0, 0, 0, 0, destination.width(), destination.height());
// Add source * opacity to the destination
XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha,
destination.x11PictureHandle(),
0, 0, 0, 0, 0, 0, destination.width(), destination.height());
XRenderFreePicture(dpy, alpha);
return destination;
}
#endif
else
{
// Fall back to using QRasterPaintEngine to do the transition.
QImage under = from.toImage();
QImage over = to.toImage();
QPainter p;
p.begin(&over);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(over.rect(), color);
p.end();
p.begin(&under);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(under.rect(), color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawImage(0, 0, over);
p.end();
return QPixmap::fromImage(under);
}
}
void KFileItemDelegate::Private::layoutTextItems(const QStyleOptionViewItemV4 &option, const QModelIndex &index,
QTextLayout *labelLayout, QTextLayout *infoLayout,
QRect *textBoundingRect) const
{
KFileItem item = fileItem(index);
const QString info = information(option, index, item);
bool showInformation = false;
setLayoutOptions(*labelLayout, option);
const QRect textArea = labelRectangle(option);
QRect textRect = subtractMargin(textArea, Private::TextMargin);
// Sizes and constraints for the different text parts
QSize maxLabelSize = textRect.size();
QSize maxInfoSize = textRect.size();
QSize labelSize;
QSize infoSize;
// If we have additional info text, and there's space for at least two lines of text,
// adjust the max label size to make room for at least one line of the info text
if (!info.isEmpty() && textRect.height() >= option.fontMetrics.lineSpacing() * 2)
{
infoLayout->setFont(labelLayout->font());
infoLayout->setTextOption(labelLayout->textOption());
maxLabelSize.rheight() -= option.fontMetrics.lineSpacing();
showInformation = true;
}
// Lay out the label text, and adjust the max info size based on the label size
labelSize = layoutText(*labelLayout, option, option.text, maxLabelSize);
maxInfoSize.rheight() -= labelSize.height();
// Lay out the info text
if (showInformation)
infoSize = layoutText(*infoLayout, option, info, maxInfoSize);
else
infoSize = QSize(0, 0);
// Compute the bounding rect of the text
const QSize size(qMax(labelSize.width(), infoSize.width()), labelSize.height() + infoSize.height());
*textBoundingRect = QStyle::alignedRect(option.direction, option.displayAlignment, size, textRect);
// Compute the positions where we should draw the layouts
labelLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y()));
infoLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y() + labelSize.height()));
}
void KFileItemDelegate::Private::drawTextItems(QPainter *painter, const QTextLayout &labelLayout,
const QTextLayout &infoLayout, const QRect &boundingRect) const
{
if (shadowColor.alpha() > 0)
{
QPixmap pixmap(boundingRect.size());
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
p.translate(-boundingRect.topLeft());
p.setPen(painter->pen());
labelLayout.draw(&p, QPoint());
if (!infoLayout.text().isEmpty())
{
QColor color = p.pen().color();
color.setAlphaF(0.6);
p.setPen(color);
infoLayout.draw(&p, QPoint());
}
p.end();
int padding = qCeil(shadowBlur);
int blurFactor = qRound(shadowBlur);
QImage image(boundingRect.size() + QSize(padding * 2, padding * 2), QImage::Format_ARGB32_Premultiplied);
image.fill(0);
p.begin(&image);
p.drawImage(padding, padding, pixmap.toImage());
p.end();
KIO::ImageFilter::shadowBlur(image, blurFactor, shadowColor);
painter->drawImage(boundingRect.topLeft() - QPoint(padding, padding) + shadowOffset.toPoint(), image);
painter->drawPixmap(boundingRect.topLeft(), pixmap);
return;
}
labelLayout.draw(painter, QPoint());
if (!infoLayout.text().isEmpty())
{
// TODO - for apps not doing funny things with the color palette,
// KColorScheme::InactiveText would be a much more correct choice. We
// should provide an API to specify what color to use for information.
QColor color = painter->pen().color();
color.setAlphaF(0.6);
painter->setPen(color);
infoLayout.draw(painter, QPoint());
}
}
void KFileItemDelegate::Private::initStyleOption(QStyleOptionViewItemV4 *option,
const QModelIndex &index) const
{
const KFileItem item = fileItem(index);
bool updateFontMetrics = false;
// Try to get the font from the model
QVariant value = index.data(Qt::FontRole);
if (value.isValid()) {
option->font = qvariant_cast<QFont>(value).resolve(option->font);
updateFontMetrics = true;
}
// Use an italic font for symlinks
if (!item.isNull() && item.isLink()) {
option->font.setItalic(true);
updateFontMetrics = true;
}
if (updateFontMetrics)
option->fontMetrics = QFontMetrics(option->font);
// Try to get the alignment for the item from the model
value = index.data(Qt::TextAlignmentRole);
if (value.isValid())
option->displayAlignment = Qt::Alignment(value.toInt());
value = index.data(Qt::BackgroundRole);
if (value.isValid())
option->backgroundBrush = brush(value, *option);
option->text = display(index);
if (!option->text.isEmpty())
option->features |= QStyleOptionViewItemV2::HasDisplay;
option->icon = decoration(*option, index);
if (!option->icon.isNull())
option->features |= QStyleOptionViewItemV2::HasDecoration;
// ### Make sure this value is always true for now
option->showDecorationSelected = true;
}
void KFileItemDelegate::Private::paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, QStyleOptionViewItemV4 opt)
{
painter->save();
QSize iconSize = opt.icon.actualSize(opt.decorationSize);
QPixmap downArrow = downArrowIcon.pixmap(iconSize * 0.30);
//corner (less x and y than bottom-right corner) that we will center the painter around
QPoint bottomRightCorner = QPoint(iconPos.x() + iconSize.width() * 0.75, iconPos.y() + iconSize.height() * 0.60);
QPainter pixmapPainter(&downArrow);
//make the icon transparent and such
pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
pixmapPainter.fillRect(downArrow.rect(), QColor(255, 255, 255, 110));
painter->translate(bottomRightCorner);
painter->drawPixmap(-downArrow.size().width() * .50, -downArrow.size().height() * .50, downArrow);
//animate the circles by rotating the painter around the center point..
painter->rotate(jobAnimationAngle);
painter->setPen(QColor(20, 20, 20, 80));
painter->setBrush(QColor(250, 250, 250, 90));
int radius = iconSize.width() * 0.04;
int spacing = radius * 4.5;
//left
painter->drawEllipse(QPoint(-spacing, 0), radius, radius);
//right
painter->drawEllipse(QPoint(spacing, 0), radius, radius);
//up
painter->drawEllipse(QPoint(0, -spacing), radius, radius);
//down
painter->drawEllipse(QPoint(0, spacing), radius, radius);
painter->restore();
}
// ---------------------------------------------------------------------------
KFileItemDelegate::KFileItemDelegate(QObject *parent)
: QAbstractItemDelegate(parent), d(new Private(this))
{
int focusHMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
int focusVMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
// Margins for horizontal mode (list views, tree views, table views)
const int textMargin = focusHMargin * 4;
if (QApplication::isRightToLeft())
d->setHorizontalMargin(Private::TextMargin, textMargin, focusVMargin, focusHMargin, focusVMargin);
else
d->setHorizontalMargin(Private::TextMargin, focusHMargin, focusVMargin, textMargin, focusVMargin);
d->setHorizontalMargin(Private::IconMargin, focusHMargin, focusVMargin);
d->setHorizontalMargin(Private::ItemMargin, 0, 0);
// Margins for vertical mode (icon views)
d->setVerticalMargin(Private::TextMargin, 6, 2);
d->setVerticalMargin(Private::IconMargin, focusHMargin, focusVMargin);
d->setVerticalMargin(Private::ItemMargin, 0, 0);
setShowInformation(NoInformation);
}
KFileItemDelegate::~KFileItemDelegate()
{
delete d;
}
QSize KFileItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// If the model wants to provide its own size hint for the item
const QVariant value = index.data(Qt::SizeHintRole);
if (value.isValid())
return qvariant_cast<QSize>(value);
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
const QSize displaySize = d->displaySizeHint(opt, index);
const QSize decorationSize = d->decorationSizeHint(opt, index);
QSize size;
if (d->verticalLayout(opt))
{
size.rwidth() = qMax(displaySize.width(), decorationSize.width());
size.rheight() = decorationSize.height() + displaySize.height() + 1;
}
else
{
size.rwidth() = decorationSize.width() + displaySize.width() + 1;
size.rheight() = qMax(decorationSize.height(), displaySize.height());
}
size = d->addMargin(size, Private::ItemMargin);
if (!d->maximumSize.isEmpty())
{
size = size.boundedTo(d->maximumSize);
}
return size;
}
QString KFileItemDelegate::Private::display(const QModelIndex &index) const
{
const QVariant value = index.data(Qt::DisplayRole);
switch (value.type())
{
case QVariant::String:
{
if (index.column() == KDirModel::Size)
return itemSize(index, fileItem(index));
else {
const QString text = replaceNewlines(value.toString());
return KStringHandler::preProcessWrap(text);
}
}
case QVariant::Double:
return KGlobal::locale()->formatNumber(value.toDouble());
case QVariant::Int:
case QVariant::UInt:
return KGlobal::locale()->formatLong(value.toInt());
default:
return QString();
}
}
void KFileItemDelegate::setShowInformation(const InformationList &list)
{
d->informationList = list;
}
void KFileItemDelegate::setShowInformation(Information value)
{
if (value != NoInformation)
d->informationList = InformationList() << value;
else
d->informationList = InformationList();
}
KFileItemDelegate::InformationList KFileItemDelegate::showInformation() const
{
return d->informationList;
}
void KFileItemDelegate::setShadowColor(const QColor &color)
{
d->shadowColor = color;
}
QColor KFileItemDelegate::shadowColor() const
{
return d->shadowColor;
}
void KFileItemDelegate::setShadowOffset(const QPointF &offset)
{
d->shadowOffset = offset;
}
QPointF KFileItemDelegate::shadowOffset() const
{
return d->shadowOffset;
}
void KFileItemDelegate::setShadowBlur(qreal factor)
{
d->shadowBlur = factor;
}
qreal KFileItemDelegate::shadowBlur() const
{
return d->shadowBlur;
}
void KFileItemDelegate::setMaximumSize(const QSize &size)
{
d->maximumSize = size;
}
QSize KFileItemDelegate::maximumSize() const
{
return d->maximumSize;
}
void KFileItemDelegate::setShowToolTipWhenElided(bool showToolTip)
{
d->showToolTipWhenElided = showToolTip;
}
bool KFileItemDelegate::showToolTipWhenElided() const
{
return d->showToolTipWhenElided;
}
void KFileItemDelegate::setWrapMode(QTextOption::WrapMode wrapMode)
{
d->wrapMode = wrapMode;
}
QTextOption::WrapMode KFileItemDelegate::wrapMode() const
{
return d->wrapMode;
}
QRect KFileItemDelegate::iconRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
return QRect(d->iconPosition(opt), opt.icon.actualSize(opt.decorationSize));
}
void KFileItemDelegate::setJobTransfersVisible(bool jobTransfersVisible)
{
d->downArrowIcon = KIcon("go-down");
d->jobTransfersVisible = jobTransfersVisible;
}
bool KFileItemDelegate::jobTransfersVisible() const
{
return d->jobTransfersVisible;
}
QIcon KFileItemDelegate::Private::decoration(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const
{
const QVariant value = index.data(Qt::DecorationRole);
QIcon icon;
switch (value.type())
{
case QVariant::Icon:
icon = qvariant_cast<QIcon>(value);
break;
case QVariant::Pixmap:
icon.addPixmap(qvariant_cast<QPixmap>(value));
break;
case QVariant::Color: {
QPixmap pixmap(option.decorationSize);
pixmap.fill(qvariant_cast<QColor>(value));
icon.addPixmap(pixmap);
break;
}
default:
break;
}
return icon;
}
QRect KFileItemDelegate::Private::labelRectangle(const QStyleOptionViewItemV4 &option) const
{
if (option.icon.isNull())
return subtractMargin(option.rect, Private::ItemMargin);
const QSize decoSize = addMargin(option.decorationSize, Private::IconMargin);
const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
QRect textArea(QPoint(0, 0), itemRect.size());
switch (option.decorationPosition)
{
case QStyleOptionViewItem::Top:
textArea.setTop(decoSize.height() + 1);
break;
case QStyleOptionViewItem::Bottom:
textArea.setBottom(itemRect.height() - decoSize.height() - 1);
break;
case QStyleOptionViewItem::Left:
textArea.setLeft(decoSize.width() + 1);
break;
case QStyleOptionViewItem::Right:
textArea.setRight(itemRect.width() - decoSize.width() - 1);
break;
}
textArea.translate(itemRect.topLeft());
return QStyle::visualRect(option.direction, option.rect, textArea);
}
QPoint KFileItemDelegate::Private::iconPosition(const QStyleOptionViewItemV4 &option) const
{
const QRect itemRect = subtractMargin(option.rect, Private::ItemMargin);
Qt::Alignment alignment;
// Convert decorationPosition to the alignment the decoration will have in option.rect
switch (option.decorationPosition)
{
case QStyleOptionViewItem::Top:
alignment = Qt::AlignHCenter | Qt::AlignTop;
break;
case QStyleOptionViewItem::Bottom:
alignment = Qt::AlignHCenter | Qt::AlignBottom;
break;
case QStyleOptionViewItem::Left:
alignment = Qt::AlignVCenter | Qt::AlignLeft;
break;
case QStyleOptionViewItem::Right:
alignment = Qt::AlignVCenter | Qt::AlignRight;
break;
}
// Compute the nominal decoration rectangle
const QSize size = addMargin(option.decorationSize, Private::IconMargin);
const QRect rect = QStyle::alignedRect(option.direction, alignment, size, itemRect);
// Position the icon in the center of the rectangle
QRect iconRect = QRect(QPoint(), option.icon.actualSize(option.decorationSize));
iconRect.moveCenter(rect.center());
return iconRect.topLeft();
}
void KFileItemDelegate::Private::drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option,
const QRect &rect) const
{
if (!(option.state & QStyle::State_HasFocus))
return;
QStyleOptionFocusRect opt;
opt.direction = option.direction;
opt.fontMetrics = option.fontMetrics;
opt.palette = option.palette;
opt.rect = rect;
opt.state = option.state | QStyle::State_KeyboardFocusChange | QStyle::State_Item;
opt.backgroundColor = option.palette.color(option.state & QStyle::State_Selected ?
QPalette::Highlight : QPalette::Base);
// Apparently some widget styles expect this hint to not be set
painter->setRenderHint(QPainter::Antialiasing, false);
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget);
painter->setRenderHint(QPainter::Antialiasing);
}
void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (!index.isValid())
return;
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
if (!(option.state & QStyle::State_Enabled))
{
opt.palette.setCurrentColorGroup(QPalette::Disabled);
}
// Unset the mouse over bit if we're not drawing the first column
if (index.column() > 0)
opt.state &= ~QStyle::State_MouseOver;
else
opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(opt.widget);
// Check if the item is being animated
// ========================================================================
KIO::AnimationState *state = d->animationState(opt, index, view);
KIO::CachedRendering *cache = 0;
qreal progress = ((opt.state & QStyle::State_MouseOver) &&
index.column() == KDirModel::Name) ? 1.0 : 0.0;
const QPoint iconPos = d->iconPosition(opt);
QIcon::Mode iconMode = option.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled;
QIcon::State iconState = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
QPixmap icon = opt.icon.pixmap(opt.decorationSize, iconMode, iconState);
if (state && !state->hasJobAnimation())
{
cache = state->cachedRendering();
progress = state->hoverProgress();
// Clear the mouse over bit temporarily
opt.state &= ~QStyle::State_MouseOver;
// If we have a cached rendering, draw the item from the cache
if (cache)
{
if (cache->checkValidity(opt.state) && cache->regular.size() == opt.rect.size())
{
QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
if (state->cachedRenderingFadeFrom() && state->fadeProgress() != 1.0)
{
// Apply icon fading animation
KIO::CachedRendering* fadeFromCache = state->cachedRenderingFadeFrom();
const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
}
painter->drawPixmap(option.rect.topLeft(), pixmap);
if (d->jobTransfersVisible && index.column() == 0) {
if (index.data(KDirModel::HasJobRole).toBool()) {
d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
}
}
return;
}
if (!cache->checkValidity(opt.state))
{
if ((KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects))
{
// Fade over from the old icon to the new one
// Only start a new fade if the previous one is ready
// Else we may start racing when checkValidity() always returns false
if (state->fadeProgress() == 1)
state->setCachedRenderingFadeFrom(state->takeCachedRendering());
}
d->gotNewIcon(index);
}
// If it wasn't valid, delete it
state->setCachedRendering(0);
}
else
{
// The cache may have been discarded, but the animation handler still needs to know about new icons
d->gotNewIcon(index);
}
}
// Compute the metrics, and lay out the text items
// ========================================================================
const QPen pen = QPen(d->foregroundBrush(opt, index), 0);
//### Apply the selection effect to the icon when the item is selected and
// showDecorationSelected is false.
QTextLayout labelLayout, infoLayout;
QRect textBoundingRect;
d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
int focusHMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
int focusVMargin = style->pixelMetric(QStyle::PM_FocusFrameVMargin);
QRect focusRect = textBoundingRect.adjusted(-focusHMargin, -focusVMargin,
+focusHMargin, +focusVMargin);
// Create a new cached rendering of a hovered and an unhovered item.
// We don't create a new cache for a fully hovered item, since we don't
// know yet if a hover out animation will be run.
// ========================================================================
if (state && (state->hoverProgress() < 1 || state->fadeProgress() < 1))
{
cache = new KIO::CachedRendering(opt.state, option.rect.size(), index);
QPainter p;
p.begin(&cache->regular);
p.translate(-option.rect.topLeft());
p.setRenderHint(QPainter::Antialiasing);
p.setPen(pen);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
p.drawPixmap(iconPos, icon);
d->drawTextItems(&p, labelLayout, infoLayout, textBoundingRect);
d->drawFocusRect(&p, opt, focusRect);
p.end();
opt.state |= QStyle::State_MouseOver;
icon = d->applyHoverEffect(icon);
p.begin(&cache->hover);
p.translate(-option.rect.topLeft());
p.setRenderHint(QPainter::Antialiasing);
p.setPen(pen);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
p.drawPixmap(iconPos, icon);
d->drawTextItems(&p, labelLayout, infoLayout, textBoundingRect);
d->drawFocusRect(&p, opt, focusRect);
p.end();
state->setCachedRendering(cache);
QPixmap pixmap = d->transition(cache->regular, cache->hover, progress);
if (state->cachedRenderingFadeFrom() && state->fadeProgress() == 0)
{
// Apply icon fading animation
KIO::CachedRendering* fadeFromCache = state->cachedRenderingFadeFrom();
const QPixmap fadeFromPixmap = d->transition(fadeFromCache->regular, fadeFromCache->hover, progress);
pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
d->restartAnimation(state);
}
painter->drawPixmap(option.rect.topLeft(), pixmap);
painter->setRenderHint(QPainter::Antialiasing);
if (d->jobTransfersVisible && index.column() == 0) {
if (index.data(KDirModel::HasJobRole).toBool()) {
d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
}
}
return;
}
// Render the item directly if we're not using a cached rendering
// ========================================================================
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(pen);
if (progress > 0 && !(opt.state & QStyle::State_MouseOver))
{
opt.state |= QStyle::State_MouseOver;
icon = d->applyHoverEffect(icon);
}
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
painter->drawPixmap(iconPos, icon);
d->drawTextItems(painter, labelLayout, infoLayout, textBoundingRect);
d->drawFocusRect(painter, opt, focusRect);
if (d->jobTransfersVisible && index.column() == 0 && state) {
if (index.data(KDirModel::HasJobRole).toBool()) {
d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
}
}
painter->restore();
}
QWidget *KFileItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
KTextEdit *edit = new KTextEdit(parent);
edit->setAcceptRichText(false);
edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
edit->setAlignment(opt.displayAlignment);
edit->setEnabled(false); //Disable the text-edit to mark it as un-initialized
return edit;
}
bool KFileItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_UNUSED(event)
Q_UNUSED(model)
Q_UNUSED(option)
Q_UNUSED(index)
return false;
}
void KFileItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
KTextEdit *textedit = qobject_cast<KTextEdit*>(editor);
Q_ASSERT(textedit != 0);
//Do not update existing text that the user may already have edited.
//The models will call setEditorData(..) whenever the icon has changed,
//and this makes the editing work correctly despite that.
if(textedit->isEnabled()) {
return;
}
textedit->setEnabled(true); //Enable the text-edit to mark it as initialized
const QVariant value = index.data(Qt::EditRole);
const QString text = value.toString();
textedit->insertPlainText(text);
textedit->selectAll();
const QString extension = KMimeType::extractKnownExtension(text);
if (!extension.isEmpty()) {
// The filename contains an extension. Assure that only the filename
// gets selected.
const int selectionLength = text.length() - extension.length() - 1;
QTextCursor cursor = textedit->textCursor();
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionLength);
textedit->setTextCursor(cursor);
}
}
void KFileItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
KTextEdit *textedit = qobject_cast<KTextEdit*>(editor);
Q_ASSERT(textedit != 0);
model->setData(index, textedit->toPlainText(), Qt::EditRole);
}
void KFileItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
QRect r = d->labelRectangle(opt);
// Use the full available width for the editor when maximumSize is set
if (!d->maximumSize.isEmpty()) {
if (d->verticalLayout(option)) {
int diff = qMax(r.width(), d->maximumSize.width()) - r.width();
if (diff > 1) {
r.adjust(-(diff / 2), 0, diff / 2, 0);
}
}
else {
int diff = qMax(r.width(), d->maximumSize.width() - opt.decorationSize.width()) - r.width();
if (diff > 0) {
if (opt.decorationPosition == QStyleOptionViewItem::Left) {
r.adjust(0, 0, diff, 0);
}
else {
r.adjust(-diff, 0, 0, 0);
}
}
}
}
KTextEdit *textedit = qobject_cast<KTextEdit*>(editor);
Q_ASSERT(textedit != 0);
const int frame = textedit->frameWidth();
r.adjust(-frame, -frame, frame, frame);
editor->setGeometry(r);
}
bool KFileItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_UNUSED(event)
Q_UNUSED(view)
// if the tooltip information the model keeps is different from the display information,
// show it always
const QVariant toolTip = index.data(Qt::ToolTipRole);
if (!toolTip.isValid()) {
return false;
}
if (index.data() != toolTip) {
return QAbstractItemDelegate::helpEvent(event, view, option, index);
}
if (!d->showToolTipWhenElided) {
return false;
}
// in the case the tooltip information is the same as the display information,
// show it only in the case the display information is elided
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
QTextLayout labelLayout;
QTextLayout infoLayout;
QRect textBoundingRect;
d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
const QString elidedText = d->elidedText(labelLayout, opt, textBoundingRect.size());
if (elidedText != d->display(index)) {
return QAbstractItemDelegate::helpEvent(event, view, option, index);
}
return false;
}
QRegion KFileItemDelegate::shape(const QStyleOptionViewItem &option, const QModelIndex &index)
{
QStyleOptionViewItemV4 opt(option);
d->initStyleOption(&opt, index);
d->setActiveMargins(d->verticalLayout(opt) ? Qt::Vertical : Qt::Horizontal);
QTextLayout labelLayout;
QTextLayout infoLayout;
QRect textBoundingRect;
d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect);
const QPoint pos = d->iconPosition(opt);
QRect iconRect = QRect(pos, opt.icon.actualSize(opt.decorationSize));
// Extend the icon rect so it touches the text rect
switch (opt.decorationPosition)
{
case QStyleOptionViewItem::Top:
if (iconRect.width() < textBoundingRect.width())
iconRect.setBottom(textBoundingRect.top());
else
textBoundingRect.setTop(iconRect.bottom());
break;
case QStyleOptionViewItem::Bottom:
if (iconRect.width() < textBoundingRect.width())
iconRect.setTop(textBoundingRect.bottom());
else
textBoundingRect.setBottom(iconRect.top());
break;
case QStyleOptionViewItem::Left:
iconRect.setRight(textBoundingRect.left());
break;
case QStyleOptionViewItem::Right:
iconRect.setLeft(textBoundingRect.right());
break;
}
QRegion region;
region += iconRect;
region += textBoundingRect;
return region;
}
bool KFileItemDelegate::eventFilter(QObject *object, QEvent *event)
{
KTextEdit *editor = qobject_cast<KTextEdit*>(object);
if (!editor)
return false;
switch (event->type())
{
case QEvent::KeyPress:
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
switch (keyEvent->key())
{
case Qt::Key_Tab:
case Qt::Key_Backtab:
emit commitData(editor);
emit closeEditor(editor, NoHint);
return true;
case Qt::Key_Enter:
- case Qt::Key_Return:
- if (editor->toPlainText().isEmpty())
+ case Qt::Key_Return: {
+ const QString text = editor->toPlainText();
+ if (text.isEmpty() || (text == QLatin1String(".")) || (text == QLatin1String("..")))
return true; // So a newline doesn't get inserted
emit commitData(editor);
emit closeEditor(editor, SubmitModelCache);
return true;
+ }
case Qt::Key_Escape:
emit closeEditor(editor, RevertModelCache);
return true;
default:
return false;
} // switch (keyEvent->key())
} // case QEvent::KeyPress
case QEvent::FocusOut:
{
const QWidget *w = QApplication::activePopupWidget();
if (!w || w->parent() != editor)
{
emit commitData(editor);
emit closeEditor(editor, NoHint);
return true;
}
else
return false;
}
default:
return false;
} // switch (event->type())
}
#include "kfileitemdelegate.moc"
// kate: space-indent on; indent-width 4; replace-tabs on;
diff --git a/kio/kio/kprotocolmanager.cpp b/kio/kio/kprotocolmanager.cpp
index a93043b4f8..5b1385e9ca 100644
--- a/kio/kio/kprotocolmanager.cpp
+++ b/kio/kio/kprotocolmanager.cpp
@@ -1,1108 +1,1108 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000- Waldo Bastain <bastain@kde.org>
Copyright (C) 2000- Dawit Alemayehu <adawit@kde.org>
Copyright (C) 2008 Jarosław Staniek <staniek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kprotocolmanager.h"
#include <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <QtCore/QCoreApplication>
#include <QtNetwork/QSslSocket>
#include <QtDBus/QtDBus>
#if !defined(QT_NO_NETWORKPROXY) && (defined (Q_OS_WIN32) || defined(Q_OS_MAC))
#include <QtNetwork/QNetworkProxyFactory>
#include <QtNetwork/QNetworkProxyQuery>
#endif
#include <kdeversion.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <kurl.h>
#include <kmimetypetrader.h>
#include <kprotocolinfofactory.h>
#include <kio/slaveconfig.h>
#include <kio/ioslave_defaults.h>
#include <kio/http_slave_defaults.h>
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
class KProtocolManagerPrivate
{
public:
KProtocolManagerPrivate();
~KProtocolManagerPrivate();
KSharedConfig::Ptr config;
KSharedConfig::Ptr http_config;
KUrl url;
QString protocol;
QStringList proxyList;
QString modifiers;
QString useragent;
QMap<QString /*mimetype*/, QString /*protocol*/> protocolForArchiveMimetypes;
};
K_GLOBAL_STATIC(KProtocolManagerPrivate, kProtocolManagerPrivate)
KProtocolManagerPrivate::KProtocolManagerPrivate()
{
// post routine since KConfig::sync() breaks if called too late
qAddPostRoutine(kProtocolManagerPrivate.destroy);
}
KProtocolManagerPrivate::~KProtocolManagerPrivate()
{
qRemovePostRoutine(kProtocolManagerPrivate.destroy);
}
#define PRIVATE_DATA \
KProtocolManagerPrivate *d = kProtocolManagerPrivate
void KProtocolManager::reparseConfiguration()
{
PRIVATE_DATA;
if (d->http_config) {
d->http_config->reparseConfiguration();
}
if (d->config) {
d->config->reparseConfiguration();
}
d->protocol.clear();
d->proxyList.clear();
d->modifiers.clear();
d->useragent.clear();
d->url.clear();
// Force the slave config to re-read its config...
KIO::SlaveConfig::self()->reset ();
}
KSharedConfig::Ptr KProtocolManager::config()
{
PRIVATE_DATA;
if (!d->config)
{
d->config = KSharedConfig::openConfig("kioslaverc", KConfig::NoGlobals);
}
return d->config;
}
static KConfigGroup http_config()
{
PRIVATE_DATA;
if (!d->http_config) {
d->http_config = KSharedConfig::openConfig("kio_httprc", KConfig::NoGlobals);
}
return KConfigGroup(d->http_config, QString());
}
/*=============================== TIMEOUT SETTINGS ==========================*/
int KProtocolManager::readTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ReadTimeout", DEFAULT_READ_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::connectTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ConnectTimeout", DEFAULT_CONNECT_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::proxyConnectTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ProxyConnectTimeout", DEFAULT_PROXY_CONNECT_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
int KProtocolManager::responseTimeout()
{
KConfigGroup cg( config(), QString() );
int val = cg.readEntry( "ResponseTimeout", DEFAULT_RESPONSE_TIMEOUT );
return qMax(MIN_TIMEOUT_VALUE, val);
}
/*========================== PROXY SETTINGS =================================*/
bool KProtocolManager::useProxy()
{
return proxyType() != NoProxy;
}
bool KProtocolManager::useReverseProxy()
{
KConfigGroup cg(config(), "Proxy Settings" );
return cg.readEntry("ReversedException", false);
}
KProtocolManager::ProxyType KProtocolManager::proxyType()
{
KConfigGroup cg(config(), "Proxy Settings" );
return static_cast<ProxyType>(cg.readEntry( "ProxyType" , 0));
}
KProtocolManager::ProxyAuthMode KProtocolManager::proxyAuthMode()
{
KConfigGroup cg(config(), "Proxy Settings" );
return static_cast<ProxyAuthMode>(cg.readEntry( "AuthMode" , 0));
}
/*========================== CACHING =====================================*/
bool KProtocolManager::useCache()
{
return http_config().readEntry( "UseCache", true );
}
KIO::CacheControl KProtocolManager::cacheControl()
{
QString tmp = http_config().readEntry("cache");
if (tmp.isEmpty())
return DEFAULT_CACHE_CONTROL;
return KIO::parseCacheControl(tmp);
}
QString KProtocolManager::cacheDir()
{
return http_config().readPathEntry("CacheDir", KGlobal::dirs()->saveLocation("cache","http"));
}
int KProtocolManager::maxCacheAge()
{
return http_config().readEntry( "MaxCacheAge", DEFAULT_MAX_CACHE_AGE ); // 14 days
}
int KProtocolManager::maxCacheSize()
{
return http_config().readEntry( "MaxCacheSize", DEFAULT_MAX_CACHE_SIZE ); // 5 MB
}
QString KProtocolManager::noProxyFor()
{
KProtocolManager::ProxyType type = proxyType();
QString noProxy = config()->group("Proxy Settings").readEntry( "NoProxyFor" );
if (type == EnvVarProxy)
noProxy = QString::fromLocal8Bit(qgetenv(noProxy.toLocal8Bit()));
return noProxy;
}
static QString adjustProtocol(const QString& scheme)
{
if (scheme.compare(QL1S("webdav"), Qt::CaseInsensitive) == 0)
return QL1S("http");
if (scheme.compare(QL1S("webdavs"), Qt::CaseInsensitive) == 0)
return QL1S("https");
return scheme.toLower();
}
QString KProtocolManager::proxyFor( const QString& protocol )
{
const QString key = adjustProtocol(protocol) + QL1S("Proxy");
QString socksProxy = config()->group("Proxy Settings").readEntry(QL1S("socksProxy"), QString());
if (!socksProxy.isEmpty() && !socksProxy.startsWith(QL1S("socks://"), Qt::CaseInsensitive))
socksProxy.prepend(QL1S("socks://"));
return config()->group("Proxy Settings").readEntry(key, socksProxy);
}
QString KProtocolManager::proxyForUrl( const KUrl &url )
{
const QStringList proxies = proxiesForUrl(url);
if (proxies.isEmpty())
return QString();
return proxies.first();
}
static QStringList getSystemProxyFor( const KUrl& url )
{
QStringList proxies;
#if !defined(QT_NO_NETWORKPROXY) && (defined(Q_OS_WIN32) || defined(Q_OS_MAC))
QNetworkProxyQuery query ( url );
const QList<QNetworkProxy> proxyList = QNetworkProxyFactory::systemProxyForQuery(query);
Q_FOREACH(const QNetworkProxy& proxy, proxyList)
{
KUrl url;
const QNetworkProxy::ProxyType type = proxy.type();
if (type == QNetworkProxy::NoProxy || type == QNetworkProxy::DefaultProxy)
{
proxies << QL1S("DIRECT");
continue;
}
if (type == QNetworkProxy::HttpProxy || type == QNetworkProxy::HttpCachingProxy)
url.setProtocol(QL1S("http"));
else if (type == QNetworkProxy::Socks5Proxy)
url.setProtocol(QL1S("socks"));
else if (type == QNetworkProxy::FtpCachingProxy)
url.setProtocol(QL1S("ftp"));
url.setHost(proxy.hostName());
url.setPort(proxy.port());
url.setUser(proxy.user());
proxies << url.url();
}
#else
// On Unix/Linux use system environment variables if any are set.
const QString proxyVar = KProtocolManager::proxyFor(url.protocol());
proxies << QString::fromLocal8Bit(qgetenv(proxyVar.toLocal8Bit())).trimmed();
#endif
return proxies;
}
QStringList KProtocolManager::proxiesForUrl( const KUrl &url )
{
QStringList proxyList;
const ProxyType pt = proxyType();
switch (pt)
{
case PACProxy:
case WPADProxy:
{
KUrl u (url);
const QString protocol = adjustProtocol(u.protocol());
u.setProtocol(protocol);
if (KProtocolInfo::protocolClass(protocol) != QL1S(":local"))
{
QDBusReply<QStringList> reply = QDBusInterface(QL1S("org.kde.kded"),
QL1S("/modules/proxyscout"),
QL1S("org.kde.KPAC.ProxyScout"))
.call(QL1S("proxiesForUrl"), u.url());
proxyList = reply;
}
break;
}
case EnvVarProxy:
proxyList = getSystemProxyFor( url );
break;
case ManualProxy:
proxyList << proxyFor( url.protocol() );
break;
case NoProxy:
default:
break;
}
if (proxyList.isEmpty()) {
proxyList << QL1S("DIRECT");
}
return proxyList;
}
void KProtocolManager::badProxy( const QString &proxy )
{
QDBusInterface( QL1S("org.kde.kded"), QL1S("/modules/proxyscout"))
.call(QL1S("blackListProxy"), proxy);
}
/*
Domain suffix match. E.g. return true if host is "cuzco.inka.de" and
nplist is "inka.de,hadiko.de" or if host is "localhost" and nplist is
"localhost".
*/
static bool revmatch(const char *host, const char *nplist)
{
if (host == 0)
return false;
const char *hptr = host + strlen( host ) - 1;
const char *nptr = nplist + strlen( nplist ) - 1;
const char *shptr = hptr;
while ( nptr >= nplist )
{
if ( *hptr != *nptr )
{
hptr = shptr;
// Try to find another domain or host in the list
while(--nptr>=nplist && *nptr!=',' && *nptr!=' ') ;
// Strip out multiple spaces and commas
while(--nptr>=nplist && (*nptr==',' || *nptr==' ')) ;
}
else
{
if ( nptr==nplist || nptr[-1]==',' || nptr[-1]==' ')
return true;
if ( nptr[-1]=='/' && hptr == host ) // "bugs.kde.org" vs "http://bugs.kde.org", the config UI says URLs are ok
return true;
if ( hptr == host ) // e.g. revmatch("bugs.kde.org","mybugs.kde.org")
return false;
hptr--;
nptr--;
}
}
return false;
}
/*
* Returns true if url is in the no proxy list.
*/
static bool shouldIgnoreProxyFor(const KUrl& url)
{
bool isRevMatch = false;
const KProtocolManager::ProxyType type = KProtocolManager::proxyType();
const bool useRevProxy = ((type == KProtocolManager::ManualProxy) && KProtocolManager::useReverseProxy());
QString noProxy;
// Check no proxy information iff the proxy type is either
// manual or environment variable based...
if ( (type == KProtocolManager::ManualProxy) || (type == KProtocolManager::EnvVarProxy) )
noProxy = KProtocolManager::noProxyFor();
if (!noProxy.isEmpty())
{
QString qhost = url.host().toLower();
QByteArray host = qhost.toLatin1();
QString qno_proxy = noProxy.trimmed().toLower();
const QByteArray no_proxy = qno_proxy.toLatin1();
isRevMatch = revmatch(host, no_proxy);
// If no match is found and the request url has a port
// number, try the combination of "host:port". This allows
// users to enter host:port in the No-proxy-For list.
if (!isRevMatch && url.port() > 0)
{
qhost += QL1C(':');
qhost += QString::number(url.port());
host = qhost.toLatin1();
isRevMatch = revmatch (host, no_proxy);
}
// If the hostname does not contain a dot, check if
// <local> is part of noProxy.
if (!isRevMatch && !host.isEmpty() && (strchr(host, '.') == NULL))
isRevMatch = revmatch("<local>", no_proxy);
}
return (useRevProxy != isRevMatch);
}
// For proxy address comparisons, we only need to compare
// protocol, host and port number. Nothing else.
static bool compareProxyUrls(const KUrl& u1, const KUrl& u2)
{
return ((u1.protocol() == u2.protocol()) &&
(u1.host() == u2.host()) &&
(u1.port() == u2.port()));
}
QString KProtocolManager::slaveProtocol(const KUrl &url, QString &proxy)
{
QStringList proxyList;
const QString protocol = KProtocolManager::slaveProtocol(url, proxyList);
if (!proxyList.isEmpty()) {
proxy = proxyList.first();
}
return protocol;
}
QString KProtocolManager::slaveProtocol(const KUrl &url, QStringList &proxyList)
{
// Do not perform a proxy lookup for any url classified as a ":local" url or
// one that does not have a host name.
if (KProtocolInfo::protocolClass(url.protocol()).compare(QL1S(":local"), Qt::CaseInsensitive) == 0 ||
!url.hasHost())
{
return url.protocol();
}
if (url.hasSubUrl()) // We don't want the suburl's protocol
{
const KUrl::List list = KUrl::split(url);
return slaveProtocol(list.last(), proxyList);
}
PRIVATE_DATA;
if (compareProxyUrls(d->url, url))
{
proxyList = d->proxyList;
return d->protocol;
}
if (useProxy() && !shouldIgnoreProxyFor(url))
{
const QStringList proxies = proxiesForUrl(url);
proxyList.clear();
Q_FOREACH(const QString& proxy, proxies)
{
kDebug() << "Proxy for" << url.host() << ":" << proxy;
if (proxy == QL1S("DIRECT") || proxy.isEmpty())
{
continue;
}
d->url = proxy;
if (d->url.isValid() && !d->url.protocol().isEmpty())
{
// The idea behind slave protocols is not applicable to http
// and webdav protocols as well as protocols unknown to KDE.
const QString protocol = url.protocol();
if (protocol.startsWith(QL1S("http")) || protocol.startsWith(QL1S("webdav")) ||
!KProtocolInfo::isKnownProtocol(protocol))
d->protocol = protocol;
else
{
d->protocol = d->url.protocol();
kDebug () << "slaveProtocol: " << d->protocol;
}
proxyList << proxy;
}
}
if (!proxyList.isEmpty())
{
d->url = url;
d->proxyList = proxyList;
return d->protocol;
}
}
d->url = url;
d->protocol = url.protocol();
d->proxyList.clear();
proxyList.clear();
return d->protocol;
}
/*================================= USER-AGENT SETTINGS =====================*/
QString KProtocolManager::userAgentForHost( const QString& hostname )
{
const QString sendUserAgent = KIO::SlaveConfig::self()->configData("http", hostname.toLower(), "SendUserAgent").toLower();
if (sendUserAgent == QL1S("false"))
return QString();
const QString useragent = KIO::SlaveConfig::self()->configData("http", hostname.toLower(), "UserAgent");
// Return the default user-agent if none is specified
// for the requested host.
if (useragent.isEmpty())
return defaultUserAgent();
return useragent;
}
QString KProtocolManager::defaultUserAgent( )
{
const QString modifiers = KIO::SlaveConfig::self()->configData("http", QString(), "UserAgentKeys");
return defaultUserAgent(modifiers);
}
static QString defaultUserAgentFromPreferredService()
{
QString agentStr;
// Check if the default COMPONENT contains a custom default UA string...
KService::Ptr service = KMimeTypeTrader::self()->preferredService(QL1S("text/html"),
QL1S("KParts/ReadOnlyPart"));
if (service && service->showInKDE())
agentStr = service->property(QL1S("X-KDE-Default-UserAgent"),
QVariant::String).toString();
return agentStr;
}
static QString platform()
{
#if defined(Q_WS_X11)
return QL1S("X11");
#elif defined(Q_WS_MAC)
return QL1S("Macintosh");
#elif defined(Q_WS_WIN)
return QL1S("Windows");
#elif defined(Q_WS_S60)
return QL1S("Symbian");
#endif
}
QString KProtocolManager::defaultUserAgent( const QString &_modifiers )
{
PRIVATE_DATA;
QString modifiers = _modifiers.toLower();
if (modifiers.isEmpty())
modifiers = DEFAULT_USER_AGENT_KEYS;
if (d->modifiers == modifiers && !d->useragent.isEmpty())
return d->useragent;
d->modifiers = modifiers;
/*
The following code attempts to determine the default user agent string
from the 'X-KDE-UA-DEFAULT-STRING' property of the desktop file
for the preferred service that was configured to handle the 'text/html'
mime type. If the prefered service's desktop file does not specify this
property, the long standing default user agent string will be used.
The following keyword placeholders are automatically converted when the
user agent string is read from the property:
%SECURITY% Expands to"N" when SSL is not supported, otherwise it is ignored.
%OSNAME% Expands to operating system name, e.g. Linux.
%OSVERSION% Expands to operating system version, e.g. 2.6.32
%SYSTYPE% Expands to machine or system type, e.g. i386
%PLATFORM% Expands to windowing system, e.g. X11 on Unix/Linux.
%LANGUAGE% Expands to default language in use, e.g. en-US.
%APPVERSION% Expands to QCoreApplication applicationName()/applicationVerison(),
e.g. Konqueror/4.5.0. If application name and/or application version
number are not set, then "KDE" and the runtime KDE version numbers
are used respectively.
All of the keywords are handled case-insensitively.
*/
QString systemName, systemVersion, machine, supp;
const bool sysInfoFound = getSystemNameVersionAndMachine( systemName, systemVersion, machine );
QString agentStr = defaultUserAgentFromPreferredService();
if (agentStr.isEmpty())
{
supp += platform();
if (sysInfoFound)
{
if (modifiers.contains('o'))
{
supp += QL1S("; ");
supp += systemName;
if (modifiers.contains('v'))
{
supp += QL1C(' ');
supp += systemVersion;
}
if (modifiers.contains('m'))
{
supp += QL1C(' ');
supp += machine;
}
}
if (modifiers.contains('l'))
{
supp += QL1S("; ");
supp += KGlobal::locale()->language();
}
}
// Full format: Mozilla/5.0 (Linux
d->useragent = QL1S("Mozilla/5.0 (");
d->useragent += supp;
d->useragent += QL1S(") KHTML/");
d->useragent += QString::number(KDE::versionMajor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionMinor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionRelease());
d->useragent += QL1S(" (like Gecko) Konqueror/");
d->useragent += QString::number(KDE::versionMajor());
d->useragent += QL1C('.');
d->useragent += QString::number(KDE::versionMinor());
}
else
{
QString appName = QCoreApplication::applicationName();
if (appName.isEmpty() || appName.startsWith(QL1S("kcmshell"), Qt::CaseInsensitive))
appName = QL1S ("KDE");
QString appVersion = QCoreApplication::applicationVersion();
if (appVersion.isEmpty()) {
appVersion += QString::number(KDE::versionMajor());
appVersion += QL1C('.');
appVersion += QString::number(KDE::versionMinor());
appVersion += QL1C('.');
appVersion += QString::number(KDE::versionRelease());
}
appName += QL1C('/');
appName += appVersion;
agentStr.replace(QL1S("%appversion%"), appName, Qt::CaseInsensitive);
if (!QSslSocket::supportsSsl())
agentStr.replace(QL1S("%security%"), QL1S("N"), Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%security%"), Qt::CaseInsensitive);
if (sysInfoFound)
{
// Platform (e.g. X11)
#if defined(Q_WS_X11)
agentStr.replace(QL1S("%platform%"), QL1S("X11"), Qt::CaseInsensitive);
#elif defined(Q_WS_MAC)
agentStr.replace(QL1S("%platform%"), QL1S("Macintosh"), Qt::CaseInsensitive);
#elif defined(Q_WS_WIN)
agentStr.replace(QL1S("%platform%"), QL1S("Windows"), Qt::CaseInsensitive);
#elif defined (Q_WS_S60)
agentStr.replace(QL1S("%platform%"), QL1S("Symbian"), Qt::CaseInsensitive);
#endif
// Platform can no longer be turned off.
agentStr.remove(QL1S("%platform%"), Qt::CaseInsensitive);
// Operating system (e.g. Linux)
if (modifiers.contains('o'))
{
agentStr.replace(QL1S("%osname%"), systemName, Qt::CaseInsensitive);
// OS version (e.g. 2.6.36)
if (modifiers.contains('v'))
agentStr.replace(QL1S("%osversion%"), systemVersion, Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%osversion%"), Qt::CaseInsensitive);
// Machine type (i686, x86-64, etc.)
if (modifiers.contains('m'))
agentStr.replace(QL1S("%systype%"), machine, Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%systype%"), Qt::CaseInsensitive);
}
else
{
agentStr.remove(QL1S("%osname%"), Qt::CaseInsensitive);
agentStr.remove(QL1S("%osversion%"), Qt::CaseInsensitive);
agentStr.remove(QL1S("%systype%"), Qt::CaseInsensitive);
}
// Language (e.g. en_US)
if (modifiers.contains('l'))
agentStr.replace(QL1S("%language%"), KGlobal::locale()->language(), Qt::CaseInsensitive);
else
agentStr.remove(QL1S("%language%"), Qt::CaseInsensitive);
// Clean up unnecessary separators that could be left over from the
// possible keyword removal above...
agentStr.replace(QRegExp("[(]\\s*[;]\\s*"), QL1S("("));
agentStr.replace(QRegExp("[;]\\s*[;]\\s*"), QL1S("; "));
agentStr.replace(QRegExp("\\s*[;]\\s*[)]"), QL1S(")"));
}
else
{
agentStr.remove(QL1S("%osname%"));
agentStr.remove(QL1S("%osversion%"));
agentStr.remove(QL1S("%platform%"));
agentStr.remove(QL1S("%systype%"));
agentStr.remove(QL1S("%language%"));
}
d->useragent = agentStr.simplified();
}
//kDebug() << "USERAGENT STRING:" << d->useragent;
return d->useragent;
}
QString KProtocolManager::userAgentForApplication( const QString &appName, const QString& appVersion,
const QStringList& extraInfo )
{
QString systemName, systemVersion, machine, info;
if (getSystemNameVersionAndMachine( systemName, systemVersion, machine ))
{
info += systemName;
info += QL1C('/');
info += systemVersion;
info += QL1S("; ");
}
info += QL1S("KDE/");
info += QString::number(KDE::versionMajor());
info += QL1C('.');
info += QString::number(KDE::versionMinor());
info += QL1C('.');
info += QString::number(KDE::versionRelease());
if (!machine.isEmpty())
{
info += QL1S("; ");
info += machine;
}
info += QL1S("; ");
info += extraInfo.join(QL1S("; "));
return (appName + QL1C('/') + appVersion + QL1S(" (") + info + QL1C(')'));
}
bool KProtocolManager::getSystemNameVersionAndMachine(
QString& systemName, QString& systemVersion, QString& machine )
{
struct utsname unameBuf;
if ( 0 != uname( &unameBuf ) )
return false;
#if defined(Q_WS_WIN) && !defined(_WIN32_WCE)
// we do not use unameBuf.sysname information constructed in kdewin32
// because we want to get separate name and version
systemName = QL1S( "Windows" );
OSVERSIONINFOEX versioninfo;
ZeroMemory(&versioninfo, sizeof(OSVERSIONINFOEX));
// try calling GetVersionEx using the OSVERSIONINFOEX, if that fails, try using the OSVERSIONINFO
versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
bool ok = GetVersionEx( (OSVERSIONINFO *) &versioninfo );
if ( !ok ) {
versioninfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
ok = GetVersionEx( (OSVERSIONINFO *) &versioninfo );
}
if ( ok ) {
systemVersion = QString::number(versioninfo.dwMajorVersion);
systemVersion += QL1C('.');
- systemVersion += QString::number(versioninfo.dwMinorVersion));
+ systemVersion += QString::number(versioninfo.dwMinorVersion);
}
#else
systemName = unameBuf.sysname;
systemVersion = unameBuf.release;
#endif
machine = unameBuf.machine;
return true;
}
QString KProtocolManager::acceptLanguagesHeader()
{
static const QString &english = KGlobal::staticQString("en");
// User's desktop language preference.
QStringList languageList = KGlobal::locale()->languageList();
// Replace possible "C" in the language list with "en", unless "en" is
// already pressent. This is to keep user's priorities in order.
// If afterwards "en" is still not present, append it.
int idx = languageList.indexOf(QString::fromLatin1("C"));
if (idx != -1)
{
if (languageList.contains(english))
languageList.removeAt(idx);
else
languageList[idx] = english;
}
if (!languageList.contains(english))
languageList += english;
// Some languages may have web codes different from locale codes,
// read them from the config and insert in proper order.
KConfig acclangConf("accept-languages.codes", KConfig::NoGlobals);
KConfigGroup replacementCodes(&acclangConf, "ReplacementCodes");
QStringList languageListFinal;
Q_FOREACH (const QString &lang, languageList)
{
const QStringList langs = replacementCodes.readEntry(lang, QStringList());
if (langs.isEmpty())
languageListFinal += lang;
else
languageListFinal += langs;
}
// The header is composed of comma separated languages, with an optional
// associated priority estimate (q=1..0) defaulting to 1.
// As our language tags are already sorted by priority, we'll just decrease
// the value evenly
int prio = 10;
QString header;
Q_FOREACH (const QString &lang,languageListFinal) {
header += lang;
if (prio < 10) {
header += QL1S(";q=0.");
header += QString::number(prio);
}
// do not add cosmetic whitespace in here : it is less compatible (#220677)
header += QL1S(",");
if (prio > 1)
--prio;
}
header.chop(1);
// Some of the languages may have country specifier delimited by
// underscore, or modifier delimited by at-sign.
// The header should use dashes instead.
header.replace('_', '-');
header.replace('@', '-');
return header;
}
/*==================================== OTHERS ===============================*/
bool KProtocolManager::markPartial()
{
return config()->group(QByteArray()).readEntry( "MarkPartial", true );
}
int KProtocolManager::minimumKeepSize()
{
return config()->group(QByteArray()).readEntry( "MinimumKeepSize",
DEFAULT_MINIMUM_KEEP_SIZE ); // 5000 byte
}
bool KProtocolManager::autoResume()
{
return config()->group(QByteArray()).readEntry( "AutoResume", false );
}
bool KProtocolManager::persistentConnections()
{
return config()->group(QByteArray()).readEntry( "PersistentConnections", true );
}
bool KProtocolManager::persistentProxyConnection()
{
return config()->group(QByteArray()).readEntry( "PersistentProxyConnection", false );
}
QString KProtocolManager::proxyConfigScript()
{
return config()->group("Proxy Settings").readEntry( "Proxy Config Script" );
}
/* =========================== PROTOCOL CAPABILITIES ============== */
static KProtocolInfo::Ptr findProtocol(const KUrl &url)
{
QString protocol = url.protocol();
if ( !KProtocolInfo::proxiedBy( protocol ).isEmpty() )
{
QString dummy;
protocol = KProtocolManager::slaveProtocol(url, dummy);
}
return KProtocolInfoFactory::self()->findProtocol(protocol);
}
KProtocolInfo::Type KProtocolManager::inputType( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::T_NONE;
return prot->m_inputType;
}
KProtocolInfo::Type KProtocolManager::outputType( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::T_NONE;
return prot->m_outputType;
}
bool KProtocolManager::isSourceProtocol( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_isSourceProtocol;
}
bool KProtocolManager::supportsListing( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsListing;
}
QStringList KProtocolManager::listing( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return QStringList();
return prot->m_listing;
}
bool KProtocolManager::supportsReading( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsReading;
}
bool KProtocolManager::supportsWriting( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsWriting;
}
bool KProtocolManager::supportsMakeDir( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsMakeDir;
}
bool KProtocolManager::supportsDeleting( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsDeleting;
}
bool KProtocolManager::supportsLinking( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsLinking;
}
bool KProtocolManager::supportsMoving( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsMoving;
}
bool KProtocolManager::supportsOpening( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_supportsOpening;
}
bool KProtocolManager::canCopyFromFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_canCopyFromFile;
}
bool KProtocolManager::canCopyToFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->m_canCopyToFile;
}
bool KProtocolManager::canRenameFromFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canRenameFromFile();
}
bool KProtocolManager::canRenameToFile( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canRenameToFile();
}
bool KProtocolManager::canDeleteRecursive( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return false;
return prot->canDeleteRecursive();
}
KProtocolInfo::FileNameUsedForCopying KProtocolManager::fileNameUsedForCopying( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return KProtocolInfo::FromUrl;
return prot->fileNameUsedForCopying();
}
QString KProtocolManager::defaultMimetype( const KUrl &url )
{
KProtocolInfo::Ptr prot = findProtocol(url);
if ( !prot )
return QString();
return prot->m_defaultMimetype;
}
QString KProtocolManager::protocolForArchiveMimetype( const QString& mimeType )
{
PRIVATE_DATA;
if (d->protocolForArchiveMimetypes.isEmpty()) {
const KProtocolInfo::List allProtocols = KProtocolInfoFactory::self()->allProtocols();
for (KProtocolInfo::List::const_iterator it = allProtocols.begin();
it != allProtocols.end(); ++it) {
const QStringList archiveMimetypes = (*it)->archiveMimeTypes();
Q_FOREACH(const QString& mime, archiveMimetypes) {
d->protocolForArchiveMimetypes.insert(mime, (*it)->name());
}
}
}
return d->protocolForArchiveMimetypes.value(mimeType);
}
#undef PRIVATE_DATA
diff --git a/kio/kio/scheduler.cpp b/kio/kio/scheduler.cpp
index f7a589e195..287dd22bb5 100644
--- a/kio/kio/scheduler.cpp
+++ b/kio/kio/scheduler.cpp
@@ -1,1333 +1,1338 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Stephan Kulow <coolo@kde.org>
Waldo Bastian <bastian@kde.org>
Copyright (C) 2009, 2010 Andreas Hartmetz <ahartmetz@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "scheduler.h"
#include "scheduler_p.h"
#include "sessiondata.h"
#include "slaveconfig.h"
#include "authinfo.h"
#include "slave.h"
#include "connection.h"
#include "job_p.h"
#include <kdebug.h>
#include <kglobal.h>
#include <kprotocolmanager.h>
#include <kprotocolinfo.h>
#include <assert.h>
#include <kdesu/client.h>
#include <QtCore/QHash>
#include <QtGui/QWidget>
#include <QtDBus/QtDBus>
// Slaves may be idle for a certain time (3 minutes) before they are killed.
static const int s_idleSlaveLifetime = 3 * 60;
using namespace KIO;
#ifndef KDE_USE_FINAL // already defined in job.cpp
static inline Slave *jobSlave(SimpleJob *job)
{
return SimpleJobPrivate::get(job)->m_slave;
}
#endif
static inline int jobCommand(SimpleJob *job)
{
return SimpleJobPrivate::get(job)->m_command;
}
static inline void startJob(SimpleJob *job, Slave *slave)
{
SimpleJobPrivate::get(job)->start(slave);
}
// here be uglies
// forward declaration to break cross-dependency of SlaveKeeper and SchedulerPrivate
static void setupSlave(KIO::Slave *slave, const KUrl &url, const QString &protocol,
const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = 0);
// same reason as above
static Scheduler *scheduler();
static Slave *heldSlaveForJob(SimpleJob *job);
int SerialPicker::changedPrioritySerial(int oldSerial, int newPriority) const
{
Q_ASSERT(newPriority >= -10 && newPriority <= 10);
newPriority = qBound(-10, newPriority, 10);
int unbiasedSerial = oldSerial % m_jobsPerPriority;
return unbiasedSerial + newPriority * m_jobsPerPriority;
}
SlaveKeeper::SlaveKeeper()
{
m_grimTimer.setSingleShot(true);
connect (&m_grimTimer, SIGNAL(timeout()), SLOT(grimReaper()));
}
void SlaveKeeper::returnSlave(Slave *slave)
{
Q_ASSERT(slave);
slave->setIdle();
m_idleSlaves.insert(slave->host(), slave);
scheduleGrimReaper();
}
Slave *SlaveKeeper::takeSlaveForJob(SimpleJob *job)
{
Slave *slave = heldSlaveForJob(job);
if (slave) {
return slave;
}
KUrl url = SimpleJobPrivate::get(job)->m_url;
// TODO take port, username and password into account
QMultiHash<QString, Slave *>::Iterator it = m_idleSlaves.find(url.host());
if (it == m_idleSlaves.end()) {
it = m_idleSlaves.begin();
}
if (it == m_idleSlaves.end()) {
return 0;
}
slave = it.value();
m_idleSlaves.erase(it);
return slave;
}
bool SlaveKeeper::removeSlave(Slave *slave)
{
// ### performance not so great
QMultiHash<QString, Slave *>::Iterator it = m_idleSlaves.begin();
for (; it != m_idleSlaves.end(); ++it) {
if (it.value() == slave) {
m_idleSlaves.erase(it);
return true;
}
}
return false;
}
QList<Slave *> SlaveKeeper::allSlaves() const
{
return m_idleSlaves.values();
}
void SlaveKeeper::scheduleGrimReaper()
{
if (!m_grimTimer.isActive()) {
m_grimTimer.start((s_idleSlaveLifetime / 2) * 1000);
}
}
//private slot
void SlaveKeeper::grimReaper()
{
QMultiHash<QString, Slave *>::Iterator it = m_idleSlaves.begin();
while (it != m_idleSlaves.end()) {
Slave *slave = it.value();
if (slave->idleTime() >= s_idleSlaveLifetime) {
it = m_idleSlaves.erase(it);
if (slave->job()) {
kDebug (7006) << "Idle slave" << slave << "still has job" << slave->job();
}
slave->kill();
// avoid invoking slotSlaveDied() because its cleanup services are not needed
slave->deref();
} else {
++it;
}
}
if (!m_idleSlaves.isEmpty()) {
scheduleGrimReaper();
}
}
int HostQueue::lowestSerial() const
{
QMap<int, SimpleJob*>::ConstIterator first = m_queuedJobs.constBegin();
if (first != m_queuedJobs.constEnd()) {
return first.key();
}
return SerialPicker::maxSerial;
}
void HostQueue::queueJob(SimpleJob *job)
{
const int serial = SimpleJobPrivate::get(job)->m_schedSerial;
Q_ASSERT(serial != 0);
Q_ASSERT(!m_queuedJobs.contains(serial));
Q_ASSERT(!m_runningJobs.contains(job));
m_queuedJobs.insert(serial, job);
}
SimpleJob *HostQueue::takeFirstInQueue()
{
Q_ASSERT(!m_queuedJobs.isEmpty());
QMap<int, SimpleJob *>::iterator first = m_queuedJobs.begin();
SimpleJob *job = first.value();
m_queuedJobs.erase(first);
m_runningJobs.insert(job);
return job;
}
bool HostQueue::removeJob(SimpleJob *job)
{
const int serial = SimpleJobPrivate::get(job)->m_schedSerial;
if (m_runningJobs.remove(job)) {
Q_ASSERT(!m_queuedJobs.contains(serial));
return true;
}
if (m_queuedJobs.remove(serial)) {
return true;
}
return false;
}
QList<Slave *> HostQueue::allSlaves() const
{
QList<Slave *> ret;
Q_FOREACH (SimpleJob *job, m_runningJobs) {
Slave *slave = jobSlave(job);
Q_ASSERT(slave);
ret.append(slave);
}
return ret;
}
ConnectedSlaveQueue::ConnectedSlaveQueue()
{
m_startJobsTimer.setSingleShot(true);
connect (&m_startJobsTimer, SIGNAL(timeout()), SLOT(startRunnableJobs()));
}
bool ConnectedSlaveQueue::queueJob(SimpleJob *job, Slave *slave)
{
QHash<Slave *, PerSlaveQueue>::Iterator it = m_connectedSlaves.find(slave);
if (it == m_connectedSlaves.end()) {
return false;
}
SimpleJobPrivate::get(job)->m_slave = slave;
PerSlaveQueue &jobs = it.value();
jobs.waitingList.append(job);
if (!jobs.runningJob) {
// idle slave now has a job to run
m_runnableSlaves.insert(slave);
m_startJobsTimer.start();
}
return true;
}
bool ConnectedSlaveQueue::removeJob(SimpleJob *job)
{
Slave *slave = jobSlave(job);
Q_ASSERT(slave);
QHash<Slave *, PerSlaveQueue>::Iterator it = m_connectedSlaves.find(slave);
if (it == m_connectedSlaves.end()) {
return false;
}
PerSlaveQueue &jobs = it.value();
if (jobs.runningJob || jobs.waitingList.isEmpty()) {
// a slave that was busy running a job was not runnable.
// a slave that has no waiting job(s) was not runnable either.
Q_ASSERT(!m_runnableSlaves.contains(slave));
}
const bool removedRunning = jobs.runningJob == job;
const bool removedWaiting = jobs.waitingList.removeAll(job) != 0;
if (removedRunning) {
jobs.runningJob = 0;
Q_ASSERT(!removedWaiting);
}
const bool removedTheJob = removedRunning || removedWaiting;
if (!slave->isAlive()) {
removeSlave(slave);
return removedTheJob;
}
if (removedRunning && jobs.waitingList.count()) {
m_runnableSlaves.insert(slave);
m_startJobsTimer.start();
}
if (removedWaiting && jobs.waitingList.isEmpty()) {
m_runnableSlaves.remove(slave);
}
return removedTheJob;
}
void ConnectedSlaveQueue::addSlave(Slave *slave)
{
Q_ASSERT(slave);
if (!m_connectedSlaves.contains(slave)) {
m_connectedSlaves.insert(slave, PerSlaveQueue());
}
}
bool ConnectedSlaveQueue::removeSlave(Slave *slave)
{
QHash<Slave *, PerSlaveQueue>::Iterator it = m_connectedSlaves.find(slave);
if (it == m_connectedSlaves.end()) {
return false;
}
PerSlaveQueue &jobs = it.value();
Q_FOREACH (SimpleJob *job, jobs.waitingList) {
// ### for compatibility with the old scheduler we don't touch the running job, if any.
// make sure that the job doesn't call back into Scheduler::cancelJob(); this would
// a) crash and b) be unnecessary because we clean up just fine.
SimpleJobPrivate::get(job)->m_schedSerial = 0;
job->kill();
}
m_connectedSlaves.erase(it);
m_runnableSlaves.remove(slave);
slave->kill();
return true;
}
// KDE5: only one caller, for doubtful reasons. remove this if possible.
bool ConnectedSlaveQueue::isIdle(Slave *slave)
{
QHash<Slave *, PerSlaveQueue>::Iterator it = m_connectedSlaves.find(slave);
if (it == m_connectedSlaves.end()) {
return false;
}
return it.value().runningJob == 0;
}
//private slot
void ConnectedSlaveQueue::startRunnableJobs()
{
QSet<Slave *>::Iterator it = m_runnableSlaves.begin();
while (it != m_runnableSlaves.end()) {
Slave *slave = *it;
if (!slave->isConnected()) {
// this polling is somewhat inefficient...
m_startJobsTimer.start();
++it;
continue;
}
it = m_runnableSlaves.erase(it);
PerSlaveQueue &jobs = m_connectedSlaves[slave];
SimpleJob *job = jobs.waitingList.takeFirst();
Q_ASSERT(!jobs.runningJob);
jobs.runningJob = job;
const KUrl url = job->url();
// no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that.
const int port = url.port() == -1 ? 0 : url.port();
if (slave->host() == "<reset>") {
MetaData configData = SlaveConfig::self()->configData(url.protocol(), url.host());
slave->setConfig(configData);
slave->setProtocol(url.protocol());
slave->setHost(url.host(), port, url.user(), url.pass());
}
Q_ASSERT(slave->protocol() == url.protocol());
Q_ASSERT(slave->host() == url.host());
Q_ASSERT(slave->port() == port);
startJob(job, slave);
}
}
static void ensureNoDuplicates(QMap<int, HostQueue *> *queuesBySerial)
{
Q_UNUSED(queuesBySerial);
#ifdef SCHEDULER_DEBUG
// a host queue may *never* be in queuesBySerial twice.
QSet<HostQueue *> seen;
Q_FOREACH (HostQueue *hq, *queuesBySerial) {
Q_ASSERT(!seen.contains(hq));
seen.insert(hq);
}
#endif
}
static void verifyRunningJobsCount(QHash<QString, HostQueue> *queues, int runningJobsCount)
{
Q_UNUSED(queues);
Q_UNUSED(runningJobsCount);
#ifdef SCHEDULER_DEBUG
int realRunningJobsCount = 0;
Q_FOREACH (const HostQueue &hq, *queues) {
realRunningJobsCount += hq.runningJobsCount();
}
Q_ASSERT(realRunningJobsCount == runningJobsCount);
// ...and of course we may never run the same job twice!
QSet<SimpleJob *> seenJobs;
Q_FOREACH (const HostQueue &hq, *queues) {
Q_FOREACH (SimpleJob *job, hq.runningJobs()) {
Q_ASSERT(!seenJobs.contains(job));
seenJobs.insert(job);
}
}
#endif
}
ProtoQueue::ProtoQueue(SchedulerPrivate *sp, int maxSlaves, int maxSlavesPerHost)
: m_schedPrivate(sp),
m_maxConnectionsPerHost(maxSlavesPerHost ? maxSlavesPerHost : maxSlaves),
m_maxConnectionsTotal(qMax(maxSlaves, maxSlavesPerHost)),
m_runningJobsCount(0)
{
kDebug(7006) << "m_maxConnectionsTotal:" << m_maxConnectionsTotal
<< "m_maxConnectionsPerHost:" << m_maxConnectionsPerHost;
Q_ASSERT(m_maxConnectionsPerHost >= 1);
Q_ASSERT(maxSlaves >= maxSlavesPerHost);
m_startJobTimer.setSingleShot(true);
connect (&m_startJobTimer, SIGNAL(timeout()), SLOT(startAJob()));
}
ProtoQueue::~ProtoQueue()
{
Q_FOREACH (Slave *slave, allSlaves()) {
// kill the slave process, then remove the interface in our process
slave->kill();
slave->deref();
}
}
void ProtoQueue::queueJob(SimpleJob *job)
{
QString hostname = SimpleJobPrivate::get(job)->m_url.host();
HostQueue &hq = m_queuesByHostname[hostname];
const int prevLowestSerial = hq.lowestSerial();
Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost);
// nevert insert a job twice
Q_ASSERT(SimpleJobPrivate::get(job)->m_schedSerial == 0);
SimpleJobPrivate::get(job)->m_schedSerial = m_serialPicker.next();
const bool wasQueueEmpty = hq.isQueueEmpty();
hq.queueJob(job);
// note that HostQueue::queueJob() into an empty queue changes its lowestSerial() too...
// the queue's lowest serial job may have changed, so update the ordered list of queues.
// however, we ignore all jobs that would cause more connections to a host than allowed.
if (prevLowestSerial != hq.lowestSerial()) {
if (hq.runningJobsCount() < m_maxConnectionsPerHost) {
// if the connection limit didn't keep the HQ unscheduled it must have been lack of jobs
if (m_queuesBySerial.remove(prevLowestSerial) == 0) {
Q_UNUSED(wasQueueEmpty);
Q_ASSERT(wasQueueEmpty);
}
m_queuesBySerial.insert(hq.lowestSerial(), &hq);
} else {
#ifdef SCHEDULER_DEBUG
// ### this assertion may fail if the limits were modified at runtime!
// if the per-host connection limit is already reached the host queue's lowest serial
// should not be queued.
Q_ASSERT(!m_queuesBySerial.contains(prevLowestSerial));
#endif
}
}
// just in case; startAJob() will refuse to start a job if it shouldn't.
m_startJobTimer.start();
ensureNoDuplicates(&m_queuesBySerial);
}
void ProtoQueue::changeJobPriority(SimpleJob *job, int newPrio)
{
SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job);
QHash<QString, HostQueue>::Iterator it = m_queuesByHostname.find(jobPriv->m_url.host());
if (it == m_queuesByHostname.end()) {
return;
}
HostQueue &hq = it.value();
const int prevLowestSerial = hq.lowestSerial();
if (hq.isJobRunning(job) || !hq.removeJob(job)) {
return;
}
jobPriv->m_schedSerial = m_serialPicker.changedPrioritySerial(jobPriv->m_schedSerial, newPrio);
hq.queueJob(job);
const bool needReinsert = hq.lowestSerial() != prevLowestSerial;
// the host queue might be absent from m_queuesBySerial because the connections per host limit
// for that host has been reached.
if (needReinsert && m_queuesBySerial.remove(prevLowestSerial)) {
m_queuesBySerial.insert(hq.lowestSerial(), &hq);
}
ensureNoDuplicates(&m_queuesBySerial);
}
void ProtoQueue::removeJob(SimpleJob *job)
{
SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(job);
HostQueue &hq = m_queuesByHostname[jobPriv->m_url.host()];
const int prevLowestSerial = hq.lowestSerial();
const int prevRunningJobs = hq.runningJobsCount();
Q_ASSERT(hq.runningJobsCount() <= m_maxConnectionsPerHost);
if (hq.removeJob(job)) {
if (hq.lowestSerial() != prevLowestSerial) {
// we have dequeued the not yet running job with the lowest serial
Q_ASSERT(!jobPriv->m_slave);
Q_ASSERT(prevRunningJobs == hq.runningJobsCount());
if (m_queuesBySerial.remove(prevLowestSerial) == 0) {
// make sure that the queue was not scheduled for a good reason
Q_ASSERT(hq.runningJobsCount() == m_maxConnectionsPerHost);
}
} else {
if (prevRunningJobs != hq.runningJobsCount()) {
// we have dequeued a previously running job
Q_ASSERT(prevRunningJobs - 1 == hq.runningJobsCount());
m_runningJobsCount--;
Q_ASSERT(m_runningJobsCount >= 0);
}
}
if (!hq.isQueueEmpty() && hq.runningJobsCount() < m_maxConnectionsPerHost) {
// this may be a no-op, but it's faster than first checking if it's already in.
m_queuesBySerial.insert(hq.lowestSerial(), &hq);
}
if (hq.isEmpty()) {
// no queued jobs, no running jobs. this destroys hq from above.
m_queuesByHostname.remove(jobPriv->m_url.host());
}
if (jobPriv->m_slave && jobPriv->m_slave->isAlive()) {
m_slaveKeeper.returnSlave(jobPriv->m_slave);
}
// just in case; startAJob() will refuse to start a job if it shouldn't.
m_startJobTimer.start();
} else {
// should be a connected slave
// if the assertion fails the job has probably changed the host part of its URL while
// running, so we can't find it by hostname. don't do this.
const bool removed = m_connectedSlaveQueue.removeJob(job);
Q_UNUSED(removed);
Q_ASSERT(removed);
}
ensureNoDuplicates(&m_queuesBySerial);
}
Slave *ProtoQueue::createSlave(const QString &protocol, SimpleJob *job, const KUrl &url)
{
int error;
QString errortext;
Slave *slave = Slave::createSlave(protocol, url, error, errortext);
if (slave) {
scheduler()->connect(slave, SIGNAL(slaveDied(KIO::Slave *)),
SLOT(slotSlaveDied(KIO::Slave *)));
scheduler()->connect(slave, SIGNAL(slaveStatus(pid_t,const QByteArray&,const QString &, bool)),
SLOT(slotSlaveStatus(pid_t,const QByteArray&, const QString &, bool)));
} else {
kError() << "couldn't create slave:" << errortext;
if (job) {
job->slotError(error, errortext);
}
}
return slave;
}
bool ProtoQueue::removeSlave (KIO::Slave *slave)
{
const bool removedConnected = m_connectedSlaveQueue.removeSlave(slave);
const bool removedUnconnected = m_slaveKeeper.removeSlave(slave);
Q_ASSERT(!(removedConnected && removedUnconnected));
return removedConnected || removedUnconnected;
}
QList<Slave *> ProtoQueue::allSlaves() const
{
QList<Slave *> ret(m_slaveKeeper.allSlaves());
Q_FOREACH (const HostQueue &hq, m_queuesByHostname) {
ret.append(hq.allSlaves());
}
ret.append(m_connectedSlaveQueue.allSlaves());
return ret;
}
//private slot
void ProtoQueue::startAJob()
{
ensureNoDuplicates(&m_queuesBySerial);
verifyRunningJobsCount(&m_queuesByHostname, m_runningJobsCount);
#ifdef SCHEDULER_DEBUG
kDebug(7006) << "m_runningJobsCount:" << m_runningJobsCount;
Q_FOREACH (const HostQueue &hq, m_queuesByHostname) {
Q_FOREACH (SimpleJob *job, hq.runningJobs()) {
kDebug(7006) << SimpleJobPrivate::get(job)->m_url;
}
}
#endif
if (m_runningJobsCount >= m_maxConnectionsTotal) {
#ifdef SCHEDULER_DEBUG
kDebug(7006) << "not starting any jobs because maxConnectionsTotal has been reached.";
#endif
return;
}
QMap<int, HostQueue *>::iterator first = m_queuesBySerial.begin();
if (first != m_queuesBySerial.end()) {
// pick a job and maintain the queue invariant: lower serials first
HostQueue *hq = first.value();
const int prevLowestSerial = first.key();
Q_UNUSED(prevLowestSerial);
Q_ASSERT(hq->lowestSerial() == prevLowestSerial);
// the following assertions should hold due to queueJob(), takeFirstInQueue() and
// removeJob() being correct
Q_ASSERT(hq->runningJobsCount() < m_maxConnectionsPerHost);
SimpleJob *startingJob = hq->takeFirstInQueue();
Q_ASSERT(hq->runningJobsCount() <= m_maxConnectionsPerHost);
Q_ASSERT(hq->lowestSerial() != prevLowestSerial);
m_queuesBySerial.erase(first);
// we've increased hq's runningJobsCount() by calling nexStartingJob()
// so we need to check again.
if (!hq->isQueueEmpty() && hq->runningJobsCount() < m_maxConnectionsPerHost) {
m_queuesBySerial.insert(hq->lowestSerial(), hq);
}
// always increase m_runningJobsCount because it's correct if there is a slave and if there
// is no slave, removeJob() will balance the number again. removeJob() would decrease the
// number too much otherwise.
// Note that createSlave() can call slotError() on a job which in turn calls removeJob(),
// so increase the count here already.
m_runningJobsCount++;
bool isNewSlave = false;
Slave *slave = m_slaveKeeper.takeSlaveForJob(startingJob);
SimpleJobPrivate *jobPriv = SimpleJobPrivate::get(startingJob);
if (!slave) {
isNewSlave = true;
slave = createSlave(jobPriv->m_protocol, startingJob, jobPriv->m_url);
}
if (slave) {
jobPriv->m_slave = slave;
setupSlave(slave, jobPriv->m_url, jobPriv->m_protocol, jobPriv->m_proxyList, isNewSlave);
startJob(startingJob, slave);
} else {
// dispose of our records about the job and mark the job as unknown
// (to prevent crashes later)
// note that the job's slotError() can have called removeJob() first, so check that
// it's not a ghost job with null serial already.
if (jobPriv->m_schedSerial) {
removeJob(startingJob);
jobPriv->m_schedSerial = 0;
}
}
} else {
#ifdef SCHEDULER_DEBUG
kDebug(7006) << "not starting any jobs because there is no queued job.";
#endif
}
if (!m_queuesBySerial.isEmpty()) {
m_startJobTimer.start();
}
}
class KIO::SchedulerPrivate
{
public:
SchedulerPrivate()
: q(new Scheduler()),
m_slaveOnHold(0),
m_checkOnHold(true), // !! Always check with KLauncher for the first request
m_ignoreConfigReparse(false)
{
}
~SchedulerPrivate()
{
delete q;
q = 0;
Q_FOREACH (ProtoQueue *p, m_protocols) {
Q_FOREACH (Slave *slave, p->allSlaves()) {
slave->kill();
}
p->deleteLater();
}
}
Scheduler *q;
Slave *m_slaveOnHold;
KUrl m_urlOnHold;
bool m_checkOnHold;
bool m_ignoreConfigReparse;
SessionData sessionData;
QMap<QObject *,WId> m_windowList;
void doJob(SimpleJob *job);
#ifndef KDE_NO_DEPRECATED
void scheduleJob(SimpleJob *job);
#endif
void setJobPriority(SimpleJob *job, int priority);
void cancelJob(SimpleJob *job);
void jobFinished(KIO::SimpleJob *job, KIO::Slave *slave);
void putSlaveOnHold(KIO::SimpleJob *job, const KUrl &url);
void removeSlaveOnHold();
Slave *getConnectedSlave(const KUrl &url, const KIO::MetaData &metaData);
bool assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job);
bool disconnectSlave(KIO::Slave *slave);
void checkSlaveOnHold(bool b);
void publishSlaveOnHold();
Slave *heldSlaveForJob(KIO::SimpleJob *job);
bool isSlaveOnHoldFor(const KUrl& url);
void registerWindow(QWidget *wid);
MetaData metaDataFor(const QString &protocol, const QStringList &proxyList, const KUrl &url);
void setupSlave(KIO::Slave *slave, const KUrl &url, const QString &protocol,
const QStringList &proxyList, bool newSlave, const KIO::MetaData *config = 0);
void slotSlaveDied(KIO::Slave *slave);
void slotSlaveStatus(pid_t pid, const QByteArray &protocol,
const QString &host, bool connected);
void slotReparseSlaveConfiguration(const QString &, const QDBusMessage&);
void slotSlaveOnHoldListChanged();
void slotSlaveConnected();
void slotSlaveError(int error, const QString &errorMsg);
void slotUnregisterWindow(QObject *);
ProtoQueue *protoQ(const QString &p)
{
ProtoQueue *pq = m_protocols.value(p, 0);
if (!pq) {
kDebug(7006) << "creating ProtoQueue instance for" << p;
pq = new ProtoQueue(this, KProtocolInfo::maxSlaves(p),
KProtocolInfo::maxSlavesPerHost(p));
m_protocols.insert(p, pq);
}
return pq;
}
private:
QHash<QString, ProtoQueue *> m_protocols;
};
K_GLOBAL_STATIC(SchedulerPrivate, schedulerPrivate)
Scheduler *Scheduler::self()
{
return schedulerPrivate->q;
}
+SchedulerPrivate *Scheduler::d_func()
+{
+ return schedulerPrivate;
+}
+
//static
Scheduler *scheduler()
{
return schedulerPrivate->q;
}
//static
Slave *heldSlaveForJob(SimpleJob *job)
{
return schedulerPrivate->heldSlaveForJob(job);
}
Scheduler::Scheduler()
- : d(0)
+ : removeMe(0)
{
setObjectName( "scheduler" );
const QString dbusPath = "/KIO/Scheduler";
const QString dbusInterface = "org.kde.KIO.Scheduler";
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerObject( "/KIO/Scheduler", this, QDBusConnection::ExportScriptableSlots |
QDBusConnection::ExportScriptableSignals );
dbus.connect(QString(), dbusPath, dbusInterface, "reparseSlaveConfiguration",
this, SLOT(slotReparseSlaveConfiguration(QString,QDBusMessage)));
dbus.connect(QString(), dbusPath, dbusInterface, "slaveOnHoldListChanged",
this, SLOT(slotSlaveOnHoldListChanged()));
}
Scheduler::~Scheduler()
{
}
void Scheduler::doJob(SimpleJob *job)
{
schedulerPrivate->doJob(job);
}
#ifndef KDE_NO_DEPRECATED
void Scheduler::scheduleJob(SimpleJob *job)
{
schedulerPrivate->scheduleJob(job);
}
#endif
void Scheduler::setJobPriority(SimpleJob *job, int priority)
{
schedulerPrivate->setJobPriority(job, priority);
}
void Scheduler::cancelJob(SimpleJob *job)
{
schedulerPrivate->cancelJob(job);
}
void Scheduler::jobFinished(KIO::SimpleJob *job, KIO::Slave *slave)
{
schedulerPrivate->jobFinished(job, slave);
}
void Scheduler::putSlaveOnHold(KIO::SimpleJob *job, const KUrl &url)
{
schedulerPrivate->putSlaveOnHold(job, url);
}
void Scheduler::removeSlaveOnHold()
{
schedulerPrivate->removeSlaveOnHold();
}
void Scheduler::publishSlaveOnHold()
{
schedulerPrivate->publishSlaveOnHold();
}
bool Scheduler::isSlaveOnHoldFor(const KUrl& url)
{
return schedulerPrivate->isSlaveOnHoldFor(url);
}
KIO::Slave *Scheduler::getConnectedSlave(const KUrl &url,
const KIO::MetaData &config )
{
return schedulerPrivate->getConnectedSlave(url, config);
}
bool Scheduler::assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job)
{
return schedulerPrivate->assignJobToSlave(slave, job);
}
bool Scheduler::disconnectSlave(KIO::Slave *slave)
{
return schedulerPrivate->disconnectSlave(slave);
}
void Scheduler::registerWindow(QWidget *wid)
{
schedulerPrivate->registerWindow(wid);
}
void Scheduler::unregisterWindow(QObject *wid)
{
schedulerPrivate->slotUnregisterWindow(wid);
}
bool Scheduler::connect( const char *signal, const QObject *receiver,
const char *member)
{
return QObject::connect(self(), signal, receiver, member);
}
bool Scheduler::connect( const QObject* sender, const char* signal,
const QObject* receiver, const char* member )
{
return QObject::connect(sender, signal, receiver, member);
}
bool Scheduler::disconnect( const QObject* sender, const char* signal,
const QObject* receiver, const char* member )
{
return QObject::disconnect(sender, signal, receiver, member);
}
bool Scheduler::connect( const QObject *sender, const char *signal,
const char *member )
{
return QObject::connect(sender, signal, member);
}
void Scheduler::checkSlaveOnHold(bool b)
{
schedulerPrivate->checkSlaveOnHold(b);
}
void Scheduler::emitReparseSlaveConfiguration()
{
// Do it immediately in this process, otherwise we might send a request before reparsing
// (e.g. when changing useragent in the plugin)
schedulerPrivate->slotReparseSlaveConfiguration(QString(), QDBusMessage());
schedulerPrivate->m_ignoreConfigReparse = true;
emit self()->reparseSlaveConfiguration( QString() );
}
void SchedulerPrivate::slotReparseSlaveConfiguration(const QString &proto, const QDBusMessage&)
{
if (m_ignoreConfigReparse) {
kDebug(7006) << "Ignoring signal sent by myself";
m_ignoreConfigReparse = false;
return;
}
kDebug(7006) << "proto=" << proto;
KProtocolManager::reparseConfiguration();
SlaveConfig::self()->reset();
sessionData.reset();
NetRC::self()->reload();
QHash<QString, ProtoQueue *>::ConstIterator it = proto.isEmpty() ? m_protocols.constBegin() :
m_protocols.constFind(proto);
// not found?
if (it == m_protocols.constEnd()) {
return;
}
QHash<QString, ProtoQueue *>::ConstIterator endIt = proto.isEmpty() ? m_protocols.constEnd() :
it + 1;
for (; it != endIt; ++it) {
Q_FOREACH(Slave *slave, (*it)->allSlaves()) {
slave->send(CMD_REPARSECONFIGURATION);
slave->resetHost();
}
}
}
void SchedulerPrivate::slotSlaveOnHoldListChanged()
{
m_checkOnHold = true;
}
static bool mayReturnContent(int cmd, const QString& protocol)
{
if (cmd == CMD_GET)
return true;
if (cmd == CMD_MULTI_GET)
return true;
if (cmd == CMD_SPECIAL && protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive))
return true;
return false;
}
void SchedulerPrivate::doJob(SimpleJob *job)
{
kDebug(7006) << job;
if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
kWarning(7006) << "KIO is not thread-safe.";
}
KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job);
jobPriv->m_protocol = KProtocolManager::slaveProtocol(job->url(), jobPriv->m_proxyList);
if (mayReturnContent(jobCommand(job), jobPriv->m_protocol)) {
jobPriv->m_checkOnHold = m_checkOnHold;
m_checkOnHold = false;
}
ProtoQueue *proto = protoQ(jobPriv->m_protocol);
proto->queueJob(job);
}
#ifndef KDE_NO_DEPRECATED
void SchedulerPrivate::scheduleJob(SimpleJob *job)
{
kDebug(7006) << job;
setJobPriority(job, 1);
}
#endif
void SchedulerPrivate::setJobPriority(SimpleJob *job, int priority)
{
kDebug(7006) << job << priority;
ProtoQueue *proto = protoQ(SimpleJobPrivate::get(job)->m_protocol);
proto->changeJobPriority(job, priority);
}
void SchedulerPrivate::cancelJob(SimpleJob *job)
{
// this method is called all over the place in job.cpp, so just do this check here to avoid
// much boilerplate in job code.
if (SimpleJobPrivate::get(job)->m_schedSerial == 0) {
//kDebug(7006) << "Doing nothing because I don't know job" << job;
return;
}
Slave *slave = jobSlave(job);
kDebug(7006) << job << slave;
if (slave) {
kDebug(7006) << "Scheduler: killing slave " << slave->slave_pid();
slave->kill();
}
jobFinished(job, slave);
}
void SchedulerPrivate::jobFinished(SimpleJob *job, Slave *slave)
{
kDebug(7006) << job << slave;
if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
kWarning(7006) << "KIO is not thread-safe.";
}
KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job);
// Preserve all internal meta-data so they can be sent back to the
// ioslaves as needed...
const KUrl jobUrl = job->url();
QMapIterator<QString, QString> it (jobPriv->m_internalMetaData);
while (it.hasNext()) {
it.next();
if (it.key().startsWith(QLatin1String("{internal~currenthost}"), Qt::CaseInsensitive)) {
SlaveConfig::self()->setConfigData(jobUrl.protocol(), jobUrl.host(), it.key().mid(22), it.value());
} else if (it.key().startsWith(QLatin1String("{internal~allhosts}"), Qt::CaseInsensitive)) {
SlaveConfig::self()->setConfigData(jobUrl.protocol(), QString(), it.key().mid(19), it.value());
}
}
// make sure that we knew about the job!
Q_ASSERT(jobPriv->m_schedSerial);
ProtoQueue *pq = m_protocols.value(jobPriv->m_protocol);
if (pq) {
pq->removeJob(job);
}
if (slave) {
// If we have internal meta-data, tell existing ioslaves to reload
// their configuration.
if (jobPriv->m_internalMetaData.count()) {
kDebug(7006) << "Updating ioslaves with new internal metadata information";
ProtoQueue * queue = m_protocols.value(slave->protocol());
if (queue) {
QListIterator<Slave*> it (queue->allSlaves());
while (it.hasNext()) {
Slave* runningSlave = it.next();
if (slave->host() == runningSlave->host()) {
slave->setConfig(metaDataFor(slave->protocol(), jobPriv->m_proxyList, jobUrl));
kDebug(7006) << "Updated configuration of" << slave->protocol()
<< "ioslave, pid=" << slave->slave_pid();
}
}
}
}
slave->setJob(0);
slave->disconnect(job);
}
jobPriv->m_schedSerial = 0; // this marks the job as unscheduled again
jobPriv->m_slave = 0;
// Clear the values in the internal metadata container since they have
// already been taken care of above...
jobPriv->m_internalMetaData.clear();
}
// static
void setupSlave(KIO::Slave *slave, const KUrl &url, const QString &protocol,
const QStringList &proxyList , bool newSlave, const KIO::MetaData *config)
{
schedulerPrivate->setupSlave(slave, url, protocol, proxyList, newSlave, config);
}
MetaData SchedulerPrivate::metaDataFor(const QString &protocol, const QStringList &proxyList, const KUrl &url)
{
const QString host = url.host();
MetaData configData = SlaveConfig::self()->configData(protocol, host);
sessionData.configDataFor( configData, protocol, host );
if (proxyList.isEmpty()) {
configData.remove(QLatin1String("UseProxy"));
configData.remove(QLatin1String("ProxyUrls"));
} else {
configData[QLatin1String("UseProxy")] = proxyList.first();
configData[QLatin1String("ProxyUrls")] = proxyList.join(QLatin1String(","));
}
if ( configData.contains("EnableAutoLogin") &&
configData.value("EnableAutoLogin").compare("true", Qt::CaseInsensitive) == 0 )
{
NetRC::AutoLogin l;
l.login = url.user();
bool usern = (protocol == "ftp");
if ( NetRC::self()->lookup( url, l, usern) )
{
configData["autoLoginUser"] = l.login;
configData["autoLoginPass"] = l.password;
if ( usern )
{
QString macdef;
QMap<QString, QStringList>::ConstIterator it = l.macdef.constBegin();
for ( ; it != l.macdef.constEnd(); ++it )
macdef += it.key() + '\\' + it.value().join( "\\" ) + '\n';
configData["autoLoginMacro"] = macdef;
}
}
}
return configData;
}
void SchedulerPrivate::setupSlave(KIO::Slave *slave, const KUrl &url, const QString &protocol,
const QStringList &proxyList, bool newSlave, const KIO::MetaData *config)
{
int port = url.port();
if ( port == -1 ) // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that.
port = 0;
const QString host = url.host();
const QString user = url.user();
const QString passwd = url.pass();
if (newSlave || slave->host() != host || slave->port() != port ||
slave->user() != user || slave->passwd() != passwd) {
MetaData configData = metaDataFor(protocol, proxyList, url);
if (config)
configData += *config;
slave->setConfig(configData);
slave->setProtocol(url.protocol());
slave->setHost(host, port, user, passwd);
}
}
void SchedulerPrivate::slotSlaveStatus(pid_t, const QByteArray&, const QString &, bool)
{
}
void SchedulerPrivate::slotSlaveDied(KIO::Slave *slave)
{
kDebug(7006) << slave;
Q_ASSERT(slave);
Q_ASSERT(!slave->isAlive());
ProtoQueue *pq = m_protocols.value(slave->protocol());
if (pq) {
if (slave->job()) {
pq->removeJob(slave->job());
}
// in case this was a connected slave...
pq->removeSlave(slave);
}
if (slave == m_slaveOnHold) {
m_slaveOnHold = 0;
m_urlOnHold.clear();
}
slave->deref(); // Delete slave
}
void SchedulerPrivate::putSlaveOnHold(KIO::SimpleJob *job, const KUrl &url)
{
Slave *slave = jobSlave(job);
kDebug(7006) << job << url << slave;
slave->disconnect(job);
// prevent the fake death of the slave from trying to kill the job again;
// cf. Slave::hold(const KUrl &url) called in SchedulerPrivate::publishSlaveOnHold().
slave->setJob(0);
SimpleJobPrivate::get(job)->m_slave = 0;
if (m_slaveOnHold) {
m_slaveOnHold->kill();
}
m_slaveOnHold = slave;
m_urlOnHold = url;
m_slaveOnHold->suspend();
}
void SchedulerPrivate::publishSlaveOnHold()
{
kDebug(7006) << m_slaveOnHold;
if (!m_slaveOnHold)
return;
m_slaveOnHold->hold(m_urlOnHold);
emit q->slaveOnHoldListChanged();
}
bool SchedulerPrivate::isSlaveOnHoldFor(const KUrl& url)
{
if (url.isValid() && m_urlOnHold.isValid() && url == m_urlOnHold)
return true;
return Slave::checkForHeldSlave(url);
}
Slave *SchedulerPrivate::heldSlaveForJob(SimpleJob *job)
{
Slave *slave = 0;
KIO::SimpleJobPrivate *const jobPriv = SimpleJobPrivate::get(job);
if (jobPriv->m_checkOnHold) {
slave = Slave::holdSlave(jobPriv->m_protocol, job->url());
}
if (!slave && m_slaveOnHold) {
// Make sure that the job wants to do a GET or a POST, and with no offset
const int cmd = jobPriv->m_command;
bool canJobReuse = (cmd == CMD_GET || cmd == CMD_MULTI_GET);
if (KIO::TransferJob *tJob = qobject_cast<KIO::TransferJob *>(job)) {
canJobReuse = ( canJobReuse || cmd == CMD_SPECIAL );
if (canJobReuse) {
KIO::MetaData outgoing = tJob->outgoingMetaData();
const QString resume = outgoing.value("resume");
kDebug(7006) << "Resume metadata is" << resume;
canJobReuse = (resume.isEmpty() || resume == "0");
}
}
if (job->url() == m_urlOnHold) {
if (canJobReuse) {
kDebug(7006) << "HOLD: Reusing held slave (" << m_slaveOnHold << ")";
slave = m_slaveOnHold;
} else {
kDebug(7006) << "HOLD: Discarding held slave (" << m_slaveOnHold << ")";
m_slaveOnHold->kill();
}
m_slaveOnHold = 0;
m_urlOnHold.clear();
}
} else if (slave) {
kDebug(7006) << "HOLD: Reusing klauncher held slave (" << slave << ")";
}
return slave;
}
void SchedulerPrivate::removeSlaveOnHold()
{
kDebug(7006) << m_slaveOnHold;
if (m_slaveOnHold) {
m_slaveOnHold->kill();
}
m_slaveOnHold = 0;
m_urlOnHold.clear();
}
Slave *SchedulerPrivate::getConnectedSlave(const KUrl &url, const KIO::MetaData &config)
{
QStringList proxyList;
const QString protocol = KProtocolManager::slaveProtocol(url, proxyList);
ProtoQueue *pq = protoQ(protocol);
Slave *slave = pq->createSlave(protocol, /* job */0, url);
if (slave) {
setupSlave(slave, url, protocol, proxyList, true, &config);
pq->m_connectedSlaveQueue.addSlave(slave);
slave->send( CMD_CONNECT );
q->connect(slave, SIGNAL(connected()),
SLOT(slotSlaveConnected()));
q->connect(slave, SIGNAL(error(int, const QString &)),
SLOT(slotSlaveError(int, const QString &)));
}
kDebug(7006) << url << slave;
return slave;
}
void SchedulerPrivate::slotSlaveConnected()
{
kDebug(7006);
Slave *slave = static_cast<Slave *>(q->sender());
slave->setConnected(true);
q->disconnect(slave, SIGNAL(connected()), q, SLOT(slotSlaveConnected()));
emit q->slaveConnected(slave);
}
void SchedulerPrivate::slotSlaveError(int errorNr, const QString &errorMsg)
{
Slave *slave = static_cast<Slave *>(q->sender());
kDebug(7006) << slave << errorNr << errorMsg;
ProtoQueue *pq = protoQ(slave->protocol());
if (!slave->isConnected() || pq->m_connectedSlaveQueue.isIdle(slave)) {
// Only forward to application if slave is idle or still connecting.
// ### KDE5: can we remove this apparently arbitrary behavior and just always emit SlaveError?
emit q->slaveError(slave, errorNr, errorMsg);
}
}
bool SchedulerPrivate::assignJobToSlave(KIO::Slave *slave, SimpleJob *job)
{
kDebug(7006) << slave << job;
// KDE5: queueing of jobs can probably be removed, it provides very little benefit
ProtoQueue *pq = m_protocols.value(slave->protocol());
pq->removeJob(job);
return (pq ? pq->m_connectedSlaveQueue.queueJob(job, slave) : false);
}
bool SchedulerPrivate::disconnectSlave(KIO::Slave *slave)
{
kDebug(7006) << slave;
ProtoQueue *pq = m_protocols.value(slave->protocol());
return (pq ? pq->m_connectedSlaveQueue.removeSlave(slave) : false);
}
void SchedulerPrivate::checkSlaveOnHold(bool b)
{
kDebug(7006) << b;
m_checkOnHold = b;
}
void SchedulerPrivate::registerWindow(QWidget *wid)
{
if (!wid)
return;
QWidget* window = wid->window();
QObject *obj = static_cast<QObject *>(window);
if (!m_windowList.contains(obj))
{
// We must store the window Id because by the time
// the destroyed signal is emitted we can no longer
// access QWidget::winId() (already destructed)
WId windowId = window->winId();
m_windowList.insert(obj, windowId);
q->connect(window, SIGNAL(destroyed(QObject *)),
SLOT(slotUnregisterWindow(QObject*)));
QDBusInterface("org.kde.kded", "/kded", "org.kde.kded").
call(QDBus::NoBlock, "registerWindowId", qlonglong(windowId));
}
}
void SchedulerPrivate::slotUnregisterWindow(QObject *obj)
{
if (!obj)
return;
QMap<QObject *, WId>::Iterator it = m_windowList.find(obj);
if (it == m_windowList.end())
return;
WId windowId = it.value();
q->disconnect(it.key(), SIGNAL(destroyed(QObject *)),
q, SLOT(slotUnregisterWindow(QObject*)));
m_windowList.erase( it );
QDBusInterface("org.kde.kded", "/kded", "org.kde.kded").
call(QDBus::NoBlock, "unregisterWindowId", qlonglong(windowId));
}
#include "scheduler.moc"
#include "scheduler_p.moc"
diff --git a/kio/kio/scheduler.h b/kio/kio/scheduler.h
index d34c1cb8b4..1b8f9b0771 100644
--- a/kio/kio/scheduler.h
+++ b/kio/kio/scheduler.h
@@ -1,308 +1,309 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 2000 Stephan Kulow <coolo@kde.org>
Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef _kio_scheduler_h
#define _kio_scheduler_h
#include "kio/job.h"
#include "kio/jobclasses.h"
#include <QtCore/QTimer>
#include <QtCore/QMap>
#include <QtGui/QWidgetList>
#include <sys/types.h> // pid_t
namespace KIO {
class Slave;
class SlaveConfig;
class SessionData;
class SchedulerPrivate;
/**
* The KIO::Scheduler manages io-slaves for the application.
* It also queues jobs and assigns the job to a slave when one
* becomes available.
*
* There are 3 possible ways for a job to get a slave:
*
* <h3>1. Direct</h3>
* This is the default. When you create a job the
* KIO::Scheduler will be notified and will find either an existing
* slave that is idle or it will create a new slave for the job.
*
* Example:
* \code
* TransferJob *job = KIO::get(KUrl("http://www.kde.org"));
* \endcode
*
*
* <h3>2. Scheduled</h3>
* If you create a lot of jobs, you might want not want to have a
* slave for each job. If you schedule a job, a maximum number
* of slaves will be created. When more jobs arrive, they will be
* queued. When a slave is finished with a job, it will be assigned
* a job from the queue.
*
* Example:
* \code
* TransferJob *job = KIO::get(KUrl("http://www.kde.org"));
* KIO::Scheduler::scheduleJob(job);
* \endcode
*
* <h3>3. Connection Oriented</h3>
* For some operations it is important that multiple jobs use
* the same connection. This can only be ensured if all these jobs
* use the same slave.
*
* You can ask the scheduler to open a slave for connection oriented
* operations. You can then use the scheduler to assign jobs to this
* slave. The jobs will be queued and the slave will handle these jobs
* one after the other.
*
* Example:
* \code
* Slave *slave = KIO::Scheduler::getConnectedSlave(
* KUrl("pop3://bastian:password@mail.kde.org"));
* TransferJob *job1 = KIO::get(
* KUrl("pop3://bastian:password@mail.kde.org/msg1"));
* KIO::Scheduler::assignJobToSlave(slave, job1);
* TransferJob *job2 = KIO::get(
* KUrl("pop3://bastian:password@mail.kde.org/msg2"));
* KIO::Scheduler::assignJobToSlave(slave, job2);
* TransferJob *job3 = KIO::get(
* KUrl("pop3://bastian:password@mail.kde.org/msg3"));
* KIO::Scheduler::assignJobToSlave(slave, job3);
*
* // ... Wait for jobs to finish...
*
* KIO::Scheduler::disconnectSlave(slave);
* \endcode
*
* Note that you need to explicitly disconnect the slave when the
* connection goes down, so your error handler should contain:
* \code
* if (error == KIO::ERR_CONNECTION_BROKEN)
* KIO::Scheduler::disconnectSlave(slave);
* \endcode
*
* @see KIO::Slave
* @see KIO::Job
**/
class KIO_EXPORT Scheduler : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KIO.Scheduler")
public:
/**
* Register @p job with the scheduler.
* The default is to create a new slave for the job if no slave
* is available. This can be changed by calling scheduleJob.
* @param job the job to register
*/
static void doJob(SimpleJob *job);
/**
* Schedules @p job scheduled for later
* execution. This just sets the job's priority to 1 now.
* @param job the job to schedule
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED static void scheduleJob(SimpleJob *job);
#endif
/**
* Changes the priority of @p job; jobs of the same priority run in the order in which
* they were created. Jobs of lower numeric priority always run before any
* waiting jobs of higher numeric priority. The range of priority is -10 to 10,
* the default priority of jobs is 0.
* @param job the job to change
* @param priority new priority of @p job, lower runs earlier
*/
static void setJobPriority(SimpleJob *job, int priority);
/**
* Stop the execution of a job.
* @param job the job to cancel
*/
static void cancelJob(SimpleJob *job);
/**
* Called when a job is done.
* @param job the finished job
* @param slave the slave that executed the @p job
*/
static void jobFinished(KIO::SimpleJob *job, KIO::Slave *slave);
/**
* Puts a slave on notice. A next job may reuse this slave if it
* requests the same URL.
*
* A job can be put on hold after it has emit'ed its mimetype.
* Based on the mimetype, the program can give control to another
* component in the same process which can then resume the job
* by simply asking for the same URL again.
* @param job the job that should be stopped
* @param url the URL that is handled by the @p url
*/
static void putSlaveOnHold(KIO::SimpleJob *job, const KUrl &url);
/**
* Removes any slave that might have been put on hold. If a slave
* was put on hold it will be killed.
*/
static void removeSlaveOnHold();
/**
* Send the slave that was put on hold back to KLauncher. This
* allows another process to take over the slave and resume the job
* that was started.
*/
static void publishSlaveOnHold();
/**
* Requests a slave for use in connection-oriented mode.
*
* @param url This defines the username,password,host & port to
* connect with.
* @param config Configuration data for the slave.
*
* @return A pointer to a connected slave or 0 if an error occurred.
* @see assignJobToSlave()
* @see disconnectSlave()
*/
static KIO::Slave *getConnectedSlave(const KUrl &url,
const KIO::MetaData &config = MetaData() );
/**
* Uses @p slave to do @p job.
* This function should be called immediately after creating a Job.
*
* @param slave The slave to use. The slave must have been obtained
* with a call to getConnectedSlave and must not
* be currently assigned to any other job.
* @param job The job to do.
*
* @return true is successful, false otherwise.
*
* @see getConnectedSlave()
* @see disconnectSlave()
* @see slaveConnected()
* @see slaveError()
*/
static bool assignJobToSlave(KIO::Slave *slave, KIO::SimpleJob *job);
/**
* Disconnects @p slave.
*
* @param slave The slave to disconnect. The slave must have been
* obtained with a call to getConnectedSlave
* and must not be assigned to any job.
*
* @return true is successful, false otherwise.
*
* @see getConnectedSlave
* @see assignJobToSlave
*/
static bool disconnectSlave(KIO::Slave *slave);
/**
* Register the mainwindow @p wid with the KIO subsystem
* Do not call this, it is called automatically from
* void KIO::Job::setWindow(QWidget*).
* @param wid the window to register
*/
static void registerWindow(QWidget *wid);
/**
* @internal
* Unregisters the window registered by registerWindow().
*/
static void unregisterWindow(QObject *wid);
/**
* Function to connect signals emitted by the scheduler.
*
* @see slaveConnected()
* @see slaveError()
*/
// KDE5: those methods should probably be removed, ugly and only marginally useful
static bool connect( const char *signal, const QObject *receiver,
const char *member);
static bool connect( const QObject* sender, const char* signal,
const QObject* receiver, const char* member );
static bool disconnect( const QObject* sender, const char* signal,
const QObject* receiver, const char* member );
bool connect( const QObject *sender, const char *signal,
const char *member );
/**
* When true, the next job will check whether KLauncher has a slave
* on hold that is suitable for the job.
* @param b true when KLauncher has a job on hold
*/
static void checkSlaveOnHold(bool b);
static void emitReparseSlaveConfiguration();
/**
* Returns true if there is a slave on hold for @p url.
*
* @since 4.7
*/
static bool isSlaveOnHoldFor(const KUrl& url);
Q_SIGNALS:
void slaveConnected(KIO::Slave *slave);
void slaveError(KIO::Slave *slave, int error, const QString &errorMsg);
// DBUS
Q_SCRIPTABLE void reparseSlaveConfiguration(const QString &);
Q_SCRIPTABLE void slaveOnHoldListChanged();
private:
Q_DISABLE_COPY(Scheduler)
Scheduler();
~Scheduler();
static Scheduler *self();
- Q_PRIVATE_SLOT(schedulerPrivate, void slotSlaveDied(KIO::Slave *slave))
- Q_PRIVATE_SLOT(schedulerPrivate, void slotSlaveStatus(pid_t pid, const QByteArray &protocol,
+ Q_PRIVATE_SLOT(d_func(), void slotSlaveDied(KIO::Slave *slave))
+ Q_PRIVATE_SLOT(d_func(), void slotSlaveStatus(pid_t pid, const QByteArray &protocol,
const QString &host, bool connected))
// connected to D-Bus signal:
- Q_PRIVATE_SLOT(schedulerPrivate, void slotReparseSlaveConfiguration(const QString &, const QDBusMessage&))
- Q_PRIVATE_SLOT(schedulerPrivate, void slotSlaveOnHoldListChanged())
+ Q_PRIVATE_SLOT(d_func(), void slotReparseSlaveConfiguration(const QString &, const QDBusMessage&))
+ Q_PRIVATE_SLOT(d_func(), void slotSlaveOnHoldListChanged())
- Q_PRIVATE_SLOT(schedulerPrivate, void slotSlaveConnected())
- Q_PRIVATE_SLOT(schedulerPrivate, void slotSlaveError(int error, const QString &errorMsg))
- Q_PRIVATE_SLOT(schedulerPrivate, void slotUnregisterWindow(QObject *))
+ Q_PRIVATE_SLOT(d_func(), void slotSlaveConnected())
+ Q_PRIVATE_SLOT(d_func(), void slotSlaveError(int error, const QString &errorMsg))
+ Q_PRIVATE_SLOT(d_func(), void slotUnregisterWindow(QObject *))
private:
friend class SchedulerPrivate;
- SchedulerPrivate *const d;
+ SchedulerPrivate *const removeMe; // for BC only, KDE5: remove
+ SchedulerPrivate *d_func();
};
}
#endif
diff --git a/kio/tests/kdirmodeltest.cpp b/kio/tests/kdirmodeltest.cpp
index 4878cf2fcb..d3ad365f53 100644
--- a/kio/tests/kdirmodeltest.cpp
+++ b/kio/tests/kdirmodeltest.cpp
@@ -1,1392 +1,1413 @@
/* This file is part of the KDE project
Copyright 2006 - 2007 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdirmodeltest.h"
#include <kdirnotify.h>
#include <kio/copyjob.h>
#include <kio/chmodjob.h>
#include <kprotocolinfo.h>
#include "kdirmodeltest.moc"
#include <kdirmodel.h>
#include <kdirlister.h>
//TODO #include "../../kdeui/tests/proxymodeltestsuite/modelspy.h"
#include <qtest_kde.h>
#ifdef Q_OS_UNIX
#include <utime.h>
#endif
#include <kdebug.h>
#include <kio/deletejob.h>
#include <kio/job.h>
#include <kio/netaccess.h>
#include <kdirwatch.h>
#include "kiotesthelper.h"
QTEST_KDEMAIN( KDirModelTest, NoGUI )
#ifndef USE_QTESTEVENTLOOP
#define exitLoop quit
#endif
#define connect(a,b,c,d) QVERIFY(QObject::connect(a,b,c,d))
#ifndef Q_WS_WIN
#define SPECIALCHARS "specialchars%:.pdf"
#else
#define SPECIALCHARS "specialchars%.pdf"
#endif
Q_DECLARE_METATYPE(KFileItemList)
void KDirModelTest::initTestCase()
{
qRegisterMetaType<QModelIndex>("QModelIndex"); // beats me why Qt doesn't do that
qRegisterMetaType<KFileItemList>("KFileItemList");
m_dirModelForExpand = 0;
m_dirModel = 0;
s_referenceTimeStamp = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
m_tempDir = 0;
m_topLevelFileNames << "toplevelfile_1"
<< "toplevelfile_2"
<< "toplevelfile_3"
<< SPECIALCHARS
;
recreateTestData();
fillModel( false );
}
void KDirModelTest::recreateTestData()
{
if (m_tempDir) {
kDebug() << "Deleting old tempdir" << m_tempDir->name();
delete m_tempDir;
qApp->processEvents(); // process inotify events so they don't pollute us later on
}
m_tempDir = new KTempDir;
kDebug() << "new tmp dir:" << m_tempDir->name();
// Create test data:
/*
* PATH/toplevelfile_1
* PATH/toplevelfile_2
* PATH/toplevelfile_3
* PATH/specialchars%:.pdf
* PATH/.hidden
* PATH/.hidden2
* PATH/subdir
* PATH/subdir/testfile
* PATH/subdir/testsymlink
* PATH/subdir/subsubdir
* PATH/subdir/subsubdir/testfile
*/
const QString path = m_tempDir->name();
foreach(const QString &f, m_topLevelFileNames) {
createTestFile(path+f);
}
createTestFile(path+".hidden");
createTestFile(path+".hidden2");
createTestDirectory(path+"subdir");
createTestDirectory(path+"subdir/subsubdir", NoSymlink);
m_dirIndex = QModelIndex();
m_fileIndex = QModelIndex();
m_secondFileIndex = QModelIndex();
}
void KDirModelTest::cleanupTestCase()
{
delete m_tempDir;
m_tempDir = 0;
delete m_dirModel;
m_dirModel = 0;
}
void KDirModelTest::fillModel(bool reload, bool expectAllIndexes)
{
if (!m_dirModel)
m_dirModel = new KDirModel;
m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false, 0);
const QString path = m_tempDir->name();
KDirLister* dirLister = m_dirModel->dirLister();
kDebug() << "Calling openUrl";
dirLister->openUrl(KUrl(path), reload ? KDirLister::Reload : KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
kDebug() << "enterLoop, waiting for completed()";
enterLoop();
if (expectAllIndexes)
collectKnownIndexes();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
// Called after test function
void KDirModelTest::cleanup()
{
if (m_dirModel) {
disconnect(m_dirModel, 0, &m_eventLoop, 0);
disconnect(m_dirModel->dirLister(), 0, this, 0);
m_dirModel->dirLister()->setNameFilter(QString());
m_dirModel->dirLister()->setMimeFilter(QStringList());
m_dirModel->dirLister()->emitChanges();
}
}
void KDirModelTest::collectKnownIndexes()
{
m_dirIndex = QModelIndex();
m_fileIndex = QModelIndex();
m_secondFileIndex = QModelIndex();
// Create the indexes once and for all
// The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance.
for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) {
QModelIndex idx = m_dirModel->index(row, 0, QModelIndex());
QVERIFY(idx.isValid());
KFileItem item = m_dirModel->itemForIndex(idx);
kDebug() << item.url() << "isDir=" << item.isDir();
if (item.isDir())
m_dirIndex = idx;
else if (item.url().fileName() == "toplevelfile_1")
m_fileIndex = idx;
else if (item.url().fileName() == "toplevelfile_2")
m_secondFileIndex = idx;
else if (item.url().fileName().startsWith("special"))
m_specialFileIndex = idx;
}
QVERIFY(m_dirIndex.isValid());
QVERIFY(m_fileIndex.isValid());
QVERIFY(m_secondFileIndex.isValid());
QVERIFY(m_specialFileIndex.isValid());
// Now list subdir/
QVERIFY(m_dirModel->canFetchMore(m_dirIndex));
m_dirModel->fetchMore(m_dirIndex);
kDebug() << "Listing subdir/";
enterLoop();
// Index of a file inside a directory (subdir/testfile)
QModelIndex subdirIndex;
m_fileInDirIndex = QModelIndex();
for (int row = 0; row < 3; ++row) {
QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex);
if (m_dirModel->itemForIndex(idx).isDir())
subdirIndex = idx;
else if (m_dirModel->itemForIndex(idx).name() == "testfile")
m_fileInDirIndex = idx;
}
// List subdir/subsubdir
QVERIFY(m_dirModel->canFetchMore(subdirIndex));
kDebug() << "Listing subdir/subsubdir";
m_dirModel->fetchMore(subdirIndex);
enterLoop();
// Index of ... well, subdir/subsubdir/testfile
m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex);
}
void KDirModelTest::enterLoop()
{
#ifdef USE_QTESTEVENTLOOP
m_eventLoop.enterLoop(10 /*seconds max*/);
QVERIFY(!m_eventLoop.timeout());
#else
m_eventLoop.exec();
#endif
}
void KDirModelTest::slotListingCompleted()
{
kDebug();
#ifdef USE_QTESTEVENTLOOP
m_eventLoop.exitLoop();
#else
m_eventLoop.quit();
#endif
}
void KDirModelTest::testRowCount()
{
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/);
const int subdirRowCount = m_dirModel->rowCount(m_dirIndex);
QCOMPARE(subdirRowCount, 3);
QVERIFY(m_fileIndex.isValid());
const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555
QCOMPARE(fileRowCount, 0);
}
void KDirModelTest::testIndex()
{
QVERIFY(m_dirModel->hasChildren());
// Index of the first file
QVERIFY(m_fileIndex.isValid());
QCOMPARE(m_fileIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileIndex.row(), 0);
QCOMPARE(m_fileIndex.column(), 0);
QVERIFY(!m_fileIndex.parent().isValid());
QVERIFY(!m_dirModel->hasChildren(m_fileIndex));
// Index of a directory
QVERIFY(m_dirIndex.isValid());
QCOMPARE(m_dirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_dirIndex.row(), 3);
QCOMPARE(m_dirIndex.column(), 0);
QVERIFY(!m_dirIndex.parent().isValid());
QVERIFY(m_dirModel->hasChildren(m_dirIndex));
// Index of a file inside a directory (subdir/testfile)
QVERIFY(m_fileInDirIndex.isValid());
QCOMPARE(m_fileInDirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileInDirIndex.row(), 0);
QCOMPARE(m_fileInDirIndex.column(), 0);
QVERIFY(m_fileInDirIndex.parent() == m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex));
// Index of subdir/subsubdir/testfile
QVERIFY(m_fileInSubdirIndex.isValid());
QCOMPARE(m_fileInSubdirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileInSubdirIndex.row(), 0);
QCOMPARE(m_fileInSubdirIndex.column(), 0);
QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex));
}
void KDirModelTest::testNames()
{
QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString();
QCOMPARE(fileName, QString("toplevelfile_1"));
QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString();
QCOMPARE(specialFileName, QString(SPECIALCHARS));
QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString();
QCOMPARE(dirName, QString("subdir"));
QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString();
QCOMPARE(fileInDirName, QString("testfile"));
QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString();
QCOMPARE(fileInSubdirName, QString("testfile"));
}
void KDirModelTest::testItemForIndex()
{
// root item
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
QVERIFY(!rootItem.isNull());
QCOMPARE(rootItem.name(), QString("."));
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QVERIFY(!fileItem.isNull());
QCOMPARE(fileItem.name(), QString("toplevelfile_1"));
QVERIFY(!fileItem.isDir());
QCOMPARE(fileItem.url().path(), QString(m_tempDir->name() + "toplevelfile_1"));
KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex);
QVERIFY(!dirItem.isNull());
QCOMPARE(dirItem.name(), QString("subdir"));
QVERIFY(dirItem.isDir());
QCOMPARE(dirItem.url().path(), QString(m_tempDir->name() + "subdir"));
KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex);
QVERIFY(!fileInDirItem.isNull());
QCOMPARE(fileInDirItem.name(), QString("testfile"));
QVERIFY(!fileInDirItem.isDir());
QCOMPARE(fileInDirItem.url().path(), QString(m_tempDir->name() + "subdir/testfile"));
KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex);
QVERIFY(!fileInSubdirItem.isNull());
QCOMPARE(fileInSubdirItem.name(), QString("testfile"));
QVERIFY(!fileInSubdirItem.isDir());
QCOMPARE(fileInSubdirItem.url().path(), QString(m_tempDir->name() + "subdir/subsubdir/testfile"));
}
void KDirModelTest::testIndexForItem()
{
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
QModelIndex rootIndex = m_dirModel->indexForItem(rootItem);
QVERIFY(!rootIndex.isValid());
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QModelIndex fileIndex = m_dirModel->indexForItem(fileItem);
QCOMPARE(fileIndex, m_fileIndex);
KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex);
QModelIndex dirIndex = m_dirModel->indexForItem(dirItem);
QCOMPARE(dirIndex, m_dirIndex);
KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex);
QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem);
QCOMPARE(fileInDirIndex, m_fileInDirIndex);
KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex);
QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem);
QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex);
}
void KDirModelTest::testData()
{
// First file
QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex());
int size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toInt();
QCOMPARE(size1, 11);
KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value<KFileItem>();
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QCOMPARE(item, fileItem);
QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown);
// Second file
QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex());
QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString();
QCOMPARE(display2, QString("toplevelfile_2"));
// Subdir: check child count
QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 3);
// Subsubdir: check child count
QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1);
}
void KDirModelTest::testReload()
{
fillModel( true );
testItemForIndex();
}
Q_DECLARE_METATYPE(QModelIndex) // needed for .value<QModelIndex>()
// We want more info than just "the values differ", if they do.
#define COMPARE_INDEXES(a, b) \
QCOMPARE(a.row(), b.row()); \
QCOMPARE(a.column(), b.column()); \
QCOMPARE(a.model(), b.model()); \
QCOMPARE(a.parent().isValid(), b.parent().isValid()); \
QCOMPARE(a, b);
void KDirModelTest::testModifyFile()
{
const QString file = m_tempDir->name() + "toplevelfile_2";
const KUrl url(file);
#if 1
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
#else
ModelSpy modelSpy(m_dirModel);
#endif
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
// "Touch" the file
setTimeStamp(file, s_referenceTimeStamp.addSecs(20) );
// In stat mode, kdirwatch doesn't notice file changes; we need to trigger it
// by creating a file.
//createTestFile(m_tempDir->name() + "toplevelfile_5");
KDirWatch::self()->setDirty(m_tempDir->name());
// Wait for KDirWatch to notify the change (especially when using Stat)
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
#if 0
QCOMPARE(modelSpy.count(), 1);
const QVariantList dataChanged = modelSpy.first();
#else
const QVariantList dataChanged = spyDataChanged[0];
#endif
QModelIndex receivedIndex = dataChanged[0].value<QModelIndex>();
COMPARE_INDEXES(receivedIndex, m_secondFileIndex);
receivedIndex = dataChanged[1].value<QModelIndex>();
QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
void KDirModelTest::testRenameFile()
{
const KUrl url(m_tempDir->name() + "toplevelfile_2");
const KUrl newUrl(m_tempDir->name() + "toplevelfile_2_renamed");
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 1);
COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_secondFileIndex);
QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>();
QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1
// check renaming happened
QCOMPARE( m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), newUrl.url() );
// check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385)
KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl);
QVERIFY(!cachedItem.isNull());
QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed"));
QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed"));
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
QCOMPARE( m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), url.url() );
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
void KDirModelTest::testMoveDirectory()
{
testMoveDirectory("subdir");
}
void KDirModelTest::testMoveDirectory(const QString& dir /*just a dir name, no slash*/)
{
const QString path = m_tempDir->name();
const QString srcdir = path + dir;
QVERIFY(QDir(srcdir).exists());
KTempDir destDir;
const QString dest = destDir.name();
QVERIFY(QDir(dest).exists());
connect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// Move
kDebug() << "Moving" << srcdir << "to" << dest;
KIO::CopyJob* job = KIO::move(KUrl(srcdir), KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
// wait for kdirnotify
enterLoop();
disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
connect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// Move back
kDebug() << "Moving" << dest+dir << "back to" << srcdir;
job = KIO::move(KUrl(dest + dir), KUrl(srcdir), KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
enterLoop();
QVERIFY(QDir(srcdir).exists());
disconnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// m_dirIndex is invalid after the above...
fillModel(true);
}
void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156)
{
const QString path = m_tempDir->name();
const KUrl url(path + "subdir");
const KUrl newUrl(path + "subdir_renamed");
// For #180156 we need a second kdirmodel, viewing the subdir being renamed.
// I'm abusing m_dirModelForExpand for that purpose.
delete m_dirModelForExpand;
m_dirModelForExpand = new KDirModel;
KDirLister* dirListerForExpand = m_dirModelForExpand->dirLister();
connect(dirListerForExpand, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
dirListerForExpand->openUrl(url); // async
enterLoop();
// Now do the renaming
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
//QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times...
//COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_dirIndex);
//QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>();
//QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1
// check renaming happened
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), newUrl.url());
QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex);
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/testfile")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir/testfile")).isValid());
// Check the other kdirmodel got redirected
QCOMPARE(dirListerForExpand->url().path(), QString(path+"subdir_renamed"));
kDebug() << "calling testMoveDirectory(subdir_renamed)";
// Test moving the renamed directory; if something inside KDirModel
// wasn't properly updated by the renaming, this would detect it and crash (#180673)
testMoveDirectory("subdir_renamed");
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), url.url());
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), url.url());
QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex);
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/testfile")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir/testfile")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/testfile")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir/testfile")).isValid());
// TODO INVESTIGATE
// QCOMPARE(dirListerForExpand->url().path(), path+"subdir");
delete m_dirModelForExpand;
m_dirModelForExpand = 0;
}
void KDirModelTest::testRenameDirectoryInCache() // #188807
{
// Ensure the stuff is in cache.
fillModel(true);
const QString path = m_tempDir->name();
QVERIFY(!m_dirModel->dirLister()->findByUrl(path).isNull());
// No more dirmodel nor dirlister.
delete m_dirModel;
m_dirModel = 0;
// Now let's rename a directory that is in KDirListerCache
const KUrl url(path);
KUrl newUrl(path);
newUrl.adjustPath(KUrl::RemoveTrailingSlash);
newUrl.setPath(newUrl.path() + "_renamed");
kDebug() << newUrl;
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// KDirNotify emits FileRenamed for each rename() above, which in turn
// re-lists the directory. We need to wait for both signals to be emitted
// otherwise the dirlister will not be in the state we expect.
QTest::qWait(200);
fillModel(true);
QVERIFY(m_dirIndex.isValid());
KFileItem rootItem = m_dirModel->dirLister()->findByUrl(path);
QVERIFY(!rootItem.isNull());
}
void KDirModelTest::testChmodDirectory() // #53397
{
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
const QString path = m_tempDir->name();
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
const mode_t origPerm = rootItem.permissions();
mode_t newPerm = origPerm ^ S_IWGRP;
QVERIFY(newPerm != origPerm);
KFileItemList items; items << rootItem;
KIO::Job* job = KIO::chmod(items, newPerm, S_IWGRP, QString(), QString(), false, KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
// ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does.
// [this allows to group notifications after all the changes one can make in the dialog]
org::kde::KDirNotify::emitFilesChanged( QStringList() << path );
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 1);
QModelIndex receivedIndex = spyDataChanged[0][0].value<QModelIndex>();
kDebug() << receivedIndex;
QVERIFY(!receivedIndex.isValid());
QCOMPARE(m_dirModel->itemForIndex(QModelIndex()).permissions(), newPerm);
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
enum {
NoFlag = 0,
NewDir = 1, // whether to re-create a new KTempDir completely, to avoid cached fileitems
ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364
Recreate = 4,
CacheSubdir = 8, // put subdir in the cache before expandToUrl
// flags, next item is 16!
};
void KDirModelTest::testExpandToUrl_data()
{
QTest::addColumn<int>("flags"); // see enum above
QTest::addColumn<QString>("expandToPath"); // relative path
QTest::addColumn<QStringList>("expectedExpandSignals");
QTest::newRow("the root, nothing to do")
<< int(NoFlag) << QString() << QStringList();
QTest::newRow(".")
<< int(NoFlag) << "." << (QStringList());
QTest::newRow("subdir")
<< int(NoFlag) << "subdir" << (QStringList()<<"subdir");
QTest::newRow("subdir/.")
<< int(NoFlag) << "subdir/." << (QStringList()<<"subdir");
const QString subsubdir = "subdir/subsubdir";
// Must list root, emit expand for subdir, list subdir, emit expand for subsubdir.
QTest::newRow("subdir/subsubdir")
<< int(NoFlag) << subsubdir << (QStringList()<<"subdir"<<subsubdir);
// Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir.
const QString subsubdirfile = subsubdir + "/testfile";
QTest::newRow("subdir/subsubdir/testfile sync")
<< int(NoFlag) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
#ifndef Q_WS_WIN
// Expand a symlink to a directory (#219547)
const QString dirlink = m_tempDir->name() + "dirlink";
createTestSymlink(dirlink, "/");
QTest::newRow("dirlink")
<< int(NoFlag) << "dirlink/tmp" << (QStringList()<<"dirlink"<<"dirlink/tmp");
#endif
// Do a cold-cache test too, but nowadays it doesn't change anything anymore,
// apart from testing different code paths inside KDirLister.
QTest::newRow("subdir/subsubdir/testfile with reload")
<< int(NewDir) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
QTest::newRow("hold dest dir") // #193364
<< int(NewDir|ListFinalDir) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
// Put subdir in cache too (#175035)
QTest::newRow("hold subdir and dest dir")
<< int(NewDir|CacheSubdir|ListFinalDir|Recreate) << subsubdirfile
<< (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
// Make sure the last test has the Recreate option set, for the subsequent test methods.
}
void KDirModelTest::testExpandToUrl()
{
QFETCH(int, flags);
QFETCH(QString, expandToPath); // relative
QFETCH(QStringList, expectedExpandSignals);
if (flags & NewDir) {
recreateTestData();
// WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point!
}
const QString path = m_tempDir->name();
if (flags & CacheSubdir) {
// This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob
m_dirModel->dirLister()->openUrl(KUrl(path + "subdir"));
QTest::kWaitForSignal(m_dirModel->dirLister(), SIGNAL(completed()), 2000);
}
if (flags & ListFinalDir) {
// This way, the last listDir will find items in cache, and will schedule a CachedItemsJob
m_dirModel->dirLister()->openUrl(KUrl(path + "subdir/subsubdir"));
QTest::kWaitForSignal(m_dirModel->dirLister(), SIGNAL(completed()), 2000);
}
if (!m_dirModelForExpand || (flags & NewDir)) {
delete m_dirModelForExpand;
m_dirModelForExpand = new KDirModel;
connect(m_dirModelForExpand, SIGNAL(expand(QModelIndex)),
this, SLOT(slotExpand(QModelIndex)));
connect(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)));
KDirLister* dirListerForExpand = m_dirModelForExpand->dirLister();
dirListerForExpand->openUrl(KUrl(path), KDirLister::NoFlags); // async
}
m_rowsInsertedEmitted = false;
m_expectedExpandSignals = expectedExpandSignals;
m_nextExpectedExpandSignals = 0;
QSignalSpy spyExpand(m_dirModelForExpand, SIGNAL(expand(QModelIndex)));
m_urlToExpandTo = KUrl(path + expandToPath);
// If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals
// being emitted, so that the slots can get the index to that url then.
m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid();
m_dirModelForExpand->expandToUrl(m_urlToExpandTo);
if (expectedExpandSignals.isEmpty()) {
QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug...
QCOMPARE(spyExpand.count(), 0);
} else {
if (spyExpand.count() < expectedExpandSignals.count()) {
enterLoop();
QCOMPARE(spyExpand.count(), expectedExpandSignals.count());
}
if (m_expectRowsInserted)
QVERIFY(m_rowsInsertedEmitted);
}
// Now it should exist
if (!expandToPath.isEmpty() && expandToPath != ".") {
kDebug() << "Do I know" << m_urlToExpandTo << "?";
QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid());
}
if (flags & ListFinalDir) {
testUpdateParentAfterExpand();
}
if (flags & Recreate) {
// Clean up, for the next tests
recreateTestData();
fillModel(false);
}
}
void KDirModelTest::slotExpand(const QModelIndex& index)
{
QVERIFY(index.isValid());
const QString path = m_tempDir->name();
KFileItem item = m_dirModelForExpand->itemForIndex(index);
QVERIFY(!item.isNull());
kDebug() << item.url().path();
QCOMPARE(item.url().path(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++]));
// if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point
if (item.url() == m_urlToExpandTo) {
QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid());
if (m_expectRowsInserted)
QVERIFY(m_rowsInsertedEmitted);
}
if (m_nextExpectedExpandSignals == m_expectedExpandSignals.count())
m_eventLoop.exitLoop(); // done
}
void KDirModelTest::slotRowsInserted(const QModelIndex&, int, int)
{
m_rowsInsertedEmitted = true;
}
// This code is called by testExpandToUrl
void KDirModelTest::testUpdateParentAfterExpand() // #193364
{
const QString path = m_tempDir->name();
const QString file = path + "subdir/aNewFile";
kDebug() << "Creating" << file;
QVERIFY(!QFile::exists(file));
createTestFile(file);
//QSignalSpy spyRowsInserted(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)));
QTest::kWaitForSignal(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)));
}
void KDirModelTest::testFilter()
{
QVERIFY(m_dirIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex);
QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList)));
QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
m_dirModel->dirLister()->setNameFilter("toplevel*");
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir
QCOMPARE(m_dirModel->rowCount(m_dirIndex), 1); // the files get filtered out, the subdir remains
// In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2),
// depending on the order of the files in the model.
// So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need
// to sum up the removed rows per parent directory.
QMap<QString, int> rowsRemovedPerDir;
for (int i = 0; i < spyRowsRemoved.count(); ++i) {
const QVariantList args = spyRowsRemoved[i];
const QModelIndex parentIdx = args[0].value<QModelIndex>();
QString dirName;
if (parentIdx.isValid()) {
const KFileItem item = m_dirModel->itemForIndex(parentIdx);
dirName = item.name();
} else {
dirName = "root";
}
rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1;
//kDebug() << parentIdx << args[1].toInt() << args[2].toInt();
}
QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir
QCOMPARE(rowsRemovedPerDir.value("root" ), 1); // one from toplevel ('specialchars')
QCOMPARE(rowsRemovedPerDir.value("subdir" ), 2); // two from subdir
QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir
QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir
QCOMPARE(spyItemsDeleted[0][0].value<KFileItemList>().count(), 1); // one from toplevel ('specialchars')
QCOMPARE(spyItemsDeleted[1][0].value<KFileItemList>().count(), 2); // two from subdir
QCOMPARE(spyItemsDeleted[2][0].value<KFileItemList>().count(), 1); // one from subsubdir
QCOMPARE(spyItemsFilteredByMime.count(), 0);
spyItemsDeleted.clear();
spyItemsFilteredByMime.clear();
// Reset the filter
kDebug() << "reset to no filter";
m_dirModel->dirLister()->setNameFilter(QString());
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount);
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount);
QCOMPARE(spyItemsDeleted.count(), 0);
QCOMPARE(spyItemsFilteredByMime.count(), 0);
// The order of things changed because of filtering.
// Fill again, so that m_fileIndex etc. are correct again.
fillModel(true);
}
void KDirModelTest::testMimeFilter()
{
QVERIFY(m_dirIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex);
QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList)));
QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
m_dirModel->dirLister()->setMimeFilter(QStringList() << "application/pdf");
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore
QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity...
QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir
// Maybe it would make sense to have those items in itemsFilteredByMime,
// but well, for the only existing use of that signal (mime filter plugin),
// it's not really necessary, the plugin has seen those files before anyway.
// The signal is mostly useful for the case of listing a dir with a mime filter set.
//QCOMPARE(spyItemsFilteredByMime.count(), 1);
//QCOMPARE(spyItemsFilteredByMime[0][0].value<KFileItemList>().count(), 4);
spyItemsDeleted.clear();
spyItemsFilteredByMime.clear();
// Reset the filter
kDebug() << "reset to no filter";
m_dirModel->dirLister()->setMimeFilter(QStringList());
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount);
QCOMPARE(spyItemsDeleted.count(), 0);
QCOMPARE(spyItemsFilteredByMime.count(), 0);
// The order of things changed because of filtering.
// Fill again, so that m_fileIndex etc. are correct again.
fillModel(true);
}
void KDirModelTest::testShowHiddenFiles() // #174788
{
KDirLister* dirLister = m_dirModel->dirLister();
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex, int, int)));
QSignalSpy spyNewItems(dirLister, SIGNAL(newItems(KFileItemList)));
QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)));
dirLister->setShowingDotFiles(true);
dirLister->emitChanges();
const int numberOfDotFiles = 2;
QCOMPARE(spyNewItems.count(), 1);
QCOMPARE(spyNewItems[0][0].value<KFileItemList>().count(), numberOfDotFiles);
QCOMPARE(spyRowsInserted.count(), 1);
QCOMPARE(spyRowsRemoved.count(), 0);
spyNewItems.clear();
spyRowsInserted.clear();
dirLister->setShowingDotFiles(false);
dirLister->emitChanges();
QCOMPARE(spyNewItems.count(), 0);
QCOMPARE(spyRowsInserted.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 1);
}
void KDirModelTest::testMultipleSlashes()
{
const QString path = m_tempDir->name();
QModelIndex index = m_dirModel->indexForUrl(KUrl(path+"subdir//testfile"));
QVERIFY(index.isValid());
index = m_dirModel->indexForUrl(KUrl(path+"subdir//subsubdir//"));
QVERIFY(index.isValid());
index = m_dirModel->indexForUrl(KUrl(path+"subdir///subsubdir////testfile"));
QVERIFY(index.isValid());
}
void KDirModelTest::testUrlWithRef() // #171117
{
const QString path = m_tempDir->name();
KDirLister* dirLister = m_dirModel->dirLister();
KUrl url(path);
url.setRef("ref");
QVERIFY(url.url().endsWith("#ref"));
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), url.url(KUrl::RemoveTrailingSlash));
collectKnownIndexes();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
void KDirModelTest::testFontUrlWithHost() // #160057
{
if (!KProtocolInfo::isKnownProtocol("fonts")) {
QSKIP("kio_fonts not installed", SkipAll);
}
KUrl url("fonts://foo/System");
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), QString("fonts:/System"));
}
void KDirModelTest::testRemoteUrlWithHost() // #178416
{
if (!KProtocolInfo::isKnownProtocol("remote")) {
QSKIP("kio_remote not installed", SkipAll);
}
KUrl url("remote://foo");
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), QString("remote:"));
}
void KDirModelTest::testZipFile() // # 171721
{
const QString path = KDESRCDIR;
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(KUrl(path), KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
KUrl zipUrl(path);
zipUrl.addPath("wronglocalsizes.zip"); // just a zip file lying here for other reasons
QVERIFY(QFile::exists(zipUrl.path()));
zipUrl.setProtocol("zip");
QModelIndex index = m_dirModel->indexForUrl(zipUrl);
QVERIFY(!index.isValid()); // protocol mismatch, can't find it!
zipUrl.setProtocol("file");
index = m_dirModel->indexForUrl(zipUrl);
QVERIFY(index.isValid());
}
void KDirModelTest::testSmb()
{
const KUrl smbUrl("smb:/");
// TODO: feed a KDirModel without using a KDirLister.
// Calling the slots directly.
// This requires that KDirModel does not ask the KDirLister for its rootItem anymore,
// but that KDirLister emits the root item whenever it changes.
if (!KProtocolInfo::isKnownProtocol("smb")) {
QSKIP("kio_smb not installed", SkipAll);
}
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(smbUrl, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
connect(dirLister, SIGNAL(canceled()), this, SLOT(slotListingCompleted()));
QSignalSpy spyCanceled(dirLister, SIGNAL(canceled()));
enterLoop(); // wait for completed signal
if (spyCanceled.count() > 0) {
QSKIP("smb:/ returns an error, probably no network available", SkipAll);
}
QModelIndex index = m_dirModel->index(0, 0);
if (index.isValid()) {
QVERIFY(m_dirModel->canFetchMore(index));
m_dirModel->fetchMore(index);
enterLoop(); // wait for completed signal
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
}
+class MyDirLister : public KDirLister
+{
+public:
+ void emitItemsDeleted(const KFileItemList& items) { emit itemsDeleted(items); }
+};
+
+void KDirModelTest::testBug196695()
+{
+ KFileItem rootItem(KUrl(m_tempDir->name()), QString(), KFileItem::Unknown);
+ KFileItem childItem(KUrl(QString(m_tempDir->name() + "toplevelfile_1")), QString(), KFileItem::Unknown);
+
+ KFileItemList list;
+ // Important: the root item must not be first in the list to trigger bug 196695
+ list << childItem << rootItem;
+
+ MyDirLister* dirLister = static_cast<MyDirLister*>(m_dirModel->dirLister());
+ dirLister->emitItemsDeleted(list);
+
+ fillModel(true);
+}
+
void KDirModelTest::testDeleteFile()
{
fillModel(false);
QVERIFY(m_fileIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->name();
const QString file = path + "toplevelfile_1";
const KUrl url(file);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row());
QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row());
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
// Recreate the file, for consistency in the next tests
// So the second part of this test is a "testCreateFile"
createTestFile(file);
// Tricky problem - KDirLister::openUrl will emit items from cache
// and then schedule an update; so just calling fillModel would
// not wait enough, it would abort due to not finding toplevelfile_1
// in the items from cache. This progressive-emitting behavior is fine
// for GUIs but not for unit tests ;-)
fillModel(true, false);
fillModel(false);
}
void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->name();
const QString file = path + "toplevelfile_1";
const KUrl url(file);
KDirLister* dirLister = m_dirModel->dirLister();
QSignalSpy spyCompleted(dirLister, SIGNAL(completed()));
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
dirLister->openUrl(KUrl(path), KDirLister::NoFlags);
if (!spyCompleted.isEmpty())
QSKIP("listing completed too early", SkipAll);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
if (spyCompleted.isEmpty())
enterLoop();
// TODO QTest::kWaitForSignalSpy(spyRowsRemoved)?
QTest::kWaitForSignal(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row());
QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row());
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
kDebug() << "Test done, recreating file";
// Recreate the file, for consistency in the next tests
// So the second part of this test is a "testCreateFile"
createTestFile(file);
fillModel(true, false); // see testDeleteFile
fillModel(false);
}
void KDirModelTest::testOverwriteFileWithDir() // #151851 c4
{
fillModel(false);
const QString path = m_tempDir->name();
const QString dir = path + "subdir";
const QString file = path + "toplevelfile_1";
const int oldTopLevelRowCount = m_dirModel->rowCount();
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::Job* job = KIO::move(dir, file, KIO::HideProgressInfo);
PredefinedAnswerJobUiDelegate* delegate = new PredefinedAnswerJobUiDelegate;
delegate->m_renameResult = KIO::R_OVERWRITE;
job->setUiDelegate(delegate);
QVERIFY(job->exec());
QCOMPARE(delegate->m_askFileRenameCalled, 1);
if (spyRowsRemoved.isEmpty()) {
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
QVERIFY(!spyRowsRemoved.isEmpty());
}
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QVERIFY(!m_dirModel->indexForUrl(dir).isValid());
QModelIndex newIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(newIndex.isValid());
KFileItem newItem = m_dirModel->itemForIndex(newIndex);
QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-)
kDebug() << "========= Test done, recreating test data =========";
recreateTestData();
fillModel(false);
}
void KDirModelTest::testDeleteFiles()
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString file = m_tempDir->name() + "toplevelfile_";
KUrl::List urls;
urls << KUrl(file + '1') << KUrl(file + '2') << KUrl(file + '3');
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
KIO::DeleteJob* job = KIO::del(urls, KIO::HideProgressInfo);
QVERIFY(job->exec());
int numRowsRemoved = 0;
while (numRowsRemoved < 3) {
QTest::qWait(20);
numRowsRemoved = 0;
for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum)
numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1;
kDebug() << "numRowsRemoved=" << numRowsRemoved;
}
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before
kDebug() << "Recreating test data";
recreateTestData();
kDebug() << "Re-filling model";
fillModel(false);
}
// A renaming that looks more like a deletion to the model
void KDirModelTest::testRenameFileToHidden() // #174721
{
const KUrl url(m_tempDir->name() + "toplevelfile_2");
const KUrl newUrl(m_tempDir->name() + ".toplevelfile_2");
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsInserted.count(), 0);
COMPARE_INDEXES(spyRowsRemoved[0][0].value<QModelIndex>(), QModelIndex()); // parent is invalid
const int row = spyRowsRemoved[0][1].toInt();
QCOMPARE(row, m_secondFileIndex.row()); // only compare row
disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
spyRowsRemoved.clear();
// Put things back to normal, should make the file reappear
connect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister
enterLoop();
QCOMPARE(spyDataChanged.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 0);
QCOMPARE(spyRowsInserted.count(), 1);
int newRow = spyRowsInserted[0][1].toInt();
m_secondFileIndex = m_dirModel->index(newRow, 0);
QVERIFY(m_secondFileIndex.isValid());
QCOMPARE(m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), url.url());
}
void KDirModelTest::testDeleteDirectory()
{
const QString path = m_tempDir->name();
const KUrl url(path + "subdir/subsubdir");
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QSignalSpy spyDirWatchDeleted(KDirWatch::self(), SIGNAL(deleted(QString)));
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
QCOMPARE(spyRowsRemoved.count(), 1);
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex deletedDirIndex = m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir"));
QVERIFY(!deletedDirIndex.isValid());
QModelIndex dirIndex = m_dirModel->indexForUrl(KUrl(path + "subdir"));
QVERIFY(dirIndex.isValid());
// TODO!!! Bug in KDirWatch? ###
// QCOMPARE(spyDirWatchDeleted.count(), 1);
}
void KDirModelTest::testDeleteCurrentDirectory()
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->name();
const KUrl url(path);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KDirWatch::self()->statistics();
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, 0); // empty
// We can get rowsRemoved for subdirs first, since kdirwatch notices that.
QVERIFY(spyRowsRemoved.count() >= 1);
// Look for the signal(s) that had QModelIndex() as parent.
int i;
int numDeleted = 0;
for (i = 0; i < spyRowsRemoved.count(); ++i) {
const int from = spyRowsRemoved[i][1].toInt();
const int to = spyRowsRemoved[i][2].toInt();
kDebug() << spyRowsRemoved[i][0].value<QModelIndex>() << from << to;
if (!spyRowsRemoved[i][0].value<QModelIndex>().isValid()) {
numDeleted += (to - from) + 1;
}
}
QCOMPARE(numDeleted, oldTopLevelRowCount);
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
}
#if QT_VERSION < 0x040700
// The old slow way. (this isn't QUrl's fault, I'm just using QUrl in order
// to be able to test a different hashing function than the KUrl one).
inline uint qHash(const QUrl& qurl) {
return qHash(qurl.toEncoded());
}
#endif
void KDirModelTest::testKUrlHash()
{
const int count = 3000;
// Prepare an array of QUrls so that url constructing isn't part of the timing
QVector<QUrl> urls;
urls.resize(count);
for (int i = 0; i < count; ++i) {
urls[i] = QUrl("http://www.kde.org/path/"+QString::number(i));
}
QHash<QUrl, int> qurlHash;
QHash<KUrl, int> kurlHash;
QTime dt; dt.start();
for (int i = 0; i < count; ++i) {
qurlHash.insert(urls[i], i);
}
//kDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs";
dt.start();
for (int i = 0; i < count; ++i) {
kurlHash.insert(urls[i], i);
}
//kDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs";
// Nice results: for count=30000 I got 4515 (before) and 103 (after)
dt.start();
for (int i = 0; i < count; ++i) {
QCOMPARE(qurlHash.value(urls[i]), i);
}
//kDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs";
dt.start();
for (int i = 0; i < count; ++i) {
QCOMPARE(kurlHash.value(urls[i]), i);
}
//kDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs";
// Nice results: for count=30000 I got 4296 (before) and 63 (after)
}
diff --git a/kio/tests/kdirmodeltest.h b/kio/tests/kdirmodeltest.h
index 3572a2fb67..85e9a3420f 100644
--- a/kio/tests/kdirmodeltest.h
+++ b/kio/tests/kdirmodeltest.h
@@ -1,115 +1,116 @@
/* This file is part of the KDE project
Copyright (C) 2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KDIRMODELTEST_H
#define KDIRMODELTEST_H
#include <QtCore/QObject>
#include <ktempdir.h>
#include <QtCore/QDate>
#include <kdirmodel.h>
#include <QtCore/QEventLoop>
#include <QtTest/QtTest>
// If you disable this, you need to change all exitLoop into quit in connect() statements...
#define USE_QTESTEVENTLOOP
class KDirModelTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void cleanup();
void testRowCount();
void testIndex();
void testNames();
void testItemForIndex();
void testIndexForItem();
void testData();
void testReload();
void testModifyFile();
void testRenameFile();
void testMoveDirectory();
void testRenameDirectory();
void testRenameDirectoryInCache();
void testChmodDirectory();
void testExpandToUrl_data();
void testExpandToUrl();
void testFilter();
void testMimeFilter();
void testShowHiddenFiles();
void testMultipleSlashes();
void testUrlWithRef();
void testFontUrlWithHost();
void testRemoteUrlWithHost();
void testZipFile();
void testSmb();
+ void testBug196695();
// These tests must be done last
void testDeleteFile();
void testDeleteFileWhileListing();
void testOverwriteFileWithDir();
void testDeleteFiles();
void testRenameFileToHidden();
void testDeleteDirectory();
void testDeleteCurrentDirectory();
// Somewhat unrelated
void testKUrlHash();
protected Q_SLOTS: // 'more private than private slots' - i.e. not seen by qtestlib
void slotListingCompleted();
void slotExpand(const QModelIndex& index);
void slotRowsInserted(const QModelIndex& index, int, int);
private:
void recreateTestData();
void enterLoop();
void fillModel(bool reload, bool expectAllIndexes = true);
void collectKnownIndexes();
void testMoveDirectory(const QString& srcdir);
void testUpdateParentAfterExpand();
private:
#ifdef USE_QTESTEVENTLOOP
QTestEventLoop m_eventLoop;
#else
QEventLoop m_eventLoop;
#endif
KTempDir* m_tempDir;
KDirModel* m_dirModel;
QModelIndex m_fileIndex;
QModelIndex m_specialFileIndex;
QModelIndex m_secondFileIndex;
QModelIndex m_dirIndex;
QModelIndex m_fileInDirIndex;
QModelIndex m_fileInSubdirIndex;
QStringList m_topLevelFileNames; // files only
// for slotExpand
QStringList m_expectedExpandSignals;
int m_nextExpectedExpandSignals; // index into m_expectedExpandSignals
KDirModel* m_dirModelForExpand;
KUrl m_urlToExpandTo;
bool m_rowsInsertedEmitted;
bool m_expectRowsInserted;
};
#endif
diff --git a/kioslave/http/http.cpp b/kioslave/http/http.cpp
index b4d3c6588b..7228309a36 100644
--- a/kioslave/http/http.cpp
+++ b/kioslave/http/http.cpp
@@ -1,5294 +1,5294 @@
/*
Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
Copyright (C) 2007 Nick Shaforostoff <shafff@ukr.net>
Copyright (C) 2007 Daniel Nicoletti <mirttex@users.sourceforge.net>
Copyright (C) 2008,2009 Andreas Hartmetz <ahartmetz@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// TODO delete / do not save very big files; "very big" to be defined
#define QT_NO_CAST_FROM_ASCII
#include "http.h"
#include <config.h>
#include <fcntl.h>
#include <utime.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h> // must be explicitly included for MacOSX
#include <QtXml/qdom.h>
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <QtCore/QDate>
#include <QtCore/QBuffer>
#include <QtCore/QIODevice>
#include <QtDBus/QtDBus>
#include <QtNetwork/QAuthenticator>
#include <QtNetwork/QNetworkProxy>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QHostInfo>
#include <kurl.h>
#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kservice.h>
#include <kdatetime.h>
#include <kcomponentdata.h>
#include <krandom.h>
#include <kmimetype.h>
#include <ktoolinvocation.h>
#include <kstandarddirs.h>
#include <kremoteencoding.h>
#include <ktcpsocket.h>
#include <kmessagebox.h>
#include <kio/ioslave_defaults.h>
#include <kio/http_slave_defaults.h>
#include <httpfilter.h>
#include <solid/networking.h>
#ifdef HAVE_LIBGSSAPI
#ifdef GSSAPI_MIT
#include <gssapi/gssapi.h>
#else
#include <gssapi.h>
#endif /* GSSAPI_MIT */
// Catch uncompatible crap (BR86019)
#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
#include <gssapi/gssapi_generic.h>
#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
#endif
#endif /* HAVE_LIBGSSAPI */
#include <misc/kntlm/kntlm.h>
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kde_file.h>
#include <ktemporaryfile.h>
//string parsing helpers and HeaderTokenizer implementation
#include "parsinghelpers.cpp"
//authentication handlers
#include "httpauthentication.cpp"
// see filenameFromUrl(): a sha1 hash is 160 bits
static const int s_hashedUrlBits = 160; // this number should always be divisible by eight
static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
static const int s_MaxInMemPostBufSize = 256 * 1024; // Write anyting over 256 KB to file...
using namespace KIO;
extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
{
QCoreApplication app( argc, argv ); // needed for QSocketNotifier
KComponentData componentData( "kio_http", "kdelibs4" );
(void) KGlobal::locale();
if (argc != 4)
{
fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
exit(-1);
}
HTTPProtocol slave(argv[1], argv[2], argv[3]);
slave.dispatchLoop();
return 0;
}
/*********************************** Generic utility functions ********************/
static QString toQString(const QByteArray& value)
{
return QString::fromLatin1(value.constData(), value.size());
}
static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
{
//TODO read the RFC
if (originURL == QLatin1String("true")) // Backwards compatibility
return true;
KUrl url ( originURL );
// Document Origin domain
QString a = url.host();
// Current request domain
QString b = fqdn;
if (a == b)
return false;
QStringList la = a.split(QLatin1Char('.'), QString::SkipEmptyParts);
QStringList lb = b.split(QLatin1Char('.'), QString::SkipEmptyParts);
if (qMin(la.count(), lb.count()) < 2) {
return true; // better safe than sorry...
}
while(la.count() > 2)
la.pop_front();
while(lb.count() > 2)
lb.pop_front();
return la != lb;
}
/*
Eliminates any custom header that could potentially alter the request
*/
static QString sanitizeCustomHTTPHeader(const QString& _header)
{
QString sanitizedHeaders;
const QStringList headers = _header.split(QRegExp(QLatin1String("[\r\n]")));
for(QStringList::ConstIterator it = headers.begin(); it != headers.end(); ++it)
{
// Do not allow Request line to be specified and ignore
// the other HTTP headers.
if (!(*it).contains(QLatin1Char(':')) ||
(*it).startsWith(QLatin1String("host"), Qt::CaseInsensitive) ||
(*it).startsWith(QLatin1String("proxy-authorization"), Qt::CaseInsensitive) ||
(*it).startsWith(QLatin1String("via"), Qt::CaseInsensitive))
continue;
sanitizedHeaders += (*it);
sanitizedHeaders += QLatin1String("\r\n");
}
sanitizedHeaders.chop(2);
return sanitizedHeaders;
}
// for a given response code, conclude if the response is going to/likely to have a response body
static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method)
{
/* RFC 2616 says...
1xx: false
200: method HEAD: false, otherwise:true
201: true
202: true
203: see 200
204: false
205: false
206: true
300: see 200
301: see 200
302: see 200
303: see 200
304: false
305: probably like 300, RFC seems to expect disconnection afterwards...
306: (reserved), for simplicity do it just like 200
307: see 200
4xx: see 200
5xx :see 200
*/
if (responseCode >= 100 && responseCode < 200) {
return false;
}
switch (responseCode) {
case 201:
case 202:
case 206:
// RFC 2616 does not mention HEAD in the description of the above. if the assert turns out
// to be a problem the response code should probably be treated just like 200 and friends.
Q_ASSERT(method != HTTP_HEAD);
return true;
case 204:
case 205:
case 304:
return false;
default:
break;
}
// safe (and for most remaining response codes exactly correct) default
return method != HTTP_HEAD;
}
static bool isEncryptedHttpVariety(const QByteArray &p)
{
return p == "https" || p == "webdavs";
}
static bool isValidProxy(const KUrl &u)
{
return u.isValid() && u.hasHost();
}
static bool isHttpProxy(const KUrl &u)
{
return isValidProxy(u) && u.protocol() == QLatin1String("http");
}
static QIODevice* createPostBufferDeviceFor (KIO::filesize_t size)
{
QIODevice* device;
if (size > static_cast<KIO::filesize_t>(s_MaxInMemPostBufSize))
device = new KTemporaryFile;
else
device = new QBuffer;
if (!device->open(QIODevice::ReadWrite))
return 0;
return device;
}
QByteArray HTTPProtocol::HTTPRequest::methodString() const
{
if (!methodStringOverride.isEmpty())
return (methodStringOverride + QLatin1Char(' ')).toLatin1();
switch(method) {
case HTTP_GET:
return "GET ";
case HTTP_PUT:
return "PUT ";
case HTTP_POST:
return "POST ";
case HTTP_HEAD:
return "HEAD ";
case HTTP_DELETE:
return "DELETE ";
case HTTP_OPTIONS:
return "OPTIONS ";
case DAV_PROPFIND:
return "PROPFIND ";
case DAV_PROPPATCH:
return "PROPPATCH ";
case DAV_MKCOL:
return "MKCOL ";
case DAV_COPY:
return "COPY ";
case DAV_MOVE:
return "MOVE ";
case DAV_LOCK:
return "LOCK ";
case DAV_UNLOCK:
return "UNLOCK ";
case DAV_SEARCH:
return "SEARCH ";
case DAV_SUBSCRIBE:
return "SUBSCRIBE ";
case DAV_UNSUBSCRIBE:
return "UNSUBSCRIBE ";
case DAV_POLL:
return "POLL ";
case DAV_NOTIFY:
return "NOTIFY ";
case DAV_REPORT:
return "REPORT ";
default:
Q_ASSERT(false);
return QByteArray();
}
}
static QString formatHttpDate(qint64 date)
{
KDateTime dt;
dt.setTime_t(date);
QString ret = dt.toString(KDateTime::RFCDateDay);
ret.chop(6); // remove " +0000"
// RFCDate[Day] omits the second if zero, but HTTP requires it; see bug 240585.
if (!dt.time().second()) {
ret.append(QLatin1String(":00"));
}
ret.append(QLatin1String(" GMT"));
return ret;
}
static bool isAuthenticationRequired(int responseCode)
{
return (responseCode == 401) || (responseCode == 407);
}
#define NO_SIZE ((KIO::filesize_t) -1)
#ifdef HAVE_STRTOLL
#define STRTOLL strtoll
#else
#define STRTOLL strtol
#endif
/************************************** HTTPProtocol **********************************************/
HTTPProtocol::HTTPProtocol( const QByteArray &protocol, const QByteArray &pool,
const QByteArray &app )
: TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol))
, m_iSize(NO_SIZE)
, m_iPostDataSize(NO_SIZE)
, m_isBusy(false)
, m_POSTbuf(0)
, m_maxCacheAge(DEFAULT_MAX_CACHE_AGE)
, m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE)
, m_protocol(protocol)
, m_wwwAuth(0)
, m_proxyAuth(0)
, m_socketProxyAuth(0)
, m_isError(false)
, m_isLoadingErrorPage(false)
, m_remoteRespTimeout(DEFAULT_RESPONSE_TIMEOUT)
{
reparseConfiguration();
setBlocking(true);
connect(socket(), SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
this, SLOT(proxyAuthenticationForSocket(const QNetworkProxy &, QAuthenticator *)));
}
HTTPProtocol::~HTTPProtocol()
{
httpClose(false);
}
void HTTPProtocol::reparseConfiguration()
{
kDebug(7113);
delete m_proxyAuth;
delete m_wwwAuth;
m_proxyAuth = 0;
m_wwwAuth = 0;
m_request.proxyUrl.clear(); //TODO revisit
}
void HTTPProtocol::resetConnectionSettings()
{
m_isEOF = false;
m_isError = false;
m_isLoadingErrorPage = false;
}
quint16 HTTPProtocol::defaultPort() const
{
return isEncryptedHttpVariety(m_protocol) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
}
void HTTPProtocol::resetResponseParsing()
{
m_isRedirection = false;
m_isChunked = false;
m_iSize = NO_SIZE;
clearUnreadBuffer();
m_responseHeaders.clear();
m_contentEncodings.clear();
m_transferEncodings.clear();
m_contentMD5.clear();
m_mimeType.clear();
setMetaData(QLatin1String("request-id"), m_request.id);
}
void HTTPProtocol::resetSessionSettings()
{
// Do not reset the URL on redirection if the proxy
// URL, username or password has not changed!
KUrl proxy ( config()->readEntry("UseProxy") );
QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
#if 0
if ( m_proxyAuth.realm.isEmpty() || !proxy.isValid() ||
m_request.proxyUrl.host() != proxy.host() ||
m_request.proxyUrl.port() != proxy.port() ||
(!proxy.user().isEmpty() && proxy.user() != m_request.proxyUrl.user()) ||
(!proxy.pass().isEmpty() && proxy.pass() != m_request.proxyUrl.pass()) )
{
m_request.proxyUrl = proxy;
kDebug(7113) << "Using proxy:" << m_request.useProxy()
<< "URL:" << m_request.proxyUrl.url()
<< "Realm:" << m_proxyAuth.realm;
}
#endif
m_request.proxyUrl = proxy;
kDebug(7113) << "Using proxy:" << isValidProxy(m_request.proxyUrl)
<< "URL:" << m_request.proxyUrl.url();
//<< "Realm:" << m_proxyAuth.realm;
if (isValidProxy(m_request.proxyUrl)) {
if (m_request.proxyUrl.protocol() == QLatin1String("socks")) {
// Let Qt do SOCKS because it's already implemented there...
proxyType = QNetworkProxy::Socks5Proxy;
} else if (isAutoSsl()) {
// and for HTTPS we use HTTP CONNECT on the proxy server, also implemented in Qt.
// This is the usual way to handle SSL proxying.
proxyType = QNetworkProxy::HttpProxy;
}
m_request.proxyUrl = proxy;
} else {
m_request.proxyUrl = KUrl();
}
QNetworkProxy appProxy(proxyType, m_request.proxyUrl.host(), m_request.proxyUrl.port(),
m_request.proxyUrl.user(), m_request.proxyUrl.pass());
QNetworkProxy::setApplicationProxy(appProxy);
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
m_request.isKeepAlive = config()->readEntry("PersistentProxyConnection", false);
kDebug(7113) << "Enable Persistent Proxy Connection:" << m_request.isKeepAlive;
} else {
// Follow HTTP/1.1 spec and enable keep-alive by default
// unless the remote side tells us otherwise or we determine
// the persistent link has been terminated by the remote end.
m_request.isKeepAlive = true;
}
m_request.keepAliveTimeout = 0;
m_request.redirectUrl = KUrl();
m_request.useCookieJar = config()->readEntry("Cookies", false);
m_request.cacheTag.useCache = config()->readEntry("UseCache", true);
m_request.preferErrorPage = config()->readEntry("errorPage", true);
m_request.doNotAuthenticate = config()->readEntry("no-auth", false);
m_strCacheDir = config()->readPathEntry("CacheDir", QString());
m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
m_request.windowId = config()->readEntry("window-id");
m_request.methodStringOverride = metaData(QLatin1String("CustomHTTPMethod"));
kDebug(7113) << "Window Id =" << m_request.windowId;
kDebug(7113) << "ssl_was_in_use =" << metaData(QLatin1String("ssl_was_in_use"));
m_request.referrer.clear();
// RFC 2616: do not send the referrer if the referrer page was served using SSL and
// the current page does not use SSL.
if ( config()->readEntry("SendReferrer", true) &&
(isEncryptedHttpVariety(m_protocol) || metaData(QLatin1String("ssl_was_in_use")) != QLatin1String("TRUE") ) )
{
KUrl refUrl(metaData(QLatin1String("referrer")));
if (refUrl.isValid()) {
// Sanitize
QString protocol = refUrl.protocol();
if (protocol.startsWith(QLatin1String("webdav"))) {
protocol.replace(0, 6, QLatin1String("http"));
refUrl.setProtocol(protocol);
}
if (protocol.startsWith(QLatin1String("http"))) {
m_request.referrer = toQString(refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment));
}
}
}
if (config()->readEntry("SendLanguageSettings", true)) {
m_request.charsets = config()->readEntry("Charsets", DEFAULT_PARTIAL_CHARSET_HEADER);
if (!m_request.charsets.contains(QLatin1String("*;"), Qt::CaseInsensitive)) {
m_request.charsets += QLatin1String(",*;q=0.5");
}
m_request.languages = config()->readEntry("Languages", DEFAULT_LANGUAGE_HEADER);
} else {
m_request.charsets.clear();
m_request.languages.clear();
}
// Adjust the offset value based on the "resume" meta-data.
QString resumeOffset = metaData(QLatin1String("resume"));
if (!resumeOffset.isEmpty()) {
m_request.offset = resumeOffset.toULongLong();
} else {
m_request.offset = 0;
}
// Same procedure for endoffset.
QString resumeEndOffset = metaData(QLatin1String("resume_until"));
if (!resumeEndOffset.isEmpty()) {
m_request.endoffset = resumeEndOffset.toULongLong();
} else {
m_request.endoffset = 0;
}
m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false);
m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true);
m_request.id = metaData(QLatin1String("request-id"));
// Store user agent for this host.
if (config()->readEntry("SendUserAgent", true)) {
m_request.userAgent = metaData(QLatin1String("UserAgent"));
} else {
m_request.userAgent.clear();
}
m_request.cacheTag.etag.clear();
// -1 is also the value returned by KDateTime::toTime_t() from an invalid instance.
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
m_request.responseCode = 0;
m_request.prevResponseCode = 0;
delete m_wwwAuth;
m_wwwAuth = 0;
delete m_socketProxyAuth;
m_socketProxyAuth = 0;
// Obtain timeout values
m_remoteRespTimeout = responseTimeout();
// Bounce back the actual referrer sent
setMetaData(QLatin1String("referrer"), m_request.referrer);
// Reset the post data size
m_iPostDataSize = NO_SIZE;
}
void HTTPProtocol::setHost( const QString& host, quint16 port,
const QString& user, const QString& pass )
{
// Reset the webdav-capable flags for this host
if ( m_request.url.host() != host )
m_davHostOk = m_davHostUnsupported = false;
m_request.url.setHost(host);
// is it an IPv6 address?
if (host.indexOf(QLatin1Char(':')) == -1) {
m_request.encoded_hostname = toQString(QUrl::toAce(host));
} else {
int pos = host.indexOf(QLatin1Char('%'));
if (pos == -1)
m_request.encoded_hostname = QLatin1Char('[') + host + QLatin1Char(']');
else
// don't send the scope-id in IPv6 addresses to the server
m_request.encoded_hostname = QLatin1Char('[') + host.left(pos) + QLatin1Char(']');
}
m_request.url.setPort((port > 0 && port != defaultPort()) ? port : -1);
m_request.url.setUser(user);
m_request.url.setPass(pass);
//TODO need to do anything about proxying?
kDebug(7113) << "Hostname is now:" << m_request.url.host()
<< "(" << m_request.encoded_hostname << ")";
}
bool HTTPProtocol::maybeSetRequestUrl(const KUrl &u)
{
kDebug (7113) << u.url();
m_request.url = u;
m_request.url.setPort(u.port(defaultPort()) != defaultPort() ? u.port() : -1);
if (u.host().isEmpty()) {
error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
return false;
}
if (u.path().isEmpty()) {
KUrl newUrl(u);
newUrl.setPath(QLatin1String("/"));
redirection(newUrl);
finished();
return false;
}
return true;
}
void HTTPProtocol::proceedUntilResponseContent( bool dataInternal /* = false */ )
{
kDebug (7113);
if (!(proceedUntilResponseHeader() && readBody(dataInternal))) {
return;
}
httpClose(m_request.isKeepAlive);
// if data is required internally, don't finish,
// it is processed before we finish()
if (dataInternal) {
return;
}
if (m_request.responseCode == 204 &&
(m_request.method == HTTP_GET || m_request.method == HTTP_POST)) {
infoMessage(QLatin1String(""));
error(ERR_NO_CONTENT, QString());
return;
}
finished();
}
bool HTTPProtocol::proceedUntilResponseHeader()
{
kDebug (7113);
// Retry the request until it succeeds or an unrecoverable error occurs.
// Recoverable errors are, for example:
// - Proxy or server authentication required: Ask for credentials and try again,
// this time with an authorization header in the request.
// - Server-initiated timeout on keep-alive connection: Reconnect and try again
while (true) {
if (!sendQuery()) {
return false;
}
if (readResponseHeader()) {
// Success, finish the request.
break;
}
// If not loading error page and the response code requires us to resend the query,
// then throw away any error message that might have been sent by the server.
if (!m_isLoadingErrorPage && isAuthenticationRequired(m_request.responseCode)) {
// This gets rid of any error page sent with 401 or 407 authentication required response...
readBody(true);
}
// no success, close the cache file so the cache state is reset - that way most other code
// doesn't have to deal with the cache being in various states.
cacheFileClose();
if (m_isError || m_isLoadingErrorPage) {
// Unrecoverable error, abort everything.
// Also, if we've just loaded an error page there is nothing more to do.
// In that case we abort to avoid loops; some webservers manage to send 401 and
// no authentication request. Or an auth request we don't understand.
return false;
}
if (!m_request.isKeepAlive) {
httpCloseConnection();
}
}
// Do not save authorization if the current response code is
// 4xx (client error) or 5xx (server error).
kDebug(7113) << "Previous Response:" << m_request.prevResponseCode;
kDebug(7113) << "Current Response:" << m_request.responseCode;
setMetaData(QLatin1String("responsecode"), QString::number(m_request.responseCode));
setMetaData(QLatin1String("content-type"), m_mimeType);
// At this point sendBody() should have delivered any POST data.
clearPostDataBuffer();
return true;
}
void HTTPProtocol::stat(const KUrl& url)
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
if ( m_protocol != "webdav" && m_protocol != "webdavs" )
{
QString statSide = metaData(QLatin1String("statSide"));
if (statSide != QLatin1String("source"))
{
// When uploading we assume the file doesn't exit
error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
return;
}
// When downloading we assume it exists
UDSEntry entry;
entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG ); // a file
entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH ); // readable by everybody
statEntry( entry );
finished();
return;
}
davStatList( url );
}
void HTTPProtocol::listDir( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
davStatList( url, false );
}
void HTTPProtocol::davSetRequest( const QByteArray& requestXML )
{
// insert the document into the POST buffer, kill trailing zero byte
cachePostData(requestXML);
}
void HTTPProtocol::davStatList( const KUrl& url, bool stat )
{
UDSEntry entry;
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// Maybe it's a disguised SEARCH...
QString query = metaData(QLatin1String("davSearchQuery"));
if ( !query.isEmpty() )
{
QByteArray request = "<?xml version=\"1.0\"?>\r\n";
request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
request.append( query.toUtf8() );
request.append( "</D:searchrequest>\r\n" );
davSetRequest( request );
} else {
// We are only after certain features...
QByteArray request;
request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\">";
// insert additional XML request from the davRequestResponse metadata
if ( hasMetaData(QLatin1String("davRequestResponse")) )
request += metaData(QLatin1String("davRequestResponse")).toUtf8();
else {
// No special request, ask for default properties
request += "<D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:source/>"
"<D:getcontentlanguage/>"
"<D:getcontenttype/>"
"<D:executable/>"
"<D:getlastmodified/>"
"<D:getetag/>"
"<D:supportedlock/>"
"<D:lockdiscovery/>"
"<D:resourcetype/>"
"</D:prop>";
}
request += "</D:propfind>";
davSetRequest( request );
}
// WebDAV Stat or List...
m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_request.davData.depth = stat ? 0 : 1;
if (!stat)
m_request.url.adjustPath(KUrl::AddTrailingSlash);
proceedUntilResponseContent( true );
// Has a redirection already been called? If so, we're done.
if (m_isRedirection) {
finished();
return;
}
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
bool hasResponse = false;
for ( QDomNode n = multiResponse.documentElement().firstChild();
!n.isNull(); n = n.nextSibling())
{
QDomElement thisResponse = n.toElement();
if (thisResponse.isNull())
continue;
hasResponse = true;
QDomElement href = thisResponse.namedItem(QLatin1String("href")).toElement();
if ( !href.isNull() )
{
entry.clear();
QString urlStr = QUrl::fromPercentEncoding(href.text().toUtf8());
#if 0 // qt4/kde4 say: it's all utf8...
int encoding = remoteEncoding()->encodingMib();
if ((encoding == 106) && (!KStringHandler::isUtf8(KUrl::decode_string(urlStr, 4).toLatin1())))
encoding = 4; // Use latin1 if the file is not actually utf-8
KUrl thisURL ( urlStr, encoding );
#else
KUrl thisURL( urlStr );
#endif
if ( thisURL.isValid() ) {
QString name = thisURL.fileName();
// base dir of a listDir(): name should be "."
if ( !stat && thisURL.path(KUrl::AddTrailingSlash).length() == url.path(KUrl::AddTrailingSlash).length() )
name = QLatin1Char('.');
entry.insert( KIO::UDSEntry::UDS_NAME, name.isEmpty() ? href.text() : name );
}
QDomNodeList propstats = thisResponse.elementsByTagName(QLatin1String("propstat"));
davParsePropstats( propstats, entry );
if ( stat )
{
// return an item
statEntry( entry );
finished();
return;
}
else
{
listEntry( entry, false );
}
}
else
{
kDebug(7113) << "Error: no URL contained in response to PROPFIND on" << url;
}
}
if ( stat || !hasResponse )
{
error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
}
else
{
listEntry( entry, true );
finished();
}
}
void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method, qint64 size )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// WebDAV method
m_request.method = method;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
proceedUntilResponseContent( false );
}
int HTTPProtocol::codeFromResponse( const QString& response )
{
const int firstSpace = response.indexOf( QLatin1Char(' ') );
const int secondSpace = response.indexOf( QLatin1Char(' '), firstSpace + 1 );
return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
}
void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
{
QString mimeType;
bool foundExecutable = false;
bool isDirectory = false;
uint lockCount = 0;
uint supportedLockCount = 0;
for ( int i = 0; i < propstats.count(); i++)
{
QDomElement propstat = propstats.item(i).toElement();
QDomElement status = propstat.namedItem(QLatin1String("status")).toElement();
if ( status.isNull() )
{
// error, no status code in this propstat
kDebug(7113) << "Error, no status code in this propstat";
return;
}
int code = codeFromResponse( status.text() );
if ( code != 200 )
{
kDebug(7113) << "Warning: status code" << code << "(this may mean that some properties are unavailable";
continue;
}
QDomElement prop = propstat.namedItem( QLatin1String("prop") ).toElement();
if ( prop.isNull() )
{
kDebug(7113) << "Error: no prop segment in this propstat.";
return;
}
if ( hasMetaData( QLatin1String("davRequestResponse") ) )
{
QDomDocument doc;
doc.appendChild(prop);
entry.insert( KIO::UDSEntry::UDS_XML_PROPERTIES, doc.toString() );
}
for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
{
QDomElement property = n.toElement();
if (property.isNull())
continue;
if ( property.namespaceURI() != QLatin1String("DAV:") )
{
// break out - we're only interested in properties from the DAV namespace
continue;
}
if ( property.tagName() == QLatin1String("creationdate") )
{
// Resource creation date. Should be is ISO 8601 format.
entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
}
else if ( property.tagName() == QLatin1String("getcontentlength") )
{
// Content length (file size)
entry.insert( KIO::UDSEntry::UDS_SIZE, property.text().toULong() );
}
else if ( property.tagName() == QLatin1String("displayname") )
{
// Name suitable for presentation to the user
setMetaData( QLatin1String("davDisplayName"), property.text() );
}
else if ( property.tagName() == QLatin1String("source") )
{
// Source template location
QDomElement source = property.namedItem( QLatin1String("link") ).toElement()
.namedItem( QLatin1String("dst") ).toElement();
if ( !source.isNull() )
setMetaData( QLatin1String("davSource"), source.text() );
}
else if ( property.tagName() == QLatin1String("getcontentlanguage") )
{
// equiv. to Content-Language header on a GET
setMetaData( QLatin1String("davContentLanguage"), property.text() );
}
else if ( property.tagName() == QLatin1String("getcontenttype") )
{
// Content type (mime type)
// This may require adjustments for other server-side webdav implementations
// (tested with Apache + mod_dav 1.0.3)
if ( property.text() == QLatin1String("httpd/unix-directory") )
{
isDirectory = true;
}
else
{
mimeType = property.text();
}
}
else if ( property.tagName() == QLatin1String("executable") )
{
// File executable status
if ( property.text() == QLatin1String("T") )
foundExecutable = true;
}
else if ( property.tagName() == QLatin1String("getlastmodified") )
{
// Last modification date
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
}
else if ( property.tagName() == QLatin1String("getetag") )
{
// Entity tag
setMetaData( QLatin1String("davEntityTag"), property.text() );
}
else if ( property.tagName() == QLatin1String("supportedlock") )
{
// Supported locking specifications
for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
{
QDomElement lockEntry = n2.toElement();
if ( lockEntry.tagName() == QLatin1String("lockentry") )
{
QDomElement lockScope = lockEntry.namedItem( QLatin1String("lockscope") ).toElement();
QDomElement lockType = lockEntry.namedItem( QLatin1String("locktype") ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() )
{
// Lock type was properly specified
supportedLockCount++;
const QString lockCountStr = QString::number(supportedLockCount);
const QString scope = lockScope.firstChild().toElement().tagName();
const QString type = lockType.firstChild().toElement().tagName();
setMetaData( QLatin1String("davSupportedLockScope") + lockCountStr, scope );
setMetaData( QLatin1String("davSupportedLockType") + lockCountStr, type );
}
}
}
}
else if ( property.tagName() == QLatin1String("lockdiscovery") )
{
// Lists the available locks
davParseActiveLocks( property.elementsByTagName( QLatin1String("activelock") ), lockCount );
}
else if ( property.tagName() == QLatin1String("resourcetype") )
{
// Resource type. "Specifies the nature of the resource."
if ( !property.namedItem( QLatin1String("collection") ).toElement().isNull() )
{
// This is a collection (directory)
isDirectory = true;
}
}
else
{
kDebug(7113) << "Found unknown webdav property:" << property.tagName();
}
}
}
setMetaData( QLatin1String("davLockCount"), QString::number(lockCount) );
setMetaData( QLatin1String("davSupportedLockCount"), QString::number(supportedLockCount) );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDirectory ? S_IFDIR : S_IFREG );
if ( foundExecutable || isDirectory )
{
// File was executable, or is a directory.
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700 );
}
else
{
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0600 );
}
if ( !isDirectory && !mimeType.isEmpty() )
{
entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimeType );
}
}
void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
uint& lockCount )
{
for ( int i = 0; i < activeLocks.count(); i++ )
{
const QDomElement activeLock = activeLocks.item(i).toElement();
lockCount++;
// required
const QDomElement lockScope = activeLock.namedItem( QLatin1String("lockscope") ).toElement();
const QDomElement lockType = activeLock.namedItem( QLatin1String("locktype") ).toElement();
const QDomElement lockDepth = activeLock.namedItem( QLatin1String("depth") ).toElement();
// optional
const QDomElement lockOwner = activeLock.namedItem( QLatin1String("owner") ).toElement();
const QDomElement lockTimeout = activeLock.namedItem( QLatin1String("timeout") ).toElement();
const QDomElement lockToken = activeLock.namedItem( QLatin1String("locktoken") ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
{
// lock was properly specified
lockCount++;
const QString lockCountStr = QString::number(lockCount);
const QString scope = lockScope.firstChild().toElement().tagName();
const QString type = lockType.firstChild().toElement().tagName();
const QString depth = lockDepth.text();
setMetaData( QLatin1String("davLockScope") + lockCountStr, scope );
setMetaData( QLatin1String("davLockType") + lockCountStr, type );
setMetaData( QLatin1String("davLockDepth") + lockCountStr, depth );
if ( !lockOwner.isNull() )
setMetaData( QLatin1String("davLockOwner") + lockCountStr, lockOwner.text() );
if ( !lockTimeout.isNull() )
setMetaData( QLatin1String("davLockTimeout") + lockCountStr, lockTimeout.text() );
if ( !lockToken.isNull() )
{
QDomElement tokenVal = lockScope.namedItem( QLatin1String("href") ).toElement();
if ( !tokenVal.isNull() )
setMetaData( QLatin1String("davLockToken") + lockCountStr, tokenVal.text() );
}
}
}
}
long HTTPProtocol::parseDateTime( const QString& input, const QString& type )
{
if ( type == QLatin1String("dateTime.tz") )
{
return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
}
else if ( type == QLatin1String("dateTime.rfc1123") )
{
return KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
}
// format not advertised... try to parse anyway
time_t time = KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
if ( time != 0 )
return time;
return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
}
QString HTTPProtocol::davProcessLocks()
{
if ( hasMetaData( QLatin1String("davLockCount") ) )
{
QString response = QLatin1String("If:");
int numLocks = metaData( QLatin1String("davLockCount") ).toInt();
bool bracketsOpen = false;
for ( int i = 0; i < numLocks; i++ )
{
const QString countStr = QString::number(i);
if ( hasMetaData( QLatin1String("davLockToken") + countStr ) )
{
if ( hasMetaData( QLatin1String("davLockURL") + countStr ) )
{
if ( bracketsOpen )
{
response += QLatin1Char(')');
bracketsOpen = false;
}
response += QLatin1String(" <") + metaData( QLatin1String("davLockURL") + countStr ) + QLatin1Char('>');
}
if ( !bracketsOpen )
{
response += QLatin1String(" (");
bracketsOpen = true;
}
else
{
response += QLatin1Char(' ');
}
if ( hasMetaData( QLatin1String("davLockNot") + countStr ) )
response += QLatin1String("Not ");
response += QLatin1Char('<') + metaData( QLatin1String("davLockToken") + countStr ) + QLatin1Char('>');
}
}
if ( bracketsOpen )
response += QLatin1Char(')');
response += QLatin1String("\r\n");
return response;
}
return QString();
}
bool HTTPProtocol::davHostOk()
{
// FIXME needs to be reworked. Switched off for now.
return true;
// cached?
if ( m_davHostOk )
{
kDebug(7113) << "true";
return true;
}
else if ( m_davHostUnsupported )
{
kDebug(7113) << " false";
davError( -2 );
return false;
}
m_request.method = HTTP_OPTIONS;
// query the server's capabilities generally, not for a specific URL
m_request.url.setPath(QLatin1String("*"));
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
// clear davVersions variable, which holds the response to the DAV: header
m_davCapabilities.clear();
proceedUntilResponseHeader();
if (m_davCapabilities.count())
{
for (int i = 0; i < m_davCapabilities.count(); i++)
{
bool ok;
uint verNo = m_davCapabilities[i].toUInt(&ok);
if (ok && verNo > 0 && verNo < 3)
{
m_davHostOk = true;
kDebug(7113) << "Server supports DAV version" << verNo;
}
}
if ( m_davHostOk )
return true;
}
m_davHostUnsupported = true;
davError( -2 );
return false;
}
// This function is for closing proceedUntilResponseHeader(); requests
// Required because there may or may not be further info expected
void HTTPProtocol::davFinished()
{
// TODO: Check with the DAV extension developers
httpClose(m_request.isKeepAlive);
finished();
}
void HTTPProtocol::mkdir( const KUrl& url, int )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_MKCOL;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
if ( m_request.responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::get( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_GET;
QString tmp(metaData(QLatin1String("cache")));
if (!tmp.isEmpty())
m_request.cacheTag.policy = parseCacheControl(tmp);
else
m_request.cacheTag.policy = DEFAULT_CACHE_CONTROL;
proceedUntilResponseContent();
httpClose(m_request.isKeepAlive);
}
void HTTPProtocol::put( const KUrl &url, int, KIO::JobFlags flags )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
// Webdav hosts are capable of observing overwrite == false
if (!(flags & KIO::Overwrite) && m_protocol.startsWith("webdav")) { // krazy:exclude=strings
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
QByteArray request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\"><D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:resourcetype/>"
"</D:prop></D:propfind>";
davSetRequest( request );
// WebDAV Stat or List...
m_request.method = DAV_PROPFIND;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_request.davData.depth = 0;
proceedUntilResponseContent(true);
if (m_request.responseCode == 207) {
error(ERR_FILE_ALREADY_EXIST, QString());
return;
}
m_isError = false;
}
m_request.method = HTTP_PUT;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
kDebug(7113) << "error =" << m_isError;
if (m_isError)
return;
kDebug(7113) << "responseCode =" << m_request.responseCode;
httpClose(false); // Always close connection.
if ( (m_request.responseCode >= 200) && (m_request.responseCode < 300) )
finished();
else
httpPutError();
}
void HTTPProtocol::copy( const KUrl& src, const KUrl& dest, int, KIO::JobFlags flags )
{
kDebug(7113) << src.url() << "->" << dest.url();
if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
return;
resetSessionSettings();
// destination has to be "http(s)://..."
KUrl newDest = dest;
if (newDest.protocol() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
else if (newDest.protocol() == QLatin1String("webdav"))
newDest.setProtocol(QLatin1String("http"));
m_request.method = DAV_COPY;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
// The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
if ( m_request.responseCode == 201 || m_request.responseCode == 204 )
davFinished();
else
davError();
}
void HTTPProtocol::rename( const KUrl& src, const KUrl& dest, KIO::JobFlags flags )
{
kDebug(7113) << src.url() << "->" << dest.url();
if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
return;
resetSessionSettings();
// destination has to be "http://..."
KUrl newDest = dest;
if (newDest.protocol() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
else if (newDest.protocol() == QLatin1String("webdav"))
newDest.setProtocol(QLatin1String("http"));
m_request.method = DAV_MOVE;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
// Work around strict Apache-2 WebDAV implementation which refuses to cooperate
// with webdav://host/directory, instead requiring webdav://host/directory/
// (strangely enough it accepts Destination: without a trailing slash)
// See BR# 209508 and BR#187970
if ( m_request.responseCode == 301) {
m_request.url = m_request.redirectUrl;
m_request.method = DAV_MOVE;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
// force re-authentication...
delete m_wwwAuth;
m_wwwAuth = 0;
proceedUntilResponseHeader();
}
if ( m_request.responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::del( const KUrl& url, bool )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_DELETE;
m_request.url.setQuery(QString());;
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
// Work around strict Apache-2 WebDAV implementation which refuses to cooperate
// with webdav://host/directory, instead requiring webdav://host/directory/
// (strangely enough it accepts Destination: without a trailing slash)
// See BR# 209508 and BR#187970.
if (m_request.responseCode == 301) {
m_request.url = m_request.redirectUrl;
m_request.method = HTTP_DELETE;
m_request.url.setQuery(QString());;
m_request.cacheTag.policy = CC_Reload;
// force re-authentication...
delete m_wwwAuth;
m_wwwAuth = 0;
proceedUntilResponseHeader();
}
// The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
// on successful completion
if ( m_protocol.startsWith( "webdav" ) ) { // krazy:exclude=strings
if ( m_request.responseCode == 200 || m_request.responseCode == 204 )
davFinished();
else
davError();
} else {
if ( m_request.responseCode == 200 || m_request.responseCode == 204 )
finished();
else
error( ERR_SLAVE_DEFINED, i18n( "The resource cannot be deleted." ) );
}
}
void HTTPProtocol::post( const KUrl& url, qint64 size )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_POST;
m_request.cacheTag.policy= CC_Reload;
m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
proceedUntilResponseContent();
}
void HTTPProtocol::davLock( const KUrl& url, const QString& scope,
const QString& type, const QString& owner )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_LOCK;
m_request.url.setQuery(QString());
m_request.cacheTag.policy= CC_Reload;
/* Create appropriate lock XML request. */
QDomDocument lockReq;
QDomElement lockInfo = lockReq.createElementNS( QLatin1String("DAV:"), QLatin1String("lockinfo") );
lockReq.appendChild( lockInfo );
QDomElement lockScope = lockReq.createElement( QLatin1String("lockscope") );
lockInfo.appendChild( lockScope );
lockScope.appendChild( lockReq.createElement( scope ) );
QDomElement lockType = lockReq.createElement( QLatin1String("locktype") );
lockInfo.appendChild( lockType );
lockType.appendChild( lockReq.createElement( type ) );
if ( !owner.isNull() ) {
QDomElement ownerElement = lockReq.createElement( QLatin1String("owner") );
lockReq.appendChild( ownerElement );
QDomElement ownerHref = lockReq.createElement( QLatin1String("href") );
ownerElement.appendChild( ownerHref );
ownerHref.appendChild( lockReq.createTextNode( owner ) );
}
// insert the document into the POST buffer
cachePostData(lockReq.toByteArray());
proceedUntilResponseContent( true );
if ( m_request.responseCode == 200 ) {
// success
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
QDomElement prop = multiResponse.documentElement().namedItem( QLatin1String("prop") ).toElement();
QDomElement lockdiscovery = prop.namedItem( QLatin1String("lockdiscovery") ).toElement();
uint lockCount = 0;
davParseActiveLocks( lockdiscovery.elementsByTagName( QLatin1String("activelock") ), lockCount );
setMetaData( QLatin1String("davLockCount"), QString::number( lockCount ) );
finished();
} else
davError();
}
void HTTPProtocol::davUnlock( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_UNLOCK;
m_request.url.setQuery(QString());
m_request.cacheTag.policy= CC_Reload;
proceedUntilResponseContent( true );
if ( m_request.responseCode == 200 )
finished();
else
davError();
}
QString HTTPProtocol::davError( int code /* = -1 */, const QString &_url )
{
bool callError = false;
if ( code == -1 ) {
code = m_request.responseCode;
callError = true;
}
if ( code == -2 ) {
callError = true;
}
QString url = _url;
if ( !url.isNull() )
url = m_request.url.url();
QString action, errorString;
// for 412 Precondition Failed
QString ow = i18n( "Otherwise, the request would have succeeded." );
switch ( m_request.method ) {
case DAV_PROPFIND:
action = i18nc( "request type", "retrieve property values" );
break;
case DAV_PROPPATCH:
action = i18nc( "request type", "set property values" );
break;
case DAV_MKCOL:
action = i18nc( "request type", "create the requested folder" );
break;
case DAV_COPY:
action = i18nc( "request type", "copy the specified file or folder" );
break;
case DAV_MOVE:
action = i18nc( "request type", "move the specified file or folder" );
break;
case DAV_SEARCH:
action = i18nc( "request type", "search in the specified folder" );
break;
case DAV_LOCK:
action = i18nc( "request type", "lock the specified file or folder" );
break;
case DAV_UNLOCK:
action = i18nc( "request type", "unlock the specified file or folder" );
break;
case HTTP_DELETE:
action = i18nc( "request type", "delete the specified file or folder" );
break;
case HTTP_OPTIONS:
action = i18nc( "request type", "query the server's capabilities" );
break;
case HTTP_GET:
action = i18nc( "request type", "retrieve the contents of the specified file or folder" );
break;
case DAV_REPORT:
action = i18nc( "request type", "run a report in the specified folder" );
break;
case HTTP_PUT:
case HTTP_POST:
case HTTP_HEAD:
default:
// this should not happen, this function is for webdav errors only
Q_ASSERT(0);
}
// default error message if the following code fails
errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred "
"while attempting to %2.", code, action);
switch ( code )
{
case -2:
// internal error: OPTIONS request did not specify DAV compliance
// ERR_UNSUPPORTED_PROTOCOL
errorString = i18n("The server does not support the WebDAV protocol.");
break;
case 207:
// 207 Multi-status
{
// our error info is in the returned XML document.
// retrieve the XML document
// there was an error retrieving the XML document.
// ironic, eh?
if ( !readBody( true ) && m_isError )
return QString();
QStringList errors;
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
QDomElement multistatus = multiResponse.documentElement().namedItem( QLatin1String("multistatus") ).toElement();
QDomNodeList responses = multistatus.elementsByTagName( QLatin1String("response") );
for (int i = 0; i < responses.count(); i++)
{
int errCode;
QString errUrl;
QDomElement response = responses.item(i).toElement();
QDomElement code = response.namedItem( QLatin1String("status") ).toElement();
if ( !code.isNull() )
{
errCode = codeFromResponse( code.text() );
QDomElement href = response.namedItem( QLatin1String("href") ).toElement();
if ( !href.isNull() )
errUrl = href.text();
errors << davError( errCode, errUrl );
}
}
//kError = ERR_SLAVE_DEFINED;
errorString = i18nc( "%1: request type, %2: url",
"An error occurred while attempting to %1, %2. A "
"summary of the reasons is below.", action, url );
errorString += QLatin1String("<ul>");
for ( QStringList::const_iterator it = errors.constBegin(); it != errors.constEnd(); ++it )
errorString += QLatin1String("<li>") + *it + QLatin1String("</li>");
errorString += QLatin1String("</ul>");
}
case 403:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
break;
case 405:
// 405 Method Not Allowed
if ( m_request.method == DAV_MKCOL ) {
// ERR_DIR_ALREADY_EXIST
errorString = i18n("The specified folder already exists.");
}
break;
case 409:
// 409 Conflict
// ERR_ACCESS_DENIED
errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
break;
case 412:
// 412 Precondition failed
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
// ERR_ACCESS_DENIED
errorString = i18n("The server was unable to maintain the liveness of "
"the properties listed in the propertybehavior XML "
"element or you attempted to overwrite a file while "
"requesting that files are not overwritten. %1",
ow );
} else if ( m_request.method == DAV_LOCK ) {
// ERR_ACCESS_DENIED
errorString = i18n("The requested lock could not be granted. %1", ow );
}
break;
case 415:
// 415 Unsupported Media Type
// ERR_ACCESS_DENIED
errorString = i18n("The server does not support the request type of the body.");
break;
case 423:
// 423 Locked
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
break;
case 425:
// 424 Failed Dependency
errorString = i18n("This action was prevented by another error.");
break;
case 502:
// 502 Bad Gateway
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
// ERR_WRITE_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
"to accept the file or folder.", action );
}
break;
case 507:
// 507 Insufficient Storage
// ERR_DISK_FULL
errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
break;
}
// if ( kError != ERR_SLAVE_DEFINED )
//errorString += " (" + url + ')';
if ( callError )
error( ERR_SLAVE_DEFINED, errorString );
return errorString;
}
void HTTPProtocol::httpPutError()
{
QString action, errorString;
switch ( m_request.method ) {
case HTTP_PUT:
action = i18nc("request type", "upload %1", m_request.url.prettyUrl());
break;
default:
// this should not happen, this function is for http errors only
// ### WTF, what about HTTP_GET?
Q_ASSERT(0);
}
// default error message if the following code fails
errorString = i18nc("%1: response code, %2: request type",
"An unexpected error (%1) occurred while attempting to %2.",
m_request.responseCode, action);
switch ( m_request.responseCode )
{
case 403:
case 405:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
// 405 Method Not Allowed
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
break;
case 409:
// 409 Conflict
// ERR_ACCESS_DENIED
errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
break;
case 423:
// 423 Locked
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
break;
case 502:
// 502 Bad Gateway
// ERR_WRITE_ACCESS_DENIED;
errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
"to accept the file or folder.", action );
break;
case 507:
// 507 Insufficient Storage
// ERR_DISK_FULL
errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
break;
}
// if ( kError != ERR_SLAVE_DEFINED )
//errorString += " (" + url + ')';
error( ERR_SLAVE_DEFINED, errorString );
}
bool HTTPProtocol::sendErrorPageNotification()
{
if (!m_request.preferErrorPage)
return false;
if (m_isLoadingErrorPage)
kWarning(7113) << "called twice during one request, something is probably wrong.";
m_isLoadingErrorPage = true;
SlaveBase::errorPage();
return true;
}
bool HTTPProtocol::isOffline()
{
// ### TEMPORARY WORKAROUND (While investigating why solid may
// produce false positives)
return false;
Solid::Networking::Status status = Solid::Networking::status();
kDebug(7113) << "networkstatus:" << status;
// on error or unknown, we assume online
return status == Solid::Networking::Unconnected;
}
void HTTPProtocol::multiGet(const QByteArray &data)
{
QDataStream stream(data);
quint32 n;
stream >> n;
kDebug(7113) << n;
HTTPRequest saveRequest;
if (m_isBusy)
saveRequest = m_request;
resetSessionSettings();
for (unsigned i = 0; i < n; ++i) {
KUrl url;
stream >> url >> mIncomingMetaData;
if (!maybeSetRequestUrl(url))
continue;
//### should maybe call resetSessionSettings() if the server/domain is
// different from the last request!
kDebug(7113) << url.url();
m_request.method = HTTP_GET;
m_request.isKeepAlive = true; //readResponseHeader clears it if necessary
QString tmp = metaData(QLatin1String("cache"));
if (!tmp.isEmpty())
m_request.cacheTag.policy= parseCacheControl(tmp);
else
m_request.cacheTag.policy= DEFAULT_CACHE_CONTROL;
m_requestQueue.append(m_request);
}
if (m_isBusy)
m_request = saveRequest;
#if 0
if (!m_isBusy) {
m_isBusy = true;
QMutableListIterator<HTTPRequest> it(m_requestQueue);
while (it.hasNext()) {
m_request = it.next();
it.remove();
proceedUntilResponseContent();
}
m_isBusy = false;
}
#endif
if (!m_isBusy) {
m_isBusy = true;
QMutableListIterator<HTTPRequest> it(m_requestQueue);
// send the requests
while (it.hasNext()) {
m_request = it.next();
sendQuery();
// save the request state so we can pick it up again in the collection phase
it.setValue(m_request);
kDebug(7113) << "check one: isKeepAlive =" << m_request.isKeepAlive;
if (m_request.cacheTag.ioMode != ReadFromCache) {
m_server.initFrom(m_request);
}
}
// collect the responses
//### for the moment we use a hack: instead of saving and restoring request-id
// we just count up like ParallelGetJobs does.
int requestId = 0;
Q_FOREACH (const HTTPRequest &r, m_requestQueue) {
m_request = r;
kDebug(7113) << "check two: isKeepAlive =" << m_request.isKeepAlive;
setMetaData(QLatin1String("request-id"), QString::number(requestId++));
sendAndKeepMetaData();
if (!(readResponseHeader() && readBody())) {
return;
}
// the "next job" signal for ParallelGetJob is data of size zero which
// readBody() sends without our intervention.
kDebug(7113) << "check three: isKeepAlive =" << m_request.isKeepAlive;
httpClose(m_request.isKeepAlive); //actually keep-alive is mandatory for pipelining
}
finished();
m_requestQueue.clear();
m_isBusy = false;
}
}
ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
{
size_t sent = 0;
const char* buf = static_cast<const char*>(_buf);
while (sent < nbytes)
{
int n = TCPSlaveBase::write(buf + sent, nbytes - sent);
if (n < 0) {
// some error occurred
return -1;
}
sent += n;
}
return sent;
}
void HTTPProtocol::clearUnreadBuffer()
{
m_unreadBuf.clear();
}
// Note: the implementation of unread/readBuffered assumes that unread will only
// be used when there is extra data we don't want to handle, and not to wait for more data.
void HTTPProtocol::unread(char *buf, size_t size)
{
// implement LIFO (stack) semantics
const int newSize = m_unreadBuf.size() + size;
m_unreadBuf.resize(newSize);
for (size_t i = 0; i < size; i++) {
m_unreadBuf.data()[newSize - i - 1] = buf[i];
}
if (size) {
//hey, we still have data, closed connection or not!
m_isEOF = false;
}
}
size_t HTTPProtocol::readBuffered(char *buf, size_t size, bool unlimited)
{
size_t bytesRead = 0;
if (!m_unreadBuf.isEmpty()) {
const int bufSize = m_unreadBuf.size();
bytesRead = qMin((int)size, bufSize);
for (size_t i = 0; i < bytesRead; i++) {
buf[i] = m_unreadBuf.constData()[bufSize - i - 1];
}
m_unreadBuf.truncate(bufSize - bytesRead);
// If we have an unread buffer and the size of the content returned by the
// server is unknown, e.g. chuncked transfer, return the bytes read here since
// we may already have enough data to complete the response and don't want to
// wait for more. See BR# 180631.
if (unlimited)
return bytesRead;
}
if (bytesRead < size) {
int rawRead = TCPSlaveBase::read(buf + bytesRead, size - bytesRead);
if (rawRead < 1) {
m_isEOF = true;
return bytesRead;
}
bytesRead += rawRead;
}
return bytesRead;
}
//### this method will detect an n*(\r\n) sequence if it crosses invocations.
// it will look (n*2 - 1) bytes before start at most and never before buf, naturally.
// supported number of newlines are one and two, in line with HTTP syntax.
// return true if numNewlines newlines were found.
bool HTTPProtocol::readDelimitedText(char *buf, int *idx, int end, int numNewlines)
{
Q_ASSERT(numNewlines >=1 && numNewlines <= 2);
char mybuf[64]; //somewhere close to the usual line length to avoid unread()ing too much
int pos = *idx;
while (pos < end && !m_isEOF) {
int step = qMin((int)sizeof(mybuf), end - pos);
if (m_isChunked) {
//we might be reading the end of the very last chunk after which there is no data.
//don't try to read any more bytes than there are because it causes stalls
//(yes, it shouldn't stall but it does)
step = 1;
}
size_t bufferFill = readBuffered(mybuf, step);
for (size_t i = 0; i < bufferFill ; ++i, ++pos) {
// we copy the data from mybuf to buf immediately and look for the newlines in buf.
// that way we don't miss newlines split over several invocations of this method.
buf[pos] = mybuf[i];
// did we just copy one or two times the (usually) \r\n delimiter?
// until we find even more broken webservers in the wild let's assume that they either
// send \r\n (RFC compliant) or \n (broken) as delimiter...
if (buf[pos] == '\n') {
bool found = numNewlines == 1;
if (!found) { // looking for two newlines
found = ((pos >= 1 && buf[pos - 1] == '\n') ||
(pos >= 3 && buf[pos - 3] == '\r' && buf[pos - 2] == '\n' &&
buf[pos - 1] == '\r'));
}
if (found) {
i++; // unread bytes *after* CRLF
unread(&mybuf[i], bufferFill - i);
*idx = pos + 1;
return true;
}
}
}
}
*idx = pos;
return false;
}
static bool isCompatibleNextUrl(const KUrl &previous, const KUrl &now)
{
if (previous.host() != now.host() || previous.port() != now.port()) {
return false;
}
if (previous.user().isEmpty() && previous.pass().isEmpty()) {
return true;
}
return previous.user() == now.user() && previous.pass() == now.pass();
}
bool HTTPProtocol::httpShouldCloseConnection()
{
kDebug(7113) << "Keep Alive:" << m_request.isKeepAlive;
if (!isConnected()) {
return false;
}
if (m_request.method != HTTP_GET && m_request.method != HTTP_POST) {
return true;
}
// TODO compare current proxy state against proxy needs of next request,
// *when* we actually have variable proxy settings!
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
return !isCompatibleNextUrl(m_server.proxyUrl, m_request.proxyUrl);
}
return !isCompatibleNextUrl(m_server.url, m_request.url);
}
bool HTTPProtocol::httpOpenConnection()
{
kDebug(7113);
m_server.clear();
// Only save proxy auth information after proxy authentication has
// actually taken place, which will set up exactly this connection.
disconnect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
clearUnreadBuffer();
bool connectOk = false;
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
connectOk = connectToHost(m_request.proxyUrl.protocol(), m_request.proxyUrl.host(), m_request.proxyUrl.port());
} else {
connectOk = connectToHost(toQString(m_protocol), m_request.url.host(), m_request.url.port(defaultPort()));
}
if (!connectOk) {
return false;
}
// Disable Nagle's algorithm, i.e turn on TCP_NODELAY.
KTcpSocket *sock = qobject_cast<KTcpSocket *>(socket());
if (sock) {
// kDebug(7113) << "TCP_NODELAY:" << sock->socketOption(QAbstractSocket::LowDelayOption);
sock->setSocketOption(QAbstractSocket::LowDelayOption, 1);
}
m_server.initFrom(m_request);
connected();
return true;
}
bool HTTPProtocol::satisfyRequestFromCache(bool *cacheHasPage)
{
kDebug(7113);
if (m_request.cacheTag.useCache) {
const bool offline = isOffline();
if (offline && m_request.cacheTag.policy != KIO::CC_Reload) {
m_request.cacheTag.policy= KIO::CC_CacheOnly;
}
const bool isCacheOnly = m_request.cacheTag.policy == KIO::CC_CacheOnly;
const CacheTag::CachePlan plan = m_request.cacheTag.plan(m_maxCacheAge);
bool openForReading = false;
if (plan == CacheTag::UseCached || plan == CacheTag::ValidateCached) {
openForReading = cacheFileOpenRead();
if (!openForReading && (isCacheOnly || offline)) {
// cache-only or offline -> we give a definite answer and it is "no"
*cacheHasPage = false;
if (isCacheOnly) {
error(ERR_DOES_NOT_EXIST, m_request.url.url());
} else if (offline) {
error(ERR_COULD_NOT_CONNECT, m_request.url.url());
}
return true;
}
}
if (openForReading) {
m_request.cacheTag.ioMode = ReadFromCache;
*cacheHasPage = true;
// return false if validation is required, so a network request will be sent
return m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached;
}
}
*cacheHasPage = false;
return false;
}
QString HTTPProtocol::formatRequestUri() const
{
// Only specify protocol, host and port when they are not already clear, i.e. when
// we handle HTTP proxying ourself and the proxy server needs to know them.
// Sending protocol/host/port in other cases confuses some servers, and it's not their fault.
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
KUrl u;
QString protocol = m_request.url.protocol();
if (protocol.startsWith(QLatin1String("webdav"))) {
protocol.replace(0, qstrlen("webdav"), QLatin1String("http"));
}
u.setProtocol(protocol);
u.setHost(m_request.url.host());
// if the URL contained the default port it should have been stripped earlier
Q_ASSERT(m_request.url.port() != defaultPort());
u.setPort(m_request.url.port());
u.setEncodedPathAndQuery(m_request.url.encodedPathAndQuery(
KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath));
return u.url();
} else {
return m_request.url.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
}
}
/**
* This function is responsible for opening up the connection to the remote
* HTTP server and sending the header. If this requires special
* authentication or other such fun stuff, then it will handle it. This
* function will NOT receive anything from the server, however. This is in
* contrast to previous incarnations of 'httpOpen' as this method used to be
* called.
*
* The basic process now is this:
*
* 1) Open up the socket and port
* 2) Format our request/header
* 3) Send the header to the remote server
* 4) Call sendBody() if the HTTP method requires sending body data
*/
bool HTTPProtocol::sendQuery()
{
kDebug(7113);
// Cannot have an https request without autoSsl! This can
// only happen if the current installation does not support SSL...
if (isEncryptedHttpVariety(m_protocol) && !isAutoSsl()) {
error(ERR_UNSUPPORTED_PROTOCOL, toQString(m_protocol));
return false;
}
m_request.cacheTag.ioMode = NoCache;
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
QString header;
bool hasBodyData = false;
bool hasDavData = false;
{
header = toQString(m_request.methodString());
QString davHeader;
// Fill in some values depending on the HTTP method to guide further processing
switch (m_request.method)
{
case HTTP_GET: {
bool cacheHasPage = false;
if (satisfyRequestFromCache(&cacheHasPage)) {
kDebug(7113) << "cacheHasPage =" << cacheHasPage;
return cacheHasPage;
}
if (!cacheHasPage) {
// start a new cache file later if appropriate
m_request.cacheTag.ioMode = WriteToCache;
}
break;
}
case HTTP_HEAD:
break;
case HTTP_PUT:
case HTTP_POST:
hasBodyData = true;
break;
case HTTP_DELETE:
case HTTP_OPTIONS:
break;
case DAV_PROPFIND:
hasDavData = true;
davHeader = QLatin1String("Depth: ");
if ( hasMetaData( QLatin1String("davDepth") ) )
{
kDebug(7113) << "Reading DAV depth from metadata:" << metaData( QLatin1String("davDepth") );
davHeader += metaData( QLatin1String("davDepth") );
}
else
{
if ( m_request.davData.depth == 2 )
davHeader += QLatin1String("infinity");
else
davHeader += QString::number( m_request.davData.depth );
}
davHeader += QLatin1String("\r\n");
break;
case DAV_PROPPATCH:
hasDavData = true;
break;
case DAV_MKCOL:
break;
case DAV_COPY:
case DAV_MOVE:
davHeader = QLatin1String("Destination: ") + m_request.davData.desturl;
// infinity depth means copy recursively
// (optional for copy -> but is the desired action)
davHeader += QLatin1String("\r\nDepth: infinity\r\nOverwrite: ");
davHeader += QLatin1Char(m_request.davData.overwrite ? 'T' : 'F');
davHeader += QLatin1String("\r\n");
break;
case DAV_LOCK:
davHeader = QLatin1String("Timeout: ");
{
uint timeout = 0;
if ( hasMetaData( QLatin1String("davTimeout") ) )
timeout = metaData( QLatin1String("davTimeout") ).toUInt();
if ( timeout == 0 )
davHeader += QLatin1String("Infinite");
else
davHeader += QLatin1String("Seconds-") + QString::number(timeout);
}
davHeader += QLatin1String("\r\n");
hasDavData = true;
break;
case DAV_UNLOCK:
davHeader = QLatin1String("Lock-token: ") + metaData(QLatin1String("davLockToken")) + QLatin1String("\r\n");
break;
case DAV_SEARCH:
case DAV_REPORT:
hasDavData = true;
/* fall through */
case DAV_SUBSCRIBE:
case DAV_UNSUBSCRIBE:
case DAV_POLL:
break;
default:
error (ERR_UNSUPPORTED_ACTION, QString());
return false;
}
// DAV_POLL; DAV_NOTIFY
header += formatRequestUri() + QLatin1String(" HTTP/1.1\r\n"); /* start header */
/* support for virtual hosts and required by HTTP 1.1 */
header += QLatin1String("Host: ") + m_request.encoded_hostname;
if (m_request.url.port(defaultPort()) != defaultPort()) {
header += QLatin1Char(':') + QString::number(m_request.url.port());
}
header += QLatin1String("\r\n");
// Support old HTTP/1.0 style keep-alive header for compatibility
// purposes as well as performance improvements while giving end
// users the ability to disable this feature for proxy servers that
// don't support it, e.g. junkbuster proxy server.
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
header += QLatin1String("Proxy-Connection: ");
} else {
header += QLatin1String("Connection: ");
}
if (m_request.isKeepAlive) {
header += QLatin1String("Keep-Alive\r\n");
} else {
header += QLatin1String("close\r\n");
}
if (!m_request.userAgent.isEmpty())
{
header += QLatin1String("User-Agent: ");
header += m_request.userAgent;
header += QLatin1String("\r\n");
}
if (!m_request.referrer.isEmpty())
{
header += QLatin1String("Referer: "); //Don't try to correct spelling!
header += m_request.referrer;
header += QLatin1String("\r\n");
}
if ( m_request.endoffset > m_request.offset )
{
header += QLatin1String("Range: bytes=");
header += KIO::number(m_request.offset);
header += QLatin1Char('-');
header += KIO::number(m_request.endoffset);
header += QLatin1String("\r\n");
kDebug(7103) << "kio_http : Range =" << KIO::number(m_request.offset)
<< "-" << KIO::number(m_request.endoffset);
}
else if ( m_request.offset > 0 && m_request.endoffset == 0 )
{
header += QLatin1String("Range: bytes=");
header += KIO::number(m_request.offset);
header += QLatin1String("-\r\n");
kDebug(7103) << "kio_http: Range =" << KIO::number(m_request.offset);
}
if ( !m_request.cacheTag.useCache || m_request.cacheTag.policy==CC_Reload )
{
/* No caching for reload */
header += QLatin1String("Pragma: no-cache\r\n"); /* for HTTP/1.0 caches */
header += QLatin1String("Cache-control: no-cache\r\n"); /* for HTTP >=1.1 caches */
}
else if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached)
{
kDebug(7113) << "needs validation, performing conditional get.";
/* conditional get */
if (!m_request.cacheTag.etag.isEmpty())
header += QLatin1String("If-None-Match: ") + m_request.cacheTag.etag + QLatin1String("\r\n");
if (m_request.cacheTag.lastModifiedDate != -1) {
const QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate);
header += QLatin1String("If-Modified-Since: ") + httpDate + QLatin1String("\r\n");
setMetaData(QLatin1String("modified"), httpDate);
}
}
header += QLatin1String("Accept: ");
const QString acceptHeader = metaData(QLatin1String("accept"));
if (!acceptHeader.isEmpty())
header += acceptHeader;
else
header += QLatin1String(DEFAULT_ACCEPT_HEADER);
header += QLatin1String("\r\n");
if (m_request.allowTransferCompression)
header += QLatin1String("Accept-Encoding: gzip, deflate, x-gzip, x-deflate\r\n");
if (!m_request.charsets.isEmpty())
header += QLatin1String("Accept-Charset: ") + m_request.charsets + QLatin1String("\r\n");
if (!m_request.languages.isEmpty())
header += QLatin1String("Accept-Language: ") + m_request.languages + QLatin1String("\r\n");
QString cookieStr;
const QString cookieMode = metaData(QLatin1String("cookies")).toLower();
if (cookieMode == QLatin1String("none"))
{
m_request.cookieMode = HTTPRequest::CookiesNone;
}
else if (cookieMode == QLatin1String("manual"))
{
m_request.cookieMode = HTTPRequest::CookiesManual;
cookieStr = metaData(QLatin1String("setcookies"));
}
else
{
m_request.cookieMode = HTTPRequest::CookiesAuto;
if (m_request.useCookieJar)
cookieStr = findCookies(m_request.url.url());
}
if (!cookieStr.isEmpty())
header += cookieStr + QLatin1String("\r\n");
const QString customHeader = metaData( QLatin1String("customHTTPHeader") );
if (!customHeader.isEmpty())
{
header += sanitizeCustomHTTPHeader(customHeader);
header += QLatin1String("\r\n");
}
const QString contentType = metaData(QLatin1String("content-type"));
if (!contentType.isEmpty())
{
if (!contentType.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive))
header += QLatin1String("Content-Type:");
header += contentType;
header += QLatin1String("\r\n");
}
// DoNotTrack feature...
if (config()->readEntry("DoNotTrack", false))
header += QLatin1String("DNT: 1\r\n");
// Remember that at least one failed (with 401 or 407) request/response
// roundtrip is necessary for the server to tell us that it requires
// authentication. However, we proactively add authentication headers if when
// we have cached credentials to avoid the extra roundtrip where possible.
header += authenticationHeader();
if ( m_protocol == "webdav" || m_protocol == "webdavs" )
{
header += davProcessLocks();
// add extra webdav headers, if supplied
davHeader += metaData(QLatin1String("davHeader"));
// Set content type of webdav data
if (hasDavData)
davHeader += QLatin1String("Content-Type: text/xml; charset=utf-8\r\n");
// add extra header elements for WebDAV
header += davHeader;
}
}
kDebug(7103) << "============ Sending Header:";
Q_FOREACH (const QString &s, header.split(QLatin1String("\r\n"), QString::SkipEmptyParts)) {
kDebug(7103) << s;
}
// End the header iff there is no payload data. If we do have payload data
// sendBody() will add another field to the header, Content-Length.
if (!hasBodyData && !hasDavData)
header += QLatin1String("\r\n");
// Check the reusability of the current connection.
if (httpShouldCloseConnection()) {
httpCloseConnection();
}
// Now that we have our formatted header, let's send it!
// Create a new connection to the remote machine if we do
// not already have one...
// NB: the !m_socketProxyAuth condition is a workaround for a proxied Qt socket sometimes
// looking disconnected after receiving the initial 407 response.
// I guess the Qt socket fails to hide the effect of proxy-connection: close after receiving
// the 407 header.
if ((!isConnected() && !m_socketProxyAuth))
{
if (!httpOpenConnection())
{
kDebug(7113) << "Couldn't connect, oopsie!";
return false;
}
}
// Clear out per-connection settings...
resetConnectionSettings();
// Send the data to the remote machine...
ssize_t written = write(header.toLatin1(), header.length());
bool sendOk = (written == (ssize_t) header.length());
if (!sendOk)
{
kDebug(7113) << "Connection broken! (" << m_request.url.host() << ")"
<< " -- intended to write" << header.length()
<< "bytes but wrote" << (int)written << ".";
// The server might have closed the connection due to a timeout, or maybe
// some transport problem arose while the connection was idle.
if (m_request.isKeepAlive)
{
httpCloseConnection();
return true; // Try again
}
kDebug(7113) << "sendOk == false. Connection broken !"
<< " -- intended to write" << header.length()
<< "bytes but wrote" << (int)written << ".";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
else
kDebug(7113) << "sent it!";
bool res = true;
if (hasBodyData || hasDavData)
res = sendBody();
infoMessage(i18n("%1 contacted. Waiting for reply...", m_request.url.host()));
return res;
}
void HTTPProtocol::forwardHttpResponseHeader(bool forwardImmediately)
{
// Send the response header if it was requested...
if (!config()->readEntry("PropagateHttpHeader", false))
return;
setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
if (forwardImmediately)
sendMetaData();
}
bool HTTPProtocol::parseHeaderFromCache()
{
kDebug(7113);
if (!cacheFileReadTextHeader2()) {
return false;
}
Q_FOREACH (const QString &str, m_responseHeaders) {
QString header = str.trimmed().toLower();
if (header.startsWith(QLatin1String("content-type: "))) {
int pos = header.indexOf(QLatin1String("charset="));
if (pos != -1) {
QString charset = header.mid(pos+8);
m_request.cacheTag.charset = charset;
setMetaData(QLatin1String("charset"), charset);
}
} else if (header.startsWith(QLatin1String("content-language: "))) {
QString language = header.mid(18);
setMetaData(QLatin1String("content-language"), language);
} else if (header.startsWith(QLatin1String("content-disposition:"))) {
parseContentDisposition(header.mid(20));
}
}
if (m_request.cacheTag.lastModifiedDate != -1) {
setMetaData(QLatin1String("modified"), formatHttpDate(m_request.cacheTag.lastModifiedDate));
}
// this header comes from the cache, so the response must have been cacheable :)
setCacheabilityMetadata(true);
kDebug(7113) << "Emitting mimeType" << m_mimeType;
forwardHttpResponseHeader(false);
mimeType(m_mimeType);
// IMPORTANT: Do not remove the call below or the http response headers will
// not be available to the application if this slave is put on hold.
forwardHttpResponseHeader();
return true;
}
void HTTPProtocol::fixupResponseMimetype()
{
if (m_mimeType.isEmpty())
return;
kDebug(7113) << "before fixup" << m_mimeType;
// Convert some common mimetypes to standard mimetypes
if (m_mimeType == QLatin1String("application/x-targz"))
m_mimeType = QLatin1String("application/x-compressed-tar");
else if (m_mimeType == QLatin1String("image/x-png"))
m_mimeType = QLatin1String("image/png");
else if (m_mimeType == QLatin1String("audio/x-mp3") || m_mimeType == QLatin1String("audio/x-mpeg") || m_mimeType == QLatin1String("audio/mp3"))
m_mimeType = QLatin1String("audio/mpeg");
else if (m_mimeType == QLatin1String("audio/microsoft-wave"))
m_mimeType = QLatin1String("audio/x-wav");
// Crypto ones....
else if (m_mimeType == QLatin1String("application/pkix-cert") ||
m_mimeType == QLatin1String("application/binary-certificate")) {
m_mimeType = QLatin1String("application/x-x509-ca-cert");
}
// Prefer application/x-compressed-tar or x-gzpostscript over application/x-gzip.
else if (m_mimeType == QLatin1String("application/x-gzip")) {
if ((m_request.url.path().endsWith(QLatin1String(".tar.gz"))) ||
(m_request.url.path().endsWith(QLatin1String(".tar"))))
m_mimeType = QLatin1String("application/x-compressed-tar");
if ((m_request.url.path().endsWith(QLatin1String(".ps.gz"))))
m_mimeType = QLatin1String("application/x-gzpostscript");
}
// Some webservers say "text/plain" when they mean "application/x-bzip"
else if ((m_mimeType == QLatin1String("text/plain")) || (m_mimeType == QLatin1String("application/octet-stream"))) {
const QString ext = QFileInfo(m_request.url.path()).suffix().toUpper();
if (ext == QLatin1String("BZ2"))
m_mimeType = QLatin1String("application/x-bzip");
else if (ext == QLatin1String("PEM"))
m_mimeType = QLatin1String("application/x-x509-ca-cert");
else if (ext == QLatin1String("SWF"))
m_mimeType = QLatin1String("application/x-shockwave-flash");
else if (ext == QLatin1String("PLS"))
m_mimeType = QLatin1String("audio/x-scpls");
else if (ext == QLatin1String("WMV"))
m_mimeType = QLatin1String("video/x-ms-wmv");
else if (ext == QLatin1String("WEBM"))
m_mimeType = QLatin1String("video/webm");
}
kDebug(7113) << "after fixup" << m_mimeType;
}
void HTTPProtocol::fixupResponseContentEncoding()
{
// WABA: Correct for tgz files with a gzip-encoding.
// They really shouldn't put gzip in the Content-Encoding field!
// Web-servers really shouldn't do this: They let Content-Size refer
// to the size of the tgz file, not to the size of the tar file,
// while the Content-Type refers to "tar" instead of "tgz".
if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("gzip")) {
if (m_mimeType == QLatin1String("application/x-tar")) {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-compressed-tar");
} else if (m_mimeType == QLatin1String("application/postscript")) {
// LEONB: Adding another exception for psgz files.
// Could we use the mimelnk files instead of hardcoding all this?
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-gzpostscript");
} else if ((m_request.allowTransferCompression &&
m_mimeType == QLatin1String("text/html"))
||
(m_request.allowTransferCompression &&
m_mimeType != QLatin1String("application/x-compressed-tar") &&
m_mimeType != QLatin1String("application/x-tgz") && // deprecated name
m_mimeType != QLatin1String("application/x-targz") && // deprecated name
m_mimeType != QLatin1String("application/x-gzip") &&
!m_request.url.path().endsWith(QLatin1String(".gz")))) {
// Unzip!
} else {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-gzip");
}
}
// We can't handle "bzip2" encoding (yet). So if we get something with
// bzip2 encoding, we change the mimetype to "application/x-bzip".
// Note for future changes: some web-servers send both "bzip2" as
// encoding and "application/x-bzip[2]" as mimetype. That is wrong.
// currently that doesn't bother us, because we remove the encoding
// and set the mimetype to x-bzip anyway.
if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("bzip2")) {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-bzip");
}
}
//Return true if the term was found, false otherwise. Advance *pos.
//If (*pos + strlen(term) >= end) just advance *pos to end and return false.
//This means that users should always search for the shortest terms first.
static bool consume(const char input[], int *pos, int end, const char *term)
{
// note: gcc/g++ is quite good at optimizing away redundant strlen()s
int idx = *pos;
if (idx + (int)strlen(term) >= end) {
*pos = end;
return false;
}
if (strncasecmp(&input[idx], term, strlen(term)) == 0) {
*pos = idx + strlen(term);
return true;
}
return false;
}
/**
* This function will read in the return header from the server. It will
* not read in the body of the return message. It will also not transmit
* the header to our client as the client doesn't need to know the gory
* details of HTTP headers.
*/
bool HTTPProtocol::readResponseHeader()
{
resetResponseParsing();
if (m_request.cacheTag.ioMode == ReadFromCache &&
m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached) {
// parseHeaderFromCache replaces this method in case of cached content
return parseHeaderFromCache();
}
try_again:
kDebug(7113);
bool upgradeRequired = false; // Server demands that we upgrade to something
// This is also true if we ask to upgrade and
// the server accepts, since we are now
// committed to doing so
bool noHeadersFound = false;
m_request.cacheTag.charset.clear();
m_responseHeaders.clear();
static const int maxHeaderSize = 128 * 1024;
char buffer[maxHeaderSize];
bool cont = false;
bool bCanResume = false;
if (!isConnected()) {
kDebug(7113) << "No connection.";
return false; // Reestablish connection and try again
}
#if 0
// NOTE: This is unnecessary since TCPSlaveBase::read does the same exact
// thing. Plus, if we are unable to read from the socket we need to resend
// the request as done below, not error out! Do not assume remote server
// will honor persistent connections!!
if (!waitForResponse(m_remoteRespTimeout)) {
kDebug(7113) << "Got socket error:" << socket()->errorString();
// No response error
error(ERR_SERVER_TIMEOUT , m_request.url.host());
return false;
}
#endif
int bufPos = 0;
bool foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 1);
if (!foundDelimiter && bufPos < maxHeaderSize) {
kDebug(7113) << "EOF while waiting for header start.";
if (m_request.isKeepAlive) {
// Try to reestablish connection.
httpCloseConnection();
return false; // Reestablish connection and try again.
}
if (m_request.method == HTTP_HEAD) {
// HACK
// Some web-servers fail to respond properly to a HEAD request.
// We compensate for their failure to properly implement the HTTP standard
// by assuming that they will be sending html.
kDebug(7113) << "HEAD -> returned mimetype:" << DEFAULT_MIME_TYPE;
mimeType(QLatin1String(DEFAULT_MIME_TYPE));
return true;
}
kDebug(7113) << "Connection broken !";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
if (!foundDelimiter) {
//### buffer too small for first line of header(!)
Q_ASSERT(0);
}
kDebug(7103) << "============ Received Status Response:";
kDebug(7103) << QByteArray(buffer, bufPos).trimmed();
HTTP_REV httpRev = HTTP_None;
int idx = 0;
if (idx != bufPos && buffer[idx] == '<') {
kDebug(7103) << "No valid HTTP header found! Document starts with XML/HTML tag";
// document starts with a tag, assume HTML instead of text/plain
m_mimeType = QLatin1String("text/html");
m_request.responseCode = 200; // Fake it
httpRev = HTTP_Unknown;
m_request.isKeepAlive = false;
noHeadersFound = true;
// put string back
unread(buffer, bufPos);
goto endParsing;
}
// "HTTP/1.1" or similar
if (consume(buffer, &idx, bufPos, "ICY ")) {
httpRev = SHOUTCAST;
m_request.isKeepAlive = false;
} else if (consume(buffer, &idx, bufPos, "HTTP/")) {
if (consume(buffer, &idx, bufPos, "1.0")) {
httpRev = HTTP_10;
m_request.isKeepAlive = false;
} else if (consume(buffer, &idx, bufPos, "1.1")) {
httpRev = HTTP_11;
}
}
if (httpRev == HTTP_None && bufPos != 0) {
// Remote server does not seem to speak HTTP at all
// Put the crap back into the buffer and hope for the best
kDebug(7113) << "DO NOT WANT." << bufPos;
unread(buffer, bufPos);
if (m_request.responseCode) {
m_request.prevResponseCode = m_request.responseCode;
}
m_request.responseCode = 200; // Fake it
httpRev = HTTP_Unknown;
m_request.isKeepAlive = false;
noHeadersFound = true;
goto endParsing;
}
// response code //### maybe wrong if we need several iterations for this response...
//### also, do multiple iterations (cf. try_again) to parse one header work w/ pipelining?
if (m_request.responseCode) {
m_request.prevResponseCode = m_request.responseCode;
}
skipSpace(buffer, &idx, bufPos);
//TODO saner handling of invalid response code strings
if (idx != bufPos) {
m_request.responseCode = atoi(&buffer[idx]);
} else {
m_request.responseCode = 200;
}
// move idx to start of (yet to be fetched) next line, skipping the "OK"
idx = bufPos;
// (don't bother parsing the "OK", what do we do if it isn't there anyway?)
// immediately act on most response codes...
if (m_request.responseCode != 200 && m_request.responseCode != 304) {
m_request.cacheTag.ioMode = NoCache;
}
if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
// Server side errors
if (m_request.method == HTTP_HEAD) {
; // Ignore error
} else {
if (!sendErrorPageNotification()) {
error(ERR_INTERNAL_SERVER, m_request.url.url());
return false;
}
}
} else if (m_request.responseCode == 416) {
// Range not supported
m_request.offset = 0;
return false; // Try again.
} else if (m_request.responseCode == 426) {
// Upgrade Required
upgradeRequired = true;
} else if (!isAuthenticationRequired(m_request.responseCode) && m_request.responseCode >= 400 && m_request.responseCode <= 499) {
// Any other client errors
// Tell that we will only get an error page here.
if (!sendErrorPageNotification()) {
if (m_request.responseCode == 403)
error(ERR_ACCESS_DENIED, m_request.url.url());
else
error(ERR_DOES_NOT_EXIST, m_request.url.url());
return false;
}
} else if (m_request.responseCode >= 301 && m_request.responseCode<= 303) {
// 301 Moved permanently
if (m_request.responseCode == 301) {
setMetaData(QLatin1String("permanent-redirect"), QLatin1String("true"));
}
// 302 Found (temporary location)
// 303 See Other
if (m_request.method == HTTP_POST) {
// NOTE: This is wrong according to RFC 2616 (section 10.3.[2-4,8]).
// However, because almost all client implementations treat a 301/302
// response as a 303 response in violation of the spec, many servers
// have simply adapted to this way of doing things! Thus, we are
// forced to do the same thing. Otherwise, we won't be able to retrieve
// these pages correctly.
m_request.method = HTTP_GET; // Force a GET
}
} else if (m_request.responseCode == 204) {
// No content
// error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
// Short circuit and do nothing!
// The original handling here was wrong, this is not an error: eg. in the
// example of a 204 No Content response to a PUT completing.
// m_isError = true;
// return false;
} else if (m_request.responseCode == 206) {
if (m_request.offset) {
bCanResume = true;
}
} else if (m_request.responseCode == 102) {
// Processing (for WebDAV)
/***
* This status code is given when the server expects the
* command to take significant time to complete. So, inform
* the user.
*/
infoMessage( i18n( "Server processing request, please wait..." ) );
cont = true;
} else if (m_request.responseCode == 100) {
// We got 'Continue' - ignore it
cont = true;
}
endParsing:
bool authRequiresAnotherRoundtrip = false;
// Skip the whole header parsing if we got no HTTP headers at all
if (!noHeadersFound) {
// Auth handling
{
const bool wasAuthError = isAuthenticationRequired(m_request.prevResponseCode);
const bool isAuthError = isAuthenticationRequired(m_request.responseCode);
const bool sameAuthError = (m_request.responseCode == m_request.prevResponseCode);
kDebug(7113) << "wasAuthError=" << wasAuthError << "isAuthError=" << isAuthError
<< "sameAuthError=" << sameAuthError;
// Not the same authorization error as before and no generic error?
// -> save the successful credentials.
if (wasAuthError && (m_request.responseCode < 400 || (isAuthError && !sameAuthError))) {
KIO::AuthInfo authinfo;
bool alreadyCached = false;
KAbstractHttpAuthentication *auth = 0;
switch (m_request.prevResponseCode) {
case 401:
auth = m_wwwAuth;
alreadyCached = config()->readEntry("cached-www-auth", false);
break;
case 407:
auth = m_proxyAuth;
alreadyCached = config()->readEntry("cached-proxy-auth", false);
break;
default:
Q_ASSERT(false); // should never happen!
}
kDebug(7113) << "authentication object:" << auth;
// Prevent recaching of the same credentials over and over again.
if (auth && (!auth->realm().isEmpty() || !alreadyCached)) {
auth->fillKioAuthInfo(&authinfo);
if (auth == m_wwwAuth) {
setMetaData(QLatin1String("{internal~currenthost}cached-www-auth"), QLatin1String("true"));
if (auth->realm().isEmpty() && !auth->supportsPathMatching())
setMetaData(QLatin1String("{internal~currenthost}www-auth-realm"), authinfo.realmValue);
} else {
setMetaData(QLatin1String("{internal~allhosts}cached-proxy-auth"), QLatin1String("true"));
if (auth->realm().isEmpty() && !auth->supportsPathMatching())
setMetaData(QLatin1String("{internal~allhosts}proxy-auth-realm"), authinfo.realmValue);
}
if (authinfo.keepPassword) {
cacheAuthentication(authinfo);
}
kDebug(7113) << "Caching authentication for" << m_request.url;
}
// Update our server connection state which includes www and proxy username and password.
m_server.updateCredentials(m_request);
}
}
// done with the first line; now tokenize the other lines
// TODO review use of STRTOLL vs. QByteArray::toInt()
foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 2);
kDebug(7113) << " -- full response:" << endl << QByteArray(buffer, bufPos).trimmed();
Q_ASSERT(foundDelimiter);
//NOTE because tokenizer will overwrite newlines in case of line continuations in the header
// unread(buffer, bufSize) will not generally work anymore. we don't need it either.
// either we have a http response line -> try to parse the header, fail if it doesn't work
// or we have garbage -> fail.
HeaderTokenizer tokenizer(buffer);
tokenizer.tokenize(idx, sizeof(buffer));
// Note that not receiving "accept-ranges" means that all bets are off
// wrt the server supporting ranges.
TokenIterator tIt = tokenizer.iterator("accept-ranges");
if (tIt.hasNext() && tIt.next().toLower().startsWith("none")) { // krazy:exclude=strings
bCanResume = false;
}
tIt = tokenizer.iterator("keep-alive");
while (tIt.hasNext()) {
if (tIt.next().startsWith("timeout=")) { // krazy:exclude=strings
m_request.keepAliveTimeout = tIt.current().mid(qstrlen("timeout=")).trimmed().toInt();
}
}
// get the size of our data
tIt = tokenizer.iterator("content-length");
if (tIt.hasNext()) {
m_iSize = STRTOLL(tIt.next().constData(), 0, 10);
}
tIt = tokenizer.iterator("content-location");
if (tIt.hasNext()) {
setMetaData(QLatin1String("content-location"), toQString(tIt.next().trimmed()));
}
// which type of data do we have?
QString mediaValue;
QString mediaAttribute;
tIt = tokenizer.iterator("content-type");
if (tIt.hasNext()) {
QList<QByteArray> l = tIt.next().split(';');
if (!l.isEmpty()) {
// Assign the mime-type.
m_mimeType = toQString(l.first().trimmed().toLower());
kDebug(7113) << "Content-type:" << m_mimeType;
l.removeFirst();
}
// If we still have text, then it means we have a mime-type with a
// parameter (eg: charset=iso-8851) ; so let's get that...
Q_FOREACH (const QByteArray &statement, l) {
const int index = statement.indexOf('=');
if (index <= 0) {
mediaAttribute = toQString(statement.mid(0, index));
} else {
mediaAttribute = toQString(statement.mid(0, index));
mediaValue = toQString(statement.mid(index+1));
}
mediaAttribute = mediaAttribute.trimmed();
mediaValue = mediaValue.trimmed();
bool quoted = false;
if (mediaValue.startsWith(QLatin1Char('"'))) {
quoted = true;
mediaValue.remove(QLatin1Char('"'));
}
if (mediaValue.endsWith(QLatin1Char('"'))) {
mediaValue.truncate(mediaValue.length()-1);
}
kDebug (7113) << "Encoding-type:" << mediaAttribute << "=" << mediaValue;
if (mediaAttribute == QLatin1String("charset")) {
mediaValue = mediaValue.toLower();
m_request.cacheTag.charset = mediaValue;
setMetaData(QLatin1String("charset"), mediaValue);
} else {
setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue);
if (quoted) {
setMetaData(QLatin1String("media-") + mediaAttribute + QLatin1String("-kio-quoted"),
QLatin1String("true"));
}
}
}
}
// content?
tIt = tokenizer.iterator("content-encoding");
while (tIt.hasNext()) {
// This is so wrong !! No wonder kio_http is stripping the
// gzip encoding from downloaded files. This solves multiple
// bug reports and caitoo's problem with downloads when such a
// header is encountered...
// A quote from RFC 2616:
// " When present, its (Content-Encoding) value indicates what additional
// content have been applied to the entity body, and thus what decoding
// mechanism must be applied to obtain the media-type referenced by the
// Content-Type header field. Content-Encoding is primarily used to allow
// a document to be compressed without loosing the identity of its underlying
// media type. Simply put if it is specified, this is the actual mime-type
// we should use when we pull the resource !!!
addEncoding(toQString(tIt.next()), m_contentEncodings);
}
// Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
tIt = tokenizer.iterator("content-disposition");
if (tIt.hasNext()) {
parseContentDisposition(toQString(tIt.next()));
}
tIt = tokenizer.iterator("content-language");
if (tIt.hasNext()) {
QString language = toQString(tIt.next().trimmed());
if (!language.isEmpty()) {
setMetaData(QLatin1String("content-language"), language);
}
}
tIt = tokenizer.iterator("proxy-connection");
if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
QByteArray pc = tIt.next().toLower();
if (pc.startsWith("close")) { // krazy:exclude=strings
m_request.isKeepAlive = false;
} else if (pc.startsWith("keep-alive")) { // krazy:exclude=strings
m_request.isKeepAlive = true;
}
}
tIt = tokenizer.iterator("link");
if (tIt.hasNext()) {
// We only support Link: <url>; rel="type" so far
QStringList link = toQString(tIt.next()).split(QLatin1Char(';'), QString::SkipEmptyParts);
if (link.count() == 2) {
QString rel = link[1].trimmed();
if (rel.startsWith(QLatin1String("rel=\""))) {
rel = rel.mid(5, rel.length() - 6);
if (rel.toLower() == QLatin1String("pageservices")) {
//### the remove() part looks fishy!
QString url = link[0].remove(QRegExp(QLatin1String("[<>]"))).trimmed();
setMetaData(QLatin1String("PageServices"), url);
}
}
}
}
tIt = tokenizer.iterator("p3p");
if (tIt.hasNext()) {
// P3P privacy policy information
QStringList policyrefs, compact;
while (tIt.hasNext()) {
QStringList policy = toQString(tIt.next().simplified())
.split(QLatin1Char('='), QString::SkipEmptyParts);
if (policy.count() == 2) {
if (policy[0].toLower() == QLatin1String("policyref")) {
policyrefs << policy[1].remove(QRegExp(QLatin1String("[\")\']"))).trimmed();
} else if (policy[0].toLower() == QLatin1String("cp")) {
// We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
// other metadata sent in strings. This could be a bit more
// efficient but I'm going for correctness right now.
const QString s = policy[1].remove(QRegExp(QLatin1String("[\")\']")));
const QStringList cps = s.split(QLatin1Char(' '), QString::SkipEmptyParts);
compact << cps;
}
}
}
if (!policyrefs.isEmpty()) {
setMetaData(QLatin1String("PrivacyPolicy"), policyrefs.join(QLatin1String("\n")));
}
if (!compact.isEmpty()) {
setMetaData(QLatin1String("PrivacyCompactPolicy"), compact.join(QLatin1String("\n")));
}
}
// continue only if we know that we're at least HTTP/1.0
if (httpRev == HTTP_11 || httpRev == HTTP_10) {
// let them tell us if we should stay alive or not
tIt = tokenizer.iterator("connection");
while (tIt.hasNext()) {
QByteArray connection = tIt.next().toLower();
if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) {
if (connection.startsWith("close")) { // krazy:exclude=strings
m_request.isKeepAlive = false;
} else if (connection.startsWith("keep-alive")) { // krazy:exclude=strings
m_request.isKeepAlive = true;
}
}
if (connection.startsWith("upgrade")) { // krazy:exclude=strings
if (m_request.responseCode == 101) {
// Ok, an upgrade was accepted, now we must do it
upgradeRequired = true;
} else if (upgradeRequired) { // 426
// Nothing to do since we did it above already
}
}
}
// what kind of encoding do we have? transfer?
tIt = tokenizer.iterator("transfer-encoding");
while (tIt.hasNext()) {
// If multiple encodings have been applied to an entity, the
// transfer-codings MUST be listed in the order in which they
// were applied.
addEncoding(toQString(tIt.next().trimmed()), m_transferEncodings);
}
// md5 signature
tIt = tokenizer.iterator("content-md5");
if (tIt.hasNext()) {
m_contentMD5 = toQString(tIt.next().trimmed());
}
// *** Responses to the HTTP OPTIONS method follow
// WebDAV capabilities
tIt = tokenizer.iterator("dav");
while (tIt.hasNext()) {
m_davCapabilities << toQString(tIt.next());
}
// *** Responses to the HTTP OPTIONS method finished
}
// Now process the HTTP/1.1 upgrade
QStringList upgradeOffers;
tIt = tokenizer.iterator("upgrade");
if (tIt.hasNext()) {
// Now we have to check to see what is offered for the upgrade
QString offered = toQString(tIt.next());
upgradeOffers = offered.split(QRegExp(QLatin1String("[ \n,\r\t]")), QString::SkipEmptyParts);
}
Q_FOREACH (const QString &opt, upgradeOffers) {
if (opt == QLatin1String("TLS/1.0")) {
if (!startSsl() && upgradeRequired) {
error(ERR_UPGRADE_REQUIRED, opt);
return false;
}
} else if (opt == QLatin1String("HTTP/1.1")) {
httpRev = HTTP_11;
} else if (upgradeRequired) {
// we are told to do an upgrade we don't understand
error(ERR_UPGRADE_REQUIRED, opt);
return false;
}
}
// Harvest cookies (mmm, cookie fields!)
QByteArray cookieStr; // In case we get a cookie.
tIt = tokenizer.iterator("set-cookie");
while (tIt.hasNext()) {
cookieStr += "Set-Cookie: ";
cookieStr += tIt.next();
cookieStr += '\n';
}
if (!cookieStr.isEmpty()) {
if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar) {
// Give cookies to the cookiejar.
const QString domain = config()->readEntry("cross-domain");
if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) {
cookieStr = "Cross-Domain\n" + cookieStr;
}
addCookies( m_request.url.url(), cookieStr );
} else if (m_request.cookieMode == HTTPRequest::CookiesManual) {
// Pass cookie to application
setMetaData(QLatin1String("setcookies"), QString::fromUtf8(cookieStr)); // ## is encoding ok?
}
}
// We need to reread the header if we got a '100 Continue' or '102 Processing'
// This may be a non keepalive connection so we handle this kind of loop internally
if ( cont )
{
kDebug(7113) << "cont; returning to mark try_again";
goto try_again;
}
if (!m_isChunked && (m_iSize == NO_SIZE) && m_request.isKeepAlive &&
canHaveResponseBody(m_request.responseCode, m_request.method)) {
kDebug(7113) << "Ignoring keep-alive: otherwise unable to determine response body length.";
m_request.isKeepAlive = false;
}
// TODO cache the proxy auth data (not doing this means a small performance regression for now)
// we may need to send (Proxy or WWW) authorization data
authRequiresAnotherRoundtrip = false;
if (!m_request.doNotAuthenticate && isAuthenticationRequired(m_request.responseCode)) {
KIO::AuthInfo authinfo;
KAbstractHttpAuthentication **auth;
if (m_request.responseCode == 401) {
auth = &m_wwwAuth;
tIt = tokenizer.iterator("www-authenticate");
authinfo.url = m_request.url;
authinfo.username = m_server.url.user();
authinfo.prompt = i18n("You need to supply a username and a "
"password to access this site.");
authinfo.commentLabel = i18n("Site:");
} else {
// make sure that the 407 header hasn't escaped a lower layer when it shouldn't.
// this may break proxy chains which were never tested anyway, and AFAIK they are
// rare to nonexistent in the wild.
Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy);
auth = &m_proxyAuth;
tIt = tokenizer.iterator("proxy-authenticate");
authinfo.url = m_request.proxyUrl;
authinfo.username = m_request.proxyUrl.user();
authinfo.prompt = i18n("You need to supply a username and a password for "
"the proxy server listed below before you are allowed "
"to access any sites." );
authinfo.commentLabel = i18n("Proxy:");
}
QList<QByteArray> authTokens = tIt.all();
// Workaround brain dead server responses that violate the spec and
// incorrectly return a 401/407 without the required WWW/Proxy-Authenticate
// header fields. See bug 215736...
if (!authTokens.isEmpty()) {
authRequiresAnotherRoundtrip = true;
kDebug(7113) << "parsing authentication request; response code =" << m_request.responseCode;
try_next_auth_scheme:
QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens);
if (*auth) {
if (!bestOffer.toLower().startsWith((*auth)->scheme().toLower())) {
// huh, the strongest authentication scheme offered has changed.
kDebug(7113) << "deleting old auth class...";
delete *auth;
*auth = 0;
}
}
if (!(*auth)) {
*auth = KAbstractHttpAuthentication::newAuth(bestOffer, config());
}
kDebug(7113) << "pointer to auth class is now" << *auth;
if (*auth) {
kDebug(7113) << "Trying authentication scheme:" << (*auth)->scheme();
// remove trailing space from the method string, or digest auth will fail
(*auth)->setChallenge(bestOffer, authinfo.url, m_request.methodString());
QString username;
QString password;
bool generateAuthorization = true;
if ((*auth)->needCredentials()) {
// use credentials supplied by the application if available
if (!m_request.url.user().isEmpty() && !m_request.url.pass().isEmpty()) {
username = m_request.url.user();
password = m_request.url.pass();
// don't try this password any more
m_request.url.setPass(QString());
} else {
// try to get credentials from kpasswdserver's cache, then try asking the user.
authinfo.verifyPath = false; // we have realm, no path based checking please!
authinfo.realmValue = (*auth)->realm();
if (authinfo.realmValue.isEmpty() && !(*auth)->supportsPathMatching())
authinfo.realmValue = QLatin1String((*auth)->scheme());
// Save the current authinfo url because it can be modified by the call to
// checkCachedAuthentication. That way we can restore it if the call
// modified it.
const KUrl reqUrl = authinfo.url;
if (!checkCachedAuthentication(authinfo) ||
((*auth)->wasFinalStage() && m_request.responseCode == m_request.prevResponseCode)) {
QString errorMsg;
if ((*auth)->wasFinalStage()) {
switch (m_request.prevResponseCode) {
case 401:
errorMsg = i18n("Authentication Failed.");
break;
case 407:
errorMsg = i18n("Proxy Authentication Failed.");
break;
default:
break;
}
}
// Reset url to the saved url...
authinfo.url = reqUrl;
authinfo.keepPassword = true;
authinfo.comment = i18n("<b>%1</b> at <b>%2</b>",
authinfo.realmValue, authinfo.url.host());
if (!openPasswordDialog(authinfo, errorMsg)) {
if (sendErrorPageNotification()) {
generateAuthorization = false;
authRequiresAnotherRoundtrip = false;
} else {
error(ERR_ACCESS_DENIED, reqUrl.host());
return false;
}
}
}
username = authinfo.username;
password = authinfo.password;
}
}
if (generateAuthorization) {
(*auth)->generateResponse(username, password);
kDebug(7113) << "Auth State: isError=" << (*auth)->isError()
<< "needCredentials=" << (*auth)->needCredentials()
<< "forceKeepAlive=" << (*auth)->forceKeepAlive()
<< "forceDisconnect=" << (*auth)->forceDisconnect()
<< "headerFragment=" << (*auth)->headerFragment();
if ((*auth)->isError()) {
authTokens.removeOne(bestOffer);
if (!authTokens.isEmpty())
goto try_next_auth_scheme;
else {
error(ERR_UNSUPPORTED_ACTION, i18n("Authorization failed."));
return false;
}
//### return false; ?
} else if ((*auth)->forceKeepAlive()) {
//### think this through for proxied / not proxied
m_request.isKeepAlive = true;
} else if ((*auth)->forceDisconnect()) {
//### think this through for proxied / not proxied
m_request.isKeepAlive = false;
httpCloseConnection();
}
}
} else {
if (sendErrorPageNotification())
authRequiresAnotherRoundtrip = false;
else {
error(ERR_UNSUPPORTED_ACTION, i18n("Unknown Authorization method."));
return false;
}
}
}
}
QString locationStr;
// In fact we should do redirection only if we have a redirection response code (300 range)
tIt = tokenizer.iterator("location");
if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) {
locationStr = QString::fromUtf8(tIt.next().trimmed());
}
// We need to do a redirect
if (!locationStr.isEmpty())
{
KUrl u(m_request.url, locationStr);
if(!u.isValid())
{
error(ERR_MALFORMED_URL, u.url());
return false;
}
if ((u.protocol() != QLatin1String("http")) && (u.protocol() != QLatin1String("https")) &&
(u.protocol() != QLatin1String("webdav")) && (u.protocol() != QLatin1String("webdavs")))
{
redirection(u);
error(ERR_ACCESS_DENIED, u.url());
return false;
}
// preserve #ref: (bug 124654)
// if we were at http://host/resource1#ref, we sent a GET for "/resource1"
// if we got redirected to http://host/resource2, then we have to re-add
// the fragment:
if (m_request.url.hasRef() && !u.hasRef() &&
(m_request.url.host() == u.host()) &&
(m_request.url.protocol() == u.protocol()))
u.setRef(m_request.url.ref());
m_isRedirection = true;
if (!m_request.id.isEmpty())
{
sendMetaData();
}
// If we're redirected to a http:// url, remember that we're doing webdav...
if (m_protocol == "webdav" || m_protocol == "webdavs"){
if(u.protocol() == QLatin1String("http")){
u.setProtocol(QLatin1String("webdav"));
}else if(u.protocol() == QLatin1String("https")){
u.setProtocol(QLatin1String("webdavs"));
}
m_request.redirectUrl = u;
}
kDebug(7113) << "Re-directing from" << m_request.url.url()
<< "to" << u.url();
redirection(u);
// It would be hard to cache the redirection response correctly. The possible benefit
// is small (if at all, assuming fast disk and slow network), so don't do it.
cacheFileClose();
setCacheabilityMetadata(false);
}
// Inform the job that we can indeed resume...
if (bCanResume && m_request.offset) {
//TODO turn off caching???
canResume();
} else {
m_request.offset = 0;
}
// Correct a few common wrong content encodings
fixupResponseContentEncoding();
// Correct some common incorrect pseudo-mimetypes
fixupResponseMimetype();
// parse everything related to expire and other dates, and cache directives; also switch
// between cache reading and writing depending on cache validation result.
cacheParseResponseHeader(tokenizer);
}
if (m_request.cacheTag.ioMode == ReadFromCache) {
if (m_request.cacheTag.policy == CC_Verify &&
m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
kDebug(7113) << "Reading resource from cache even though the cache plan is not "
"UseCached; the server is probably sending wrong expiry information.";
}
// parseHeaderFromCache replaces this method in case of cached content
return parseHeaderFromCache();
}
if (config()->readEntry("PropagateHttpHeader", false) ||
m_request.cacheTag.ioMode == WriteToCache) {
// store header lines if they will be used; note that the tokenizer removing
// line continuation special cases is probably more good than bad.
int nextLinePos = 0;
int prevLinePos = 0;
bool haveMore = true;
while (haveMore) {
haveMore = nextLine(buffer, &nextLinePos, bufPos);
int prevLineEnd = nextLinePos;
while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') {
prevLineEnd--;
}
m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos],
prevLineEnd - prevLinePos));
prevLinePos = nextLinePos;
}
// IMPORTNAT: Do not remove this line because forwardHttpResponseHeader
// is called below. This line is here to ensure the response headers are
// available to the client before it receives mimetype information.
// The support for putting ioslaves on hold in the KIO-QNAM integration
// will break if this line is removed.
setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
}
// Let the app know about the mime-type iff this is not a redirection and
// the mime-type string is not empty.
if (!m_isRedirection && m_request.responseCode != 204 &&
(!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) &&
(m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) {
kDebug(7113) << "Emitting mimetype " << m_mimeType;
mimeType( m_mimeType );
}
// IMPORTANT: Do not move the function call below before doing any
// redirection. Otherwise it might mess up some sites, see BR# 150904.
forwardHttpResponseHeader();
if (m_request.method == HTTP_HEAD)
return true;
return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent
}
void HTTPProtocol::parseContentDisposition(const QString &disposition)
{
const QMap<QString, QString> parameters = contentDispositionParser(disposition);
QMap<QString, QString>::const_iterator i = parameters.constBegin();
while (i != parameters.constEnd()) {
setMetaData(QLatin1String("content-disposition-") + i.key(), i.value());
kDebug(7113) << "Content-Disposition:" << i.key() << "=" << i.value();
++i;
}
}
void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs)
{
QString encoding = _encoding.trimmed().toLower();
// Identity is the same as no encoding
if (encoding == QLatin1String("identity")) {
return;
} else if (encoding == QLatin1String("8bit")) {
// Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
return;
} else if (encoding == QLatin1String("chunked")) {
m_isChunked = true;
// Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
//if ( m_cmd != CMD_COPY )
m_iSize = NO_SIZE;
} else if ((encoding == QLatin1String("x-gzip")) || (encoding == QLatin1String("gzip"))) {
encs.append(QLatin1String("gzip"));
} else if ((encoding == QLatin1String("x-bzip2")) || (encoding == QLatin1String("bzip2"))) {
encs.append(QLatin1String("bzip2")); // Not yet supported!
} else if ((encoding == QLatin1String("x-deflate")) || (encoding == QLatin1String("deflate"))) {
encs.append(QLatin1String("deflate"));
} else {
kDebug(7113) << "Unknown encoding encountered. "
<< "Please write code. Encoding =" << encoding;
}
}
void HTTPProtocol::cacheParseResponseHeader(const HeaderTokenizer &tokenizer)
{
if (!m_request.cacheTag.useCache)
return;
// might have to add more response codes
if (m_request.responseCode != 200 && m_request.responseCode != 304) {
return;
}
// -1 is also the value returned by KDateTime::toTime_t() from an invalid instance.
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
const qint64 currentDate = time(0);
bool mayCache = m_request.cacheTag.ioMode != NoCache;
TokenIterator tIt = tokenizer.iterator("last-modified");
if (tIt.hasNext()) {
m_request.cacheTag.lastModifiedDate =
KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
//### might be good to canonicalize the date by using KDateTime::toString()
if (m_request.cacheTag.lastModifiedDate != -1) {
setMetaData(QLatin1String("modified"), toQString(tIt.current()));
}
}
// determine from available information when the response was served by the origin server
{
qint64 dateHeader = -1;
tIt = tokenizer.iterator("date");
if (tIt.hasNext()) {
dateHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
// -1 on error
}
qint64 ageHeader = 0;
tIt = tokenizer.iterator("age");
if (tIt.hasNext()) {
ageHeader = tIt.next().toLongLong();
// 0 on error
}
if (dateHeader != -1) {
m_request.cacheTag.servedDate = dateHeader;
} else if (ageHeader) {
m_request.cacheTag.servedDate = currentDate - ageHeader;
} else {
m_request.cacheTag.servedDate = currentDate;
}
}
bool hasCacheDirective = false;
// determine when the response "expires", i.e. becomes stale and needs revalidation
{
// (we also parse other cache directives here)
qint64 maxAgeHeader = 0;
tIt = tokenizer.iterator("cache-control");
while (tIt.hasNext()) {
QByteArray cacheStr = tIt.next().toLower();
if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) { // krazy:exclude=strings
// Don't put in cache
mayCache = false;
hasCacheDirective = true;
} else if (cacheStr.startsWith("max-age=")) { // krazy:exclude=strings
QByteArray ba = cacheStr.mid(qstrlen("max-age=")).trimmed();
bool ok = false;
maxAgeHeader = ba.toLongLong(&ok);
if (ok) {
hasCacheDirective = true;
}
}
}
qint64 expiresHeader = -1;
tIt = tokenizer.iterator("expires");
if (tIt.hasNext()) {
expiresHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
kDebug(7113) << "parsed expire date from 'expires' header:" << tIt.current();
}
if (maxAgeHeader) {
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + maxAgeHeader;
} else if (expiresHeader != -1) {
m_request.cacheTag.expireDate = expiresHeader;
} else {
// heuristic expiration date
if (m_request.cacheTag.lastModifiedDate != -1) {
// expAge is following the RFC 2616 suggestion for heuristic expiration
qint64 expAge = (m_request.cacheTag.servedDate -
m_request.cacheTag.lastModifiedDate) / 10;
// not in the RFC: make sure not to have a huge heuristic cache lifetime
expAge = qMin(expAge, qint64(3600 * 24));
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + expAge;
} else {
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate +
DEFAULT_CACHE_EXPIRE;
}
}
// make sure that no future clock monkey business causes the cache entry to un-expire
if (m_request.cacheTag.expireDate < currentDate) {
m_request.cacheTag.expireDate = 0; // January 1, 1970 :)
}
}
tIt = tokenizer.iterator("etag");
if (tIt.hasNext()) {
QString prevEtag = m_request.cacheTag.etag;
m_request.cacheTag.etag = toQString(tIt.next());
if (m_request.cacheTag.etag != prevEtag && m_request.responseCode == 304) {
kDebug(7103) << "304 Not Modified but new entity tag - I don't think this is legal HTTP.";
}
}
// whoops.. we received a warning
tIt = tokenizer.iterator("warning");
if (tIt.hasNext()) {
//Don't use warning() here, no need to bother the user.
//Those warnings are mostly about caches.
infoMessage(toQString(tIt.next()));
}
// Cache management (HTTP 1.0)
tIt = tokenizer.iterator("pragma");
while (tIt.hasNext()) {
if (tIt.next().toLower().startsWith("no-cache")) { // krazy:exclude=strings
mayCache = false;
hasCacheDirective = true;
}
}
// The deprecated Refresh Response
tIt = tokenizer.iterator("refresh");
if (tIt.hasNext()) {
mayCache = false;
setMetaData(QLatin1String("http-refresh"), toQString(tIt.next().trimmed()));
}
// We don't cache certain text objects
if (m_mimeType.startsWith(QLatin1String("text/")) && (m_mimeType != QLatin1String("text/css")) &&
(m_mimeType != QLatin1String("text/x-javascript")) && !hasCacheDirective) {
// Do not cache secure pages or pages
// originating from password protected sites
// unless the webserver explicitly allows it.
if (isUsingSsl() || m_wwwAuth) {
mayCache = false;
}
}
// note that we've updated cacheTag, so the plan() is with current data
if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) {
kDebug(7113) << "Cache needs validation";
if (m_request.responseCode == 304) {
kDebug(7113) << "...was revalidated by response code but not by updated expire times. "
"We're going to set the expire date to 60 seconds in the future...";
m_request.cacheTag.expireDate = currentDate + 60;
if (m_request.cacheTag.policy == CC_Verify &&
m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
// "apparently" because we /could/ have made an error ourselves, but the errors I
// witnessed were all the server's fault.
kDebug(7113) << "this proxy or server apparently sends bogus expiry information.";
}
}
}
// validation handling
if (mayCache && m_request.responseCode == 200 && !m_mimeType.isEmpty()) {
kDebug(7113) << "Cache, adding" << m_request.url.url();
// ioMode can still be ReadFromCache here if we're performing a conditional get
// aka validation
m_request.cacheTag.ioMode = WriteToCache;
if (!cacheFileOpenWrite()) {
kDebug(7113) << "Error creating cache entry for " << m_request.url.url()<<"!\n";
}
m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE);
} else if (m_request.responseCode == 304 && m_request.cacheTag.file) {
if (!mayCache) {
kDebug(7113) << "This webserver is confused about the cacheability of the data it sends.";
}
// the cache file should still be open for reading, see satisfyRequestFromCache().
Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
} else {
cacheFileClose();
}
setCacheabilityMetadata(mayCache);
}
void HTTPProtocol::setCacheabilityMetadata(bool cachingAllowed)
{
if (!cachingAllowed) {
setMetaData(QLatin1String("no-cache"), QLatin1String("true"));
setMetaData(QLatin1String("expire-date"), QLatin1String("1")); // Expired
} else {
QString tmp;
tmp.setNum(m_request.cacheTag.expireDate);
setMetaData(QLatin1String("expire-date"), tmp);
// slightly changed semantics from old creationDate, probably more correct now
tmp.setNum(m_request.cacheTag.servedDate);
setMetaData(QLatin1String("cache-creation-date"), tmp);
}
}
bool HTTPProtocol::sendCachedBody()
{
infoMessage(i18n("Sending data to %1" , m_request.url.host()));
QByteArray cLength ("Content-Length: ");
cLength += QByteArray::number(m_POSTbuf->size());
cLength += "\r\n\r\n";
kDebug(7113) << "sending cached data (size=" << m_POSTbuf->size() << ")";
// Send the content length...
bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
if (!sendOk) {
kDebug( 7113 ) << "Connection broken when sending "
<< "content length: (" << m_request.url.host() << ")";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
// Make sure the read head is at the beginning...
m_POSTbuf->reset();
// Send the data...
while (!m_POSTbuf->atEnd()) {
const QByteArray buffer = m_POSTbuf->read(s_MaxInMemPostBufSize);
sendOk = (write(buffer.data(), buffer.size()) == (ssize_t) buffer.size());
if (!sendOk) {
kDebug(7113) << "Connection broken when sending message body: ("
<< m_request.url.host() << ")";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
}
return true;
}
bool HTTPProtocol::sendBody()
{
// If we have cached data, the it is either a repost or a DAV request so send
// the cached data...
if (m_POSTbuf)
return sendCachedBody();
if (m_iPostDataSize == NO_SIZE) {
// Try the old approach of retireving content data from the job
// before giving up.
if (retrieveAllData())
return sendCachedBody();
error(ERR_POST_NO_SIZE, m_request.url.host());
return false;
}
kDebug(7113) << "sending data (size=" << m_iPostDataSize << ")";
infoMessage(i18n("Sending data to %1", m_request.url.host()));
QByteArray cLength ("Content-Length: ");
cLength += QByteArray::number(m_iPostDataSize);
cLength += "\r\n\r\n";
kDebug(7113) << cLength.trimmed();
// Send the content length...
bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
if (!sendOk) {
// The server might have closed the connection due to a timeout, or maybe
// some transport problem arose while the connection was idle.
if (m_request.isKeepAlive)
{
httpCloseConnection();
return true; // Try again
}
kDebug(7113) << "Connection broken while sending POST content size to" << m_request.url.host();
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
// Send the amount
totalSize(m_iPostDataSize);
sendOk = true;
KIO::filesize_t bytesSent = 0;
while (true) {
dataReq();
QByteArray buffer;
const int bytesRead = readData(buffer);
// On done...
if (bytesRead == 0) {
sendOk = (bytesSent == m_iPostDataSize);
break;
}
// On error return false...
if (bytesRead < 0) {
error(ERR_ABORTED, m_request.url.host());
sendOk = false;
break;
}
// Cache the POST data in case of a repost request.
cachePostData(buffer);
// This will only happen if transmitting the data fails, so we will simply
// cache the content locally for the potential re-transmit...
if (!sendOk)
continue;
if (write(buffer.data(), bytesRead) == static_cast<ssize_t>(bytesRead)) {
bytesSent += bytesRead;
processedSize(bytesSent); // Send update status...
continue;
}
kDebug(7113) << "Connection broken while sending POST content to" << m_request.url.host();
error(ERR_CONNECTION_BROKEN, m_request.url.host());
sendOk = false;
}
return sendOk;
}
void HTTPProtocol::httpClose( bool keepAlive )
{
kDebug(7113) << "keepAlive =" << keepAlive;
cacheFileClose();
// Only allow persistent connections for GET requests.
// NOTE: we might even want to narrow this down to non-form
// based submit requests which will require a meta-data from
// khtml.
if (keepAlive) {
if (!m_request.keepAliveTimeout)
m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
else if (m_request.keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
m_request.keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
kDebug(7113) << "keep alive (" << m_request.keepAliveTimeout << ")";
QByteArray data;
QDataStream stream( &data, QIODevice::WriteOnly );
stream << int(99); // special: Close connection
setTimeoutSpecialCommand(m_request.keepAliveTimeout, data);
return;
}
httpCloseConnection();
}
void HTTPProtocol::closeConnection()
{
kDebug(7113);
httpCloseConnection();
}
void HTTPProtocol::httpCloseConnection()
{
kDebug(7113);
m_request.isKeepAlive = false;
m_server.clear();
disconnectFromHost();
clearUnreadBuffer();
setTimeoutSpecialCommand(-1); // Cancel any connection timeout
}
void HTTPProtocol::slave_status()
{
kDebug(7113);
if ( !isConnected() )
httpCloseConnection();
slaveStatus( m_server.url.host(), isConnected() );
}
void HTTPProtocol::mimetype( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_HEAD;
m_request.cacheTag.policy= CC_Cache;
proceedUntilResponseHeader();
httpClose(m_request.isKeepAlive);
finished();
kDebug(7113) << "http: mimetype =" << m_mimeType;
}
void HTTPProtocol::special( const QByteArray &data )
{
kDebug(7113);
int tmp;
QDataStream stream(data);
stream >> tmp;
switch (tmp) {
case 1: // HTTP POST
{
KUrl url;
qint64 size;
stream >> url >> size;
post( url, size );
break;
}
case 2: // cache_update
{
KUrl url;
bool no_cache;
qint64 expireDate;
stream >> url >> no_cache >> expireDate;
if (no_cache) {
QString filename = cacheFilePathFromUrl(url);
// there is a tiny risk of deleting the wrong file due to hash collisions here.
// this is an unimportant performance issue.
// FIXME on Windows we may be unable to delete the file if open
QFile::remove(filename);
finished();
break;
}
// let's be paranoid and inefficient here...
HTTPRequest savedRequest = m_request;
m_request.url = url;
if (cacheFileOpenRead()) {
m_request.cacheTag.expireDate = expireDate;
cacheFileClose(); // this sends an update command to the cache cleaner process
}
m_request = savedRequest;
finished();
break;
}
case 5: // WebDAV lock
{
KUrl url;
QString scope, type, owner;
stream >> url >> scope >> type >> owner;
davLock( url, scope, type, owner );
break;
}
case 6: // WebDAV unlock
{
KUrl url;
stream >> url;
davUnlock( url );
break;
}
case 7: // Generic WebDAV
{
KUrl url;
int method;
qint64 size;
stream >> url >> method >> size;
davGeneric( url, (KIO::HTTP_METHOD) method, size );
break;
}
case 99: // Close Connection
{
httpCloseConnection();
break;
}
default:
// Some command we don't understand.
// Just ignore it, it may come from some future version of KDE.
break;
}
}
/**
* Read a chunk from the data stream.
*/
int HTTPProtocol::readChunked()
{
if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
{
// discard CRLF from previous chunk, if any, and read size of next chunk
int bufPos = 0;
m_receiveBuf.resize(4096);
bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
if (foundCrLf && bufPos == 2) {
// The previous read gave us the CRLF from the previous chunk. As bufPos includes
// the trailing CRLF it has to be > 2 to possibly include the next chunksize.
bufPos = 0;
foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
}
if (!foundCrLf) {
kDebug(7113) << "Failed to read chunk header.";
return -1;
}
Q_ASSERT(bufPos > 2);
long long nextChunkSize = STRTOLL(m_receiveBuf.data(), 0, 16);
if (nextChunkSize < 0)
{
kDebug(7113) << "Negative chunk size";
return -1;
}
m_iBytesLeft = nextChunkSize;
kDebug(7113) << "Chunk size =" << m_iBytesLeft << "bytes";
if (m_iBytesLeft == 0)
{
// Last chunk; read and discard chunk trailer.
// The last trailer line ends with CRLF and is followed by another CRLF
// so we have CRLFCRLF like at the end of a standard HTTP header.
// Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes.
//NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over.
char trash[4096];
trash[0] = m_receiveBuf.constData()[bufPos - 2];
trash[1] = m_receiveBuf.constData()[bufPos - 1];
int trashBufPos = 2;
bool done = false;
while (!done && !m_isEOF) {
if (trashBufPos > 3) {
// shift everything but the last three bytes out of the buffer
for (int i = 0; i < 3; i++) {
trash[i] = trash[trashBufPos - 3 + i];
}
trashBufPos = 3;
}
done = readDelimitedText(trash, &trashBufPos, 4096, 2);
}
if (m_isEOF && !done) {
kDebug(7113) << "Failed to read chunk trailer.";
return -1;
}
return 0;
}
}
int bytesReceived = readLimited();
if (!m_iBytesLeft) {
m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
}
return bytesReceived;
}
int HTTPProtocol::readLimited()
{
if (!m_iBytesLeft)
return 0;
m_receiveBuf.resize(4096);
int bytesToReceive;
if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size()))
bytesToReceive = m_receiveBuf.size();
else
bytesToReceive = m_iBytesLeft;
const int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive, false);
if (bytesReceived <= 0)
return -1; // Error: connection lost
m_iBytesLeft -= bytesReceived;
return bytesReceived;
}
int HTTPProtocol::readUnlimited()
{
if (m_request.isKeepAlive)
{
kDebug(7113) << "Unbounded datastream on a Keep-alive connection!";
m_request.isKeepAlive = false;
}
m_receiveBuf.resize(4096);
int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size());
if (result > 0)
return result;
m_isEOF = true;
m_iBytesLeft = 0;
return 0;
}
void HTTPProtocol::slotData(const QByteArray &_d)
{
if (!_d.size())
{
m_isEOD = true;
return;
}
if (m_iContentLeft != NO_SIZE)
{
if (m_iContentLeft >= KIO::filesize_t(_d.size()))
m_iContentLeft -= _d.size();
else
m_iContentLeft = NO_SIZE;
}
QByteArray d = _d;
if ( !m_dataInternal )
{
// If a broken server does not send the mime-type,
// we try to id it from the content before dealing
// with the content itself.
if ( m_mimeType.isEmpty() && !m_isRedirection &&
!( m_request.responseCode >= 300 && m_request.responseCode <=399) )
{
kDebug(7113) << "Determining mime-type from content...";
int old_size = m_mimeTypeBuffer.size();
m_mimeTypeBuffer.resize( old_size + d.size() );
memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
&& (m_mimeTypeBuffer.size() < 1024) )
{
m_cpMimeBuffer = true;
return; // Do not send up the data since we do not yet know its mimetype!
}
kDebug(7113) << "Mimetype buffer size:" << m_mimeTypeBuffer.size();
KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_request.url.fileName(), m_mimeTypeBuffer);
if( mime && !mime->isDefault() )
{
m_mimeType = mime->name();
kDebug(7113) << "Mimetype from content:" << m_mimeType;
}
if ( m_mimeType.isEmpty() )
{
m_mimeType = QLatin1String( DEFAULT_MIME_TYPE );
kDebug(7113) << "Using default mimetype:" << m_mimeType;
}
//### we could also open the cache file here
if ( m_cpMimeBuffer )
{
d.resize(0);
d.resize(m_mimeTypeBuffer.size());
memcpy(d.data(), m_mimeTypeBuffer.data(), d.size());
}
mimeType(m_mimeType);
m_mimeTypeBuffer.resize(0);
}
//kDebug(7113) << "Sending data of size" << d.size();
data( d );
if (m_request.cacheTag.ioMode == WriteToCache) {
cacheFileWritePayload(d);
}
}
else
{
uint old_size = m_webDavDataBuf.size();
m_webDavDataBuf.resize (old_size + d.size());
memcpy (m_webDavDataBuf.data() + old_size, d.data(), d.size());
}
}
/**
* This function is our "receive" function. It is responsible for
* downloading the message (not the header) from the HTTP server. It
* is called either as a response to a client's KIOJob::dataEnd()
* (meaning that the client is done sending data) or by 'sendQuery()'
* (if we are in the process of a PUT/POST request). It can also be
* called by a webDAV function, to receive stat/list/property/etc.
* data; in this case the data is stored in m_webDavDataBuf.
*/
bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
{
// Security check against bogus username intended to fool the user into
// visiting a site they did not meant to.
- if ((!m_request.url.user().isEmpty() && m_request.responseCode != 401) ||
- (!m_request.proxyUrl.user().isEmpty() && m_request.responseCode != 407)) {
+ if ((!m_request.url.user().isEmpty() && m_request.prevResponseCode != 401 && m_request.responseCode != 401) ||
+ (!m_request.proxyUrl.user().isEmpty() && m_request.prevResponseCode != 407 && m_request.responseCode != 407)) {
const int result = messageBox(WarningYesNo,
i18nc("@warning: Security check on url "
"being accessed", "You are about to "
"log in to the site \"%1\" with the "
"username \"%2\", but the website "
"does not require authentication. "
"This may be an attempt to trick you."
"<p>Is \"%1\" the site you want to visit?",
m_request.url.host(), m_request.url.user()),
i18nc("@title:window", "Confirm Website Access"));
if (result == KMessageBox::No) {
error(ERR_USER_CANCELED, m_request.url.url());
return false;
}
}
// special case for reading cached body since we also do it in this function. oh well.
if (!canHaveResponseBody(m_request.responseCode, m_request.method) &&
!(m_request.cacheTag.ioMode == ReadFromCache && m_request.responseCode == 304 &&
m_request.method != HTTP_HEAD)) {
return true;
}
m_isEOD = false;
// Note that when dataInternal is true, we are going to:
// 1) save the body data to a member variable, m_webDavDataBuf
// 2) _not_ advertise the data, speed, size, etc., through the
// corresponding functions.
// This is used for returning data to WebDAV.
m_dataInternal = dataInternal;
if (dataInternal) {
m_webDavDataBuf.clear();
}
// Check if we need to decode the data.
// If we are in copy mode, then use only transfer decoding.
bool useMD5 = !m_contentMD5.isEmpty();
// Deal with the size of the file.
KIO::filesize_t sz = m_request.offset;
if ( sz )
m_iSize += sz;
if (!m_isRedirection) {
// Update the application with total size except when
// it is compressed, or when the data is to be handled
// internally (webDAV). If compressed we have to wait
// until we uncompress to find out the actual data size
if ( !dataInternal ) {
if ((m_iSize > 0) && (m_iSize != NO_SIZE)) {
totalSize(m_iSize);
infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize),
m_request.url.host()));
} else {
totalSize(0);
}
} else {
infoMessage(i18n("Retrieving from %1...", m_request.url.host()));
}
if (m_request.cacheTag.ioMode == ReadFromCache) {
kDebug(7113) << "reading data from cache...";
m_iContentLeft = NO_SIZE;
QByteArray d;
while (true) {
d = cacheFileReadPayload(MAX_IPC_SIZE);
if (d.isEmpty()) {
break;
}
slotData(d);
sz += d.size();
if (!dataInternal) {
processedSize(sz);
}
}
m_receiveBuf.resize(0);
if (!dataInternal) {
data(QByteArray());
}
return true;
}
}
if (m_iSize != NO_SIZE)
m_iBytesLeft = m_iSize - sz;
else
m_iBytesLeft = NO_SIZE;
m_iContentLeft = m_iBytesLeft;
if (m_isChunked)
m_iBytesLeft = NO_SIZE;
kDebug(7113) << KIO::number(m_iBytesLeft) << "bytes left.";
// Main incoming loop... Gather everything while we can...
m_cpMimeBuffer = false;
m_mimeTypeBuffer.resize(0);
HTTPFilterChain chain;
// redirection ignores the body
if (!m_isRedirection) {
QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
this, SLOT(slotData(const QByteArray &)));
}
QObject::connect(&chain, SIGNAL(error(const QString &)),
this, SLOT(slotFilterError(const QString &)));
// decode all of the transfer encodings
while (!m_transferEncodings.isEmpty())
{
QString enc = m_transferEncodings.takeLast();
if ( enc == QLatin1String("gzip") )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == QLatin1String("deflate") )
chain.addFilter(new HTTPFilterDeflate);
}
// From HTTP 1.1 Draft 6:
// The MD5 digest is computed based on the content of the entity-body,
// including any content-coding that has been applied, but not including
// any transfer-encoding applied to the message-body. If the message is
// received with a transfer-encoding, that encoding MUST be removed
// prior to checking the Content-MD5 value against the received entity.
HTTPFilterMD5 *md5Filter = 0;
if ( useMD5 )
{
md5Filter = new HTTPFilterMD5;
chain.addFilter(md5Filter);
}
// now decode all of the content encodings
// -- Why ?? We are not
// -- a proxy server, be a client side implementation!! The applications
// -- are capable of determinig how to extract the encoded implementation.
// WB: That's a misunderstanding. We are free to remove the encoding.
// WB: Some braindead www-servers however, give .tgz files an encoding
// WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
// WB: They shouldn't do that. We can work around that though...
while (!m_contentEncodings.isEmpty())
{
QString enc = m_contentEncodings.takeLast();
if ( enc == QLatin1String("gzip") )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == QLatin1String("deflate") )
chain.addFilter(new HTTPFilterDeflate);
}
while (!m_isEOF)
{
int bytesReceived;
if (m_isChunked)
bytesReceived = readChunked();
else if (m_iSize != NO_SIZE)
bytesReceived = readLimited();
else
bytesReceived = readUnlimited();
// make sure that this wasn't an error, first
// kDebug(7113) << "bytesReceived:"
// << (int) bytesReceived << " m_iSize:" << (int) m_iSize << " Chunked:"
// << m_isChunked << " BytesLeft:"<< (int) m_iBytesLeft;
if (bytesReceived == -1)
{
if (m_iContentLeft == 0)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
break;
}
// Oh well... log an error and bug out
kDebug(7113) << "bytesReceived==-1 sz=" << (int)sz
<< " Connection broken !";
error(ERR_CONNECTION_BROKEN, m_request.url.host());
return false;
}
// I guess that nbytes == 0 isn't an error.. but we certainly
// won't work with it!
if (bytesReceived > 0)
{
// Important: truncate the buffer to the actual size received!
// Otherwise garbage will be passed to the app
m_receiveBuf.truncate( bytesReceived );
chain.slotInput(m_receiveBuf);
if (m_isError)
return false;
sz += bytesReceived;
if (!dataInternal)
processedSize( sz );
}
m_receiveBuf.resize(0); // res
if (m_iBytesLeft && m_isEOD && !m_isChunked)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
}
if (m_iBytesLeft == 0)
{
kDebug(7113) << "EOD received! Left ="<< KIO::number(m_iBytesLeft);
break;
}
}
chain.slotInput(QByteArray()); // Flush chain.
if ( useMD5 )
{
QString calculatedMD5 = md5Filter->md5();
if ( m_contentMD5 != calculatedMD5 )
kWarning(7113) << "MD5 checksum MISMATCH! Expected:"
<< calculatedMD5 << ", Got:" << m_contentMD5;
}
// Close cache entry
if (m_iBytesLeft == 0) {
cacheFileClose(); // no-op if not necessary
}
if (sz <= 1)
{
if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
error(ERR_INTERNAL_SERVER, m_request.url.host());
return false;
} else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 &&
!isAuthenticationRequired(m_request.responseCode) &&
// If we're doing a propfind, a 404 is not an error
m_request.method != DAV_PROPFIND) {
error(ERR_DOES_NOT_EXIST, m_request.url.host());
return false;
}
}
if (!dataInternal && !m_isRedirection)
data( QByteArray() );
return true;
}
void HTTPProtocol::slotFilterError(const QString &text)
{
error(KIO::ERR_SLAVE_DEFINED, text);
}
void HTTPProtocol::error( int _err, const QString &_text )
{
httpClose(false);
if (!m_request.id.isEmpty())
{
forwardHttpResponseHeader();
sendMetaData();
}
// It's over, we don't need it anymore
clearPostDataBuffer();
SlaveBase::error( _err, _text );
m_isError = true;
}
void HTTPProtocol::addCookies( const QString &url, const QByteArray &cookieHeader )
{
qlonglong windowId = m_request.windowId.toLongLong();
QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
(void)kcookiejar.call( QDBus::NoBlock, QLatin1String("addCookies"), url,
cookieHeader, windowId );
}
QString HTTPProtocol::findCookies( const QString &url)
{
qlonglong windowId = m_request.windowId.toLongLong();
QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
QDBusReply<QString> reply = kcookiejar.call( QLatin1String("findCookies"), url, windowId );
if ( !reply.isValid() )
{
kWarning(7113) << "Can't communicate with kded_kcookiejar!";
return QString();
}
return reply;
}
/******************************* CACHING CODE ****************************/
HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(time_t maxCacheAge) const
{
//notable omission: we're not checking cache file presence or integrity
switch (policy) {
case KIO::CC_Refresh:
// Conditional GET requires the presence of either an ETag or
// last modified date.
if (lastModifiedDate != -1 || !etag.isEmpty()) {
return ValidateCached;
}
break;
case KIO::CC_Reload:
return IgnoreCached;
case KIO::CC_CacheOnly:
case KIO::CC_Cache:
return UseCached;
default:
break;
}
Q_ASSERT((policy == CC_Verify || policy == CC_Refresh));
time_t currentDate = time(0);
if ((servedDate != -1 && currentDate > (servedDate + maxCacheAge)) ||
(expireDate != -1 && currentDate > expireDate)) {
return ValidateCached;
}
return UseCached;
}
// !START SYNC!
// The following code should be kept in sync
// with the code in http_cache_cleaner.cpp
// we use QDataStream; this is just an illustration
struct BinaryCacheFileHeader
{
quint8 version[2];
quint8 compression; // for now fixed to 0
quint8 reserved; // for now; also alignment
qint32 useCount;
qint64 servedDate;
qint64 lastModifiedDate;
qint64 expireDate;
qint32 bytesCached;
// packed size should be 36 bytes; we explicitly set it here to make sure that no compiler
// padding ruins it. We write the fields to disk without any padding.
static const int size = 36;
};
enum CacheCleanerCommandCode {
InvalidCommand = 0,
CreateFileNotificationCommand,
UpdateFileCommand
};
// illustration for cache cleaner update "commands"
struct CacheCleanerCommand
{
BinaryCacheFileHeader header;
quint32 commandCode;
// filename in ASCII, binary isn't worth the coding and decoding
quint8 filename[s_hashedUrlNibbles];
};
QByteArray HTTPProtocol::CacheTag::serialize() const
{
QByteArray ret;
QDataStream stream(&ret, QIODevice::WriteOnly);
stream << quint8('A');
stream << quint8('\n');
stream << quint8(0);
stream << quint8(0);
stream << fileUseCount;
// time_t overflow will only be checked when reading; we have no way to tell here.
stream << qint64(servedDate);
stream << qint64(lastModifiedDate);
stream << qint64(expireDate);
stream << bytesCached;
Q_ASSERT(ret.size() == BinaryCacheFileHeader::size);
return ret;
}
static bool compareByte(QDataStream *stream, quint8 value)
{
quint8 byte;
*stream >> byte;
return byte == value;
}
static bool readTime(QDataStream *stream, time_t *time)
{
qint64 intTime = 0;
*stream >> intTime;
*time = static_cast<time_t>(intTime);
qint64 check = static_cast<qint64>(*time);
return check == intTime;
}
// If starting a new file cacheFileWriteVariableSizeHeader() must have been called *before*
// calling this! This is to fill in the headerEnd field.
// If the file is not new headerEnd has already been read from the file and in fact the variable
// size header *may* not be rewritten because a size change would mess up the file layout.
bool HTTPProtocol::CacheTag::deserialize(const QByteArray &d)
{
if (d.size() != BinaryCacheFileHeader::size) {
return false;
}
QDataStream stream(d);
stream.setVersion(QDataStream::Qt_4_5);
bool ok = true;
ok = ok && compareByte(&stream, 'A');
ok = ok && compareByte(&stream, '\n');
ok = ok && compareByte(&stream, 0);
ok = ok && compareByte(&stream, 0);
if (!ok) {
return false;
}
stream >> fileUseCount;
// read and check for time_t overflow
ok = ok && readTime(&stream, &servedDate);
ok = ok && readTime(&stream, &lastModifiedDate);
ok = ok && readTime(&stream, &expireDate);
if (!ok) {
return false;
}
stream >> bytesCached;
return true;
}
/* Text part of the header, directly following the binary first part:
URL\n
etag\n
mimetype\n
header line\n
header line\n
...
\n
*/
static KUrl storableUrl(const KUrl &url)
{
KUrl ret(url);
ret.setPassword(QString());
ret.setFragment(QString());
return ret;
}
static void writeLine(QIODevice *dev, const QByteArray &line)
{
static const char linefeed = '\n';
dev->write(line);
dev->write(&linefeed, 1);
}
void HTTPProtocol::cacheFileWriteTextHeader()
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
file->seek(BinaryCacheFileHeader::size);
writeLine(file, storableUrl(m_request.url).toEncoded());
writeLine(file, m_request.cacheTag.etag.toLatin1());
writeLine(file, m_mimeType.toLatin1());
writeLine(file, m_responseHeaders.join(QString(QLatin1Char('\n'))).toLatin1());
// join("\n") adds no \n to the end, but writeLine() does.
// Add another newline to mark the end of text.
writeLine(file, QByteArray());
}
static bool readLineChecked(QIODevice *dev, QByteArray *line)
{
*line = dev->readLine(MAX_IPC_SIZE);
// if nothing read or the line didn't fit into 8192 bytes(!)
if (line->isEmpty() || !line->endsWith('\n')) {
return false;
}
// we don't actually want the newline!
line->chop(1);
return true;
}
bool HTTPProtocol::cacheFileReadTextHeader1(const KUrl &desiredUrl)
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
QByteArray readBuf;
bool ok = readLineChecked(file, &readBuf);
if (storableUrl(desiredUrl).toEncoded() != readBuf) {
kDebug(7103) << "You have witnessed a very improbable hash collision!";
return false;
}
ok = ok && readLineChecked(file, &readBuf);
m_request.cacheTag.etag = toQString(readBuf);
return ok;
}
bool HTTPProtocol::cacheFileReadTextHeader2()
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
bool ok = true;
QByteArray readBuf;
#ifndef NDEBUG
// we assume that the URL and etag have already been read
qint64 oldPos = file->pos();
file->seek(BinaryCacheFileHeader::size);
ok = ok && readLineChecked(file, &readBuf);
ok = ok && readLineChecked(file, &readBuf);
Q_ASSERT(file->pos() == oldPos);
#endif
ok = ok && readLineChecked(file, &readBuf);
m_mimeType = toQString(readBuf);
m_responseHeaders.clear();
// read as long as no error and no empty line found
while (true) {
ok = ok && readLineChecked(file, &readBuf);
if (ok && !readBuf.isEmpty()) {
m_responseHeaders.append(toQString(readBuf));
} else {
break;
}
}
return ok; // it may still be false ;)
}
static QString filenameFromUrl(const KUrl &url)
{
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(storableUrl(url).toEncoded());
return toQString(hash.result().toHex());
}
QString HTTPProtocol::cacheFilePathFromUrl(const KUrl &url) const
{
QString filePath = m_strCacheDir;
if (!filePath.endsWith(QLatin1Char('/'))) {
filePath.append(QLatin1Char('/'));
}
filePath.append(filenameFromUrl(url));
return filePath;
}
bool HTTPProtocol::cacheFileOpenRead()
{
kDebug(7113);
QString filename = cacheFilePathFromUrl(m_request.url);
QFile *&file = m_request.cacheTag.file;
if (file) {
kDebug(7113) << "File unexpectedly open; old file is" << file->fileName()
<< "new name is" << filename;
Q_ASSERT(file->fileName() == filename);
}
Q_ASSERT(!file);
file = new QFile(filename);
if (file->open(QIODevice::ReadOnly)) {
QByteArray header = file->read(BinaryCacheFileHeader::size);
if (!m_request.cacheTag.deserialize(header)) {
kDebug(7103) << "Cache file header is invalid.";
file->close();
}
}
if (file->isOpen() && !cacheFileReadTextHeader1(m_request.url)) {
file->close();
}
if (!file->isOpen()) {
cacheFileClose();
return false;
}
return true;
}
bool HTTPProtocol::cacheFileOpenWrite()
{
kDebug(7113);
QString filename = cacheFilePathFromUrl(m_request.url);
// if we open a cache file for writing while we have a file open for reading we must have
// found out that the old cached content is obsolete, so delete the file.
QFile *&file = m_request.cacheTag.file;
if (file) {
// ensure that the file is in a known state - either open for reading or null
Q_ASSERT(!qobject_cast<QTemporaryFile *>(file));
Q_ASSERT((file->openMode() & QIODevice::WriteOnly) == 0);
Q_ASSERT(file->fileName() == filename);
kDebug(7113) << "deleting expired cache entry and recreating.";
file->remove();
delete file;
file = 0;
}
// note that QTemporaryFile will automatically append random chars to filename
file = new QTemporaryFile(filename);
file->open(QIODevice::WriteOnly);
// if we have started a new file we have not initialized some variables from disk data.
m_request.cacheTag.fileUseCount = 0; // the file has not been *read* yet
m_request.cacheTag.bytesCached = 0;
if ((file->openMode() & QIODevice::WriteOnly) == 0) {
kDebug(7113) << "Could not open file for writing:" << file->fileName()
<< "due to error" << file->error();
cacheFileClose();
return false;
}
return true;
}
static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag,
CacheCleanerCommandCode cmd)
{
QByteArray ret = cacheTag.serialize();
QDataStream stream(&ret, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_4_5);
stream.skipRawData(BinaryCacheFileHeader::size);
// append the command code
stream << quint32(cmd);
// append the filename
QString fileName = cacheTag.file->fileName();
int basenameStart = fileName.lastIndexOf(QLatin1Char('/')) + 1;
QByteArray baseName = fileName.mid(basenameStart, s_hashedUrlNibbles).toLatin1();
stream.writeRawData(baseName.constData(), baseName.size());
Q_ASSERT(ret.size() == BinaryCacheFileHeader::size + sizeof(quint32) + s_hashedUrlNibbles);
return ret;
}
//### not yet 100% sure when and when not to call this
void HTTPProtocol::cacheFileClose()
{
kDebug(7113);
QFile *&file = m_request.cacheTag.file;
if (!file) {
return;
}
m_request.cacheTag.ioMode = NoCache;
QByteArray ccCommand;
QTemporaryFile *tempFile = qobject_cast<QTemporaryFile *>(file);
if (file->openMode() & QIODevice::WriteOnly) {
Q_ASSERT(tempFile);
if (m_request.cacheTag.bytesCached && !m_isError) {
QByteArray header = m_request.cacheTag.serialize();
tempFile->seek(0);
tempFile->write(header);
ccCommand = makeCacheCleanerCommand(m_request.cacheTag, CreateFileNotificationCommand);
QString oldName = tempFile->fileName();
QString newName = oldName;
int basenameStart = newName.lastIndexOf(QLatin1Char('/')) + 1;
// remove the randomized name part added by QTemporaryFile
newName.chop(newName.length() - basenameStart - s_hashedUrlNibbles);
kDebug(7113) << "Renaming temporary file" << oldName << "to" << newName;
// on windows open files can't be renamed
tempFile->setAutoRemove(false);
delete tempFile;
file = 0;
if (!QFile::rename(oldName, newName)) {
// ### currently this hides a minor bug when force-reloading a resource. We
// should not even open a new file for writing in that case.
kDebug(7113) << "Renaming temporary file failed, deleting it instead.";
QFile::remove(oldName);
ccCommand.clear(); // we have nothing of value to tell the cache cleaner
}
} else {
// oh, we've never written payload data to the cache file.
// the temporary file is closed and removed and no proper cache entry is created.
}
} else if (file->openMode() == QIODevice::ReadOnly) {
Q_ASSERT(!tempFile);
ccCommand = makeCacheCleanerCommand(m_request.cacheTag, UpdateFileCommand);
}
delete file;
file = 0;
if (!ccCommand.isEmpty()) {
sendCacheCleanerCommand(ccCommand);
}
}
void HTTPProtocol::sendCacheCleanerCommand(const QByteArray &command)
{
kDebug(7113);
Q_ASSERT(command.size() == BinaryCacheFileHeader::size + s_hashedUrlNibbles + sizeof(quint32));
int attempts = 0;
while (m_cacheCleanerConnection.state() != QLocalSocket::ConnectedState && attempts < 6) {
if (attempts == 2) {
KToolInvocation::startServiceByDesktopPath(QLatin1String("http_cache_cleaner.desktop"));
}
QString socketFileName = KStandardDirs::locateLocal("socket", QLatin1String("kio_http_cache_cleaner"));
m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly);
m_cacheCleanerConnection.waitForConnected(1500);
attempts++;
}
if (m_cacheCleanerConnection.state() == QLocalSocket::ConnectedState) {
m_cacheCleanerConnection.write(command);
m_cacheCleanerConnection.flush();
} else {
// updating the stats is not vital, so we just give up.
kDebug(7113) << "Could not connect to cache cleaner, not updating stats of this cache file.";
}
}
QByteArray HTTPProtocol::cacheFileReadPayload(int maxLength)
{
Q_ASSERT(m_request.cacheTag.file);
Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
QByteArray ret = m_request.cacheTag.file->read(maxLength);
if (ret.isEmpty()) {
cacheFileClose();
}
return ret;
}
void HTTPProtocol::cacheFileWritePayload(const QByteArray &d)
{
if (!m_request.cacheTag.file) {
return;
}
// If the file being downloaded is so big that it exceeds the max cache size,
// do not cache it! See BR# 244215. NOTE: this can be improved upon in the
// future...
if (m_iSize >= KIO::filesize_t(m_maxCacheSize * 1024)) {
kDebug(7113) << "Caching disabled because content size is too big.";
cacheFileClose();
return;
}
Q_ASSERT(m_request.cacheTag.ioMode == WriteToCache);
Q_ASSERT(m_request.cacheTag.file->openMode() & QIODevice::WriteOnly);
if (d.isEmpty()) {
cacheFileClose();
}
//TODO: abort if file grows too big!
// write the variable length text header as soon as we start writing to the file
if (!m_request.cacheTag.bytesCached) {
cacheFileWriteTextHeader();
}
m_request.cacheTag.bytesCached += d.size();
m_request.cacheTag.file->write(d);
}
void HTTPProtocol::cachePostData(const QByteArray& data)
{
if (!m_POSTbuf) {
m_POSTbuf = createPostBufferDeviceFor(qMax(m_iPostDataSize, static_cast<KIO::filesize_t>(data.size())));
if (!m_POSTbuf)
return;
}
m_POSTbuf->write (data.constData(), data.size());
}
void HTTPProtocol::clearPostDataBuffer()
{
if (!m_POSTbuf)
return;
delete m_POSTbuf;
m_POSTbuf = 0;
}
bool HTTPProtocol::retrieveAllData()
{
if (!m_POSTbuf) {
m_POSTbuf = createPostBufferDeviceFor(s_MaxInMemPostBufSize + 1);
}
if (!m_POSTbuf) {
error (ERR_OUT_OF_MEMORY, m_request.url.host());
return false;
}
while (true) {
dataReq();
QByteArray buffer;
const int bytesRead = readData(buffer);
if (bytesRead < 0) {
error(ERR_ABORTED, m_request.url.host());
return false;
}
if (bytesRead == 0) {
break;
}
m_POSTbuf->write(buffer.constData(), buffer.size());
}
return true;
}
// The above code should be kept in sync
// with the code in http_cache_cleaner.cpp
// !END SYNC!
//************************** AUTHENTICATION CODE ********************/
QString HTTPProtocol::authenticationHeader()
{
QByteArray ret;
// If the internal meta-data "cached-www-auth" is set, then check for cached
// authentication data and preemtively send the authentication header if a
// matching one is found.
if (!m_wwwAuth && config()->readEntry("cached-www-auth", false)) {
KIO::AuthInfo authinfo;
authinfo.url = m_request.url;
authinfo.realmValue = config()->readEntry("www-auth-realm", QString());
// If no relam metadata, then make sure path matching is turned on.
authinfo.verifyPath = (authinfo.realmValue.isEmpty());
const bool useCachedAuth = (m_request.responseCode == 401 || !(config()->readEntry("no-preemptive-auth-reuse", false)));
if (useCachedAuth && checkCachedAuthentication(authinfo)) {
const QByteArray cachedChallenge = authinfo.digestInfo.toLatin1();
if (!cachedChallenge.isEmpty()) {
m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
if (m_wwwAuth) {
kDebug(7113) << "Creating WWW authentcation object from cached info";
m_wwwAuth->setChallenge(cachedChallenge, m_request.url, m_request.methodString());
m_wwwAuth->generateResponse(authinfo.username, authinfo.password);
}
}
}
}
// If the internal meta-data "cached-proxy-auth" is set, then check for cached
// authentication data and preemtively send the authentication header if a
// matching one is found.
if (!m_proxyAuth && config()->readEntry("cached-proxy-auth", false)) {
KIO::AuthInfo authinfo;
authinfo.url = m_request.proxyUrl;
authinfo.realmValue = config()->readEntry("proxy-auth-realm", QString());
// If no relam metadata, then make sure path matching is turned on.
authinfo.verifyPath = (authinfo.realmValue.isEmpty());
if (checkCachedAuthentication(authinfo)) {
const QByteArray cachedChallenge = authinfo.digestInfo.toLatin1();
if (!cachedChallenge.isEmpty()) {
m_proxyAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
if (m_proxyAuth) {
kDebug(7113) << "Creating Proxy authentcation object from cached info";
m_proxyAuth->setChallenge(cachedChallenge, m_request.proxyUrl, m_request.methodString());
m_proxyAuth->generateResponse(authinfo.username, authinfo.password);
}
}
}
}
// the authentication classes don't know if they are for proxy or webserver authentication...
if (m_wwwAuth && !m_wwwAuth->isError()) {
ret += "Authorization: ";
ret += m_wwwAuth->headerFragment();
}
if (m_proxyAuth && !m_proxyAuth->isError()) {
ret += "Proxy-Authorization: ";
ret += m_proxyAuth->headerFragment();
}
return toQString(ret); // ## encoding ok?
}
void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator)
{
Q_UNUSED(proxy);
kDebug(7113) << "Authenticator received -- realm:" << authenticator->realm() << "user:"
<< authenticator->user();
AuthInfo info;
Q_ASSERT(proxy.hostName() == m_request.proxyUrl.host() && proxy.port() == m_request.proxyUrl.port());
info.url = m_request.proxyUrl;
info.realmValue = authenticator->realm();
info.verifyPath = true; //### whatever
info.username = authenticator->user();
const bool haveCachedCredentials = checkCachedAuthentication(info);
// if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
// and it was not successful. see below and saveProxyAuthenticationForSocket().
if (!haveCachedCredentials || m_socketProxyAuth) {
// Save authentication info if the connection succeeds. We need to disconnect
// this after saving the auth data (or an error) so we won't save garbage afterwards!
connect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
//### fillPromptInfo(&info);
info.prompt = i18n("You need to supply a username and a password for "
"the proxy server listed below before you are allowed "
"to access any sites.");
info.keepPassword = true;
info.commentLabel = i18n("Proxy:");
info.comment = i18n("<b>%1</b> at <b>%2</b>", info.realmValue, m_request.proxyUrl.host());
const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
if (!dataEntered) {
kDebug(7103) << "looks like the user canceled proxy authentication.";
error(ERR_USER_CANCELED, m_request.proxyUrl.host());
}
}
authenticator->setUser(info.username);
authenticator->setPassword(info.password);
if (m_socketProxyAuth) {
*m_socketProxyAuth = *authenticator;
} else {
m_socketProxyAuth = new QAuthenticator(*authenticator);
}
m_request.proxyUrl.setUser(info.username);
m_request.proxyUrl.setPassword(info.password);
}
void HTTPProtocol::saveProxyAuthenticationForSocket()
{
kDebug(7113) << "Saving authenticator";
disconnect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
Q_ASSERT(m_socketProxyAuth);
if (m_socketProxyAuth) {
kDebug(7113) << "-- realm:" << m_socketProxyAuth->realm() << "user:"
<< m_socketProxyAuth->user();
KIO::AuthInfo a;
a.verifyPath = true;
a.url = m_request.proxyUrl;
a.realmValue = m_socketProxyAuth->realm();
a.username = m_socketProxyAuth->user();
a.password = m_socketProxyAuth->password();
cacheAuthentication(a);
}
delete m_socketProxyAuth;
m_socketProxyAuth = 0;
}
#include "http.moc"
diff --git a/nepomuk/query/comparisonterm.cpp b/nepomuk/query/comparisonterm.cpp
index 8b1a0fc70c..fb5d4eb054 100644
--- a/nepomuk/query/comparisonterm.cpp
+++ b/nepomuk/query/comparisonterm.cpp
@@ -1,523 +1,552 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2009-2010 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "comparisonterm.h"
#include "comparisonterm_p.h"
#include "querybuilderdata_p.h"
#include "literalterm.h"
#include "literalterm_p.h"
#include "resourceterm.h"
#include "resource.h"
#include "query_p.h"
#include <Soprano/LiteralValue>
#include <Soprano/Node>
#include <Soprano/Vocabulary/RDFS>
#include "literal.h"
#include "class.h"
#include <kdebug.h>
namespace {
QString varInAggregateFunction( Nepomuk::Query::ComparisonTerm::AggregateFunction f, const QString& varName )
{
switch( f ) {
case Nepomuk::Query::ComparisonTerm::Count:
return QString::fromLatin1("count(%1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::DistinctCount:
return QString::fromLatin1("count(distinct %1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::Max:
return QString::fromLatin1("max(%1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::Min:
return QString::fromLatin1("min(%1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::Sum:
return QString::fromLatin1("sum(%1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::DistinctSum:
return QString::fromLatin1("sum(distinct %1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::Average:
return QString::fromLatin1("avg(%1)").arg(varName);
case Nepomuk::Query::ComparisonTerm::DistinctAverage:
return QString::fromLatin1("avg(distinct %1)").arg(varName);
default:
return QString();
}
}
}
QString Nepomuk::Query::comparatorToString( Nepomuk::Query::ComparisonTerm::Comparator c )
{
switch( c ) {
case Nepomuk::Query::ComparisonTerm::Contains:
return QChar( ':' );
case Nepomuk::Query::ComparisonTerm::Equal:
return QChar( '=' );
case Nepomuk::Query::ComparisonTerm::Regexp:
return QLatin1String( "regex" );
case Nepomuk::Query::ComparisonTerm::Greater:
return QChar( '>' );
case Nepomuk::Query::ComparisonTerm::Smaller:
return QChar( '<' );
case Nepomuk::Query::ComparisonTerm::GreaterOrEqual:
return QLatin1String( ">=" );
case Nepomuk::Query::ComparisonTerm::SmallerOrEqual:
return QLatin1String( "<=" );
default:
return QString();
}
}
Nepomuk::Query::ComparisonTerm::Comparator Nepomuk::Query::stringToComparator( const QStringRef& c )
{
if( c == QChar( '=' ) )
return Nepomuk::Query::ComparisonTerm::Equal;
else if( c == QLatin1String( "regex" ) )
return Nepomuk::Query::ComparisonTerm::Regexp;
else if( c == QChar( '>' ) )
return Nepomuk::Query::ComparisonTerm::Greater;
else if( c == QChar( '<' ) )
return Nepomuk::Query::ComparisonTerm::Smaller;
else if( c == QLatin1String( ">=" ) )
return Nepomuk::Query::ComparisonTerm::GreaterOrEqual;
else if( c == QLatin1String( "<=" ) )
return Nepomuk::Query::ComparisonTerm::SmallerOrEqual;
else
return Nepomuk::Query::ComparisonTerm::Contains;
}
QString Nepomuk::Query::ComparisonTermPrivate::toSparqlGraphPattern( const QString& resourceVarName, const TermPrivate* parentTerm, QueryBuilderData* qbd ) const
{
Q_UNUSED(parentTerm);
//
// 1. property range: literal
// 1.1. operator =
// use a single pattern like: ?r <prop> "value"
// 1.2. operator :
// use two patterns: ?r <prop> ?v . ?v bif:contains "'value'"
// 1.3. operator <,>,<=,>=
// use two patterns: ?r <prop> ?v . FILTER(?v < value)
// fail if subterm is not a literal term
//
// 2. property range is class
// 2.1. operator =
// 2.1.1. literal subterm
// use two patterns like: ?r <prop> ?v . ?v rdfs:label "value"
// 2.1.2. resource subterm
// use one pattern: ?r <prop> <res>
// 2.1.3. subterm type and, or, comparision
// use one pattern and the subpattern: ?r <prop> ?v . subpattern(?v)
// 2.2. operator :
// 2.2.1. literal subterm
// use 3 pattern: ?r <prop> ?v . ?v rdfs:label ?l . ?l bif:contains "'value'"
// 2.2.2. resource subterm
// same as =
// 2.2.3. subterm type and, or, comparision
// same as =
// 2.3. operator <,>,<=,>=
// fail!
//
if ( !m_subTerm.isValid() ) {
QString prop = propertyToString( qbd );
- QString ov = getMainVariableName( qbd );
+ bool firstUse = false;
+ QString ov = getMainVariableName( qbd, &firstUse );
if( m_inverted )
return QString::fromLatin1( "%1 %2 %3 . " )
.arg( ov, prop, resourceVarName );
- else
+ else if( firstUse )
return QString::fromLatin1( "%1 %2 %3 . " )
.arg( resourceVarName, prop, ov );
+ else
+ return QString();
}
// when using Regexp comparator with a resource range property we match the resource URI to the regular expression
else if ( m_property.literalRangeType().isValid() || m_comparator == ComparisonTerm::Regexp ) {
if( !m_subTerm.isLiteralTerm() )
kDebug() << "Incompatible subterm type:" << m_subTerm.type();
if ( m_comparator == ComparisonTerm::Equal ) {
return QString::fromLatin1( "%1 %2 %3 . " )
.arg( resourceVarName,
propertyToString( qbd ),
Soprano::Node::literalToN3( m_subTerm.toLiteralTerm().value() ) );
}
else if ( m_comparator == ComparisonTerm::Contains ) {
- const QString v = getMainVariableName(qbd);
- return QString::fromLatin1( "%1 %2 %3 . " )
- .arg( resourceVarName,
- propertyToString( qbd ),
- v )
- + LiteralTermPrivate::createContainsPattern( v, m_subTerm.toLiteralTerm().value().toString(), qbd );
+ bool firstUse = false;
+ const QString v = getMainVariableName(qbd, &firstUse);
+ QString pattern = LiteralTermPrivate::createContainsPattern( v, m_subTerm.toLiteralTerm().value().toString(), qbd );
+ if(firstUse) {
+ pattern.prepend(QString::fromLatin1( "%1 %2 %3 . " )
+ .arg( resourceVarName,
+ propertyToString( qbd ),
+ v ));
+ }
+ return pattern;
}
else if ( m_comparator == ComparisonTerm::Regexp ) {
- QString v = getMainVariableName(qbd);
- return QString::fromLatin1( "%1 %2 %3 . FILTER(REGEX(STR(%3), '%4', 'i')) . " )
- .arg( resourceVarName,
- propertyToString( qbd ),
- v,
- m_subTerm.toLiteralTerm().value().toString() );
+ bool firstUse = false;
+ const QString v = getMainVariableName(qbd, &firstUse);
+ QString pattern = QString::fromLatin1( "FILTER(REGEX(STR(%1), '%2', 'i')) . " )
+ .arg( v, m_subTerm.toLiteralTerm().value().toString() );
+ if( firstUse ) {
+ pattern.prepend(QString::fromLatin1( "%1 %2 %3 . " )
+ .arg( resourceVarName,
+ propertyToString( qbd ),
+ v ));
+ }
+ return pattern;
}
else {
- QString v = getMainVariableName(qbd);
- return QString::fromLatin1( "%1 %2 %3 . FILTER(%3%4%5) . " )
- .arg( resourceVarName,
- propertyToString( qbd ),
- v,
- comparatorToString( m_comparator ),
- Soprano::Node::literalToN3(m_subTerm.toLiteralTerm().value()) );
+ bool firstUse = false;
+ const QString v = getMainVariableName(qbd, &firstUse);
+ QString pattern = QString::fromLatin1( "FILTER(%1%2%3) . " )
+ .arg( v,
+ comparatorToString( m_comparator ),
+ Soprano::Node::literalToN3(m_subTerm.toLiteralTerm().value()) );
+ if( firstUse ) {
+ pattern.prepend( QString::fromLatin1( "%1 %2 %3 . " )
+ .arg( resourceVarName,
+ propertyToString( qbd ),
+ v ) );
+ }
+ return pattern;
}
}
else { // resource range
if( !(m_comparator == ComparisonTerm::Equal ||
m_comparator == ComparisonTerm::Contains ||
- m_comparator == ComparisonTerm::Regexp ))
+ m_comparator == ComparisonTerm::Regexp )) {
kDebug() << "Incompatible property range:" << m_property.range().uri();
+ }
//
// The core pattern is always the same: we match to resources that have a certain
// property defined. The value of that property is filled in below.
//
QString corePattern;
QString subject;
QString object;
if( m_inverted && !m_subTerm.isLiteralTerm() ) {
subject = QLatin1String("%1"); // funny way to have a resulting string which takes only one arg
object = resourceVarName;
}
else {
subject = resourceVarName;
object = QLatin1String("%1");
}
if( qbd->flags() & Query::HandleInverseProperties &&
m_property.inverseProperty().isValid() ) {
corePattern = QString::fromLatin1("{ %1 %2 %3 . } UNION { %3 %4 %1 . } . ")
.arg( subject,
propertyToString( qbd ),
object,
Soprano::Node::resourceToN3( m_property.inverseProperty().uri() ) );
}
else {
corePattern = QString::fromLatin1("%1 %2 %3 . ")
.arg( subject,
propertyToString( qbd ),
object );
}
if ( m_subTerm.isLiteralTerm() ) {
//
// the base of the pattern is always the same: match to resources related to X
// which has a label that we compare somehow. This label's value will be filled below
//
- QString v1 = getMainVariableName(qbd);
+ bool firstUse = true;
+ QString v1 = getMainVariableName(qbd, &firstUse);
QString v2 = qbd->uniqueVarName();
QString pattern = QString::fromLatin1( "%1%2 %3 %4 . %3 %5 %6 . " )
- .arg( corePattern.arg(v1),
- v1,
- v2,
- QLatin1String("%1"), // funny way to have a resulting string which takes only one arg
- Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::subPropertyOf() ), // using crappy inferencing for now
- Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::label() ) );
+ .arg( firstUse ? corePattern.arg(v1) : QString(),
+ v1,
+ v2,
+ QLatin1String("%1"), // funny way to have a resulting string which takes only one arg
+ Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::subPropertyOf() ), // using crappy inferencing for now
+ Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::label() ) );
if ( m_comparator == ComparisonTerm::Equal ) {
return pattern.arg( Soprano::Node::literalToN3( m_subTerm.toLiteralTerm().value() ) );
}
else if ( m_comparator == ComparisonTerm::Contains ) {
QString v3 = qbd->uniqueVarName();
// since this is not a "real" full text search but rather a match on resource "names" we do not call QueryBuilderData::addFullTextSearchTerm
return pattern.arg(v3)
+ LiteralTermPrivate::createContainsPattern( v3,
m_subTerm.toLiteralTerm().value().toString(),
qbd );
}
else if ( m_comparator == ComparisonTerm::Regexp ) {
QString v3 = qbd->uniqueVarName();
return QString::fromLatin1( "%1FILTER(REGEX(STR(%2)), '%3', 'i') . " )
.arg( pattern.arg(v3),
v3,
m_subTerm.toLiteralTerm().value().toString() );
}
else {
kDebug() << QString( "Invalid Term: comparator %1 cannot be used for matching to a resource!" ).arg( comparatorToString( m_comparator ) );
return QString();
}
}
else if ( m_subTerm.isResourceTerm() ) {
// ?r <prop> <res>
return corePattern.arg( Soprano::Node::resourceToN3(m_subTerm.toResourceTerm().resource().resourceUri()) );
}
else {
// ?r <prop> ?v1 . ?v1 ...
- QString v = getMainVariableName(qbd);
+ bool firstUse = true;
+ QString v = getMainVariableName(qbd, &firstUse);
qbd->increaseDepth();
QString subTermSparql = m_subTerm.d_ptr->toSparqlGraphPattern( v, this, qbd );
qbd->decreaseDepth();
- return corePattern.arg(v) + subTermSparql;
+ if( firstUse )
+ return corePattern.arg(v) + subTermSparql;
+ else
+ return subTermSparql;
}
}
}
bool Nepomuk::Query::ComparisonTermPrivate::equals( const TermPrivate* other ) const
{
if ( other->m_type == m_type ) {
const ComparisonTermPrivate* ctp = static_cast<const ComparisonTermPrivate*>( other );
return( ctp->m_property == m_property &&
ctp->m_comparator == m_comparator &&
ctp->m_subTerm == m_subTerm &&
ctp->m_inverted == m_inverted &&
ctp->m_sortOrder == m_sortOrder &&
ctp->m_sortWeight == m_sortWeight &&
ctp->m_variableName == m_variableName );
}
else {
return false;
}
}
bool Nepomuk::Query::ComparisonTermPrivate::isValid() const
{
// an invalid property will simply match all properties
// and an invalid subterm is a wildcard, too
// Thus, a ComparisonTerm is always valid
return true;
}
//
// Determines the main variable name, i.e. the variable representing the compared value, i.e. the one
// that can be set vie setVariableName.
//
// Thus, the basic scheme is: if a variable name has been set via setVariableName, return that name,
// otherwise create a random new one.
//
// But this method also handles AggregateFunction and sorting. The latter is a bit hacky.
//
// There a quite a few cases that are handled:
//
// 1. No variable name set
// 1.1 no aggregate function set
// 1.1.1 no sorting weight set
// -> return a new random variable
// 1.1.2 sorting weight set
// -> determine a new random variable, use it as sorting var (via QueryBuilderData::addOrderVariable),
// and return it
// 1.2 an aggregate function has been set
// 1.2.1 sorting weight set (no sorting weight is useless and behaves exactly as if no aggregate function was set)
// -> embed a new random variable in the aggregate expression, add that as sort variable, and
// return the new variable
// 2. A variable name has been set
// 2.1 no aggregate function set
// 2.1.1 no sorting weight set
// -> add the variable name as custom variable via QueryBuilderData::addCustomVariable and return the variable name
// 2.1.2 sorting weight set
// -> add the variable name as sort car via QueryBuilderData::addOrderVariable and return it
// 2.2 an aggregate function has been set
// 2.2.1 no sorting weight set
// -> Create a new random variable, embed it in the aggregate expression with the set variable name,
// use that expression as custom variable (this is the hacky part), and return the random one
// 2.2.2 sorting weight set
// -> Do the same as above only also add the set variable name as sort variable via QueryBuilderData::addOrderVariable
//
-QString Nepomuk::Query::ComparisonTermPrivate::getMainVariableName( QueryBuilderData* qbd ) const
+QString Nepomuk::Query::ComparisonTermPrivate::getMainVariableName( QueryBuilderData* qbd, bool* firstUse ) const
{
QString v;
QString sortVar;
if( !m_variableName.isEmpty() ) {
qbd->registerVarName( m_property, QLatin1String("?") + m_variableName );
+ *firstUse = true;
sortVar = QLatin1String("?") + m_variableName;
if( m_aggregateFunction == ComparisonTerm::NoAggregateFunction ) {
v = sortVar;
qbd->addCustomVariable( v );
}
else {
// this is a bit hacky as far as the method naming in QueryBuilderData is concerned.
// we add a select statement as a variable name.
v = qbd->uniqueVarName();
QString selectVar = QString::fromLatin1( "%1 as ?%2")
.arg(varInAggregateFunction(m_aggregateFunction, v),
m_variableName );
qbd->addCustomVariable( selectVar );
}
}
else {
- v = qbd->uniqueVarName( m_property );
+ //
+ // for inverted terms we do not perform variable sharing as described in QueryBuilderData::uniqueVarName
+ // since we would have to check inverse properties and their cardinality and the former is rarely defined.
+ //
+ v = qbd->uniqueVarName( m_inverted ? Types::Property() : m_property, firstUse );
if( m_aggregateFunction == ComparisonTerm::NoAggregateFunction )
sortVar = v;
else
sortVar = varInAggregateFunction(m_aggregateFunction, v);
}
if( m_sortWeight != 0 ) {
qbd->addOrderVariable( sortVar, m_sortWeight, m_sortOrder );
// trueg: there seems to be a bug in Virtuoso which gives wrong search order if the sort variable is not in the select list.
if( m_aggregateFunction == ComparisonTerm::NoAggregateFunction )
qbd->addCustomVariable( sortVar );
}
return v;
}
QString Nepomuk::Query::ComparisonTermPrivate::propertyToString( QueryBuilderData* qbd ) const
{
if( m_property.isValid() )
return Soprano::Node::resourceToN3( m_property.uri() );
else
return qbd->uniqueVarName();
}
Nepomuk::Query::ComparisonTerm::ComparisonTerm()
: SimpleTerm( new ComparisonTermPrivate() )
{
}
Nepomuk::Query::ComparisonTerm::ComparisonTerm( const ComparisonTerm& term )
: SimpleTerm( term )
{
}
Nepomuk::Query::ComparisonTerm::ComparisonTerm( const Types::Property& property, const Term& term, Comparator comparator )
: SimpleTerm( new ComparisonTermPrivate() )
{
N_D( ComparisonTerm );
d->m_property = property;
d->m_subTerm = term;
d->m_comparator = comparator;
}
Nepomuk::Query::ComparisonTerm::~ComparisonTerm()
{
}
Nepomuk::Query::ComparisonTerm& Nepomuk::Query::ComparisonTerm::operator=( const ComparisonTerm& term )
{
d_ptr = term.d_ptr;
return *this;
}
Nepomuk::Query::ComparisonTerm::Comparator Nepomuk::Query::ComparisonTerm::comparator() const
{
N_D_CONST( ComparisonTerm );
return d->m_comparator;
}
Nepomuk::Types::Property Nepomuk::Query::ComparisonTerm::property() const
{
N_D_CONST( ComparisonTerm );
return d->m_property;
}
void Nepomuk::Query::ComparisonTerm::setComparator( Comparator comparator )
{
N_D( ComparisonTerm );
d->m_comparator = comparator;
}
void Nepomuk::Query::ComparisonTerm::setProperty( const Types::Property& property )
{
N_D( ComparisonTerm );
d->m_property = property;
}
void Nepomuk::Query::ComparisonTerm::setVariableName( const QString& name )
{
N_D( ComparisonTerm );
d->m_variableName = name;
}
QString Nepomuk::Query::ComparisonTerm::variableName() const
{
N_D_CONST( ComparisonTerm );
return d->m_variableName;
}
void Nepomuk::Query::ComparisonTerm::setAggregateFunction( AggregateFunction function )
{
N_D( ComparisonTerm );
d->m_aggregateFunction = function;
}
Nepomuk::Query::ComparisonTerm::AggregateFunction Nepomuk::Query::ComparisonTerm::aggregateFunction() const
{
N_D_CONST( ComparisonTerm );
return d->m_aggregateFunction;
}
void Nepomuk::Query::ComparisonTerm::setSortWeight( int weight, Qt::SortOrder sortOrder )
{
N_D( ComparisonTerm );
d->m_sortWeight = weight;
d->m_sortOrder = sortOrder;
}
int Nepomuk::Query::ComparisonTerm::sortWeight() const
{
N_D_CONST( ComparisonTerm );
return d->m_sortWeight;
}
Qt::SortOrder Nepomuk::Query::ComparisonTerm::sortOrder() const
{
N_D_CONST( ComparisonTerm );
return d->m_sortOrder;
}
bool Nepomuk::Query::ComparisonTerm::isInverted() const
{
N_D_CONST( ComparisonTerm );
return d->m_inverted;
}
void Nepomuk::Query::ComparisonTerm::setInverted( bool invert )
{
N_D( ComparisonTerm );
d->m_inverted = invert;
}
Nepomuk::Query::ComparisonTerm Nepomuk::Query::ComparisonTerm::inverted() const
{
ComparisonTerm ct( *this );
ct.setInverted( !isInverted() );
return ct;
}
diff --git a/nepomuk/query/comparisonterm_p.h b/nepomuk/query/comparisonterm_p.h
index df06c38280..def595e446 100644
--- a/nepomuk/query/comparisonterm_p.h
+++ b/nepomuk/query/comparisonterm_p.h
@@ -1,79 +1,79 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2009-2010 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _NEPOMUK_QUERY_COMPARISON_TERM_P_H_
#define _NEPOMUK_QUERY_COMPARISON_TERM_P_H_
#include "simpleterm_p.h"
#include "comparisonterm.h"
#include "property.h"
namespace Nepomuk {
namespace Query {
class QueryBuilderData;
class ComparisonTermPrivate : public SimpleTermPrivate
{
public:
ComparisonTermPrivate()
: m_aggregateFunction(ComparisonTerm::NoAggregateFunction),
m_sortWeight(0),
m_sortOrder(Qt::AscendingOrder),
m_inverted(false) {
m_type = Term::Comparison;
}
TermPrivate* clone() const { return new ComparisonTermPrivate( *this ); }
bool equals( const TermPrivate* other ) const;
bool isValid() const;
QString toSparqlGraphPattern( const QString& resourceVarName, const TermPrivate* parentTerm, QueryBuilderData* qbd ) const;
/**
* return m_variableName and register it with qbd
* or ask the latter to create a new variable.
*/
- QString getMainVariableName( QueryBuilderData* qbd ) const;
+ QString getMainVariableName( QueryBuilderData* qbd, bool* firstUse ) const;
/**
* return the N3 form of m_property or a new variable
* in case m_property is invalid.
*/
QString propertyToString( QueryBuilderData* qbd ) const;
Types::Property m_property;
ComparisonTerm::Comparator m_comparator;
QString m_variableName;
ComparisonTerm::AggregateFunction m_aggregateFunction;
int m_sortWeight;
Qt::SortOrder m_sortOrder;
bool m_inverted;
};
QString comparatorToString( Nepomuk::Query::ComparisonTerm::Comparator c );
Nepomuk::Query::ComparisonTerm::Comparator stringToComparator( const QStringRef& c );
}
}
#endif
diff --git a/nepomuk/query/querybuilderdata_p.h b/nepomuk/query/querybuilderdata_p.h
index 34138057df..b78ebba380 100644
--- a/nepomuk/query/querybuilderdata_p.h
+++ b/nepomuk/query/querybuilderdata_p.h
@@ -1,262 +1,268 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2009-2010 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _NEPOMUK_QUERY_QUERY_BUILDER_DATA_H_
#define _NEPOMUK_QUERY_QUERY_BUILDER_DATA_H_
#include <QtCore/QString>
#include <QtCore/QLatin1String>
#include <QtCore/QSet>
#include <QtCore/QStack>
#include "literal.h"
#include "query.h"
#include "query_p.h"
#include "groupterm_p.h"
namespace Nepomuk {
namespace Query {
class QueryBuilderData
{
private:
/// the query we are working for
const QueryPrivate* m_query;
struct OrderVariable {
OrderVariable(int w, const QString& n, Qt::SortOrder o)
: weight(w),
name(n),
sortOrder(o) {
}
int weight;
QString name;
Qt::SortOrder sortOrder;
};
/// see uniqueVarName()
struct GroupTermPropertyCache {
const GroupTermPrivate* term;
QHash<Types::Property, QString> variableNameHash;
};
/// a running counter for unique variable names
int m_varNameCnt;
/// copy of the flags as set in Query::toSparqlQuery
const Query::SparqlFlags m_flags;
/// custom variables that have been added via ComparisonTerm::setVariableName
QSet<QString> m_customVariables;
/// variables that are used for sorting set via ComparisonTerm::setSortWeight
QList<OrderVariable> m_orderVariables;
/// all full-text matching scoring variables
QHash<QString, int> m_scoreVariables;
/// The depth of a term in the query. This is only changed by ComparisonTerm
int m_depth;
/// a stack of the group terms, the top item is the group the currently handled term is in
QStack<GroupTermPropertyCache> m_groupTermStack;
/// full text search terms with depth 0 for which bif:search_excerpt will be used
QHash<QString, QStringList> m_fullTextSearchTerms;
public:
inline QueryBuilderData( const QueryPrivate* query, Query::SparqlFlags flags )
: m_query( query ),
m_varNameCnt( 0 ),
m_flags( flags ),
m_depth( 0 ) {
}
inline const QueryPrivate* query() const {
return m_query;
}
inline Query::SparqlFlags flags() const {
return m_flags;
}
inline int depth() const {
return m_depth;
}
inline void increaseDepth() {
++m_depth;
}
inline void decreaseDepth() {
--m_depth;
}
/// used by ComparisonTerm to register var names set via ComparisonTerm::setVariableName
/// to allow them to be reused in the same way auto-generated variables are reused in
/// uniqueVarName()
inline void registerVarName( const Types::Property& property, const QString& varName ) {
if( property.isValid() &&
property.maxCardinality() == 1 &&
!m_groupTermStack.isEmpty() ) {
GroupTermPropertyCache& gpc = m_groupTermStack.top();
gpc.variableNameHash[property] = varName;
}
}
/// used by different implementations of TermPrivate::toSparqlGraphPattern and Query::toSparqlQuery
///
/// we use a simple query optimization trick here that Virtuoso cannot pull itself since it does
/// not know about NRL cardinalities:
/// In one group term we can use the same variable name for comparisons on the same property with a
/// cardinality of 1. This reduces the number of match candidates for Virtuoso, thus, significantly
/// speeding up the query.
- inline QString uniqueVarName( const Types::Property& property = Types::Property() ) {
+ inline QString uniqueVarName( const Types::Property& property = Types::Property(), bool* firstUse = 0 ) {
if( property.isValid() &&
property.maxCardinality() == 1 &&
!m_groupTermStack.isEmpty() ) {
// use only one variable name for all occurrences of this property
GroupTermPropertyCache& gpc = m_groupTermStack.top();
QHash<Types::Property, QString>::const_iterator it = gpc.variableNameHash.constFind( property );
if( it == gpc.variableNameHash.constEnd() ) {
const QString v = QLatin1String( "?v" ) + QString::number( ++m_varNameCnt );
gpc.variableNameHash.insert( property, v );
+ if(firstUse)
+ *firstUse = true;
return v;
}
else {
+ if(firstUse)
+ *firstUse = false;
return *it;
}
}
else {
+ if(firstUse)
+ *firstUse = true;
return QLatin1String( "?v" ) + QString::number( ++m_varNameCnt );
}
}
/// used by ComparisonTerm to add variable names set via ComparisonTerm::setVariableName
inline void addCustomVariable( const QString& name ) {
m_customVariables << name;
}
/// used by Query::toSparqlQuery
inline QStringList customVariables() const {
return m_customVariables.toList();
}
/// used by ComparisonTerm to add sorting variables (names include the '?')
inline void addOrderVariable( const QString& name, int weight, Qt::SortOrder order ) {
int i = 0;
while( i < m_orderVariables.count() &&
m_orderVariables[i].weight > weight )
++i;
m_orderVariables.insert( i, OrderVariable( weight, name, order ) );
}
/// used by LiteralTerm and ComparisonTerm
/// states that "varName" is a value matching the given full text search terms
inline void addFullTextSearchTerms( const QString& varName, const QStringList& terms ) {
m_fullTextSearchTerms.insert( varName, terms );
}
/// used by AndTermPrivate and OrTermPrivate in toSparqlGraphPattern
inline void pushGroupTerm( const GroupTermPrivate* group ) {
GroupTermPropertyCache gpc;
gpc.term = group;
m_groupTermStack.push( gpc );
}
/// used by AndTermPrivate and OrTermPrivate in toSparqlGraphPattern
inline void popGroupTerm() {
m_groupTermStack.pop();
}
/// used by Query::toSparqlQuery
inline QString buildOrderString() const {
QList<OrderVariable> orderVars = m_orderVariables;
if( m_query->m_fullTextScoringEnabled &&
!m_fullTextSearchTerms.isEmpty() ) {
orderVars.append( OrderVariable( 0, QLatin1String("?_n_f_t_m_s_"), m_query->m_fullTextScoringSortOrder ) );
}
if( !orderVars.isEmpty() ) {
QStringList s;
Q_FOREACH( const OrderVariable& orderVar, orderVars ) {
if( orderVar.sortOrder == Qt::DescendingOrder )
s += QLatin1String("DESC ");
else
s += QLatin1String("ASC ");
s += QString::fromLatin1("( %1 )").arg(orderVar.name);
}
return QLatin1String(" ORDER BY ") + s.join( QLatin1String(" ") );
}
else {
return QString();
}
}
/// create and remember a scoring variable for full text matching
inline QString createScoringVariable() {
QString v = uniqueVarName();
m_scoreVariables.insert(v, m_depth);
return v;
}
inline QString buildScoringExpression() const {
QStringList scores;
for( QHash<QString, int>::const_iterator it = m_scoreVariables.constBegin();
it != m_scoreVariables.constEnd(); ++it ) {
const QString var = it.key();
int depth = it.value();
if( depth > 0 )
scores += QString::fromLatin1("(%1/%2)").arg(var).arg(depth+1);
else
scores += var;
}
if( !scores.isEmpty() )
return QLatin1String("max(") + scores.join(QLatin1String("+")) + QLatin1String(") as ?_n_f_t_m_s_");
else
return QString();
}
inline QString buildSearchExcerptExpression() const {
if( !m_fullTextSearchTerms.isEmpty() ) {
QStringList excerptParts;
for( QHash<QString, QStringList>::const_iterator it = m_fullTextSearchTerms.constBegin();
it != m_fullTextSearchTerms.constEnd(); ++it ) {
const QString& varName = it.key();
const QStringList& terms = it.value();
// bif:search_excerpt wants a vector of all search terms
excerptParts
<< QString::fromLatin1("bif:search_excerpt(bif:vector('%1'), %2)")
.arg( terms.join(QLatin1String("','")),
varName );
}
return QString::fromLatin1("(bif:concat(%1)) as ?_n_f_t_m_ex_")
.arg(excerptParts.join(QLatin1String(",")));
}
else {
return QString();
}
}
};
}
}
#endif
diff --git a/nepomuk/test/querytest.cpp b/nepomuk/test/querytest.cpp
index 5db20d2444..0a23a41e72 100644
--- a/nepomuk/test/querytest.cpp
+++ b/nepomuk/test/querytest.cpp
@@ -1,664 +1,664 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2009-2010 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "querytest.h"
#include "qtest_querytostring.h"
#include "query.h"
#include "filequery.h"
#include "literalterm.h"
#include "resourceterm.h"
#include "andterm.h"
#include "orterm.h"
#include "negationterm.h"
#include "comparisonterm.h"
#include "resourcetypeterm.h"
#include "optionalterm.h"
#include "nie.h"
#include "nfo.h"
#include "nco.h"
#include "pimo.h"
#include "property.h"
#include "variant.h"
#include "resource.h"
#include <QtTest>
#include <Soprano/LiteralValue>
#include <Soprano/Node>
#include <Soprano/Vocabulary/NAO>
#include <Soprano/Vocabulary/RDFS>
#include <Soprano/Vocabulary/RDF>
#include <Soprano/Vocabulary/XMLSchema>
#include <kdebug.h>
#include <qtest_kde.h>
Q_DECLARE_METATYPE( Nepomuk::Query::Query )
using namespace Nepomuk::Query;
using namespace Nepomuk::Vocabulary;
// this is a tricky one as we nee to match the variable names and order of the queries exactly.
void QueryTest::testToSparql_data()
{
QTest::addColumn<Nepomuk::Query::Query>( "query" );
QTest::addColumn<QString>( "queryString" );
Query simpleLiteralQuery( LiteralTerm( "Hello" ) );
simpleLiteralQuery.setFullTextScoringEnabled( true );
QTest::newRow( "simple literal query" )
<< simpleLiteralQuery
<< QString::fromLatin1( "select distinct ?r max(?v5) as ?_n_f_t_m_s_ where { { ?r ?v1 ?v3 . ?v3 bif:contains \"'Hello'\" OPTION (score ?v5) . } "
"UNION { ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . ?v3 bif:contains \"'Hello'\" OPTION (score ?v5) . } . } ORDER BY DESC ( ?_n_f_t_m_s_ )" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
QTest::newRow( "simple literal query with space" )
<< Query( LiteralTerm( "Hello World" ) )
<< QString::fromLatin1( "select distinct ?r where { { ?r ?v1 ?v3 . FILTER(bif:contains(?v3, \"'Hello' AND 'World'\")) . } "
"UNION "
"{ ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . FILTER(bif:contains(?v3, \"'Hello' AND 'World'\")) . } . }" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
const QString helloWorldQuery = QString::fromLatin1( "select distinct ?r where { { ?r ?v1 ?v3 . FILTER(bif:contains(?v3, \"'Hello World'\")) . } "
"UNION "
"{ ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . FILTER(bif:contains(?v3, \"'Hello World'\")) . } . }" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
QTest::newRow( "simple literal query with space and quotes" )
<< Query( LiteralTerm( "'Hello World'" ) )
<< helloWorldQuery;
QTest::newRow( "simple literal query with space and double quotes" )
<< Query( LiteralTerm( "\"Hello World\"" ) )
<< helloWorldQuery;
QTest::newRow( "simple literal query with wildcard 1" )
<< Query( LiteralTerm( "Hello*" ) )
<< QString::fromLatin1( "select distinct ?r where { { ?r ?v1 ?v3 . FILTER(bif:contains(?v3, \"'Hello*'\")) . } "
"UNION "
"{ ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . FILTER(bif:contains(?v3, \"'Hello*'\")) . } . }" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
QTest::newRow( "simple literal query with wildcard 2" )
<< Query( LiteralTerm( "*Hello" ) )
<< QString::fromLatin1( "select distinct ?r where { { ?r ?v1 ?v3 . FILTER(REGEX(?v3, \".*Hello\")) . } "
"UNION "
"{ ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . FILTER(REGEX(?v3, \".*Hello\")) . } . }" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
QTest::newRow( "simple literal query with wildcard 3" )
<< Query( LiteralTerm( "Hel?o" ) )
<< QString::fromLatin1( "select distinct ?r where { { ?r ?v1 ?v3 . FILTER(REGEX(?v3, \"Hel.o\")) . } "
"UNION "
"{ ?r ?v1 ?v4 . ?v4 ?v2 ?v3 . ?v2 %1 %2 . FILTER(REGEX(?v3, \"Hel.o\")) . } . }" )
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()) );
Query literalQueryWithDepth2(
AndTerm( LiteralTerm("foo"),
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
ComparisonTerm( Soprano::Vocabulary::NAO::prefLabel(), LiteralTerm("bar") ) ) ) );
literalQueryWithDepth2.setFullTextScoringEnabled( true );
literalQueryWithDepth2.setFullTextScoringSortOrder( Qt::DescendingOrder );
QTest::newRow( "literal query with depth 2" )
<< literalQueryWithDepth2
<< QString::fromLatin1("select distinct ?r max((?v8/2)+?v5) as ?_n_f_t_m_s_ where { "
"{ { ?r ?v1 ?v2 . ?v2 bif:contains \"'foo'\" OPTION (score ?v5) . } "
"UNION "
"{ ?r ?v1 ?v3 . ?v3 ?v4 ?v2 . "
"?v4 <http://www.w3.org/2000/01/rdf-schema#subPropertyOf> <http://www.w3.org/2000/01/rdf-schema#label> . ?v2 bif:contains \"'foo'\" OPTION (score ?v5) . } . "
"?r <http://www.semanticdesktop.org/ontologies/2007/08/15/nao#hasTag> ?v6 . ?v6 <http://www.semanticdesktop.org/ontologies/2007/08/15/nao#prefLabel> ?v7 . "
"?v7 bif:contains \"'bar'\" OPTION (score ?v8) . } . } ORDER BY DESC ( ?_n_f_t_m_s_ )");
QTest::newRow( "type query" )
<< Query( ResourceTypeTerm( Soprano::Vocabulary::NAO::Tag() ) )
<< QString::fromLatin1("select distinct ?r where { ?r a %1 . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::Tag()));
QTest::newRow( "negated resource type" )
<< Query( NegationTerm::negateTerm( ResourceTypeTerm( Soprano::Vocabulary::NAO::Tag() ) ) )
<< QString::fromLatin1("select distinct ?r where { ?r a ?v1 . FILTER(!bif:exists((select (1) where { ?r a %1 . }))) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::Tag()));
QDateTime now = QDateTime::currentDateTime();
QTest::newRow( "nie:lastModified" )
<< Query( ComparisonTerm( Nepomuk::Vocabulary::NIE::lastModified(), LiteralTerm( now ), ComparisonTerm::GreaterOrEqual ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . FILTER(?v1>=%2) . }")
.arg(Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NIE::lastModified()),
Soprano::Node::literalToN3(now));
QTest::newRow( "hastag with literal term" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), LiteralTerm( QLatin1String("nepomuk")) ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . ?v1 ?v2 ?v3 . ?v2 %2 %3 . FILTER(bif:contains(?v3, \"'nepomuk'\")) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()))
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()))
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()));
QTest::newRow( "hastag with resource" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm( QUrl("nepomuk:/res/foobar") ) ))
<< QString::fromLatin1("select distinct ?r where { ?r %1 <nepomuk:/res/foobar> . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "negated hastag with resource" )
<< Query( NegationTerm::negateTerm(ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm( QUrl("nepomuk:/res/foobar") ) )))
<< QString::fromLatin1("select distinct ?r where { ?r a ?v1 . FILTER(!bif:exists((select (1) where { ?r %1 <nepomuk:/res/foobar> . }))) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "comparators <" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Smaller ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . FILTER(?v1<\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
QTest::newRow( "comparators <=" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::SmallerOrEqual ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . FILTER(?v1<=\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
QTest::newRow( "comparators >" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Greater ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . FILTER(?v1>\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
QTest::newRow( "comparators >=" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::GreaterOrEqual ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . FILTER(?v1>=\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
QTest::newRow( "inverted comparisonterm" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm( QUrl("nepomuk:/res/foobar") ) ).inverted() )
<< QString::fromLatin1("select distinct ?r where { <nepomuk:/res/foobar> %1 ?r . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "inverted comparisonterm 2" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), Term() ).inverted() )
<< QString::fromLatin1("select distinct ?r where { ?v1 %1 ?r . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "optional term" )
<< Query(OptionalTerm::optionalizeTerm(ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm( QUrl("nepomuk:/res/foobar") ) )))
<< QString::fromLatin1("select distinct ?r where { OPTIONAL { ?r %1 <nepomuk:/res/foobar> . } }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "and term" )
<< Query( AndTerm( ComparisonTerm( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Greater ),
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm(QUrl("nepomuk:/test")) ) ) )
<< QString::fromLatin1("select distinct ?r where { { ?r %1 ?v1 . FILTER(?v1>\"4\"^^%2) . ?r %3 <nepomuk:/test> . } . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
ComparisonTerm setVarNameTerm1( Soprano::Vocabulary::NAO::hasTag(), ResourceTypeTerm( Soprano::Vocabulary::NAO::Tag() ) );
setVarNameTerm1.setVariableName("myvar");
QTest::newRow( "set variable name 1" )
<< Query( setVarNameTerm1 )
<< QString::fromLatin1("select distinct ?r ?myvar where { ?r %1 ?myvar . ?myvar a %2 . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::Tag()));
ComparisonTerm setVarNameTerm2( Soprano::Vocabulary::NAO::hasTag(), LiteralTerm( "nepomuk" ) );
setVarNameTerm2.setVariableName( "myvar" );
QTest::newRow( "set variable name 2" )
<< Query( setVarNameTerm2 )
<< QString::fromLatin1("select distinct ?r ?myvar where { ?r %1 ?myvar . ?myvar ?v1 ?v2 . ?v1 %2 %3 . FILTER(bif:contains(?v2, \"'nepomuk'\")) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::label()));
ComparisonTerm setVarNameTerm3( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Smaller );
setVarNameTerm3.setVariableName("myvar");
QTest::newRow( "set variable name 3" )
<< Query( setVarNameTerm3 )
<< QString::fromLatin1("select distinct ?r ?myvar where { ?r %1 ?myvar . FILTER(?myvar<\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
ComparisonTerm setVarNameTerm4( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Smaller );
setVarNameTerm3.setVariableName("myvar" );
setVarNameTerm3.setAggregateFunction(ComparisonTerm::Count);
QTest::newRow( "set variable name 4 (with aggregate function count)" )
<< Query( setVarNameTerm3 )
<< QString::fromLatin1("select distinct ?r count(?v1) as ?myvar where { ?r %1 ?v1 . FILTER(?v1<\"4\"^^%2) . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
ComparisonTerm orderByTerm1( Soprano::Vocabulary::NAO::numericRating(), LiteralTerm(4), ComparisonTerm::Smaller );
orderByTerm1.setSortWeight( 1 );
QTest::newRow( "order by 1" )
<< Query( orderByTerm1 )
<< QString::fromLatin1("select distinct ?r ?v1 where { ?r %1 ?v1 . FILTER(?v1<\"4\"^^%2) . } ORDER BY ASC ( ?v1 )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
orderByTerm1.setSortWeight( 1, Qt::DescendingOrder );
QTest::newRow( "order by 2" )
<< Query( orderByTerm1 )
<< QString::fromLatin1("select distinct ?r ?v1 where { ?r %1 ?v1 . FILTER(?v1<\"4\"^^%2) . } ORDER BY DESC ( ?v1 )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
ComparisonTerm orderByTerm2( Soprano::Vocabulary::NAO::prefLabel(), LiteralTerm("hello") );
orderByTerm2.setSortWeight( 2 );
QTest::newRow( "order by 3" )
<< Query( AndTerm( orderByTerm1, orderByTerm2 ) )
<< QString::fromLatin1("select distinct ?r ?v1 ?v2 where { { ?r %1 ?v1 . FILTER(?v1<\"4\"^^%2) . ?r %3 ?v2 . FILTER(bif:contains(?v2, \"'hello'\")) . } . } ORDER BY ASC ( ?v2 ) DESC ( ?v1 )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::prefLabel()) );
orderByTerm1.setVariableName("myvar");
QTest::newRow( "order by 4" )
<< Query( orderByTerm1 )
<< QString::fromLatin1("select distinct ?r ?myvar where { ?r %1 ?myvar . FILTER(?myvar<\"4\"^^%2) . } ORDER BY DESC ( ?myvar )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()),
Soprano::Node::resourceToN3(Soprano::Vocabulary::XMLSchema::xsdInt()) );
QTest::newRow( "ComparisonTerm with invalid property" )
<< Query( ComparisonTerm( Nepomuk::Types::Property(), ResourceTerm( QUrl("nepomuk:/res/foobar") ) ))
<< QString::fromLatin1("select distinct ?r where { ?r ?v1 <nepomuk:/res/foobar> . }");
QTest::newRow( "ComparisonTerm with invalid subterm" )
<< Query( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), Term() ) )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . }")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()));
QTest::newRow( "ComparisonTerm with invalid property and subterm" )
<< Query( ComparisonTerm( Nepomuk::Types::Property(), Term() ) )
<< QString::fromLatin1("select distinct ?r where { ?r ?v1 ?v2 . }");
ComparisonTerm orderByTerm5( Soprano::Vocabulary::NAO::numericRating(), Term() );
orderByTerm5.setSortWeight( 1 );
orderByTerm5.setAggregateFunction( ComparisonTerm::Max );
QTest::newRow( "order by 5 (with aggregate function and invalid subterm)" )
<< Query( orderByTerm5 )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . } ORDER BY ASC ( max(?v1) )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()) );
orderByTerm5.setVariableName( "myvar" );
QTest::newRow( "order by 5 (with aggregate function and invalid subterm and varname)" )
<< Query( orderByTerm5 )
<< QString::fromLatin1("select distinct ?r max(?v1) as ?myvar where { ?r %1 ?v1 . } ORDER BY ASC ( ?myvar )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()) );
orderByTerm5.setVariableName( QString() );
orderByTerm5.setAggregateFunction( ComparisonTerm::DistinctCount );
QTest::newRow( "order by 5 (with aggregate function and invalid subterm and varname)" )
<< Query( orderByTerm5 )
<< QString::fromLatin1("select distinct ?r where { ?r %1 ?v1 . } ORDER BY ASC ( count(distinct ?v1) )")
.arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::numericRating()) );
// the empty file query should query all files
FileQuery emptyFileQuery;
QTest::newRow( "empty file query" )
<< Query(emptyFileQuery)
<< QString::fromLatin1("select distinct ?r where { ?r a ?v1 . FILTER(?v1 in (%1,%2)) . }")
.arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Folder()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::FileDataObject()) );
FileQuery fileQuery( ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(), ResourceTerm(QUrl("nepomuk:/res/foobar")) ) );
QTest::newRow( "file query" )
<< Query(fileQuery)
<< QString::fromLatin1("select distinct ?r where { { ?r %1 <nepomuk:/res/foobar> . ?r a ?v1 . FILTER(?v1 in (%2,%3)) . } . }")
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Folder()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::FileDataObject()) );
fileQuery.setFileMode(FileQuery::QueryFiles);
QTest::newRow( "file query (only files)" )
<< Query(fileQuery)
<< QString::fromLatin1("select distinct ?r where { { ?r %1 <nepomuk:/res/foobar> . ?r a %2 . FILTER(!bif:exists((select (1) where { ?r a %3 . }))) . } . }")
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::FileDataObject()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Folder()) );
fileQuery.setFileMode(FileQuery::QueryFolders);
QTest::newRow( "file query (only folders)" )
<< Query(fileQuery)
<< QString::fromLatin1("select distinct ?r where { { ?r %1 <nepomuk:/res/foobar> . ?r a %2 . FILTER(!bif:exists((select (1) where { ?r a %3 . }))) . } . }")
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Folder()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::FileDataObject()) );
fileQuery.setFileMode(FileQuery::QueryFilesAndFolders);
fileQuery.addIncludeFolder(KUrl(QLatin1String("/home/test/includeme")));
fileQuery.addExcludeFolder(KUrl(QLatin1String("/home/test/includeme/excludeme")));
fileQuery.addRequestProperty(Query::RequestProperty(NIE::url(), false));
QTest::newRow( "file query with include folder" )
<< Query(fileQuery)
<< QString::fromLatin1("select distinct ?r ?reqProp1 where { { "
"?r %4 ?reqProp1 . "
"?r %1 <nepomuk:/res/foobar> . "
"?r a ?v1 . FILTER(?v1 in (%2,%3)) . "
- "?r %4 ?reqProp1 . FILTER(REGEX(STR(?reqProp1), '(^file:///home/test/includeme/)', 'i')) . "
- "?r %4 ?reqProp1 . FILTER(!REGEX(STR(?reqProp1), '^(file:///home/test/includeme/excludeme/)', 'i')) . } . }")
+ "FILTER(REGEX(STR(?reqProp1), '(^file:///home/test/includeme/)', 'i')) . "
+ "FILTER(!REGEX(STR(?reqProp1), '^(file:///home/test/includeme/excludeme/)', 'i')) . } . }")
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::hasTag()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Folder()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::FileDataObject()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NIE::url()) );
QTest::newRow( "Query one resource" )
<< Query(ResourceTerm(QUrl("nepomuk:/A")))
<< QString::fromLatin1("select distinct ?r where { ?r a ?v1 . FILTER(?r=<nepomuk:/A>) . }");
QTest::newRow( "Query several resources resource" )
<< Query(OrTerm(ResourceTerm(QUrl("nepomuk:/A")), ResourceTerm(QUrl("nepomuk:/B")), ResourceTerm(QUrl("nepomuk:/C"))))
<< QString::fromLatin1("select distinct ?r where { { ?r a ?v1 . FILTER(?r=<nepomuk:/A>) . } UNION { ?r a ?v2 . FILTER(?r=<nepomuk:/B>) . } UNION { ?r a ?v3 . FILTER(?r=<nepomuk:/C>) . } . }");
QTest::newRow( "Pointless but correct: Query several resources resource 2" )
<< Query(AndTerm(ResourceTerm(QUrl("nepomuk:/A")), ResourceTerm(QUrl("nepomuk:/B")), ResourceTerm(QUrl("nepomuk:/C"))))
<< QString::fromLatin1("select distinct ?r where { { ?r a ?v1 . FILTER(?r=<nepomuk:/A>) . ?r a ?v2 . FILTER(?r=<nepomuk:/B>) . ?r a ?v3 . FILTER(?r=<nepomuk:/C>) . } . }");
QTest::newRow("query several types")
<< Query(OrTerm(ResourceTypeTerm(QUrl("onto:/typeA")), ResourceTypeTerm(QUrl("onto:/typeB"))))
<< QString(QLatin1String("select distinct ?r where { ?r a ?v1 . FILTER(?v1 in (<onto:/typeA>,<onto:/typeB>)) . }"));
//
// A more complex example
//
QUrl res("nepomuk:/res/foobar");
AndTerm mainTerm;
OrTerm typeOr;
typeOr.addSubTerm( ResourceTypeTerm( Nepomuk::Vocabulary::NFO::RasterImage() ) );
typeOr.addSubTerm( ResourceTypeTerm( Nepomuk::Vocabulary::NFO::Audio() ) );
mainTerm.addSubTerm( typeOr );
mainTerm.addSubTerm( NegationTerm::negateTerm( ComparisonTerm( Nepomuk::Types::Property(), ResourceTerm( res ) ).inverted() ) );
// an empty comparisonterm results in "?r ?v1 ?v2"
ComparisonTerm ct;
// change the var name: "?r ?v1 ?cnt"
ct.setVariableName( "cnt" );
ct.setAggregateFunction( ComparisonTerm::Count );
// by default all have 0, Query::toSparqlQuery will add ORDER BY for all ComparisonTerm with sortweight != 0
ct.setSortWeight( 1, Qt::DescendingOrder );
mainTerm.addSubTerm(ct.inverted());
QString sparql = QString::fromLatin1("select distinct ?r count(?v3) as ?cnt where { { "
"?v3 ?v2 ?r . "
"?r a ?v4 . FILTER(?v4 in (%2,%1)) . "
"FILTER(!bif:exists((select (1) where { <nepomuk:/res/foobar> ?v1 ?r . }))) . "
"} . } ORDER BY DESC ( ?cnt )")
.arg(Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::RasterImage()),
Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NFO::Audio()));
QTest::newRow( "a complex one" )
<< Query( mainTerm )
<< sparql;
#if 0
// subquery to match grouding occurrences of nepomuk:/TEST
ComparisonTerm goterm( Nepomuk::Vocabulary::PIMO::groundingOccurrence(),
ResourceTerm( Nepomuk::Resource( QUrl("nepomuk:/TEST")) ) );
goterm.setInverted(true);
// combine that with only nco:PersonContacts
AndTerm pcgoterm( ResourceTypeTerm( Nepomuk::Vocabulary::NCO::PersonContact() ),
goterm );
// now look for im accounts of those grounding occurrences (pcgoterm will become the subject of this comparison,
// thus the comparison will match the im accounts)
ComparisonTerm impcgoterm( Nepomuk::Vocabulary::NCO::hasIMAccount(),
pcgoterm );
impcgoterm.setInverted(true);
// now look for all buddies of the accounts
ComparisonTerm buddyTerm( QUrl("http://kde.org/telepathy#isBuddyOf")/*Nepomuk::Vocabulary::Telepathy::isBuddyOf()*/,
impcgoterm );
// set the name of the variable (i.e. the buddies) to be able to match it later
buddyTerm.setVariableName("t");
// same comparison, other property, but use the same variable name to match them
ComparisonTerm ppterm( QUrl("http://kde.org/telepathy#publishesPresenceTo")/*Nepomuk::Vocabulary::Telepathy::publishesPresenceTo()*/,
ResourceTypeTerm( Nepomuk::Vocabulary::NCO::IMAccount() ) );
ppterm.setVariableName("t");
// combine both to complete the matching of the im account ?account
AndTerm accountTerm( ResourceTypeTerm( Nepomuk::Vocabulary::NCO::IMAccount() ),
buddyTerm,
ppterm );
// match the account and select it for the results
ComparisonTerm imaccountTerm( Nepomuk::Vocabulary::NCO::hasIMAccount(), accountTerm );
imaccountTerm.setVariableName("account");
// and finally the exclusion of those person contacts that already have a pimo person attached
ComparisonTerm personTerm( Nepomuk::Vocabulary::PIMO::groundingOccurrence(),
ResourceTypeTerm( Nepomuk::Vocabulary::PIMO::Person() ) );
personTerm.setInverted(true);
// and all combined
Query theQuery( AndTerm( ResourceTypeTerm( Nepomuk::Vocabulary::NCO::PersonContact() ),
imaccountTerm,
NegationTerm::negateTerm(personTerm)) );
QTest::newRow( "and term" )
<< theQuery
<< QString::fromLatin1("");
#endif
}
namespace {
/**
* Changes the ordering of ?vN variables in the given query string
* to always start at 1 and go up from there depending on the first
* occurrence.
*
* This allows to use an arbitrary variable naming in the tests and
* in the SPARQL query generation code.
*/
QString normalizeVariables(const QString& s) {
QList<int> variableNumbers;
QRegExp varExp(QLatin1String("\\?v(\\d+)"));
int pos = -1;
// extract all generated variables
while((pos = s.indexOf(varExp, pos+1)) >= 0) {
const int varNum = varExp.cap(1).toInt();
if( !variableNumbers.contains(varNum) ) {
variableNumbers << varNum;
}
}
// replace all generated var numbers with a normalized order
QString newQuery(s);
for(int i = 0; i < variableNumbers.count(); ++i) {
newQuery.replace(QString::fromLatin1("?v%1").arg(variableNumbers[i]), QString::fromLatin1("?$$%1").arg(i+1));
}
newQuery.replace(QLatin1String("?$$"), QLatin1String("?v"));
return newQuery;
}
}
void QueryTest::testToSparql()
{
QFETCH( Nepomuk::Query::Query, query );
QFETCH( QString, queryString );
// we test without result restrictions which always look the same anyway
query.setQueryFlags( Query::NoResultRestrictions|Query::WithoutFullTextExcerpt );
QCOMPARE( normalizeVariables(query.toSparqlQuery().simplified()), normalizeVariables(queryString) );
// test fromQueryUrl
QCOMPARE( Query::fromQueryUrl(query.toSearchUrl()), query );
QCOMPARE( Query::fromQueryUrl(query.toSearchUrl(QLatin1String("Hello World"))), query );
}
void QueryTest::testOptimization()
{
LiteralTerm literal("Hello World");
AndTerm and1;
and1.addSubTerm(literal);
QCOMPARE( Query(and1).optimized(), Query(literal) );
AndTerm and2;
and2.addSubTerm(and1);
QCOMPARE( Query(and2).optimized(), Query(literal) );
Term invalidTerm;
and2.addSubTerm(invalidTerm);
QCOMPARE( Query(and2).optimized(), Query(literal) );
and1.setSubTerms(QList<Term>() << invalidTerm);
and2.setSubTerms(QList<Term>() << and1 << literal);
QCOMPARE( Query(and2).optimized(), Query(literal) );
// make sure duplicate negations are removed
QCOMPARE( Query(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag())))
).optimized(),
Query( ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag()) ) );
// make sure more than two negations are removed
QCOMPARE( Query(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag())))))
).optimized(),
Query( ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag()) ) );
// test negation removal while keeping one
QCOMPARE( Query(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
NegationTerm::negateTerm(
ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag()))))
).optimized(),
Query( NegationTerm::negateTerm(ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag())) ) );
// make sure duplicate optionals are removed
QCOMPARE( Query(
OptionalTerm::optionalizeTerm(
OptionalTerm::optionalizeTerm(
OptionalTerm::optionalizeTerm(
ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag()))))
).optimized(),
Query( OptionalTerm::optionalizeTerm(
ResourceTypeTerm(Soprano::Vocabulary::NAO::Tag()))) );
}
void QueryTest::testLogicalOperators()
{
// test negation
ComparisonTerm ct1( Soprano::Vocabulary::NAO::hasTag(), LiteralTerm("bla") );
QCOMPARE( NegationTerm::negateTerm(ct1), !ct1 );
// test logical and
ComparisonTerm ct2( Soprano::Vocabulary::NAO::hasTag(), LiteralTerm("foo") );
LiteralTerm lt1( "bar" );
QCOMPARE( ct1 && ct2 && lt1, Term(AndTerm( ct1, ct2, lt1 )) );
// test logical or
QCOMPARE( ct1 || ct2 || lt1, Term(OrTerm( ct1, ct2, lt1 )) );
}
void QueryTest::testComparison_data()
{
QTest::addColumn<Nepomuk::Query::Query>( "q1" );
QTest::addColumn<Nepomuk::Query::Query>( "q2" );
// invalid queries should always be similar - trivial but worth checking anyway
Query q1, q2;
QTest::newRow("invalid") << q1 << q2;
// file queries with differently sorted folder lists
FileQuery fq1, fq2;
fq1.addIncludeFolder( KUrl("/tmp") );
fq1.addIncludeFolder( KUrl("/wurst") );
fq2.addIncludeFolder( KUrl("/wurst") );
fq2.addIncludeFolder( KUrl("/tmp") );
QTest::newRow("file query include sorting") << Query(fq1) << Query(fq2);
}
void QueryTest::testComparison()
{
QFETCH( Nepomuk::Query::Query, q1 );
QFETCH( Nepomuk::Query::Query, q2 );
QCOMPARE( q1, q2 );
}
void QueryTest::testTermFromProperty()
{
QCOMPARE( Term::fromProperty(Soprano::Vocabulary::NAO::hasTag(), Nepomuk::Variant( QString::fromLatin1("Hello World") )),
Term(
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
LiteralTerm( QLatin1String("Hello World") ),
ComparisonTerm::Equal ) )
);
QCOMPARE( Term::fromProperty(Soprano::Vocabulary::NAO::hasTag(), Nepomuk::Variant( 42 )),
Term(
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
LiteralTerm( 42 ),
ComparisonTerm::Equal ) )
);
Nepomuk::Resource res( QUrl("nepomuk:/res/foobar") );
QCOMPARE( Term::fromProperty(Soprano::Vocabulary::NAO::hasTag(), Nepomuk::Variant( res )),
Term(
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
ResourceTerm( res ),
ComparisonTerm::Equal ) )
);
Nepomuk::Resource res2( QUrl("nepomuk:/res/foobar2") );
QCOMPARE( Term::fromProperty(Soprano::Vocabulary::NAO::hasTag(), Nepomuk::Variant( QList<Nepomuk::Resource>() << res << res2 )),
Term(
AndTerm(
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
ResourceTerm( res ),
ComparisonTerm::Equal ),
ComparisonTerm( Soprano::Vocabulary::NAO::hasTag(),
ResourceTerm( res2 ),
ComparisonTerm::Equal ) ) )
);
}
QTEST_KDEMAIN_CORE( QueryTest )
#include "querytest.moc"
diff --git a/plasma/private/dataenginebindings_p.h b/plasma/private/dataenginebindings_p.h
index 671d7896de..a492397026 100644
--- a/plasma/private/dataenginebindings_p.h
+++ b/plasma/private/dataenginebindings_p.h
@@ -1,85 +1,83 @@
/*
* Copyright 2007 Richard J. Moore <rich@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as
* published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef DATAENGINE_H
#define DATAENGINE_H
#include <QScriptEngine>
#include <QScriptValue>
#include <QScriptValueIterator>
#include <kdebug.h>
#include <dataengine.h>
#include <service.h>
#include <servicejob.h>
using namespace Plasma;
-Q_DECLARE_METATYPE(Service*)
-Q_DECLARE_METATYPE(ServiceJob*)
Q_DECLARE_METATYPE(DataEngine::Dict)
Q_DECLARE_METATYPE(DataEngine::Data)
template <class M>
QScriptValue qScriptValueFromMap(QScriptEngine *eng, const M &map)
{
//kDebug() << "qScriptValueFromMap called";
QScriptValue obj = eng->newObject();
typename M::const_iterator begin = map.constBegin();
typename M::const_iterator end = map.constEnd();
typename M::const_iterator it;
for (it = begin; it != end; ++it) {
if (it.value().type() == QVariant::Hash) {
obj.setProperty(it.key(), qScriptValueFromMap(eng, it.value().toHash()));
} else if (it.value().type() == QVariant::Map) {
obj.setProperty(it.key(), qScriptValueFromMap(eng, it.value().toMap()));
} else {
obj.setProperty(it.key(), qScriptValueFromValue(eng, it.value()));
}
}
return obj;
}
template <class M>
void qScriptValueToMap(const QScriptValue &value, M &map)
{
//kDebug() << "qScriptValueToMap called";
QScriptValueIterator it(value);
while (it.hasNext()) {
it.next();
map[it.name()] = qscriptvalue_cast<typename M::mapped_type>(it.value());
}
}
template<typename T>
int qScriptRegisterMapMetaType(
QScriptEngine *engine,
const QScriptValue &prototype = QScriptValue()
#ifndef qdoc
, T * /* dummy */ = 0
#endif
)
{
return qScriptRegisterMetaType<T>(engine, qScriptValueFromMap, qScriptValueToMap, prototype);
}
void registerDataEngineMetaTypes(QScriptEngine *engine);
#endif // DATAENGINE_H
diff --git a/plasma/service.cpp b/plasma/service.cpp
index 7fef537dd2..633220124f 100644
--- a/plasma/service.cpp
+++ b/plasma/service.cpp
@@ -1,400 +1,416 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "service.h"
#include "private/service_p.h"
#include "private/serviceprovider_p.h"
#include "config-plasma.h"
#include <QFile>
#include <QTimer>
#include <kdebug.h>
#include <kservice.h>
#include <kservicetypetrader.h>
#include <ksharedconfig.h>
#include <kstandarddirs.h>
#include <dnssd/publicservice.h>
#include <dnssd/servicebrowser.h>
#include "configloader.h"
#include "version.h"
#include "private/configloader_p.h"
#include "private/remoteservice_p.h"
#include "private/remoteservicejob_p.h"
#include "pluginloader.h"
#include "remote/authorizationmanager_p.h"
namespace Plasma
{
Service::Service(QObject *parent)
: QObject(parent),
d(new ServicePrivate(this))
{
}
Service::Service(QObject *parent, const QVariantList &args)
: QObject(parent),
d(new ServicePrivate(this))
{
Q_UNUSED(args)
}
Service::~Service()
{
d->unpublish();
delete d;
}
Service *Service::load(const QString &name, QObject *parent)
{
QVariantList args;
return load(name, args, parent);
}
Service *Service::load(const QString &name, const QVariantList &args, QObject *parent)
{
return PluginLoader::pluginLoader()->loadService(name, args, parent);
}
Service *Service::access(const KUrl &url, QObject *parent)
{
return new RemoteService(parent, url);
}
void ServicePrivate::jobFinished(KJob *job)
{
emit q->finished(static_cast<ServiceJob*>(job));
}
void ServicePrivate::associatedWidgetDestroyed(QObject *obj)
{
associatedWidgets.remove(static_cast<QWidget*>(obj));
}
void ServicePrivate::associatedGraphicsWidgetDestroyed(QObject *obj)
{
associatedGraphicsWidgets.remove(static_cast<QGraphicsWidget*>(obj));
}
void ServicePrivate::publish(AnnouncementMethods methods, const QString &name, const KPluginInfo &metadata)
{
#ifdef ENABLE_REMOTE_WIDGETS
if (!serviceProvider) {
AuthorizationManager::self()->d->prepareForServicePublication();
serviceProvider = new ServiceProvider(name, q);
if (methods.testFlag(ZeroconfAnnouncement) &&
(DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working)) {
//TODO: dynamically pick a free port number.
publicService = new DNSSD::PublicService(name, "_plasma._tcp", 4000);
QMap<QString, QByteArray> textData;
textData["name"] = name.toUtf8();
textData["plasmoidname"] = metadata.name().toUtf8();
textData["description"] = metadata.comment().toUtf8();
textData["icon"] = metadata.icon().toUtf8();
publicService->setTextData(textData);
kDebug() << "about to publish";
publicService->publishAsync();
} else if (methods.testFlag(ZeroconfAnnouncement) &&
(DNSSD::ServiceBrowser::isAvailable() != DNSSD::ServiceBrowser::Working)) {
kDebug() << "sorry, but your zeroconf daemon doesn't seem to be running.";
}
} else {
kDebug() << "already published!";
}
#else
kWarning() << "libplasma is compiled without support for remote widgets. not publishing.";
#endif
}
void ServicePrivate::unpublish()
{
delete serviceProvider;
serviceProvider = 0;
delete publicService;
publicService = 0;
}
bool ServicePrivate::isPublished() const
{
if (serviceProvider) {
return true;
} else {
return false;
}
}
KConfigGroup ServicePrivate::dummyGroup()
{
if (!dummyConfig) {
dummyConfig = new KConfig(QString(), KConfig::SimpleConfig);
}
return KConfigGroup(dummyConfig, "DummyGroup");
}
void Service::setDestination(const QString &destination)
{
d->destination = destination;
}
QString Service::destination() const
{
return d->destination;
}
QStringList Service::operationNames() const
{
if (!d->config) {
kDebug() << "No valid operations scheme has been registered";
return QStringList();
}
return d->config->groupList();
}
KConfigGroup Service::operationDescription(const QString &operationName)
{
if (!d->config) {
kDebug() << "No valid operations scheme has been registered";
return d->dummyGroup();
}
d->config->writeConfig();
KConfigGroup params(d->config->config(), operationName);
//kDebug() << "operation" << operationName
// << "requested, has keys" << params.keyList() << "from"
// << d->config->config()->name();
return params;
}
QHash<QString, QVariant> Service::parametersFromDescription(const KConfigGroup &description)
{
QHash<QString, QVariant> params;
if (!d->config || !description.isValid()) {
return params;
}
const QString op = description.name();
foreach (const QString &key, description.keyList()) {
KConfigSkeletonItem *item = d->config->findItem(op, key);
if (item) {
params.insert(key, description.readEntry(key, item->property()));
}
}
return params;
}
ServiceJob *Service::startOperationCall(const KConfigGroup &description, QObject *parent)
{
// TODO: nested groups?
ServiceJob *job = 0;
const QString op = description.isValid() ? description.name() : QString();
RemoteService *rs = qobject_cast<RemoteService *>(this);
if (!op.isEmpty() && rs && !rs->isReady()) {
// if we have an operation, but a non-ready remote service, just let it through
kDebug() << "Remote service is not ready; queueing operation";
QHash<QString, QVariant> params;
job = createJob(op, params);
RemoteServiceJob *rsj = qobject_cast<RemoteServiceJob *>(job);
if (rsj) {
rsj->setDelayedDescription(description);
}
} else if (!d->config) {
kDebug() << "No valid operations scheme has been registered";
} else if (!op.isEmpty() && d->config->hasGroup(op)) {
if (d->disabledOperations.contains(op)) {
kDebug() << "Operation" << op << "is disabled";
} else {
QHash<QString, QVariant> params = parametersFromDescription(description);
job = createJob(op, params);
}
} else {
kDebug() << "Not a valid group!"<<d->config->groupList();
}
if (!job) {
job = new NullServiceJob(destination(), op, this);
}
job->setParent(parent ? parent : this);
connect(job, SIGNAL(finished(KJob*)), this, SLOT(jobFinished(KJob*)));
QTimer::singleShot(0, job, SLOT(autoStart()));
return job;
}
void Service::associateWidget(QWidget *widget, const QString &operation)
{
+ if (!widget) {
+ return;
+ }
+
disassociateWidget(widget);
d->associatedWidgets.insert(widget, operation);
connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(associatedWidgetDestroyed(QObject*)));
widget->setEnabled(!d->disabledOperations.contains(operation));
}
void Service::disassociateWidget(QWidget *widget)
{
+ if (!widget) {
+ return;
+ }
+
disconnect(widget, SIGNAL(destroyed(QObject*)),
this, SLOT(associatedWidgetDestroyed(QObject*)));
d->associatedWidgets.remove(widget);
}
void Service::associateWidget(QGraphicsWidget *widget, const QString &operation)
{
+ if (!widget) {
+ return;
+ }
+
disassociateWidget(widget);
d->associatedGraphicsWidgets.insert(widget, operation);
connect(widget, SIGNAL(destroyed(QObject*)),
this, SLOT(associatedGraphicsWidgetDestroyed(QObject*)));
widget->setEnabled(!d->disabledOperations.contains(operation));
}
void Service::disassociateWidget(QGraphicsWidget *widget)
{
+ if (!widget) {
+ return;
+ }
+
disconnect(widget, SIGNAL(destroyed(QObject*)),
this, SLOT(associatedGraphicsWidgetDestroyed(QObject*)));
d->associatedGraphicsWidgets.remove(widget);
}
QString Service::name() const
{
return d->name;
}
void Service::setName(const QString &name)
{
d->name = name;
// now reset the config, which may be based on our name
delete d->config;
d->config = 0;
delete d->dummyConfig;
d->dummyConfig = 0;
registerOperationsScheme();
emit serviceReady(this);
}
void Service::setOperationEnabled(const QString &operation, bool enable)
{
if (!d->config || !d->config->hasGroup(operation)) {
return;
}
if (enable) {
d->disabledOperations.remove(operation);
} else {
d->disabledOperations.insert(operation);
}
{
QHashIterator<QWidget *, QString> it(d->associatedWidgets);
while (it.hasNext()) {
it.next();
if (it.value() == operation) {
it.key()->setEnabled(enable);
}
}
}
{
QHashIterator<QGraphicsWidget *, QString> it(d->associatedGraphicsWidgets);
while (it.hasNext()) {
it.next();
if (it.value() == operation) {
it.key()->setEnabled(enable);
}
}
}
}
bool Service::isOperationEnabled(const QString &operation) const
{
return d->config && d->config->hasGroup(operation) && !d->disabledOperations.contains(operation);
}
void Service::setOperationsScheme(QIODevice *xml)
{
delete d->config;
delete d->dummyConfig;
d->dummyConfig = 0;
KSharedConfigPtr c = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
d->config = new ConfigLoader(c, xml, this);
d->config->d->setWriteDefaults(true);
emit operationsChanged();
{
QHashIterator<QWidget *, QString> it(d->associatedWidgets);
while (it.hasNext()) {
it.next();
it.key()->setEnabled(d->config->hasGroup(it.value()));
}
}
{
QHashIterator<QGraphicsWidget *, QString> it(d->associatedGraphicsWidgets);
while (it.hasNext()) {
it.next();
it.key()->setEnabled(d->config->hasGroup(it.value()));
}
}
}
void Service::registerOperationsScheme()
{
if (d->config) {
// we've already done our job. let's go home.
return;
}
if (d->name.isEmpty()) {
kDebug() << "No name found";
return;
}
const QString path = KStandardDirs::locate("data", "plasma/services/" + d->name + ".operations");
if (path.isEmpty()) {
kDebug() << "Cannot find operations description:" << d->name << ".operations";
return;
}
QFile file(path);
setOperationsScheme(&file);
}
} // namespace Plasma
#include "service.moc"
diff --git a/plasma/service.h b/plasma/service.h
index 96309b9f01..00d1ba91ae 100644
--- a/plasma/service.h
+++ b/plasma/service.h
@@ -1,329 +1,331 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PLASMA_SERVICE_H
#define PLASMA_SERVICE_H
#include <QtCore/QHash>
#include <QtCore/QObject>
#include <QtCore/QVariant>
#include <kconfiggroup.h>
#include <plasma/plasma_export.h>
#include <plasma/plasma.h>
class QGraphicsWidget;
class QIODevice;
class QWidget;
namespace Plasma
{
class ServiceJob;
class ServicePrivate;
/**
* @class Service plasma/service.h <Plasma/Service>
*
* @short This class provides a generic API for write access to settings or services.
*
* Plasma::Service allows interaction with a "destination", the definition of which
* depends on the Service itself. For a network settings Service this might be a
* profile name ("Home", "Office", "Road Warrior") while a web based Service this
* might be a username ("aseigo", "stranger65").
*
* A Service provides one or more operations, each of which provides some sort
* of interaction with the destination. Operations are described using config
* XML which is used to create a KConfig object with one group per operation.
* The group names are used as the operation names, and the defined items in
* the group are the parameters available to be set when using that operation.
*
* A service is started with a KConfigGroup (representing a ready to be serviced
* operation) and automatically deletes itself after completion and signaling
* success or failure. See KJob for more information on this part of the process.
*
* Services may either be loaded "stand alone" from plugins, or from a DataEngine
* by passing in a source name to be used as the destination.
*
* Sample use might look like:
*
* @code
* Plasma::DataEngine *twitter = dataEngine("twitter");
* Plasma::Service *service = twitter.serviceForSource("aseigo");
* KConfigGroup op = service->operationDescription("update");
* op.writeEntry("tweet", "Hacking on plasma!");
* Plasma::ServiceJob *job = service->startOperationCall(op);
* connect(job, SIGNAL(finished(KJob*)), this, SLOT(jobCompeted()));
* @endcode
*
* Please remember, the service needs to be deleted when it will no longer be
* used. This can be done manually or by these (perhaps easier) alternatives:
*
* If it is needed throughout the lifetime of the object:
* @code
* service->setParent(this);
* @endcode
*
* If the service will not be used after just one operation call, use:
* @code
* connect(job, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()));
* @endcode
*
*/
class PLASMA_EXPORT Service : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(Service)
Q_PROPERTY(QString destination READ destination WRITE setDestination)
Q_PROPERTY(QStringList operationNames READ operationNames)
Q_PROPERTY(QString name READ name)
public:
/**
* Destructor
*/
~Service();
/**
* Used to load a given service from a plugin.
*
* @param name the plugin name of the service to load
* @param args a list of arguments to supply to the service plugin when loading it
* @param parent the parent object, if any, for the service
*
* @return a Service object, guaranteed to be not null.
* @since 4.5
*/
static Service *load(const QString &name, const QVariantList &args, QObject *parent = 0);
/**
* Used to load a given service from a plugin.
*
* @param name the plugin name of the service to load
* @param parent the parent object, if any, for the service
*
* @return a Service object, guaranteed to be not null.
*/
static Service *load(const QString &name, QObject *parent = 0);
/**
* Used to access a service from an url. Always check for the signal serviceReady() that fires
* when this service is actually ready for use.
*/
static Service *access(const KUrl &url, QObject *parent = 0);
/**
* Sets the destination for this Service to operate on
*
* @arg destination specific to each Service, this sets which
* target or address for ServiceJobs to operate on
*/
Q_INVOKABLE void setDestination(const QString &destination);
/**
* @return the target destination, if any, that this service is associated with
*/
Q_INVOKABLE QString destination() const;
/**
* @return the possible operations for this profile
*/
Q_INVOKABLE QStringList operationNames() const;
/**
* Retrieves the parameters for a given operation
*
* @param operationName the operation to retrieve parameters for
* @return KConfigGroup containing the parameters
*/
Q_INVOKABLE KConfigGroup operationDescription(const QString &operationName);
/**
* Called to create a ServiceJob which is associated with a given
* operation and parameter set.
*
* @return a started ServiceJob; the consumer may connect to relevant
* signals before returning to the event loop
*/
Q_INVOKABLE ServiceJob *startOperationCall(const KConfigGroup &description, QObject *parent = 0);
/**
* Query to find if an operation is enabled or not.
*
* @param operation the name of the operation to check
* @return true if the operation is enabled, false otherwise
*/
Q_INVOKABLE bool isOperationEnabled(const QString &operation) const;
/**
* The name of this service
*/
Q_INVOKABLE QString name() const;
/**
* Assoicates a widget with an operation, which allows the service to
* automatically manage, for example, the enabled state of a widget.
*
* This will remove any previous associations the widget had with
* operations on this engine.
*
* @param widget the QWidget to associate with the service
* @param operation the operation to associate the widget with
*/
Q_INVOKABLE void associateWidget(QWidget *widget, const QString &operation);
/**
* Disassociates a widget if it has been associated with an operation
* on this service.
*
* This will not change the enabled state of the widget.
*
* @param widget the QWidget to disassociate.
*/
Q_INVOKABLE void disassociateWidget(QWidget *widget);
/**
* Assoicates a widget with an operation, which allows the service to
* automatically manage, for example, the enabled state of a widget.
*
* This will remove any previous associations the widget had with
* operations on this engine.
*
* @param widget the QGraphicsItem to associate with the service
* @param operation the operation to associate the widget with
*/
Q_INVOKABLE void associateWidget(QGraphicsWidget *widget, const QString &operation);
/**
* Disassociates a widget if it has been associated with an operation
* on this service.
*
* This will not change the enabled state of the widget.
*
* @param widget the QGraphicsWidget to disassociate.
*/
Q_INVOKABLE void disassociateWidget(QGraphicsWidget *widget);
/**
* @return a parameter map for the given description
* @arg description the configuration values to turn into the parameter map
* @since 4.4
*/
Q_INVOKABLE QHash<QString, QVariant> parametersFromDescription(const KConfigGroup &description);
Q_SIGNALS:
/**
* Emitted when a job associated with this Service completes its task
*/
void finished(Plasma::ServiceJob *job);
/**
* Emitted when the Service's operations change. For example, a
* media player service may change what operations are available
* in response to the state of the player.
*/
void operationsChanged();
/**
* Emitted when this service is ready for use
*/
void serviceReady(Plasma::Service *service);
protected:
/**
* Default constructor
*
* @arg parent the parent object for this service
*/
explicit Service(QObject *parent = 0);
/**
* Constructor for plugin loading
*/
Service(QObject *parent, const QVariantList &args);
/**
* Called when a job should be created by the Service.
*
* @param operation which operation to work on
* @param parameters the parameters set by the user for the operation
* @return a ServiceJob that can be started and monitored by the consumer
*/
virtual ServiceJob *createJob(const QString &operation,
QHash<QString, QVariant> &parameters) = 0;
/**
* By default this is based on the file in plasma/services/name.operations, but can be
* reimplented to use a different mechanism.
*
* It should result in a call to setOperationsScheme(QIODevice *);
*/
virtual void registerOperationsScheme();
/**
* Sets the XML used to define the operation schema for
* this Service.
*/
void setOperationsScheme(QIODevice *xml);
/**
* Sets the name of the Service; useful for Services not loaded from plugins,
* which use the plugin name for this.
*
* @arg name the name to use for this service
*/
void setName(const QString &name);
/**
* Enables a given service by name
*
* @param operation the name of the operation to enable or disable
* @param enable true if the operation should be enabld, false if disabled
*/
void setOperationEnabled(const QString &operation, bool enable);
private:
Q_PRIVATE_SLOT(d, void jobFinished(KJob *))
Q_PRIVATE_SLOT(d, void associatedWidgetDestroyed(QObject *))
Q_PRIVATE_SLOT(d, void associatedGraphicsWidgetDestroyed(QObject *))
ServicePrivate * const d;
friend class Applet;
friend class DataEnginePrivate;
friend class GetSource;
friend class PackagePrivate;
friend class ServiceProvider;
friend class RemoveService;
friend class PluginLoader;
};
} // namespace Plasma
+Q_DECLARE_METATYPE(Plasma::Service *)
+
/**
* Register a service when it is contained in a loadable module
*/
#define K_EXPORT_PLASMA_SERVICE(libname, classname) \
K_PLUGIN_FACTORY(factory, registerPlugin<classname>();) \
K_EXPORT_PLUGIN(factory("plasma_service_" #libname)) \
K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION)
#endif // multiple inclusion guard
diff --git a/plasma/servicejob.h b/plasma/servicejob.h
index 375ba3b5e5..edcb58dfb7 100644
--- a/plasma/servicejob.h
+++ b/plasma/servicejob.h
@@ -1,137 +1,139 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PLASMA_SERVICEJOB_H
#define PLASMA_SERVICEJOB_H
#include <QtCore/QVariant>
#include <kjob.h>
#include <kservice.h>
#include <plasma/plasma_export.h>
#include "credentials.h"
namespace Plasma
{
class ServiceJobPrivate;
/**
* @class ServiceJob plasma/servicejob.h <Plasma/ServiceJob>
*
* @short This class provides jobs for use with Plasma::Service
*
* Unlike KJob, you can do the work in start(), since Plasma::Service already
* delays the call to start() until the event loop is reached.
*
* If the job is quick enough that it is not worth reporting the progress,
* you just need to implement start() to do the task, then call emitResult()
* at the end of it. If the task does not complete successfully, you should
* set a non-zero error code with setError(int) and an error message with
* setErrorText(QString).
*
* If the job is longer (involving network access, for instance), you should
* report the progress at regular intervals. See the KJob documentation for
* information on how to do this.
*/
class PLASMA_EXPORT ServiceJob : public KJob
{
Q_OBJECT
Q_PROPERTY(QString destination READ destination)
Q_PROPERTY(QString operationName READ operationName)
Q_PROPERTY(QVariant result READ result)
public:
/**
* Default constructor
*
* @arg destination the subject that the job is acting on
* @arg operation the action that the job is performing on the @p destination
* @arg parameters the parameters of the @p action
* @arg parent the parent object for this service
*/
ServiceJob(const QString &destination, const QString &operation,
const QHash<QString, QVariant> &parameters, QObject *parent = 0);
/**
* Destructor
*/
~ServiceJob();
/**
* @return the subject that the job is acting on
*/
QString destination() const;
/**
* @return the operation the job is performing on the destination
*/
QString operationName() const;
/**
* @return the parameters for the operation
*/
QHash<QString, QVariant> parameters() const;
/**
* @return the identity of the caller of this operation
*/
Credentials identity() const;
/**
* Returns the result of the operation
*
* The result will be invalid if the job has not completed yet, or
* if the job does not have a meaningful result.
*
* Note that this should not be used to find out whether the operation
* was successful. Instead, you should check the value of error().
*
* @return the result of the operation
*/
QVariant result() const;
/**
* Default implementation of start, which simply sets the results to false.
* This makes it easy to create a "failure" job.
*/
Q_INVOKABLE virtual void start();
protected:
/**
* Sets the result for an operation.
*/
void setResult(const QVariant &result);
private:
Q_PRIVATE_SLOT(d, void autoStart())
Q_PRIVATE_SLOT(d, void preventAutoStart())
ServiceJobPrivate * const d;
friend class ServiceProvider;
friend class RemoteServiceJob;
};
} // namespace Plasma
+Q_DECLARE_METATYPE(Plasma::ServiceJob *)
+
#endif // multiple inclusion guard
diff --git a/plasma/widgets/slider.h b/plasma/widgets/slider.h
index 4c1b8d219b..a9834fd209 100644
--- a/plasma/widgets/slider.h
+++ b/plasma/widgets/slider.h
@@ -1,147 +1,147 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PLASMA_SLIDER_H
#define PLASMA_SLIDER_H
#include <QtGui/QGraphicsProxyWidget>
#include <plasma/plasma_export.h>
class QSlider;
namespace Plasma
{
class SliderPrivate;
/**
* @class Slider plasma/widgets/slider.h <Plasma/Widgets/Slider>
*
* @short Provides a plasma-themed QSlider.
*/
class PLASMA_EXPORT Slider : public QGraphicsProxyWidget
{
Q_OBJECT
Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget)
- Q_PROPERTY(int maximum READ maximum WRITE setMinimum)
+ Q_PROPERTY(int maximum READ maximum WRITE setMaximum)
Q_PROPERTY(int minimum READ minimum WRITE setMinimum)
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation)
Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet)
Q_PROPERTY(QSlider *nativeWidget READ nativeWidget)
public:
explicit Slider(QGraphicsWidget *parent = 0);
~Slider();
/**
* @return the maximum value
*/
int maximum() const;
/**
* @return the minimum value
*/
int minimum() const;
/**
* @return the current value
*/
int value() const;
/**
* @return the orientation of the slider
*/
Qt::Orientation orientation() const;
/**
* Sets the stylesheet used to control the visual display of this Slider
*
* @arg stylesheet a CSS string
*/
void setStyleSheet(const QString &stylesheet);
/**
* @return the stylesheet currently used with this widget
*/
QString styleSheet();
/**
* @return the native widget wrapped by this Slider
*/
QSlider *nativeWidget() const;
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void wheelEvent(QGraphicsSceneWheelEvent *event);
public Q_SLOTS:
/**
* Sets the maximum value the slider can take.
*/
void setMaximum(int maximum);
/**
* Sets the minimum value the slider can take.
*/
void setMinimum(int minimum);
/**
* Sets the minimum and maximum values the slider can take.
*/
void setRange(int minimum, int maximum);
/**
* Sets the value of the slider.
*
* If it is outside the range specified by minimum() and maximum(),
* it will be adjusted to fit.
*/
void setValue(int value);
/**
* Sets the orientation of the slider.
*/
void setOrientation(Qt::Orientation orientation);
Q_SIGNALS:
/**
* This signal is emitted when the user drags the slider.
*
* In fact, it is emitted whenever the sliderMoved(int) signal
* of QSlider would be emitted. See the Qt documentation for
* more information.
*/
void sliderMoved(int value);
/**
* This signal is emitted when the slider value has changed,
* with the new slider value as argument.
*/
void valueChanged(int value);
private:
SliderPrivate * const d;
};
} // namespace Plasma
#endif // multiple inclusion guard

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 8:39 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10073013
Default Alt Text
(741 KB)

Event Timeline